Update copyright year to 2015
[emacs.git] / lwlib / xlwmenu.c
blob4cd0e42b8953b0a3271f573ca236b0d51c44b0b4
1 /* Implements a lightweight menubar widget.
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2015 Free Software Foundation, Inc.
6 This file is part of the Lucid Widget Library.
8 The Lucid Widget Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2, or (at your option)
11 any later version.
13 The Lucid Widget Library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
21 /* Created by devin@lucid.com */
23 #include <config.h>
25 #include <setjmp.h>
26 #include <lisp.h>
28 #include <stdio.h>
30 #include <sys/types.h>
31 #if (defined __sun) && !(defined SUNOS41)
32 #define SUNOS41
33 #include <X11/Xos.h>
34 #undef SUNOS41
35 #else
36 #include <X11/Xos.h>
37 #endif
38 #include <X11/IntrinsicP.h>
39 #include <X11/ObjectP.h>
40 #include <X11/StringDefs.h>
41 #include <X11/cursorfont.h>
42 #include <X11/Shell.h>
43 #include "xlwmenuP.h"
45 #ifdef emacs
47 #include <xterm.h>
48 #include "bitmaps/gray.xbm"
50 #else /* not emacs */
52 #include <X11/bitmaps/gray>
54 #endif /* not emacs */
56 static int pointer_grabbed;
57 static XEvent menu_post_event;
59 static char
60 xlwMenuTranslations [] =
61 "<BtnDown>: start()\n\
62 <Motion>: drag()\n\
63 <BtnUp>: select()\n\
64 <Key>Shift_L: nothing()\n\
65 <Key>Shift_R: nothing()\n\
66 <Key>Meta_L: nothing()\n\
67 <Key>Meta_R: nothing()\n\
68 <Key>Control_L: nothing()\n\
69 <Key>Control_R: nothing()\n\
70 <Key>Hyper_L: nothing()\n\
71 <Key>Hyper_R: nothing()\n\
72 <Key>Super_L: nothing()\n\
73 <Key>Super_R: nothing()\n\
74 <Key>Alt_L: nothing()\n\
75 <Key>Alt_R: nothing()\n\
76 <Key>Caps_Lock: nothing()\n\
77 <Key>Shift_Lock: nothing()\n\
78 <KeyUp>Shift_L: nothing()\n\
79 <KeyUp>Shift_R: nothing()\n\
80 <KeyUp>Meta_L: nothing()\n\
81 <KeyUp>Meta_R: nothing()\n\
82 <KeyUp>Control_L: nothing()\n\
83 <KeyUp>Control_R: nothing()\n\
84 <KeyUp>Hyper_L: nothing()\n\
85 <KeyUp>Hyper_R: nothing()\n\
86 <KeyUp>Super_L: nothing()\n\
87 <KeyUp>Super_R: nothing()\n\
88 <KeyUp>Alt_L: nothing()\n\
89 <KeyUp>Alt_R: nothing()\n\
90 <KeyUp>Caps_Lock: nothing()\n\
91 <KeyUp>Shift_Lock:nothing()\n\
92 <Key>Return: select()\n\
93 <Key>Down: down()\n\
94 <Key>Up: up()\n\
95 <Key>Left: left()\n\
96 <Key>Right: right()\n\
97 <Key>: key()\n\
98 <KeyUp>: key()\n\
101 /* FIXME: Space should toggle togglable menu item but not remove the menu
102 so you can toggle the next one without entering the menu again. */
104 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
106 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
108 #define offset(field) XtOffset(XlwMenuWidget, field)
109 static XtResource
110 xlwMenuResources[] =
112 #ifdef HAVE_X_I18N
113 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
114 offset(menu.fontSet), XtRFontSet, NULL},
115 #endif
116 #ifdef HAVE_XFT
117 #define DEFAULT_FONTNAME "Sans-10"
118 #else
119 #define DEFAULT_FONTNAME "XtDefaultFont"
120 #endif
121 {XtNfont, XtCFont, XtRString, sizeof(String),
122 offset(menu.fontName), XtRString, DEFAULT_FONTNAME },
123 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
124 offset(menu.foreground), XtRString, "XtDefaultForeground"},
125 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
126 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
127 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
128 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
129 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
130 offset(menu.margin), XtRImmediate, (XtPointer)1},
131 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
132 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
133 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
134 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
135 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
136 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
138 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
139 sizeof (Dimension), offset (menu.shadow_thickness),
140 XtRImmediate, (XtPointer)1},
141 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
142 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
143 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
144 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
145 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
146 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
147 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
148 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
150 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
151 offset(menu.open), XtRCallback, (XtPointer)NULL},
152 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
153 offset(menu.select), XtRCallback, (XtPointer)NULL},
154 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
155 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
156 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
157 offset(menu.enter), XtRCallback, (XtPointer)NULL},
158 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
159 offset(menu.leave), XtRCallback, (XtPointer)NULL},
160 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
161 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
162 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
163 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
164 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
165 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
167 #undef offset
169 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
170 ArgList args, Cardinal *num_args);
171 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
172 static void XlwMenuResize(Widget w);
173 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
174 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
175 static void XlwMenuDestroy(Widget w);
176 static void XlwMenuClassInitialize(void);
177 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
178 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
179 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
180 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
181 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
182 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
183 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
184 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
185 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
186 static int separator_height (enum menu_separator);
187 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
189 static XtActionsRec
190 xlwMenuActionsList [] =
192 {"start", Start},
193 {"drag", Drag},
194 {"down", Down},
195 {"up", Up},
196 {"left", Left},
197 {"right", Right},
198 {"select", Select},
199 {"key", Key},
200 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
201 {"nothing", Nothing},
204 #define SuperClass ((CoreWidgetClass)&coreClassRec)
206 XlwMenuClassRec xlwMenuClassRec =
208 { /* CoreClass fields initialization */
209 (WidgetClass) SuperClass, /* superclass */
210 "XlwMenu", /* class_name */
211 sizeof(XlwMenuRec), /* size */
212 XlwMenuClassInitialize, /* class_initialize */
213 NULL, /* class_part_initialize */
214 FALSE, /* class_inited */
215 XlwMenuInitialize, /* initialize */
216 NULL, /* initialize_hook */
217 XlwMenuRealize, /* realize */
218 xlwMenuActionsList, /* actions */
219 XtNumber(xlwMenuActionsList), /* num_actions */
220 xlwMenuResources, /* resources */
221 XtNumber(xlwMenuResources), /* resource_count */
222 NULLQUARK, /* xrm_class */
223 TRUE, /* compress_motion */
224 XtExposeCompressMaximal, /* compress_exposure */
225 TRUE, /* compress_enterleave */
226 FALSE, /* visible_interest */
227 XlwMenuDestroy, /* destroy */
228 XlwMenuResize, /* resize */
229 XlwMenuRedisplay, /* expose */
230 XlwMenuSetValues, /* set_values */
231 NULL, /* set_values_hook */
232 XtInheritSetValuesAlmost, /* set_values_almost */
233 NULL, /* get_values_hook */
234 NULL, /* accept_focus */
235 XtVersion, /* version */
236 NULL, /* callback_private */
237 xlwMenuTranslations, /* tm_table */
238 XtInheritQueryGeometry, /* query_geometry */
239 XtInheritDisplayAccelerator, /* display_accelerator */
240 NULL /* extension */
241 }, /* XlwMenuClass fields initialization */
243 0 /* dummy */
247 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
249 int submenu_destroyed;
251 /* For debug, if installation-directory is non-nil this is not an installed
252 Emacs. In that case we do not grab the keyboard to make it easier to
253 debug. */
254 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
256 static int next_release_must_exit;
258 \f/* Utilities */
260 /* Ungrab pointer and keyboard */
261 static void
262 ungrab_all (Widget w, Time ungrabtime)
264 XtUngrabPointer (w, ungrabtime);
265 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
268 /* Like abort, but remove grabs from widget W before. */
270 static _Noreturn void
271 abort_gracefully (Widget w)
273 if (XtIsShell (XtParent (w)))
274 XtRemoveGrab (w);
275 ungrab_all (w, CurrentTime);
276 abort ();
279 static void
280 push_new_stack (XlwMenuWidget mw, widget_value *val)
282 if (!mw->menu.new_stack)
284 mw->menu.new_stack_length = 10;
285 mw->menu.new_stack =
286 (widget_value**)XtCalloc (mw->menu.new_stack_length,
287 sizeof (widget_value*));
289 else if (mw->menu.new_depth == mw->menu.new_stack_length)
291 mw->menu.new_stack_length *= 2;
292 mw->menu.new_stack =
293 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
294 mw->menu.new_stack_length * sizeof (widget_value*));
296 mw->menu.new_stack [mw->menu.new_depth++] = val;
299 static void
300 pop_new_stack_if_no_contents (XlwMenuWidget mw)
302 if (mw->menu.new_depth > 1)
304 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
305 mw->menu.new_depth -= 1;
309 static void
310 make_old_stack_space (XlwMenuWidget mw, int n)
312 if (!mw->menu.old_stack)
314 mw->menu.old_stack_length = 10;
315 mw->menu.old_stack =
316 (widget_value**)XtCalloc (mw->menu.old_stack_length,
317 sizeof (widget_value*));
319 else if (mw->menu.old_stack_length < n)
321 mw->menu.old_stack_length *= 2;
322 mw->menu.old_stack =
323 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
324 mw->menu.old_stack_length * sizeof (widget_value*));
328 \f/* Size code */
329 static int
330 string_width (XlwMenuWidget mw, char *s)
332 XCharStruct xcs;
333 int drop;
334 #ifdef HAVE_XFT
335 if (mw->menu.xft_font)
337 XGlyphInfo gi;
338 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
339 (FcChar8 *) s,
340 strlen (s), &gi);
341 return gi.width;
343 #endif
344 #ifdef HAVE_X_I18N
345 if (mw->menu.fontSet)
347 XRectangle ink, logical;
348 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
349 return logical.width;
351 #endif
353 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
354 return xcs.width;
358 #ifdef HAVE_XFT
359 #define MENU_FONT_HEIGHT(mw) \
360 ((mw)->menu.xft_font != NULL \
361 ? (mw)->menu.xft_font->height \
362 : ((mw)->menu.fontSet != NULL \
363 ? (mw)->menu.font_extents->max_logical_extent.height \
364 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
365 #define MENU_FONT_ASCENT(mw) \
366 ((mw)->menu.xft_font != NULL \
367 ? (mw)->menu.xft_font->ascent \
368 : ((mw)->menu.fontSet != NULL \
369 ? - (mw)->menu.font_extents->max_logical_extent.y \
370 : (mw)->menu.font->ascent))
371 #else
372 #ifdef HAVE_X_I18N
373 #define MENU_FONT_HEIGHT(mw) \
374 ((mw)->menu.fontSet != NULL \
375 ? (mw)->menu.font_extents->max_logical_extent.height \
376 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
377 #define MENU_FONT_ASCENT(mw) \
378 ((mw)->menu.fontSet != NULL \
379 ? - (mw)->menu.font_extents->max_logical_extent.y \
380 : (mw)->menu.font->ascent)
381 #else
382 #define MENU_FONT_HEIGHT(mw) \
383 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
384 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
385 #endif
386 #endif
388 static int
389 arrow_width (XlwMenuWidget mw)
391 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
394 /* Return the width of toggle buttons of widget MW. */
396 static int
397 toggle_button_width (XlwMenuWidget mw)
399 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
403 /* Return the width of radio buttons of widget MW. */
405 static int
406 radio_button_width (XlwMenuWidget mw)
408 return toggle_button_width (mw) * 1.41;
412 static XtResource
413 nameResource[] =
415 {"labelString", "LabelString", XtRString, sizeof(String),
416 0, XtRImmediate, 0},
419 static char*
420 resource_widget_value (XlwMenuWidget mw, widget_value *val)
422 if (!val->toolkit_data)
424 char* resourced_name = NULL;
425 char* complete_name;
426 XtGetSubresources ((Widget) mw,
427 (XtPointer) &resourced_name,
428 val->name, val->name,
429 nameResource, 1, NULL, 0);
430 if (!resourced_name)
431 resourced_name = val->name;
432 if (!val->value)
434 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
435 strcpy (complete_name, resourced_name);
437 else
439 int complete_length =
440 strlen (resourced_name) + strlen (val->value) + 2;
441 complete_name = XtMalloc (complete_length);
442 char *z = stpcpy (complete_name, resourced_name);
443 *z++ = ' ';
444 strcpy (z, val->value);
447 val->toolkit_data = complete_name;
448 val->free_toolkit_data = True;
450 return (char*)val->toolkit_data;
453 /* Returns the sizes of an item */
454 static void
455 size_menu_item (XlwMenuWidget mw,
456 widget_value* val,
457 int horizontal_p,
458 int* label_width,
459 int* rest_width,
460 int* button_width,
461 int* height)
463 enum menu_separator separator;
465 if (lw_separator_p (val->name, &separator, 0))
467 *height = separator_height (separator);
468 *label_width = 1;
469 *rest_width = 0;
470 *button_width = 0;
472 else
474 *height = MENU_FONT_HEIGHT (mw)
475 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
477 *label_width =
478 string_width (mw, resource_widget_value (mw, val))
479 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
481 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
482 if (!horizontal_p)
484 if (val->contents)
485 /* Add width of the arrow displayed for submenus. */
486 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
487 else if (val->key)
488 /* Add width of key equivalent string. */
489 *rest_width += (string_width (mw, val->key)
490 + mw->menu.arrow_spacing);
492 if (val->button_type == BUTTON_TYPE_TOGGLE)
493 *button_width = (toggle_button_width (mw)
494 + mw->menu.horizontal_spacing);
495 else if (val->button_type == BUTTON_TYPE_RADIO)
496 *button_width = (radio_button_width (mw)
497 + mw->menu.horizontal_spacing);
502 static void
503 size_menu (XlwMenuWidget mw, int level)
505 int label_width = 0;
506 int rest_width = 0;
507 int button_width = 0;
508 int max_rest_width = 0;
509 int max_button_width = 0;
510 int height = 0;
511 int horizontal_p = mw->menu.horizontal && (level == 0);
512 widget_value* val;
513 window_state* ws;
515 if (level >= mw->menu.old_depth)
516 abort_gracefully ((Widget) mw);
518 ws = &mw->menu.windows [level];
519 ws->width = 0;
520 ws->height = 0;
521 ws->label_width = 0;
522 ws->button_width = 0;
524 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
526 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
527 &button_width, &height);
528 if (horizontal_p)
530 ws->width += label_width + rest_width;
531 if (height > ws->height)
532 ws->height = height;
534 else
536 if (label_width > ws->label_width)
537 ws->label_width = label_width;
538 if (rest_width > max_rest_width)
539 max_rest_width = rest_width;
540 if (button_width > max_button_width)
541 max_button_width = button_width;
542 ws->height += height;
546 if (horizontal_p)
547 ws->label_width = ws->button_width = 0;
548 else
550 ws->width = ws->label_width + max_rest_width + max_button_width;
551 ws->button_width = max_button_width;
554 ws->width += 2 * mw->menu.shadow_thickness;
555 ws->height += 2 * mw->menu.shadow_thickness;
556 ws->max_rest_width = max_rest_width;
558 if (horizontal_p)
560 ws->width += 2 * mw->menu.margin;
561 ws->height += 2 * mw->menu.margin;
566 \f/* Display code */
568 static void
569 draw_arrow (XlwMenuWidget mw,
570 Window window,
571 GC gc,
572 int x,
573 int y,
574 int width,
575 int down_p)
577 Display *dpy = XtDisplay (mw);
578 GC top_gc = mw->menu.shadow_top_gc;
579 GC bottom_gc = mw->menu.shadow_bottom_gc;
580 int thickness = mw->menu.shadow_thickness;
581 int height = width;
582 XPoint pt[10];
583 /* alpha = atan (0.5)
584 factor = (1 + sin (alpha)) / cos (alpha) */
585 double factor = 1.62;
586 int thickness2 = thickness * factor;
588 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
590 if (down_p)
592 GC temp;
593 temp = top_gc;
594 top_gc = bottom_gc;
595 bottom_gc = temp;
598 pt[0].x = x;
599 pt[0].y = y + height;
600 pt[1].x = x + thickness;
601 pt[1].y = y + height - thickness2;
602 pt[2].x = x + thickness2;
603 pt[2].y = y + thickness2;
604 pt[3].x = x;
605 pt[3].y = y;
606 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
608 pt[0].x = x;
609 pt[0].y = y;
610 pt[1].x = x + thickness;
611 pt[1].y = y + thickness2;
612 pt[2].x = x + width - thickness2;
613 pt[2].y = y + height / 2;
614 pt[3].x = x + width;
615 pt[3].y = y + height / 2;
616 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
618 pt[0].x = x;
619 pt[0].y = y + height;
620 pt[1].x = x + thickness;
621 pt[1].y = y + height - thickness2;
622 pt[2].x = x + width - thickness2;
623 pt[2].y = y + height / 2;
624 pt[3].x = x + width;
625 pt[3].y = y + height / 2;
626 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
631 static void
632 draw_shadow_rectangle (XlwMenuWidget mw,
633 Window window,
634 int x,
635 int y,
636 int width,
637 int height,
638 int erase_p,
639 int down_p)
641 Display *dpy = XtDisplay (mw);
642 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
643 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
644 int thickness = mw->menu.shadow_thickness;
645 XPoint points [4];
647 if (!erase_p && down_p)
649 GC temp;
650 temp = top_gc;
651 top_gc = bottom_gc;
652 bottom_gc = temp;
655 points [0].x = x;
656 points [0].y = y;
657 points [1].x = x + width;
658 points [1].y = y;
659 points [2].x = x + width - thickness;
660 points [2].y = y + thickness;
661 points [3].x = x;
662 points [3].y = y + thickness;
663 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
664 points [0].x = x;
665 points [0].y = y + thickness;
666 points [1].x = x;
667 points [1].y = y + height;
668 points [2].x = x + thickness;
669 points [2].y = y + height - thickness;
670 points [3].x = x + thickness;
671 points [3].y = y + thickness;
672 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
673 points [0].x = x + width;
674 points [0].y = y;
675 points [1].x = x + width - thickness;
676 points [1].y = y + thickness;
677 points [2].x = x + width - thickness;
678 points [2].y = y + height - thickness;
679 points [3].x = x + width;
680 points [3].y = y + height - thickness;
681 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
682 points [0].x = x;
683 points [0].y = y + height;
684 points [1].x = x + width;
685 points [1].y = y + height;
686 points [2].x = x + width;
687 points [2].y = y + height - thickness;
688 points [3].x = x + thickness;
689 points [3].y = y + height - thickness;
690 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
694 static void
695 draw_shadow_rhombus (XlwMenuWidget mw,
696 Window window,
697 int x,
698 int y,
699 int width,
700 int height,
701 int erase_p,
702 int down_p)
704 Display *dpy = XtDisplay (mw);
705 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
706 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
707 int thickness = mw->menu.shadow_thickness;
708 XPoint points [4];
710 if (!erase_p && down_p)
712 GC temp;
713 temp = top_gc;
714 top_gc = bottom_gc;
715 bottom_gc = temp;
718 points [0].x = x;
719 points [0].y = y + height / 2;
720 points [1].x = x + thickness;
721 points [1].y = y + height / 2;
722 points [2].x = x + width / 2;
723 points [2].y = y + thickness;
724 points [3].x = x + width / 2;
725 points [3].y = y;
726 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
727 points [0].x = x + width / 2;
728 points [0].y = y;
729 points [1].x = x + width / 2;
730 points [1].y = y + thickness;
731 points [2].x = x + width - thickness;
732 points [2].y = y + height / 2;
733 points [3].x = x + width;
734 points [3].y = y + height / 2;
735 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
736 points [0].x = x;
737 points [0].y = y + height / 2;
738 points [1].x = x + thickness;
739 points [1].y = y + height / 2;
740 points [2].x = x + width / 2;
741 points [2].y = y + height - thickness;
742 points [3].x = x + width / 2;
743 points [3].y = y + height;
744 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
745 points [0].x = x + width / 2;
746 points [0].y = y + height;
747 points [1].x = x + width / 2;
748 points [1].y = y + height - thickness;
749 points [2].x = x + width - thickness;
750 points [2].y = y + height / 2;
751 points [3].x = x + width;
752 points [3].y = y + height / 2;
753 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
757 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
758 top-left corner of the menu item. SELECTED_P non-zero means the
759 toggle button is selected. */
761 static void
762 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
764 int width, height;
766 width = toggle_button_width (mw);
767 height = width;
768 x += mw->menu.horizontal_spacing;
769 y += (MENU_FONT_ASCENT (mw) - height) / 2;
770 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
774 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
775 top-left corner of the menu item. SELECTED_P non-zero means the
776 toggle button is selected. */
778 static void
779 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
781 int width, height;
783 width = radio_button_width (mw);
784 height = width;
785 x += mw->menu.horizontal_spacing;
786 y += (MENU_FONT_ASCENT (mw) - height) / 2;
787 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
791 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
792 top-left corner of the menu item. WIDTH is the width of the
793 separator to draw. TYPE is the separator type. */
795 static void
796 draw_separator (XlwMenuWidget mw,
797 Window window,
798 int x,
799 int y,
800 int width,
801 enum menu_separator type)
803 Display *dpy = XtDisplay (mw);
804 XGCValues xgcv;
806 switch (type)
808 case SEPARATOR_NO_LINE:
809 break;
811 case SEPARATOR_SINGLE_LINE:
812 XDrawLine (dpy, window, mw->menu.foreground_gc,
813 x, y, x + width, y);
814 break;
816 case SEPARATOR_DOUBLE_LINE:
817 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
818 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
819 break;
821 case SEPARATOR_SINGLE_DASHED_LINE:
822 xgcv.line_style = LineOnOffDash;
823 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
824 XDrawLine (dpy, window, mw->menu.foreground_gc,
825 x, y, x + width, y);
826 xgcv.line_style = LineSolid;
827 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
828 break;
830 case SEPARATOR_DOUBLE_DASHED_LINE:
831 draw_separator (mw, window, x, y, width,
832 SEPARATOR_SINGLE_DASHED_LINE);
833 draw_separator (mw, window, x, y + 2, width,
834 SEPARATOR_SINGLE_DASHED_LINE);
835 break;
837 case SEPARATOR_SHADOW_ETCHED_IN:
838 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
839 x, y, x + width, y);
840 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
841 x, y + 1, x + width, y + 1);
842 break;
844 case SEPARATOR_SHADOW_ETCHED_OUT:
845 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
846 x, y, x + width, y);
847 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
848 x, y + 1, x + width, y + 1);
849 break;
851 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
852 xgcv.line_style = LineOnOffDash;
853 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
854 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
855 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
856 xgcv.line_style = LineSolid;
857 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
858 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
859 break;
861 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
862 xgcv.line_style = LineOnOffDash;
863 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
864 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
865 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
866 xgcv.line_style = LineSolid;
867 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
868 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
869 break;
871 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
872 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
873 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
874 break;
876 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
877 draw_separator (mw, window, x, y, width,
878 SEPARATOR_SHADOW_ETCHED_OUT);
879 draw_separator (mw, window, x, y + 3, width,
880 SEPARATOR_SHADOW_ETCHED_OUT);
881 break;
883 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
884 xgcv.line_style = LineOnOffDash;
885 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
886 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
887 draw_separator (mw, window, x, y, width,
888 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
889 xgcv.line_style = LineSolid;
890 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
891 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
892 break;
894 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
895 xgcv.line_style = LineOnOffDash;
896 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
897 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
898 draw_separator (mw, window, x, y, width,
899 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
900 xgcv.line_style = LineSolid;
901 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
902 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
903 break;
905 default:
906 abort ();
911 /* Return the pixel height of menu separator SEPARATOR. */
913 static int
914 separator_height (enum menu_separator separator)
916 switch (separator)
918 case SEPARATOR_NO_LINE:
919 return 2;
921 case SEPARATOR_SINGLE_LINE:
922 case SEPARATOR_SINGLE_DASHED_LINE:
923 return 1;
925 case SEPARATOR_DOUBLE_LINE:
926 case SEPARATOR_DOUBLE_DASHED_LINE:
927 return 3;
929 case SEPARATOR_SHADOW_ETCHED_IN:
930 case SEPARATOR_SHADOW_ETCHED_OUT:
931 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
932 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
933 return 2;
935 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
936 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
937 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
938 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
939 return 5;
941 default:
942 abort ();
947 /* Display the menu item and increment where.x and where.y to show how large
948 the menu item was. */
950 static void
951 display_menu_item (XlwMenuWidget mw,
952 widget_value* val,
953 window_state* ws,
954 XPoint* where,
955 Boolean highlighted_p,
956 Boolean horizontal_p,
957 Boolean just_compute_p)
959 GC deco_gc;
960 GC text_gc;
961 int font_height = MENU_FONT_HEIGHT (mw);
962 int font_ascent = MENU_FONT_ASCENT (mw);
963 int shadow = mw->menu.shadow_thickness;
964 int margin = mw->menu.margin;
965 int h_spacing = mw->menu.horizontal_spacing;
966 int v_spacing = mw->menu.vertical_spacing;
967 int label_width;
968 int rest_width;
969 int button_width;
970 int height;
971 int width;
972 enum menu_separator separator;
973 int separator_p = lw_separator_p (val->name, &separator, 0);
974 #ifdef HAVE_XFT
975 XftColor *xftfg;
976 #endif
978 /* compute the sizes of the item */
979 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
980 &button_width, &height);
982 if (horizontal_p)
983 width = label_width + rest_width;
984 else
986 label_width = ws->label_width;
987 width = ws->width - 2 * shadow;
990 /* Only highlight an enabled item that has a callback. */
991 if (highlighted_p)
992 if (!val->enabled || !(val->call_data || val->contents))
993 highlighted_p = 0;
995 /* do the drawing. */
996 if (!just_compute_p)
998 /* Add the shadow border of the containing menu */
999 int x = where->x + shadow;
1000 int y = where->y + shadow;
1002 if (horizontal_p)
1004 x += margin;
1005 y += margin;
1008 /* pick the foreground and background GC. */
1009 if (val->enabled)
1010 text_gc = mw->menu.foreground_gc;
1011 else
1012 text_gc = mw->menu.disabled_gc;
1013 deco_gc = mw->menu.foreground_gc;
1014 #ifdef HAVE_XFT
1015 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1016 #endif
1018 if (separator_p)
1020 draw_separator (mw, ws->pixmap, x, y, width, separator);
1022 else
1024 int x_offset = x + h_spacing + shadow;
1025 char* display_string = resource_widget_value (mw, val);
1026 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1027 False);
1029 /* Deal with centering a menu title. */
1030 if (!horizontal_p && !val->contents && !val->call_data)
1032 int l = string_width (mw, display_string);
1034 if (width > l)
1035 x_offset = (width - l) >> 1;
1037 else if (!horizontal_p && ws->button_width)
1038 x_offset += ws->button_width;
1041 #ifdef HAVE_XFT
1042 if (ws->xft_draw)
1044 int draw_y = y + v_spacing + shadow;
1045 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1046 mw->menu.xft_font,
1047 x_offset, draw_y + font_ascent,
1048 (unsigned char *) display_string,
1049 strlen (display_string));
1051 else
1052 #endif
1053 #ifdef HAVE_X_I18N
1054 if (mw->menu.fontSet)
1055 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1056 text_gc, x_offset,
1057 y + v_spacing + shadow + font_ascent,
1058 display_string, strlen (display_string));
1059 else
1060 #endif
1061 XDrawString (XtDisplay (mw), ws->pixmap,
1062 text_gc, x_offset,
1063 y + v_spacing + shadow + font_ascent,
1064 display_string, strlen (display_string));
1066 if (!horizontal_p)
1068 if (val->button_type == BUTTON_TYPE_TOGGLE)
1069 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1070 val->selected);
1071 else if (val->button_type == BUTTON_TYPE_RADIO)
1072 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1073 val->selected);
1075 if (val->contents)
1077 int a_w = arrow_width (mw);
1078 draw_arrow (mw, ws->pixmap, deco_gc,
1079 x + width - a_w
1080 - mw->menu.horizontal_spacing
1081 - mw->menu.shadow_thickness,
1082 y + v_spacing + shadow, a_w,
1083 highlighted_p);
1085 else if (val->key)
1087 #ifdef HAVE_XFT
1088 if (ws->xft_draw)
1090 int draw_x = ws->width - ws->max_rest_width
1091 + mw->menu.arrow_spacing;
1092 int draw_y = y + v_spacing + shadow + font_ascent;
1093 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1094 mw->menu.xft_font,
1095 draw_x, draw_y,
1096 (unsigned char *) val->key,
1097 strlen (val->key));
1099 else
1100 #endif
1101 #ifdef HAVE_X_I18N
1102 if (mw->menu.fontSet)
1103 XmbDrawString (XtDisplay (mw), ws->pixmap,
1104 mw->menu.fontSet,
1105 text_gc,
1106 x + label_width + mw->menu.arrow_spacing,
1107 y + v_spacing + shadow + font_ascent,
1108 val->key, strlen (val->key));
1109 else
1110 #endif
1111 XDrawString (XtDisplay (mw), ws->pixmap,
1112 text_gc,
1113 x + label_width + mw->menu.arrow_spacing,
1114 y + v_spacing + shadow + font_ascent,
1115 val->key, strlen (val->key));
1118 else
1120 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1121 mw->menu.background_gc,
1122 x + shadow, y + shadow,
1123 label_width + h_spacing - 1,
1124 font_height + 2 * v_spacing - 1);
1125 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1126 True, False);
1129 if (highlighted_p)
1130 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1131 False);
1135 where->x += width;
1136 where->y += height;
1139 static void
1140 display_menu (XlwMenuWidget mw,
1141 int level,
1142 Boolean just_compute_p,
1143 XPoint *highlighted_pos,
1144 XPoint *hit,
1145 widget_value **hit_return)
1147 widget_value* val;
1148 widget_value* following_item;
1149 window_state* ws;
1150 XPoint where;
1151 int horizontal_p = mw->menu.horizontal && (level == 0);
1152 int highlighted_p;
1153 int no_return = 0;
1154 enum menu_separator separator;
1156 if (level >= mw->menu.old_depth)
1157 abort_gracefully ((Widget) mw);
1159 if (level < mw->menu.old_depth - 1)
1160 following_item = mw->menu.old_stack [level + 1];
1161 else
1162 following_item = NULL;
1164 if (hit)
1165 *hit_return = NULL;
1167 where.x = 0;
1168 where.y = 0;
1170 ws = &mw->menu.windows [level];
1172 if (!just_compute_p)
1173 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1174 0, 0, ws->width, ws->height);
1176 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1178 highlighted_p = val == following_item;
1179 if (highlighted_p && highlighted_pos)
1181 if (horizontal_p)
1182 highlighted_pos->x = where.x;
1183 else
1184 highlighted_pos->y = where.y;
1187 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1188 just_compute_p);
1190 if (highlighted_p && highlighted_pos)
1192 if (horizontal_p)
1193 highlighted_pos->y = where.y;
1194 else
1195 highlighted_pos->x = where.x;
1198 if (hit
1199 && !*hit_return
1200 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1201 && !lw_separator_p (val->name, &separator, 0)
1202 && !no_return)
1204 if (val->enabled)
1205 *hit_return = val;
1206 else
1207 no_return = 1;
1208 if (mw->menu.inside_entry != val)
1210 if (mw->menu.inside_entry)
1211 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1212 (XtPointer) mw->menu.inside_entry);
1213 mw->menu.inside_entry = val;
1214 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1215 (XtPointer) mw->menu.inside_entry);
1219 if (horizontal_p)
1220 where.y = 0;
1221 else
1222 where.x = 0;
1225 if (!just_compute_p)
1227 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1228 False, False);
1229 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1230 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1234 \f/* Motion code */
1235 static void
1236 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1238 int i;
1240 mw->menu.new_depth = 0;
1241 for (i = 0; i < level; i++)
1242 push_new_stack (mw, mw->menu.old_stack [i]);
1243 push_new_stack (mw, val);
1246 static void
1247 expose_cb (Widget widget,
1248 XtPointer closure,
1249 XEvent* event,
1250 Boolean* continue_to_dispatch)
1252 XlwMenuWidget mw = (XlwMenuWidget) closure;
1253 int i;
1255 *continue_to_dispatch = False;
1256 for (i = 0; i < mw->menu.windows_length; ++i)
1257 if (mw->menu.windows [i].w == widget) break;
1258 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1259 display_menu (mw, i, False, NULL, NULL, NULL);
1262 static void
1263 set_window_type (Widget w, XlwMenuWidget mw)
1265 int popup_menu_p = mw->menu.top_depth == 1;
1266 Atom type = XInternAtom (XtDisplay (w),
1267 popup_menu_p
1268 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1269 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1270 False);
1272 XChangeProperty (XtDisplay (w), XtWindow (w),
1273 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1274 XA_ATOM, 32, PropModeReplace,
1275 (unsigned char *)&type, 1);
1279 static void
1280 make_windows_if_needed (XlwMenuWidget mw, int n)
1282 int i;
1283 int start_at;
1284 window_state* windows;
1286 if (mw->menu.windows_length >= n)
1287 return;
1289 if (!mw->menu.windows)
1291 mw->menu.windows =
1292 (window_state*)XtMalloc (n * sizeof (window_state));
1293 start_at = 0;
1295 else
1297 mw->menu.windows =
1298 (window_state*)XtRealloc ((char*)mw->menu.windows,
1299 n * sizeof (window_state));
1300 start_at = mw->menu.windows_length;
1302 mw->menu.windows_length = n;
1304 windows = mw->menu.windows;
1306 for (i = start_at; i < n; i++)
1308 Arg av[10];
1309 int ac = 0;
1310 windows [i].x = 0;
1311 windows [i].y = 0;
1312 windows [i].width = 1;
1313 windows [i].height = 1;
1314 windows [i].max_rest_width = 0;
1315 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1316 XtSetArg (av[ac], XtNheight, 1); ++ac;
1317 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1318 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1319 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1320 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1321 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1322 windows [i].w =
1323 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1324 (Widget) mw, av, ac);
1325 XtRealizeWidget (windows [i].w);
1326 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1327 windows [i].window = XtWindow (windows [i].w);
1328 windows [i].pixmap = None;
1329 #ifdef HAVE_XFT
1330 windows [i].xft_draw = 0;
1331 #endif
1332 set_window_type (windows [i].w, mw);
1334 XFlush (XtDisplay (mw));
1337 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1340 xlwmenu_window_p (Widget w, Window window)
1342 XlwMenuWidget mw = (XlwMenuWidget) w;
1343 int i;
1345 for (i = 0; i < mw->menu.windows_length; ++i)
1346 if (window == mw->menu.windows[i].window)
1347 break;
1349 return i < mw->menu.windows_length;
1352 /* Make the window fit in the screen */
1353 static void
1354 fit_to_screen (XlwMenuWidget mw,
1355 window_state *ws,
1356 window_state *previous_ws,
1357 Boolean horizontal_p)
1359 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1360 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1361 /* 1 if we are unable to avoid an overlap between
1362 this menu and the parent menu in the X dimension. */
1363 int horizontal_overlap = 0;
1365 if (ws->x < 0)
1366 ws->x = 0;
1367 else if (ws->x + ws->width > screen_width)
1369 if (!horizontal_p)
1370 /* The addition of shadow-thickness for a sub-menu's position is
1371 to reflect a similar adjustment when the menu is displayed to
1372 the right of the invoking menu-item; it makes the sub-menu
1373 look more `attached' to the menu-item. */
1374 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1375 else
1376 ws->x = screen_width - ws->width;
1377 if (ws->x < 0)
1379 ws->x = 0;
1380 horizontal_overlap = 1;
1383 /* If we overlap in X, try to avoid overlap in Y. */
1384 if (horizontal_overlap
1385 && ws->y < previous_ws->y + previous_ws->height
1386 && previous_ws->y < ws->y + ws->height)
1388 /* Put this menu right below or right above PREVIOUS_WS
1389 if there's room. */
1390 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1391 ws->y = previous_ws->y + previous_ws->height;
1392 else if (previous_ws->y - ws->height > 0)
1393 ws->y = previous_ws->y - ws->height;
1396 if (ws->y < 0)
1397 ws->y = 0;
1398 else if (ws->y + ws->height > screen_height)
1400 if (horizontal_p)
1401 ws->y = previous_ws->y - ws->height;
1402 else
1403 ws->y = screen_height - ws->height;
1404 if (ws->y < 0)
1405 ws->y = 0;
1409 static void
1410 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1412 if (ws->pixmap != None)
1414 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1415 ws->pixmap = None;
1417 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1418 ws->width, ws->height,
1419 DefaultDepthOfScreen (XtScreen (ws->w)));
1420 #ifdef HAVE_XFT
1421 if (ws->xft_draw)
1422 XftDrawDestroy (ws->xft_draw);
1423 if (mw->menu.xft_font)
1425 int screen = XScreenNumberOfScreen (mw->core.screen);
1426 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1427 ws->pixmap,
1428 DefaultVisual (XtDisplay (ws->w), screen),
1429 mw->core.colormap);
1431 else
1432 ws->xft_draw = 0;
1433 #endif
1436 /* Updates old_stack from new_stack and redisplays. */
1437 static void
1438 remap_menubar (XlwMenuWidget mw)
1440 int i;
1441 int last_same;
1442 XPoint selection_position;
1443 int old_depth = mw->menu.old_depth;
1444 int new_depth = mw->menu.new_depth;
1445 widget_value** old_stack;
1446 widget_value** new_stack;
1447 window_state* windows;
1448 widget_value* old_selection;
1449 widget_value* new_selection;
1451 /* Check that enough windows and old_stack are ready. */
1452 make_windows_if_needed (mw, new_depth);
1453 make_old_stack_space (mw, new_depth);
1454 windows = mw->menu.windows;
1455 old_stack = mw->menu.old_stack;
1456 new_stack = mw->menu.new_stack;
1458 /* compute the last identical different entry */
1459 for (i = 1; i < old_depth && i < new_depth; i++)
1460 if (old_stack [i] != new_stack [i])
1461 break;
1462 last_same = i - 1;
1464 /* Memorize the previously selected item to be able to refresh it */
1465 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1466 if (old_selection && !old_selection->enabled)
1467 old_selection = NULL;
1468 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1469 if (new_selection && !new_selection->enabled)
1470 new_selection = NULL;
1472 /* Call callback when the highlighted item changes. */
1473 if (old_selection || new_selection)
1474 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1475 (XtPointer) new_selection);
1477 /* updates old_state from new_state. It has to be done now because
1478 display_menu (called below) uses the old_stack to know what to display. */
1479 for (i = last_same + 1; i < new_depth; i++)
1481 XtPopdown (mw->menu.windows [i].w);
1482 old_stack [i] = new_stack [i];
1484 mw->menu.old_depth = new_depth;
1486 /* refresh the last selection */
1487 selection_position.x = 0;
1488 selection_position.y = 0;
1489 display_menu (mw, last_same, new_selection == old_selection,
1490 &selection_position, NULL, NULL);
1492 /* Now place the new menus. */
1493 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1495 window_state *previous_ws = &windows[i - 1];
1496 window_state *ws = &windows[i];
1498 ws->x = (previous_ws->x + selection_position.x
1499 + mw->menu.shadow_thickness);
1500 if (mw->menu.horizontal && i == 1)
1501 ws->x += mw->menu.margin;
1503 #if 0
1504 if (!mw->menu.horizontal || i > 1)
1505 ws->x += mw->menu.shadow_thickness;
1506 #endif
1508 ws->y = (previous_ws->y + selection_position.y
1509 + mw->menu.shadow_thickness);
1510 if (mw->menu.horizontal && i == 1)
1511 ws->y += mw->menu.margin;
1513 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 XtMoveWidget (ws->w, ws->x, ws->y);
1519 XtPopup (ws->w, XtGrabNone);
1520 XtResizeWidget (ws->w, ws->width, ws->height,
1521 mw->core.border_width);
1522 XtResizeWindow (ws->w);
1523 display_menu (mw, i, False, &selection_position, NULL, NULL);
1526 /* unmap the menus that popped down */
1527 for (i = new_depth - 1; i < old_depth; i++)
1528 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1529 XtPopdown (windows[i].w);
1532 static Boolean
1533 motion_event_is_in_menu (XlwMenuWidget mw,
1534 XMotionEvent *ev,
1535 int level,
1536 XPoint *relative_pos)
1538 window_state* ws = &mw->menu.windows [level];
1539 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1540 int x = ws->x + shadow;
1541 int y = ws->y + shadow;
1542 relative_pos->x = ev->x_root - x;
1543 relative_pos->y = ev->y_root - y;
1544 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1545 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1548 static Boolean
1549 map_event_to_widget_value (XlwMenuWidget mw,
1550 XMotionEvent *ev,
1551 widget_value **val,
1552 int *level)
1554 int i;
1555 XPoint relative_pos;
1556 window_state* ws;
1557 int inside = 0;
1559 *val = NULL;
1561 /* Find the window */
1562 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1564 ws = &mw->menu.windows [i];
1565 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1567 inside = 1;
1568 display_menu (mw, i, True, NULL, &relative_pos, val);
1570 if (*val)
1572 *level = i + 1;
1573 return True;
1578 if (!inside)
1580 if (mw->menu.inside_entry != NULL)
1581 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1582 (XtPointer) mw->menu.inside_entry);
1583 mw->menu.inside_entry = NULL;
1586 return False;
1589 \f/* Procedures */
1590 static void
1591 make_drawing_gcs (XlwMenuWidget mw)
1593 XGCValues xgcv;
1594 float scale;
1595 XtGCMask mask = GCForeground | GCBackground;
1597 #ifdef HAVE_X_I18N
1598 if (!mw->menu.fontSet && mw->menu.font)
1600 xgcv.font = mw->menu.font->fid;
1601 mask |= GCFont;
1603 #else
1604 if (mw->menu.font)
1606 xgcv.font = mw->menu.font->fid;
1607 mask |= GCFont;
1609 #endif
1610 xgcv.foreground = mw->menu.foreground;
1611 xgcv.background = mw->core.background_pixel;
1612 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1614 xgcv.foreground = mw->menu.button_foreground;
1615 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1617 xgcv.background = mw->core.background_pixel;
1619 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1621 /* Allocate color for disabled menu-items. */
1622 mw->menu.disabled_foreground = mw->menu.foreground;
1623 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1624 scale = 2.3;
1625 else
1626 scale = 0.55;
1628 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1629 mw->core.colormap,
1630 &mw->menu.disabled_foreground,
1631 scale,
1632 0x8000);
1634 if (mw->menu.foreground == mw->menu.disabled_foreground
1635 || mw->core.background_pixel == mw->menu.disabled_foreground)
1637 /* Too few colors, use stipple. */
1638 xgcv.foreground = mw->menu.foreground;
1639 xgcv.fill_style = FillStippled;
1640 xgcv.stipple = mw->menu.gray_pixmap;
1641 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1642 | GCFillStyle | GCStipple, &xgcv);
1644 else
1646 /* Many colors available, use disabled pixel. */
1647 xgcv.foreground = mw->menu.disabled_foreground;
1648 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1651 xgcv.foreground = mw->menu.button_foreground;
1652 xgcv.background = mw->core.background_pixel;
1653 xgcv.fill_style = FillStippled;
1654 xgcv.stipple = mw->menu.gray_pixmap;
1655 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1656 | GCFillStyle | GCStipple, &xgcv);
1658 xgcv.foreground = mw->core.background_pixel;
1659 xgcv.background = mw->menu.foreground;
1660 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1663 static void
1664 release_drawing_gcs (XlwMenuWidget mw)
1666 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1667 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1668 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1669 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1670 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1671 /* let's get some segvs if we try to use these... */
1672 mw->menu.foreground_gc = (GC) -1;
1673 mw->menu.button_gc = (GC) -1;
1674 mw->menu.disabled_gc = (GC) -1;
1675 mw->menu.inactive_button_gc = (GC) -1;
1676 mw->menu.background_gc = (GC) -1;
1679 #ifndef emacs
1680 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1681 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1682 #endif
1684 static void
1685 make_shadow_gcs (XlwMenuWidget mw)
1687 XGCValues xgcv;
1688 unsigned long pm = 0;
1689 Display *dpy = XtDisplay ((Widget) mw);
1690 Screen *screen = XtScreen ((Widget) mw);
1691 Colormap cmap = mw->core.colormap;
1692 XColor topc, botc;
1693 int top_frobbed = 0, bottom_frobbed = 0;
1695 mw->menu.free_top_shadow_color_p = 0;
1696 mw->menu.free_bottom_shadow_color_p = 0;
1698 if (mw->menu.top_shadow_color == -1)
1699 mw->menu.top_shadow_color = mw->core.background_pixel;
1700 else
1701 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1703 if (mw->menu.bottom_shadow_color == -1)
1704 mw->menu.bottom_shadow_color = mw->menu.foreground;
1705 else
1706 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1708 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1709 mw->menu.top_shadow_color == mw->menu.foreground)
1711 topc.pixel = mw->core.background_pixel;
1712 #ifdef emacs
1713 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1714 &topc.pixel,
1715 1.2, 0x8000))
1716 #else
1717 XQueryColor (dpy, cmap, &topc);
1718 /* don't overflow/wrap! */
1719 topc.red = MINL (65535, topc.red * 1.2);
1720 topc.green = MINL (65535, topc.green * 1.2);
1721 topc.blue = MINL (65535, topc.blue * 1.2);
1722 if (XAllocColor (dpy, cmap, &topc))
1723 #endif
1725 mw->menu.top_shadow_color = topc.pixel;
1726 mw->menu.free_top_shadow_color_p = 1;
1727 top_frobbed = 1;
1730 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1731 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1733 botc.pixel = mw->core.background_pixel;
1734 #ifdef emacs
1735 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1736 &botc.pixel,
1737 0.6, 0x4000))
1738 #else
1739 XQueryColor (dpy, cmap, &botc);
1740 botc.red *= 0.6;
1741 botc.green *= 0.6;
1742 botc.blue *= 0.6;
1743 if (XAllocColor (dpy, cmap, &botc))
1744 #endif
1746 mw->menu.bottom_shadow_color = botc.pixel;
1747 mw->menu.free_bottom_shadow_color_p = 1;
1748 bottom_frobbed = 1;
1752 if (top_frobbed && bottom_frobbed)
1754 if (topc.pixel == botc.pixel)
1756 if (botc.pixel == mw->menu.foreground)
1758 if (mw->menu.free_top_shadow_color_p)
1760 x_free_dpy_colors (dpy, screen, cmap,
1761 &mw->menu.top_shadow_color, 1);
1762 mw->menu.free_top_shadow_color_p = 0;
1764 mw->menu.top_shadow_color = mw->core.background_pixel;
1766 else
1768 if (mw->menu.free_bottom_shadow_color_p)
1770 x_free_dpy_colors (dpy, screen, cmap,
1771 &mw->menu.bottom_shadow_color, 1);
1772 mw->menu.free_bottom_shadow_color_p = 0;
1774 mw->menu.bottom_shadow_color = mw->menu.foreground;
1779 if (!mw->menu.top_shadow_pixmap &&
1780 mw->menu.top_shadow_color == mw->core.background_pixel)
1782 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1783 if (mw->menu.free_top_shadow_color_p)
1785 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1786 mw->menu.free_top_shadow_color_p = 0;
1788 mw->menu.top_shadow_color = mw->menu.foreground;
1790 if (!mw->menu.bottom_shadow_pixmap &&
1791 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1793 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1794 if (mw->menu.free_bottom_shadow_color_p)
1796 x_free_dpy_colors (dpy, screen, cmap,
1797 &mw->menu.bottom_shadow_color, 1);
1798 mw->menu.free_bottom_shadow_color_p = 0;
1800 mw->menu.bottom_shadow_color = mw->menu.foreground;
1803 xgcv.fill_style = FillStippled;
1804 xgcv.foreground = mw->menu.top_shadow_color;
1805 xgcv.stipple = mw->menu.top_shadow_pixmap;
1806 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1807 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1809 xgcv.foreground = mw->menu.bottom_shadow_color;
1810 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1811 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1812 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1816 static void
1817 release_shadow_gcs (XlwMenuWidget mw)
1819 Display *dpy = XtDisplay ((Widget) mw);
1820 Screen *screen = XtScreen ((Widget) mw);
1821 Colormap cmap = mw->core.colormap;
1822 Pixel px[2];
1823 int i = 0;
1825 if (mw->menu.free_top_shadow_color_p)
1826 px[i++] = mw->menu.top_shadow_color;
1827 if (mw->menu.free_bottom_shadow_color_p)
1828 px[i++] = mw->menu.bottom_shadow_color;
1829 if (i > 0)
1830 x_free_dpy_colors (dpy, screen, cmap, px, i);
1832 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1833 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1836 #ifdef HAVE_XFT
1837 static XftFont *
1838 getDefaultXftFont (XlwMenuWidget mw)
1840 int screen = XScreenNumberOfScreen (mw->core.screen);
1841 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1844 static int
1845 openXftFont (XlwMenuWidget mw)
1847 char *fname = mw->menu.fontName;
1849 mw->menu.xft_font = 0;
1850 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1852 if (fname && strcmp (fname, "none") != 0)
1854 int screen = XScreenNumberOfScreen (mw->core.screen);
1855 int len = strlen (fname), i = len-1;
1856 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1857 while (i > 0 && '0' <= fname[i] && fname[i] <= '9')
1858 --i;
1859 if (fname[i] == ' ')
1861 fname = xstrdup (mw->menu.fontName);
1862 fname[i] = '-';
1865 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1866 if (!mw->menu.xft_font)
1867 mw->menu.xft_font = getDefaultXftFont (mw);
1870 if (fname != mw->menu.fontName) xfree (fname);
1872 return mw->menu.xft_font != 0;
1874 #endif
1876 static void
1877 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1879 /* Get the GCs and the widget size */
1880 XlwMenuWidget mw = (XlwMenuWidget) w;
1881 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1882 Display* display = XtDisplay (mw);
1884 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1885 mw->menu.cursor = mw->menu.cursor_shape;
1887 mw->menu.gray_pixmap
1888 = XCreatePixmapFromBitmapData (display, window, gray_bits,
1889 gray_width, gray_height,
1890 (unsigned long)1, (unsigned long)0, 1);
1892 #ifdef HAVE_XFT
1893 if (openXftFont (mw))
1895 else
1896 #endif
1898 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1899 if (!mw->menu.font)
1901 mw->menu.font = XLoadQueryFont (display, "fixed");
1902 if (!mw->menu.font)
1904 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1905 abort ();
1910 #ifdef HAVE_X_I18N
1911 if (mw->menu.fontSet)
1912 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1913 #endif
1915 make_drawing_gcs (mw);
1916 make_shadow_gcs (mw);
1918 mw->menu.popped_up = False;
1920 mw->menu.old_depth = 1;
1921 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1922 mw->menu.old_stack_length = 1;
1923 mw->menu.old_stack [0] = mw->menu.contents;
1925 mw->menu.new_depth = 0;
1926 mw->menu.new_stack = 0;
1927 mw->menu.new_stack_length = 0;
1928 push_new_stack (mw, mw->menu.contents);
1930 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1931 mw->menu.windows_length = 1;
1932 mw->menu.windows [0].x = 0;
1933 mw->menu.windows [0].y = 0;
1934 mw->menu.windows [0].width = 0;
1935 mw->menu.windows [0].height = 0;
1936 mw->menu.windows [0].max_rest_width = 0;
1937 mw->menu.windows [0].pixmap = None;
1938 #ifdef HAVE_XFT
1939 mw->menu.windows [0].xft_draw = 0;
1940 #endif
1941 size_menu (mw, 0);
1943 mw->core.width = mw->menu.windows [0].width;
1944 mw->core.height = mw->menu.windows [0].height;
1947 static void
1948 XlwMenuClassInitialize (void)
1952 static void
1953 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1955 XlwMenuWidget mw = (XlwMenuWidget)w;
1956 XSetWindowAttributes xswa;
1957 int mask;
1959 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1960 (w, valueMask, attributes);
1962 xswa.save_under = True;
1963 xswa.cursor = mw->menu.cursor_shape;
1964 mask = CWSaveUnder | CWCursor;
1965 /* I sometimes get random BadCursor errors while creating the first
1966 frame on a display. I can not find their reason, but they are
1967 annoying so for now let's ignore any errors here. -- lorentey */
1968 #ifdef emacs
1969 x_catch_errors (XtDisplay (w));
1970 #endif
1971 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1972 #ifdef emacs
1973 x_uncatch_errors ();
1974 #endif
1976 mw->menu.windows [0].w = w;
1977 mw->menu.windows [0].window = XtWindow (w);
1978 mw->menu.windows [0].x = w->core.x;
1979 mw->menu.windows [0].y = w->core.y;
1980 mw->menu.windows [0].width = w->core.width;
1981 mw->menu.windows [0].height = w->core.height;
1983 set_window_type (mw->menu.windows [0].w, mw);
1984 create_pixmap_for_menu (&mw->menu.windows [0], mw);
1986 #ifdef HAVE_XFT
1987 if (mw->menu.xft_font)
1989 XColor colors[3];
1990 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
1991 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
1992 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
1993 = mw->menu.disabled_foreground;
1994 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
1995 mw->menu.xft_fg.color.alpha = 0xFFFF;
1996 mw->menu.xft_fg.color.red = colors[0].red;
1997 mw->menu.xft_fg.color.green = colors[0].green;
1998 mw->menu.xft_fg.color.blue = colors[0].blue;
1999 mw->menu.xft_bg.color.alpha = 0xFFFF;
2000 mw->menu.xft_bg.color.red = colors[1].red;
2001 mw->menu.xft_bg.color.green = colors[1].green;
2002 mw->menu.xft_bg.color.blue = colors[1].blue;
2003 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2004 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2005 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2006 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2008 #endif
2011 /* Only the toplevel menubar/popup is a widget so it's the only one that
2012 receives expose events through Xt. So we repaint all the other panes
2013 when receiving an Expose event. */
2014 static void
2015 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2017 XlwMenuWidget mw = (XlwMenuWidget)w;
2019 /* If we have a depth beyond 1, it's because a submenu was displayed.
2020 If the submenu has been destroyed, set the depth back to 1. */
2021 if (submenu_destroyed)
2023 mw->menu.old_depth = 1;
2024 submenu_destroyed = 0;
2027 display_menu (mw, 0, False, NULL, NULL, NULL);
2031 /* Part of a hack to make the menu redisplay when a tooltip frame
2032 over a menu item is unmapped. */
2034 void
2035 xlwmenu_redisplay (Widget w)
2037 XlwMenuRedisplay (w, NULL, None);
2040 static void
2041 XlwMenuDestroy (Widget w)
2043 int i;
2044 XlwMenuWidget mw = (XlwMenuWidget) w;
2046 if (pointer_grabbed)
2047 ungrab_all ((Widget)w, CurrentTime);
2048 pointer_grabbed = 0;
2050 submenu_destroyed = 1;
2052 release_drawing_gcs (mw);
2053 release_shadow_gcs (mw);
2055 /* this doesn't come from the resource db but is created explicitly
2056 so we must free it ourselves. */
2057 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2058 mw->menu.gray_pixmap = (Pixmap) -1;
2060 /* Don't free mw->menu.contents because that comes from our creator.
2061 The `*_stack' elements are just pointers into `contents' so leave
2062 that alone too. But free the stacks themselves. */
2063 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2064 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2066 /* Original comment was:
2068 Remember, you can't free anything that came from the resource
2069 database. This includes:
2070 mw->menu.cursor
2071 mw->menu.top_shadow_pixmap
2072 mw->menu.bottom_shadow_pixmap
2073 mw->menu.font
2074 Also the color cells of top_shadow_color, bottom_shadow_color,
2075 foreground, and button_foreground will never be freed until this
2076 client exits. Nice, eh?
2078 But now I can free font without any visible glitches. */
2080 if (mw->menu.font)
2081 XFreeFont (XtDisplay (mw), mw->menu.font);
2083 #ifdef HAVE_XFT
2084 if (mw->menu.windows [0].xft_draw)
2085 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2086 if (mw->menu.xft_font)
2087 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2088 #endif
2090 if (mw->menu.windows [0].pixmap != None)
2091 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2092 /* start from 1 because the one in slot 0 is w->core.window */
2093 for (i = 1; i < mw->menu.windows_length; i++)
2095 if (mw->menu.windows [i].pixmap != None)
2096 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2097 #ifdef HAVE_XFT
2098 if (mw->menu.windows [i].xft_draw)
2099 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2100 #endif
2103 if (mw->menu.windows)
2104 XtFree ((char *) mw->menu.windows);
2107 #ifdef HAVE_XFT
2108 static int
2109 fontname_changed (XlwMenuWidget newmw,
2110 XlwMenuWidget oldmw)
2112 /* This will force a new XftFont even if the same string is set.
2113 This is good, as rendering parameters may have changed and
2114 we just want to do a redisplay. */
2115 return newmw->menu.fontName != oldmw->menu.fontName;
2117 #endif
2119 static Boolean
2120 XlwMenuSetValues (Widget current, Widget request, Widget new,
2121 ArgList args, Cardinal *num_args)
2123 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2124 XlwMenuWidget newmw = (XlwMenuWidget)new;
2125 Boolean do_redisplay = False;
2127 if (newmw->menu.contents
2128 && newmw->menu.contents->contents
2129 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2130 do_redisplay = True;
2131 /* Do redisplay if the contents are entirely eliminated. */
2132 if (newmw->menu.contents
2133 && newmw->menu.contents->contents == 0
2134 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2135 do_redisplay = True;
2137 if (newmw->core.background_pixel != oldmw->core.background_pixel
2138 || newmw->menu.foreground != oldmw->menu.foreground
2139 #ifdef HAVE_XFT
2140 || fontname_changed (newmw, oldmw)
2141 #endif
2142 #ifdef HAVE_X_I18N
2143 || newmw->menu.fontSet != oldmw->menu.fontSet
2144 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2145 #else
2146 || newmw->menu.font != oldmw->menu.font
2147 #endif
2150 int i;
2151 release_drawing_gcs (newmw);
2152 make_drawing_gcs (newmw);
2154 release_shadow_gcs (newmw);
2155 /* Cause the shadow colors to be recalculated. */
2156 newmw->menu.top_shadow_color = -1;
2157 newmw->menu.bottom_shadow_color = -1;
2158 make_shadow_gcs (newmw);
2160 do_redisplay = True;
2162 if (XtIsRealized (current))
2163 /* If the menu is currently displayed, change the display. */
2164 for (i = 0; i < oldmw->menu.windows_length; i++)
2166 XSetWindowBackground (XtDisplay (oldmw),
2167 oldmw->menu.windows [i].window,
2168 newmw->core.background_pixel);
2169 /* clear windows and generate expose events */
2170 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2171 0, 0, 0, 0, True);
2175 #ifdef HAVE_XFT
2176 if (fontname_changed (newmw, oldmw))
2178 int i;
2179 int screen = XScreenNumberOfScreen (newmw->core.screen);
2180 if (newmw->menu.xft_font)
2181 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2182 openXftFont (newmw);
2183 for (i = 0; i < newmw->menu.windows_length; i++)
2185 if (newmw->menu.windows [i].xft_draw)
2186 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2187 newmw->menu.windows [i].xft_draw = 0;
2189 if (newmw->menu.xft_font)
2190 for (i = 0; i < newmw->menu.windows_length; i++)
2191 newmw->menu.windows [i].xft_draw
2192 = XftDrawCreate (XtDisplay (newmw),
2193 newmw->menu.windows [i].window,
2194 DefaultVisual (XtDisplay (newmw), screen),
2195 newmw->core.colormap);
2197 #endif
2198 #ifdef HAVE_X_I18N
2199 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2201 do_redisplay = True;
2202 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2204 #endif
2206 return do_redisplay;
2209 static void
2210 XlwMenuResize (Widget w)
2212 XlwMenuWidget mw = (XlwMenuWidget)w;
2214 if (mw->menu.popped_up)
2216 /* Don't allow the popup menu to resize itself. */
2217 mw->core.width = mw->menu.windows [0].width;
2218 mw->core.height = mw->menu.windows [0].height;
2219 mw->core.parent->core.width = mw->core.width;
2220 mw->core.parent->core.height = mw->core.height;
2222 else
2224 mw->menu.windows [0].width = mw->core.width;
2225 mw->menu.windows [0].height = mw->core.height;
2226 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2230 \f/* Action procedures */
2231 static void
2232 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2234 widget_value* val;
2235 int level;
2237 if (!map_event_to_widget_value (mw, ev, &val, &level))
2238 pop_new_stack_if_no_contents (mw);
2239 else
2240 set_new_state (mw, val, level);
2241 remap_menubar (mw);
2243 /* Sync with the display. Makes it feel better on X terms. */
2244 XSync (XtDisplay (mw), False);
2247 static void
2248 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2250 int x = ev->x_root;
2251 int y = ev->y_root;
2252 int state = ev->state;
2253 XMotionEvent oldev = *ev;
2255 /* allow motion events to be generated again */
2256 if (ev->is_hint
2257 && XQueryPointer (XtDisplay (mw), ev->window,
2258 &ev->root, &ev->subwindow,
2259 &ev->x_root, &ev->y_root,
2260 &ev->x, &ev->y,
2261 &ev->state)
2262 && ev->state == state
2263 && (ev->x_root != x || ev->y_root != y))
2264 handle_single_motion_event (mw, ev);
2265 else
2266 handle_single_motion_event (mw, &oldev);
2269 static void
2270 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2272 XlwMenuWidget mw = (XlwMenuWidget)w;
2274 if (!mw->menu.popped_up)
2276 menu_post_event = *ev;
2277 /* If event is set to CurrentTime, get the last known time stamp.
2278 This is for calculating if (popup) menus should stay up after
2279 a fast click. */
2280 if (menu_post_event.xbutton.time == CurrentTime)
2281 menu_post_event.xbutton.time
2282 = XtLastTimestampProcessed (XtDisplay (w));
2284 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2286 else
2288 /* If we push a button while the menu is posted semipermanently,
2289 releasing the button should always pop the menu down. */
2290 next_release_must_exit = 1;
2292 /* notes the absolute position of the menubar window */
2293 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2294 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2296 /* handles the down like a move, slots are compatible */
2297 ev->xmotion.is_hint = 0;
2298 handle_motion_event (mw, &ev->xmotion);
2302 static void
2303 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2305 XlwMenuWidget mw = (XlwMenuWidget)w;
2306 if (mw->menu.popped_up)
2307 handle_motion_event (mw, &ev->xmotion);
2310 /* Do nothing.
2311 This is how we handle presses and releases of modifier keys. */
2312 static void
2313 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2317 static widget_value *
2318 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2320 widget_value *current = item;
2321 enum menu_separator separator;
2323 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2324 || (skip_titles && !current->call_data && !current->contents))
2325 if (current->next)
2326 current=current->next;
2327 else
2328 return NULL;
2330 return current;
2333 static widget_value *
2334 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2336 widget_value *current = item;
2337 enum menu_separator separator;
2339 while (current->next && (current=current->next) &&
2340 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2341 || (skip_titles && !current->call_data && !current->contents)))
2344 if (current == item)
2346 if (mw->menu.old_depth < 2)
2347 return current;
2348 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2350 while (lw_separator_p (current->name, &separator, 0)
2351 || !current->enabled
2352 || (skip_titles && !current->call_data
2353 && !current->contents))
2355 if (current->next)
2356 current=current->next;
2358 if (current == item)
2359 break;
2364 return current;
2367 static widget_value *
2368 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2370 widget_value *current = item;
2371 widget_value *prev = item;
2373 while ((current=find_next_selectable (mw, current, skip_titles))
2374 != item)
2376 if (prev == current)
2377 break;
2378 prev=current;
2381 return prev;
2384 static void
2385 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2387 XlwMenuWidget mw = (XlwMenuWidget) w;
2388 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2389 int popup_menu_p = mw->menu.top_depth == 1;
2391 /* Inside top-level menu-bar? */
2392 if (mw->menu.old_depth == mw->menu.top_depth)
2393 /* When <down> in the menu-bar is pressed, display the corresponding
2394 sub-menu and select the first selectable menu item there.
2395 If this is a popup menu, skip title item of the popup. */
2396 set_new_state (mw,
2397 find_first_selectable (mw,
2398 selected_item->contents,
2399 popup_menu_p),
2400 mw->menu.old_depth);
2401 else
2402 /* Highlight next possible (enabled and not separator) menu item. */
2403 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2404 mw->menu.old_depth - 1);
2406 remap_menubar (mw);
2409 static void
2410 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2412 XlwMenuWidget mw = (XlwMenuWidget) w;
2413 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2414 int popup_menu_p = mw->menu.top_depth == 1;
2416 /* Inside top-level menu-bar? */
2417 if (mw->menu.old_depth == mw->menu.top_depth)
2419 /* FIXME: this is tricky. <up> in the menu-bar should select the
2420 last selectable item in the list. So we select the first
2421 selectable one and find the previous selectable item. Is there
2422 a better way? */
2423 /* If this is a popup menu, skip title item of the popup. */
2424 set_new_state (mw,
2425 find_first_selectable (mw,
2426 selected_item->contents,
2427 popup_menu_p),
2428 mw->menu.old_depth);
2429 remap_menubar (mw);
2430 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2431 set_new_state (mw,
2432 find_prev_selectable (mw,
2433 selected_item,
2434 popup_menu_p),
2435 mw->menu.old_depth - 1);
2437 else
2438 /* Highlight previous (enabled and not separator) menu item. */
2439 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2440 mw->menu.old_depth - 1);
2442 remap_menubar (mw);
2445 void
2446 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2448 XlwMenuWidget mw = (XlwMenuWidget) w;
2449 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2451 /* Inside top-level menu-bar? */
2452 if (mw->menu.old_depth == mw->menu.top_depth)
2453 /* When <left> in the menu-bar is pressed, display the previous item on
2454 the menu-bar. If the current item is the first one, highlight the
2455 last item in the menubar (probably Help). */
2456 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2457 mw->menu.old_depth - 1);
2458 else if (mw->menu.old_depth == 1
2459 && selected_item->contents) /* Is this menu item expandable? */
2461 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2462 remap_menubar (mw);
2463 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2464 if (!selected_item->enabled && find_first_selectable (mw,
2465 selected_item,
2467 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2468 mw->menu.old_depth - 1);
2471 else
2473 pop_new_stack_if_no_contents (mw);
2474 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2475 mw->menu.old_depth - 2);
2478 remap_menubar (mw);
2481 void
2482 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2484 XlwMenuWidget mw = (XlwMenuWidget) w;
2485 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2487 /* Inside top-level menu-bar? */
2488 if (mw->menu.old_depth == mw->menu.top_depth)
2489 /* When <right> in the menu-bar is pressed, display the next item on
2490 the menu-bar. If the current item is the last one, highlight the
2491 first item (probably File). */
2492 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2493 mw->menu.old_depth - 1);
2494 else if (selected_item->contents) /* Is this menu item expandable? */
2496 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2497 remap_menubar (mw);
2498 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2499 if (!selected_item->enabled && find_first_selectable (mw,
2500 selected_item,
2502 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2503 mw->menu.old_depth - 1);
2505 else
2507 pop_new_stack_if_no_contents (mw);
2508 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2509 mw->menu.old_depth - 2);
2512 remap_menubar (mw);
2515 /* Handle key press and release events while menu is popped up.
2516 Our action is to get rid of the menu. */
2517 static void
2518 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2520 XlwMenuWidget mw = (XlwMenuWidget)w;
2522 /* Pop down everything. */
2523 mw->menu.new_depth = 1;
2524 remap_menubar (mw);
2526 if (mw->menu.popped_up)
2528 mw->menu.popped_up = False;
2529 ungrab_all ((Widget)mw, ev->xmotion.time);
2530 if (XtIsShell (XtParent ((Widget) mw)))
2531 XtPopdown (XtParent ((Widget) mw));
2532 else
2534 XtRemoveGrab ((Widget) mw);
2535 display_menu (mw, 0, False, NULL, NULL, NULL);
2539 /* callback */
2540 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2543 static void
2544 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2546 XlwMenuWidget mw = (XlwMenuWidget)w;
2547 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2549 /* If user releases the button quickly, without selecting anything,
2550 after the initial down-click that brought the menu up,
2551 do nothing. */
2552 if ((selected_item == 0
2553 || ((widget_value *) selected_item)->call_data == 0)
2554 && !next_release_must_exit
2555 && (ev->xbutton.time - menu_post_event.xbutton.time
2556 < XtGetMultiClickTime (XtDisplay (w))))
2557 return;
2559 /* pop down everything. */
2560 mw->menu.new_depth = 1;
2561 remap_menubar (mw);
2563 if (mw->menu.popped_up)
2565 mw->menu.popped_up = False;
2566 ungrab_all ((Widget)mw, ev->xmotion.time);
2567 if (XtIsShell (XtParent ((Widget) mw)))
2568 XtPopdown (XtParent ((Widget) mw));
2569 else
2571 XtRemoveGrab ((Widget) mw);
2572 display_menu (mw, 0, False, NULL, NULL, NULL);
2576 /* callback */
2577 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2581 \f/* Special code to pop-up a menu */
2582 static void
2583 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2585 int x = event->x_root;
2586 int y = event->y_root;
2587 int w;
2588 int h;
2589 int borderwidth = mw->menu.shadow_thickness;
2590 Screen* screen = XtScreen (mw);
2591 Display *display = XtDisplay (mw);
2593 next_release_must_exit = 0;
2595 mw->menu.inside_entry = NULL;
2596 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2598 if (XtIsShell (XtParent ((Widget)mw)))
2599 size_menu (mw, 0);
2601 w = mw->menu.windows [0].width;
2602 h = mw->menu.windows [0].height;
2604 x -= borderwidth;
2605 y -= borderwidth;
2606 if (x < borderwidth)
2607 x = borderwidth;
2608 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2609 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2610 if (y < borderwidth)
2611 y = borderwidth;
2612 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2613 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2615 mw->menu.popped_up = True;
2616 if (XtIsShell (XtParent ((Widget)mw)))
2618 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2619 XtParent ((Widget)mw)->core.border_width);
2620 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2621 display_menu (mw, 0, False, NULL, NULL, NULL);
2622 mw->menu.windows [0].x = x + borderwidth;
2623 mw->menu.windows [0].y = y + borderwidth;
2624 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2626 else
2628 XEvent *ev = (XEvent *) event;
2630 XtAddGrab ((Widget) mw, True, True);
2632 /* notes the absolute position of the menubar window */
2633 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2634 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2635 mw->menu.top_depth = 2;
2638 #ifdef emacs
2639 x_catch_errors (display);
2640 #endif
2641 if (XtGrabPointer ((Widget)mw, False,
2642 (PointerMotionMask
2643 | PointerMotionHintMask
2644 | ButtonReleaseMask
2645 | ButtonPressMask),
2646 GrabModeAsync, GrabModeAsync, None,
2647 mw->menu.cursor_shape,
2648 event->time) == Success)
2650 if (! GRAB_KEYBOARD
2651 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2652 GrabModeAsync, event->time) == Success)
2654 XtSetKeyboardFocus((Widget)mw, None);
2655 pointer_grabbed = 1;
2657 else
2658 XtUngrabPointer ((Widget)mw, event->time);
2661 #ifdef emacs
2662 if (x_had_errors_p (display))
2664 pointer_grabbed = 0;
2665 XtUngrabPointer ((Widget)mw, event->time);
2667 x_uncatch_errors ();
2668 #endif
2670 ((XMotionEvent*)event)->is_hint = 0;
2671 handle_motion_event (mw, (XMotionEvent*)event);