Move note on lack of setf functions from cl.texi to lispref
[emacs.git] / lwlib / xlwmenu.c
blobc76cb1a3f386e2aae13a4a2d700b12c8381a2f89
1 /* Implements a lightweight menubar widget.
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2012 Free Software Foundation, Inc.
6 This file is part of the Lucid Widget Library.
8 The Lucid Widget Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2, or (at your option)
11 any later version.
13 The Lucid Widget Library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with GNU Emacs; see the file COPYING. If not, write to the
20 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA. */
23 /* Created by devin@lucid.com */
25 #include <config.h>
27 #include <setjmp.h>
28 #include <lisp.h>
30 #include <stdio.h>
32 #include <sys/types.h>
33 #if (defined __sun) && !(defined SUNOS41)
34 #define SUNOS41
35 #include <X11/Xos.h>
36 #undef SUNOS41
37 #else
38 #include <X11/Xos.h>
39 #endif
40 #include <X11/IntrinsicP.h>
41 #include <X11/ObjectP.h>
42 #include <X11/StringDefs.h>
43 #include <X11/cursorfont.h>
44 #include <X11/Shell.h>
45 #include "xlwmenuP.h"
47 #ifdef emacs
49 #include <xterm.h>
50 #include "bitmaps/gray.xbm"
52 #else /* not emacs */
54 #include <X11/bitmaps/gray>
56 #endif /* not emacs */
58 static int pointer_grabbed;
59 static XEvent menu_post_event;
61 static char
62 xlwMenuTranslations [] =
63 "<BtnDown>: start()\n\
64 <Motion>: drag()\n\
65 <BtnUp>: select()\n\
66 <Key>Shift_L: nothing()\n\
67 <Key>Shift_R: nothing()\n\
68 <Key>Meta_L: nothing()\n\
69 <Key>Meta_R: nothing()\n\
70 <Key>Control_L: nothing()\n\
71 <Key>Control_R: nothing()\n\
72 <Key>Hyper_L: nothing()\n\
73 <Key>Hyper_R: nothing()\n\
74 <Key>Super_L: nothing()\n\
75 <Key>Super_R: nothing()\n\
76 <Key>Alt_L: nothing()\n\
77 <Key>Alt_R: nothing()\n\
78 <Key>Caps_Lock: nothing()\n\
79 <Key>Shift_Lock: nothing()\n\
80 <KeyUp>Shift_L: nothing()\n\
81 <KeyUp>Shift_R: nothing()\n\
82 <KeyUp>Meta_L: nothing()\n\
83 <KeyUp>Meta_R: nothing()\n\
84 <KeyUp>Control_L: nothing()\n\
85 <KeyUp>Control_R: nothing()\n\
86 <KeyUp>Hyper_L: nothing()\n\
87 <KeyUp>Hyper_R: nothing()\n\
88 <KeyUp>Super_L: nothing()\n\
89 <KeyUp>Super_R: nothing()\n\
90 <KeyUp>Alt_L: nothing()\n\
91 <KeyUp>Alt_R: nothing()\n\
92 <KeyUp>Caps_Lock: nothing()\n\
93 <KeyUp>Shift_Lock:nothing()\n\
94 <Key>Return: select()\n\
95 <Key>Down: down()\n\
96 <Key>Up: up()\n\
97 <Key>Left: left()\n\
98 <Key>Right: right()\n\
99 <Key>: key()\n\
100 <KeyUp>: key()\n\
103 /* FIXME: Space should toggle togglable menu item but not remove the menu
104 so you can toggle the next one without entering the menu again. */
106 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
108 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
110 #define offset(field) XtOffset(XlwMenuWidget, field)
111 static XtResource
112 xlwMenuResources[] =
114 #ifdef HAVE_X_I18N
115 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
116 offset(menu.fontSet), XtRFontSet, NULL},
117 #endif
118 #ifdef HAVE_XFT
119 #define DEFAULT_FONTNAME "Sans-10"
120 #else
121 #define DEFAULT_FONTNAME "XtDefaultFont"
122 #endif
123 {XtNfont, XtCFont, XtRString, sizeof(String),
124 offset(menu.fontName), XtRString, DEFAULT_FONTNAME },
125 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
126 offset(menu.foreground), XtRString, "XtDefaultForeground"},
127 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
128 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
129 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
130 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
131 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
132 offset(menu.margin), XtRImmediate, (XtPointer)1},
133 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
134 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
135 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
136 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
137 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
138 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
140 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
141 sizeof (Dimension), offset (menu.shadow_thickness),
142 XtRImmediate, (XtPointer)1},
143 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
144 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
145 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
146 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
147 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
148 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
149 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
150 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
152 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
153 offset(menu.open), XtRCallback, (XtPointer)NULL},
154 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
155 offset(menu.select), XtRCallback, (XtPointer)NULL},
156 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
157 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
158 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
159 offset(menu.enter), XtRCallback, (XtPointer)NULL},
160 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
161 offset(menu.leave), XtRCallback, (XtPointer)NULL},
162 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
163 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
164 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
165 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
166 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
167 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
169 #undef offset
171 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
172 ArgList args, Cardinal *num_args);
173 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
174 static void XlwMenuResize(Widget w);
175 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
176 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
177 static void XlwMenuDestroy(Widget w);
178 static void XlwMenuClassInitialize(void);
179 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
180 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
181 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
182 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
183 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
184 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
185 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
186 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
187 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
188 static int separator_height (enum menu_separator);
189 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
191 static XtActionsRec
192 xlwMenuActionsList [] =
194 {"start", Start},
195 {"drag", Drag},
196 {"down", Down},
197 {"up", Up},
198 {"left", Left},
199 {"right", Right},
200 {"select", Select},
201 {"key", Key},
202 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
203 {"nothing", Nothing},
206 #define SuperClass ((CoreWidgetClass)&coreClassRec)
208 XlwMenuClassRec xlwMenuClassRec =
210 { /* CoreClass fields initialization */
211 (WidgetClass) SuperClass, /* superclass */
212 "XlwMenu", /* class_name */
213 sizeof(XlwMenuRec), /* size */
214 XlwMenuClassInitialize, /* class_initialize */
215 NULL, /* class_part_initialize */
216 FALSE, /* class_inited */
217 XlwMenuInitialize, /* initialize */
218 NULL, /* initialize_hook */
219 XlwMenuRealize, /* realize */
220 xlwMenuActionsList, /* actions */
221 XtNumber(xlwMenuActionsList), /* num_actions */
222 xlwMenuResources, /* resources */
223 XtNumber(xlwMenuResources), /* resource_count */
224 NULLQUARK, /* xrm_class */
225 TRUE, /* compress_motion */
226 XtExposeCompressMaximal, /* compress_exposure */
227 TRUE, /* compress_enterleave */
228 FALSE, /* visible_interest */
229 XlwMenuDestroy, /* destroy */
230 XlwMenuResize, /* resize */
231 XlwMenuRedisplay, /* expose */
232 XlwMenuSetValues, /* set_values */
233 NULL, /* set_values_hook */
234 XtInheritSetValuesAlmost, /* set_values_almost */
235 NULL, /* get_values_hook */
236 NULL, /* accept_focus */
237 XtVersion, /* version */
238 NULL, /* callback_private */
239 xlwMenuTranslations, /* tm_table */
240 XtInheritQueryGeometry, /* query_geometry */
241 XtInheritDisplayAccelerator, /* display_accelerator */
242 NULL /* extension */
243 }, /* XlwMenuClass fields initialization */
245 0 /* dummy */
249 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
251 int submenu_destroyed;
253 /* For debug, if installation-directory is non-nil this is not an installed
254 Emacs. In that case we do not grab the keyboard to make it easier to
255 debug. */
256 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
258 static int next_release_must_exit;
260 \f/* Utilities */
262 /* Ungrab pointer and keyboard */
263 static void
264 ungrab_all (Widget w, Time ungrabtime)
266 XtUngrabPointer (w, ungrabtime);
267 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
270 /* Like abort, but remove grabs from widget W before. */
272 static _Noreturn void
273 abort_gracefully (Widget w)
275 if (XtIsShell (XtParent (w)))
276 XtRemoveGrab (w);
277 ungrab_all (w, CurrentTime);
278 abort ();
281 static void
282 push_new_stack (XlwMenuWidget mw, widget_value *val)
284 if (!mw->menu.new_stack)
286 mw->menu.new_stack_length = 10;
287 mw->menu.new_stack =
288 (widget_value**)XtCalloc (mw->menu.new_stack_length,
289 sizeof (widget_value*));
291 else if (mw->menu.new_depth == mw->menu.new_stack_length)
293 mw->menu.new_stack_length *= 2;
294 mw->menu.new_stack =
295 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
296 mw->menu.new_stack_length * sizeof (widget_value*));
298 mw->menu.new_stack [mw->menu.new_depth++] = val;
301 static void
302 pop_new_stack_if_no_contents (XlwMenuWidget mw)
304 if (mw->menu.new_depth > 1)
306 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
307 mw->menu.new_depth -= 1;
311 static void
312 make_old_stack_space (XlwMenuWidget mw, int n)
314 if (!mw->menu.old_stack)
316 mw->menu.old_stack_length = 10;
317 mw->menu.old_stack =
318 (widget_value**)XtCalloc (mw->menu.old_stack_length,
319 sizeof (widget_value*));
321 else if (mw->menu.old_stack_length < n)
323 mw->menu.old_stack_length *= 2;
324 mw->menu.old_stack =
325 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
326 mw->menu.old_stack_length * sizeof (widget_value*));
330 \f/* Size code */
331 static int
332 string_width (XlwMenuWidget mw, char *s)
334 XCharStruct xcs;
335 int drop;
336 #ifdef HAVE_XFT
337 if (mw->menu.xft_font)
339 XGlyphInfo gi;
340 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
341 (FcChar8 *) s,
342 strlen (s), &gi);
343 return gi.width;
345 #endif
346 #ifdef HAVE_X_I18N
347 if (mw->menu.fontSet)
349 XRectangle ink, logical;
350 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
351 return logical.width;
353 #endif
355 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
356 return xcs.width;
360 #ifdef HAVE_XFT
361 #define MENU_FONT_HEIGHT(mw) \
362 ((mw)->menu.xft_font != NULL \
363 ? (mw)->menu.xft_font->height \
364 : ((mw)->menu.fontSet != NULL \
365 ? (mw)->menu.font_extents->max_logical_extent.height \
366 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
367 #define MENU_FONT_ASCENT(mw) \
368 ((mw)->menu.xft_font != NULL \
369 ? (mw)->menu.xft_font->ascent \
370 : ((mw)->menu.fontSet != NULL \
371 ? - (mw)->menu.font_extents->max_logical_extent.y \
372 : (mw)->menu.font->ascent))
373 #else
374 #ifdef HAVE_X_I18N
375 #define MENU_FONT_HEIGHT(mw) \
376 ((mw)->menu.fontSet != NULL \
377 ? (mw)->menu.font_extents->max_logical_extent.height \
378 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
379 #define MENU_FONT_ASCENT(mw) \
380 ((mw)->menu.fontSet != NULL \
381 ? - (mw)->menu.font_extents->max_logical_extent.y \
382 : (mw)->menu.font->ascent)
383 #else
384 #define MENU_FONT_HEIGHT(mw) \
385 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
386 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
387 #endif
388 #endif
390 static int
391 arrow_width (XlwMenuWidget mw)
393 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
396 /* Return the width of toggle buttons of widget MW. */
398 static int
399 toggle_button_width (XlwMenuWidget mw)
401 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
405 /* Return the width of radio buttons of widget MW. */
407 static int
408 radio_button_width (XlwMenuWidget mw)
410 return toggle_button_width (mw) * 1.41;
414 static XtResource
415 nameResource[] =
417 {"labelString", "LabelString", XtRString, sizeof(String),
418 0, XtRImmediate, 0},
421 static char*
422 resource_widget_value (XlwMenuWidget mw, widget_value *val)
424 if (!val->toolkit_data)
426 char* resourced_name = NULL;
427 char* complete_name;
428 XtGetSubresources ((Widget) mw,
429 (XtPointer) &resourced_name,
430 val->name, val->name,
431 nameResource, 1, NULL, 0);
432 if (!resourced_name)
433 resourced_name = val->name;
434 if (!val->value)
436 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
437 strcpy (complete_name, resourced_name);
439 else
441 int complete_length =
442 strlen (resourced_name) + strlen (val->value) + 2;
443 complete_name = XtMalloc (complete_length);
444 *complete_name = 0;
445 strcat (complete_name, resourced_name);
446 strcat (complete_name, " ");
447 strcat (complete_name, val->value);
450 val->toolkit_data = complete_name;
451 val->free_toolkit_data = True;
453 return (char*)val->toolkit_data;
456 /* Returns the sizes of an item */
457 static void
458 size_menu_item (XlwMenuWidget mw,
459 widget_value* val,
460 int horizontal_p,
461 int* label_width,
462 int* rest_width,
463 int* button_width,
464 int* height)
466 enum menu_separator separator;
468 if (lw_separator_p (val->name, &separator, 0))
470 *height = separator_height (separator);
471 *label_width = 1;
472 *rest_width = 0;
473 *button_width = 0;
475 else
477 *height = MENU_FONT_HEIGHT (mw)
478 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
480 *label_width =
481 string_width (mw, resource_widget_value (mw, val))
482 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
484 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
485 if (!horizontal_p)
487 if (val->contents)
488 /* Add width of the arrow displayed for submenus. */
489 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
490 else if (val->key)
491 /* Add width of key equivalent string. */
492 *rest_width += (string_width (mw, val->key)
493 + mw->menu.arrow_spacing);
495 if (val->button_type == BUTTON_TYPE_TOGGLE)
496 *button_width = (toggle_button_width (mw)
497 + mw->menu.horizontal_spacing);
498 else if (val->button_type == BUTTON_TYPE_RADIO)
499 *button_width = (radio_button_width (mw)
500 + mw->menu.horizontal_spacing);
505 static void
506 size_menu (XlwMenuWidget mw, int level)
508 int label_width = 0;
509 int rest_width = 0;
510 int button_width = 0;
511 int max_rest_width = 0;
512 int max_button_width = 0;
513 int height = 0;
514 int horizontal_p = mw->menu.horizontal && (level == 0);
515 widget_value* val;
516 window_state* ws;
518 if (level >= mw->menu.old_depth)
519 abort_gracefully ((Widget) mw);
521 ws = &mw->menu.windows [level];
522 ws->width = 0;
523 ws->height = 0;
524 ws->label_width = 0;
525 ws->button_width = 0;
527 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
529 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
530 &button_width, &height);
531 if (horizontal_p)
533 ws->width += label_width + rest_width;
534 if (height > ws->height)
535 ws->height = height;
537 else
539 if (label_width > ws->label_width)
540 ws->label_width = label_width;
541 if (rest_width > max_rest_width)
542 max_rest_width = rest_width;
543 if (button_width > max_button_width)
544 max_button_width = button_width;
545 ws->height += height;
549 if (horizontal_p)
550 ws->label_width = ws->button_width = 0;
551 else
553 ws->width = ws->label_width + max_rest_width + max_button_width;
554 ws->button_width = max_button_width;
557 ws->width += 2 * mw->menu.shadow_thickness;
558 ws->height += 2 * mw->menu.shadow_thickness;
559 ws->max_rest_width = max_rest_width;
561 if (horizontal_p)
563 ws->width += 2 * mw->menu.margin;
564 ws->height += 2 * mw->menu.margin;
569 \f/* Display code */
571 static void
572 draw_arrow (XlwMenuWidget mw,
573 Window window,
574 GC gc,
575 int x,
576 int y,
577 int width,
578 int down_p)
580 Display *dpy = XtDisplay (mw);
581 GC top_gc = mw->menu.shadow_top_gc;
582 GC bottom_gc = mw->menu.shadow_bottom_gc;
583 int thickness = mw->menu.shadow_thickness;
584 int height = width;
585 XPoint pt[10];
586 /* alpha = atan (0.5)
587 factor = (1 + sin (alpha)) / cos (alpha) */
588 double factor = 1.62;
589 int thickness2 = thickness * factor;
591 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
593 if (down_p)
595 GC temp;
596 temp = top_gc;
597 top_gc = bottom_gc;
598 bottom_gc = temp;
601 pt[0].x = x;
602 pt[0].y = y + height;
603 pt[1].x = x + thickness;
604 pt[1].y = y + height - thickness2;
605 pt[2].x = x + thickness2;
606 pt[2].y = y + thickness2;
607 pt[3].x = x;
608 pt[3].y = y;
609 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
611 pt[0].x = x;
612 pt[0].y = y;
613 pt[1].x = x + thickness;
614 pt[1].y = y + thickness2;
615 pt[2].x = x + width - thickness2;
616 pt[2].y = y + height / 2;
617 pt[3].x = x + width;
618 pt[3].y = y + height / 2;
619 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
621 pt[0].x = x;
622 pt[0].y = y + height;
623 pt[1].x = x + thickness;
624 pt[1].y = y + height - thickness2;
625 pt[2].x = x + width - thickness2;
626 pt[2].y = y + height / 2;
627 pt[3].x = x + width;
628 pt[3].y = y + height / 2;
629 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
634 static void
635 draw_shadow_rectangle (XlwMenuWidget mw,
636 Window window,
637 int x,
638 int y,
639 int width,
640 int height,
641 int erase_p,
642 int down_p)
644 Display *dpy = XtDisplay (mw);
645 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
646 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
647 int thickness = mw->menu.shadow_thickness;
648 XPoint points [4];
650 if (!erase_p && down_p)
652 GC temp;
653 temp = top_gc;
654 top_gc = bottom_gc;
655 bottom_gc = temp;
658 points [0].x = x;
659 points [0].y = y;
660 points [1].x = x + width;
661 points [1].y = y;
662 points [2].x = x + width - thickness;
663 points [2].y = y + thickness;
664 points [3].x = x;
665 points [3].y = y + thickness;
666 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
667 points [0].x = x;
668 points [0].y = y + thickness;
669 points [1].x = x;
670 points [1].y = y + height;
671 points [2].x = x + thickness;
672 points [2].y = y + height - thickness;
673 points [3].x = x + thickness;
674 points [3].y = y + thickness;
675 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
676 points [0].x = x + width;
677 points [0].y = y;
678 points [1].x = x + width - thickness;
679 points [1].y = y + thickness;
680 points [2].x = x + width - thickness;
681 points [2].y = y + height - thickness;
682 points [3].x = x + width;
683 points [3].y = y + height - thickness;
684 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
685 points [0].x = x;
686 points [0].y = y + height;
687 points [1].x = x + width;
688 points [1].y = y + height;
689 points [2].x = x + width;
690 points [2].y = y + height - thickness;
691 points [3].x = x + thickness;
692 points [3].y = y + height - thickness;
693 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
697 static void
698 draw_shadow_rhombus (XlwMenuWidget mw,
699 Window window,
700 int x,
701 int y,
702 int width,
703 int height,
704 int erase_p,
705 int down_p)
707 Display *dpy = XtDisplay (mw);
708 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
709 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
710 int thickness = mw->menu.shadow_thickness;
711 XPoint points [4];
713 if (!erase_p && down_p)
715 GC temp;
716 temp = top_gc;
717 top_gc = bottom_gc;
718 bottom_gc = temp;
721 points [0].x = x;
722 points [0].y = y + height / 2;
723 points [1].x = x + thickness;
724 points [1].y = y + height / 2;
725 points [2].x = x + width / 2;
726 points [2].y = y + thickness;
727 points [3].x = x + width / 2;
728 points [3].y = y;
729 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
730 points [0].x = x + width / 2;
731 points [0].y = y;
732 points [1].x = x + width / 2;
733 points [1].y = y + thickness;
734 points [2].x = x + width - thickness;
735 points [2].y = y + height / 2;
736 points [3].x = x + width;
737 points [3].y = y + height / 2;
738 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
739 points [0].x = x;
740 points [0].y = y + height / 2;
741 points [1].x = x + thickness;
742 points [1].y = y + height / 2;
743 points [2].x = x + width / 2;
744 points [2].y = y + height - thickness;
745 points [3].x = x + width / 2;
746 points [3].y = y + height;
747 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
748 points [0].x = x + width / 2;
749 points [0].y = y + height;
750 points [1].x = x + width / 2;
751 points [1].y = y + height - thickness;
752 points [2].x = x + width - thickness;
753 points [2].y = y + height / 2;
754 points [3].x = x + width;
755 points [3].y = y + height / 2;
756 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
760 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
761 top-left corner of the menu item. SELECTED_P non-zero means the
762 toggle button is selected. */
764 static void
765 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
767 int width, height;
769 width = toggle_button_width (mw);
770 height = width;
771 x += mw->menu.horizontal_spacing;
772 y += (MENU_FONT_ASCENT (mw) - height) / 2;
773 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
777 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
778 top-left corner of the menu item. SELECTED_P non-zero means the
779 toggle button is selected. */
781 static void
782 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
784 int width, height;
786 width = radio_button_width (mw);
787 height = width;
788 x += mw->menu.horizontal_spacing;
789 y += (MENU_FONT_ASCENT (mw) - height) / 2;
790 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
794 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
795 top-left corner of the menu item. WIDTH is the width of the
796 separator to draw. TYPE is the separator type. */
798 static void
799 draw_separator (XlwMenuWidget mw,
800 Window window,
801 int x,
802 int y,
803 int width,
804 enum menu_separator type)
806 Display *dpy = XtDisplay (mw);
807 XGCValues xgcv;
809 switch (type)
811 case SEPARATOR_NO_LINE:
812 break;
814 case SEPARATOR_SINGLE_LINE:
815 XDrawLine (dpy, window, mw->menu.foreground_gc,
816 x, y, x + width, y);
817 break;
819 case SEPARATOR_DOUBLE_LINE:
820 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
821 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
822 break;
824 case SEPARATOR_SINGLE_DASHED_LINE:
825 xgcv.line_style = LineOnOffDash;
826 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
827 XDrawLine (dpy, window, mw->menu.foreground_gc,
828 x, y, x + width, y);
829 xgcv.line_style = LineSolid;
830 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
831 break;
833 case SEPARATOR_DOUBLE_DASHED_LINE:
834 draw_separator (mw, window, x, y, width,
835 SEPARATOR_SINGLE_DASHED_LINE);
836 draw_separator (mw, window, x, y + 2, width,
837 SEPARATOR_SINGLE_DASHED_LINE);
838 break;
840 case SEPARATOR_SHADOW_ETCHED_IN:
841 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
842 x, y, x + width, y);
843 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
844 x, y + 1, x + width, y + 1);
845 break;
847 case SEPARATOR_SHADOW_ETCHED_OUT:
848 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
849 x, y, x + width, y);
850 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
851 x, y + 1, x + width, y + 1);
852 break;
854 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
855 xgcv.line_style = LineOnOffDash;
856 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
857 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
858 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
859 xgcv.line_style = LineSolid;
860 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
861 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
862 break;
864 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
865 xgcv.line_style = LineOnOffDash;
866 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
867 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
868 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
869 xgcv.line_style = LineSolid;
870 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
871 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
872 break;
874 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
875 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
876 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
877 break;
879 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
880 draw_separator (mw, window, x, y, width,
881 SEPARATOR_SHADOW_ETCHED_OUT);
882 draw_separator (mw, window, x, y + 3, width,
883 SEPARATOR_SHADOW_ETCHED_OUT);
884 break;
886 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
887 xgcv.line_style = LineOnOffDash;
888 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
889 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
890 draw_separator (mw, window, x, y, width,
891 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
892 xgcv.line_style = LineSolid;
893 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
894 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
895 break;
897 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
898 xgcv.line_style = LineOnOffDash;
899 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
900 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
901 draw_separator (mw, window, x, y, width,
902 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
903 xgcv.line_style = LineSolid;
904 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
905 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
906 break;
908 default:
909 abort ();
914 /* Return the pixel height of menu separator SEPARATOR. */
916 static int
917 separator_height (enum menu_separator separator)
919 switch (separator)
921 case SEPARATOR_NO_LINE:
922 return 2;
924 case SEPARATOR_SINGLE_LINE:
925 case SEPARATOR_SINGLE_DASHED_LINE:
926 return 1;
928 case SEPARATOR_DOUBLE_LINE:
929 case SEPARATOR_DOUBLE_DASHED_LINE:
930 return 3;
932 case SEPARATOR_SHADOW_ETCHED_IN:
933 case SEPARATOR_SHADOW_ETCHED_OUT:
934 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
935 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
936 return 2;
938 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
939 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
940 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
941 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
942 return 5;
944 default:
945 abort ();
950 /* Display the menu item and increment where.x and where.y to show how large
951 the menu item was. */
953 static void
954 display_menu_item (XlwMenuWidget mw,
955 widget_value* val,
956 window_state* ws,
957 XPoint* where,
958 Boolean highlighted_p,
959 Boolean horizontal_p,
960 Boolean just_compute_p)
962 GC deco_gc;
963 GC text_gc;
964 int font_height = MENU_FONT_HEIGHT (mw);
965 int font_ascent = MENU_FONT_ASCENT (mw);
966 int shadow = mw->menu.shadow_thickness;
967 int margin = mw->menu.margin;
968 int h_spacing = mw->menu.horizontal_spacing;
969 int v_spacing = mw->menu.vertical_spacing;
970 int label_width;
971 int rest_width;
972 int button_width;
973 int height;
974 int width;
975 enum menu_separator separator;
976 int separator_p = lw_separator_p (val->name, &separator, 0);
977 #ifdef HAVE_XFT
978 XftColor *xftfg;
979 #endif
981 /* compute the sizes of the item */
982 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
983 &button_width, &height);
985 if (horizontal_p)
986 width = label_width + rest_width;
987 else
989 label_width = ws->label_width;
990 width = ws->width - 2 * shadow;
993 /* Only highlight an enabled item that has a callback. */
994 if (highlighted_p)
995 if (!val->enabled || !(val->call_data || val->contents))
996 highlighted_p = 0;
998 /* do the drawing. */
999 if (!just_compute_p)
1001 /* Add the shadow border of the containing menu */
1002 int x = where->x + shadow;
1003 int y = where->y + shadow;
1005 if (horizontal_p)
1007 x += margin;
1008 y += margin;
1011 /* pick the foreground and background GC. */
1012 if (val->enabled)
1013 text_gc = mw->menu.foreground_gc;
1014 else
1015 text_gc = mw->menu.disabled_gc;
1016 deco_gc = mw->menu.foreground_gc;
1017 #ifdef HAVE_XFT
1018 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1019 #endif
1021 if (separator_p)
1023 draw_separator (mw, ws->pixmap, x, y, width, separator);
1025 else
1027 int x_offset = x + h_spacing + shadow;
1028 char* display_string = resource_widget_value (mw, val);
1029 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1030 False);
1032 /* Deal with centering a menu title. */
1033 if (!horizontal_p && !val->contents && !val->call_data)
1035 int l = string_width (mw, display_string);
1037 if (width > l)
1038 x_offset = (width - l) >> 1;
1040 else if (!horizontal_p && ws->button_width)
1041 x_offset += ws->button_width;
1044 #ifdef HAVE_XFT
1045 if (ws->xft_draw)
1047 int draw_y = y + v_spacing + shadow;
1048 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1049 mw->menu.xft_font,
1050 x_offset, draw_y + font_ascent,
1051 (unsigned char *) display_string,
1052 strlen (display_string));
1054 else
1055 #endif
1056 #ifdef HAVE_X_I18N
1057 if (mw->menu.fontSet)
1058 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1059 text_gc, x_offset,
1060 y + v_spacing + shadow + font_ascent,
1061 display_string, strlen (display_string));
1062 else
1063 #endif
1064 XDrawString (XtDisplay (mw), ws->pixmap,
1065 text_gc, x_offset,
1066 y + v_spacing + shadow + font_ascent,
1067 display_string, strlen (display_string));
1069 if (!horizontal_p)
1071 if (val->button_type == BUTTON_TYPE_TOGGLE)
1072 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1073 val->selected);
1074 else if (val->button_type == BUTTON_TYPE_RADIO)
1075 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1076 val->selected);
1078 if (val->contents)
1080 int a_w = arrow_width (mw);
1081 draw_arrow (mw, ws->pixmap, deco_gc,
1082 x + width - a_w
1083 - mw->menu.horizontal_spacing
1084 - mw->menu.shadow_thickness,
1085 y + v_spacing + shadow, a_w,
1086 highlighted_p);
1088 else if (val->key)
1090 #ifdef HAVE_XFT
1091 if (ws->xft_draw)
1093 int draw_x = ws->width - ws->max_rest_width
1094 + mw->menu.arrow_spacing;
1095 int draw_y = y + v_spacing + shadow + font_ascent;
1096 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1097 mw->menu.xft_font,
1098 draw_x, draw_y,
1099 (unsigned char *) val->key,
1100 strlen (val->key));
1102 else
1103 #endif
1104 #ifdef HAVE_X_I18N
1105 if (mw->menu.fontSet)
1106 XmbDrawString (XtDisplay (mw), ws->pixmap,
1107 mw->menu.fontSet,
1108 text_gc,
1109 x + label_width + mw->menu.arrow_spacing,
1110 y + v_spacing + shadow + font_ascent,
1111 val->key, strlen (val->key));
1112 else
1113 #endif
1114 XDrawString (XtDisplay (mw), ws->pixmap,
1115 text_gc,
1116 x + label_width + mw->menu.arrow_spacing,
1117 y + v_spacing + shadow + font_ascent,
1118 val->key, strlen (val->key));
1121 else
1123 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1124 mw->menu.background_gc,
1125 x + shadow, y + shadow,
1126 label_width + h_spacing - 1,
1127 font_height + 2 * v_spacing - 1);
1128 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1129 True, False);
1132 if (highlighted_p)
1133 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1134 False);
1138 where->x += width;
1139 where->y += height;
1142 static void
1143 display_menu (XlwMenuWidget mw,
1144 int level,
1145 Boolean just_compute_p,
1146 XPoint *highlighted_pos,
1147 XPoint *hit,
1148 widget_value **hit_return)
1150 widget_value* val;
1151 widget_value* following_item;
1152 window_state* ws;
1153 XPoint where;
1154 int horizontal_p = mw->menu.horizontal && (level == 0);
1155 int highlighted_p;
1156 int no_return = 0;
1157 enum menu_separator separator;
1159 if (level >= mw->menu.old_depth)
1160 abort_gracefully ((Widget) mw);
1162 if (level < mw->menu.old_depth - 1)
1163 following_item = mw->menu.old_stack [level + 1];
1164 else
1165 following_item = NULL;
1167 if (hit)
1168 *hit_return = NULL;
1170 where.x = 0;
1171 where.y = 0;
1173 ws = &mw->menu.windows [level];
1175 if (!just_compute_p)
1176 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1177 0, 0, ws->width, ws->height);
1179 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1181 highlighted_p = val == following_item;
1182 if (highlighted_p && highlighted_pos)
1184 if (horizontal_p)
1185 highlighted_pos->x = where.x;
1186 else
1187 highlighted_pos->y = where.y;
1190 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1191 just_compute_p);
1193 if (highlighted_p && highlighted_pos)
1195 if (horizontal_p)
1196 highlighted_pos->y = where.y;
1197 else
1198 highlighted_pos->x = where.x;
1201 if (hit
1202 && !*hit_return
1203 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1204 && !lw_separator_p (val->name, &separator, 0)
1205 && !no_return)
1207 if (val->enabled)
1208 *hit_return = val;
1209 else
1210 no_return = 1;
1211 if (mw->menu.inside_entry != val)
1213 if (mw->menu.inside_entry)
1214 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1215 (XtPointer) mw->menu.inside_entry);
1216 mw->menu.inside_entry = val;
1217 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1218 (XtPointer) mw->menu.inside_entry);
1222 if (horizontal_p)
1223 where.y = 0;
1224 else
1225 where.x = 0;
1228 if (!just_compute_p)
1230 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1231 False, False);
1232 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1233 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1237 \f/* Motion code */
1238 static void
1239 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1241 int i;
1243 mw->menu.new_depth = 0;
1244 for (i = 0; i < level; i++)
1245 push_new_stack (mw, mw->menu.old_stack [i]);
1246 push_new_stack (mw, val);
1249 static void
1250 expose_cb (Widget widget,
1251 XtPointer closure,
1252 XEvent* event,
1253 Boolean* continue_to_dispatch)
1255 XlwMenuWidget mw = (XlwMenuWidget) closure;
1256 int i;
1258 *continue_to_dispatch = False;
1259 for (i = 0; i < mw->menu.windows_length; ++i)
1260 if (mw->menu.windows [i].w == widget) break;
1261 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1262 display_menu (mw, i, False, NULL, NULL, NULL);
1265 static void
1266 set_window_type (Widget w, XlwMenuWidget mw)
1268 int popup_menu_p = mw->menu.top_depth == 1;
1269 Atom type = XInternAtom (XtDisplay (w),
1270 popup_menu_p
1271 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1272 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1273 False);
1275 XChangeProperty (XtDisplay (w), XtWindow (w),
1276 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1277 XA_ATOM, 32, PropModeReplace,
1278 (unsigned char *)&type, 1);
1282 static void
1283 make_windows_if_needed (XlwMenuWidget mw, int n)
1285 int i;
1286 int start_at;
1287 window_state* windows;
1289 if (mw->menu.windows_length >= n)
1290 return;
1292 if (!mw->menu.windows)
1294 mw->menu.windows =
1295 (window_state*)XtMalloc (n * sizeof (window_state));
1296 start_at = 0;
1298 else
1300 mw->menu.windows =
1301 (window_state*)XtRealloc ((char*)mw->menu.windows,
1302 n * sizeof (window_state));
1303 start_at = mw->menu.windows_length;
1305 mw->menu.windows_length = n;
1307 windows = mw->menu.windows;
1309 for (i = start_at; i < n; i++)
1311 Arg av[10];
1312 int ac = 0;
1313 windows [i].x = 0;
1314 windows [i].y = 0;
1315 windows [i].width = 1;
1316 windows [i].height = 1;
1317 windows [i].max_rest_width = 0;
1318 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1319 XtSetArg (av[ac], XtNheight, 1); ++ac;
1320 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1321 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1322 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1323 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1324 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1325 windows [i].w =
1326 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1327 (Widget) mw, av, ac);
1328 XtRealizeWidget (windows [i].w);
1329 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1330 windows [i].window = XtWindow (windows [i].w);
1331 windows [i].pixmap = None;
1332 #ifdef HAVE_XFT
1333 windows [i].xft_draw = 0;
1334 #endif
1335 set_window_type (windows [i].w, mw);
1337 XFlush (XtDisplay (mw));
1340 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1343 xlwmenu_window_p (Widget w, Window window)
1345 XlwMenuWidget mw = (XlwMenuWidget) w;
1346 int i;
1348 for (i = 0; i < mw->menu.windows_length; ++i)
1349 if (window == mw->menu.windows[i].window)
1350 break;
1352 return i < mw->menu.windows_length;
1355 /* Make the window fit in the screen */
1356 static void
1357 fit_to_screen (XlwMenuWidget mw,
1358 window_state *ws,
1359 window_state *previous_ws,
1360 Boolean horizontal_p)
1362 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1363 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1364 /* 1 if we are unable to avoid an overlap between
1365 this menu and the parent menu in the X dimension. */
1366 int horizontal_overlap = 0;
1368 if (ws->x < 0)
1369 ws->x = 0;
1370 else if (ws->x + ws->width > screen_width)
1372 if (!horizontal_p)
1373 /* The addition of shadow-thickness for a sub-menu's position is
1374 to reflect a similar adjustment when the menu is displayed to
1375 the right of the invoking menu-item; it makes the sub-menu
1376 look more `attached' to the menu-item. */
1377 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1378 else
1379 ws->x = screen_width - ws->width;
1380 if (ws->x < 0)
1382 ws->x = 0;
1383 horizontal_overlap = 1;
1386 /* If we overlap in X, try to avoid overlap in Y. */
1387 if (horizontal_overlap
1388 && ws->y < previous_ws->y + previous_ws->height
1389 && previous_ws->y < ws->y + ws->height)
1391 /* Put this menu right below or right above PREVIOUS_WS
1392 if there's room. */
1393 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1394 ws->y = previous_ws->y + previous_ws->height;
1395 else if (previous_ws->y - ws->height > 0)
1396 ws->y = previous_ws->y - ws->height;
1399 if (ws->y < 0)
1400 ws->y = 0;
1401 else if (ws->y + ws->height > screen_height)
1403 if (horizontal_p)
1404 ws->y = previous_ws->y - ws->height;
1405 else
1406 ws->y = screen_height - ws->height;
1407 if (ws->y < 0)
1408 ws->y = 0;
1412 static void
1413 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1415 if (ws->pixmap != None)
1417 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1418 ws->pixmap = None;
1420 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1421 ws->width, ws->height,
1422 DefaultDepthOfScreen (XtScreen (ws->w)));
1423 #ifdef HAVE_XFT
1424 if (ws->xft_draw)
1425 XftDrawDestroy (ws->xft_draw);
1426 if (mw->menu.xft_font)
1428 int screen = XScreenNumberOfScreen (mw->core.screen);
1429 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1430 ws->pixmap,
1431 DefaultVisual (XtDisplay (ws->w), screen),
1432 mw->core.colormap);
1434 else
1435 ws->xft_draw = 0;
1436 #endif
1439 /* Updates old_stack from new_stack and redisplays. */
1440 static void
1441 remap_menubar (XlwMenuWidget mw)
1443 int i;
1444 int last_same;
1445 XPoint selection_position;
1446 int old_depth = mw->menu.old_depth;
1447 int new_depth = mw->menu.new_depth;
1448 widget_value** old_stack;
1449 widget_value** new_stack;
1450 window_state* windows;
1451 widget_value* old_selection;
1452 widget_value* new_selection;
1454 /* Check that enough windows and old_stack are ready. */
1455 make_windows_if_needed (mw, new_depth);
1456 make_old_stack_space (mw, new_depth);
1457 windows = mw->menu.windows;
1458 old_stack = mw->menu.old_stack;
1459 new_stack = mw->menu.new_stack;
1461 /* compute the last identical different entry */
1462 for (i = 1; i < old_depth && i < new_depth; i++)
1463 if (old_stack [i] != new_stack [i])
1464 break;
1465 last_same = i - 1;
1467 /* Memorize the previously selected item to be able to refresh it */
1468 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1469 if (old_selection && !old_selection->enabled)
1470 old_selection = NULL;
1471 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1472 if (new_selection && !new_selection->enabled)
1473 new_selection = NULL;
1475 /* Call callback when the highlighted item changes. */
1476 if (old_selection || new_selection)
1477 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1478 (XtPointer) new_selection);
1480 /* updates old_state from new_state. It has to be done now because
1481 display_menu (called below) uses the old_stack to know what to display. */
1482 for (i = last_same + 1; i < new_depth; i++)
1484 XtPopdown (mw->menu.windows [i].w);
1485 old_stack [i] = new_stack [i];
1487 mw->menu.old_depth = new_depth;
1489 /* refresh the last selection */
1490 selection_position.x = 0;
1491 selection_position.y = 0;
1492 display_menu (mw, last_same, new_selection == old_selection,
1493 &selection_position, NULL, NULL);
1495 /* Now place the new menus. */
1496 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1498 window_state *previous_ws = &windows[i - 1];
1499 window_state *ws = &windows[i];
1501 ws->x = (previous_ws->x + selection_position.x
1502 + mw->menu.shadow_thickness);
1503 if (mw->menu.horizontal && i == 1)
1504 ws->x += mw->menu.margin;
1506 #if 0
1507 if (!mw->menu.horizontal || i > 1)
1508 ws->x += mw->menu.shadow_thickness;
1509 #endif
1511 ws->y = (previous_ws->y + selection_position.y
1512 + mw->menu.shadow_thickness);
1513 if (mw->menu.horizontal && i == 1)
1514 ws->y += mw->menu.margin;
1516 size_menu (mw, i);
1518 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1520 create_pixmap_for_menu (ws, mw);
1521 XtMoveWidget (ws->w, ws->x, ws->y);
1522 XtPopup (ws->w, XtGrabNone);
1523 XtResizeWidget (ws->w, ws->width, ws->height,
1524 mw->core.border_width);
1525 XtResizeWindow (ws->w);
1526 display_menu (mw, i, False, &selection_position, NULL, NULL);
1529 /* unmap the menus that popped down */
1530 for (i = new_depth - 1; i < old_depth; i++)
1531 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1532 XtPopdown (windows[i].w);
1535 static Boolean
1536 motion_event_is_in_menu (XlwMenuWidget mw,
1537 XMotionEvent *ev,
1538 int level,
1539 XPoint *relative_pos)
1541 window_state* ws = &mw->menu.windows [level];
1542 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1543 int x = ws->x + shadow;
1544 int y = ws->y + shadow;
1545 relative_pos->x = ev->x_root - x;
1546 relative_pos->y = ev->y_root - y;
1547 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1548 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1551 static Boolean
1552 map_event_to_widget_value (XlwMenuWidget mw,
1553 XMotionEvent *ev,
1554 widget_value **val,
1555 int *level)
1557 int i;
1558 XPoint relative_pos;
1559 window_state* ws;
1560 int inside = 0;
1562 *val = NULL;
1564 /* Find the window */
1565 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1567 ws = &mw->menu.windows [i];
1568 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1570 inside = 1;
1571 display_menu (mw, i, True, NULL, &relative_pos, val);
1573 if (*val)
1575 *level = i + 1;
1576 return True;
1581 if (!inside)
1583 if (mw->menu.inside_entry != NULL)
1584 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1585 (XtPointer) mw->menu.inside_entry);
1586 mw->menu.inside_entry = NULL;
1589 return False;
1592 \f/* Procedures */
1593 static void
1594 make_drawing_gcs (XlwMenuWidget mw)
1596 XGCValues xgcv;
1597 float scale;
1598 XtGCMask mask = GCForeground | GCBackground;
1600 #ifdef HAVE_X_I18N
1601 if (!mw->menu.fontSet && mw->menu.font)
1603 xgcv.font = mw->menu.font->fid;
1604 mask |= GCFont;
1606 #else
1607 if (mw->menu.font)
1609 xgcv.font = mw->menu.font->fid;
1610 mask |= GCFont;
1612 #endif
1613 xgcv.foreground = mw->menu.foreground;
1614 xgcv.background = mw->core.background_pixel;
1615 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1617 xgcv.foreground = mw->menu.button_foreground;
1618 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1620 xgcv.background = mw->core.background_pixel;
1622 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1624 /* Allocate color for disabled menu-items. */
1625 mw->menu.disabled_foreground = mw->menu.foreground;
1626 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1627 scale = 2.3;
1628 else
1629 scale = 0.55;
1631 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1632 mw->core.colormap,
1633 &mw->menu.disabled_foreground,
1634 scale,
1635 0x8000);
1637 if (mw->menu.foreground == mw->menu.disabled_foreground
1638 || mw->core.background_pixel == mw->menu.disabled_foreground)
1640 /* Too few colors, use stipple. */
1641 xgcv.foreground = mw->menu.foreground;
1642 xgcv.fill_style = FillStippled;
1643 xgcv.stipple = mw->menu.gray_pixmap;
1644 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1645 | GCFillStyle | GCStipple, &xgcv);
1647 else
1649 /* Many colors available, use disabled pixel. */
1650 xgcv.foreground = mw->menu.disabled_foreground;
1651 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1654 xgcv.foreground = mw->menu.button_foreground;
1655 xgcv.background = mw->core.background_pixel;
1656 xgcv.fill_style = FillStippled;
1657 xgcv.stipple = mw->menu.gray_pixmap;
1658 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1659 | GCFillStyle | GCStipple, &xgcv);
1661 xgcv.foreground = mw->core.background_pixel;
1662 xgcv.background = mw->menu.foreground;
1663 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1666 static void
1667 release_drawing_gcs (XlwMenuWidget mw)
1669 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1670 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1671 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1672 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1673 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1674 /* let's get some segvs if we try to use these... */
1675 mw->menu.foreground_gc = (GC) -1;
1676 mw->menu.button_gc = (GC) -1;
1677 mw->menu.disabled_gc = (GC) -1;
1678 mw->menu.inactive_button_gc = (GC) -1;
1679 mw->menu.background_gc = (GC) -1;
1682 #ifndef emacs
1683 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1684 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1685 #endif
1687 static void
1688 make_shadow_gcs (XlwMenuWidget mw)
1690 XGCValues xgcv;
1691 unsigned long pm = 0;
1692 Display *dpy = XtDisplay ((Widget) mw);
1693 Screen *screen = XtScreen ((Widget) mw);
1694 Colormap cmap = mw->core.colormap;
1695 XColor topc, botc;
1696 int top_frobbed = 0, bottom_frobbed = 0;
1698 mw->menu.free_top_shadow_color_p = 0;
1699 mw->menu.free_bottom_shadow_color_p = 0;
1701 if (mw->menu.top_shadow_color == -1)
1702 mw->menu.top_shadow_color = mw->core.background_pixel;
1703 else
1704 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1706 if (mw->menu.bottom_shadow_color == -1)
1707 mw->menu.bottom_shadow_color = mw->menu.foreground;
1708 else
1709 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1711 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1712 mw->menu.top_shadow_color == mw->menu.foreground)
1714 topc.pixel = mw->core.background_pixel;
1715 #ifdef emacs
1716 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1717 &topc.pixel,
1718 1.2, 0x8000))
1719 #else
1720 XQueryColor (dpy, cmap, &topc);
1721 /* don't overflow/wrap! */
1722 topc.red = MINL (65535, topc.red * 1.2);
1723 topc.green = MINL (65535, topc.green * 1.2);
1724 topc.blue = MINL (65535, topc.blue * 1.2);
1725 if (XAllocColor (dpy, cmap, &topc))
1726 #endif
1728 mw->menu.top_shadow_color = topc.pixel;
1729 mw->menu.free_top_shadow_color_p = 1;
1730 top_frobbed = 1;
1733 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1734 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1736 botc.pixel = mw->core.background_pixel;
1737 #ifdef emacs
1738 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1739 &botc.pixel,
1740 0.6, 0x4000))
1741 #else
1742 XQueryColor (dpy, cmap, &botc);
1743 botc.red *= 0.6;
1744 botc.green *= 0.6;
1745 botc.blue *= 0.6;
1746 if (XAllocColor (dpy, cmap, &botc))
1747 #endif
1749 mw->menu.bottom_shadow_color = botc.pixel;
1750 mw->menu.free_bottom_shadow_color_p = 1;
1751 bottom_frobbed = 1;
1755 if (top_frobbed && bottom_frobbed)
1757 if (topc.pixel == botc.pixel)
1759 if (botc.pixel == mw->menu.foreground)
1761 if (mw->menu.free_top_shadow_color_p)
1763 x_free_dpy_colors (dpy, screen, cmap,
1764 &mw->menu.top_shadow_color, 1);
1765 mw->menu.free_top_shadow_color_p = 0;
1767 mw->menu.top_shadow_color = mw->core.background_pixel;
1769 else
1771 if (mw->menu.free_bottom_shadow_color_p)
1773 x_free_dpy_colors (dpy, screen, cmap,
1774 &mw->menu.bottom_shadow_color, 1);
1775 mw->menu.free_bottom_shadow_color_p = 0;
1777 mw->menu.bottom_shadow_color = mw->menu.foreground;
1782 if (!mw->menu.top_shadow_pixmap &&
1783 mw->menu.top_shadow_color == mw->core.background_pixel)
1785 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1786 if (mw->menu.free_top_shadow_color_p)
1788 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1789 mw->menu.free_top_shadow_color_p = 0;
1791 mw->menu.top_shadow_color = mw->menu.foreground;
1793 if (!mw->menu.bottom_shadow_pixmap &&
1794 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1796 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1797 if (mw->menu.free_bottom_shadow_color_p)
1799 x_free_dpy_colors (dpy, screen, cmap,
1800 &mw->menu.bottom_shadow_color, 1);
1801 mw->menu.free_bottom_shadow_color_p = 0;
1803 mw->menu.bottom_shadow_color = mw->menu.foreground;
1806 xgcv.fill_style = FillStippled;
1807 xgcv.foreground = mw->menu.top_shadow_color;
1808 xgcv.stipple = mw->menu.top_shadow_pixmap;
1809 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1810 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1812 xgcv.foreground = mw->menu.bottom_shadow_color;
1813 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1814 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1815 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1819 static void
1820 release_shadow_gcs (XlwMenuWidget mw)
1822 Display *dpy = XtDisplay ((Widget) mw);
1823 Screen *screen = XtScreen ((Widget) mw);
1824 Colormap cmap = mw->core.colormap;
1825 Pixel px[2];
1826 int i = 0;
1828 if (mw->menu.free_top_shadow_color_p)
1829 px[i++] = mw->menu.top_shadow_color;
1830 if (mw->menu.free_bottom_shadow_color_p)
1831 px[i++] = mw->menu.bottom_shadow_color;
1832 if (i > 0)
1833 x_free_dpy_colors (dpy, screen, cmap, px, i);
1835 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1836 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1839 #ifdef HAVE_XFT
1840 static XftFont *
1841 getDefaultXftFont (XlwMenuWidget mw)
1843 int screen = XScreenNumberOfScreen (mw->core.screen);
1844 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1847 static int
1848 openXftFont (XlwMenuWidget mw)
1850 char *fname = mw->menu.fontName;
1852 mw->menu.xft_font = 0;
1853 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1855 if (fname && strcmp (fname, "none") != 0)
1857 int screen = XScreenNumberOfScreen (mw->core.screen);
1858 int len = strlen (fname), i = len-1;
1859 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1860 while (i > 0 && '0' <= fname[i] && fname[i] <= '9')
1861 --i;
1862 if (fname[i] == ' ')
1864 fname = xstrdup (mw->menu.fontName);
1865 fname[i] = '-';
1868 mw->menu.font = XLoadQueryFont (XtDisplay (mw), fname);
1869 if (!mw->menu.font)
1871 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1872 if (!mw->menu.xft_font)
1874 fprintf (stderr, "Can't find font '%s'\n", fname);
1875 mw->menu.xft_font = getDefaultXftFont (mw);
1880 if (fname != mw->menu.fontName) xfree (fname);
1882 return mw->menu.xft_font != 0;
1884 #endif
1886 static void
1887 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1889 /* Get the GCs and the widget size */
1890 XlwMenuWidget mw = (XlwMenuWidget) w;
1891 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1892 Display* display = XtDisplay (mw);
1894 #if 0
1895 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1897 /* _XtCreate is freeing the object that was passed to us,
1898 so make a copy that we will actually keep. */
1899 memcpy (tem, mw->menu.contents, sizeof (widget_value));
1900 mw->menu.contents = tem;
1901 #endif
1903 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1904 mw->menu.cursor = mw->menu.cursor_shape;
1906 mw->menu.gray_pixmap
1907 = XCreatePixmapFromBitmapData (display, window, gray_bits,
1908 gray_width, gray_height,
1909 (unsigned long)1, (unsigned long)0, 1);
1911 #ifdef HAVE_XFT
1912 if (openXftFont (mw))
1914 else
1915 #endif
1917 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1918 if (!mw->menu.font)
1920 mw->menu.font = XLoadQueryFont (display, "fixed");
1921 if (!mw->menu.font)
1923 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1924 abort ();
1929 #ifdef HAVE_X_I18N
1930 if (mw->menu.fontSet)
1931 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1932 #endif
1934 make_drawing_gcs (mw);
1935 make_shadow_gcs (mw);
1937 mw->menu.popped_up = False;
1939 mw->menu.old_depth = 1;
1940 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1941 mw->menu.old_stack_length = 1;
1942 mw->menu.old_stack [0] = mw->menu.contents;
1944 mw->menu.new_depth = 0;
1945 mw->menu.new_stack = 0;
1946 mw->menu.new_stack_length = 0;
1947 push_new_stack (mw, mw->menu.contents);
1949 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1950 mw->menu.windows_length = 1;
1951 mw->menu.windows [0].x = 0;
1952 mw->menu.windows [0].y = 0;
1953 mw->menu.windows [0].width = 0;
1954 mw->menu.windows [0].height = 0;
1955 mw->menu.windows [0].max_rest_width = 0;
1956 mw->menu.windows [0].pixmap = None;
1957 #ifdef HAVE_XFT
1958 mw->menu.windows [0].xft_draw = 0;
1959 #endif
1960 size_menu (mw, 0);
1962 mw->core.width = mw->menu.windows [0].width;
1963 mw->core.height = mw->menu.windows [0].height;
1966 static void
1967 XlwMenuClassInitialize (void)
1971 static void
1972 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1974 XlwMenuWidget mw = (XlwMenuWidget)w;
1975 XSetWindowAttributes xswa;
1976 int mask;
1978 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1979 (w, valueMask, attributes);
1981 xswa.save_under = True;
1982 xswa.cursor = mw->menu.cursor_shape;
1983 mask = CWSaveUnder | CWCursor;
1984 /* I sometimes get random BadCursor errors while creating the first
1985 frame on a display. I can not find their reason, but they are
1986 annoying so for now let's ignore any errors here. -- lorentey */
1987 #ifdef emacs
1988 x_catch_errors (XtDisplay (w));
1989 #endif
1990 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1991 #ifdef emacs
1992 x_uncatch_errors ();
1993 #endif
1995 mw->menu.windows [0].w = w;
1996 mw->menu.windows [0].window = XtWindow (w);
1997 mw->menu.windows [0].x = w->core.x;
1998 mw->menu.windows [0].y = w->core.y;
1999 mw->menu.windows [0].width = w->core.width;
2000 mw->menu.windows [0].height = w->core.height;
2002 set_window_type (mw->menu.windows [0].w, mw);
2003 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2005 #ifdef HAVE_XFT
2006 if (mw->menu.xft_font)
2008 XColor colors[3];
2009 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
2010 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
2011 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
2012 = mw->menu.disabled_foreground;
2013 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
2014 mw->menu.xft_fg.color.alpha = 0xFFFF;
2015 mw->menu.xft_fg.color.red = colors[0].red;
2016 mw->menu.xft_fg.color.green = colors[0].green;
2017 mw->menu.xft_fg.color.blue = colors[0].blue;
2018 mw->menu.xft_bg.color.alpha = 0xFFFF;
2019 mw->menu.xft_bg.color.red = colors[1].red;
2020 mw->menu.xft_bg.color.green = colors[1].green;
2021 mw->menu.xft_bg.color.blue = colors[1].blue;
2022 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2023 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2024 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2025 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2027 #endif
2030 /* Only the toplevel menubar/popup is a widget so it's the only one that
2031 receives expose events through Xt. So we repaint all the other panes
2032 when receiving an Expose event. */
2033 static void
2034 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2036 XlwMenuWidget mw = (XlwMenuWidget)w;
2038 /* If we have a depth beyond 1, it's because a submenu was displayed.
2039 If the submenu has been destroyed, set the depth back to 1. */
2040 if (submenu_destroyed)
2042 mw->menu.old_depth = 1;
2043 submenu_destroyed = 0;
2046 display_menu (mw, 0, False, NULL, NULL, NULL);
2050 /* Part of a hack to make the menu redisplay when a tooltip frame
2051 over a menu item is unmapped. */
2053 void
2054 xlwmenu_redisplay (Widget w)
2056 XlwMenuRedisplay (w, NULL, None);
2059 static void
2060 XlwMenuDestroy (Widget w)
2062 int i;
2063 XlwMenuWidget mw = (XlwMenuWidget) w;
2065 if (pointer_grabbed)
2066 ungrab_all ((Widget)w, CurrentTime);
2067 pointer_grabbed = 0;
2069 submenu_destroyed = 1;
2071 release_drawing_gcs (mw);
2072 release_shadow_gcs (mw);
2074 /* this doesn't come from the resource db but is created explicitly
2075 so we must free it ourselves. */
2076 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2077 mw->menu.gray_pixmap = (Pixmap) -1;
2079 #if 0
2080 /* Do free mw->menu.contents because nowadays we copy it
2081 during initialization. */
2082 XtFree (mw->menu.contents);
2083 #endif
2085 /* Don't free mw->menu.contents because that comes from our creator.
2086 The `*_stack' elements are just pointers into `contents' so leave
2087 that alone too. But free the stacks themselves. */
2088 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2089 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2091 /* Remember, you can't free anything that came from the resource
2092 database. This includes:
2093 mw->menu.cursor
2094 mw->menu.top_shadow_pixmap
2095 mw->menu.bottom_shadow_pixmap
2096 mw->menu.font
2097 Also the color cells of top_shadow_color, bottom_shadow_color,
2098 foreground, and button_foreground will never be freed until this
2099 client exits. Nice, eh?
2102 #ifdef HAVE_XFT
2103 if (mw->menu.windows [0].xft_draw)
2104 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2105 if (mw->menu.xft_font)
2106 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2107 #endif
2109 if (mw->menu.windows [0].pixmap != None)
2110 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2111 /* start from 1 because the one in slot 0 is w->core.window */
2112 for (i = 1; i < mw->menu.windows_length; i++)
2114 if (mw->menu.windows [i].pixmap != None)
2115 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2116 #ifdef HAVE_XFT
2117 if (mw->menu.windows [i].xft_draw)
2118 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2119 #endif
2122 if (mw->menu.windows)
2123 XtFree ((char *) mw->menu.windows);
2126 #ifdef HAVE_XFT
2127 static int
2128 fontname_changed (XlwMenuWidget newmw,
2129 XlwMenuWidget oldmw)
2131 /* This will force a new XftFont even if the same string is set.
2132 This is good, as rendering parameters may have changed and
2133 we just want to do a redisplay. */
2134 return newmw->menu.fontName != oldmw->menu.fontName;
2136 #endif
2138 static Boolean
2139 XlwMenuSetValues (Widget current, Widget request, Widget new,
2140 ArgList args, Cardinal *num_args)
2142 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2143 XlwMenuWidget newmw = (XlwMenuWidget)new;
2144 Boolean do_redisplay = False;
2146 if (newmw->menu.contents
2147 && newmw->menu.contents->contents
2148 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2149 do_redisplay = True;
2150 /* Do redisplay if the contents are entirely eliminated. */
2151 if (newmw->menu.contents
2152 && newmw->menu.contents->contents == 0
2153 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2154 do_redisplay = True;
2156 if (newmw->core.background_pixel != oldmw->core.background_pixel
2157 || newmw->menu.foreground != oldmw->menu.foreground
2158 #ifdef HAVE_XFT
2159 || fontname_changed (newmw, oldmw)
2160 #endif
2161 #ifdef HAVE_X_I18N
2162 || newmw->menu.fontSet != oldmw->menu.fontSet
2163 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2164 #else
2165 || newmw->menu.font != oldmw->menu.font
2166 #endif
2169 int i;
2170 release_drawing_gcs (newmw);
2171 make_drawing_gcs (newmw);
2173 release_shadow_gcs (newmw);
2174 /* Cause the shadow colors to be recalculated. */
2175 newmw->menu.top_shadow_color = -1;
2176 newmw->menu.bottom_shadow_color = -1;
2177 make_shadow_gcs (newmw);
2179 do_redisplay = True;
2181 if (XtIsRealized (current))
2182 /* If the menu is currently displayed, change the display. */
2183 for (i = 0; i < oldmw->menu.windows_length; i++)
2185 XSetWindowBackground (XtDisplay (oldmw),
2186 oldmw->menu.windows [i].window,
2187 newmw->core.background_pixel);
2188 /* clear windows and generate expose events */
2189 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2190 0, 0, 0, 0, True);
2194 #ifdef HAVE_XFT
2195 if (fontname_changed (newmw, oldmw))
2197 int i;
2198 int screen = XScreenNumberOfScreen (newmw->core.screen);
2199 if (newmw->menu.xft_font)
2200 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2201 openXftFont (newmw);
2202 for (i = 0; i < newmw->menu.windows_length; i++)
2204 if (newmw->menu.windows [i].xft_draw)
2205 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2206 newmw->menu.windows [i].xft_draw = 0;
2208 if (newmw->menu.xft_font)
2209 for (i = 0; i < newmw->menu.windows_length; i++)
2210 newmw->menu.windows [i].xft_draw
2211 = XftDrawCreate (XtDisplay (newmw),
2212 newmw->menu.windows [i].window,
2213 DefaultVisual (XtDisplay (newmw), screen),
2214 newmw->core.colormap);
2216 #endif
2217 #ifdef HAVE_X_I18N
2218 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2220 do_redisplay = True;
2221 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2223 #endif
2225 return do_redisplay;
2228 static void
2229 XlwMenuResize (Widget w)
2231 XlwMenuWidget mw = (XlwMenuWidget)w;
2233 if (mw->menu.popped_up)
2235 /* Don't allow the popup menu to resize itself. */
2236 mw->core.width = mw->menu.windows [0].width;
2237 mw->core.height = mw->menu.windows [0].height;
2238 mw->core.parent->core.width = mw->core.width;
2239 mw->core.parent->core.height = mw->core.height;
2241 else
2243 mw->menu.windows [0].width = mw->core.width;
2244 mw->menu.windows [0].height = mw->core.height;
2245 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2249 \f/* Action procedures */
2250 static void
2251 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2253 widget_value* val;
2254 int level;
2256 if (!map_event_to_widget_value (mw, ev, &val, &level))
2257 pop_new_stack_if_no_contents (mw);
2258 else
2259 set_new_state (mw, val, level);
2260 remap_menubar (mw);
2262 /* Sync with the display. Makes it feel better on X terms. */
2263 XSync (XtDisplay (mw), False);
2266 static void
2267 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2269 int x = ev->x_root;
2270 int y = ev->y_root;
2271 int state = ev->state;
2272 XMotionEvent oldev = *ev;
2274 /* allow motion events to be generated again */
2275 if (ev->is_hint
2276 && XQueryPointer (XtDisplay (mw), ev->window,
2277 &ev->root, &ev->subwindow,
2278 &ev->x_root, &ev->y_root,
2279 &ev->x, &ev->y,
2280 &ev->state)
2281 && ev->state == state
2282 && (ev->x_root != x || ev->y_root != y))
2283 handle_single_motion_event (mw, ev);
2284 else
2285 handle_single_motion_event (mw, &oldev);
2288 static void
2289 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2291 XlwMenuWidget mw = (XlwMenuWidget)w;
2293 if (!mw->menu.popped_up)
2295 menu_post_event = *ev;
2296 /* If event is set to CurrentTime, get the last known time stamp.
2297 This is for calculating if (popup) menus should stay up after
2298 a fast click. */
2299 if (menu_post_event.xbutton.time == CurrentTime)
2300 menu_post_event.xbutton.time
2301 = XtLastTimestampProcessed (XtDisplay (w));
2303 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2305 else
2307 /* If we push a button while the menu is posted semipermanently,
2308 releasing the button should always pop the menu down. */
2309 next_release_must_exit = 1;
2311 /* notes the absolute position of the menubar window */
2312 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2313 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2315 /* handles the down like a move, slots are compatible */
2316 ev->xmotion.is_hint = 0;
2317 handle_motion_event (mw, &ev->xmotion);
2321 static void
2322 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2324 XlwMenuWidget mw = (XlwMenuWidget)w;
2325 if (mw->menu.popped_up)
2326 handle_motion_event (mw, &ev->xmotion);
2329 /* Do nothing.
2330 This is how we handle presses and releases of modifier keys. */
2331 static void
2332 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2336 static widget_value *
2337 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2339 widget_value *current = item;
2340 enum menu_separator separator;
2342 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2343 || (skip_titles && !current->call_data && !current->contents))
2344 if (current->next)
2345 current=current->next;
2346 else
2347 return NULL;
2349 return current;
2352 static widget_value *
2353 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2355 widget_value *current = item;
2356 enum menu_separator separator;
2358 while (current->next && (current=current->next) &&
2359 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2360 || (skip_titles && !current->call_data && !current->contents)))
2363 if (current == item)
2365 if (mw->menu.old_depth < 2)
2366 return current;
2367 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2369 while (lw_separator_p (current->name, &separator, 0)
2370 || !current->enabled
2371 || (skip_titles && !current->call_data
2372 && !current->contents))
2374 if (current->next)
2375 current=current->next;
2377 if (current == item)
2378 break;
2383 return current;
2386 static widget_value *
2387 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2389 widget_value *current = item;
2390 widget_value *prev = item;
2392 while ((current=find_next_selectable (mw, current, skip_titles))
2393 != item)
2395 if (prev == current)
2396 break;
2397 prev=current;
2400 return prev;
2403 static void
2404 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2406 XlwMenuWidget mw = (XlwMenuWidget) w;
2407 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2408 int popup_menu_p = mw->menu.top_depth == 1;
2410 /* Inside top-level menu-bar? */
2411 if (mw->menu.old_depth == mw->menu.top_depth)
2412 /* When <down> in the menu-bar is pressed, display the corresponding
2413 sub-menu and select the first selectable menu item there.
2414 If this is a popup menu, skip title item of the popup. */
2415 set_new_state (mw,
2416 find_first_selectable (mw,
2417 selected_item->contents,
2418 popup_menu_p),
2419 mw->menu.old_depth);
2420 else
2421 /* Highlight next possible (enabled and not separator) menu item. */
2422 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2423 mw->menu.old_depth - 1);
2425 remap_menubar (mw);
2428 static void
2429 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2431 XlwMenuWidget mw = (XlwMenuWidget) w;
2432 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2433 int popup_menu_p = mw->menu.top_depth == 1;
2435 /* Inside top-level menu-bar? */
2436 if (mw->menu.old_depth == mw->menu.top_depth)
2438 /* FIXME: this is tricky. <up> in the menu-bar should select the
2439 last selectable item in the list. So we select the first
2440 selectable one and find the previous selectable item. Is there
2441 a better way? */
2442 /* If this is a popup menu, skip title item of the popup. */
2443 set_new_state (mw,
2444 find_first_selectable (mw,
2445 selected_item->contents,
2446 popup_menu_p),
2447 mw->menu.old_depth);
2448 remap_menubar (mw);
2449 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2450 set_new_state (mw,
2451 find_prev_selectable (mw,
2452 selected_item,
2453 popup_menu_p),
2454 mw->menu.old_depth - 1);
2456 else
2457 /* Highlight previous (enabled and not separator) menu item. */
2458 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2459 mw->menu.old_depth - 1);
2461 remap_menubar (mw);
2464 void
2465 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2467 XlwMenuWidget mw = (XlwMenuWidget) w;
2468 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2470 /* Inside top-level menu-bar? */
2471 if (mw->menu.old_depth == mw->menu.top_depth)
2472 /* When <left> in the menu-bar is pressed, display the previous item on
2473 the menu-bar. If the current item is the first one, highlight the
2474 last item in the menubar (probably Help). */
2475 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2476 mw->menu.old_depth - 1);
2477 else if (mw->menu.old_depth == 1
2478 && selected_item->contents) /* Is this menu item expandable? */
2480 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2481 remap_menubar (mw);
2482 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2483 if (!selected_item->enabled && find_first_selectable (mw,
2484 selected_item,
2486 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2487 mw->menu.old_depth - 1);
2490 else
2492 pop_new_stack_if_no_contents (mw);
2493 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2494 mw->menu.old_depth - 2);
2497 remap_menubar (mw);
2500 void
2501 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2503 XlwMenuWidget mw = (XlwMenuWidget) w;
2504 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2506 /* Inside top-level menu-bar? */
2507 if (mw->menu.old_depth == mw->menu.top_depth)
2508 /* When <right> in the menu-bar is pressed, display the next item on
2509 the menu-bar. If the current item is the last one, highlight the
2510 first item (probably File). */
2511 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2512 mw->menu.old_depth - 1);
2513 else if (selected_item->contents) /* Is this menu item expandable? */
2515 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2516 remap_menubar (mw);
2517 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2518 if (!selected_item->enabled && find_first_selectable (mw,
2519 selected_item,
2521 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2522 mw->menu.old_depth - 1);
2524 else
2526 pop_new_stack_if_no_contents (mw);
2527 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2528 mw->menu.old_depth - 2);
2531 remap_menubar (mw);
2534 /* Handle key press and release events while menu is popped up.
2535 Our action is to get rid of the menu. */
2536 static void
2537 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2539 XlwMenuWidget mw = (XlwMenuWidget)w;
2541 /* Pop down everything. */
2542 mw->menu.new_depth = 1;
2543 remap_menubar (mw);
2545 if (mw->menu.popped_up)
2547 mw->menu.popped_up = False;
2548 ungrab_all ((Widget)mw, ev->xmotion.time);
2549 if (XtIsShell (XtParent ((Widget) mw)))
2550 XtPopdown (XtParent ((Widget) mw));
2551 else
2553 XtRemoveGrab ((Widget) mw);
2554 display_menu (mw, 0, False, NULL, NULL, NULL);
2558 /* callback */
2559 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2562 static void
2563 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2565 XlwMenuWidget mw = (XlwMenuWidget)w;
2566 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2568 /* If user releases the button quickly, without selecting anything,
2569 after the initial down-click that brought the menu up,
2570 do nothing. */
2571 if ((selected_item == 0
2572 || ((widget_value *) selected_item)->call_data == 0)
2573 && !next_release_must_exit
2574 && (ev->xbutton.time - menu_post_event.xbutton.time
2575 < XtGetMultiClickTime (XtDisplay (w))))
2576 return;
2578 /* pop down everything. */
2579 mw->menu.new_depth = 1;
2580 remap_menubar (mw);
2582 if (mw->menu.popped_up)
2584 mw->menu.popped_up = False;
2585 ungrab_all ((Widget)mw, ev->xmotion.time);
2586 if (XtIsShell (XtParent ((Widget) mw)))
2587 XtPopdown (XtParent ((Widget) mw));
2588 else
2590 XtRemoveGrab ((Widget) mw);
2591 display_menu (mw, 0, False, NULL, NULL, NULL);
2595 /* callback */
2596 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2600 \f/* Special code to pop-up a menu */
2601 static void
2602 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2604 int x = event->x_root;
2605 int y = event->y_root;
2606 int w;
2607 int h;
2608 int borderwidth = mw->menu.shadow_thickness;
2609 Screen* screen = XtScreen (mw);
2610 Display *display = XtDisplay (mw);
2612 next_release_must_exit = 0;
2614 mw->menu.inside_entry = NULL;
2615 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2617 if (XtIsShell (XtParent ((Widget)mw)))
2618 size_menu (mw, 0);
2620 w = mw->menu.windows [0].width;
2621 h = mw->menu.windows [0].height;
2623 x -= borderwidth;
2624 y -= borderwidth;
2625 if (x < borderwidth)
2626 x = borderwidth;
2627 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2628 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2629 if (y < borderwidth)
2630 y = borderwidth;
2631 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2632 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2634 mw->menu.popped_up = True;
2635 if (XtIsShell (XtParent ((Widget)mw)))
2637 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2638 XtParent ((Widget)mw)->core.border_width);
2639 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2640 display_menu (mw, 0, False, NULL, NULL, NULL);
2641 mw->menu.windows [0].x = x + borderwidth;
2642 mw->menu.windows [0].y = y + borderwidth;
2643 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2645 else
2647 XEvent *ev = (XEvent *) event;
2649 XtAddGrab ((Widget) mw, True, True);
2651 /* notes the absolute position of the menubar window */
2652 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2653 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2654 mw->menu.top_depth = 2;
2657 #ifdef emacs
2658 x_catch_errors (display);
2659 #endif
2660 if (XtGrabPointer ((Widget)mw, False,
2661 (PointerMotionMask
2662 | PointerMotionHintMask
2663 | ButtonReleaseMask
2664 | ButtonPressMask),
2665 GrabModeAsync, GrabModeAsync, None,
2666 mw->menu.cursor_shape,
2667 event->time) == Success)
2669 if (! GRAB_KEYBOARD
2670 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2671 GrabModeAsync, event->time) == Success)
2673 XtSetKeyboardFocus((Widget)mw, None);
2674 pointer_grabbed = 1;
2676 else
2677 XtUngrabPointer ((Widget)mw, event->time);
2680 #ifdef emacs
2681 if (x_had_errors_p (display))
2683 pointer_grabbed = 0;
2684 XtUngrabPointer ((Widget)mw, event->time);
2686 x_uncatch_errors ();
2687 #endif
2689 ((XMotionEvent*)event)->is_hint = 0;
2690 handle_motion_event (mw, (XMotionEvent*)event);