Use unsafep to check for theme safety.
[emacs.git] / lwlib / xlwmenu.c
blobd3205eb0d33e40ef5b044fe1ba169a8afbf78adc
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 1994, 1995, 1997, 1999, 2000, 2001, 2002, 2003, 2004,
4 2005, 2006, 2007, 2008, 2009, 2010 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 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include <setjmp.h>
30 #include "lisp.h"
32 #include <stdio.h>
33 #include <ctype.h>
35 #include <sys/types.h>
36 #if (defined __sun) && !(defined SUNOS41)
37 #define SUNOS41
38 #include <X11/Xos.h>
39 #undef SUNOS41
40 #else
41 #include <X11/Xos.h>
42 #endif
43 #include <X11/IntrinsicP.h>
44 #include <X11/ObjectP.h>
45 #include <X11/StringDefs.h>
46 #include <X11/cursorfont.h>
47 #include <X11/Shell.h>
48 #include "xlwmenuP.h"
50 #ifdef emacs
52 /* Defined in xfns.c. When config.h defines `static' as empty, we get
53 redefinition errors when gray_bitmap is included more than once, so
54 we're referring to the one include in xfns.c here. */
56 extern int gray_bitmap_width;
57 extern int gray_bitmap_height;
58 extern char *gray_bitmap_bits;
60 #include "xterm.h"
62 #else /* not emacs */
64 #include <X11/bitmaps/gray>
65 #define gray_bitmap_width gray_width
66 #define gray_bitmap_height gray_height
67 #define gray_bitmap_bits gray_bits
69 #endif /* not emacs */
71 static int pointer_grabbed;
72 static XEvent menu_post_event;
74 static XFontStruct *xlwmenu_default_font;
76 static char
77 xlwMenuTranslations [] =
78 "<BtnDown>: start()\n\
79 <Motion>: drag()\n\
80 <BtnUp>: select()\n\
81 <Key>Shift_L: nothing()\n\
82 <Key>Shift_R: nothing()\n\
83 <Key>Meta_L: nothing()\n\
84 <Key>Meta_R: nothing()\n\
85 <Key>Control_L: nothing()\n\
86 <Key>Control_R: nothing()\n\
87 <Key>Hyper_L: nothing()\n\
88 <Key>Hyper_R: nothing()\n\
89 <Key>Super_L: nothing()\n\
90 <Key>Super_R: nothing()\n\
91 <Key>Alt_L: nothing()\n\
92 <Key>Alt_R: nothing()\n\
93 <Key>Caps_Lock: nothing()\n\
94 <Key>Shift_Lock: nothing()\n\
95 <KeyUp>Shift_L: nothing()\n\
96 <KeyUp>Shift_R: nothing()\n\
97 <KeyUp>Meta_L: nothing()\n\
98 <KeyUp>Meta_R: nothing()\n\
99 <KeyUp>Control_L: nothing()\n\
100 <KeyUp>Control_R: nothing()\n\
101 <KeyUp>Hyper_L: nothing()\n\
102 <KeyUp>Hyper_R: nothing()\n\
103 <KeyUp>Super_L: nothing()\n\
104 <KeyUp>Super_R: nothing()\n\
105 <KeyUp>Alt_L: nothing()\n\
106 <KeyUp>Alt_R: nothing()\n\
107 <KeyUp>Caps_Lock: nothing()\n\
108 <KeyUp>Shift_Lock:nothing()\n\
109 <Key>Return: select()\n\
110 <Key>Down: down()\n\
111 <Key>Up: up()\n\
112 <Key>Left: left()\n\
113 <Key>Right: right()\n\
114 <Key>: key()\n\
115 <KeyUp>: key()\n\
118 /* FIXME: Space should toggle toggleable menu item but not remove the menu
119 so you can toggle the next one without entering the menu again. */
121 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
123 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
125 #define offset(field) XtOffset(XlwMenuWidget, field)
126 static XtResource
127 xlwMenuResources[] =
129 #ifdef HAVE_X_I18N
130 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
131 offset(menu.fontSet), XtRFontSet, NULL},
132 #endif
133 #ifdef HAVE_XFT
134 #define DEFAULT_FACENAME "Sans-10"
135 {XtNfaceName, XtCFaceName, XtRString, sizeof(String),
136 offset(menu.faceName), XtRString, DEFAULT_FACENAME},
137 {XtNdefaultFace, XtCDefaultFace, XtRInt, sizeof(int),
138 offset(menu.default_face), XtRImmediate, (XtPointer)1},
139 #endif
140 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
141 offset(menu.font), XtRString, "XtDefaultFont"},
142 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
143 offset(menu.foreground), XtRString, "XtDefaultForeground"},
144 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
145 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
146 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
147 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
148 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
149 offset(menu.margin), XtRImmediate, (XtPointer)1},
150 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
151 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
152 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
153 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
154 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
155 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
157 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
158 sizeof (Dimension), offset (menu.shadow_thickness),
159 XtRImmediate, (XtPointer)1},
160 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
161 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
162 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
163 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
164 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
165 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
166 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
167 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
169 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
170 offset(menu.open), XtRCallback, (XtPointer)NULL},
171 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
172 offset(menu.select), XtRCallback, (XtPointer)NULL},
173 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
174 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
175 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
176 offset(menu.enter), XtRCallback, (XtPointer)NULL},
177 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
178 offset(menu.leave), XtRCallback, (XtPointer)NULL},
179 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
180 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
181 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
182 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
183 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
184 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
186 #undef offset
188 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
189 ArgList args, Cardinal *num_args);
190 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
191 static void XlwMenuResize(Widget w);
192 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
193 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
194 static void XlwMenuDestroy(Widget w);
195 static void XlwMenuClassInitialize(void);
196 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
197 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
198 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
199 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
200 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
201 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
202 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
203 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
204 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
205 static int separator_height (enum menu_separator);
206 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
207 static void abort_gracefully (Widget w) NO_RETURN;
209 static XtActionsRec
210 xlwMenuActionsList [] =
212 {"start", Start},
213 {"drag", Drag},
214 {"down", Down},
215 {"up", Up},
216 {"left", Left},
217 {"right", Right},
218 {"select", Select},
219 {"key", Key},
220 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
221 {"nothing", Nothing},
224 #define SuperClass ((CoreWidgetClass)&coreClassRec)
226 XlwMenuClassRec xlwMenuClassRec =
228 { /* CoreClass fields initialization */
229 (WidgetClass) SuperClass, /* superclass */
230 "XlwMenu", /* class_name */
231 sizeof(XlwMenuRec), /* size */
232 XlwMenuClassInitialize, /* class_initialize */
233 NULL, /* class_part_initialize */
234 FALSE, /* class_inited */
235 XlwMenuInitialize, /* initialize */
236 NULL, /* initialize_hook */
237 XlwMenuRealize, /* realize */
238 xlwMenuActionsList, /* actions */
239 XtNumber(xlwMenuActionsList), /* num_actions */
240 xlwMenuResources, /* resources */
241 XtNumber(xlwMenuResources), /* resource_count */
242 NULLQUARK, /* xrm_class */
243 TRUE, /* compress_motion */
244 XtExposeCompressMaximal, /* compress_exposure */
245 TRUE, /* compress_enterleave */
246 FALSE, /* visible_interest */
247 XlwMenuDestroy, /* destroy */
248 XlwMenuResize, /* resize */
249 XlwMenuRedisplay, /* expose */
250 XlwMenuSetValues, /* set_values */
251 NULL, /* set_values_hook */
252 XtInheritSetValuesAlmost, /* set_values_almost */
253 NULL, /* get_values_hook */
254 NULL, /* accept_focus */
255 XtVersion, /* version */
256 NULL, /* callback_private */
257 xlwMenuTranslations, /* tm_table */
258 XtInheritQueryGeometry, /* query_geometry */
259 XtInheritDisplayAccelerator, /* display_accelerator */
260 NULL /* extension */
261 }, /* XlwMenuClass fields initialization */
263 0 /* dummy */
267 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
269 int submenu_destroyed;
271 /* For debug, if installation-directory is non-nil this is not an installed
272 Emacs. In that case we do not grab the keyboard to make it easier to
273 debug. */
274 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
276 static int next_release_must_exit;
278 \f/* Utilities */
280 /* Ungrab pointer and keyboard */
281 static void
282 ungrab_all (Widget w, Time ungrabtime)
284 XtUngrabPointer (w, ungrabtime);
285 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
288 /* Like abort, but remove grabs from widget W before. */
290 static void
291 abort_gracefully (Widget w)
293 if (XtIsShell (XtParent (w)))
294 XtRemoveGrab (w);
295 ungrab_all (w, CurrentTime);
296 abort ();
299 static void
300 push_new_stack (XlwMenuWidget mw, widget_value *val)
302 if (!mw->menu.new_stack)
304 mw->menu.new_stack_length = 10;
305 mw->menu.new_stack =
306 (widget_value**)XtCalloc (mw->menu.new_stack_length,
307 sizeof (widget_value*));
309 else if (mw->menu.new_depth == mw->menu.new_stack_length)
311 mw->menu.new_stack_length *= 2;
312 mw->menu.new_stack =
313 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
314 mw->menu.new_stack_length * sizeof (widget_value*));
316 mw->menu.new_stack [mw->menu.new_depth++] = val;
319 static void
320 pop_new_stack_if_no_contents (XlwMenuWidget mw)
322 if (mw->menu.new_depth > 1)
324 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
325 mw->menu.new_depth -= 1;
329 static void
330 make_old_stack_space (XlwMenuWidget mw, int n)
332 if (!mw->menu.old_stack)
334 mw->menu.old_stack_length = 10;
335 mw->menu.old_stack =
336 (widget_value**)XtCalloc (mw->menu.old_stack_length,
337 sizeof (widget_value*));
339 else if (mw->menu.old_stack_length < n)
341 mw->menu.old_stack_length *= 2;
342 mw->menu.old_stack =
343 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
344 mw->menu.old_stack_length * sizeof (widget_value*));
348 \f/* Size code */
349 static int
350 string_width (XlwMenuWidget mw, char *s)
352 XCharStruct xcs;
353 int drop;
354 #ifdef HAVE_XFT
355 if (mw->menu.xft_font)
357 XGlyphInfo gi;
358 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
359 (FcChar8 *) s,
360 strlen (s), &gi);
361 return gi.width;
363 #endif
364 #ifdef HAVE_X_I18N
365 if (mw->menu.fontSet)
367 XRectangle ink, logical;
368 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
369 return logical.width;
371 #endif
373 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
374 return xcs.width;
378 #ifdef HAVE_XFT
379 #define MENU_FONT_HEIGHT(mw) \
380 ((mw)->menu.xft_font != NULL \
381 ? (mw)->menu.xft_font->height \
382 : ((mw)->menu.fontSet != NULL \
383 ? (mw)->menu.font_extents->max_logical_extent.height \
384 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
385 #define MENU_FONT_ASCENT(mw) \
386 ((mw)->menu.xft_font != NULL \
387 ? (mw)->menu.xft_font->ascent \
388 : ((mw)->menu.fontSet != NULL \
389 ? - (mw)->menu.font_extents->max_logical_extent.y \
390 : (mw)->menu.font->ascent))
391 #else
392 #ifdef HAVE_X_I18N
393 #define MENU_FONT_HEIGHT(mw) \
394 ((mw)->menu.fontSet != NULL \
395 ? (mw)->menu.font_extents->max_logical_extent.height \
396 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
397 #define MENU_FONT_ASCENT(mw) \
398 ((mw)->menu.fontSet != NULL \
399 ? - (mw)->menu.font_extents->max_logical_extent.y \
400 : (mw)->menu.font->ascent)
401 #else
402 #define MENU_FONT_HEIGHT(mw) \
403 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
404 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
405 #endif
406 #endif
408 static int
409 arrow_width (XlwMenuWidget mw)
411 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
414 /* Return the width of toggle buttons of widget MW. */
416 static int
417 toggle_button_width (XlwMenuWidget mw)
419 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
423 /* Return the width of radio buttons of widget MW. */
425 static int
426 radio_button_width (XlwMenuWidget mw)
428 return toggle_button_width (mw) * 1.41;
432 static XtResource
433 nameResource[] =
435 {"labelString", "LabelString", XtRString, sizeof(String),
436 0, XtRImmediate, 0},
439 static char*
440 resource_widget_value (XlwMenuWidget mw, widget_value *val)
442 if (!val->toolkit_data)
444 char* resourced_name = NULL;
445 char* complete_name;
446 XtGetSubresources ((Widget) mw,
447 (XtPointer) &resourced_name,
448 val->name, val->name,
449 nameResource, 1, NULL, 0);
450 if (!resourced_name)
451 resourced_name = val->name;
452 if (!val->value)
454 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
455 strcpy (complete_name, resourced_name);
457 else
459 int complete_length =
460 strlen (resourced_name) + strlen (val->value) + 2;
461 complete_name = XtMalloc (complete_length);
462 *complete_name = 0;
463 strcat (complete_name, resourced_name);
464 strcat (complete_name, " ");
465 strcat (complete_name, val->value);
468 val->toolkit_data = complete_name;
469 val->free_toolkit_data = True;
471 return (char*)val->toolkit_data;
474 /* Returns the sizes of an item */
475 static void
476 size_menu_item (XlwMenuWidget mw,
477 widget_value* val,
478 int horizontal_p,
479 int* label_width,
480 int* rest_width,
481 int* button_width,
482 int* height)
484 enum menu_separator separator;
486 if (lw_separator_p (val->name, &separator, 0))
488 *height = separator_height (separator);
489 *label_width = 1;
490 *rest_width = 0;
491 *button_width = 0;
493 else
495 *height = MENU_FONT_HEIGHT (mw)
496 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
498 *label_width =
499 string_width (mw, resource_widget_value (mw, val))
500 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
502 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
503 if (!horizontal_p)
505 if (val->contents)
506 /* Add width of the arrow displayed for submenus. */
507 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
508 else if (val->key)
509 /* Add width of key equivalent string. */
510 *rest_width += (string_width (mw, val->key)
511 + mw->menu.arrow_spacing);
513 if (val->button_type == BUTTON_TYPE_TOGGLE)
514 *button_width = (toggle_button_width (mw)
515 + mw->menu.horizontal_spacing);
516 else if (val->button_type == BUTTON_TYPE_RADIO)
517 *button_width = (radio_button_width (mw)
518 + mw->menu.horizontal_spacing);
523 static void
524 size_menu (XlwMenuWidget mw, int level)
526 int label_width = 0;
527 int rest_width = 0;
528 int button_width = 0;
529 int max_rest_width = 0;
530 int max_button_width = 0;
531 int height = 0;
532 int horizontal_p = mw->menu.horizontal && (level == 0);
533 widget_value* val;
534 window_state* ws;
536 if (level >= mw->menu.old_depth)
537 abort_gracefully ((Widget) mw);
539 ws = &mw->menu.windows [level];
540 ws->width = 0;
541 ws->height = 0;
542 ws->label_width = 0;
543 ws->button_width = 0;
545 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
547 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
548 &button_width, &height);
549 if (horizontal_p)
551 ws->width += label_width + rest_width;
552 if (height > ws->height)
553 ws->height = height;
555 else
557 if (label_width > ws->label_width)
558 ws->label_width = label_width;
559 if (rest_width > max_rest_width)
560 max_rest_width = rest_width;
561 if (button_width > max_button_width)
562 max_button_width = button_width;
563 ws->height += height;
567 if (horizontal_p)
568 ws->label_width = ws->button_width = 0;
569 else
571 ws->width = ws->label_width + max_rest_width + max_button_width;
572 ws->button_width = max_button_width;
575 ws->width += 2 * mw->menu.shadow_thickness;
576 ws->height += 2 * mw->menu.shadow_thickness;
577 ws->max_rest_width = max_rest_width;
579 if (horizontal_p)
581 ws->width += 2 * mw->menu.margin;
582 ws->height += 2 * mw->menu.margin;
587 \f/* Display code */
589 static void
590 draw_arrow (XlwMenuWidget mw,
591 Window window,
592 GC gc,
593 int x,
594 int y,
595 int width,
596 int down_p)
598 Display *dpy = XtDisplay (mw);
599 GC top_gc = mw->menu.shadow_top_gc;
600 GC bottom_gc = mw->menu.shadow_bottom_gc;
601 int thickness = mw->menu.shadow_thickness;
602 int height = width;
603 XPoint pt[10];
604 /* alpha = atan (0.5)
605 factor = (1 + sin (alpha)) / cos (alpha) */
606 double factor = 1.62;
607 int thickness2 = thickness * factor;
609 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
611 if (down_p)
613 GC temp;
614 temp = top_gc;
615 top_gc = bottom_gc;
616 bottom_gc = temp;
619 pt[0].x = x;
620 pt[0].y = y + height;
621 pt[1].x = x + thickness;
622 pt[1].y = y + height - thickness2;
623 pt[2].x = x + thickness2;
624 pt[2].y = y + thickness2;
625 pt[3].x = x;
626 pt[3].y = y;
627 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
629 pt[0].x = x;
630 pt[0].y = y;
631 pt[1].x = x + thickness;
632 pt[1].y = y + thickness2;
633 pt[2].x = x + width - thickness2;
634 pt[2].y = y + height / 2;
635 pt[3].x = x + width;
636 pt[3].y = y + height / 2;
637 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
639 pt[0].x = x;
640 pt[0].y = y + height;
641 pt[1].x = x + thickness;
642 pt[1].y = y + height - thickness2;
643 pt[2].x = x + width - thickness2;
644 pt[2].y = y + height / 2;
645 pt[3].x = x + width;
646 pt[3].y = y + height / 2;
647 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
652 static void
653 draw_shadow_rectangle (XlwMenuWidget mw,
654 Window window,
655 int x,
656 int y,
657 int width,
658 int height,
659 int erase_p,
660 int down_p)
662 Display *dpy = XtDisplay (mw);
663 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
664 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
665 int thickness = mw->menu.shadow_thickness;
666 XPoint points [4];
668 if (!erase_p && down_p)
670 GC temp;
671 temp = top_gc;
672 top_gc = bottom_gc;
673 bottom_gc = temp;
676 points [0].x = x;
677 points [0].y = y;
678 points [1].x = x + width;
679 points [1].y = y;
680 points [2].x = x + width - thickness;
681 points [2].y = y + thickness;
682 points [3].x = x;
683 points [3].y = y + thickness;
684 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
685 points [0].x = x;
686 points [0].y = y + thickness;
687 points [1].x = x;
688 points [1].y = y + height;
689 points [2].x = x + thickness;
690 points [2].y = y + height - thickness;
691 points [3].x = x + thickness;
692 points [3].y = y + thickness;
693 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
694 points [0].x = x + width;
695 points [0].y = y;
696 points [1].x = x + width - thickness;
697 points [1].y = y + thickness;
698 points [2].x = x + width - thickness;
699 points [2].y = y + height - thickness;
700 points [3].x = x + width;
701 points [3].y = y + height - thickness;
702 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
703 points [0].x = x;
704 points [0].y = y + height;
705 points [1].x = x + width;
706 points [1].y = y + height;
707 points [2].x = x + width;
708 points [2].y = y + height - thickness;
709 points [3].x = x + thickness;
710 points [3].y = y + height - thickness;
711 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
715 static void
716 draw_shadow_rhombus (XlwMenuWidget mw,
717 Window window,
718 int x,
719 int y,
720 int width,
721 int height,
722 int erase_p,
723 int down_p)
725 Display *dpy = XtDisplay (mw);
726 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
727 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
728 int thickness = mw->menu.shadow_thickness;
729 XPoint points [4];
731 if (!erase_p && down_p)
733 GC temp;
734 temp = top_gc;
735 top_gc = bottom_gc;
736 bottom_gc = temp;
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 + thickness;
745 points [3].x = x + width / 2;
746 points [3].y = y;
747 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
748 points [0].x = x + width / 2;
749 points [0].y = y;
750 points [1].x = x + width / 2;
751 points [1].y = y + 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, top_gc, points, 4, Convex, CoordModeOrigin);
757 points [0].x = x;
758 points [0].y = y + height / 2;
759 points [1].x = x + thickness;
760 points [1].y = y + height / 2;
761 points [2].x = x + width / 2;
762 points [2].y = y + height - thickness;
763 points [3].x = x + width / 2;
764 points [3].y = y + height;
765 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
766 points [0].x = x + width / 2;
767 points [0].y = y + height;
768 points [1].x = x + width / 2;
769 points [1].y = y + height - thickness;
770 points [2].x = x + width - thickness;
771 points [2].y = y + height / 2;
772 points [3].x = x + width;
773 points [3].y = y + height / 2;
774 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
778 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
779 top-left corner of the menu item. SELECTED_P non-zero means the
780 toggle button is selected. */
782 static void
783 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
785 int width, height;
787 width = toggle_button_width (mw);
788 height = width;
789 x += mw->menu.horizontal_spacing;
790 y += (MENU_FONT_ASCENT (mw) - height) / 2;
791 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
795 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
796 top-left corner of the menu item. SELECTED_P non-zero means the
797 toggle button is selected. */
799 static void
800 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
802 int width, height;
804 width = radio_button_width (mw);
805 height = width;
806 x += mw->menu.horizontal_spacing;
807 y += (MENU_FONT_ASCENT (mw) - height) / 2;
808 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
812 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
813 top-left corner of the menu item. WIDTH is the width of the
814 separator to draw. TYPE is the separator type. */
816 static void
817 draw_separator (XlwMenuWidget mw,
818 Window window,
819 int x,
820 int y,
821 int width,
822 enum menu_separator type)
824 Display *dpy = XtDisplay (mw);
825 XGCValues xgcv;
827 switch (type)
829 case SEPARATOR_NO_LINE:
830 break;
832 case SEPARATOR_SINGLE_LINE:
833 XDrawLine (dpy, window, mw->menu.foreground_gc,
834 x, y, x + width, y);
835 break;
837 case SEPARATOR_DOUBLE_LINE:
838 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
839 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
840 break;
842 case SEPARATOR_SINGLE_DASHED_LINE:
843 xgcv.line_style = LineOnOffDash;
844 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
845 XDrawLine (dpy, window, mw->menu.foreground_gc,
846 x, y, x + width, y);
847 xgcv.line_style = LineSolid;
848 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
849 break;
851 case SEPARATOR_DOUBLE_DASHED_LINE:
852 draw_separator (mw, window, x, y, width,
853 SEPARATOR_SINGLE_DASHED_LINE);
854 draw_separator (mw, window, x, y + 2, width,
855 SEPARATOR_SINGLE_DASHED_LINE);
856 break;
858 case SEPARATOR_SHADOW_ETCHED_IN:
859 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
860 x, y, x + width, y);
861 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
862 x, y + 1, x + width, y + 1);
863 break;
865 case SEPARATOR_SHADOW_ETCHED_OUT:
866 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
867 x, y, x + width, y);
868 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
869 x, y + 1, x + width, y + 1);
870 break;
872 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
873 xgcv.line_style = LineOnOffDash;
874 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
875 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
876 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
877 xgcv.line_style = LineSolid;
878 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
879 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
880 break;
882 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
883 xgcv.line_style = LineOnOffDash;
884 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
885 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
886 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
887 xgcv.line_style = LineSolid;
888 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
889 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
890 break;
892 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
893 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
894 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
895 break;
897 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
898 draw_separator (mw, window, x, y, width,
899 SEPARATOR_SHADOW_ETCHED_OUT);
900 draw_separator (mw, window, x, y + 3, width,
901 SEPARATOR_SHADOW_ETCHED_OUT);
902 break;
904 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
905 xgcv.line_style = LineOnOffDash;
906 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
907 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
908 draw_separator (mw, window, x, y, width,
909 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
910 xgcv.line_style = LineSolid;
911 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
912 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
913 break;
915 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
916 xgcv.line_style = LineOnOffDash;
917 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
918 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
919 draw_separator (mw, window, x, y, width,
920 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
921 xgcv.line_style = LineSolid;
922 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
923 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
924 break;
926 default:
927 abort ();
932 /* Return the pixel height of menu separator SEPARATOR. */
934 static int
935 separator_height (enum menu_separator separator)
937 switch (separator)
939 case SEPARATOR_NO_LINE:
940 return 2;
942 case SEPARATOR_SINGLE_LINE:
943 case SEPARATOR_SINGLE_DASHED_LINE:
944 return 1;
946 case SEPARATOR_DOUBLE_LINE:
947 case SEPARATOR_DOUBLE_DASHED_LINE:
948 return 3;
950 case SEPARATOR_SHADOW_ETCHED_IN:
951 case SEPARATOR_SHADOW_ETCHED_OUT:
952 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
953 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
954 return 2;
956 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
957 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
958 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
959 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
960 return 5;
962 default:
963 abort ();
968 /* Display the menu item and increment where.x and where.y to show how large
969 the menu item was. */
971 static void
972 display_menu_item (XlwMenuWidget mw,
973 widget_value* val,
974 window_state* ws,
975 XPoint* where,
976 Boolean highlighted_p,
977 Boolean horizontal_p,
978 Boolean just_compute_p)
980 GC deco_gc;
981 GC text_gc;
982 int font_height = MENU_FONT_HEIGHT (mw);
983 int font_ascent = MENU_FONT_ASCENT (mw);
984 int shadow = mw->menu.shadow_thickness;
985 int margin = mw->menu.margin;
986 int h_spacing = mw->menu.horizontal_spacing;
987 int v_spacing = mw->menu.vertical_spacing;
988 int label_width;
989 int rest_width;
990 int button_width;
991 int height;
992 int width;
993 enum menu_separator separator;
994 int separator_p = lw_separator_p (val->name, &separator, 0);
995 #ifdef HAVE_XFT
996 XftColor *xftfg;
997 #endif
999 /* compute the sizes of the item */
1000 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
1001 &button_width, &height);
1003 if (horizontal_p)
1004 width = label_width + rest_width;
1005 else
1007 label_width = ws->label_width;
1008 width = ws->width - 2 * shadow;
1011 /* Only highlight an enabled item that has a callback. */
1012 if (highlighted_p)
1013 if (!val->enabled || !(val->call_data || val->contents))
1014 highlighted_p = 0;
1016 /* do the drawing. */
1017 if (!just_compute_p)
1019 /* Add the shadow border of the containing menu */
1020 int x = where->x + shadow;
1021 int y = where->y + shadow;
1023 if (horizontal_p)
1025 x += margin;
1026 y += margin;
1029 /* pick the foreground and background GC. */
1030 if (val->enabled)
1031 text_gc = mw->menu.foreground_gc;
1032 else
1033 text_gc = mw->menu.disabled_gc;
1034 deco_gc = mw->menu.foreground_gc;
1035 #ifdef HAVE_XFT
1036 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1037 #endif
1039 if (separator_p)
1041 draw_separator (mw, ws->pixmap, x, y, width, separator);
1043 else
1045 int x_offset = x + h_spacing + shadow;
1046 char* display_string = resource_widget_value (mw, val);
1047 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1048 False);
1050 /* Deal with centering a menu title. */
1051 if (!horizontal_p && !val->contents && !val->call_data)
1053 int l = string_width (mw, display_string);
1055 if (width > l)
1056 x_offset = (width - l) >> 1;
1058 else if (!horizontal_p && ws->button_width)
1059 x_offset += ws->button_width;
1062 #ifdef HAVE_XFT
1063 if (ws->xft_draw)
1065 int draw_y = y + v_spacing + shadow;
1066 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1067 mw->menu.xft_font,
1068 x_offset, draw_y + font_ascent,
1069 (unsigned char *) display_string,
1070 strlen (display_string));
1072 else
1073 #endif
1074 #ifdef HAVE_X_I18N
1075 if (mw->menu.fontSet)
1076 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1077 text_gc, x_offset,
1078 y + v_spacing + shadow + font_ascent,
1079 display_string, strlen (display_string));
1080 else
1081 #endif
1082 XDrawString (XtDisplay (mw), ws->pixmap,
1083 text_gc, x_offset,
1084 y + v_spacing + shadow + font_ascent,
1085 display_string, strlen (display_string));
1087 if (!horizontal_p)
1089 if (val->button_type == BUTTON_TYPE_TOGGLE)
1090 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1091 val->selected);
1092 else if (val->button_type == BUTTON_TYPE_RADIO)
1093 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1094 val->selected);
1096 if (val->contents)
1098 int a_w = arrow_width (mw);
1099 draw_arrow (mw, ws->pixmap, deco_gc,
1100 x + width - a_w
1101 - mw->menu.horizontal_spacing
1102 - mw->menu.shadow_thickness,
1103 y + v_spacing + shadow, a_w,
1104 highlighted_p);
1106 else if (val->key)
1108 #ifdef HAVE_XFT
1109 if (ws->xft_draw)
1111 int draw_x = ws->width - ws->max_rest_width
1112 + mw->menu.arrow_spacing;
1113 int draw_y = y + v_spacing + shadow + font_ascent;
1114 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1115 mw->menu.xft_font,
1116 draw_x, draw_y,
1117 (unsigned char *) val->key,
1118 strlen (val->key));
1120 else
1121 #endif
1122 #ifdef HAVE_X_I18N
1123 if (mw->menu.fontSet)
1124 XmbDrawString (XtDisplay (mw), ws->pixmap,
1125 mw->menu.fontSet,
1126 text_gc,
1127 x + label_width + mw->menu.arrow_spacing,
1128 y + v_spacing + shadow + font_ascent,
1129 val->key, strlen (val->key));
1130 else
1131 #endif
1132 XDrawString (XtDisplay (mw), ws->pixmap,
1133 text_gc,
1134 x + label_width + mw->menu.arrow_spacing,
1135 y + v_spacing + shadow + font_ascent,
1136 val->key, strlen (val->key));
1139 else
1141 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1142 mw->menu.background_gc,
1143 x + shadow, y + shadow,
1144 label_width + h_spacing - 1,
1145 font_height + 2 * v_spacing - 1);
1146 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1147 True, False);
1150 if (highlighted_p)
1151 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1152 False);
1156 where->x += width;
1157 where->y += height;
1160 static void
1161 display_menu (XlwMenuWidget mw,
1162 int level,
1163 Boolean just_compute_p,
1164 XPoint *highlighted_pos,
1165 XPoint *hit,
1166 widget_value **hit_return)
1168 widget_value* val;
1169 widget_value* following_item;
1170 window_state* ws;
1171 XPoint where;
1172 int horizontal_p = mw->menu.horizontal && (level == 0);
1173 int highlighted_p;
1174 int no_return = 0;
1175 enum menu_separator separator;
1177 if (level >= mw->menu.old_depth)
1178 abort_gracefully ((Widget) mw);
1180 if (level < mw->menu.old_depth - 1)
1181 following_item = mw->menu.old_stack [level + 1];
1182 else
1183 following_item = NULL;
1185 if (hit)
1186 *hit_return = NULL;
1188 where.x = 0;
1189 where.y = 0;
1191 ws = &mw->menu.windows [level];
1193 if (!just_compute_p)
1194 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1195 0, 0, ws->width, ws->height);
1197 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1199 highlighted_p = val == following_item;
1200 if (highlighted_p && highlighted_pos)
1202 if (horizontal_p)
1203 highlighted_pos->x = where.x;
1204 else
1205 highlighted_pos->y = where.y;
1208 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1209 just_compute_p);
1211 if (highlighted_p && highlighted_pos)
1213 if (horizontal_p)
1214 highlighted_pos->y = where.y;
1215 else
1216 highlighted_pos->x = where.x;
1219 if (hit
1220 && !*hit_return
1221 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1222 && !lw_separator_p (val->name, &separator, 0)
1223 && !no_return)
1225 if (val->enabled)
1226 *hit_return = val;
1227 else
1228 no_return = 1;
1229 if (mw->menu.inside_entry != val)
1231 if (mw->menu.inside_entry)
1232 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1233 (XtPointer) mw->menu.inside_entry);
1234 mw->menu.inside_entry = val;
1235 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1236 (XtPointer) mw->menu.inside_entry);
1240 if (horizontal_p)
1241 where.y = 0;
1242 else
1243 where.x = 0;
1246 if (!just_compute_p)
1248 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1249 False, False);
1250 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1251 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1255 \f/* Motion code */
1256 static void
1257 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1259 int i;
1261 mw->menu.new_depth = 0;
1262 for (i = 0; i < level; i++)
1263 push_new_stack (mw, mw->menu.old_stack [i]);
1264 push_new_stack (mw, val);
1267 static void
1268 expose_cb (Widget widget,
1269 XtPointer closure,
1270 XEvent* event,
1271 Boolean* continue_to_dispatch)
1273 XlwMenuWidget mw = (XlwMenuWidget) closure;
1274 int i;
1276 *continue_to_dispatch = False;
1277 for (i = 0; i < mw->menu.windows_length; ++i)
1278 if (mw->menu.windows [i].w == widget) break;
1279 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1280 display_menu (mw, i, False, NULL, NULL, NULL);
1283 static void
1284 set_window_type (Widget w, XlwMenuWidget mw)
1286 int popup_menu_p = mw->menu.top_depth == 1;
1287 Atom type = XInternAtom (XtDisplay (w),
1288 popup_menu_p
1289 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1290 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1291 False);
1293 XChangeProperty (XtDisplay (w), XtWindow (w),
1294 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1295 XA_ATOM, 32, PropModeReplace,
1296 (unsigned char *)&type, 1);
1300 static void
1301 make_windows_if_needed (XlwMenuWidget mw, int n)
1303 int i;
1304 int start_at;
1305 window_state* windows;
1307 if (mw->menu.windows_length >= n)
1308 return;
1310 if (!mw->menu.windows)
1312 mw->menu.windows =
1313 (window_state*)XtMalloc (n * sizeof (window_state));
1314 start_at = 0;
1316 else
1318 mw->menu.windows =
1319 (window_state*)XtRealloc ((char*)mw->menu.windows,
1320 n * sizeof (window_state));
1321 start_at = mw->menu.windows_length;
1323 mw->menu.windows_length = n;
1325 windows = mw->menu.windows;
1327 for (i = start_at; i < n; i++)
1329 Arg av[10];
1330 int ac = 0;
1331 windows [i].x = 0;
1332 windows [i].y = 0;
1333 windows [i].width = 1;
1334 windows [i].height = 1;
1335 windows [i].max_rest_width = 0;
1336 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1337 XtSetArg (av[ac], XtNheight, 1); ++ac;
1338 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1339 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1340 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1341 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1342 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1343 windows [i].w =
1344 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1345 (Widget) mw, av, ac);
1346 XtRealizeWidget (windows [i].w);
1347 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1348 windows [i].window = XtWindow (windows [i].w);
1349 windows [i].pixmap = None;
1350 #ifdef HAVE_XFT
1351 windows [i].xft_draw = 0;
1352 #endif
1353 set_window_type (windows [i].w, mw);
1357 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1360 xlwmenu_window_p (Widget w, Window window)
1362 XlwMenuWidget mw = (XlwMenuWidget) w;
1363 int i;
1365 for (i = 0; i < mw->menu.windows_length; ++i)
1366 if (window == mw->menu.windows[i].window)
1367 break;
1369 return i < mw->menu.windows_length;
1372 /* Make the window fit in the screen */
1373 static void
1374 fit_to_screen (XlwMenuWidget mw,
1375 window_state *ws,
1376 window_state *previous_ws,
1377 Boolean horizontal_p)
1379 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1380 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1381 /* 1 if we are unable to avoid an overlap between
1382 this menu and the parent menu in the X dimension. */
1383 int horizontal_overlap = 0;
1385 if (ws->x < 0)
1386 ws->x = 0;
1387 else if (ws->x + ws->width > screen_width)
1389 if (!horizontal_p)
1390 /* The addition of shadow-thickness for a sub-menu's position is
1391 to reflect a similar adjustment when the menu is displayed to
1392 the right of the invoking menu-item; it makes the sub-menu
1393 look more `attached' to the menu-item. */
1394 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1395 else
1396 ws->x = screen_width - ws->width;
1397 if (ws->x < 0)
1399 ws->x = 0;
1400 horizontal_overlap = 1;
1403 /* If we overlap in X, try to avoid overlap in Y. */
1404 if (horizontal_overlap
1405 && ws->y < previous_ws->y + previous_ws->height
1406 && previous_ws->y < ws->y + ws->height)
1408 /* Put this menu right below or right above PREVIOUS_WS
1409 if there's room. */
1410 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1411 ws->y = previous_ws->y + previous_ws->height;
1412 else if (previous_ws->y - ws->height > 0)
1413 ws->y = previous_ws->y - ws->height;
1416 if (ws->y < 0)
1417 ws->y = 0;
1418 else if (ws->y + ws->height > screen_height)
1420 if (horizontal_p)
1421 ws->y = previous_ws->y - ws->height;
1422 else
1423 ws->y = screen_height - ws->height;
1424 if (ws->y < 0)
1425 ws->y = 0;
1429 static void
1430 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1432 if (ws->pixmap != None)
1434 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1435 ws->pixmap = None;
1437 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1438 ws->width, ws->height,
1439 DefaultDepthOfScreen (XtScreen (ws->w)));
1440 #ifdef HAVE_XFT
1441 if (ws->xft_draw)
1442 XftDrawDestroy (ws->xft_draw);
1443 if (mw->menu.xft_font)
1445 int screen = XScreenNumberOfScreen (mw->core.screen);
1446 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1447 ws->pixmap,
1448 DefaultVisual (XtDisplay (ws->w), screen),
1449 mw->core.colormap);
1451 else
1452 ws->xft_draw = 0;
1453 #endif
1456 /* Updates old_stack from new_stack and redisplays. */
1457 static void
1458 remap_menubar (XlwMenuWidget mw)
1460 int i;
1461 int last_same;
1462 XPoint selection_position;
1463 int old_depth = mw->menu.old_depth;
1464 int new_depth = mw->menu.new_depth;
1465 widget_value** old_stack;
1466 widget_value** new_stack;
1467 window_state* windows;
1468 widget_value* old_selection;
1469 widget_value* new_selection;
1471 /* Check that enough windows and old_stack are ready. */
1472 make_windows_if_needed (mw, new_depth);
1473 make_old_stack_space (mw, new_depth);
1474 windows = mw->menu.windows;
1475 old_stack = mw->menu.old_stack;
1476 new_stack = mw->menu.new_stack;
1478 /* compute the last identical different entry */
1479 for (i = 1; i < old_depth && i < new_depth; i++)
1480 if (old_stack [i] != new_stack [i])
1481 break;
1482 last_same = i - 1;
1484 /* Memorize the previously selected item to be able to refresh it */
1485 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1486 if (old_selection && !old_selection->enabled)
1487 old_selection = NULL;
1488 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1489 if (new_selection && !new_selection->enabled)
1490 new_selection = NULL;
1492 /* Call callback when the hightlighted item changes. */
1493 if (old_selection || new_selection)
1494 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1495 (XtPointer) new_selection);
1497 /* updates old_state from new_state. It has to be done now because
1498 display_menu (called below) uses the old_stack to know what to display. */
1499 for (i = last_same + 1; i < new_depth; i++)
1501 XtPopdown (mw->menu.windows [i].w);
1502 old_stack [i] = new_stack [i];
1504 mw->menu.old_depth = new_depth;
1506 /* refresh the last selection */
1507 selection_position.x = 0;
1508 selection_position.y = 0;
1509 display_menu (mw, last_same, new_selection == old_selection,
1510 &selection_position, NULL, NULL);
1512 /* Now place the new menus. */
1513 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1515 window_state *previous_ws = &windows[i - 1];
1516 window_state *ws = &windows[i];
1518 ws->x = (previous_ws->x + selection_position.x
1519 + mw->menu.shadow_thickness);
1520 if (mw->menu.horizontal && i == 1)
1521 ws->x += mw->menu.margin;
1523 #if 0
1524 if (!mw->menu.horizontal || i > 1)
1525 ws->x += mw->menu.shadow_thickness;
1526 #endif
1528 ws->y = (previous_ws->y + selection_position.y
1529 + mw->menu.shadow_thickness);
1530 if (mw->menu.horizontal && i == 1)
1531 ws->y += mw->menu.margin;
1533 size_menu (mw, i);
1535 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1537 XtVaSetValues (ws->w, XtNwidth, ws->width, XtNheight, ws->height,
1538 XtNx, ws->x, XtNy, ws->y, NULL);
1539 create_pixmap_for_menu (ws, mw);
1540 XtPopup (ws->w, XtGrabNone);
1541 display_menu (mw, i, False, &selection_position, NULL, NULL);
1544 /* unmap the menus that popped down */
1545 for (i = new_depth - 1; i < old_depth; i++)
1546 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1547 XtPopdown (windows[i].w);
1550 static Boolean
1551 motion_event_is_in_menu (XlwMenuWidget mw,
1552 XMotionEvent *ev,
1553 int level,
1554 XPoint *relative_pos)
1556 window_state* ws = &mw->menu.windows [level];
1557 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1558 int x = ws->x + shadow;
1559 int y = ws->y + shadow;
1560 relative_pos->x = ev->x_root - x;
1561 relative_pos->y = ev->y_root - y;
1562 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1563 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1566 static Boolean
1567 map_event_to_widget_value (XlwMenuWidget mw,
1568 XMotionEvent *ev,
1569 widget_value **val,
1570 int *level)
1572 int i;
1573 XPoint relative_pos;
1574 window_state* ws;
1575 int inside = 0;
1577 *val = NULL;
1579 /* Find the window */
1580 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1582 ws = &mw->menu.windows [i];
1583 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1585 inside = 1;
1586 display_menu (mw, i, True, NULL, &relative_pos, val);
1588 if (*val)
1590 *level = i + 1;
1591 return True;
1596 if (!inside)
1598 if (mw->menu.inside_entry != NULL)
1599 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1600 (XtPointer) mw->menu.inside_entry);
1601 mw->menu.inside_entry = NULL;
1604 return False;
1607 \f/* Procedures */
1608 static void
1609 make_drawing_gcs (XlwMenuWidget mw)
1611 XGCValues xgcv;
1612 float scale;
1613 XtGCMask mask = GCForeground | GCBackground;
1615 #ifdef HAVE_X_I18N
1616 if (!mw->menu.fontSet)
1618 xgcv.font = mw->menu.font->fid;
1619 mask |= GCFont;
1621 #else
1622 xgcv.font = mw->menu.font->fid;
1623 mask |= GCFont;
1624 #endif
1625 xgcv.foreground = mw->menu.foreground;
1626 xgcv.background = mw->core.background_pixel;
1627 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1629 xgcv.foreground = mw->menu.button_foreground;
1630 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1632 xgcv.background = mw->core.background_pixel;
1634 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1636 /* Allocate color for disabled menu-items. */
1637 mw->menu.disabled_foreground = mw->menu.foreground;
1638 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1639 scale = 2.3;
1640 else
1641 scale = 0.55;
1643 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1644 mw->core.colormap,
1645 &mw->menu.disabled_foreground,
1646 scale,
1647 0x8000);
1649 if (mw->menu.foreground == mw->menu.disabled_foreground
1650 || mw->core.background_pixel == mw->menu.disabled_foreground)
1652 /* Too few colors, use stipple. */
1653 xgcv.foreground = mw->menu.foreground;
1654 xgcv.fill_style = FillStippled;
1655 xgcv.stipple = mw->menu.gray_pixmap;
1656 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1657 | GCFillStyle | GCStipple, &xgcv);
1659 else
1661 /* Many colors available, use disabled pixel. */
1662 xgcv.foreground = mw->menu.disabled_foreground;
1663 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1666 xgcv.foreground = mw->menu.button_foreground;
1667 xgcv.background = mw->core.background_pixel;
1668 xgcv.fill_style = FillStippled;
1669 xgcv.stipple = mw->menu.gray_pixmap;
1670 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1671 | GCFillStyle | GCStipple, &xgcv);
1673 xgcv.foreground = mw->core.background_pixel;
1674 xgcv.background = mw->menu.foreground;
1675 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1678 static void
1679 release_drawing_gcs (XlwMenuWidget mw)
1681 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1682 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1683 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1684 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1685 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1686 /* let's get some segvs if we try to use these... */
1687 mw->menu.foreground_gc = (GC) -1;
1688 mw->menu.button_gc = (GC) -1;
1689 mw->menu.disabled_gc = (GC) -1;
1690 mw->menu.inactive_button_gc = (GC) -1;
1691 mw->menu.background_gc = (GC) -1;
1694 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1695 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1697 static void
1698 make_shadow_gcs (XlwMenuWidget mw)
1700 XGCValues xgcv;
1701 unsigned long pm = 0;
1702 Display *dpy = XtDisplay ((Widget) mw);
1703 Screen *screen = XtScreen ((Widget) mw);
1704 Colormap cmap = mw->core.colormap;
1705 XColor topc, botc;
1706 int top_frobbed = 0, bottom_frobbed = 0;
1708 mw->menu.free_top_shadow_color_p = 0;
1709 mw->menu.free_bottom_shadow_color_p = 0;
1711 if (mw->menu.top_shadow_color == -1)
1712 mw->menu.top_shadow_color = mw->core.background_pixel;
1713 else
1714 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1716 if (mw->menu.bottom_shadow_color == -1)
1717 mw->menu.bottom_shadow_color = mw->menu.foreground;
1718 else
1719 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1721 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1722 mw->menu.top_shadow_color == mw->menu.foreground)
1724 topc.pixel = mw->core.background_pixel;
1725 #ifdef emacs
1726 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1727 &topc.pixel,
1728 1.2, 0x8000))
1729 #else
1730 XQueryColor (dpy, cmap, &topc);
1731 /* don't overflow/wrap! */
1732 topc.red = MINL (65535, topc.red * 1.2);
1733 topc.green = MINL (65535, topc.green * 1.2);
1734 topc.blue = MINL (65535, topc.blue * 1.2);
1735 if (XAllocColor (dpy, cmap, &topc))
1736 #endif
1738 mw->menu.top_shadow_color = topc.pixel;
1739 mw->menu.free_top_shadow_color_p = 1;
1740 top_frobbed = 1;
1743 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1744 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1746 botc.pixel = mw->core.background_pixel;
1747 #ifdef emacs
1748 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1749 &botc.pixel,
1750 0.6, 0x4000))
1751 #else
1752 XQueryColor (dpy, cmap, &botc);
1753 botc.red *= 0.6;
1754 botc.green *= 0.6;
1755 botc.blue *= 0.6;
1756 if (XAllocColor (dpy, cmap, &botc))
1757 #endif
1759 mw->menu.bottom_shadow_color = botc.pixel;
1760 mw->menu.free_bottom_shadow_color_p = 1;
1761 bottom_frobbed = 1;
1765 if (top_frobbed && bottom_frobbed)
1767 if (topc.pixel == botc.pixel)
1769 if (botc.pixel == mw->menu.foreground)
1771 if (mw->menu.free_top_shadow_color_p)
1773 x_free_dpy_colors (dpy, screen, cmap,
1774 &mw->menu.top_shadow_color, 1);
1775 mw->menu.free_top_shadow_color_p = 0;
1777 mw->menu.top_shadow_color = mw->core.background_pixel;
1779 else
1781 if (mw->menu.free_bottom_shadow_color_p)
1783 x_free_dpy_colors (dpy, screen, cmap,
1784 &mw->menu.bottom_shadow_color, 1);
1785 mw->menu.free_bottom_shadow_color_p = 0;
1787 mw->menu.bottom_shadow_color = mw->menu.foreground;
1792 if (!mw->menu.top_shadow_pixmap &&
1793 mw->menu.top_shadow_color == mw->core.background_pixel)
1795 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1796 if (mw->menu.free_top_shadow_color_p)
1798 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1799 mw->menu.free_top_shadow_color_p = 0;
1801 mw->menu.top_shadow_color = mw->menu.foreground;
1803 if (!mw->menu.bottom_shadow_pixmap &&
1804 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1806 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1807 if (mw->menu.free_bottom_shadow_color_p)
1809 x_free_dpy_colors (dpy, screen, cmap,
1810 &mw->menu.bottom_shadow_color, 1);
1811 mw->menu.free_bottom_shadow_color_p = 0;
1813 mw->menu.bottom_shadow_color = mw->menu.foreground;
1816 xgcv.fill_style = FillStippled;
1817 xgcv.foreground = mw->menu.top_shadow_color;
1818 xgcv.stipple = mw->menu.top_shadow_pixmap;
1819 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1820 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1822 xgcv.foreground = mw->menu.bottom_shadow_color;
1823 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1824 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1825 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1829 static void
1830 release_shadow_gcs (XlwMenuWidget mw)
1832 Display *dpy = XtDisplay ((Widget) mw);
1833 Screen *screen = XtScreen ((Widget) mw);
1834 Colormap cmap = mw->core.colormap;
1835 Pixel px[2];
1836 int i = 0;
1838 if (mw->menu.free_top_shadow_color_p)
1839 px[i++] = mw->menu.top_shadow_color;
1840 if (mw->menu.free_bottom_shadow_color_p)
1841 px[i++] = mw->menu.bottom_shadow_color;
1842 if (i > 0)
1843 x_free_dpy_colors (dpy, screen, cmap, px, i);
1845 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1846 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1849 #ifdef HAVE_XFT
1850 static int
1851 openXftFont (XlwMenuWidget mw)
1853 char *fname = mw->menu.faceName;
1855 mw->menu.xft_font = 0;
1856 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FACENAME) == 0;
1858 if (fname && strcmp (fname, "none") != 0)
1860 int screen = XScreenNumberOfScreen (mw->core.screen);
1861 int len = strlen (fname), i = len-1;
1862 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1863 while (i > 0 && isdigit (fname[i]))
1864 --i;
1865 if (fname[i] == ' ')
1867 fname = xstrdup (mw->menu.faceName);
1868 fname[i] = '-';
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 = XftFontOpenName (XtDisplay (mw), screen,
1876 DEFAULT_FACENAME);
1880 if (fname != mw->menu.faceName) free (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_bitmap_bits,
1908 gray_bitmap_width, gray_bitmap_height,
1909 (unsigned long)1, (unsigned long)0, 1);
1911 #ifdef HAVE_XFT
1912 if (openXftFont (mw))
1914 else
1915 #endif
1917 if (!mw->menu.font)
1919 if (!xlwmenu_default_font)
1920 xlwmenu_default_font = XLoadQueryFont (display, "fixed");
1921 mw->menu.font = xlwmenu_default_font;
1922 if (!mw->menu.font)
1924 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1925 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)
1969 xlwmenu_default_font = 0;
1972 static void
1973 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1975 XlwMenuWidget mw = (XlwMenuWidget)w;
1976 XSetWindowAttributes xswa;
1977 int mask;
1979 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1980 (w, valueMask, attributes);
1982 xswa.save_under = True;
1983 xswa.cursor = mw->menu.cursor_shape;
1984 mask = CWSaveUnder | CWCursor;
1985 /* I sometimes get random BadCursor errors while creating the first
1986 frame on a display. I can not find their reason, but they are
1987 annoying so for now let's ignore any errors here. -- lorentey */
1988 #ifdef emacs
1989 x_catch_errors (XtDisplay (w));
1990 #endif
1991 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1992 #ifdef emacs
1993 x_uncatch_errors ();
1994 #endif
1996 mw->menu.windows [0].w = w;
1997 mw->menu.windows [0].window = XtWindow (w);
1998 mw->menu.windows [0].x = w->core.x;
1999 mw->menu.windows [0].y = w->core.y;
2000 mw->menu.windows [0].width = w->core.width;
2001 mw->menu.windows [0].height = w->core.height;
2003 set_window_type (mw->menu.windows [0].w, mw);
2004 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2006 #ifdef HAVE_XFT
2007 if (mw->menu.xft_font)
2009 XColor colors[3];
2010 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
2011 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
2012 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
2013 = mw->menu.disabled_foreground;
2014 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
2015 mw->menu.xft_fg.color.alpha = 0xFFFF;
2016 mw->menu.xft_fg.color.red = colors[0].red;
2017 mw->menu.xft_fg.color.green = colors[0].green;
2018 mw->menu.xft_fg.color.blue = colors[0].blue;
2019 mw->menu.xft_bg.color.alpha = 0xFFFF;
2020 mw->menu.xft_bg.color.red = colors[1].red;
2021 mw->menu.xft_bg.color.green = colors[1].green;
2022 mw->menu.xft_bg.color.blue = colors[1].blue;
2023 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2024 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2025 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2026 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2028 #endif
2031 /* Only the toplevel menubar/popup is a widget so it's the only one that
2032 receives expose events through Xt. So we repaint all the other panes
2033 when receiving an Expose event. */
2034 static void
2035 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2037 XlwMenuWidget mw = (XlwMenuWidget)w;
2039 /* If we have a depth beyond 1, it's because a submenu was displayed.
2040 If the submenu has been destroyed, set the depth back to 1. */
2041 if (submenu_destroyed)
2043 mw->menu.old_depth = 1;
2044 submenu_destroyed = 0;
2047 display_menu (mw, 0, False, NULL, NULL, NULL);
2051 /* Part of a hack to make the menu redisplay when a tooltip frame
2052 over a menu item is unmapped. */
2054 void
2055 xlwmenu_redisplay (Widget w)
2057 XlwMenuRedisplay (w, NULL, None);
2060 static void
2061 XlwMenuDestroy (Widget w)
2063 int i;
2064 XlwMenuWidget mw = (XlwMenuWidget) w;
2066 if (pointer_grabbed)
2067 ungrab_all ((Widget)w, CurrentTime);
2068 pointer_grabbed = 0;
2070 submenu_destroyed = 1;
2072 release_drawing_gcs (mw);
2073 release_shadow_gcs (mw);
2075 /* this doesn't come from the resource db but is created explicitly
2076 so we must free it ourselves. */
2077 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2078 mw->menu.gray_pixmap = (Pixmap) -1;
2080 #if 0
2081 /* Do free mw->menu.contents because nowadays we copy it
2082 during initialization. */
2083 XtFree (mw->menu.contents);
2084 #endif
2086 /* Don't free mw->menu.contents because that comes from our creator.
2087 The `*_stack' elements are just pointers into `contents' so leave
2088 that alone too. But free the stacks themselves. */
2089 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2090 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2092 /* Remember, you can't free anything that came from the resource
2093 database. This includes:
2094 mw->menu.cursor
2095 mw->menu.top_shadow_pixmap
2096 mw->menu.bottom_shadow_pixmap
2097 mw->menu.font
2098 Also the color cells of top_shadow_color, bottom_shadow_color,
2099 foreground, and button_foreground will never be freed until this
2100 client exits. Nice, eh?
2103 #ifdef HAVE_XFT
2104 if (mw->menu.windows [0].xft_draw)
2105 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2106 if (mw->menu.xft_font)
2107 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2108 #endif
2110 if (mw->menu.windows [0].pixmap != None)
2111 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2112 /* start from 1 because the one in slot 0 is w->core.window */
2113 for (i = 1; i < mw->menu.windows_length; i++)
2115 if (mw->menu.windows [i].pixmap != None)
2116 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2117 #ifdef HAVE_XFT
2118 if (mw->menu.windows [i].xft_draw)
2119 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2120 #endif
2123 if (mw->menu.windows)
2124 XtFree ((char *) mw->menu.windows);
2127 #ifdef HAVE_XFT
2128 static int
2129 facename_changed (XlwMenuWidget newmw,
2130 XlwMenuWidget oldmw)
2132 /* This will fore a new XftFont even if the same string is set.
2133 This is good, as rendering parameters may have changed and
2134 we just want to do a redisplay. */
2135 return newmw->menu.faceName != oldmw->menu.faceName;
2137 #endif
2139 static Boolean
2140 XlwMenuSetValues (Widget current, Widget request, Widget new,
2141 ArgList args, Cardinal *num_args)
2143 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2144 XlwMenuWidget newmw = (XlwMenuWidget)new;
2145 Boolean redisplay = False;
2146 int i;
2148 if (newmw->menu.contents
2149 && newmw->menu.contents->contents
2150 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2151 redisplay = True;
2152 /* Do redisplay if the contents are entirely eliminated. */
2153 if (newmw->menu.contents
2154 && newmw->menu.contents->contents == 0
2155 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2156 redisplay = True;
2158 if (newmw->core.background_pixel != oldmw->core.background_pixel
2159 || newmw->menu.foreground != oldmw->menu.foreground
2160 #ifdef HAVE_XFT
2161 || facename_changed (newmw, oldmw)
2162 #endif
2163 #ifdef HAVE_X_I18N
2164 || newmw->menu.fontSet != oldmw->menu.fontSet
2165 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2166 #else
2167 || newmw->menu.font != oldmw->menu.font
2168 #endif
2171 release_drawing_gcs (newmw);
2172 make_drawing_gcs (newmw);
2174 release_shadow_gcs (newmw);
2175 /* Cause the shadow colors to be recalculated. */
2176 newmw->menu.top_shadow_color = -1;
2177 newmw->menu.bottom_shadow_color = -1;
2178 make_shadow_gcs (newmw);
2180 redisplay = True;
2182 if (XtIsRealized (current))
2183 /* If the menu is currently displayed, change the display. */
2184 for (i = 0; i < oldmw->menu.windows_length; i++)
2186 XSetWindowBackground (XtDisplay (oldmw),
2187 oldmw->menu.windows [i].window,
2188 newmw->core.background_pixel);
2189 /* clear windows and generate expose events */
2190 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2191 0, 0, 0, 0, True);
2195 #ifdef HAVE_XFT
2196 if (facename_changed (newmw, oldmw))
2198 int i;
2199 int screen = XScreenNumberOfScreen (newmw->core.screen);
2200 if (newmw->menu.xft_font)
2201 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2202 openXftFont (newmw);
2203 for (i = 0; i < newmw->menu.windows_length; i++)
2205 if (newmw->menu.windows [i].xft_draw)
2206 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2207 newmw->menu.windows [i].xft_draw = 0;
2209 if (newmw->menu.xft_font)
2210 for (i = 0; i < newmw->menu.windows_length; i++)
2211 newmw->menu.windows [i].xft_draw
2212 = XftDrawCreate (XtDisplay (newmw),
2213 newmw->menu.windows [i].window,
2214 DefaultVisual (XtDisplay (newmw), screen),
2215 newmw->core.colormap);
2217 #endif
2218 #ifdef HAVE_X_I18N
2219 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2221 redisplay = True;
2222 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2224 #endif
2226 return redisplay;
2229 static void
2230 XlwMenuResize (Widget w)
2232 XlwMenuWidget mw = (XlwMenuWidget)w;
2234 if (mw->menu.popped_up)
2236 /* Don't allow the popup menu to resize itself. */
2237 mw->core.width = mw->menu.windows [0].width;
2238 mw->core.height = mw->menu.windows [0].height;
2239 mw->core.parent->core.width = mw->core.width;
2240 mw->core.parent->core.height = mw->core.height;
2242 else
2244 mw->menu.windows [0].width = mw->core.width;
2245 mw->menu.windows [0].height = mw->core.height;
2246 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2250 \f/* Action procedures */
2251 static void
2252 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2254 widget_value* val;
2255 int level;
2257 if (!map_event_to_widget_value (mw, ev, &val, &level))
2258 pop_new_stack_if_no_contents (mw);
2259 else
2260 set_new_state (mw, val, level);
2261 remap_menubar (mw);
2263 /* Sync with the display. Makes it feel better on X terms. */
2264 XSync (XtDisplay (mw), False);
2267 static void
2268 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2270 int x = ev->x_root;
2271 int y = ev->y_root;
2272 int state = ev->state;
2273 XMotionEvent oldev = *ev;
2275 /* allow motion events to be generated again */
2276 if (ev->is_hint
2277 && XQueryPointer (XtDisplay (mw), ev->window,
2278 &ev->root, &ev->subwindow,
2279 &ev->x_root, &ev->y_root,
2280 &ev->x, &ev->y,
2281 &ev->state)
2282 && ev->state == state
2283 && (ev->x_root != x || ev->y_root != y))
2284 handle_single_motion_event (mw, ev);
2285 else
2286 handle_single_motion_event (mw, &oldev);
2289 static void
2290 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2292 XlwMenuWidget mw = (XlwMenuWidget)w;
2294 if (!mw->menu.popped_up)
2296 menu_post_event = *ev;
2297 /* If event is set to CurrentTime, get the last known time stamp.
2298 This is for calculating if (popup) menus should stay up after
2299 a fast click. */
2300 if (menu_post_event.xbutton.time == CurrentTime)
2301 menu_post_event.xbutton.time
2302 = XtLastTimestampProcessed (XtDisplay (w));
2304 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2306 else
2308 /* If we push a button while the menu is posted semipermanently,
2309 releasing the button should always pop the menu down. */
2310 next_release_must_exit = 1;
2312 /* notes the absolute position of the menubar window */
2313 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2314 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2316 /* handles the down like a move, slots are compatible */
2317 ev->xmotion.is_hint = 0;
2318 handle_motion_event (mw, &ev->xmotion);
2322 static void
2323 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2325 XlwMenuWidget mw = (XlwMenuWidget)w;
2326 if (mw->menu.popped_up)
2327 handle_motion_event (mw, &ev->xmotion);
2330 /* Do nothing.
2331 This is how we handle presses and releases of modifier keys. */
2332 static void
2333 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2337 static widget_value *
2338 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2340 widget_value *current = item;
2341 enum menu_separator separator;
2343 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2344 || (skip_titles && !current->call_data && !current->contents))
2345 if (current->next)
2346 current=current->next;
2347 else
2348 return NULL;
2350 return current;
2353 static widget_value *
2354 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2356 widget_value *current = item;
2357 enum menu_separator separator;
2359 while (current->next && (current=current->next) &&
2360 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2361 || (skip_titles && !current->call_data && !current->contents)))
2364 if (current == item)
2366 if (mw->menu.old_depth < 2)
2367 return current;
2368 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2370 while (lw_separator_p (current->name, &separator, 0)
2371 || !current->enabled
2372 || (skip_titles && !current->call_data
2373 && !current->contents))
2375 if (current->next)
2376 current=current->next;
2378 if (current == item)
2379 break;
2384 return current;
2387 static widget_value *
2388 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2390 widget_value *current = item;
2391 widget_value *prev = item;
2393 while ((current=find_next_selectable (mw, current, skip_titles))
2394 != item)
2396 if (prev == current)
2397 break;
2398 prev=current;
2401 return prev;
2404 static void
2405 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2407 XlwMenuWidget mw = (XlwMenuWidget) w;
2408 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2409 int popup_menu_p = mw->menu.top_depth == 1;
2411 /* Inside top-level menu-bar? */
2412 if (mw->menu.old_depth == mw->menu.top_depth)
2413 /* When <down> in the menu-bar is pressed, display the corresponding
2414 sub-menu and select the first selectable menu item there.
2415 If this is a popup menu, skip title item of the popup. */
2416 set_new_state (mw,
2417 find_first_selectable (mw,
2418 selected_item->contents,
2419 popup_menu_p),
2420 mw->menu.old_depth);
2421 else
2422 /* Highlight next possible (enabled and not separator) menu item. */
2423 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2424 mw->menu.old_depth - 1);
2426 remap_menubar (mw);
2429 static void
2430 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2432 XlwMenuWidget mw = (XlwMenuWidget) w;
2433 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2434 int popup_menu_p = mw->menu.top_depth == 1;
2436 /* Inside top-level menu-bar? */
2437 if (mw->menu.old_depth == mw->menu.top_depth)
2439 /* FIXME: this is tricky. <up> in the menu-bar should select the
2440 last selectable item in the list. So we select the first
2441 selectable one and find the previous selectable item. Is there
2442 a better way? */
2443 /* If this is a popup menu, skip title item of the popup. */
2444 set_new_state (mw,
2445 find_first_selectable (mw,
2446 selected_item->contents,
2447 popup_menu_p),
2448 mw->menu.old_depth);
2449 remap_menubar (mw);
2450 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2451 set_new_state (mw,
2452 find_prev_selectable (mw,
2453 selected_item,
2454 popup_menu_p),
2455 mw->menu.old_depth - 1);
2457 else
2458 /* Highlight previous (enabled and not separator) menu item. */
2459 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2460 mw->menu.old_depth - 1);
2462 remap_menubar (mw);
2465 void
2466 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2468 XlwMenuWidget mw = (XlwMenuWidget) w;
2469 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2471 /* Inside top-level menu-bar? */
2472 if (mw->menu.old_depth == mw->menu.top_depth)
2473 /* When <left> in the menu-bar is pressed, display the previous item on
2474 the menu-bar. If the current item is the first one, highlight the
2475 last item in the menubar (probably Help). */
2476 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2477 mw->menu.old_depth - 1);
2478 else if (mw->menu.old_depth == 1
2479 && selected_item->contents) /* Is this menu item expandable? */
2481 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2482 remap_menubar (mw);
2483 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2484 if (!selected_item->enabled && find_first_selectable (mw,
2485 selected_item,
2487 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2488 mw->menu.old_depth - 1);
2491 else
2493 pop_new_stack_if_no_contents (mw);
2494 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2495 mw->menu.old_depth - 2);
2498 remap_menubar (mw);
2501 void
2502 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2504 XlwMenuWidget mw = (XlwMenuWidget) w;
2505 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2507 /* Inside top-level menu-bar? */
2508 if (mw->menu.old_depth == mw->menu.top_depth)
2509 /* When <right> in the menu-bar is pressed, display the next item on
2510 the menu-bar. If the current item is the last one, highlight the
2511 first item (probably File). */
2512 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2513 mw->menu.old_depth - 1);
2514 else if (selected_item->contents) /* Is this menu item expandable? */
2516 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2517 remap_menubar (mw);
2518 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2519 if (!selected_item->enabled && find_first_selectable (mw,
2520 selected_item,
2522 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2523 mw->menu.old_depth - 1);
2525 else
2527 pop_new_stack_if_no_contents (mw);
2528 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2529 mw->menu.old_depth - 2);
2532 remap_menubar (mw);
2535 /* Handle key press and release events while menu is popped up.
2536 Our action is to get rid of the menu. */
2537 static void
2538 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2540 XlwMenuWidget mw = (XlwMenuWidget)w;
2542 /* Pop down everything. */
2543 mw->menu.new_depth = 1;
2544 remap_menubar (mw);
2546 if (mw->menu.popped_up)
2548 mw->menu.popped_up = False;
2549 ungrab_all ((Widget)mw, ev->xmotion.time);
2550 if (XtIsShell (XtParent ((Widget) mw)))
2551 XtPopdown (XtParent ((Widget) mw));
2552 else
2554 XtRemoveGrab ((Widget) mw);
2555 display_menu (mw, 0, False, NULL, NULL, NULL);
2559 /* callback */
2560 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2563 static void
2564 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2566 XlwMenuWidget mw = (XlwMenuWidget)w;
2567 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2569 /* If user releases the button quickly, without selecting anything,
2570 after the initial down-click that brought the menu up,
2571 do nothing. */
2572 if ((selected_item == 0
2573 || ((widget_value *) selected_item)->call_data == 0)
2574 && !next_release_must_exit
2575 && (ev->xbutton.time - menu_post_event.xbutton.time
2576 < XtGetMultiClickTime (XtDisplay (w))))
2577 return;
2579 /* pop down everything. */
2580 mw->menu.new_depth = 1;
2581 remap_menubar (mw);
2583 if (mw->menu.popped_up)
2585 mw->menu.popped_up = False;
2586 ungrab_all ((Widget)mw, ev->xmotion.time);
2587 if (XtIsShell (XtParent ((Widget) mw)))
2588 XtPopdown (XtParent ((Widget) mw));
2589 else
2591 XtRemoveGrab ((Widget) mw);
2592 display_menu (mw, 0, False, NULL, NULL, NULL);
2596 /* callback */
2597 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2601 \f/* Special code to pop-up a menu */
2602 static void
2603 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2605 int x = event->x_root;
2606 int y = event->y_root;
2607 int w;
2608 int h;
2609 int borderwidth = mw->menu.shadow_thickness;
2610 Screen* screen = XtScreen (mw);
2611 Display *display = XtDisplay (mw);
2613 next_release_must_exit = 0;
2615 mw->menu.inside_entry = NULL;
2616 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2618 if (XtIsShell (XtParent ((Widget)mw)))
2619 size_menu (mw, 0);
2621 w = mw->menu.windows [0].width;
2622 h = mw->menu.windows [0].height;
2624 x -= borderwidth;
2625 y -= borderwidth;
2626 if (x < borderwidth)
2627 x = borderwidth;
2628 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2629 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2630 if (y < borderwidth)
2631 y = borderwidth;
2632 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2633 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2635 mw->menu.popped_up = True;
2636 if (XtIsShell (XtParent ((Widget)mw)))
2638 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2639 XtParent ((Widget)mw)->core.border_width);
2640 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2641 display_menu (mw, 0, False, NULL, NULL, NULL);
2642 mw->menu.windows [0].x = x + borderwidth;
2643 mw->menu.windows [0].y = y + borderwidth;
2644 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2646 else
2648 XEvent *ev = (XEvent *) event;
2650 XtAddGrab ((Widget) mw, True, True);
2652 /* notes the absolute position of the menubar window */
2653 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2654 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2655 mw->menu.top_depth = 2;
2658 #ifdef emacs
2659 x_catch_errors (display);
2660 #endif
2661 if (XtGrabPointer ((Widget)mw, False,
2662 (PointerMotionMask
2663 | PointerMotionHintMask
2664 | ButtonReleaseMask
2665 | ButtonPressMask),
2666 GrabModeAsync, GrabModeAsync, None,
2667 mw->menu.cursor_shape,
2668 event->time) == Success)
2670 if (! GRAB_KEYBOARD
2671 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2672 GrabModeAsync, event->time) == Success)
2674 XtSetKeyboardFocus((Widget)mw, None);
2675 pointer_grabbed = 1;
2677 else
2678 XtUngrabPointer ((Widget)mw, event->time);
2681 #ifdef emacs
2682 if (x_had_errors_p (display))
2684 pointer_grabbed = 0;
2685 XtUngrabPointer ((Widget)mw, event->time);
2687 x_uncatch_errors ();
2688 #endif
2690 ((XMotionEvent*)event)->is_hint = 0;
2691 handle_motion_event (mw, (XMotionEvent*)event);
2694 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2695 (do not change this comment) */