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