Recognize more system descriptions in report-emacs-bug
[emacs.git] / lwlib / xlwmenu.c
blob20a868d3e0727b0192fcfa2f6bc08771a6b16c5e
1 /* Implements a lightweight menubar widget.
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2018 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 <https://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 #include <X11/Xos.h>
32 #include <X11/IntrinsicP.h>
33 #include <X11/ObjectP.h>
34 #include <X11/StringDefs.h>
35 #include <X11/cursorfont.h>
36 #include <X11/Shell.h>
37 #include "xlwmenuP.h"
39 #ifdef emacs
41 #include <xterm.h>
42 #include "bitmaps/gray.xbm"
44 #else /* not emacs */
46 #include <X11/bitmaps/gray>
48 #endif /* not emacs */
50 static int pointer_grabbed;
51 static XEvent menu_post_event;
53 static char
54 xlwMenuTranslations [] =
55 "<BtnDown>: start()\n\
56 <Motion>: drag()\n\
57 <BtnUp>: select()\n\
58 <Key>Shift_L: nothing()\n\
59 <Key>Shift_R: nothing()\n\
60 <Key>Meta_L: nothing()\n\
61 <Key>Meta_R: nothing()\n\
62 <Key>Control_L: nothing()\n\
63 <Key>Control_R: nothing()\n\
64 <Key>Hyper_L: nothing()\n\
65 <Key>Hyper_R: nothing()\n\
66 <Key>Super_L: nothing()\n\
67 <Key>Super_R: nothing()\n\
68 <Key>Alt_L: nothing()\n\
69 <Key>Alt_R: nothing()\n\
70 <Key>Caps_Lock: nothing()\n\
71 <Key>Shift_Lock: nothing()\n\
72 <KeyUp>Shift_L: nothing()\n\
73 <KeyUp>Shift_R: nothing()\n\
74 <KeyUp>Meta_L: nothing()\n\
75 <KeyUp>Meta_R: nothing()\n\
76 <KeyUp>Control_L: nothing()\n\
77 <KeyUp>Control_R: nothing()\n\
78 <KeyUp>Hyper_L: nothing()\n\
79 <KeyUp>Hyper_R: nothing()\n\
80 <KeyUp>Super_L: nothing()\n\
81 <KeyUp>Super_R: nothing()\n\
82 <KeyUp>Alt_L: nothing()\n\
83 <KeyUp>Alt_R: nothing()\n\
84 <KeyUp>Caps_Lock: nothing()\n\
85 <KeyUp>Shift_Lock:nothing()\n\
86 <Key>Return: select()\n\
87 <Key>Down: down()\n\
88 <Key>Up: up()\n\
89 <Key>Left: left()\n\
90 <Key>Right: right()\n\
91 <Key>: key()\n\
92 <KeyUp>: key()\n\
95 /* FIXME: Space should toggle togglable menu item but not remove the menu
96 so you can toggle the next one without entering the menu again. */
98 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
100 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
102 #define offset(field) offsetof (XlwMenuRec, field)
103 static XtResource
104 xlwMenuResources[] =
106 #ifdef HAVE_X_I18N
107 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
108 offset(menu.fontSet), XtRFontSet, NULL},
109 #endif
110 #ifdef HAVE_XFT
111 #define DEFAULT_FONTNAME "Sans-10"
112 #else
113 #define DEFAULT_FONTNAME "XtDefaultFont"
114 #endif
115 {XtNfont, XtCFont, XtRString, sizeof(String),
116 offset(menu.fontName), XtRString, DEFAULT_FONTNAME },
117 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
118 offset(menu.foreground), XtRString, "XtDefaultForeground"},
119 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
120 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
121 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
122 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
123 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
124 offset(menu.margin), XtRImmediate, (XtPointer)1},
125 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
126 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
127 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
128 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
129 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
130 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
132 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
133 sizeof (Dimension), offset (menu.shadow_thickness),
134 XtRImmediate, (XtPointer)1},
135 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
136 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
137 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
138 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
139 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
140 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
141 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
142 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
144 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
145 offset(menu.open), XtRCallback, (XtPointer)NULL},
146 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
147 offset(menu.select), XtRCallback, (XtPointer)NULL},
148 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
149 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
150 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
151 offset(menu.enter), XtRCallback, (XtPointer)NULL},
152 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
153 offset(menu.leave), XtRCallback, (XtPointer)NULL},
154 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
155 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
156 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
157 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
158 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
159 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
161 #undef offset
163 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
164 ArgList args, Cardinal *num_args);
165 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
166 static void XlwMenuResize(Widget w);
167 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
168 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
169 static void XlwMenuDestroy(Widget w);
170 static void XlwMenuClassInitialize(void);
171 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
172 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
173 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
174 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
175 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
176 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
177 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
178 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
179 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
180 static int separator_height (enum menu_separator);
181 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
183 static XtActionsRec
184 xlwMenuActionsList [] =
186 {"start", Start},
187 {"drag", Drag},
188 {"down", Down},
189 {"up", Up},
190 {"left", Left},
191 {"right", Right},
192 {"select", Select},
193 {"key", Key},
194 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
195 {"nothing", Nothing},
198 #define SuperClass ((CoreWidgetClass)&coreClassRec)
200 XlwMenuClassRec xlwMenuClassRec =
202 { /* CoreClass fields initialization */
203 (WidgetClass) SuperClass, /* superclass */
204 "XlwMenu", /* class_name */
205 sizeof(XlwMenuRec), /* size */
206 XlwMenuClassInitialize, /* class_initialize */
207 NULL, /* class_part_initialize */
208 FALSE, /* class_inited */
209 XlwMenuInitialize, /* initialize */
210 NULL, /* initialize_hook */
211 XlwMenuRealize, /* realize */
212 xlwMenuActionsList, /* actions */
213 XtNumber(xlwMenuActionsList), /* num_actions */
214 xlwMenuResources, /* resources */
215 XtNumber(xlwMenuResources), /* resource_count */
216 NULLQUARK, /* xrm_class */
217 TRUE, /* compress_motion */
218 XtExposeCompressMaximal, /* compress_exposure */
219 TRUE, /* compress_enterleave */
220 FALSE, /* visible_interest */
221 XlwMenuDestroy, /* destroy */
222 XlwMenuResize, /* resize */
223 XlwMenuRedisplay, /* expose */
224 XlwMenuSetValues, /* set_values */
225 NULL, /* set_values_hook */
226 XtInheritSetValuesAlmost, /* set_values_almost */
227 NULL, /* get_values_hook */
228 NULL, /* accept_focus */
229 XtVersion, /* version */
230 NULL, /* callback_private */
231 xlwMenuTranslations, /* tm_table */
232 XtInheritQueryGeometry, /* query_geometry */
233 XtInheritDisplayAccelerator, /* display_accelerator */
234 NULL /* extension */
235 }, /* XlwMenuClass fields initialization */
237 0 /* dummy */
241 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
243 int submenu_destroyed;
245 /* For debug, if installation-directory is non-nil this is not an installed
246 Emacs. In that case we do not grab the keyboard to make it easier to
247 debug. */
248 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
250 static int next_release_must_exit;
252 \f/* Utilities */
254 /* Ungrab pointer and keyboard */
255 static void
256 ungrab_all (Widget w, Time ungrabtime)
258 XtUngrabPointer (w, ungrabtime);
259 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
262 /* Like abort, but remove grabs from widget W before. */
264 static _Noreturn void
265 abort_gracefully (Widget w)
267 if (XtIsShell (XtParent (w)))
268 XtRemoveGrab (w);
269 ungrab_all (w, CurrentTime);
270 emacs_abort ();
273 static void
274 push_new_stack (XlwMenuWidget mw, widget_value *val)
276 if (!mw->menu.new_stack)
278 mw->menu.new_stack_length = 10;
279 mw->menu.new_stack =
280 (widget_value**)XtCalloc (mw->menu.new_stack_length,
281 sizeof (widget_value*));
283 else if (mw->menu.new_depth == mw->menu.new_stack_length)
285 mw->menu.new_stack_length *= 2;
286 mw->menu.new_stack =
287 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
288 mw->menu.new_stack_length * sizeof (widget_value*));
290 mw->menu.new_stack [mw->menu.new_depth++] = val;
293 static void
294 pop_new_stack_if_no_contents (XlwMenuWidget mw)
296 if (mw->menu.new_depth > 1)
298 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
299 mw->menu.new_depth -= 1;
303 static void
304 make_old_stack_space (XlwMenuWidget mw, int n)
306 if (!mw->menu.old_stack)
308 mw->menu.old_stack_length = 10;
309 mw->menu.old_stack =
310 (widget_value**)XtCalloc (mw->menu.old_stack_length,
311 sizeof (widget_value*));
313 else if (mw->menu.old_stack_length < n)
315 mw->menu.old_stack_length *= 2;
316 mw->menu.old_stack =
317 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
318 mw->menu.old_stack_length * sizeof (widget_value*));
322 \f/* Size code */
323 static int
324 string_width (XlwMenuWidget mw, char *s)
326 XCharStruct xcs;
327 int drop;
328 #ifdef HAVE_XFT
329 if (mw->menu.xft_font)
331 XGlyphInfo gi;
332 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
333 (FcChar8 *) s,
334 strlen (s), &gi);
335 return gi.width;
337 #endif
338 #ifdef HAVE_X_I18N
339 if (mw->menu.fontSet)
341 XRectangle ink, logical;
342 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
343 return logical.width;
345 #endif
347 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
348 return xcs.width;
352 #ifdef HAVE_XFT
353 #define MENU_FONT_HEIGHT(mw) \
354 ((mw)->menu.xft_font != NULL \
355 ? (mw)->menu.xft_font->height \
356 : ((mw)->menu.fontSet != NULL \
357 ? (mw)->menu.font_extents->max_logical_extent.height \
358 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
359 #define MENU_FONT_ASCENT(mw) \
360 ((mw)->menu.xft_font != NULL \
361 ? (mw)->menu.xft_font->ascent \
362 : ((mw)->menu.fontSet != NULL \
363 ? - (mw)->menu.font_extents->max_logical_extent.y \
364 : (mw)->menu.font->ascent))
365 #else
366 #ifdef HAVE_X_I18N
367 #define MENU_FONT_HEIGHT(mw) \
368 ((mw)->menu.fontSet != NULL \
369 ? (mw)->menu.font_extents->max_logical_extent.height \
370 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
371 #define MENU_FONT_ASCENT(mw) \
372 ((mw)->menu.fontSet != NULL \
373 ? - (mw)->menu.font_extents->max_logical_extent.y \
374 : (mw)->menu.font->ascent)
375 #else
376 #define MENU_FONT_HEIGHT(mw) \
377 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
378 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
379 #endif
380 #endif
382 static int
383 arrow_width (XlwMenuWidget mw)
385 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
388 /* Return the width of toggle buttons of widget MW. */
390 static int
391 toggle_button_width (XlwMenuWidget mw)
393 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
397 /* Return the width of radio buttons of widget MW. */
399 static int
400 radio_button_width (XlwMenuWidget mw)
402 return toggle_button_width (mw) * 1.41;
406 static XtResource
407 nameResource[] =
409 {"labelString", "LabelString", XtRString, sizeof(String),
410 0, XtRImmediate, 0},
413 static char*
414 resource_widget_value (XlwMenuWidget mw, widget_value *val)
416 if (!val->toolkit_data)
418 char* resourced_name = NULL;
419 char* complete_name;
420 XtGetSubresources ((Widget) mw,
421 (XtPointer) &resourced_name,
422 val->name, val->name,
423 nameResource, 1, NULL, 0);
424 if (!resourced_name)
425 resourced_name = val->name;
426 if (!val->value)
428 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
429 strcpy (complete_name, resourced_name);
431 else
433 int complete_length =
434 strlen (resourced_name) + strlen (val->value) + 2;
435 complete_name = XtMalloc (complete_length);
436 char *z = stpcpy (complete_name, resourced_name);
437 *z++ = ' ';
438 strcpy (z, val->value);
441 val->toolkit_data = complete_name;
442 val->free_toolkit_data = True;
444 return (char*)val->toolkit_data;
447 /* Returns the sizes of an item */
448 static void
449 size_menu_item (XlwMenuWidget mw,
450 widget_value* val,
451 int horizontal_p,
452 int* label_width,
453 int* rest_width,
454 int* button_width,
455 int* height)
457 enum menu_separator separator;
459 if (lw_separator_p (val->name, &separator, 0))
461 *height = separator_height (separator);
462 *label_width = 1;
463 *rest_width = 0;
464 *button_width = 0;
466 else
468 *height = MENU_FONT_HEIGHT (mw)
469 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
471 *label_width =
472 string_width (mw, resource_widget_value (mw, val))
473 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
475 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
476 if (!horizontal_p)
478 if (val->contents)
479 /* Add width of the arrow displayed for submenus. */
480 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
481 else if (val->key)
482 /* Add width of key equivalent string. */
483 *rest_width += (string_width (mw, val->key)
484 + mw->menu.arrow_spacing);
486 if (val->button_type == BUTTON_TYPE_TOGGLE)
487 *button_width = (toggle_button_width (mw)
488 + mw->menu.horizontal_spacing);
489 else if (val->button_type == BUTTON_TYPE_RADIO)
490 *button_width = (radio_button_width (mw)
491 + mw->menu.horizontal_spacing);
496 static void
497 size_menu (XlwMenuWidget mw, int level)
499 int label_width = 0;
500 int rest_width = 0;
501 int button_width = 0;
502 int max_rest_width = 0;
503 int max_button_width = 0;
504 int height = 0;
505 int horizontal_p = mw->menu.horizontal && (level == 0);
506 widget_value* val;
507 window_state* ws;
509 if (level >= mw->menu.old_depth)
510 abort_gracefully ((Widget) mw);
512 ws = &mw->menu.windows [level];
513 ws->width = 0;
514 ws->height = 0;
515 ws->label_width = 0;
516 ws->button_width = 0;
518 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
520 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
521 &button_width, &height);
522 if (horizontal_p)
524 ws->width += label_width + rest_width;
525 if (height > ws->height)
526 ws->height = height;
528 else
530 if (label_width > ws->label_width)
531 ws->label_width = label_width;
532 if (rest_width > max_rest_width)
533 max_rest_width = rest_width;
534 if (button_width > max_button_width)
535 max_button_width = button_width;
536 ws->height += height;
540 if (horizontal_p)
541 ws->label_width = ws->button_width = 0;
542 else
544 ws->width = ws->label_width + max_rest_width + max_button_width;
545 ws->button_width = max_button_width;
548 ws->width += 2 * mw->menu.shadow_thickness;
549 ws->height += 2 * mw->menu.shadow_thickness;
550 ws->max_rest_width = max_rest_width;
552 if (horizontal_p)
554 ws->width += 2 * mw->menu.margin;
555 ws->height += 2 * mw->menu.margin;
560 \f/* Display code */
562 static void
563 draw_arrow (XlwMenuWidget mw,
564 Window window,
565 GC gc,
566 int x,
567 int y,
568 int width,
569 int down_p)
571 Display *dpy = XtDisplay (mw);
572 GC top_gc = mw->menu.shadow_top_gc;
573 GC bottom_gc = mw->menu.shadow_bottom_gc;
574 int thickness = mw->menu.shadow_thickness;
575 int height = width;
576 XPoint pt[10];
577 /* alpha = atan (0.5)
578 factor = (1 + sin (alpha)) / cos (alpha) */
579 double factor = 1.62;
580 int thickness2 = thickness * factor;
582 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
584 if (down_p)
586 GC temp;
587 temp = top_gc;
588 top_gc = bottom_gc;
589 bottom_gc = temp;
592 pt[0].x = x;
593 pt[0].y = y + height;
594 pt[1].x = x + thickness;
595 pt[1].y = y + height - thickness2;
596 pt[2].x = x + thickness2;
597 pt[2].y = y + thickness2;
598 pt[3].x = x;
599 pt[3].y = y;
600 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
602 pt[0].x = x;
603 pt[0].y = y;
604 pt[1].x = x + thickness;
605 pt[1].y = y + thickness2;
606 pt[2].x = x + width - thickness2;
607 pt[2].y = y + height / 2;
608 pt[3].x = x + width;
609 pt[3].y = y + height / 2;
610 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
612 pt[0].x = x;
613 pt[0].y = y + height;
614 pt[1].x = x + thickness;
615 pt[1].y = y + height - 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, bottom_gc, pt, 4, Convex, CoordModeOrigin);
625 static void
626 draw_shadow_rectangle (XlwMenuWidget mw,
627 Window window,
628 int x,
629 int y,
630 int width,
631 int height,
632 int erase_p,
633 int down_p)
635 Display *dpy = XtDisplay (mw);
636 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
637 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
638 int thickness = mw->menu.shadow_thickness;
639 XPoint points [4];
641 if (!erase_p && down_p)
643 GC temp;
644 temp = top_gc;
645 top_gc = bottom_gc;
646 bottom_gc = temp;
649 points [0].x = x;
650 points [0].y = y;
651 points [1].x = x + width;
652 points [1].y = y;
653 points [2].x = x + width - thickness;
654 points [2].y = y + thickness;
655 points [3].x = x;
656 points [3].y = y + thickness;
657 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
658 points [0].x = x;
659 points [0].y = y + thickness;
660 points [1].x = x;
661 points [1].y = y + height;
662 points [2].x = x + thickness;
663 points [2].y = y + height - thickness;
664 points [3].x = x + thickness;
665 points [3].y = y + thickness;
666 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
667 points [0].x = x + width;
668 points [0].y = y;
669 points [1].x = x + width - thickness;
670 points [1].y = y + thickness;
671 points [2].x = x + width - thickness;
672 points [2].y = y + height - thickness;
673 points [3].x = x + width;
674 points [3].y = y + height - thickness;
675 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
676 points [0].x = x;
677 points [0].y = y + height;
678 points [1].x = x + width;
679 points [1].y = y + height;
680 points [2].x = x + width;
681 points [2].y = y + height - thickness;
682 points [3].x = x + thickness;
683 points [3].y = y + height - thickness;
684 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
688 static void
689 draw_shadow_rhombus (XlwMenuWidget mw,
690 Window window,
691 int x,
692 int y,
693 int width,
694 int height,
695 int erase_p,
696 int down_p)
698 Display *dpy = XtDisplay (mw);
699 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
700 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
701 int thickness = mw->menu.shadow_thickness;
702 XPoint points [4];
704 if (!erase_p && down_p)
706 GC temp;
707 temp = top_gc;
708 top_gc = bottom_gc;
709 bottom_gc = temp;
712 points [0].x = x;
713 points [0].y = y + height / 2;
714 points [1].x = x + thickness;
715 points [1].y = y + height / 2;
716 points [2].x = x + width / 2;
717 points [2].y = y + thickness;
718 points [3].x = x + width / 2;
719 points [3].y = y;
720 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
721 points [0].x = x + width / 2;
722 points [0].y = y;
723 points [1].x = x + width / 2;
724 points [1].y = y + thickness;
725 points [2].x = x + width - thickness;
726 points [2].y = y + height / 2;
727 points [3].x = x + width;
728 points [3].y = y + height / 2;
729 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
730 points [0].x = x;
731 points [0].y = y + height / 2;
732 points [1].x = x + thickness;
733 points [1].y = y + height / 2;
734 points [2].x = x + width / 2;
735 points [2].y = y + height - thickness;
736 points [3].x = x + width / 2;
737 points [3].y = y + height;
738 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
739 points [0].x = x + width / 2;
740 points [0].y = y + height;
741 points [1].x = x + width / 2;
742 points [1].y = y + height - thickness;
743 points [2].x = x + width - thickness;
744 points [2].y = y + height / 2;
745 points [3].x = x + width;
746 points [3].y = y + height / 2;
747 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
751 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
752 top-left corner of the menu item. SELECTED_P non-zero means the
753 toggle button is selected. */
755 static void
756 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
758 int width, height;
760 width = toggle_button_width (mw);
761 height = width;
762 x += mw->menu.horizontal_spacing;
763 y += (MENU_FONT_ASCENT (mw) - height) / 2;
764 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
768 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
769 top-left corner of the menu item. SELECTED_P non-zero means the
770 toggle button is selected. */
772 static void
773 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
775 int width, height;
777 width = radio_button_width (mw);
778 height = width;
779 x += mw->menu.horizontal_spacing;
780 y += (MENU_FONT_ASCENT (mw) - height) / 2;
781 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
785 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
786 top-left corner of the menu item. WIDTH is the width of the
787 separator to draw. TYPE is the separator type. */
789 static void
790 draw_separator (XlwMenuWidget mw,
791 Window window,
792 int x,
793 int y,
794 int width,
795 enum menu_separator type)
797 Display *dpy = XtDisplay (mw);
798 XGCValues xgcv;
800 switch (type)
802 case SEPARATOR_NO_LINE:
803 break;
805 case SEPARATOR_SINGLE_LINE:
806 XDrawLine (dpy, window, mw->menu.foreground_gc,
807 x, y, x + width, y);
808 break;
810 case SEPARATOR_DOUBLE_LINE:
811 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
812 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
813 break;
815 case SEPARATOR_SINGLE_DASHED_LINE:
816 xgcv.line_style = LineOnOffDash;
817 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
818 XDrawLine (dpy, window, mw->menu.foreground_gc,
819 x, y, x + width, y);
820 xgcv.line_style = LineSolid;
821 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
822 break;
824 case SEPARATOR_DOUBLE_DASHED_LINE:
825 draw_separator (mw, window, x, y, width,
826 SEPARATOR_SINGLE_DASHED_LINE);
827 draw_separator (mw, window, x, y + 2, width,
828 SEPARATOR_SINGLE_DASHED_LINE);
829 break;
831 case SEPARATOR_SHADOW_ETCHED_IN:
832 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
833 x, y, x + width, y);
834 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
835 x, y + 1, x + width, y + 1);
836 break;
838 case SEPARATOR_SHADOW_ETCHED_OUT:
839 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
840 x, y, x + width, y);
841 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
842 x, y + 1, x + width, y + 1);
843 break;
845 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
846 xgcv.line_style = LineOnOffDash;
847 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
848 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
849 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
850 xgcv.line_style = LineSolid;
851 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
852 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
853 break;
855 case SEPARATOR_SHADOW_ETCHED_OUT_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_OUT);
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_DOUBLE_ETCHED_IN:
866 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
867 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
868 break;
870 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
871 draw_separator (mw, window, x, y, width,
872 SEPARATOR_SHADOW_ETCHED_OUT);
873 draw_separator (mw, window, x, y + 3, width,
874 SEPARATOR_SHADOW_ETCHED_OUT);
875 break;
877 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
878 xgcv.line_style = LineOnOffDash;
879 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
880 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
881 draw_separator (mw, window, x, y, width,
882 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
883 xgcv.line_style = LineSolid;
884 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
885 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
886 break;
888 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
889 xgcv.line_style = LineOnOffDash;
890 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
891 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
892 draw_separator (mw, window, x, y, width,
893 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
894 xgcv.line_style = LineSolid;
895 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
896 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
897 break;
899 default:
900 emacs_abort ();
905 /* Return the pixel height of menu separator SEPARATOR. */
907 static int
908 separator_height (enum menu_separator separator)
910 switch (separator)
912 case SEPARATOR_NO_LINE:
913 return 2;
915 case SEPARATOR_SINGLE_LINE:
916 case SEPARATOR_SINGLE_DASHED_LINE:
917 return 1;
919 case SEPARATOR_DOUBLE_LINE:
920 case SEPARATOR_DOUBLE_DASHED_LINE:
921 return 3;
923 case SEPARATOR_SHADOW_ETCHED_IN:
924 case SEPARATOR_SHADOW_ETCHED_OUT:
925 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
926 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
927 return 2;
929 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
930 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
931 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
932 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
933 return 5;
935 default:
936 emacs_abort ();
941 /* Display the menu item and increment where.x and where.y to show how large
942 the menu item was. */
944 static void
945 display_menu_item (XlwMenuWidget mw,
946 widget_value* val,
947 window_state* ws,
948 XPoint* where,
949 Boolean highlighted_p,
950 Boolean horizontal_p,
951 Boolean just_compute_p)
953 GC deco_gc;
954 GC text_gc;
955 int font_height = MENU_FONT_HEIGHT (mw);
956 int font_ascent = MENU_FONT_ASCENT (mw);
957 int shadow = mw->menu.shadow_thickness;
958 int margin = mw->menu.margin;
959 int h_spacing = mw->menu.horizontal_spacing;
960 int v_spacing = mw->menu.vertical_spacing;
961 int label_width;
962 int rest_width;
963 int button_width;
964 int height;
965 int width;
966 enum menu_separator separator;
967 int separator_p = lw_separator_p (val->name, &separator, 0);
968 #ifdef HAVE_XFT
969 XftColor *xftfg;
970 #endif
972 /* compute the sizes of the item */
973 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
974 &button_width, &height);
976 if (horizontal_p)
977 width = label_width + rest_width;
978 else
980 label_width = ws->label_width;
981 width = ws->width - 2 * shadow;
984 /* Only highlight an enabled item that has a callback. */
985 if (highlighted_p)
986 if (!val->enabled || !(val->call_data || val->contents))
987 highlighted_p = 0;
989 /* do the drawing. */
990 if (!just_compute_p)
992 /* Add the shadow border of the containing menu */
993 int x = where->x + shadow;
994 int y = where->y + shadow;
996 if (horizontal_p)
998 x += margin;
999 y += margin;
1002 /* pick the foreground and background GC. */
1003 if (val->enabled)
1004 text_gc = mw->menu.foreground_gc;
1005 else
1006 text_gc = mw->menu.disabled_gc;
1007 deco_gc = mw->menu.foreground_gc;
1008 #ifdef HAVE_XFT
1009 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1010 #endif
1012 if (separator_p)
1014 draw_separator (mw, ws->pixmap, x, y, width, separator);
1016 else
1018 int x_offset = x + h_spacing + shadow;
1019 char* display_string = resource_widget_value (mw, val);
1020 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1021 False);
1023 /* Deal with centering a menu title. */
1024 if (!horizontal_p && !val->contents && !val->call_data)
1026 int l = string_width (mw, display_string);
1028 if (width > l)
1029 x_offset = (width - l) >> 1;
1031 else if (!horizontal_p && ws->button_width)
1032 x_offset += ws->button_width;
1035 #ifdef HAVE_XFT
1036 if (ws->xft_draw)
1038 int draw_y = y + v_spacing + shadow;
1039 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1040 mw->menu.xft_font,
1041 x_offset, draw_y + font_ascent,
1042 (unsigned char *) display_string,
1043 strlen (display_string));
1045 else
1046 #endif
1047 #ifdef HAVE_X_I18N
1048 if (mw->menu.fontSet)
1049 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1050 text_gc, x_offset,
1051 y + v_spacing + shadow + font_ascent,
1052 display_string, strlen (display_string));
1053 else
1054 #endif
1055 XDrawString (XtDisplay (mw), ws->pixmap,
1056 text_gc, x_offset,
1057 y + v_spacing + shadow + font_ascent,
1058 display_string, strlen (display_string));
1060 if (!horizontal_p)
1062 if (val->button_type == BUTTON_TYPE_TOGGLE)
1063 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1064 val->selected);
1065 else if (val->button_type == BUTTON_TYPE_RADIO)
1066 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1067 val->selected);
1069 if (val->contents)
1071 int a_w = arrow_width (mw);
1072 draw_arrow (mw, ws->pixmap, deco_gc,
1073 x + width - a_w
1074 - mw->menu.horizontal_spacing
1075 - mw->menu.shadow_thickness,
1076 y + v_spacing + shadow, a_w,
1077 highlighted_p);
1079 else if (val->key)
1081 #ifdef HAVE_XFT
1082 if (ws->xft_draw)
1084 int draw_x = ws->width - ws->max_rest_width
1085 + mw->menu.arrow_spacing;
1086 int draw_y = y + v_spacing + shadow + font_ascent;
1087 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1088 mw->menu.xft_font,
1089 draw_x, draw_y,
1090 (unsigned char *) val->key,
1091 strlen (val->key));
1093 else
1094 #endif
1095 #ifdef HAVE_X_I18N
1096 if (mw->menu.fontSet)
1097 XmbDrawString (XtDisplay (mw), ws->pixmap,
1098 mw->menu.fontSet,
1099 text_gc,
1100 x + label_width + mw->menu.arrow_spacing,
1101 y + v_spacing + shadow + font_ascent,
1102 val->key, strlen (val->key));
1103 else
1104 #endif
1105 XDrawString (XtDisplay (mw), ws->pixmap,
1106 text_gc,
1107 x + label_width + mw->menu.arrow_spacing,
1108 y + v_spacing + shadow + font_ascent,
1109 val->key, strlen (val->key));
1112 else
1114 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1115 mw->menu.background_gc,
1116 x + shadow, y + shadow,
1117 label_width + h_spacing - 1,
1118 font_height + 2 * v_spacing - 1);
1119 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1120 True, False);
1123 if (highlighted_p)
1124 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1125 False);
1129 where->x += width;
1130 where->y += height;
1133 static void
1134 display_menu (XlwMenuWidget mw,
1135 int level,
1136 Boolean just_compute_p,
1137 XPoint *highlighted_pos,
1138 XPoint *hit,
1139 widget_value **hit_return)
1141 widget_value* val;
1142 widget_value* following_item;
1143 window_state* ws;
1144 XPoint where;
1145 int horizontal_p = mw->menu.horizontal && (level == 0);
1146 int highlighted_p;
1147 int no_return = 0;
1148 enum menu_separator separator;
1150 if (level >= mw->menu.old_depth)
1151 abort_gracefully ((Widget) mw);
1153 if (level < mw->menu.old_depth - 1)
1154 following_item = mw->menu.old_stack [level + 1];
1155 else
1156 following_item = NULL;
1158 if (hit)
1159 *hit_return = NULL;
1161 where.x = 0;
1162 where.y = 0;
1164 ws = &mw->menu.windows [level];
1166 if (!just_compute_p)
1167 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1168 0, 0, ws->width, ws->height);
1170 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1172 highlighted_p = val == following_item;
1173 if (highlighted_p && highlighted_pos)
1175 if (horizontal_p)
1176 highlighted_pos->x = where.x;
1177 else
1178 highlighted_pos->y = where.y;
1181 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1182 just_compute_p);
1184 if (highlighted_p && highlighted_pos)
1186 if (horizontal_p)
1187 highlighted_pos->y = where.y;
1188 else
1189 highlighted_pos->x = where.x;
1192 if (hit
1193 && !*hit_return
1194 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1195 && !lw_separator_p (val->name, &separator, 0)
1196 && !no_return)
1198 if (val->enabled)
1199 *hit_return = val;
1200 else
1201 no_return = 1;
1202 if (mw->menu.inside_entry != val)
1204 if (mw->menu.inside_entry)
1205 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1206 (XtPointer) mw->menu.inside_entry);
1207 mw->menu.inside_entry = val;
1208 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1209 (XtPointer) mw->menu.inside_entry);
1213 if (horizontal_p)
1214 where.y = 0;
1215 else
1216 where.x = 0;
1219 if (!just_compute_p)
1221 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1222 False, False);
1223 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1224 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1228 \f/* Motion code */
1229 static void
1230 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1232 int i;
1234 mw->menu.new_depth = 0;
1235 for (i = 0; i < level; i++)
1236 push_new_stack (mw, mw->menu.old_stack [i]);
1237 push_new_stack (mw, val);
1240 static void
1241 expose_cb (Widget widget,
1242 XtPointer closure,
1243 XEvent* event,
1244 Boolean* continue_to_dispatch)
1246 XlwMenuWidget mw = (XlwMenuWidget) closure;
1247 int i;
1249 *continue_to_dispatch = False;
1250 for (i = 0; i < mw->menu.windows_length; ++i)
1251 if (mw->menu.windows [i].w == widget) break;
1252 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1253 display_menu (mw, i, False, NULL, NULL, NULL);
1256 static void
1257 set_window_type (Widget w, XlwMenuWidget mw)
1259 int popup_menu_p = mw->menu.top_depth == 1;
1260 Atom type = XInternAtom (XtDisplay (w),
1261 popup_menu_p
1262 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1263 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1264 False);
1266 XChangeProperty (XtDisplay (w), XtWindow (w),
1267 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1268 XA_ATOM, 32, PropModeReplace,
1269 (unsigned char *)&type, 1);
1273 static void
1274 make_windows_if_needed (XlwMenuWidget mw, int n)
1276 int i;
1277 int start_at;
1278 window_state* windows;
1280 if (mw->menu.windows_length >= n)
1281 return;
1283 if (!mw->menu.windows)
1285 mw->menu.windows =
1286 (window_state*)XtMalloc (n * sizeof (window_state));
1287 start_at = 0;
1289 else
1291 mw->menu.windows =
1292 (window_state*)XtRealloc ((char*)mw->menu.windows,
1293 n * sizeof (window_state));
1294 start_at = mw->menu.windows_length;
1296 mw->menu.windows_length = n;
1298 windows = mw->menu.windows;
1300 for (i = start_at; i < n; i++)
1302 Arg av[10];
1303 int ac = 0;
1304 windows [i].x = 0;
1305 windows [i].y = 0;
1306 windows [i].width = 1;
1307 windows [i].height = 1;
1308 windows [i].max_rest_width = 0;
1309 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1310 XtSetArg (av[ac], XtNheight, 1); ++ac;
1311 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1312 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1313 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1314 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1315 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1316 windows [i].w =
1317 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1318 (Widget) mw, av, ac);
1319 XtRealizeWidget (windows [i].w);
1320 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1321 windows [i].window = XtWindow (windows [i].w);
1322 windows [i].pixmap = None;
1323 #ifdef HAVE_XFT
1324 windows [i].xft_draw = 0;
1325 #endif
1326 set_window_type (windows [i].w, mw);
1328 XFlush (XtDisplay (mw));
1331 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1334 xlwmenu_window_p (Widget w, Window window)
1336 XlwMenuWidget mw = (XlwMenuWidget) w;
1337 int i;
1339 for (i = 0; i < mw->menu.windows_length; ++i)
1340 if (window == mw->menu.windows[i].window)
1341 break;
1343 return i < mw->menu.windows_length;
1346 /* Make the window fit in the screen */
1347 static void
1348 fit_to_screen (XlwMenuWidget mw,
1349 window_state *ws,
1350 window_state *previous_ws,
1351 Boolean horizontal_p)
1353 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1354 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1355 /* 1 if we are unable to avoid an overlap between
1356 this menu and the parent menu in the X dimension. */
1357 int horizontal_overlap = 0;
1359 if (ws->x < 0)
1360 ws->x = 0;
1361 else if (ws->x + ws->width > screen_width)
1363 if (!horizontal_p)
1364 /* The addition of shadow-thickness for a sub-menu's position is
1365 to reflect a similar adjustment when the menu is displayed to
1366 the right of the invoking menu-item; it makes the sub-menu
1367 look more `attached' to the menu-item. */
1368 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1369 else
1370 ws->x = screen_width - ws->width;
1371 if (ws->x < 0)
1373 ws->x = 0;
1374 horizontal_overlap = 1;
1377 /* If we overlap in X, try to avoid overlap in Y. */
1378 if (horizontal_overlap
1379 && ws->y < previous_ws->y + previous_ws->height
1380 && previous_ws->y < ws->y + ws->height)
1382 /* Put this menu right below or right above PREVIOUS_WS
1383 if there's room. */
1384 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1385 ws->y = previous_ws->y + previous_ws->height;
1386 else if (previous_ws->y - ws->height > 0)
1387 ws->y = previous_ws->y - ws->height;
1390 if (ws->y < 0)
1391 ws->y = 0;
1392 else if (ws->y + ws->height > screen_height)
1394 if (horizontal_p)
1395 ws->y = previous_ws->y - ws->height;
1396 else
1397 ws->y = screen_height - ws->height;
1398 if (ws->y < 0)
1399 ws->y = 0;
1403 static void
1404 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1406 if (ws->pixmap != None)
1408 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1409 ws->pixmap = None;
1411 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1412 ws->width, ws->height,
1413 DefaultDepthOfScreen (XtScreen (ws->w)));
1414 #ifdef HAVE_XFT
1415 if (ws->xft_draw)
1416 XftDrawDestroy (ws->xft_draw);
1417 if (mw->menu.xft_font)
1419 int screen = XScreenNumberOfScreen (mw->core.screen);
1420 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1421 ws->pixmap,
1422 DefaultVisual (XtDisplay (ws->w), screen),
1423 mw->core.colormap);
1425 else
1426 ws->xft_draw = 0;
1427 #endif
1430 /* Updates old_stack from new_stack and redisplays. */
1431 static void
1432 remap_menubar (XlwMenuWidget mw)
1434 int i;
1435 int last_same;
1436 XPoint selection_position;
1437 int old_depth = mw->menu.old_depth;
1438 int new_depth = mw->menu.new_depth;
1439 widget_value** old_stack;
1440 widget_value** new_stack;
1441 window_state* windows;
1442 widget_value* old_selection;
1443 widget_value* new_selection;
1445 /* Check that enough windows and old_stack are ready. */
1446 make_windows_if_needed (mw, new_depth);
1447 make_old_stack_space (mw, new_depth);
1448 windows = mw->menu.windows;
1449 old_stack = mw->menu.old_stack;
1450 new_stack = mw->menu.new_stack;
1452 /* compute the last identical different entry */
1453 for (i = 1; i < old_depth && i < new_depth; i++)
1454 if (old_stack [i] != new_stack [i])
1455 break;
1456 last_same = i - 1;
1458 /* Memorize the previously selected item to be able to refresh it */
1459 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1460 if (old_selection && !old_selection->enabled)
1461 old_selection = NULL;
1462 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1463 if (new_selection && !new_selection->enabled)
1464 new_selection = NULL;
1466 /* Call callback when the highlighted item changes. */
1467 if (old_selection || new_selection)
1468 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1469 (XtPointer) new_selection);
1471 /* updates old_state from new_state. It has to be done now because
1472 display_menu (called below) uses the old_stack to know what to display. */
1473 for (i = last_same + 1; i < new_depth; i++)
1475 XtPopdown (mw->menu.windows [i].w);
1476 old_stack [i] = new_stack [i];
1478 mw->menu.old_depth = new_depth;
1480 /* refresh the last selection */
1481 selection_position.x = 0;
1482 selection_position.y = 0;
1483 display_menu (mw, last_same, new_selection == old_selection,
1484 &selection_position, NULL, NULL);
1486 /* Now place the new menus. */
1487 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1489 window_state *previous_ws = &windows[i - 1];
1490 window_state *ws = &windows[i];
1492 ws->x = (previous_ws->x + selection_position.x
1493 + mw->menu.shadow_thickness);
1494 if (mw->menu.horizontal && i == 1)
1495 ws->x += mw->menu.margin;
1497 #if 0
1498 if (!mw->menu.horizontal || i > 1)
1499 ws->x += mw->menu.shadow_thickness;
1500 #endif
1502 ws->y = (previous_ws->y + selection_position.y
1503 + mw->menu.shadow_thickness);
1504 if (mw->menu.horizontal && i == 1)
1505 ws->y += mw->menu.margin;
1507 /* WMs like Gnome 3 ignores requests to move windows. So we
1508 must destroy the current one and create a new to get it to move. */
1509 XtUnrealizeWidget (ws->w);
1510 XtRealizeWidget (ws->w);
1511 ws->window = XtWindow (ws->w);
1513 size_menu (mw, i);
1515 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1517 create_pixmap_for_menu (ws, mw);
1518 XtConfigureWidget (ws->w, ws->x, ws->y, ws->width, ws->height,
1519 ws->w->core.border_width);
1520 display_menu (mw, i, False, &selection_position, NULL, NULL);
1521 XtPopup (ws->w, XtGrabNone);
1524 /* unmap the menus that popped down */
1525 for (i = new_depth - 1; i < old_depth; i++)
1526 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1527 XtPopdown (windows[i].w);
1530 static Boolean
1531 motion_event_is_in_menu (XlwMenuWidget mw,
1532 XMotionEvent *ev,
1533 int level,
1534 XPoint *relative_pos)
1536 window_state* ws = &mw->menu.windows [level];
1537 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1538 int x = ws->x + shadow;
1539 int y = ws->y + shadow;
1540 relative_pos->x = ev->x_root - x;
1541 relative_pos->y = ev->y_root - y;
1542 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1543 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1546 static Boolean
1547 map_event_to_widget_value (XlwMenuWidget mw,
1548 XMotionEvent *ev,
1549 widget_value **val,
1550 int *level)
1552 int i;
1553 XPoint relative_pos;
1554 window_state* ws;
1555 int inside = 0;
1557 *val = NULL;
1559 /* Find the window */
1560 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1562 ws = &mw->menu.windows [i];
1563 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1565 inside = 1;
1566 display_menu (mw, i, True, NULL, &relative_pos, val);
1568 if (*val)
1570 *level = i + 1;
1571 return True;
1576 if (!inside)
1578 if (mw->menu.inside_entry != NULL)
1579 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1580 (XtPointer) mw->menu.inside_entry);
1581 mw->menu.inside_entry = NULL;
1584 return False;
1587 \f/* Procedures */
1588 static void
1589 make_drawing_gcs (XlwMenuWidget mw)
1591 XGCValues xgcv;
1592 float scale;
1593 XtGCMask mask = GCForeground | GCBackground;
1595 #ifdef HAVE_X_I18N
1596 if (!mw->menu.fontSet && mw->menu.font)
1598 xgcv.font = mw->menu.font->fid;
1599 mask |= GCFont;
1601 #else
1602 if (mw->menu.font)
1604 xgcv.font = mw->menu.font->fid;
1605 mask |= GCFont;
1607 #endif
1608 xgcv.foreground = mw->menu.foreground;
1609 xgcv.background = mw->core.background_pixel;
1610 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1612 xgcv.foreground = mw->menu.button_foreground;
1613 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1615 xgcv.background = mw->core.background_pixel;
1617 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1619 /* Allocate color for disabled menu-items. */
1620 mw->menu.disabled_foreground = mw->menu.foreground;
1621 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1622 scale = 2.3;
1623 else
1624 scale = 0.55;
1626 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1627 mw->core.colormap,
1628 &mw->menu.disabled_foreground,
1629 scale,
1630 0x8000);
1632 if (mw->menu.foreground == mw->menu.disabled_foreground
1633 || mw->core.background_pixel == mw->menu.disabled_foreground)
1635 /* Too few colors, use stipple. */
1636 xgcv.foreground = mw->menu.foreground;
1637 xgcv.fill_style = FillStippled;
1638 xgcv.stipple = mw->menu.gray_pixmap;
1639 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1640 | GCFillStyle | GCStipple, &xgcv);
1642 else
1644 /* Many colors available, use disabled pixel. */
1645 xgcv.foreground = mw->menu.disabled_foreground;
1646 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1649 xgcv.foreground = mw->menu.button_foreground;
1650 xgcv.background = mw->core.background_pixel;
1651 xgcv.fill_style = FillStippled;
1652 xgcv.stipple = mw->menu.gray_pixmap;
1653 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1654 | GCFillStyle | GCStipple, &xgcv);
1656 xgcv.foreground = mw->core.background_pixel;
1657 xgcv.background = mw->menu.foreground;
1658 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1661 static void
1662 release_drawing_gcs (XlwMenuWidget mw)
1664 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1665 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1666 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1667 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1668 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1669 /* let's get some segvs if we try to use these... */
1670 mw->menu.foreground_gc = (GC) -1;
1671 mw->menu.button_gc = (GC) -1;
1672 mw->menu.disabled_gc = (GC) -1;
1673 mw->menu.inactive_button_gc = (GC) -1;
1674 mw->menu.background_gc = (GC) -1;
1677 #ifndef emacs
1678 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1679 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1680 #endif
1682 static void
1683 make_shadow_gcs (XlwMenuWidget mw)
1685 XGCValues xgcv;
1686 unsigned long pm = 0;
1687 Display *dpy = XtDisplay ((Widget) mw);
1688 Screen *screen = XtScreen ((Widget) mw);
1689 Colormap cmap = mw->core.colormap;
1690 XColor topc, botc;
1691 int top_frobbed = 0, bottom_frobbed = 0;
1693 mw->menu.free_top_shadow_color_p = 0;
1694 mw->menu.free_bottom_shadow_color_p = 0;
1696 if (mw->menu.top_shadow_color == -1)
1697 mw->menu.top_shadow_color = mw->core.background_pixel;
1698 else
1699 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1701 if (mw->menu.bottom_shadow_color == -1)
1702 mw->menu.bottom_shadow_color = mw->menu.foreground;
1703 else
1704 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1706 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1707 mw->menu.top_shadow_color == mw->menu.foreground)
1709 topc.pixel = mw->core.background_pixel;
1710 #ifdef emacs
1711 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1712 &topc.pixel,
1713 1.2, 0x8000))
1714 #else
1715 XQueryColor (dpy, cmap, &topc);
1716 /* Don't overflow/wrap! */
1717 topc.red = MINL (65535, topc.red * 1.2);
1718 topc.green = MINL (65535, topc.green * 1.2);
1719 topc.blue = MINL (65535, topc.blue * 1.2);
1720 if (XAllocColor (dpy, cmap, &topc))
1721 #endif
1723 mw->menu.top_shadow_color = topc.pixel;
1724 mw->menu.free_top_shadow_color_p = 1;
1725 top_frobbed = 1;
1728 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1729 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1731 botc.pixel = mw->core.background_pixel;
1732 #ifdef emacs
1733 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1734 &botc.pixel,
1735 0.6, 0x4000))
1736 #else
1737 XQueryColor (dpy, cmap, &botc);
1738 botc.red *= 0.6;
1739 botc.green *= 0.6;
1740 botc.blue *= 0.6;
1741 if (XAllocColor (dpy, cmap, &botc))
1742 #endif
1744 mw->menu.bottom_shadow_color = botc.pixel;
1745 mw->menu.free_bottom_shadow_color_p = 1;
1746 bottom_frobbed = 1;
1750 if (top_frobbed && bottom_frobbed)
1752 if (topc.pixel == botc.pixel)
1754 if (botc.pixel == mw->menu.foreground)
1756 if (mw->menu.free_top_shadow_color_p)
1758 x_free_dpy_colors (dpy, screen, cmap,
1759 &mw->menu.top_shadow_color, 1);
1760 mw->menu.free_top_shadow_color_p = 0;
1762 mw->menu.top_shadow_color = mw->core.background_pixel;
1764 else
1766 if (mw->menu.free_bottom_shadow_color_p)
1768 x_free_dpy_colors (dpy, screen, cmap,
1769 &mw->menu.bottom_shadow_color, 1);
1770 mw->menu.free_bottom_shadow_color_p = 0;
1772 mw->menu.bottom_shadow_color = mw->menu.foreground;
1777 if (!mw->menu.top_shadow_pixmap
1778 && mw->menu.top_shadow_color == mw->core.background_pixel)
1780 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1781 if (mw->menu.free_top_shadow_color_p)
1783 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1784 mw->menu.free_top_shadow_color_p = 0;
1786 mw->menu.top_shadow_color = mw->menu.foreground;
1788 if (!mw->menu.bottom_shadow_pixmap
1789 && mw->menu.bottom_shadow_color == mw->core.background_pixel)
1791 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1792 if (mw->menu.free_bottom_shadow_color_p)
1794 x_free_dpy_colors (dpy, screen, cmap,
1795 &mw->menu.bottom_shadow_color, 1);
1796 mw->menu.free_bottom_shadow_color_p = 0;
1798 mw->menu.bottom_shadow_color = mw->menu.foreground;
1801 xgcv.fill_style = FillStippled;
1802 xgcv.foreground = mw->menu.top_shadow_color;
1803 xgcv.stipple = mw->menu.top_shadow_pixmap;
1804 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1805 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1807 xgcv.foreground = mw->menu.bottom_shadow_color;
1808 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1809 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1810 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1814 static void
1815 release_shadow_gcs (XlwMenuWidget mw)
1817 Display *dpy = XtDisplay ((Widget) mw);
1818 Screen *screen = XtScreen ((Widget) mw);
1819 Colormap cmap = mw->core.colormap;
1820 Pixel px[2];
1821 int i = 0;
1823 if (mw->menu.free_top_shadow_color_p)
1824 px[i++] = mw->menu.top_shadow_color;
1825 if (mw->menu.free_bottom_shadow_color_p)
1826 px[i++] = mw->menu.bottom_shadow_color;
1827 if (i > 0)
1828 x_free_dpy_colors (dpy, screen, cmap, px, i);
1830 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1831 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1834 #ifdef HAVE_XFT
1835 static XftFont *
1836 getDefaultXftFont (XlwMenuWidget mw)
1838 int screen = XScreenNumberOfScreen (mw->core.screen);
1839 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1842 static int
1843 openXftFont (XlwMenuWidget mw)
1845 char *fname = mw->menu.fontName;
1847 mw->menu.xft_font = 0;
1848 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1850 if (fname && strcmp (fname, "none") != 0)
1852 int screen = XScreenNumberOfScreen (mw->core.screen);
1853 int len = strlen (fname), i = len - 1;
1854 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1855 while (i > 0 && '0' <= fname[i] && fname[i] <= '9')
1856 --i;
1857 if (fname[i] == ' ')
1859 fname = xstrdup (mw->menu.fontName);
1860 fname[i] = '-';
1863 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1864 if (!mw->menu.xft_font)
1865 mw->menu.xft_font = getDefaultXftFont (mw);
1868 if (fname != mw->menu.fontName) xfree (fname);
1870 return mw->menu.xft_font != 0;
1872 #endif
1874 static void
1875 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1877 /* Get the GCs and the widget size. */
1878 XlwMenuWidget mw = (XlwMenuWidget) w;
1879 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1880 Display* display = XtDisplay (mw);
1882 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1883 mw->menu.cursor = mw->menu.cursor_shape;
1885 mw->menu.gray_pixmap
1886 = XCreatePixmapFromBitmapData (display, window, gray_bits,
1887 gray_width, gray_height,
1888 (unsigned long)1, (unsigned long)0, 1);
1890 #ifdef HAVE_XFT
1891 if (openXftFont (mw))
1893 else
1894 #endif
1896 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1897 if (!mw->menu.font)
1899 mw->menu.font = XLoadQueryFont (display, "fixed");
1900 if (!mw->menu.font)
1902 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1903 emacs_abort ();
1908 #ifdef HAVE_X_I18N
1909 if (mw->menu.fontSet)
1910 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1911 #endif
1913 make_drawing_gcs (mw);
1914 make_shadow_gcs (mw);
1916 mw->menu.popped_up = False;
1918 mw->menu.old_depth = 1;
1919 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1920 mw->menu.old_stack_length = 1;
1921 mw->menu.old_stack [0] = mw->menu.contents;
1923 mw->menu.new_depth = 0;
1924 mw->menu.new_stack = 0;
1925 mw->menu.new_stack_length = 0;
1926 push_new_stack (mw, mw->menu.contents);
1928 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1929 mw->menu.windows_length = 1;
1930 mw->menu.windows [0].x = 0;
1931 mw->menu.windows [0].y = 0;
1932 mw->menu.windows [0].width = 0;
1933 mw->menu.windows [0].height = 0;
1934 mw->menu.windows [0].max_rest_width = 0;
1935 mw->menu.windows [0].pixmap = None;
1936 #ifdef HAVE_XFT
1937 mw->menu.windows [0].xft_draw = 0;
1938 #endif
1939 size_menu (mw, 0);
1941 mw->core.width = mw->menu.windows [0].width;
1942 mw->core.height = mw->menu.windows [0].height;
1945 static void
1946 XlwMenuClassInitialize (void)
1950 static void
1951 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1953 XlwMenuWidget mw = (XlwMenuWidget)w;
1954 XSetWindowAttributes xswa;
1955 int mask;
1957 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1958 (w, valueMask, attributes);
1960 xswa.save_under = True;
1961 xswa.cursor = mw->menu.cursor_shape;
1962 mask = CWSaveUnder | CWCursor;
1963 /* I sometimes get random BadCursor errors while creating the first
1964 frame on a display. I can not find their reason, but they are
1965 annoying so for now let's ignore any errors here. -- lorentey */
1966 #ifdef emacs
1967 x_catch_errors (XtDisplay (w));
1968 #endif
1969 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1970 #ifdef emacs
1971 x_uncatch_errors ();
1972 #endif
1974 mw->menu.windows [0].w = w;
1975 mw->menu.windows [0].window = XtWindow (w);
1976 mw->menu.windows [0].x = w->core.x;
1977 mw->menu.windows [0].y = w->core.y;
1978 mw->menu.windows [0].width = w->core.width;
1979 mw->menu.windows [0].height = w->core.height;
1981 set_window_type (mw->menu.windows [0].w, mw);
1982 create_pixmap_for_menu (&mw->menu.windows [0], mw);
1984 #ifdef HAVE_XFT
1985 if (mw->menu.xft_font)
1987 XColor colors[3];
1988 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
1989 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
1990 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
1991 = mw->menu.disabled_foreground;
1992 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
1993 mw->menu.xft_fg.color.alpha = 0xFFFF;
1994 mw->menu.xft_fg.color.red = colors[0].red;
1995 mw->menu.xft_fg.color.green = colors[0].green;
1996 mw->menu.xft_fg.color.blue = colors[0].blue;
1997 mw->menu.xft_bg.color.alpha = 0xFFFF;
1998 mw->menu.xft_bg.color.red = colors[1].red;
1999 mw->menu.xft_bg.color.green = colors[1].green;
2000 mw->menu.xft_bg.color.blue = colors[1].blue;
2001 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2002 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2003 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2004 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2006 #endif
2009 /* Only the toplevel menubar/popup is a widget so it's the only one that
2010 receives expose events through Xt. So we repaint all the other panes
2011 when receiving an Expose event. */
2012 static void
2013 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2015 XlwMenuWidget mw = (XlwMenuWidget)w;
2017 /* If we have a depth beyond 1, it's because a submenu was displayed.
2018 If the submenu has been destroyed, set the depth back to 1. */
2019 if (submenu_destroyed)
2021 mw->menu.old_depth = 1;
2022 submenu_destroyed = 0;
2025 display_menu (mw, 0, False, NULL, NULL, NULL);
2029 /* Part of a hack to make the menu redisplay when a tooltip frame
2030 over a menu item is unmapped. */
2032 void
2033 xlwmenu_redisplay (Widget w)
2035 XlwMenuRedisplay (w, NULL, None);
2038 static void
2039 XlwMenuDestroy (Widget w)
2041 int i;
2042 XlwMenuWidget mw = (XlwMenuWidget) w;
2044 if (pointer_grabbed)
2045 ungrab_all ((Widget)w, CurrentTime);
2046 pointer_grabbed = 0;
2048 submenu_destroyed = 1;
2050 release_drawing_gcs (mw);
2051 release_shadow_gcs (mw);
2053 /* This doesn't come from the resource db but is created explicitly
2054 so we must free it ourselves. */
2055 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2056 mw->menu.gray_pixmap = (Pixmap) -1;
2058 /* Don't free mw->menu.contents because that comes from our creator.
2059 The `*_stack' elements are just pointers into `contents' so leave
2060 that alone too. But free the stacks themselves. */
2061 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2062 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2064 /* Original comment was:
2066 Remember, you can't free anything that came from the resource
2067 database. This includes:
2068 mw->menu.cursor
2069 mw->menu.top_shadow_pixmap
2070 mw->menu.bottom_shadow_pixmap
2071 mw->menu.font
2072 Also the color cells of top_shadow_color, bottom_shadow_color,
2073 foreground, and button_foreground will never be freed until this
2074 client exits. Nice, eh?
2076 But now I can free font without any visible glitches. */
2078 if (mw->menu.font)
2079 XFreeFont (XtDisplay (mw), mw->menu.font);
2081 #ifdef HAVE_XFT
2082 if (mw->menu.windows [0].xft_draw)
2083 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2084 if (mw->menu.xft_font)
2085 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2086 #endif
2088 if (mw->menu.windows [0].pixmap != None)
2089 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2090 /* Start from 1 because the one in slot 0 is w->core.window. */
2091 for (i = 1; i < mw->menu.windows_length; i++)
2093 if (mw->menu.windows [i].pixmap != None)
2094 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2095 #ifdef HAVE_XFT
2096 if (mw->menu.windows [i].xft_draw)
2097 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2098 #endif
2101 if (mw->menu.windows)
2102 XtFree ((char *) mw->menu.windows);
2105 #ifdef HAVE_XFT
2106 static int
2107 fontname_changed (XlwMenuWidget newmw,
2108 XlwMenuWidget oldmw)
2110 /* This will force a new XftFont even if the same string is set.
2111 This is good, as rendering parameters may have changed and
2112 we just want to do a redisplay. */
2113 return newmw->menu.fontName != oldmw->menu.fontName;
2115 #endif
2117 static Boolean
2118 XlwMenuSetValues (Widget current, Widget request, Widget new,
2119 ArgList args, Cardinal *num_args)
2121 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2122 XlwMenuWidget newmw = (XlwMenuWidget)new;
2123 Boolean do_redisplay = False;
2125 if (newmw->menu.contents
2126 && newmw->menu.contents->contents
2127 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2128 do_redisplay = True;
2129 /* Do redisplay if the contents are entirely eliminated. */
2130 if (newmw->menu.contents
2131 && newmw->menu.contents->contents == 0
2132 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2133 do_redisplay = True;
2135 if (newmw->core.background_pixel != oldmw->core.background_pixel
2136 || newmw->menu.foreground != oldmw->menu.foreground
2137 #ifdef HAVE_XFT
2138 || fontname_changed (newmw, oldmw)
2139 #endif
2140 #ifdef HAVE_X_I18N
2141 || newmw->menu.fontSet != oldmw->menu.fontSet
2142 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2143 #else
2144 || newmw->menu.font != oldmw->menu.font
2145 #endif
2148 int i;
2149 release_drawing_gcs (newmw);
2150 make_drawing_gcs (newmw);
2152 release_shadow_gcs (newmw);
2153 /* Cause the shadow colors to be recalculated. */
2154 newmw->menu.top_shadow_color = -1;
2155 newmw->menu.bottom_shadow_color = -1;
2156 make_shadow_gcs (newmw);
2158 do_redisplay = True;
2160 if (XtIsRealized (current))
2161 /* If the menu is currently displayed, change the display. */
2162 for (i = 0; i < oldmw->menu.windows_length; i++)
2164 XSetWindowBackground (XtDisplay (oldmw),
2165 oldmw->menu.windows [i].window,
2166 newmw->core.background_pixel);
2167 /* Clear windows and generate expose events. */
2168 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2169 0, 0, 0, 0, True);
2173 #ifdef HAVE_XFT
2174 if (fontname_changed (newmw, oldmw))
2176 int i;
2177 int screen = XScreenNumberOfScreen (newmw->core.screen);
2178 if (newmw->menu.xft_font)
2179 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2180 openXftFont (newmw);
2181 for (i = 0; i < newmw->menu.windows_length; i++)
2183 if (newmw->menu.windows [i].xft_draw)
2184 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2185 newmw->menu.windows [i].xft_draw = 0;
2187 if (newmw->menu.xft_font)
2188 for (i = 0; i < newmw->menu.windows_length; i++)
2189 newmw->menu.windows [i].xft_draw
2190 = XftDrawCreate (XtDisplay (newmw),
2191 newmw->menu.windows [i].window,
2192 DefaultVisual (XtDisplay (newmw), screen),
2193 newmw->core.colormap);
2195 #endif
2196 #ifdef HAVE_X_I18N
2197 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2199 do_redisplay = True;
2200 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2202 #endif
2204 return do_redisplay;
2207 static void
2208 XlwMenuResize (Widget w)
2210 XlwMenuWidget mw = (XlwMenuWidget)w;
2212 if (mw->menu.popped_up)
2214 /* Don't allow the popup menu to resize itself. */
2215 mw->core.width = mw->menu.windows [0].width;
2216 mw->core.height = mw->menu.windows [0].height;
2217 mw->core.parent->core.width = mw->core.width;
2218 mw->core.parent->core.height = mw->core.height;
2220 else
2222 mw->menu.windows [0].width = mw->core.width;
2223 mw->menu.windows [0].height = mw->core.height;
2224 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2228 \f/* Action procedures */
2229 static void
2230 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2232 widget_value* val;
2233 int level;
2235 if (!map_event_to_widget_value (mw, ev, &val, &level))
2236 pop_new_stack_if_no_contents (mw);
2237 else
2238 set_new_state (mw, val, level);
2239 remap_menubar (mw);
2241 /* Sync with the display. Makes it feel better on X terms. */
2242 XSync (XtDisplay (mw), False);
2245 static void
2246 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2248 int x = ev->x_root;
2249 int y = ev->y_root;
2250 int state = ev->state;
2251 XMotionEvent oldev = *ev;
2253 /* Allow motion events to be generated again. */
2254 if (ev->is_hint
2255 && XQueryPointer (XtDisplay (mw), ev->window,
2256 &ev->root, &ev->subwindow,
2257 &ev->x_root, &ev->y_root,
2258 &ev->x, &ev->y,
2259 &ev->state)
2260 && ev->state == state
2261 && (ev->x_root != x || ev->y_root != y))
2262 handle_single_motion_event (mw, ev);
2263 else
2264 handle_single_motion_event (mw, &oldev);
2267 static void
2268 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2270 XlwMenuWidget mw = (XlwMenuWidget)w;
2272 if (!mw->menu.popped_up)
2274 menu_post_event = *ev;
2275 /* If event is set to CurrentTime, get the last known time stamp.
2276 This is for calculating if (popup) menus should stay up after
2277 a fast click. */
2278 if (menu_post_event.xbutton.time == CurrentTime)
2279 menu_post_event.xbutton.time
2280 = XtLastTimestampProcessed (XtDisplay (w));
2282 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2284 else
2286 /* If we push a button while the menu is posted semipermanently,
2287 releasing the button should always pop the menu down. */
2288 next_release_must_exit = 1;
2290 /* Notes the absolute position of the menubar window. */
2291 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2292 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2294 /* Handles the down like a move, slots are compatible. */
2295 ev->xmotion.is_hint = 0;
2296 handle_motion_event (mw, &ev->xmotion);
2300 static void
2301 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2303 XlwMenuWidget mw = (XlwMenuWidget)w;
2304 if (mw->menu.popped_up)
2305 handle_motion_event (mw, &ev->xmotion);
2308 /* Do nothing.
2309 This is how we handle presses and releases of modifier keys. */
2310 static void
2311 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2315 static widget_value *
2316 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2318 widget_value *current = item;
2319 enum menu_separator separator;
2321 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2322 || (skip_titles && !current->call_data && !current->contents))
2323 if (current->next)
2324 current = current->next;
2325 else
2326 return NULL;
2328 return current;
2331 static widget_value *
2332 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2334 widget_value *current = item;
2335 enum menu_separator separator;
2337 while (current->next && (current = current->next)
2338 && (lw_separator_p (current->name, &separator, 0) || !current->enabled
2339 || (skip_titles && !current->call_data && !current->contents)))
2342 if (current == item)
2344 if (mw->menu.old_depth < 2)
2345 return current;
2346 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2348 while (lw_separator_p (current->name, &separator, 0)
2349 || !current->enabled
2350 || (skip_titles && !current->call_data
2351 && !current->contents))
2353 if (current->next)
2354 current = current->next;
2356 if (current == item)
2357 break;
2362 return current;
2365 static widget_value *
2366 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2368 widget_value *current = item;
2369 widget_value *prev = item;
2371 while ((current = find_next_selectable (mw, current, skip_titles))
2372 != item)
2374 if (prev == current)
2375 break;
2376 prev = current;
2379 return prev;
2382 static void
2383 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2385 XlwMenuWidget mw = (XlwMenuWidget) w;
2386 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2387 int popup_menu_p = mw->menu.top_depth == 1;
2389 /* Inside top-level menu-bar? */
2390 if (mw->menu.old_depth == mw->menu.top_depth)
2391 /* When <down> in the menu-bar is pressed, display the corresponding
2392 sub-menu and select the first selectable menu item there.
2393 If this is a popup menu, skip title item of the popup. */
2394 set_new_state (mw,
2395 find_first_selectable (mw,
2396 selected_item->contents,
2397 popup_menu_p),
2398 mw->menu.old_depth);
2399 else
2400 /* Highlight next possible (enabled and not separator) menu item. */
2401 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2402 mw->menu.old_depth - 1);
2404 remap_menubar (mw);
2407 static void
2408 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2410 XlwMenuWidget mw = (XlwMenuWidget) w;
2411 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2412 int popup_menu_p = mw->menu.top_depth == 1;
2414 /* Inside top-level menu-bar? */
2415 if (mw->menu.old_depth == mw->menu.top_depth)
2417 /* FIXME: this is tricky. <up> in the menu-bar should select the
2418 last selectable item in the list. So we select the first
2419 selectable one and find the previous selectable item. Is there
2420 a better way? */
2421 /* If this is a popup menu, skip title item of the popup. */
2422 set_new_state (mw,
2423 find_first_selectable (mw,
2424 selected_item->contents,
2425 popup_menu_p),
2426 mw->menu.old_depth);
2427 remap_menubar (mw);
2428 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2429 set_new_state (mw,
2430 find_prev_selectable (mw,
2431 selected_item,
2432 popup_menu_p),
2433 mw->menu.old_depth - 1);
2435 else
2436 /* Highlight previous (enabled and not separator) menu item. */
2437 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2438 mw->menu.old_depth - 1);
2440 remap_menubar (mw);
2443 void
2444 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2446 XlwMenuWidget mw = (XlwMenuWidget) w;
2447 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2449 /* Inside top-level menu-bar? */
2450 if (mw->menu.old_depth == mw->menu.top_depth)
2451 /* When <left> in the menu-bar is pressed, display the previous item on
2452 the menu-bar. If the current item is the first one, highlight the
2453 last item in the menubar (probably Help). */
2454 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2455 mw->menu.old_depth - 1);
2456 else if (mw->menu.old_depth == 1
2457 && selected_item->contents) /* Is this menu item expandable? */
2459 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2460 remap_menubar (mw);
2461 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2462 if (!selected_item->enabled && find_first_selectable (mw,
2463 selected_item,
2465 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2466 mw->menu.old_depth - 1);
2469 else
2471 pop_new_stack_if_no_contents (mw);
2472 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2473 mw->menu.old_depth - 2);
2476 remap_menubar (mw);
2479 void
2480 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2482 XlwMenuWidget mw = (XlwMenuWidget) w;
2483 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2485 /* Inside top-level menu-bar? */
2486 if (mw->menu.old_depth == mw->menu.top_depth)
2487 /* When <right> in the menu-bar is pressed, display the next item on
2488 the menu-bar. If the current item is the last one, highlight the
2489 first item (probably File). */
2490 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2491 mw->menu.old_depth - 1);
2492 else if (selected_item->contents) /* Is this menu item expandable? */
2494 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2495 remap_menubar (mw);
2496 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2497 if (!selected_item->enabled && find_first_selectable (mw,
2498 selected_item,
2500 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2501 mw->menu.old_depth - 1);
2503 else
2505 pop_new_stack_if_no_contents (mw);
2506 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2507 mw->menu.old_depth - 2);
2510 remap_menubar (mw);
2513 /* Handle key press and release events while menu is popped up.
2514 Our action is to get rid of the menu. */
2515 static void
2516 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2518 XlwMenuWidget mw = (XlwMenuWidget)w;
2520 /* Pop down everything. */
2521 mw->menu.new_depth = 1;
2522 remap_menubar (mw);
2524 if (mw->menu.popped_up)
2526 mw->menu.popped_up = False;
2527 ungrab_all ((Widget)mw, ev->xmotion.time);
2528 if (XtIsShell (XtParent ((Widget) mw)))
2529 XtPopdown (XtParent ((Widget) mw));
2530 else
2532 XtRemoveGrab ((Widget) mw);
2533 display_menu (mw, 0, False, NULL, NULL, NULL);
2537 /* callback */
2538 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2541 static void
2542 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2544 XlwMenuWidget mw = (XlwMenuWidget)w;
2545 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2547 /* If user releases the button quickly, without selecting anything,
2548 after the initial down-click that brought the menu up,
2549 do nothing. */
2550 if ((selected_item == 0
2551 || ((widget_value *) selected_item)->call_data == 0)
2552 && !next_release_must_exit
2553 && (ev->xbutton.time - menu_post_event.xbutton.time
2554 < XtGetMultiClickTime (XtDisplay (w))))
2555 return;
2557 /* Pop down everything. */
2558 mw->menu.new_depth = 1;
2559 remap_menubar (mw);
2561 if (mw->menu.popped_up)
2563 mw->menu.popped_up = False;
2564 ungrab_all ((Widget)mw, ev->xmotion.time);
2565 if (XtIsShell (XtParent ((Widget) mw)))
2566 XtPopdown (XtParent ((Widget) mw));
2567 else
2569 XtRemoveGrab ((Widget) mw);
2570 display_menu (mw, 0, False, NULL, NULL, NULL);
2574 /* callback */
2575 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2579 \f/* Special code to pop-up a menu. */
2580 static void
2581 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2583 int x = event->x_root;
2584 int y = event->y_root;
2585 int w;
2586 int h;
2587 int borderwidth = mw->menu.shadow_thickness;
2588 Screen* screen = XtScreen (mw);
2589 Display *display = XtDisplay (mw);
2591 next_release_must_exit = 0;
2593 mw->menu.inside_entry = NULL;
2594 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2596 if (XtIsShell (XtParent ((Widget)mw)))
2597 size_menu (mw, 0);
2599 w = mw->menu.windows [0].width;
2600 h = mw->menu.windows [0].height;
2602 x -= borderwidth;
2603 y -= borderwidth;
2604 if (x < borderwidth)
2605 x = borderwidth;
2606 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2607 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2608 if (y < borderwidth)
2609 y = borderwidth;
2610 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2611 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2613 mw->menu.popped_up = True;
2614 if (XtIsShell (XtParent ((Widget)mw)))
2616 /* fprintf (stderr, "Config %d %d\n", x, y); */
2617 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2618 XtParent ((Widget)mw)->core.border_width);
2619 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2620 display_menu (mw, 0, False, NULL, NULL, NULL);
2621 mw->menu.windows [0].x = x + borderwidth;
2622 mw->menu.windows [0].y = y + borderwidth;
2623 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1. */
2625 else
2627 XEvent *ev = (XEvent *) event;
2629 XtAddGrab ((Widget) mw, True, True);
2631 /* Notes the absolute position of the menubar window. */
2632 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2633 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2634 mw->menu.top_depth = 2;
2637 #ifdef emacs
2638 x_catch_errors (display);
2639 #endif
2640 if (XtGrabPointer ((Widget)mw, False,
2641 (PointerMotionMask
2642 | PointerMotionHintMask
2643 | ButtonReleaseMask
2644 | ButtonPressMask),
2645 GrabModeAsync, GrabModeAsync, None,
2646 mw->menu.cursor_shape,
2647 event->time) == Success)
2649 if (! GRAB_KEYBOARD
2650 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2651 GrabModeAsync, event->time) == Success)
2653 XtSetKeyboardFocus((Widget)mw, None);
2654 pointer_grabbed = 1;
2656 else
2657 XtUngrabPointer ((Widget)mw, event->time);
2660 #ifdef emacs
2661 if (x_had_errors_p (display))
2663 pointer_grabbed = 0;
2664 XtUngrabPointer ((Widget)mw, event->time);
2666 x_uncatch_errors ();
2667 #endif
2669 ((XMotionEvent*)event)->is_hint = 0;
2670 handle_motion_event (mw, (XMotionEvent*)event);