Merge from gnulib
[emacs.git] / lwlib / xlwmenu.c
blob9317dea02b0394edb5aba2b8ed8afd5ee2933f24
1 /* Implements a lightweight menubar widget.
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2015 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 char *z = stpcpy (complete_name, resourced_name);
443 *z++ = ' ';
444 strcpy (z, val->value);
447 val->toolkit_data = complete_name;
448 val->free_toolkit_data = True;
450 return (char*)val->toolkit_data;
453 /* Returns the sizes of an item */
454 static void
455 size_menu_item (XlwMenuWidget mw,
456 widget_value* val,
457 int horizontal_p,
458 int* label_width,
459 int* rest_width,
460 int* button_width,
461 int* height)
463 enum menu_separator separator;
465 if (lw_separator_p (val->name, &separator, 0))
467 *height = separator_height (separator);
468 *label_width = 1;
469 *rest_width = 0;
470 *button_width = 0;
472 else
474 *height = MENU_FONT_HEIGHT (mw)
475 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
477 *label_width =
478 string_width (mw, resource_widget_value (mw, val))
479 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
481 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
482 if (!horizontal_p)
484 if (val->contents)
485 /* Add width of the arrow displayed for submenus. */
486 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
487 else if (val->key)
488 /* Add width of key equivalent string. */
489 *rest_width += (string_width (mw, val->key)
490 + mw->menu.arrow_spacing);
492 if (val->button_type == BUTTON_TYPE_TOGGLE)
493 *button_width = (toggle_button_width (mw)
494 + mw->menu.horizontal_spacing);
495 else if (val->button_type == BUTTON_TYPE_RADIO)
496 *button_width = (radio_button_width (mw)
497 + mw->menu.horizontal_spacing);
502 static void
503 size_menu (XlwMenuWidget mw, int level)
505 int label_width = 0;
506 int rest_width = 0;
507 int button_width = 0;
508 int max_rest_width = 0;
509 int max_button_width = 0;
510 int height = 0;
511 int horizontal_p = mw->menu.horizontal && (level == 0);
512 widget_value* val;
513 window_state* ws;
515 if (level >= mw->menu.old_depth)
516 abort_gracefully ((Widget) mw);
518 ws = &mw->menu.windows [level];
519 ws->width = 0;
520 ws->height = 0;
521 ws->label_width = 0;
522 ws->button_width = 0;
524 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
526 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
527 &button_width, &height);
528 if (horizontal_p)
530 ws->width += label_width + rest_width;
531 if (height > ws->height)
532 ws->height = height;
534 else
536 if (label_width > ws->label_width)
537 ws->label_width = label_width;
538 if (rest_width > max_rest_width)
539 max_rest_width = rest_width;
540 if (button_width > max_button_width)
541 max_button_width = button_width;
542 ws->height += height;
546 if (horizontal_p)
547 ws->label_width = ws->button_width = 0;
548 else
550 ws->width = ws->label_width + max_rest_width + max_button_width;
551 ws->button_width = max_button_width;
554 ws->width += 2 * mw->menu.shadow_thickness;
555 ws->height += 2 * mw->menu.shadow_thickness;
556 ws->max_rest_width = max_rest_width;
558 if (horizontal_p)
560 ws->width += 2 * mw->menu.margin;
561 ws->height += 2 * mw->menu.margin;
566 \f/* Display code */
568 static void
569 draw_arrow (XlwMenuWidget mw,
570 Window window,
571 GC gc,
572 int x,
573 int y,
574 int width,
575 int down_p)
577 Display *dpy = XtDisplay (mw);
578 GC top_gc = mw->menu.shadow_top_gc;
579 GC bottom_gc = mw->menu.shadow_bottom_gc;
580 int thickness = mw->menu.shadow_thickness;
581 int height = width;
582 XPoint pt[10];
583 /* alpha = atan (0.5)
584 factor = (1 + sin (alpha)) / cos (alpha) */
585 double factor = 1.62;
586 int thickness2 = thickness * factor;
588 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
590 if (down_p)
592 GC temp;
593 temp = top_gc;
594 top_gc = bottom_gc;
595 bottom_gc = temp;
598 pt[0].x = x;
599 pt[0].y = y + height;
600 pt[1].x = x + thickness;
601 pt[1].y = y + height - thickness2;
602 pt[2].x = x + thickness2;
603 pt[2].y = y + thickness2;
604 pt[3].x = x;
605 pt[3].y = y;
606 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
608 pt[0].x = x;
609 pt[0].y = y;
610 pt[1].x = x + thickness;
611 pt[1].y = y + thickness2;
612 pt[2].x = x + width - thickness2;
613 pt[2].y = y + height / 2;
614 pt[3].x = x + width;
615 pt[3].y = y + height / 2;
616 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
618 pt[0].x = x;
619 pt[0].y = y + height;
620 pt[1].x = x + thickness;
621 pt[1].y = y + height - thickness2;
622 pt[2].x = x + width - thickness2;
623 pt[2].y = y + height / 2;
624 pt[3].x = x + width;
625 pt[3].y = y + height / 2;
626 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
631 static void
632 draw_shadow_rectangle (XlwMenuWidget mw,
633 Window window,
634 int x,
635 int y,
636 int width,
637 int height,
638 int erase_p,
639 int down_p)
641 Display *dpy = XtDisplay (mw);
642 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
643 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
644 int thickness = mw->menu.shadow_thickness;
645 XPoint points [4];
647 if (!erase_p && down_p)
649 GC temp;
650 temp = top_gc;
651 top_gc = bottom_gc;
652 bottom_gc = temp;
655 points [0].x = x;
656 points [0].y = y;
657 points [1].x = x + width;
658 points [1].y = y;
659 points [2].x = x + width - thickness;
660 points [2].y = y + thickness;
661 points [3].x = x;
662 points [3].y = y + thickness;
663 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
664 points [0].x = x;
665 points [0].y = y + thickness;
666 points [1].x = x;
667 points [1].y = y + height;
668 points [2].x = x + thickness;
669 points [2].y = y + height - thickness;
670 points [3].x = x + thickness;
671 points [3].y = y + thickness;
672 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
673 points [0].x = x + width;
674 points [0].y = y;
675 points [1].x = x + width - thickness;
676 points [1].y = y + thickness;
677 points [2].x = x + width - thickness;
678 points [2].y = y + height - thickness;
679 points [3].x = x + width;
680 points [3].y = y + height - thickness;
681 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
682 points [0].x = x;
683 points [0].y = y + height;
684 points [1].x = x + width;
685 points [1].y = y + height;
686 points [2].x = x + width;
687 points [2].y = y + height - thickness;
688 points [3].x = x + thickness;
689 points [3].y = y + height - thickness;
690 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
694 static void
695 draw_shadow_rhombus (XlwMenuWidget mw,
696 Window window,
697 int x,
698 int y,
699 int width,
700 int height,
701 int erase_p,
702 int down_p)
704 Display *dpy = XtDisplay (mw);
705 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
706 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
707 int thickness = mw->menu.shadow_thickness;
708 XPoint points [4];
710 if (!erase_p && down_p)
712 GC temp;
713 temp = top_gc;
714 top_gc = bottom_gc;
715 bottom_gc = temp;
718 points [0].x = x;
719 points [0].y = y + height / 2;
720 points [1].x = x + thickness;
721 points [1].y = y + height / 2;
722 points [2].x = x + width / 2;
723 points [2].y = y + thickness;
724 points [3].x = x + width / 2;
725 points [3].y = y;
726 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
727 points [0].x = x + width / 2;
728 points [0].y = y;
729 points [1].x = x + width / 2;
730 points [1].y = y + thickness;
731 points [2].x = x + width - thickness;
732 points [2].y = y + height / 2;
733 points [3].x = x + width;
734 points [3].y = y + height / 2;
735 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
736 points [0].x = x;
737 points [0].y = y + height / 2;
738 points [1].x = x + thickness;
739 points [1].y = y + height / 2;
740 points [2].x = x + width / 2;
741 points [2].y = y + height - thickness;
742 points [3].x = x + width / 2;
743 points [3].y = y + height;
744 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
745 points [0].x = x + width / 2;
746 points [0].y = y + height;
747 points [1].x = x + width / 2;
748 points [1].y = y + height - thickness;
749 points [2].x = x + width - thickness;
750 points [2].y = y + height / 2;
751 points [3].x = x + width;
752 points [3].y = y + height / 2;
753 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
757 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
758 top-left corner of the menu item. SELECTED_P non-zero means the
759 toggle button is selected. */
761 static void
762 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
764 int width, height;
766 width = toggle_button_width (mw);
767 height = width;
768 x += mw->menu.horizontal_spacing;
769 y += (MENU_FONT_ASCENT (mw) - height) / 2;
770 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
774 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
775 top-left corner of the menu item. SELECTED_P non-zero means the
776 toggle button is selected. */
778 static void
779 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
781 int width, height;
783 width = radio_button_width (mw);
784 height = width;
785 x += mw->menu.horizontal_spacing;
786 y += (MENU_FONT_ASCENT (mw) - height) / 2;
787 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
791 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
792 top-left corner of the menu item. WIDTH is the width of the
793 separator to draw. TYPE is the separator type. */
795 static void
796 draw_separator (XlwMenuWidget mw,
797 Window window,
798 int x,
799 int y,
800 int width,
801 enum menu_separator type)
803 Display *dpy = XtDisplay (mw);
804 XGCValues xgcv;
806 switch (type)
808 case SEPARATOR_NO_LINE:
809 break;
811 case SEPARATOR_SINGLE_LINE:
812 XDrawLine (dpy, window, mw->menu.foreground_gc,
813 x, y, x + width, y);
814 break;
816 case SEPARATOR_DOUBLE_LINE:
817 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
818 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
819 break;
821 case SEPARATOR_SINGLE_DASHED_LINE:
822 xgcv.line_style = LineOnOffDash;
823 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
824 XDrawLine (dpy, window, mw->menu.foreground_gc,
825 x, y, x + width, y);
826 xgcv.line_style = LineSolid;
827 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
828 break;
830 case SEPARATOR_DOUBLE_DASHED_LINE:
831 draw_separator (mw, window, x, y, width,
832 SEPARATOR_SINGLE_DASHED_LINE);
833 draw_separator (mw, window, x, y + 2, width,
834 SEPARATOR_SINGLE_DASHED_LINE);
835 break;
837 case SEPARATOR_SHADOW_ETCHED_IN:
838 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
839 x, y, x + width, y);
840 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
841 x, y + 1, x + width, y + 1);
842 break;
844 case SEPARATOR_SHADOW_ETCHED_OUT:
845 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
846 x, y, x + width, y);
847 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
848 x, y + 1, x + width, y + 1);
849 break;
851 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
852 xgcv.line_style = LineOnOffDash;
853 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
854 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
855 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
856 xgcv.line_style = LineSolid;
857 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
858 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
859 break;
861 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
862 xgcv.line_style = LineOnOffDash;
863 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
864 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
865 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
866 xgcv.line_style = LineSolid;
867 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
868 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
869 break;
871 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
872 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
873 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
874 break;
876 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
877 draw_separator (mw, window, x, y, width,
878 SEPARATOR_SHADOW_ETCHED_OUT);
879 draw_separator (mw, window, x, y + 3, width,
880 SEPARATOR_SHADOW_ETCHED_OUT);
881 break;
883 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
884 xgcv.line_style = LineOnOffDash;
885 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
886 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
887 draw_separator (mw, window, x, y, width,
888 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
889 xgcv.line_style = LineSolid;
890 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
891 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
892 break;
894 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
895 xgcv.line_style = LineOnOffDash;
896 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
897 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
898 draw_separator (mw, window, x, y, width,
899 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
900 xgcv.line_style = LineSolid;
901 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
902 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
903 break;
905 default:
906 abort ();
911 /* Return the pixel height of menu separator SEPARATOR. */
913 static int
914 separator_height (enum menu_separator separator)
916 switch (separator)
918 case SEPARATOR_NO_LINE:
919 return 2;
921 case SEPARATOR_SINGLE_LINE:
922 case SEPARATOR_SINGLE_DASHED_LINE:
923 return 1;
925 case SEPARATOR_DOUBLE_LINE:
926 case SEPARATOR_DOUBLE_DASHED_LINE:
927 return 3;
929 case SEPARATOR_SHADOW_ETCHED_IN:
930 case SEPARATOR_SHADOW_ETCHED_OUT:
931 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
932 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
933 return 2;
935 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
936 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
937 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
938 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
939 return 5;
941 default:
942 abort ();
947 /* Display the menu item and increment where.x and where.y to show how large
948 the menu item was. */
950 static void
951 display_menu_item (XlwMenuWidget mw,
952 widget_value* val,
953 window_state* ws,
954 XPoint* where,
955 Boolean highlighted_p,
956 Boolean horizontal_p,
957 Boolean just_compute_p)
959 GC deco_gc;
960 GC text_gc;
961 int font_height = MENU_FONT_HEIGHT (mw);
962 int font_ascent = MENU_FONT_ASCENT (mw);
963 int shadow = mw->menu.shadow_thickness;
964 int margin = mw->menu.margin;
965 int h_spacing = mw->menu.horizontal_spacing;
966 int v_spacing = mw->menu.vertical_spacing;
967 int label_width;
968 int rest_width;
969 int button_width;
970 int height;
971 int width;
972 enum menu_separator separator;
973 int separator_p = lw_separator_p (val->name, &separator, 0);
974 #ifdef HAVE_XFT
975 XftColor *xftfg;
976 #endif
978 /* compute the sizes of the item */
979 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
980 &button_width, &height);
982 if (horizontal_p)
983 width = label_width + rest_width;
984 else
986 label_width = ws->label_width;
987 width = ws->width - 2 * shadow;
990 /* Only highlight an enabled item that has a callback. */
991 if (highlighted_p)
992 if (!val->enabled || !(val->call_data || val->contents))
993 highlighted_p = 0;
995 /* do the drawing. */
996 if (!just_compute_p)
998 /* Add the shadow border of the containing menu */
999 int x = where->x + shadow;
1000 int y = where->y + shadow;
1002 if (horizontal_p)
1004 x += margin;
1005 y += margin;
1008 /* pick the foreground and background GC. */
1009 if (val->enabled)
1010 text_gc = mw->menu.foreground_gc;
1011 else
1012 text_gc = mw->menu.disabled_gc;
1013 deco_gc = mw->menu.foreground_gc;
1014 #ifdef HAVE_XFT
1015 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1016 #endif
1018 if (separator_p)
1020 draw_separator (mw, ws->pixmap, x, y, width, separator);
1022 else
1024 int x_offset = x + h_spacing + shadow;
1025 char* display_string = resource_widget_value (mw, val);
1026 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1027 False);
1029 /* Deal with centering a menu title. */
1030 if (!horizontal_p && !val->contents && !val->call_data)
1032 int l = string_width (mw, display_string);
1034 if (width > l)
1035 x_offset = (width - l) >> 1;
1037 else if (!horizontal_p && ws->button_width)
1038 x_offset += ws->button_width;
1041 #ifdef HAVE_XFT
1042 if (ws->xft_draw)
1044 int draw_y = y + v_spacing + shadow;
1045 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1046 mw->menu.xft_font,
1047 x_offset, draw_y + font_ascent,
1048 (unsigned char *) display_string,
1049 strlen (display_string));
1051 else
1052 #endif
1053 #ifdef HAVE_X_I18N
1054 if (mw->menu.fontSet)
1055 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1056 text_gc, x_offset,
1057 y + v_spacing + shadow + font_ascent,
1058 display_string, strlen (display_string));
1059 else
1060 #endif
1061 XDrawString (XtDisplay (mw), ws->pixmap,
1062 text_gc, x_offset,
1063 y + v_spacing + shadow + font_ascent,
1064 display_string, strlen (display_string));
1066 if (!horizontal_p)
1068 if (val->button_type == BUTTON_TYPE_TOGGLE)
1069 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1070 val->selected);
1071 else if (val->button_type == BUTTON_TYPE_RADIO)
1072 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1073 val->selected);
1075 if (val->contents)
1077 int a_w = arrow_width (mw);
1078 draw_arrow (mw, ws->pixmap, deco_gc,
1079 x + width - a_w
1080 - mw->menu.horizontal_spacing
1081 - mw->menu.shadow_thickness,
1082 y + v_spacing + shadow, a_w,
1083 highlighted_p);
1085 else if (val->key)
1087 #ifdef HAVE_XFT
1088 if (ws->xft_draw)
1090 int draw_x = ws->width - ws->max_rest_width
1091 + mw->menu.arrow_spacing;
1092 int draw_y = y + v_spacing + shadow + font_ascent;
1093 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1094 mw->menu.xft_font,
1095 draw_x, draw_y,
1096 (unsigned char *) val->key,
1097 strlen (val->key));
1099 else
1100 #endif
1101 #ifdef HAVE_X_I18N
1102 if (mw->menu.fontSet)
1103 XmbDrawString (XtDisplay (mw), ws->pixmap,
1104 mw->menu.fontSet,
1105 text_gc,
1106 x + label_width + mw->menu.arrow_spacing,
1107 y + v_spacing + shadow + font_ascent,
1108 val->key, strlen (val->key));
1109 else
1110 #endif
1111 XDrawString (XtDisplay (mw), ws->pixmap,
1112 text_gc,
1113 x + label_width + mw->menu.arrow_spacing,
1114 y + v_spacing + shadow + font_ascent,
1115 val->key, strlen (val->key));
1118 else
1120 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1121 mw->menu.background_gc,
1122 x + shadow, y + shadow,
1123 label_width + h_spacing - 1,
1124 font_height + 2 * v_spacing - 1);
1125 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1126 True, False);
1129 if (highlighted_p)
1130 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1131 False);
1135 where->x += width;
1136 where->y += height;
1139 static void
1140 display_menu (XlwMenuWidget mw,
1141 int level,
1142 Boolean just_compute_p,
1143 XPoint *highlighted_pos,
1144 XPoint *hit,
1145 widget_value **hit_return)
1147 widget_value* val;
1148 widget_value* following_item;
1149 window_state* ws;
1150 XPoint where;
1151 int horizontal_p = mw->menu.horizontal && (level == 0);
1152 int highlighted_p;
1153 int no_return = 0;
1154 enum menu_separator separator;
1156 if (level >= mw->menu.old_depth)
1157 abort_gracefully ((Widget) mw);
1159 if (level < mw->menu.old_depth - 1)
1160 following_item = mw->menu.old_stack [level + 1];
1161 else
1162 following_item = NULL;
1164 if (hit)
1165 *hit_return = NULL;
1167 where.x = 0;
1168 where.y = 0;
1170 ws = &mw->menu.windows [level];
1172 if (!just_compute_p)
1173 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1174 0, 0, ws->width, ws->height);
1176 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1178 highlighted_p = val == following_item;
1179 if (highlighted_p && highlighted_pos)
1181 if (horizontal_p)
1182 highlighted_pos->x = where.x;
1183 else
1184 highlighted_pos->y = where.y;
1187 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1188 just_compute_p);
1190 if (highlighted_p && highlighted_pos)
1192 if (horizontal_p)
1193 highlighted_pos->y = where.y;
1194 else
1195 highlighted_pos->x = where.x;
1198 if (hit
1199 && !*hit_return
1200 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1201 && !lw_separator_p (val->name, &separator, 0)
1202 && !no_return)
1204 if (val->enabled)
1205 *hit_return = val;
1206 else
1207 no_return = 1;
1208 if (mw->menu.inside_entry != val)
1210 if (mw->menu.inside_entry)
1211 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1212 (XtPointer) mw->menu.inside_entry);
1213 mw->menu.inside_entry = val;
1214 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1215 (XtPointer) mw->menu.inside_entry);
1219 if (horizontal_p)
1220 where.y = 0;
1221 else
1222 where.x = 0;
1225 if (!just_compute_p)
1227 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1228 False, False);
1229 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1230 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1234 \f/* Motion code */
1235 static void
1236 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1238 int i;
1240 mw->menu.new_depth = 0;
1241 for (i = 0; i < level; i++)
1242 push_new_stack (mw, mw->menu.old_stack [i]);
1243 push_new_stack (mw, val);
1246 static void
1247 expose_cb (Widget widget,
1248 XtPointer closure,
1249 XEvent* event,
1250 Boolean* continue_to_dispatch)
1252 XlwMenuWidget mw = (XlwMenuWidget) closure;
1253 int i;
1255 *continue_to_dispatch = False;
1256 for (i = 0; i < mw->menu.windows_length; ++i)
1257 if (mw->menu.windows [i].w == widget) break;
1258 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1259 display_menu (mw, i, False, NULL, NULL, NULL);
1262 static void
1263 set_window_type (Widget w, XlwMenuWidget mw)
1265 int popup_menu_p = mw->menu.top_depth == 1;
1266 Atom type = XInternAtom (XtDisplay (w),
1267 popup_menu_p
1268 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1269 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1270 False);
1272 XChangeProperty (XtDisplay (w), XtWindow (w),
1273 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1274 XA_ATOM, 32, PropModeReplace,
1275 (unsigned char *)&type, 1);
1279 static void
1280 make_windows_if_needed (XlwMenuWidget mw, int n)
1282 int i;
1283 int start_at;
1284 window_state* windows;
1286 if (mw->menu.windows_length >= n)
1287 return;
1289 if (!mw->menu.windows)
1291 mw->menu.windows =
1292 (window_state*)XtMalloc (n * sizeof (window_state));
1293 start_at = 0;
1295 else
1297 mw->menu.windows =
1298 (window_state*)XtRealloc ((char*)mw->menu.windows,
1299 n * sizeof (window_state));
1300 start_at = mw->menu.windows_length;
1302 mw->menu.windows_length = n;
1304 windows = mw->menu.windows;
1306 for (i = start_at; i < n; i++)
1308 Arg av[10];
1309 int ac = 0;
1310 windows [i].x = 0;
1311 windows [i].y = 0;
1312 windows [i].width = 1;
1313 windows [i].height = 1;
1314 windows [i].max_rest_width = 0;
1315 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1316 XtSetArg (av[ac], XtNheight, 1); ++ac;
1317 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1318 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1319 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1320 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1321 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1322 windows [i].w =
1323 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1324 (Widget) mw, av, ac);
1325 XtRealizeWidget (windows [i].w);
1326 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1327 windows [i].window = XtWindow (windows [i].w);
1328 windows [i].pixmap = None;
1329 #ifdef HAVE_XFT
1330 windows [i].xft_draw = 0;
1331 #endif
1332 set_window_type (windows [i].w, mw);
1334 XFlush (XtDisplay (mw));
1337 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1340 xlwmenu_window_p (Widget w, Window window)
1342 XlwMenuWidget mw = (XlwMenuWidget) w;
1343 int i;
1345 for (i = 0; i < mw->menu.windows_length; ++i)
1346 if (window == mw->menu.windows[i].window)
1347 break;
1349 return i < mw->menu.windows_length;
1352 /* Make the window fit in the screen */
1353 static void
1354 fit_to_screen (XlwMenuWidget mw,
1355 window_state *ws,
1356 window_state *previous_ws,
1357 Boolean horizontal_p)
1359 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1360 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1361 /* 1 if we are unable to avoid an overlap between
1362 this menu and the parent menu in the X dimension. */
1363 int horizontal_overlap = 0;
1365 if (ws->x < 0)
1366 ws->x = 0;
1367 else if (ws->x + ws->width > screen_width)
1369 if (!horizontal_p)
1370 /* The addition of shadow-thickness for a sub-menu's position is
1371 to reflect a similar adjustment when the menu is displayed to
1372 the right of the invoking menu-item; it makes the sub-menu
1373 look more `attached' to the menu-item. */
1374 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1375 else
1376 ws->x = screen_width - ws->width;
1377 if (ws->x < 0)
1379 ws->x = 0;
1380 horizontal_overlap = 1;
1383 /* If we overlap in X, try to avoid overlap in Y. */
1384 if (horizontal_overlap
1385 && ws->y < previous_ws->y + previous_ws->height
1386 && previous_ws->y < ws->y + ws->height)
1388 /* Put this menu right below or right above PREVIOUS_WS
1389 if there's room. */
1390 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1391 ws->y = previous_ws->y + previous_ws->height;
1392 else if (previous_ws->y - ws->height > 0)
1393 ws->y = previous_ws->y - ws->height;
1396 if (ws->y < 0)
1397 ws->y = 0;
1398 else if (ws->y + ws->height > screen_height)
1400 if (horizontal_p)
1401 ws->y = previous_ws->y - ws->height;
1402 else
1403 ws->y = screen_height - ws->height;
1404 if (ws->y < 0)
1405 ws->y = 0;
1409 static void
1410 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1412 if (ws->pixmap != None)
1414 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1415 ws->pixmap = None;
1417 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1418 ws->width, ws->height,
1419 DefaultDepthOfScreen (XtScreen (ws->w)));
1420 #ifdef HAVE_XFT
1421 if (ws->xft_draw)
1422 XftDrawDestroy (ws->xft_draw);
1423 if (mw->menu.xft_font)
1425 int screen = XScreenNumberOfScreen (mw->core.screen);
1426 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1427 ws->pixmap,
1428 DefaultVisual (XtDisplay (ws->w), screen),
1429 mw->core.colormap);
1431 else
1432 ws->xft_draw = 0;
1433 #endif
1436 /* Updates old_stack from new_stack and redisplays. */
1437 static void
1438 remap_menubar (XlwMenuWidget mw)
1440 int i;
1441 int last_same;
1442 XPoint selection_position;
1443 int old_depth = mw->menu.old_depth;
1444 int new_depth = mw->menu.new_depth;
1445 widget_value** old_stack;
1446 widget_value** new_stack;
1447 window_state* windows;
1448 widget_value* old_selection;
1449 widget_value* new_selection;
1451 /* Check that enough windows and old_stack are ready. */
1452 make_windows_if_needed (mw, new_depth);
1453 make_old_stack_space (mw, new_depth);
1454 windows = mw->menu.windows;
1455 old_stack = mw->menu.old_stack;
1456 new_stack = mw->menu.new_stack;
1458 /* compute the last identical different entry */
1459 for (i = 1; i < old_depth && i < new_depth; i++)
1460 if (old_stack [i] != new_stack [i])
1461 break;
1462 last_same = i - 1;
1464 /* Memorize the previously selected item to be able to refresh it */
1465 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1466 if (old_selection && !old_selection->enabled)
1467 old_selection = NULL;
1468 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1469 if (new_selection && !new_selection->enabled)
1470 new_selection = NULL;
1472 /* Call callback when the highlighted item changes. */
1473 if (old_selection || new_selection)
1474 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1475 (XtPointer) new_selection);
1477 /* updates old_state from new_state. It has to be done now because
1478 display_menu (called below) uses the old_stack to know what to display. */
1479 for (i = last_same + 1; i < new_depth; i++)
1481 XtPopdown (mw->menu.windows [i].w);
1482 old_stack [i] = new_stack [i];
1484 mw->menu.old_depth = new_depth;
1486 /* refresh the last selection */
1487 selection_position.x = 0;
1488 selection_position.y = 0;
1489 display_menu (mw, last_same, new_selection == old_selection,
1490 &selection_position, NULL, NULL);
1492 /* Now place the new menus. */
1493 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1495 window_state *previous_ws = &windows[i - 1];
1496 window_state *ws = &windows[i];
1498 ws->x = (previous_ws->x + selection_position.x
1499 + mw->menu.shadow_thickness);
1500 if (mw->menu.horizontal && i == 1)
1501 ws->x += mw->menu.margin;
1503 #if 0
1504 if (!mw->menu.horizontal || i > 1)
1505 ws->x += mw->menu.shadow_thickness;
1506 #endif
1508 ws->y = (previous_ws->y + selection_position.y
1509 + mw->menu.shadow_thickness);
1510 if (mw->menu.horizontal && i == 1)
1511 ws->y += mw->menu.margin;
1513 /* WMs like Gnome 3 ignores requests to move windows. So we
1514 must destroy the current one and create a new to get it to move. */
1515 XtUnrealizeWidget (ws->w);
1516 XtRealizeWidget (ws->w);
1517 ws->window = XtWindow (ws->w);
1519 size_menu (mw, i);
1521 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1523 create_pixmap_for_menu (ws, mw);
1524 XtConfigureWidget (ws->w, ws->x, ws->y, ws->width, ws->height,
1525 ws->w->core.border_width);
1526 display_menu (mw, i, False, &selection_position, NULL, NULL);
1527 XtPopup (ws->w, XtGrabNone);
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 && '0' <= fname[i] && fname[i] <= '9')
1862 --i;
1863 if (fname[i] == ' ')
1865 fname = xstrdup (mw->menu.fontName);
1866 fname[i] = '-';
1869 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1870 if (!mw->menu.xft_font)
1871 mw->menu.xft_font = getDefaultXftFont (mw);
1874 if (fname != mw->menu.fontName) xfree (fname);
1876 return mw->menu.xft_font != 0;
1878 #endif
1880 static void
1881 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1883 /* Get the GCs and the widget size. */
1884 XlwMenuWidget mw = (XlwMenuWidget) w;
1885 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1886 Display* display = XtDisplay (mw);
1888 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1889 mw->menu.cursor = mw->menu.cursor_shape;
1891 mw->menu.gray_pixmap
1892 = XCreatePixmapFromBitmapData (display, window, gray_bits,
1893 gray_width, gray_height,
1894 (unsigned long)1, (unsigned long)0, 1);
1896 #ifdef HAVE_XFT
1897 if (openXftFont (mw))
1899 else
1900 #endif
1902 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1903 if (!mw->menu.font)
1905 mw->menu.font = XLoadQueryFont (display, "fixed");
1906 if (!mw->menu.font)
1908 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1909 abort ();
1914 #ifdef HAVE_X_I18N
1915 if (mw->menu.fontSet)
1916 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1917 #endif
1919 make_drawing_gcs (mw);
1920 make_shadow_gcs (mw);
1922 mw->menu.popped_up = False;
1924 mw->menu.old_depth = 1;
1925 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1926 mw->menu.old_stack_length = 1;
1927 mw->menu.old_stack [0] = mw->menu.contents;
1929 mw->menu.new_depth = 0;
1930 mw->menu.new_stack = 0;
1931 mw->menu.new_stack_length = 0;
1932 push_new_stack (mw, mw->menu.contents);
1934 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1935 mw->menu.windows_length = 1;
1936 mw->menu.windows [0].x = 0;
1937 mw->menu.windows [0].y = 0;
1938 mw->menu.windows [0].width = 0;
1939 mw->menu.windows [0].height = 0;
1940 mw->menu.windows [0].max_rest_width = 0;
1941 mw->menu.windows [0].pixmap = None;
1942 #ifdef HAVE_XFT
1943 mw->menu.windows [0].xft_draw = 0;
1944 #endif
1945 size_menu (mw, 0);
1947 mw->core.width = mw->menu.windows [0].width;
1948 mw->core.height = mw->menu.windows [0].height;
1951 static void
1952 XlwMenuClassInitialize (void)
1956 static void
1957 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1959 XlwMenuWidget mw = (XlwMenuWidget)w;
1960 XSetWindowAttributes xswa;
1961 int mask;
1963 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1964 (w, valueMask, attributes);
1966 xswa.save_under = True;
1967 xswa.cursor = mw->menu.cursor_shape;
1968 mask = CWSaveUnder | CWCursor;
1969 /* I sometimes get random BadCursor errors while creating the first
1970 frame on a display. I can not find their reason, but they are
1971 annoying so for now let's ignore any errors here. -- lorentey */
1972 #ifdef emacs
1973 x_catch_errors (XtDisplay (w));
1974 #endif
1975 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1976 #ifdef emacs
1977 x_uncatch_errors ();
1978 #endif
1980 mw->menu.windows [0].w = w;
1981 mw->menu.windows [0].window = XtWindow (w);
1982 mw->menu.windows [0].x = w->core.x;
1983 mw->menu.windows [0].y = w->core.y;
1984 mw->menu.windows [0].width = w->core.width;
1985 mw->menu.windows [0].height = w->core.height;
1987 set_window_type (mw->menu.windows [0].w, mw);
1988 create_pixmap_for_menu (&mw->menu.windows [0], mw);
1990 #ifdef HAVE_XFT
1991 if (mw->menu.xft_font)
1993 XColor colors[3];
1994 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
1995 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
1996 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
1997 = mw->menu.disabled_foreground;
1998 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
1999 mw->menu.xft_fg.color.alpha = 0xFFFF;
2000 mw->menu.xft_fg.color.red = colors[0].red;
2001 mw->menu.xft_fg.color.green = colors[0].green;
2002 mw->menu.xft_fg.color.blue = colors[0].blue;
2003 mw->menu.xft_bg.color.alpha = 0xFFFF;
2004 mw->menu.xft_bg.color.red = colors[1].red;
2005 mw->menu.xft_bg.color.green = colors[1].green;
2006 mw->menu.xft_bg.color.blue = colors[1].blue;
2007 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2008 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2009 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2010 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2012 #endif
2015 /* Only the toplevel menubar/popup is a widget so it's the only one that
2016 receives expose events through Xt. So we repaint all the other panes
2017 when receiving an Expose event. */
2018 static void
2019 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2021 XlwMenuWidget mw = (XlwMenuWidget)w;
2023 /* If we have a depth beyond 1, it's because a submenu was displayed.
2024 If the submenu has been destroyed, set the depth back to 1. */
2025 if (submenu_destroyed)
2027 mw->menu.old_depth = 1;
2028 submenu_destroyed = 0;
2031 display_menu (mw, 0, False, NULL, NULL, NULL);
2035 /* Part of a hack to make the menu redisplay when a tooltip frame
2036 over a menu item is unmapped. */
2038 void
2039 xlwmenu_redisplay (Widget w)
2041 XlwMenuRedisplay (w, NULL, None);
2044 static void
2045 XlwMenuDestroy (Widget w)
2047 int i;
2048 XlwMenuWidget mw = (XlwMenuWidget) w;
2050 if (pointer_grabbed)
2051 ungrab_all ((Widget)w, CurrentTime);
2052 pointer_grabbed = 0;
2054 submenu_destroyed = 1;
2056 release_drawing_gcs (mw);
2057 release_shadow_gcs (mw);
2059 /* This doesn't come from the resource db but is created explicitly
2060 so we must free it ourselves. */
2061 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2062 mw->menu.gray_pixmap = (Pixmap) -1;
2064 /* Don't free mw->menu.contents because that comes from our creator.
2065 The `*_stack' elements are just pointers into `contents' so leave
2066 that alone too. But free the stacks themselves. */
2067 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2068 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2070 /* Original comment was:
2072 Remember, you can't free anything that came from the resource
2073 database. This includes:
2074 mw->menu.cursor
2075 mw->menu.top_shadow_pixmap
2076 mw->menu.bottom_shadow_pixmap
2077 mw->menu.font
2078 Also the color cells of top_shadow_color, bottom_shadow_color,
2079 foreground, and button_foreground will never be freed until this
2080 client exits. Nice, eh?
2082 But now I can free font without any visible glitches. */
2084 if (mw->menu.font)
2085 XFreeFont (XtDisplay (mw), mw->menu.font);
2087 #ifdef HAVE_XFT
2088 if (mw->menu.windows [0].xft_draw)
2089 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2090 if (mw->menu.xft_font)
2091 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2092 #endif
2094 if (mw->menu.windows [0].pixmap != None)
2095 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2096 /* Start from 1 because the one in slot 0 is w->core.window. */
2097 for (i = 1; i < mw->menu.windows_length; i++)
2099 if (mw->menu.windows [i].pixmap != None)
2100 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2101 #ifdef HAVE_XFT
2102 if (mw->menu.windows [i].xft_draw)
2103 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2104 #endif
2107 if (mw->menu.windows)
2108 XtFree ((char *) mw->menu.windows);
2111 #ifdef HAVE_XFT
2112 static int
2113 fontname_changed (XlwMenuWidget newmw,
2114 XlwMenuWidget oldmw)
2116 /* This will force a new XftFont even if the same string is set.
2117 This is good, as rendering parameters may have changed and
2118 we just want to do a redisplay. */
2119 return newmw->menu.fontName != oldmw->menu.fontName;
2121 #endif
2123 static Boolean
2124 XlwMenuSetValues (Widget current, Widget request, Widget new,
2125 ArgList args, Cardinal *num_args)
2127 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2128 XlwMenuWidget newmw = (XlwMenuWidget)new;
2129 Boolean do_redisplay = False;
2131 if (newmw->menu.contents
2132 && newmw->menu.contents->contents
2133 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2134 do_redisplay = True;
2135 /* Do redisplay if the contents are entirely eliminated. */
2136 if (newmw->menu.contents
2137 && newmw->menu.contents->contents == 0
2138 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2139 do_redisplay = True;
2141 if (newmw->core.background_pixel != oldmw->core.background_pixel
2142 || newmw->menu.foreground != oldmw->menu.foreground
2143 #ifdef HAVE_XFT
2144 || fontname_changed (newmw, oldmw)
2145 #endif
2146 #ifdef HAVE_X_I18N
2147 || newmw->menu.fontSet != oldmw->menu.fontSet
2148 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2149 #else
2150 || newmw->menu.font != oldmw->menu.font
2151 #endif
2154 int i;
2155 release_drawing_gcs (newmw);
2156 make_drawing_gcs (newmw);
2158 release_shadow_gcs (newmw);
2159 /* Cause the shadow colors to be recalculated. */
2160 newmw->menu.top_shadow_color = -1;
2161 newmw->menu.bottom_shadow_color = -1;
2162 make_shadow_gcs (newmw);
2164 do_redisplay = True;
2166 if (XtIsRealized (current))
2167 /* If the menu is currently displayed, change the display. */
2168 for (i = 0; i < oldmw->menu.windows_length; i++)
2170 XSetWindowBackground (XtDisplay (oldmw),
2171 oldmw->menu.windows [i].window,
2172 newmw->core.background_pixel);
2173 /* Clear windows and generate expose events. */
2174 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2175 0, 0, 0, 0, True);
2179 #ifdef HAVE_XFT
2180 if (fontname_changed (newmw, oldmw))
2182 int i;
2183 int screen = XScreenNumberOfScreen (newmw->core.screen);
2184 if (newmw->menu.xft_font)
2185 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2186 openXftFont (newmw);
2187 for (i = 0; i < newmw->menu.windows_length; i++)
2189 if (newmw->menu.windows [i].xft_draw)
2190 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2191 newmw->menu.windows [i].xft_draw = 0;
2193 if (newmw->menu.xft_font)
2194 for (i = 0; i < newmw->menu.windows_length; i++)
2195 newmw->menu.windows [i].xft_draw
2196 = XftDrawCreate (XtDisplay (newmw),
2197 newmw->menu.windows [i].window,
2198 DefaultVisual (XtDisplay (newmw), screen),
2199 newmw->core.colormap);
2201 #endif
2202 #ifdef HAVE_X_I18N
2203 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2205 do_redisplay = True;
2206 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2208 #endif
2210 return do_redisplay;
2213 static void
2214 XlwMenuResize (Widget w)
2216 XlwMenuWidget mw = (XlwMenuWidget)w;
2218 if (mw->menu.popped_up)
2220 /* Don't allow the popup menu to resize itself. */
2221 mw->core.width = mw->menu.windows [0].width;
2222 mw->core.height = mw->menu.windows [0].height;
2223 mw->core.parent->core.width = mw->core.width;
2224 mw->core.parent->core.height = mw->core.height;
2226 else
2228 mw->menu.windows [0].width = mw->core.width;
2229 mw->menu.windows [0].height = mw->core.height;
2230 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2234 \f/* Action procedures */
2235 static void
2236 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2238 widget_value* val;
2239 int level;
2241 if (!map_event_to_widget_value (mw, ev, &val, &level))
2242 pop_new_stack_if_no_contents (mw);
2243 else
2244 set_new_state (mw, val, level);
2245 remap_menubar (mw);
2247 /* Sync with the display. Makes it feel better on X terms. */
2248 XSync (XtDisplay (mw), False);
2251 static void
2252 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2254 int x = ev->x_root;
2255 int y = ev->y_root;
2256 int state = ev->state;
2257 XMotionEvent oldev = *ev;
2259 /* Allow motion events to be generated again. */
2260 if (ev->is_hint
2261 && XQueryPointer (XtDisplay (mw), ev->window,
2262 &ev->root, &ev->subwindow,
2263 &ev->x_root, &ev->y_root,
2264 &ev->x, &ev->y,
2265 &ev->state)
2266 && ev->state == state
2267 && (ev->x_root != x || ev->y_root != y))
2268 handle_single_motion_event (mw, ev);
2269 else
2270 handle_single_motion_event (mw, &oldev);
2273 static void
2274 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2276 XlwMenuWidget mw = (XlwMenuWidget)w;
2278 if (!mw->menu.popped_up)
2280 menu_post_event = *ev;
2281 /* If event is set to CurrentTime, get the last known time stamp.
2282 This is for calculating if (popup) menus should stay up after
2283 a fast click. */
2284 if (menu_post_event.xbutton.time == CurrentTime)
2285 menu_post_event.xbutton.time
2286 = XtLastTimestampProcessed (XtDisplay (w));
2288 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2290 else
2292 /* If we push a button while the menu is posted semipermanently,
2293 releasing the button should always pop the menu down. */
2294 next_release_must_exit = 1;
2296 /* Notes the absolute position of the menubar window. */
2297 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2298 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2300 /* Handles the down like a move, slots are compatible. */
2301 ev->xmotion.is_hint = 0;
2302 handle_motion_event (mw, &ev->xmotion);
2306 static void
2307 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2309 XlwMenuWidget mw = (XlwMenuWidget)w;
2310 if (mw->menu.popped_up)
2311 handle_motion_event (mw, &ev->xmotion);
2314 /* Do nothing.
2315 This is how we handle presses and releases of modifier keys. */
2316 static void
2317 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2321 static widget_value *
2322 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2324 widget_value *current = item;
2325 enum menu_separator separator;
2327 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2328 || (skip_titles && !current->call_data && !current->contents))
2329 if (current->next)
2330 current = current->next;
2331 else
2332 return NULL;
2334 return current;
2337 static widget_value *
2338 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2340 widget_value *current = item;
2341 enum menu_separator separator;
2343 while (current->next && (current = current->next)
2344 && (lw_separator_p (current->name, &separator, 0) || !current->enabled
2345 || (skip_titles && !current->call_data && !current->contents)))
2348 if (current == item)
2350 if (mw->menu.old_depth < 2)
2351 return current;
2352 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2354 while (lw_separator_p (current->name, &separator, 0)
2355 || !current->enabled
2356 || (skip_titles && !current->call_data
2357 && !current->contents))
2359 if (current->next)
2360 current = current->next;
2362 if (current == item)
2363 break;
2368 return current;
2371 static widget_value *
2372 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2374 widget_value *current = item;
2375 widget_value *prev = item;
2377 while ((current = find_next_selectable (mw, current, skip_titles))
2378 != item)
2380 if (prev == current)
2381 break;
2382 prev = current;
2385 return prev;
2388 static void
2389 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2391 XlwMenuWidget mw = (XlwMenuWidget) w;
2392 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2393 int popup_menu_p = mw->menu.top_depth == 1;
2395 /* Inside top-level menu-bar? */
2396 if (mw->menu.old_depth == mw->menu.top_depth)
2397 /* When <down> in the menu-bar is pressed, display the corresponding
2398 sub-menu and select the first selectable menu item there.
2399 If this is a popup menu, skip title item of the popup. */
2400 set_new_state (mw,
2401 find_first_selectable (mw,
2402 selected_item->contents,
2403 popup_menu_p),
2404 mw->menu.old_depth);
2405 else
2406 /* Highlight next possible (enabled and not separator) menu item. */
2407 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2408 mw->menu.old_depth - 1);
2410 remap_menubar (mw);
2413 static void
2414 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2416 XlwMenuWidget mw = (XlwMenuWidget) w;
2417 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2418 int popup_menu_p = mw->menu.top_depth == 1;
2420 /* Inside top-level menu-bar? */
2421 if (mw->menu.old_depth == mw->menu.top_depth)
2423 /* FIXME: this is tricky. <up> in the menu-bar should select the
2424 last selectable item in the list. So we select the first
2425 selectable one and find the previous selectable item. Is there
2426 a better way? */
2427 /* If this is a popup menu, skip title item of the popup. */
2428 set_new_state (mw,
2429 find_first_selectable (mw,
2430 selected_item->contents,
2431 popup_menu_p),
2432 mw->menu.old_depth);
2433 remap_menubar (mw);
2434 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2435 set_new_state (mw,
2436 find_prev_selectable (mw,
2437 selected_item,
2438 popup_menu_p),
2439 mw->menu.old_depth - 1);
2441 else
2442 /* Highlight previous (enabled and not separator) menu item. */
2443 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2444 mw->menu.old_depth - 1);
2446 remap_menubar (mw);
2449 void
2450 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2452 XlwMenuWidget mw = (XlwMenuWidget) w;
2453 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2455 /* Inside top-level menu-bar? */
2456 if (mw->menu.old_depth == mw->menu.top_depth)
2457 /* When <left> in the menu-bar is pressed, display the previous item on
2458 the menu-bar. If the current item is the first one, highlight the
2459 last item in the menubar (probably Help). */
2460 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2461 mw->menu.old_depth - 1);
2462 else if (mw->menu.old_depth == 1
2463 && selected_item->contents) /* Is this menu item expandable? */
2465 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2466 remap_menubar (mw);
2467 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2468 if (!selected_item->enabled && find_first_selectable (mw,
2469 selected_item,
2471 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2472 mw->menu.old_depth - 1);
2475 else
2477 pop_new_stack_if_no_contents (mw);
2478 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2479 mw->menu.old_depth - 2);
2482 remap_menubar (mw);
2485 void
2486 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2488 XlwMenuWidget mw = (XlwMenuWidget) w;
2489 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2491 /* Inside top-level menu-bar? */
2492 if (mw->menu.old_depth == mw->menu.top_depth)
2493 /* When <right> in the menu-bar is pressed, display the next item on
2494 the menu-bar. If the current item is the last one, highlight the
2495 first item (probably File). */
2496 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2497 mw->menu.old_depth - 1);
2498 else if (selected_item->contents) /* Is this menu item expandable? */
2500 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2501 remap_menubar (mw);
2502 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2503 if (!selected_item->enabled && find_first_selectable (mw,
2504 selected_item,
2506 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2507 mw->menu.old_depth - 1);
2509 else
2511 pop_new_stack_if_no_contents (mw);
2512 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2513 mw->menu.old_depth - 2);
2516 remap_menubar (mw);
2519 /* Handle key press and release events while menu is popped up.
2520 Our action is to get rid of the menu. */
2521 static void
2522 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2524 XlwMenuWidget mw = (XlwMenuWidget)w;
2526 /* Pop down everything. */
2527 mw->menu.new_depth = 1;
2528 remap_menubar (mw);
2530 if (mw->menu.popped_up)
2532 mw->menu.popped_up = False;
2533 ungrab_all ((Widget)mw, ev->xmotion.time);
2534 if (XtIsShell (XtParent ((Widget) mw)))
2535 XtPopdown (XtParent ((Widget) mw));
2536 else
2538 XtRemoveGrab ((Widget) mw);
2539 display_menu (mw, 0, False, NULL, NULL, NULL);
2543 /* callback */
2544 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2547 static void
2548 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2550 XlwMenuWidget mw = (XlwMenuWidget)w;
2551 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2553 /* If user releases the button quickly, without selecting anything,
2554 after the initial down-click that brought the menu up,
2555 do nothing. */
2556 if ((selected_item == 0
2557 || ((widget_value *) selected_item)->call_data == 0)
2558 && !next_release_must_exit
2559 && (ev->xbutton.time - menu_post_event.xbutton.time
2560 < XtGetMultiClickTime (XtDisplay (w))))
2561 return;
2563 /* Pop down everything. */
2564 mw->menu.new_depth = 1;
2565 remap_menubar (mw);
2567 if (mw->menu.popped_up)
2569 mw->menu.popped_up = False;
2570 ungrab_all ((Widget)mw, ev->xmotion.time);
2571 if (XtIsShell (XtParent ((Widget) mw)))
2572 XtPopdown (XtParent ((Widget) mw));
2573 else
2575 XtRemoveGrab ((Widget) mw);
2576 display_menu (mw, 0, False, NULL, NULL, NULL);
2580 /* callback */
2581 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2585 \f/* Special code to pop-up a menu. */
2586 static void
2587 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2589 int x = event->x_root;
2590 int y = event->y_root;
2591 int w;
2592 int h;
2593 int borderwidth = mw->menu.shadow_thickness;
2594 Screen* screen = XtScreen (mw);
2595 Display *display = XtDisplay (mw);
2597 next_release_must_exit = 0;
2599 mw->menu.inside_entry = NULL;
2600 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2602 if (XtIsShell (XtParent ((Widget)mw)))
2603 size_menu (mw, 0);
2605 w = mw->menu.windows [0].width;
2606 h = mw->menu.windows [0].height;
2608 x -= borderwidth;
2609 y -= borderwidth;
2610 if (x < borderwidth)
2611 x = borderwidth;
2612 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2613 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2614 if (y < borderwidth)
2615 y = borderwidth;
2616 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2617 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2619 mw->menu.popped_up = True;
2620 if (XtIsShell (XtParent ((Widget)mw)))
2622 /* fprintf (stderr, "Config %d %d\n", x, y); */
2623 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2624 XtParent ((Widget)mw)->core.border_width);
2625 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2626 display_menu (mw, 0, False, NULL, NULL, NULL);
2627 mw->menu.windows [0].x = x + borderwidth;
2628 mw->menu.windows [0].y = y + borderwidth;
2629 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1. */
2631 else
2633 XEvent *ev = (XEvent *) event;
2635 XtAddGrab ((Widget) mw, True, True);
2637 /* Notes the absolute position of the menubar window. */
2638 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2639 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2640 mw->menu.top_depth = 2;
2643 #ifdef emacs
2644 x_catch_errors (display);
2645 #endif
2646 if (XtGrabPointer ((Widget)mw, False,
2647 (PointerMotionMask
2648 | PointerMotionHintMask
2649 | ButtonReleaseMask
2650 | ButtonPressMask),
2651 GrabModeAsync, GrabModeAsync, None,
2652 mw->menu.cursor_shape,
2653 event->time) == Success)
2655 if (! GRAB_KEYBOARD
2656 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2657 GrabModeAsync, event->time) == Success)
2659 XtSetKeyboardFocus((Widget)mw, None);
2660 pointer_grabbed = 1;
2662 else
2663 XtUngrabPointer ((Widget)mw, event->time);
2666 #ifdef emacs
2667 if (x_had_errors_p (display))
2669 pointer_grabbed = 0;
2670 XtUngrabPointer ((Widget)mw, event->time);
2672 x_uncatch_errors ();
2673 #endif
2675 ((XMotionEvent*)event)->is_hint = 0;
2676 handle_motion_event (mw, (XMotionEvent*)event);