Rudimentary support for vc-pull and vc-merge in Git and Mercurial.
[emacs.git] / lwlib / xlwmenu.c
bloba53275c8e879146d362b00ed9c3d10ea2b28b178
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 1994-1995, 1997, 1999-2011 Free Software Foundation, Inc.
5 This file is part of the Lucid Widget Library.
7 The Lucid Widget Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
12 The Lucid Widget Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA. */
22 /* Created by devin@lucid.com */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include <setjmp.h>
29 #include "lisp.h"
31 #include <stdio.h>
32 #include <ctype.h>
34 #include <sys/types.h>
35 #if (defined __sun) && !(defined SUNOS41)
36 #define SUNOS41
37 #include <X11/Xos.h>
38 #undef SUNOS41
39 #else
40 #include <X11/Xos.h>
41 #endif
42 #include <X11/IntrinsicP.h>
43 #include <X11/ObjectP.h>
44 #include <X11/StringDefs.h>
45 #include <X11/cursorfont.h>
46 #include <X11/Shell.h>
47 #include "xlwmenuP.h"
49 #ifdef emacs
51 /* Defined in xfns.c. When config.h defines `static' as empty, we get
52 redefinition errors when gray_bitmap is included more than once, so
53 we're referring to the one include in xfns.c here. */
55 extern int gray_bitmap_width;
56 extern int gray_bitmap_height;
57 extern char *gray_bitmap_bits;
59 #include "xterm.h"
61 #else /* not emacs */
63 #include <X11/bitmaps/gray>
64 #define gray_bitmap_width gray_width
65 #define gray_bitmap_height gray_height
66 #define gray_bitmap_bits gray_bits
68 #endif /* not emacs */
70 static int pointer_grabbed;
71 static XEvent menu_post_event;
73 static XFontStruct *xlwmenu_default_font;
75 static char
76 xlwMenuTranslations [] =
77 "<BtnDown>: start()\n\
78 <Motion>: drag()\n\
79 <BtnUp>: select()\n\
80 <Key>Shift_L: nothing()\n\
81 <Key>Shift_R: nothing()\n\
82 <Key>Meta_L: nothing()\n\
83 <Key>Meta_R: nothing()\n\
84 <Key>Control_L: nothing()\n\
85 <Key>Control_R: nothing()\n\
86 <Key>Hyper_L: nothing()\n\
87 <Key>Hyper_R: nothing()\n\
88 <Key>Super_L: nothing()\n\
89 <Key>Super_R: nothing()\n\
90 <Key>Alt_L: nothing()\n\
91 <Key>Alt_R: nothing()\n\
92 <Key>Caps_Lock: nothing()\n\
93 <Key>Shift_Lock: nothing()\n\
94 <KeyUp>Shift_L: nothing()\n\
95 <KeyUp>Shift_R: nothing()\n\
96 <KeyUp>Meta_L: nothing()\n\
97 <KeyUp>Meta_R: nothing()\n\
98 <KeyUp>Control_L: nothing()\n\
99 <KeyUp>Control_R: nothing()\n\
100 <KeyUp>Hyper_L: nothing()\n\
101 <KeyUp>Hyper_R: nothing()\n\
102 <KeyUp>Super_L: nothing()\n\
103 <KeyUp>Super_R: nothing()\n\
104 <KeyUp>Alt_L: nothing()\n\
105 <KeyUp>Alt_R: nothing()\n\
106 <KeyUp>Caps_Lock: nothing()\n\
107 <KeyUp>Shift_Lock:nothing()\n\
108 <Key>Return: select()\n\
109 <Key>Down: down()\n\
110 <Key>Up: up()\n\
111 <Key>Left: left()\n\
112 <Key>Right: right()\n\
113 <Key>: key()\n\
114 <KeyUp>: key()\n\
117 /* FIXME: Space should toggle toggleable menu item but not remove the menu
118 so you can toggle the next one without entering the menu again. */
120 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
122 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
124 #define offset(field) XtOffset(XlwMenuWidget, field)
125 static XtResource
126 xlwMenuResources[] =
128 #ifdef HAVE_X_I18N
129 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
130 offset(menu.fontSet), XtRFontSet, NULL},
131 #endif
132 #ifdef HAVE_XFT
133 #define DEFAULT_FACENAME "Sans-10"
134 {XtNfaceName, XtCFaceName, XtRString, sizeof(String),
135 offset(menu.faceName), XtRString, DEFAULT_FACENAME},
136 {XtNdefaultFace, XtCDefaultFace, XtRInt, sizeof(int),
137 offset(menu.default_face), XtRImmediate, (XtPointer)1},
138 #endif
139 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
140 offset(menu.font), XtRString, "XtDefaultFont"},
141 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
142 offset(menu.foreground), XtRString, "XtDefaultForeground"},
143 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
144 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
145 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
146 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
147 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
148 offset(menu.margin), XtRImmediate, (XtPointer)1},
149 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
150 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
151 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
152 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
153 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
154 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
156 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
157 sizeof (Dimension), offset (menu.shadow_thickness),
158 XtRImmediate, (XtPointer)1},
159 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
160 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
161 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
162 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
163 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
164 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
165 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
166 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
168 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
169 offset(menu.open), XtRCallback, (XtPointer)NULL},
170 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
171 offset(menu.select), XtRCallback, (XtPointer)NULL},
172 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
173 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
174 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
175 offset(menu.enter), XtRCallback, (XtPointer)NULL},
176 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
177 offset(menu.leave), XtRCallback, (XtPointer)NULL},
178 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
179 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
180 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
181 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
182 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
183 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
185 #undef offset
187 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
188 ArgList args, Cardinal *num_args);
189 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
190 static void XlwMenuResize(Widget w);
191 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
192 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
193 static void XlwMenuDestroy(Widget w);
194 static void XlwMenuClassInitialize(void);
195 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
196 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
197 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
198 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
199 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
200 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
201 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
202 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
203 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
204 static int separator_height (enum menu_separator);
205 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
206 static void abort_gracefully (Widget w) NO_RETURN;
208 static XtActionsRec
209 xlwMenuActionsList [] =
211 {"start", Start},
212 {"drag", Drag},
213 {"down", Down},
214 {"up", Up},
215 {"left", Left},
216 {"right", Right},
217 {"select", Select},
218 {"key", Key},
219 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
220 {"nothing", Nothing},
223 #define SuperClass ((CoreWidgetClass)&coreClassRec)
225 XlwMenuClassRec xlwMenuClassRec =
227 { /* CoreClass fields initialization */
228 (WidgetClass) SuperClass, /* superclass */
229 "XlwMenu", /* class_name */
230 sizeof(XlwMenuRec), /* size */
231 XlwMenuClassInitialize, /* class_initialize */
232 NULL, /* class_part_initialize */
233 FALSE, /* class_inited */
234 XlwMenuInitialize, /* initialize */
235 NULL, /* initialize_hook */
236 XlwMenuRealize, /* realize */
237 xlwMenuActionsList, /* actions */
238 XtNumber(xlwMenuActionsList), /* num_actions */
239 xlwMenuResources, /* resources */
240 XtNumber(xlwMenuResources), /* resource_count */
241 NULLQUARK, /* xrm_class */
242 TRUE, /* compress_motion */
243 XtExposeCompressMaximal, /* compress_exposure */
244 TRUE, /* compress_enterleave */
245 FALSE, /* visible_interest */
246 XlwMenuDestroy, /* destroy */
247 XlwMenuResize, /* resize */
248 XlwMenuRedisplay, /* expose */
249 XlwMenuSetValues, /* set_values */
250 NULL, /* set_values_hook */
251 XtInheritSetValuesAlmost, /* set_values_almost */
252 NULL, /* get_values_hook */
253 NULL, /* accept_focus */
254 XtVersion, /* version */
255 NULL, /* callback_private */
256 xlwMenuTranslations, /* tm_table */
257 XtInheritQueryGeometry, /* query_geometry */
258 XtInheritDisplayAccelerator, /* display_accelerator */
259 NULL /* extension */
260 }, /* XlwMenuClass fields initialization */
262 0 /* dummy */
266 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
268 int submenu_destroyed;
270 /* For debug, if installation-directory is non-nil this is not an installed
271 Emacs. In that case we do not grab the keyboard to make it easier to
272 debug. */
273 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
275 static int next_release_must_exit;
277 \f/* Utilities */
279 /* Ungrab pointer and keyboard */
280 static void
281 ungrab_all (Widget w, Time ungrabtime)
283 XtUngrabPointer (w, ungrabtime);
284 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
287 /* Like abort, but remove grabs from widget W before. */
289 static void
290 abort_gracefully (Widget w)
292 if (XtIsShell (XtParent (w)))
293 XtRemoveGrab (w);
294 ungrab_all (w, CurrentTime);
295 abort ();
298 static void
299 push_new_stack (XlwMenuWidget mw, widget_value *val)
301 if (!mw->menu.new_stack)
303 mw->menu.new_stack_length = 10;
304 mw->menu.new_stack =
305 (widget_value**)XtCalloc (mw->menu.new_stack_length,
306 sizeof (widget_value*));
308 else if (mw->menu.new_depth == mw->menu.new_stack_length)
310 mw->menu.new_stack_length *= 2;
311 mw->menu.new_stack =
312 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
313 mw->menu.new_stack_length * sizeof (widget_value*));
315 mw->menu.new_stack [mw->menu.new_depth++] = val;
318 static void
319 pop_new_stack_if_no_contents (XlwMenuWidget mw)
321 if (mw->menu.new_depth > 1)
323 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
324 mw->menu.new_depth -= 1;
328 static void
329 make_old_stack_space (XlwMenuWidget mw, int n)
331 if (!mw->menu.old_stack)
333 mw->menu.old_stack_length = 10;
334 mw->menu.old_stack =
335 (widget_value**)XtCalloc (mw->menu.old_stack_length,
336 sizeof (widget_value*));
338 else if (mw->menu.old_stack_length < n)
340 mw->menu.old_stack_length *= 2;
341 mw->menu.old_stack =
342 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
343 mw->menu.old_stack_length * sizeof (widget_value*));
347 \f/* Size code */
348 static int
349 string_width (XlwMenuWidget mw, char *s)
351 XCharStruct xcs;
352 int drop;
353 #ifdef HAVE_XFT
354 if (mw->menu.xft_font)
356 XGlyphInfo gi;
357 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
358 (FcChar8 *) s,
359 strlen (s), &gi);
360 return gi.width;
362 #endif
363 #ifdef HAVE_X_I18N
364 if (mw->menu.fontSet)
366 XRectangle ink, logical;
367 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
368 return logical.width;
370 #endif
372 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
373 return xcs.width;
377 #ifdef HAVE_XFT
378 #define MENU_FONT_HEIGHT(mw) \
379 ((mw)->menu.xft_font != NULL \
380 ? (mw)->menu.xft_font->height \
381 : ((mw)->menu.fontSet != NULL \
382 ? (mw)->menu.font_extents->max_logical_extent.height \
383 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
384 #define MENU_FONT_ASCENT(mw) \
385 ((mw)->menu.xft_font != NULL \
386 ? (mw)->menu.xft_font->ascent \
387 : ((mw)->menu.fontSet != NULL \
388 ? - (mw)->menu.font_extents->max_logical_extent.y \
389 : (mw)->menu.font->ascent))
390 #else
391 #ifdef HAVE_X_I18N
392 #define MENU_FONT_HEIGHT(mw) \
393 ((mw)->menu.fontSet != NULL \
394 ? (mw)->menu.font_extents->max_logical_extent.height \
395 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
396 #define MENU_FONT_ASCENT(mw) \
397 ((mw)->menu.fontSet != NULL \
398 ? - (mw)->menu.font_extents->max_logical_extent.y \
399 : (mw)->menu.font->ascent)
400 #else
401 #define MENU_FONT_HEIGHT(mw) \
402 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
403 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
404 #endif
405 #endif
407 static int
408 arrow_width (XlwMenuWidget mw)
410 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
413 /* Return the width of toggle buttons of widget MW. */
415 static int
416 toggle_button_width (XlwMenuWidget mw)
418 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
422 /* Return the width of radio buttons of widget MW. */
424 static int
425 radio_button_width (XlwMenuWidget mw)
427 return toggle_button_width (mw) * 1.41;
431 static XtResource
432 nameResource[] =
434 {"labelString", "LabelString", XtRString, sizeof(String),
435 0, XtRImmediate, 0},
438 static char*
439 resource_widget_value (XlwMenuWidget mw, widget_value *val)
441 if (!val->toolkit_data)
443 char* resourced_name = NULL;
444 char* complete_name;
445 XtGetSubresources ((Widget) mw,
446 (XtPointer) &resourced_name,
447 val->name, val->name,
448 nameResource, 1, NULL, 0);
449 if (!resourced_name)
450 resourced_name = val->name;
451 if (!val->value)
453 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
454 strcpy (complete_name, resourced_name);
456 else
458 int complete_length =
459 strlen (resourced_name) + strlen (val->value) + 2;
460 complete_name = XtMalloc (complete_length);
461 *complete_name = 0;
462 strcat (complete_name, resourced_name);
463 strcat (complete_name, " ");
464 strcat (complete_name, val->value);
467 val->toolkit_data = complete_name;
468 val->free_toolkit_data = True;
470 return (char*)val->toolkit_data;
473 /* Returns the sizes of an item */
474 static void
475 size_menu_item (XlwMenuWidget mw,
476 widget_value* val,
477 int horizontal_p,
478 int* label_width,
479 int* rest_width,
480 int* button_width,
481 int* height)
483 enum menu_separator separator;
485 if (lw_separator_p (val->name, &separator, 0))
487 *height = separator_height (separator);
488 *label_width = 1;
489 *rest_width = 0;
490 *button_width = 0;
492 else
494 *height = MENU_FONT_HEIGHT (mw)
495 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
497 *label_width =
498 string_width (mw, resource_widget_value (mw, val))
499 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
501 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
502 if (!horizontal_p)
504 if (val->contents)
505 /* Add width of the arrow displayed for submenus. */
506 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
507 else if (val->key)
508 /* Add width of key equivalent string. */
509 *rest_width += (string_width (mw, val->key)
510 + mw->menu.arrow_spacing);
512 if (val->button_type == BUTTON_TYPE_TOGGLE)
513 *button_width = (toggle_button_width (mw)
514 + mw->menu.horizontal_spacing);
515 else if (val->button_type == BUTTON_TYPE_RADIO)
516 *button_width = (radio_button_width (mw)
517 + mw->menu.horizontal_spacing);
522 static void
523 size_menu (XlwMenuWidget mw, int level)
525 int label_width = 0;
526 int rest_width = 0;
527 int button_width = 0;
528 int max_rest_width = 0;
529 int max_button_width = 0;
530 int height = 0;
531 int horizontal_p = mw->menu.horizontal && (level == 0);
532 widget_value* val;
533 window_state* ws;
535 if (level >= mw->menu.old_depth)
536 abort_gracefully ((Widget) mw);
538 ws = &mw->menu.windows [level];
539 ws->width = 0;
540 ws->height = 0;
541 ws->label_width = 0;
542 ws->button_width = 0;
544 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
546 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
547 &button_width, &height);
548 if (horizontal_p)
550 ws->width += label_width + rest_width;
551 if (height > ws->height)
552 ws->height = height;
554 else
556 if (label_width > ws->label_width)
557 ws->label_width = label_width;
558 if (rest_width > max_rest_width)
559 max_rest_width = rest_width;
560 if (button_width > max_button_width)
561 max_button_width = button_width;
562 ws->height += height;
566 if (horizontal_p)
567 ws->label_width = ws->button_width = 0;
568 else
570 ws->width = ws->label_width + max_rest_width + max_button_width;
571 ws->button_width = max_button_width;
574 ws->width += 2 * mw->menu.shadow_thickness;
575 ws->height += 2 * mw->menu.shadow_thickness;
576 ws->max_rest_width = max_rest_width;
578 if (horizontal_p)
580 ws->width += 2 * mw->menu.margin;
581 ws->height += 2 * mw->menu.margin;
586 \f/* Display code */
588 static void
589 draw_arrow (XlwMenuWidget mw,
590 Window window,
591 GC gc,
592 int x,
593 int y,
594 int width,
595 int down_p)
597 Display *dpy = XtDisplay (mw);
598 GC top_gc = mw->menu.shadow_top_gc;
599 GC bottom_gc = mw->menu.shadow_bottom_gc;
600 int thickness = mw->menu.shadow_thickness;
601 int height = width;
602 XPoint pt[10];
603 /* alpha = atan (0.5)
604 factor = (1 + sin (alpha)) / cos (alpha) */
605 double factor = 1.62;
606 int thickness2 = thickness * factor;
608 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
610 if (down_p)
612 GC temp;
613 temp = top_gc;
614 top_gc = bottom_gc;
615 bottom_gc = temp;
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 + thickness2;
623 pt[2].y = y + thickness2;
624 pt[3].x = x;
625 pt[3].y = y;
626 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
628 pt[0].x = x;
629 pt[0].y = y;
630 pt[1].x = x + thickness;
631 pt[1].y = y + thickness2;
632 pt[2].x = x + width - thickness2;
633 pt[2].y = y + height / 2;
634 pt[3].x = x + width;
635 pt[3].y = y + height / 2;
636 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
638 pt[0].x = x;
639 pt[0].y = y + height;
640 pt[1].x = x + thickness;
641 pt[1].y = y + height - thickness2;
642 pt[2].x = x + width - thickness2;
643 pt[2].y = y + height / 2;
644 pt[3].x = x + width;
645 pt[3].y = y + height / 2;
646 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
651 static void
652 draw_shadow_rectangle (XlwMenuWidget mw,
653 Window window,
654 int x,
655 int y,
656 int width,
657 int height,
658 int erase_p,
659 int down_p)
661 Display *dpy = XtDisplay (mw);
662 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
663 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
664 int thickness = mw->menu.shadow_thickness;
665 XPoint points [4];
667 if (!erase_p && down_p)
669 GC temp;
670 temp = top_gc;
671 top_gc = bottom_gc;
672 bottom_gc = temp;
675 points [0].x = x;
676 points [0].y = y;
677 points [1].x = x + width;
678 points [1].y = y;
679 points [2].x = x + width - thickness;
680 points [2].y = y + thickness;
681 points [3].x = x;
682 points [3].y = y + thickness;
683 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
684 points [0].x = x;
685 points [0].y = y + thickness;
686 points [1].x = x;
687 points [1].y = y + height;
688 points [2].x = x + thickness;
689 points [2].y = y + height - thickness;
690 points [3].x = x + thickness;
691 points [3].y = y + thickness;
692 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
693 points [0].x = x + width;
694 points [0].y = y;
695 points [1].x = x + width - thickness;
696 points [1].y = y + thickness;
697 points [2].x = x + width - thickness;
698 points [2].y = y + height - thickness;
699 points [3].x = x + width;
700 points [3].y = y + height - thickness;
701 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
702 points [0].x = x;
703 points [0].y = y + height;
704 points [1].x = x + width;
705 points [1].y = y + height;
706 points [2].x = x + width;
707 points [2].y = y + height - thickness;
708 points [3].x = x + thickness;
709 points [3].y = y + height - thickness;
710 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
714 static void
715 draw_shadow_rhombus (XlwMenuWidget mw,
716 Window window,
717 int x,
718 int y,
719 int width,
720 int height,
721 int erase_p,
722 int down_p)
724 Display *dpy = XtDisplay (mw);
725 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
726 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
727 int thickness = mw->menu.shadow_thickness;
728 XPoint points [4];
730 if (!erase_p && down_p)
732 GC temp;
733 temp = top_gc;
734 top_gc = bottom_gc;
735 bottom_gc = temp;
738 points [0].x = x;
739 points [0].y = y + height / 2;
740 points [1].x = x + thickness;
741 points [1].y = y + height / 2;
742 points [2].x = x + width / 2;
743 points [2].y = y + thickness;
744 points [3].x = x + width / 2;
745 points [3].y = y;
746 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
747 points [0].x = x + width / 2;
748 points [0].y = y;
749 points [1].x = x + width / 2;
750 points [1].y = y + thickness;
751 points [2].x = x + width - thickness;
752 points [2].y = y + height / 2;
753 points [3].x = x + width;
754 points [3].y = y + height / 2;
755 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
756 points [0].x = x;
757 points [0].y = y + height / 2;
758 points [1].x = x + thickness;
759 points [1].y = y + height / 2;
760 points [2].x = x + width / 2;
761 points [2].y = y + height - thickness;
762 points [3].x = x + width / 2;
763 points [3].y = y + height;
764 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
765 points [0].x = x + width / 2;
766 points [0].y = y + height;
767 points [1].x = x + width / 2;
768 points [1].y = y + height - thickness;
769 points [2].x = x + width - thickness;
770 points [2].y = y + height / 2;
771 points [3].x = x + width;
772 points [3].y = y + height / 2;
773 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
777 /* Draw a toggle 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_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
784 int width, height;
786 width = toggle_button_width (mw);
787 height = width;
788 x += mw->menu.horizontal_spacing;
789 y += (MENU_FONT_ASCENT (mw) - height) / 2;
790 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
794 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
795 top-left corner of the menu item. SELECTED_P non-zero means the
796 toggle button is selected. */
798 static void
799 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
801 int width, height;
803 width = radio_button_width (mw);
804 height = width;
805 x += mw->menu.horizontal_spacing;
806 y += (MENU_FONT_ASCENT (mw) - height) / 2;
807 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
811 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
812 top-left corner of the menu item. WIDTH is the width of the
813 separator to draw. TYPE is the separator type. */
815 static void
816 draw_separator (XlwMenuWidget mw,
817 Window window,
818 int x,
819 int y,
820 int width,
821 enum menu_separator type)
823 Display *dpy = XtDisplay (mw);
824 XGCValues xgcv;
826 switch (type)
828 case SEPARATOR_NO_LINE:
829 break;
831 case SEPARATOR_SINGLE_LINE:
832 XDrawLine (dpy, window, mw->menu.foreground_gc,
833 x, y, x + width, y);
834 break;
836 case SEPARATOR_DOUBLE_LINE:
837 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
838 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
839 break;
841 case SEPARATOR_SINGLE_DASHED_LINE:
842 xgcv.line_style = LineOnOffDash;
843 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
844 XDrawLine (dpy, window, mw->menu.foreground_gc,
845 x, y, x + width, y);
846 xgcv.line_style = LineSolid;
847 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
848 break;
850 case SEPARATOR_DOUBLE_DASHED_LINE:
851 draw_separator (mw, window, x, y, width,
852 SEPARATOR_SINGLE_DASHED_LINE);
853 draw_separator (mw, window, x, y + 2, width,
854 SEPARATOR_SINGLE_DASHED_LINE);
855 break;
857 case SEPARATOR_SHADOW_ETCHED_IN:
858 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
859 x, y, x + width, y);
860 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
861 x, y + 1, x + width, y + 1);
862 break;
864 case SEPARATOR_SHADOW_ETCHED_OUT:
865 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
866 x, y, x + width, y);
867 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
868 x, y + 1, x + width, y + 1);
869 break;
871 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
872 xgcv.line_style = LineOnOffDash;
873 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
874 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
875 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
876 xgcv.line_style = LineSolid;
877 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
878 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
879 break;
881 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
882 xgcv.line_style = LineOnOffDash;
883 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
884 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
885 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
886 xgcv.line_style = LineSolid;
887 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
888 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
889 break;
891 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
892 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
893 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
894 break;
896 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
897 draw_separator (mw, window, x, y, width,
898 SEPARATOR_SHADOW_ETCHED_OUT);
899 draw_separator (mw, window, x, y + 3, width,
900 SEPARATOR_SHADOW_ETCHED_OUT);
901 break;
903 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
904 xgcv.line_style = LineOnOffDash;
905 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
906 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
907 draw_separator (mw, window, x, y, width,
908 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
909 xgcv.line_style = LineSolid;
910 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
911 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
912 break;
914 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
915 xgcv.line_style = LineOnOffDash;
916 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
917 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
918 draw_separator (mw, window, x, y, width,
919 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
920 xgcv.line_style = LineSolid;
921 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
922 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
923 break;
925 default:
926 abort ();
931 /* Return the pixel height of menu separator SEPARATOR. */
933 static int
934 separator_height (enum menu_separator separator)
936 switch (separator)
938 case SEPARATOR_NO_LINE:
939 return 2;
941 case SEPARATOR_SINGLE_LINE:
942 case SEPARATOR_SINGLE_DASHED_LINE:
943 return 1;
945 case SEPARATOR_DOUBLE_LINE:
946 case SEPARATOR_DOUBLE_DASHED_LINE:
947 return 3;
949 case SEPARATOR_SHADOW_ETCHED_IN:
950 case SEPARATOR_SHADOW_ETCHED_OUT:
951 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
952 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
953 return 2;
955 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
956 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
957 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
958 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
959 return 5;
961 default:
962 abort ();
967 /* Display the menu item and increment where.x and where.y to show how large
968 the menu item was. */
970 static void
971 display_menu_item (XlwMenuWidget mw,
972 widget_value* val,
973 window_state* ws,
974 XPoint* where,
975 Boolean highlighted_p,
976 Boolean horizontal_p,
977 Boolean just_compute_p)
979 GC deco_gc;
980 GC text_gc;
981 int font_height = MENU_FONT_HEIGHT (mw);
982 int font_ascent = MENU_FONT_ASCENT (mw);
983 int shadow = mw->menu.shadow_thickness;
984 int margin = mw->menu.margin;
985 int h_spacing = mw->menu.horizontal_spacing;
986 int v_spacing = mw->menu.vertical_spacing;
987 int label_width;
988 int rest_width;
989 int button_width;
990 int height;
991 int width;
992 enum menu_separator separator;
993 int separator_p = lw_separator_p (val->name, &separator, 0);
994 #ifdef HAVE_XFT
995 XftColor *xftfg;
996 #endif
998 /* compute the sizes of the item */
999 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
1000 &button_width, &height);
1002 if (horizontal_p)
1003 width = label_width + rest_width;
1004 else
1006 label_width = ws->label_width;
1007 width = ws->width - 2 * shadow;
1010 /* Only highlight an enabled item that has a callback. */
1011 if (highlighted_p)
1012 if (!val->enabled || !(val->call_data || val->contents))
1013 highlighted_p = 0;
1015 /* do the drawing. */
1016 if (!just_compute_p)
1018 /* Add the shadow border of the containing menu */
1019 int x = where->x + shadow;
1020 int y = where->y + shadow;
1022 if (horizontal_p)
1024 x += margin;
1025 y += margin;
1028 /* pick the foreground and background GC. */
1029 if (val->enabled)
1030 text_gc = mw->menu.foreground_gc;
1031 else
1032 text_gc = mw->menu.disabled_gc;
1033 deco_gc = mw->menu.foreground_gc;
1034 #ifdef HAVE_XFT
1035 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1036 #endif
1038 if (separator_p)
1040 draw_separator (mw, ws->pixmap, x, y, width, separator);
1042 else
1044 int x_offset = x + h_spacing + shadow;
1045 char* display_string = resource_widget_value (mw, val);
1046 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1047 False);
1049 /* Deal with centering a menu title. */
1050 if (!horizontal_p && !val->contents && !val->call_data)
1052 int l = string_width (mw, display_string);
1054 if (width > l)
1055 x_offset = (width - l) >> 1;
1057 else if (!horizontal_p && ws->button_width)
1058 x_offset += ws->button_width;
1061 #ifdef HAVE_XFT
1062 if (ws->xft_draw)
1064 int draw_y = y + v_spacing + shadow;
1065 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1066 mw->menu.xft_font,
1067 x_offset, draw_y + font_ascent,
1068 (unsigned char *) display_string,
1069 strlen (display_string));
1071 else
1072 #endif
1073 #ifdef HAVE_X_I18N
1074 if (mw->menu.fontSet)
1075 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1076 text_gc, x_offset,
1077 y + v_spacing + shadow + font_ascent,
1078 display_string, strlen (display_string));
1079 else
1080 #endif
1081 XDrawString (XtDisplay (mw), ws->pixmap,
1082 text_gc, x_offset,
1083 y + v_spacing + shadow + font_ascent,
1084 display_string, strlen (display_string));
1086 if (!horizontal_p)
1088 if (val->button_type == BUTTON_TYPE_TOGGLE)
1089 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1090 val->selected);
1091 else if (val->button_type == BUTTON_TYPE_RADIO)
1092 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1093 val->selected);
1095 if (val->contents)
1097 int a_w = arrow_width (mw);
1098 draw_arrow (mw, ws->pixmap, deco_gc,
1099 x + width - a_w
1100 - mw->menu.horizontal_spacing
1101 - mw->menu.shadow_thickness,
1102 y + v_spacing + shadow, a_w,
1103 highlighted_p);
1105 else if (val->key)
1107 #ifdef HAVE_XFT
1108 if (ws->xft_draw)
1110 int draw_x = ws->width - ws->max_rest_width
1111 + mw->menu.arrow_spacing;
1112 int draw_y = y + v_spacing + shadow + font_ascent;
1113 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1114 mw->menu.xft_font,
1115 draw_x, draw_y,
1116 (unsigned char *) val->key,
1117 strlen (val->key));
1119 else
1120 #endif
1121 #ifdef HAVE_X_I18N
1122 if (mw->menu.fontSet)
1123 XmbDrawString (XtDisplay (mw), ws->pixmap,
1124 mw->menu.fontSet,
1125 text_gc,
1126 x + label_width + mw->menu.arrow_spacing,
1127 y + v_spacing + shadow + font_ascent,
1128 val->key, strlen (val->key));
1129 else
1130 #endif
1131 XDrawString (XtDisplay (mw), ws->pixmap,
1132 text_gc,
1133 x + label_width + mw->menu.arrow_spacing,
1134 y + v_spacing + shadow + font_ascent,
1135 val->key, strlen (val->key));
1138 else
1140 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1141 mw->menu.background_gc,
1142 x + shadow, y + shadow,
1143 label_width + h_spacing - 1,
1144 font_height + 2 * v_spacing - 1);
1145 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1146 True, False);
1149 if (highlighted_p)
1150 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1151 False);
1155 where->x += width;
1156 where->y += height;
1159 static void
1160 display_menu (XlwMenuWidget mw,
1161 int level,
1162 Boolean just_compute_p,
1163 XPoint *highlighted_pos,
1164 XPoint *hit,
1165 widget_value **hit_return)
1167 widget_value* val;
1168 widget_value* following_item;
1169 window_state* ws;
1170 XPoint where;
1171 int horizontal_p = mw->menu.horizontal && (level == 0);
1172 int highlighted_p;
1173 int no_return = 0;
1174 enum menu_separator separator;
1176 if (level >= mw->menu.old_depth)
1177 abort_gracefully ((Widget) mw);
1179 if (level < mw->menu.old_depth - 1)
1180 following_item = mw->menu.old_stack [level + 1];
1181 else
1182 following_item = NULL;
1184 if (hit)
1185 *hit_return = NULL;
1187 where.x = 0;
1188 where.y = 0;
1190 ws = &mw->menu.windows [level];
1192 if (!just_compute_p)
1193 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1194 0, 0, ws->width, ws->height);
1196 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1198 highlighted_p = val == following_item;
1199 if (highlighted_p && highlighted_pos)
1201 if (horizontal_p)
1202 highlighted_pos->x = where.x;
1203 else
1204 highlighted_pos->y = where.y;
1207 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1208 just_compute_p);
1210 if (highlighted_p && highlighted_pos)
1212 if (horizontal_p)
1213 highlighted_pos->y = where.y;
1214 else
1215 highlighted_pos->x = where.x;
1218 if (hit
1219 && !*hit_return
1220 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1221 && !lw_separator_p (val->name, &separator, 0)
1222 && !no_return)
1224 if (val->enabled)
1225 *hit_return = val;
1226 else
1227 no_return = 1;
1228 if (mw->menu.inside_entry != val)
1230 if (mw->menu.inside_entry)
1231 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1232 (XtPointer) mw->menu.inside_entry);
1233 mw->menu.inside_entry = val;
1234 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1235 (XtPointer) mw->menu.inside_entry);
1239 if (horizontal_p)
1240 where.y = 0;
1241 else
1242 where.x = 0;
1245 if (!just_compute_p)
1247 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1248 False, False);
1249 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1250 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1254 \f/* Motion code */
1255 static void
1256 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1258 int i;
1260 mw->menu.new_depth = 0;
1261 for (i = 0; i < level; i++)
1262 push_new_stack (mw, mw->menu.old_stack [i]);
1263 push_new_stack (mw, val);
1266 static void
1267 expose_cb (Widget widget,
1268 XtPointer closure,
1269 XEvent* event,
1270 Boolean* continue_to_dispatch)
1272 XlwMenuWidget mw = (XlwMenuWidget) closure;
1273 int i;
1275 *continue_to_dispatch = False;
1276 for (i = 0; i < mw->menu.windows_length; ++i)
1277 if (mw->menu.windows [i].w == widget) break;
1278 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1279 display_menu (mw, i, False, NULL, NULL, NULL);
1282 static void
1283 set_window_type (Widget w, XlwMenuWidget mw)
1285 int popup_menu_p = mw->menu.top_depth == 1;
1286 Atom type = XInternAtom (XtDisplay (w),
1287 popup_menu_p
1288 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1289 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1290 False);
1292 XChangeProperty (XtDisplay (w), XtWindow (w),
1293 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1294 XA_ATOM, 32, PropModeReplace,
1295 (unsigned char *)&type, 1);
1299 static void
1300 make_windows_if_needed (XlwMenuWidget mw, int n)
1302 int i;
1303 int start_at;
1304 window_state* windows;
1306 if (mw->menu.windows_length >= n)
1307 return;
1309 if (!mw->menu.windows)
1311 mw->menu.windows =
1312 (window_state*)XtMalloc (n * sizeof (window_state));
1313 start_at = 0;
1315 else
1317 mw->menu.windows =
1318 (window_state*)XtRealloc ((char*)mw->menu.windows,
1319 n * sizeof (window_state));
1320 start_at = mw->menu.windows_length;
1322 mw->menu.windows_length = n;
1324 windows = mw->menu.windows;
1326 for (i = start_at; i < n; i++)
1328 Arg av[10];
1329 int ac = 0;
1330 windows [i].x = 0;
1331 windows [i].y = 0;
1332 windows [i].width = 1;
1333 windows [i].height = 1;
1334 windows [i].max_rest_width = 0;
1335 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1336 XtSetArg (av[ac], XtNheight, 1); ++ac;
1337 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1338 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1339 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1340 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1341 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1342 windows [i].w =
1343 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1344 (Widget) mw, av, ac);
1345 XtRealizeWidget (windows [i].w);
1346 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1347 windows [i].window = XtWindow (windows [i].w);
1348 windows [i].pixmap = None;
1349 #ifdef HAVE_XFT
1350 windows [i].xft_draw = 0;
1351 #endif
1352 set_window_type (windows [i].w, mw);
1356 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1359 xlwmenu_window_p (Widget w, Window window)
1361 XlwMenuWidget mw = (XlwMenuWidget) w;
1362 int i;
1364 for (i = 0; i < mw->menu.windows_length; ++i)
1365 if (window == mw->menu.windows[i].window)
1366 break;
1368 return i < mw->menu.windows_length;
1371 /* Make the window fit in the screen */
1372 static void
1373 fit_to_screen (XlwMenuWidget mw,
1374 window_state *ws,
1375 window_state *previous_ws,
1376 Boolean horizontal_p)
1378 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1379 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1380 /* 1 if we are unable to avoid an overlap between
1381 this menu and the parent menu in the X dimension. */
1382 int horizontal_overlap = 0;
1384 if (ws->x < 0)
1385 ws->x = 0;
1386 else if (ws->x + ws->width > screen_width)
1388 if (!horizontal_p)
1389 /* The addition of shadow-thickness for a sub-menu's position is
1390 to reflect a similar adjustment when the menu is displayed to
1391 the right of the invoking menu-item; it makes the sub-menu
1392 look more `attached' to the menu-item. */
1393 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1394 else
1395 ws->x = screen_width - ws->width;
1396 if (ws->x < 0)
1398 ws->x = 0;
1399 horizontal_overlap = 1;
1402 /* If we overlap in X, try to avoid overlap in Y. */
1403 if (horizontal_overlap
1404 && ws->y < previous_ws->y + previous_ws->height
1405 && previous_ws->y < ws->y + ws->height)
1407 /* Put this menu right below or right above PREVIOUS_WS
1408 if there's room. */
1409 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1410 ws->y = previous_ws->y + previous_ws->height;
1411 else if (previous_ws->y - ws->height > 0)
1412 ws->y = previous_ws->y - ws->height;
1415 if (ws->y < 0)
1416 ws->y = 0;
1417 else if (ws->y + ws->height > screen_height)
1419 if (horizontal_p)
1420 ws->y = previous_ws->y - ws->height;
1421 else
1422 ws->y = screen_height - ws->height;
1423 if (ws->y < 0)
1424 ws->y = 0;
1428 static void
1429 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1431 if (ws->pixmap != None)
1433 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1434 ws->pixmap = None;
1436 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1437 ws->width, ws->height,
1438 DefaultDepthOfScreen (XtScreen (ws->w)));
1439 #ifdef HAVE_XFT
1440 if (ws->xft_draw)
1441 XftDrawDestroy (ws->xft_draw);
1442 if (mw->menu.xft_font)
1444 int screen = XScreenNumberOfScreen (mw->core.screen);
1445 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1446 ws->pixmap,
1447 DefaultVisual (XtDisplay (ws->w), screen),
1448 mw->core.colormap);
1450 else
1451 ws->xft_draw = 0;
1452 #endif
1455 /* Updates old_stack from new_stack and redisplays. */
1456 static void
1457 remap_menubar (XlwMenuWidget mw)
1459 int i;
1460 int last_same;
1461 XPoint selection_position;
1462 int old_depth = mw->menu.old_depth;
1463 int new_depth = mw->menu.new_depth;
1464 widget_value** old_stack;
1465 widget_value** new_stack;
1466 window_state* windows;
1467 widget_value* old_selection;
1468 widget_value* new_selection;
1470 /* Check that enough windows and old_stack are ready. */
1471 make_windows_if_needed (mw, new_depth);
1472 make_old_stack_space (mw, new_depth);
1473 windows = mw->menu.windows;
1474 old_stack = mw->menu.old_stack;
1475 new_stack = mw->menu.new_stack;
1477 /* compute the last identical different entry */
1478 for (i = 1; i < old_depth && i < new_depth; i++)
1479 if (old_stack [i] != new_stack [i])
1480 break;
1481 last_same = i - 1;
1483 /* Memorize the previously selected item to be able to refresh it */
1484 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1485 if (old_selection && !old_selection->enabled)
1486 old_selection = NULL;
1487 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1488 if (new_selection && !new_selection->enabled)
1489 new_selection = NULL;
1491 /* Call callback when the hightlighted item changes. */
1492 if (old_selection || new_selection)
1493 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1494 (XtPointer) new_selection);
1496 /* updates old_state from new_state. It has to be done now because
1497 display_menu (called below) uses the old_stack to know what to display. */
1498 for (i = last_same + 1; i < new_depth; i++)
1500 XtPopdown (mw->menu.windows [i].w);
1501 old_stack [i] = new_stack [i];
1503 mw->menu.old_depth = new_depth;
1505 /* refresh the last selection */
1506 selection_position.x = 0;
1507 selection_position.y = 0;
1508 display_menu (mw, last_same, new_selection == old_selection,
1509 &selection_position, NULL, NULL);
1511 /* Now place the new menus. */
1512 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1514 window_state *previous_ws = &windows[i - 1];
1515 window_state *ws = &windows[i];
1517 ws->x = (previous_ws->x + selection_position.x
1518 + mw->menu.shadow_thickness);
1519 if (mw->menu.horizontal && i == 1)
1520 ws->x += mw->menu.margin;
1522 #if 0
1523 if (!mw->menu.horizontal || i > 1)
1524 ws->x += mw->menu.shadow_thickness;
1525 #endif
1527 ws->y = (previous_ws->y + selection_position.y
1528 + mw->menu.shadow_thickness);
1529 if (mw->menu.horizontal && i == 1)
1530 ws->y += mw->menu.margin;
1532 size_menu (mw, i);
1534 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1536 XtVaSetValues (ws->w, XtNwidth, ws->width, XtNheight, ws->height,
1537 XtNx, ws->x, XtNy, ws->y, NULL);
1538 create_pixmap_for_menu (ws, mw);
1539 XtPopup (ws->w, XtGrabNone);
1540 display_menu (mw, i, False, &selection_position, NULL, NULL);
1543 /* unmap the menus that popped down */
1544 for (i = new_depth - 1; i < old_depth; i++)
1545 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1546 XtPopdown (windows[i].w);
1549 static Boolean
1550 motion_event_is_in_menu (XlwMenuWidget mw,
1551 XMotionEvent *ev,
1552 int level,
1553 XPoint *relative_pos)
1555 window_state* ws = &mw->menu.windows [level];
1556 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1557 int x = ws->x + shadow;
1558 int y = ws->y + shadow;
1559 relative_pos->x = ev->x_root - x;
1560 relative_pos->y = ev->y_root - y;
1561 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1562 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1565 static Boolean
1566 map_event_to_widget_value (XlwMenuWidget mw,
1567 XMotionEvent *ev,
1568 widget_value **val,
1569 int *level)
1571 int i;
1572 XPoint relative_pos;
1573 window_state* ws;
1574 int inside = 0;
1576 *val = NULL;
1578 /* Find the window */
1579 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1581 ws = &mw->menu.windows [i];
1582 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1584 inside = 1;
1585 display_menu (mw, i, True, NULL, &relative_pos, val);
1587 if (*val)
1589 *level = i + 1;
1590 return True;
1595 if (!inside)
1597 if (mw->menu.inside_entry != NULL)
1598 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1599 (XtPointer) mw->menu.inside_entry);
1600 mw->menu.inside_entry = NULL;
1603 return False;
1606 \f/* Procedures */
1607 static void
1608 make_drawing_gcs (XlwMenuWidget mw)
1610 XGCValues xgcv;
1611 float scale;
1612 XtGCMask mask = GCForeground | GCBackground;
1614 #ifdef HAVE_X_I18N
1615 if (!mw->menu.fontSet)
1617 xgcv.font = mw->menu.font->fid;
1618 mask |= GCFont;
1620 #else
1621 xgcv.font = mw->menu.font->fid;
1622 mask |= GCFont;
1623 #endif
1624 xgcv.foreground = mw->menu.foreground;
1625 xgcv.background = mw->core.background_pixel;
1626 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1628 xgcv.foreground = mw->menu.button_foreground;
1629 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1631 xgcv.background = mw->core.background_pixel;
1633 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1635 /* Allocate color for disabled menu-items. */
1636 mw->menu.disabled_foreground = mw->menu.foreground;
1637 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1638 scale = 2.3;
1639 else
1640 scale = 0.55;
1642 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1643 mw->core.colormap,
1644 &mw->menu.disabled_foreground,
1645 scale,
1646 0x8000);
1648 if (mw->menu.foreground == mw->menu.disabled_foreground
1649 || mw->core.background_pixel == mw->menu.disabled_foreground)
1651 /* Too few colors, use stipple. */
1652 xgcv.foreground = mw->menu.foreground;
1653 xgcv.fill_style = FillStippled;
1654 xgcv.stipple = mw->menu.gray_pixmap;
1655 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1656 | GCFillStyle | GCStipple, &xgcv);
1658 else
1660 /* Many colors available, use disabled pixel. */
1661 xgcv.foreground = mw->menu.disabled_foreground;
1662 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1665 xgcv.foreground = mw->menu.button_foreground;
1666 xgcv.background = mw->core.background_pixel;
1667 xgcv.fill_style = FillStippled;
1668 xgcv.stipple = mw->menu.gray_pixmap;
1669 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1670 | GCFillStyle | GCStipple, &xgcv);
1672 xgcv.foreground = mw->core.background_pixel;
1673 xgcv.background = mw->menu.foreground;
1674 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1677 static void
1678 release_drawing_gcs (XlwMenuWidget mw)
1680 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1681 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1682 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1683 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1684 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1685 /* let's get some segvs if we try to use these... */
1686 mw->menu.foreground_gc = (GC) -1;
1687 mw->menu.button_gc = (GC) -1;
1688 mw->menu.disabled_gc = (GC) -1;
1689 mw->menu.inactive_button_gc = (GC) -1;
1690 mw->menu.background_gc = (GC) -1;
1693 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1694 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1696 static void
1697 make_shadow_gcs (XlwMenuWidget mw)
1699 XGCValues xgcv;
1700 unsigned long pm = 0;
1701 Display *dpy = XtDisplay ((Widget) mw);
1702 Screen *screen = XtScreen ((Widget) mw);
1703 Colormap cmap = mw->core.colormap;
1704 XColor topc, botc;
1705 int top_frobbed = 0, bottom_frobbed = 0;
1707 mw->menu.free_top_shadow_color_p = 0;
1708 mw->menu.free_bottom_shadow_color_p = 0;
1710 if (mw->menu.top_shadow_color == -1)
1711 mw->menu.top_shadow_color = mw->core.background_pixel;
1712 else
1713 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1715 if (mw->menu.bottom_shadow_color == -1)
1716 mw->menu.bottom_shadow_color = mw->menu.foreground;
1717 else
1718 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1720 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1721 mw->menu.top_shadow_color == mw->menu.foreground)
1723 topc.pixel = mw->core.background_pixel;
1724 #ifdef emacs
1725 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1726 &topc.pixel,
1727 1.2, 0x8000))
1728 #else
1729 XQueryColor (dpy, cmap, &topc);
1730 /* don't overflow/wrap! */
1731 topc.red = MINL (65535, topc.red * 1.2);
1732 topc.green = MINL (65535, topc.green * 1.2);
1733 topc.blue = MINL (65535, topc.blue * 1.2);
1734 if (XAllocColor (dpy, cmap, &topc))
1735 #endif
1737 mw->menu.top_shadow_color = topc.pixel;
1738 mw->menu.free_top_shadow_color_p = 1;
1739 top_frobbed = 1;
1742 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1743 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1745 botc.pixel = mw->core.background_pixel;
1746 #ifdef emacs
1747 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1748 &botc.pixel,
1749 0.6, 0x4000))
1750 #else
1751 XQueryColor (dpy, cmap, &botc);
1752 botc.red *= 0.6;
1753 botc.green *= 0.6;
1754 botc.blue *= 0.6;
1755 if (XAllocColor (dpy, cmap, &botc))
1756 #endif
1758 mw->menu.bottom_shadow_color = botc.pixel;
1759 mw->menu.free_bottom_shadow_color_p = 1;
1760 bottom_frobbed = 1;
1764 if (top_frobbed && bottom_frobbed)
1766 if (topc.pixel == botc.pixel)
1768 if (botc.pixel == mw->menu.foreground)
1770 if (mw->menu.free_top_shadow_color_p)
1772 x_free_dpy_colors (dpy, screen, cmap,
1773 &mw->menu.top_shadow_color, 1);
1774 mw->menu.free_top_shadow_color_p = 0;
1776 mw->menu.top_shadow_color = mw->core.background_pixel;
1778 else
1780 if (mw->menu.free_bottom_shadow_color_p)
1782 x_free_dpy_colors (dpy, screen, cmap,
1783 &mw->menu.bottom_shadow_color, 1);
1784 mw->menu.free_bottom_shadow_color_p = 0;
1786 mw->menu.bottom_shadow_color = mw->menu.foreground;
1791 if (!mw->menu.top_shadow_pixmap &&
1792 mw->menu.top_shadow_color == mw->core.background_pixel)
1794 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1795 if (mw->menu.free_top_shadow_color_p)
1797 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1798 mw->menu.free_top_shadow_color_p = 0;
1800 mw->menu.top_shadow_color = mw->menu.foreground;
1802 if (!mw->menu.bottom_shadow_pixmap &&
1803 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1805 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1806 if (mw->menu.free_bottom_shadow_color_p)
1808 x_free_dpy_colors (dpy, screen, cmap,
1809 &mw->menu.bottom_shadow_color, 1);
1810 mw->menu.free_bottom_shadow_color_p = 0;
1812 mw->menu.bottom_shadow_color = mw->menu.foreground;
1815 xgcv.fill_style = FillStippled;
1816 xgcv.foreground = mw->menu.top_shadow_color;
1817 xgcv.stipple = mw->menu.top_shadow_pixmap;
1818 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1819 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1821 xgcv.foreground = mw->menu.bottom_shadow_color;
1822 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1823 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1824 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1828 static void
1829 release_shadow_gcs (XlwMenuWidget mw)
1831 Display *dpy = XtDisplay ((Widget) mw);
1832 Screen *screen = XtScreen ((Widget) mw);
1833 Colormap cmap = mw->core.colormap;
1834 Pixel px[2];
1835 int i = 0;
1837 if (mw->menu.free_top_shadow_color_p)
1838 px[i++] = mw->menu.top_shadow_color;
1839 if (mw->menu.free_bottom_shadow_color_p)
1840 px[i++] = mw->menu.bottom_shadow_color;
1841 if (i > 0)
1842 x_free_dpy_colors (dpy, screen, cmap, px, i);
1844 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1845 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1848 #ifdef HAVE_XFT
1849 static int
1850 openXftFont (XlwMenuWidget mw)
1852 char *fname = mw->menu.faceName;
1854 mw->menu.xft_font = 0;
1855 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FACENAME) == 0;
1857 if (fname && strcmp (fname, "none") != 0)
1859 int screen = XScreenNumberOfScreen (mw->core.screen);
1860 int len = strlen (fname), i = len-1;
1861 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1862 while (i > 0 && isdigit (fname[i]))
1863 --i;
1864 if (fname[i] == ' ')
1866 fname = xstrdup (mw->menu.faceName);
1867 fname[i] = '-';
1870 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1871 if (!mw->menu.xft_font)
1873 fprintf (stderr, "Can't find font '%s'\n", fname);
1874 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen,
1875 DEFAULT_FACENAME);
1879 if (fname != mw->menu.faceName) free (fname);
1881 return mw->menu.xft_font != 0;
1883 #endif
1885 static void
1886 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1888 /* Get the GCs and the widget size */
1889 XlwMenuWidget mw = (XlwMenuWidget) w;
1890 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1891 Display* display = XtDisplay (mw);
1893 #if 0
1894 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1896 /* _XtCreate is freeing the object that was passed to us,
1897 so make a copy that we will actually keep. */
1898 memcpy (tem, mw->menu.contents, sizeof (widget_value));
1899 mw->menu.contents = tem;
1900 #endif
1902 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1903 mw->menu.cursor = mw->menu.cursor_shape;
1905 mw->menu.gray_pixmap
1906 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1907 gray_bitmap_width, gray_bitmap_height,
1908 (unsigned long)1, (unsigned long)0, 1);
1910 #ifdef HAVE_XFT
1911 if (openXftFont (mw))
1913 else
1914 #endif
1916 if (!mw->menu.font)
1918 if (!xlwmenu_default_font)
1919 xlwmenu_default_font = XLoadQueryFont (display, "fixed");
1920 mw->menu.font = xlwmenu_default_font;
1921 if (!mw->menu.font)
1923 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1924 abort ();
1928 #ifdef HAVE_X_I18N
1929 if (mw->menu.fontSet)
1930 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1931 #endif
1933 make_drawing_gcs (mw);
1934 make_shadow_gcs (mw);
1936 mw->menu.popped_up = False;
1938 mw->menu.old_depth = 1;
1939 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1940 mw->menu.old_stack_length = 1;
1941 mw->menu.old_stack [0] = mw->menu.contents;
1943 mw->menu.new_depth = 0;
1944 mw->menu.new_stack = 0;
1945 mw->menu.new_stack_length = 0;
1946 push_new_stack (mw, mw->menu.contents);
1948 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1949 mw->menu.windows_length = 1;
1950 mw->menu.windows [0].x = 0;
1951 mw->menu.windows [0].y = 0;
1952 mw->menu.windows [0].width = 0;
1953 mw->menu.windows [0].height = 0;
1954 mw->menu.windows [0].max_rest_width = 0;
1955 mw->menu.windows [0].pixmap = None;
1956 #ifdef HAVE_XFT
1957 mw->menu.windows [0].xft_draw = 0;
1958 #endif
1959 size_menu (mw, 0);
1961 mw->core.width = mw->menu.windows [0].width;
1962 mw->core.height = mw->menu.windows [0].height;
1965 static void
1966 XlwMenuClassInitialize (void)
1968 xlwmenu_default_font = 0;
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 facename_changed (XlwMenuWidget newmw,
2129 XlwMenuWidget oldmw)
2131 /* This will fore 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.faceName != oldmw->menu.faceName;
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 redisplay = False;
2145 int i;
2147 if (newmw->menu.contents
2148 && newmw->menu.contents->contents
2149 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2150 redisplay = True;
2151 /* Do redisplay if the contents are entirely eliminated. */
2152 if (newmw->menu.contents
2153 && newmw->menu.contents->contents == 0
2154 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2155 redisplay = True;
2157 if (newmw->core.background_pixel != oldmw->core.background_pixel
2158 || newmw->menu.foreground != oldmw->menu.foreground
2159 #ifdef HAVE_XFT
2160 || facename_changed (newmw, oldmw)
2161 #endif
2162 #ifdef HAVE_X_I18N
2163 || newmw->menu.fontSet != oldmw->menu.fontSet
2164 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2165 #else
2166 || newmw->menu.font != oldmw->menu.font
2167 #endif
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 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 (facename_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 redisplay = True;
2221 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2223 #endif
2225 return 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);