* keyboard.c (parse_modifiers_uncached, parse_modifiers):
[emacs.git] / lwlib / xlwmenu.c
blob5b97f2bf999ed7bc3175c85aa85cb58d896689ca
1 /* Implements a lightweight menubar widget.
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2011 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 char
75 xlwMenuTranslations [] =
76 "<BtnDown>: start()\n\
77 <Motion>: drag()\n\
78 <BtnUp>: select()\n\
79 <Key>Shift_L: nothing()\n\
80 <Key>Shift_R: nothing()\n\
81 <Key>Meta_L: nothing()\n\
82 <Key>Meta_R: nothing()\n\
83 <Key>Control_L: nothing()\n\
84 <Key>Control_R: nothing()\n\
85 <Key>Hyper_L: nothing()\n\
86 <Key>Hyper_R: nothing()\n\
87 <Key>Super_L: nothing()\n\
88 <Key>Super_R: nothing()\n\
89 <Key>Alt_L: nothing()\n\
90 <Key>Alt_R: nothing()\n\
91 <Key>Caps_Lock: nothing()\n\
92 <Key>Shift_Lock: nothing()\n\
93 <KeyUp>Shift_L: nothing()\n\
94 <KeyUp>Shift_R: nothing()\n\
95 <KeyUp>Meta_L: nothing()\n\
96 <KeyUp>Meta_R: nothing()\n\
97 <KeyUp>Control_L: nothing()\n\
98 <KeyUp>Control_R: nothing()\n\
99 <KeyUp>Hyper_L: nothing()\n\
100 <KeyUp>Hyper_R: nothing()\n\
101 <KeyUp>Super_L: nothing()\n\
102 <KeyUp>Super_R: nothing()\n\
103 <KeyUp>Alt_L: nothing()\n\
104 <KeyUp>Alt_R: nothing()\n\
105 <KeyUp>Caps_Lock: nothing()\n\
106 <KeyUp>Shift_Lock:nothing()\n\
107 <Key>Return: select()\n\
108 <Key>Down: down()\n\
109 <Key>Up: up()\n\
110 <Key>Left: left()\n\
111 <Key>Right: right()\n\
112 <Key>: key()\n\
113 <KeyUp>: key()\n\
116 /* FIXME: Space should toggle toggleable menu item but not remove the menu
117 so you can toggle the next one without entering the menu again. */
119 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
121 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
123 #define offset(field) XtOffset(XlwMenuWidget, field)
124 static XtResource
125 xlwMenuResources[] =
127 #ifdef HAVE_X_I18N
128 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
129 offset(menu.fontSet), XtRFontSet, NULL},
130 #endif
131 #ifdef HAVE_XFT
132 #define DEFAULT_FONTNAME "Sans-10"
133 #else
134 #define DEFAULT_FONTNAME "XtDefaultFont"
135 #endif
136 {XtNfont, XtCFont, XtRString, sizeof(String),
137 offset(menu.fontName), XtRString, DEFAULT_FONTNAME },
138 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
139 offset(menu.foreground), XtRString, "XtDefaultForeground"},
140 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
141 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
142 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
143 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
144 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
145 offset(menu.margin), XtRImmediate, (XtPointer)1},
146 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
147 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
148 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
149 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
150 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
151 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
153 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
154 sizeof (Dimension), offset (menu.shadow_thickness),
155 XtRImmediate, (XtPointer)1},
156 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
157 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
158 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
159 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
160 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
161 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
162 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
163 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
165 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
166 offset(menu.open), XtRCallback, (XtPointer)NULL},
167 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
168 offset(menu.select), XtRCallback, (XtPointer)NULL},
169 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
170 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
171 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
172 offset(menu.enter), XtRCallback, (XtPointer)NULL},
173 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
174 offset(menu.leave), XtRCallback, (XtPointer)NULL},
175 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
176 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
177 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
178 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
179 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
180 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
182 #undef offset
184 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
185 ArgList args, Cardinal *num_args);
186 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
187 static void XlwMenuResize(Widget w);
188 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
189 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
190 static void XlwMenuDestroy(Widget w);
191 static void XlwMenuClassInitialize(void);
192 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
193 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
194 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
195 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
196 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
197 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
198 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
199 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
200 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
201 static int separator_height (enum menu_separator);
202 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
203 static void abort_gracefully (Widget w) NO_RETURN;
205 static XtActionsRec
206 xlwMenuActionsList [] =
208 {"start", Start},
209 {"drag", Drag},
210 {"down", Down},
211 {"up", Up},
212 {"left", Left},
213 {"right", Right},
214 {"select", Select},
215 {"key", Key},
216 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
217 {"nothing", Nothing},
220 #define SuperClass ((CoreWidgetClass)&coreClassRec)
222 XlwMenuClassRec xlwMenuClassRec =
224 { /* CoreClass fields initialization */
225 (WidgetClass) SuperClass, /* superclass */
226 "XlwMenu", /* class_name */
227 sizeof(XlwMenuRec), /* size */
228 XlwMenuClassInitialize, /* class_initialize */
229 NULL, /* class_part_initialize */
230 FALSE, /* class_inited */
231 XlwMenuInitialize, /* initialize */
232 NULL, /* initialize_hook */
233 XlwMenuRealize, /* realize */
234 xlwMenuActionsList, /* actions */
235 XtNumber(xlwMenuActionsList), /* num_actions */
236 xlwMenuResources, /* resources */
237 XtNumber(xlwMenuResources), /* resource_count */
238 NULLQUARK, /* xrm_class */
239 TRUE, /* compress_motion */
240 XtExposeCompressMaximal, /* compress_exposure */
241 TRUE, /* compress_enterleave */
242 FALSE, /* visible_interest */
243 XlwMenuDestroy, /* destroy */
244 XlwMenuResize, /* resize */
245 XlwMenuRedisplay, /* expose */
246 XlwMenuSetValues, /* set_values */
247 NULL, /* set_values_hook */
248 XtInheritSetValuesAlmost, /* set_values_almost */
249 NULL, /* get_values_hook */
250 NULL, /* accept_focus */
251 XtVersion, /* version */
252 NULL, /* callback_private */
253 xlwMenuTranslations, /* tm_table */
254 XtInheritQueryGeometry, /* query_geometry */
255 XtInheritDisplayAccelerator, /* display_accelerator */
256 NULL /* extension */
257 }, /* XlwMenuClass fields initialization */
259 0 /* dummy */
263 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
265 int submenu_destroyed;
267 /* For debug, if installation-directory is non-nil this is not an installed
268 Emacs. In that case we do not grab the keyboard to make it easier to
269 debug. */
270 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
272 static int next_release_must_exit;
274 \f/* Utilities */
276 /* Ungrab pointer and keyboard */
277 static void
278 ungrab_all (Widget w, Time ungrabtime)
280 XtUngrabPointer (w, ungrabtime);
281 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
284 /* Like abort, but remove grabs from widget W before. */
286 static void
287 abort_gracefully (Widget w)
289 if (XtIsShell (XtParent (w)))
290 XtRemoveGrab (w);
291 ungrab_all (w, CurrentTime);
292 abort ();
295 static void
296 push_new_stack (XlwMenuWidget mw, widget_value *val)
298 if (!mw->menu.new_stack)
300 mw->menu.new_stack_length = 10;
301 mw->menu.new_stack =
302 (widget_value**)XtCalloc (mw->menu.new_stack_length,
303 sizeof (widget_value*));
305 else if (mw->menu.new_depth == mw->menu.new_stack_length)
307 mw->menu.new_stack_length *= 2;
308 mw->menu.new_stack =
309 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
310 mw->menu.new_stack_length * sizeof (widget_value*));
312 mw->menu.new_stack [mw->menu.new_depth++] = val;
315 static void
316 pop_new_stack_if_no_contents (XlwMenuWidget mw)
318 if (mw->menu.new_depth > 1)
320 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
321 mw->menu.new_depth -= 1;
325 static void
326 make_old_stack_space (XlwMenuWidget mw, int n)
328 if (!mw->menu.old_stack)
330 mw->menu.old_stack_length = 10;
331 mw->menu.old_stack =
332 (widget_value**)XtCalloc (mw->menu.old_stack_length,
333 sizeof (widget_value*));
335 else if (mw->menu.old_stack_length < n)
337 mw->menu.old_stack_length *= 2;
338 mw->menu.old_stack =
339 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
340 mw->menu.old_stack_length * sizeof (widget_value*));
344 \f/* Size code */
345 static int
346 string_width (XlwMenuWidget mw, char *s)
348 XCharStruct xcs;
349 int drop;
350 #ifdef HAVE_XFT
351 if (mw->menu.xft_font)
353 XGlyphInfo gi;
354 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
355 (FcChar8 *) s,
356 strlen (s), &gi);
357 return gi.width;
359 #endif
360 #ifdef HAVE_X_I18N
361 if (mw->menu.fontSet)
363 XRectangle ink, logical;
364 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
365 return logical.width;
367 #endif
369 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
370 return xcs.width;
374 #ifdef HAVE_XFT
375 #define MENU_FONT_HEIGHT(mw) \
376 ((mw)->menu.xft_font != NULL \
377 ? (mw)->menu.xft_font->height \
378 : ((mw)->menu.fontSet != NULL \
379 ? (mw)->menu.font_extents->max_logical_extent.height \
380 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
381 #define MENU_FONT_ASCENT(mw) \
382 ((mw)->menu.xft_font != NULL \
383 ? (mw)->menu.xft_font->ascent \
384 : ((mw)->menu.fontSet != NULL \
385 ? - (mw)->menu.font_extents->max_logical_extent.y \
386 : (mw)->menu.font->ascent))
387 #else
388 #ifdef HAVE_X_I18N
389 #define MENU_FONT_HEIGHT(mw) \
390 ((mw)->menu.fontSet != NULL \
391 ? (mw)->menu.font_extents->max_logical_extent.height \
392 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
393 #define MENU_FONT_ASCENT(mw) \
394 ((mw)->menu.fontSet != NULL \
395 ? - (mw)->menu.font_extents->max_logical_extent.y \
396 : (mw)->menu.font->ascent)
397 #else
398 #define MENU_FONT_HEIGHT(mw) \
399 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
400 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
401 #endif
402 #endif
404 static int
405 arrow_width (XlwMenuWidget mw)
407 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
410 /* Return the width of toggle buttons of widget MW. */
412 static int
413 toggle_button_width (XlwMenuWidget mw)
415 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
419 /* Return the width of radio buttons of widget MW. */
421 static int
422 radio_button_width (XlwMenuWidget mw)
424 return toggle_button_width (mw) * 1.41;
428 static XtResource
429 nameResource[] =
431 {"labelString", "LabelString", XtRString, sizeof(String),
432 0, XtRImmediate, 0},
435 static char*
436 resource_widget_value (XlwMenuWidget mw, widget_value *val)
438 if (!val->toolkit_data)
440 char* resourced_name = NULL;
441 char* complete_name;
442 XtGetSubresources ((Widget) mw,
443 (XtPointer) &resourced_name,
444 val->name, val->name,
445 nameResource, 1, NULL, 0);
446 if (!resourced_name)
447 resourced_name = val->name;
448 if (!val->value)
450 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
451 strcpy (complete_name, resourced_name);
453 else
455 int complete_length =
456 strlen (resourced_name) + strlen (val->value) + 2;
457 complete_name = XtMalloc (complete_length);
458 *complete_name = 0;
459 strcat (complete_name, resourced_name);
460 strcat (complete_name, " ");
461 strcat (complete_name, val->value);
464 val->toolkit_data = complete_name;
465 val->free_toolkit_data = True;
467 return (char*)val->toolkit_data;
470 /* Returns the sizes of an item */
471 static void
472 size_menu_item (XlwMenuWidget mw,
473 widget_value* val,
474 int horizontal_p,
475 int* label_width,
476 int* rest_width,
477 int* button_width,
478 int* height)
480 enum menu_separator separator;
482 if (lw_separator_p (val->name, &separator, 0))
484 *height = separator_height (separator);
485 *label_width = 1;
486 *rest_width = 0;
487 *button_width = 0;
489 else
491 *height = MENU_FONT_HEIGHT (mw)
492 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
494 *label_width =
495 string_width (mw, resource_widget_value (mw, val))
496 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
498 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
499 if (!horizontal_p)
501 if (val->contents)
502 /* Add width of the arrow displayed for submenus. */
503 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
504 else if (val->key)
505 /* Add width of key equivalent string. */
506 *rest_width += (string_width (mw, val->key)
507 + mw->menu.arrow_spacing);
509 if (val->button_type == BUTTON_TYPE_TOGGLE)
510 *button_width = (toggle_button_width (mw)
511 + mw->menu.horizontal_spacing);
512 else if (val->button_type == BUTTON_TYPE_RADIO)
513 *button_width = (radio_button_width (mw)
514 + mw->menu.horizontal_spacing);
519 static void
520 size_menu (XlwMenuWidget mw, int level)
522 int label_width = 0;
523 int rest_width = 0;
524 int button_width = 0;
525 int max_rest_width = 0;
526 int max_button_width = 0;
527 int height = 0;
528 int horizontal_p = mw->menu.horizontal && (level == 0);
529 widget_value* val;
530 window_state* ws;
532 if (level >= mw->menu.old_depth)
533 abort_gracefully ((Widget) mw);
535 ws = &mw->menu.windows [level];
536 ws->width = 0;
537 ws->height = 0;
538 ws->label_width = 0;
539 ws->button_width = 0;
541 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
543 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
544 &button_width, &height);
545 if (horizontal_p)
547 ws->width += label_width + rest_width;
548 if (height > ws->height)
549 ws->height = height;
551 else
553 if (label_width > ws->label_width)
554 ws->label_width = label_width;
555 if (rest_width > max_rest_width)
556 max_rest_width = rest_width;
557 if (button_width > max_button_width)
558 max_button_width = button_width;
559 ws->height += height;
563 if (horizontal_p)
564 ws->label_width = ws->button_width = 0;
565 else
567 ws->width = ws->label_width + max_rest_width + max_button_width;
568 ws->button_width = max_button_width;
571 ws->width += 2 * mw->menu.shadow_thickness;
572 ws->height += 2 * mw->menu.shadow_thickness;
573 ws->max_rest_width = max_rest_width;
575 if (horizontal_p)
577 ws->width += 2 * mw->menu.margin;
578 ws->height += 2 * mw->menu.margin;
583 \f/* Display code */
585 static void
586 draw_arrow (XlwMenuWidget mw,
587 Window window,
588 GC gc,
589 int x,
590 int y,
591 int width,
592 int down_p)
594 Display *dpy = XtDisplay (mw);
595 GC top_gc = mw->menu.shadow_top_gc;
596 GC bottom_gc = mw->menu.shadow_bottom_gc;
597 int thickness = mw->menu.shadow_thickness;
598 int height = width;
599 XPoint pt[10];
600 /* alpha = atan (0.5)
601 factor = (1 + sin (alpha)) / cos (alpha) */
602 double factor = 1.62;
603 int thickness2 = thickness * factor;
605 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
607 if (down_p)
609 GC temp;
610 temp = top_gc;
611 top_gc = bottom_gc;
612 bottom_gc = temp;
615 pt[0].x = x;
616 pt[0].y = y + height;
617 pt[1].x = x + thickness;
618 pt[1].y = y + height - thickness2;
619 pt[2].x = x + thickness2;
620 pt[2].y = y + thickness2;
621 pt[3].x = x;
622 pt[3].y = y;
623 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
625 pt[0].x = x;
626 pt[0].y = y;
627 pt[1].x = x + thickness;
628 pt[1].y = y + thickness2;
629 pt[2].x = x + width - thickness2;
630 pt[2].y = y + height / 2;
631 pt[3].x = x + width;
632 pt[3].y = y + height / 2;
633 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
635 pt[0].x = x;
636 pt[0].y = y + height;
637 pt[1].x = x + thickness;
638 pt[1].y = y + height - thickness2;
639 pt[2].x = x + width - thickness2;
640 pt[2].y = y + height / 2;
641 pt[3].x = x + width;
642 pt[3].y = y + height / 2;
643 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
648 static void
649 draw_shadow_rectangle (XlwMenuWidget mw,
650 Window window,
651 int x,
652 int y,
653 int width,
654 int height,
655 int erase_p,
656 int down_p)
658 Display *dpy = XtDisplay (mw);
659 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
660 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
661 int thickness = mw->menu.shadow_thickness;
662 XPoint points [4];
664 if (!erase_p && down_p)
666 GC temp;
667 temp = top_gc;
668 top_gc = bottom_gc;
669 bottom_gc = temp;
672 points [0].x = x;
673 points [0].y = y;
674 points [1].x = x + width;
675 points [1].y = y;
676 points [2].x = x + width - thickness;
677 points [2].y = y + thickness;
678 points [3].x = x;
679 points [3].y = y + thickness;
680 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
681 points [0].x = x;
682 points [0].y = y + thickness;
683 points [1].x = x;
684 points [1].y = y + height;
685 points [2].x = x + thickness;
686 points [2].y = y + height - thickness;
687 points [3].x = x + thickness;
688 points [3].y = y + thickness;
689 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
690 points [0].x = x + width;
691 points [0].y = y;
692 points [1].x = x + width - thickness;
693 points [1].y = y + thickness;
694 points [2].x = x + width - thickness;
695 points [2].y = y + height - thickness;
696 points [3].x = x + width;
697 points [3].y = y + height - thickness;
698 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
699 points [0].x = x;
700 points [0].y = y + height;
701 points [1].x = x + width;
702 points [1].y = y + height;
703 points [2].x = x + width;
704 points [2].y = y + height - thickness;
705 points [3].x = x + thickness;
706 points [3].y = y + height - thickness;
707 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
711 static void
712 draw_shadow_rhombus (XlwMenuWidget mw,
713 Window window,
714 int x,
715 int y,
716 int width,
717 int height,
718 int erase_p,
719 int down_p)
721 Display *dpy = XtDisplay (mw);
722 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
723 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
724 int thickness = mw->menu.shadow_thickness;
725 XPoint points [4];
727 if (!erase_p && down_p)
729 GC temp;
730 temp = top_gc;
731 top_gc = bottom_gc;
732 bottom_gc = temp;
735 points [0].x = x;
736 points [0].y = y + height / 2;
737 points [1].x = x + thickness;
738 points [1].y = y + height / 2;
739 points [2].x = x + width / 2;
740 points [2].y = y + thickness;
741 points [3].x = x + width / 2;
742 points [3].y = y;
743 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
744 points [0].x = x + width / 2;
745 points [0].y = y;
746 points [1].x = x + width / 2;
747 points [1].y = y + thickness;
748 points [2].x = x + width - thickness;
749 points [2].y = y + height / 2;
750 points [3].x = x + width;
751 points [3].y = y + height / 2;
752 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
753 points [0].x = x;
754 points [0].y = y + height / 2;
755 points [1].x = x + thickness;
756 points [1].y = y + height / 2;
757 points [2].x = x + width / 2;
758 points [2].y = y + height - thickness;
759 points [3].x = x + width / 2;
760 points [3].y = y + height;
761 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
762 points [0].x = x + width / 2;
763 points [0].y = y + height;
764 points [1].x = x + width / 2;
765 points [1].y = y + height - thickness;
766 points [2].x = x + width - thickness;
767 points [2].y = y + height / 2;
768 points [3].x = x + width;
769 points [3].y = y + height / 2;
770 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
774 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
775 top-left corner of the menu item. SELECTED_P non-zero means the
776 toggle button is selected. */
778 static void
779 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
781 int width, height;
783 width = toggle_button_width (mw);
784 height = width;
785 x += mw->menu.horizontal_spacing;
786 y += (MENU_FONT_ASCENT (mw) - height) / 2;
787 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
791 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
792 top-left corner of the menu item. SELECTED_P non-zero means the
793 toggle button is selected. */
795 static void
796 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
798 int width, height;
800 width = radio_button_width (mw);
801 height = width;
802 x += mw->menu.horizontal_spacing;
803 y += (MENU_FONT_ASCENT (mw) - height) / 2;
804 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
808 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
809 top-left corner of the menu item. WIDTH is the width of the
810 separator to draw. TYPE is the separator type. */
812 static void
813 draw_separator (XlwMenuWidget mw,
814 Window window,
815 int x,
816 int y,
817 int width,
818 enum menu_separator type)
820 Display *dpy = XtDisplay (mw);
821 XGCValues xgcv;
823 switch (type)
825 case SEPARATOR_NO_LINE:
826 break;
828 case SEPARATOR_SINGLE_LINE:
829 XDrawLine (dpy, window, mw->menu.foreground_gc,
830 x, y, x + width, y);
831 break;
833 case SEPARATOR_DOUBLE_LINE:
834 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
835 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
836 break;
838 case SEPARATOR_SINGLE_DASHED_LINE:
839 xgcv.line_style = LineOnOffDash;
840 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
841 XDrawLine (dpy, window, mw->menu.foreground_gc,
842 x, y, x + width, y);
843 xgcv.line_style = LineSolid;
844 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
845 break;
847 case SEPARATOR_DOUBLE_DASHED_LINE:
848 draw_separator (mw, window, x, y, width,
849 SEPARATOR_SINGLE_DASHED_LINE);
850 draw_separator (mw, window, x, y + 2, width,
851 SEPARATOR_SINGLE_DASHED_LINE);
852 break;
854 case SEPARATOR_SHADOW_ETCHED_IN:
855 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
856 x, y, x + width, y);
857 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
858 x, y + 1, x + width, y + 1);
859 break;
861 case SEPARATOR_SHADOW_ETCHED_OUT:
862 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
863 x, y, x + width, y);
864 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
865 x, y + 1, x + width, y + 1);
866 break;
868 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
869 xgcv.line_style = LineOnOffDash;
870 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
871 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
872 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
873 xgcv.line_style = LineSolid;
874 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
875 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
876 break;
878 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
879 xgcv.line_style = LineOnOffDash;
880 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
881 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
882 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
883 xgcv.line_style = LineSolid;
884 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
885 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
886 break;
888 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
889 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
890 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
891 break;
893 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
894 draw_separator (mw, window, x, y, width,
895 SEPARATOR_SHADOW_ETCHED_OUT);
896 draw_separator (mw, window, x, y + 3, width,
897 SEPARATOR_SHADOW_ETCHED_OUT);
898 break;
900 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
901 xgcv.line_style = LineOnOffDash;
902 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
903 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
904 draw_separator (mw, window, x, y, width,
905 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
906 xgcv.line_style = LineSolid;
907 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
908 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
909 break;
911 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
912 xgcv.line_style = LineOnOffDash;
913 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
914 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
915 draw_separator (mw, window, x, y, width,
916 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
917 xgcv.line_style = LineSolid;
918 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
919 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
920 break;
922 default:
923 abort ();
928 /* Return the pixel height of menu separator SEPARATOR. */
930 static int
931 separator_height (enum menu_separator separator)
933 switch (separator)
935 case SEPARATOR_NO_LINE:
936 return 2;
938 case SEPARATOR_SINGLE_LINE:
939 case SEPARATOR_SINGLE_DASHED_LINE:
940 return 1;
942 case SEPARATOR_DOUBLE_LINE:
943 case SEPARATOR_DOUBLE_DASHED_LINE:
944 return 3;
946 case SEPARATOR_SHADOW_ETCHED_IN:
947 case SEPARATOR_SHADOW_ETCHED_OUT:
948 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
949 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
950 return 2;
952 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
953 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
954 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
955 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
956 return 5;
958 default:
959 abort ();
964 /* Display the menu item and increment where.x and where.y to show how large
965 the menu item was. */
967 static void
968 display_menu_item (XlwMenuWidget mw,
969 widget_value* val,
970 window_state* ws,
971 XPoint* where,
972 Boolean highlighted_p,
973 Boolean horizontal_p,
974 Boolean just_compute_p)
976 GC deco_gc;
977 GC text_gc;
978 int font_height = MENU_FONT_HEIGHT (mw);
979 int font_ascent = MENU_FONT_ASCENT (mw);
980 int shadow = mw->menu.shadow_thickness;
981 int margin = mw->menu.margin;
982 int h_spacing = mw->menu.horizontal_spacing;
983 int v_spacing = mw->menu.vertical_spacing;
984 int label_width;
985 int rest_width;
986 int button_width;
987 int height;
988 int width;
989 enum menu_separator separator;
990 int separator_p = lw_separator_p (val->name, &separator, 0);
991 #ifdef HAVE_XFT
992 XftColor *xftfg;
993 #endif
995 /* compute the sizes of the item */
996 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
997 &button_width, &height);
999 if (horizontal_p)
1000 width = label_width + rest_width;
1001 else
1003 label_width = ws->label_width;
1004 width = ws->width - 2 * shadow;
1007 /* Only highlight an enabled item that has a callback. */
1008 if (highlighted_p)
1009 if (!val->enabled || !(val->call_data || val->contents))
1010 highlighted_p = 0;
1012 /* do the drawing. */
1013 if (!just_compute_p)
1015 /* Add the shadow border of the containing menu */
1016 int x = where->x + shadow;
1017 int y = where->y + shadow;
1019 if (horizontal_p)
1021 x += margin;
1022 y += margin;
1025 /* pick the foreground and background GC. */
1026 if (val->enabled)
1027 text_gc = mw->menu.foreground_gc;
1028 else
1029 text_gc = mw->menu.disabled_gc;
1030 deco_gc = mw->menu.foreground_gc;
1031 #ifdef HAVE_XFT
1032 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1033 #endif
1035 if (separator_p)
1037 draw_separator (mw, ws->pixmap, x, y, width, separator);
1039 else
1041 int x_offset = x + h_spacing + shadow;
1042 char* display_string = resource_widget_value (mw, val);
1043 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1044 False);
1046 /* Deal with centering a menu title. */
1047 if (!horizontal_p && !val->contents && !val->call_data)
1049 int l = string_width (mw, display_string);
1051 if (width > l)
1052 x_offset = (width - l) >> 1;
1054 else if (!horizontal_p && ws->button_width)
1055 x_offset += ws->button_width;
1058 #ifdef HAVE_XFT
1059 if (ws->xft_draw)
1061 int draw_y = y + v_spacing + shadow;
1062 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1063 mw->menu.xft_font,
1064 x_offset, draw_y + font_ascent,
1065 (unsigned char *) display_string,
1066 strlen (display_string));
1068 else
1069 #endif
1070 #ifdef HAVE_X_I18N
1071 if (mw->menu.fontSet)
1072 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1073 text_gc, x_offset,
1074 y + v_spacing + shadow + font_ascent,
1075 display_string, strlen (display_string));
1076 else
1077 #endif
1078 XDrawString (XtDisplay (mw), ws->pixmap,
1079 text_gc, x_offset,
1080 y + v_spacing + shadow + font_ascent,
1081 display_string, strlen (display_string));
1083 if (!horizontal_p)
1085 if (val->button_type == BUTTON_TYPE_TOGGLE)
1086 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1087 val->selected);
1088 else if (val->button_type == BUTTON_TYPE_RADIO)
1089 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1090 val->selected);
1092 if (val->contents)
1094 int a_w = arrow_width (mw);
1095 draw_arrow (mw, ws->pixmap, deco_gc,
1096 x + width - a_w
1097 - mw->menu.horizontal_spacing
1098 - mw->menu.shadow_thickness,
1099 y + v_spacing + shadow, a_w,
1100 highlighted_p);
1102 else if (val->key)
1104 #ifdef HAVE_XFT
1105 if (ws->xft_draw)
1107 int draw_x = ws->width - ws->max_rest_width
1108 + mw->menu.arrow_spacing;
1109 int draw_y = y + v_spacing + shadow + font_ascent;
1110 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1111 mw->menu.xft_font,
1112 draw_x, draw_y,
1113 (unsigned char *) val->key,
1114 strlen (val->key));
1116 else
1117 #endif
1118 #ifdef HAVE_X_I18N
1119 if (mw->menu.fontSet)
1120 XmbDrawString (XtDisplay (mw), ws->pixmap,
1121 mw->menu.fontSet,
1122 text_gc,
1123 x + label_width + mw->menu.arrow_spacing,
1124 y + v_spacing + shadow + font_ascent,
1125 val->key, strlen (val->key));
1126 else
1127 #endif
1128 XDrawString (XtDisplay (mw), ws->pixmap,
1129 text_gc,
1130 x + label_width + mw->menu.arrow_spacing,
1131 y + v_spacing + shadow + font_ascent,
1132 val->key, strlen (val->key));
1135 else
1137 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1138 mw->menu.background_gc,
1139 x + shadow, y + shadow,
1140 label_width + h_spacing - 1,
1141 font_height + 2 * v_spacing - 1);
1142 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1143 True, False);
1146 if (highlighted_p)
1147 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1148 False);
1152 where->x += width;
1153 where->y += height;
1156 static void
1157 display_menu (XlwMenuWidget mw,
1158 int level,
1159 Boolean just_compute_p,
1160 XPoint *highlighted_pos,
1161 XPoint *hit,
1162 widget_value **hit_return)
1164 widget_value* val;
1165 widget_value* following_item;
1166 window_state* ws;
1167 XPoint where;
1168 int horizontal_p = mw->menu.horizontal && (level == 0);
1169 int highlighted_p;
1170 int no_return = 0;
1171 enum menu_separator separator;
1173 if (level >= mw->menu.old_depth)
1174 abort_gracefully ((Widget) mw);
1176 if (level < mw->menu.old_depth - 1)
1177 following_item = mw->menu.old_stack [level + 1];
1178 else
1179 following_item = NULL;
1181 if (hit)
1182 *hit_return = NULL;
1184 where.x = 0;
1185 where.y = 0;
1187 ws = &mw->menu.windows [level];
1189 if (!just_compute_p)
1190 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1191 0, 0, ws->width, ws->height);
1193 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1195 highlighted_p = val == following_item;
1196 if (highlighted_p && highlighted_pos)
1198 if (horizontal_p)
1199 highlighted_pos->x = where.x;
1200 else
1201 highlighted_pos->y = where.y;
1204 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1205 just_compute_p);
1207 if (highlighted_p && highlighted_pos)
1209 if (horizontal_p)
1210 highlighted_pos->y = where.y;
1211 else
1212 highlighted_pos->x = where.x;
1215 if (hit
1216 && !*hit_return
1217 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1218 && !lw_separator_p (val->name, &separator, 0)
1219 && !no_return)
1221 if (val->enabled)
1222 *hit_return = val;
1223 else
1224 no_return = 1;
1225 if (mw->menu.inside_entry != val)
1227 if (mw->menu.inside_entry)
1228 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1229 (XtPointer) mw->menu.inside_entry);
1230 mw->menu.inside_entry = val;
1231 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1232 (XtPointer) mw->menu.inside_entry);
1236 if (horizontal_p)
1237 where.y = 0;
1238 else
1239 where.x = 0;
1242 if (!just_compute_p)
1244 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1245 False, False);
1246 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1247 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1251 \f/* Motion code */
1252 static void
1253 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1255 int i;
1257 mw->menu.new_depth = 0;
1258 for (i = 0; i < level; i++)
1259 push_new_stack (mw, mw->menu.old_stack [i]);
1260 push_new_stack (mw, val);
1263 static void
1264 expose_cb (Widget widget,
1265 XtPointer closure,
1266 XEvent* event,
1267 Boolean* continue_to_dispatch)
1269 XlwMenuWidget mw = (XlwMenuWidget) closure;
1270 int i;
1272 *continue_to_dispatch = False;
1273 for (i = 0; i < mw->menu.windows_length; ++i)
1274 if (mw->menu.windows [i].w == widget) break;
1275 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1276 display_menu (mw, i, False, NULL, NULL, NULL);
1279 static void
1280 set_window_type (Widget w, XlwMenuWidget mw)
1282 int popup_menu_p = mw->menu.top_depth == 1;
1283 Atom type = XInternAtom (XtDisplay (w),
1284 popup_menu_p
1285 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1286 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1287 False);
1289 XChangeProperty (XtDisplay (w), XtWindow (w),
1290 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1291 XA_ATOM, 32, PropModeReplace,
1292 (unsigned char *)&type, 1);
1296 static void
1297 make_windows_if_needed (XlwMenuWidget mw, int n)
1299 int i;
1300 int start_at;
1301 window_state* windows;
1303 if (mw->menu.windows_length >= n)
1304 return;
1306 if (!mw->menu.windows)
1308 mw->menu.windows =
1309 (window_state*)XtMalloc (n * sizeof (window_state));
1310 start_at = 0;
1312 else
1314 mw->menu.windows =
1315 (window_state*)XtRealloc ((char*)mw->menu.windows,
1316 n * sizeof (window_state));
1317 start_at = mw->menu.windows_length;
1319 mw->menu.windows_length = n;
1321 windows = mw->menu.windows;
1323 for (i = start_at; i < n; i++)
1325 Arg av[10];
1326 int ac = 0;
1327 windows [i].x = 0;
1328 windows [i].y = 0;
1329 windows [i].width = 1;
1330 windows [i].height = 1;
1331 windows [i].max_rest_width = 0;
1332 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1333 XtSetArg (av[ac], XtNheight, 1); ++ac;
1334 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1335 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1336 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1337 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1338 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1339 windows [i].w =
1340 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1341 (Widget) mw, av, ac);
1342 XtRealizeWidget (windows [i].w);
1343 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1344 windows [i].window = XtWindow (windows [i].w);
1345 windows [i].pixmap = None;
1346 #ifdef HAVE_XFT
1347 windows [i].xft_draw = 0;
1348 #endif
1349 set_window_type (windows [i].w, mw);
1351 XFlush (XtDisplay (mw));
1354 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1357 xlwmenu_window_p (Widget w, Window window)
1359 XlwMenuWidget mw = (XlwMenuWidget) w;
1360 int i;
1362 for (i = 0; i < mw->menu.windows_length; ++i)
1363 if (window == mw->menu.windows[i].window)
1364 break;
1366 return i < mw->menu.windows_length;
1369 /* Make the window fit in the screen */
1370 static void
1371 fit_to_screen (XlwMenuWidget mw,
1372 window_state *ws,
1373 window_state *previous_ws,
1374 Boolean horizontal_p)
1376 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1377 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1378 /* 1 if we are unable to avoid an overlap between
1379 this menu and the parent menu in the X dimension. */
1380 int horizontal_overlap = 0;
1382 if (ws->x < 0)
1383 ws->x = 0;
1384 else if (ws->x + ws->width > screen_width)
1386 if (!horizontal_p)
1387 /* The addition of shadow-thickness for a sub-menu's position is
1388 to reflect a similar adjustment when the menu is displayed to
1389 the right of the invoking menu-item; it makes the sub-menu
1390 look more `attached' to the menu-item. */
1391 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1392 else
1393 ws->x = screen_width - ws->width;
1394 if (ws->x < 0)
1396 ws->x = 0;
1397 horizontal_overlap = 1;
1400 /* If we overlap in X, try to avoid overlap in Y. */
1401 if (horizontal_overlap
1402 && ws->y < previous_ws->y + previous_ws->height
1403 && previous_ws->y < ws->y + ws->height)
1405 /* Put this menu right below or right above PREVIOUS_WS
1406 if there's room. */
1407 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1408 ws->y = previous_ws->y + previous_ws->height;
1409 else if (previous_ws->y - ws->height > 0)
1410 ws->y = previous_ws->y - ws->height;
1413 if (ws->y < 0)
1414 ws->y = 0;
1415 else if (ws->y + ws->height > screen_height)
1417 if (horizontal_p)
1418 ws->y = previous_ws->y - ws->height;
1419 else
1420 ws->y = screen_height - ws->height;
1421 if (ws->y < 0)
1422 ws->y = 0;
1426 static void
1427 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1429 if (ws->pixmap != None)
1431 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1432 ws->pixmap = None;
1434 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1435 ws->width, ws->height,
1436 DefaultDepthOfScreen (XtScreen (ws->w)));
1437 #ifdef HAVE_XFT
1438 if (ws->xft_draw)
1439 XftDrawDestroy (ws->xft_draw);
1440 if (mw->menu.xft_font)
1442 int screen = XScreenNumberOfScreen (mw->core.screen);
1443 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1444 ws->pixmap,
1445 DefaultVisual (XtDisplay (ws->w), screen),
1446 mw->core.colormap);
1448 else
1449 ws->xft_draw = 0;
1450 #endif
1453 /* Updates old_stack from new_stack and redisplays. */
1454 static void
1455 remap_menubar (XlwMenuWidget mw)
1457 int i;
1458 int last_same;
1459 XPoint selection_position;
1460 int old_depth = mw->menu.old_depth;
1461 int new_depth = mw->menu.new_depth;
1462 widget_value** old_stack;
1463 widget_value** new_stack;
1464 window_state* windows;
1465 widget_value* old_selection;
1466 widget_value* new_selection;
1468 /* Check that enough windows and old_stack are ready. */
1469 make_windows_if_needed (mw, new_depth);
1470 make_old_stack_space (mw, new_depth);
1471 windows = mw->menu.windows;
1472 old_stack = mw->menu.old_stack;
1473 new_stack = mw->menu.new_stack;
1475 /* compute the last identical different entry */
1476 for (i = 1; i < old_depth && i < new_depth; i++)
1477 if (old_stack [i] != new_stack [i])
1478 break;
1479 last_same = i - 1;
1481 /* Memorize the previously selected item to be able to refresh it */
1482 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1483 if (old_selection && !old_selection->enabled)
1484 old_selection = NULL;
1485 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1486 if (new_selection && !new_selection->enabled)
1487 new_selection = NULL;
1489 /* Call callback when the hightlighted item changes. */
1490 if (old_selection || new_selection)
1491 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1492 (XtPointer) new_selection);
1494 /* updates old_state from new_state. It has to be done now because
1495 display_menu (called below) uses the old_stack to know what to display. */
1496 for (i = last_same + 1; i < new_depth; i++)
1498 XtPopdown (mw->menu.windows [i].w);
1499 old_stack [i] = new_stack [i];
1501 mw->menu.old_depth = new_depth;
1503 /* refresh the last selection */
1504 selection_position.x = 0;
1505 selection_position.y = 0;
1506 display_menu (mw, last_same, new_selection == old_selection,
1507 &selection_position, NULL, NULL);
1509 /* Now place the new menus. */
1510 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1512 window_state *previous_ws = &windows[i - 1];
1513 window_state *ws = &windows[i];
1515 ws->x = (previous_ws->x + selection_position.x
1516 + mw->menu.shadow_thickness);
1517 if (mw->menu.horizontal && i == 1)
1518 ws->x += mw->menu.margin;
1520 #if 0
1521 if (!mw->menu.horizontal || i > 1)
1522 ws->x += mw->menu.shadow_thickness;
1523 #endif
1525 ws->y = (previous_ws->y + selection_position.y
1526 + mw->menu.shadow_thickness);
1527 if (mw->menu.horizontal && i == 1)
1528 ws->y += mw->menu.margin;
1530 size_menu (mw, i);
1532 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1534 create_pixmap_for_menu (ws, mw);
1535 XtMoveWidget (ws->w, ws->x, ws->y);
1536 XtPopup (ws->w, XtGrabNone);
1537 XtResizeWidget (ws->w, ws->width, ws->height,
1538 mw->core.border_width);
1539 XtResizeWindow (ws->w);
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 && mw->menu.font)
1617 xgcv.font = mw->menu.font->fid;
1618 mask |= GCFont;
1620 #else
1621 if (mw->menu.font)
1623 xgcv.font = mw->menu.font->fid;
1624 mask |= GCFont;
1626 #endif
1627 xgcv.foreground = mw->menu.foreground;
1628 xgcv.background = mw->core.background_pixel;
1629 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1631 xgcv.foreground = mw->menu.button_foreground;
1632 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1634 xgcv.background = mw->core.background_pixel;
1636 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1638 /* Allocate color for disabled menu-items. */
1639 mw->menu.disabled_foreground = mw->menu.foreground;
1640 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1641 scale = 2.3;
1642 else
1643 scale = 0.55;
1645 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1646 mw->core.colormap,
1647 &mw->menu.disabled_foreground,
1648 scale,
1649 0x8000);
1651 if (mw->menu.foreground == mw->menu.disabled_foreground
1652 || mw->core.background_pixel == mw->menu.disabled_foreground)
1654 /* Too few colors, use stipple. */
1655 xgcv.foreground = mw->menu.foreground;
1656 xgcv.fill_style = FillStippled;
1657 xgcv.stipple = mw->menu.gray_pixmap;
1658 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1659 | GCFillStyle | GCStipple, &xgcv);
1661 else
1663 /* Many colors available, use disabled pixel. */
1664 xgcv.foreground = mw->menu.disabled_foreground;
1665 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1668 xgcv.foreground = mw->menu.button_foreground;
1669 xgcv.background = mw->core.background_pixel;
1670 xgcv.fill_style = FillStippled;
1671 xgcv.stipple = mw->menu.gray_pixmap;
1672 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1673 | GCFillStyle | GCStipple, &xgcv);
1675 xgcv.foreground = mw->core.background_pixel;
1676 xgcv.background = mw->menu.foreground;
1677 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1680 static void
1681 release_drawing_gcs (XlwMenuWidget mw)
1683 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1684 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1685 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1686 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1687 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1688 /* let's get some segvs if we try to use these... */
1689 mw->menu.foreground_gc = (GC) -1;
1690 mw->menu.button_gc = (GC) -1;
1691 mw->menu.disabled_gc = (GC) -1;
1692 mw->menu.inactive_button_gc = (GC) -1;
1693 mw->menu.background_gc = (GC) -1;
1696 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1697 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1699 static void
1700 make_shadow_gcs (XlwMenuWidget mw)
1702 XGCValues xgcv;
1703 unsigned long pm = 0;
1704 Display *dpy = XtDisplay ((Widget) mw);
1705 Screen *screen = XtScreen ((Widget) mw);
1706 Colormap cmap = mw->core.colormap;
1707 XColor topc, botc;
1708 int top_frobbed = 0, bottom_frobbed = 0;
1710 mw->menu.free_top_shadow_color_p = 0;
1711 mw->menu.free_bottom_shadow_color_p = 0;
1713 if (mw->menu.top_shadow_color == -1)
1714 mw->menu.top_shadow_color = mw->core.background_pixel;
1715 else
1716 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1718 if (mw->menu.bottom_shadow_color == -1)
1719 mw->menu.bottom_shadow_color = mw->menu.foreground;
1720 else
1721 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1723 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1724 mw->menu.top_shadow_color == mw->menu.foreground)
1726 topc.pixel = mw->core.background_pixel;
1727 #ifdef emacs
1728 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1729 &topc.pixel,
1730 1.2, 0x8000))
1731 #else
1732 XQueryColor (dpy, cmap, &topc);
1733 /* don't overflow/wrap! */
1734 topc.red = MINL (65535, topc.red * 1.2);
1735 topc.green = MINL (65535, topc.green * 1.2);
1736 topc.blue = MINL (65535, topc.blue * 1.2);
1737 if (XAllocColor (dpy, cmap, &topc))
1738 #endif
1740 mw->menu.top_shadow_color = topc.pixel;
1741 mw->menu.free_top_shadow_color_p = 1;
1742 top_frobbed = 1;
1745 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1746 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1748 botc.pixel = mw->core.background_pixel;
1749 #ifdef emacs
1750 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1751 &botc.pixel,
1752 0.6, 0x4000))
1753 #else
1754 XQueryColor (dpy, cmap, &botc);
1755 botc.red *= 0.6;
1756 botc.green *= 0.6;
1757 botc.blue *= 0.6;
1758 if (XAllocColor (dpy, cmap, &botc))
1759 #endif
1761 mw->menu.bottom_shadow_color = botc.pixel;
1762 mw->menu.free_bottom_shadow_color_p = 1;
1763 bottom_frobbed = 1;
1767 if (top_frobbed && bottom_frobbed)
1769 if (topc.pixel == botc.pixel)
1771 if (botc.pixel == mw->menu.foreground)
1773 if (mw->menu.free_top_shadow_color_p)
1775 x_free_dpy_colors (dpy, screen, cmap,
1776 &mw->menu.top_shadow_color, 1);
1777 mw->menu.free_top_shadow_color_p = 0;
1779 mw->menu.top_shadow_color = mw->core.background_pixel;
1781 else
1783 if (mw->menu.free_bottom_shadow_color_p)
1785 x_free_dpy_colors (dpy, screen, cmap,
1786 &mw->menu.bottom_shadow_color, 1);
1787 mw->menu.free_bottom_shadow_color_p = 0;
1789 mw->menu.bottom_shadow_color = mw->menu.foreground;
1794 if (!mw->menu.top_shadow_pixmap &&
1795 mw->menu.top_shadow_color == mw->core.background_pixel)
1797 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1798 if (mw->menu.free_top_shadow_color_p)
1800 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1801 mw->menu.free_top_shadow_color_p = 0;
1803 mw->menu.top_shadow_color = mw->menu.foreground;
1805 if (!mw->menu.bottom_shadow_pixmap &&
1806 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1808 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1809 if (mw->menu.free_bottom_shadow_color_p)
1811 x_free_dpy_colors (dpy, screen, cmap,
1812 &mw->menu.bottom_shadow_color, 1);
1813 mw->menu.free_bottom_shadow_color_p = 0;
1815 mw->menu.bottom_shadow_color = mw->menu.foreground;
1818 xgcv.fill_style = FillStippled;
1819 xgcv.foreground = mw->menu.top_shadow_color;
1820 xgcv.stipple = mw->menu.top_shadow_pixmap;
1821 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1822 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1824 xgcv.foreground = mw->menu.bottom_shadow_color;
1825 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1826 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1827 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1831 static void
1832 release_shadow_gcs (XlwMenuWidget mw)
1834 Display *dpy = XtDisplay ((Widget) mw);
1835 Screen *screen = XtScreen ((Widget) mw);
1836 Colormap cmap = mw->core.colormap;
1837 Pixel px[2];
1838 int i = 0;
1840 if (mw->menu.free_top_shadow_color_p)
1841 px[i++] = mw->menu.top_shadow_color;
1842 if (mw->menu.free_bottom_shadow_color_p)
1843 px[i++] = mw->menu.bottom_shadow_color;
1844 if (i > 0)
1845 x_free_dpy_colors (dpy, screen, cmap, px, i);
1847 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1848 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1851 #ifdef HAVE_XFT
1852 static XftFont *
1853 getDefaultXftFont (XlwMenuWidget mw)
1855 int screen = XScreenNumberOfScreen (mw->core.screen);
1856 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1859 static int
1860 openXftFont (XlwMenuWidget mw)
1862 char *fname = mw->menu.fontName;
1864 mw->menu.xft_font = 0;
1865 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1867 if (fname && strcmp (fname, "none") != 0)
1869 int screen = XScreenNumberOfScreen (mw->core.screen);
1870 int len = strlen (fname), i = len-1;
1871 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1872 while (i > 0 && isdigit (fname[i]))
1873 --i;
1874 if (fname[i] == ' ')
1876 fname = xstrdup (mw->menu.fontName);
1877 fname[i] = '-';
1880 mw->menu.font = XLoadQueryFont (XtDisplay (mw), fname);
1881 if (!mw->menu.font)
1883 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1884 if (!mw->menu.xft_font)
1886 fprintf (stderr, "Can't find font '%s'\n", fname);
1887 mw->menu.xft_font = getDefaultXftFont (mw);
1892 if (fname != mw->menu.fontName) free (fname);
1894 return mw->menu.xft_font != 0;
1896 #endif
1898 static void
1899 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1901 /* Get the GCs and the widget size */
1902 XlwMenuWidget mw = (XlwMenuWidget) w;
1903 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1904 Display* display = XtDisplay (mw);
1906 #if 0
1907 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1909 /* _XtCreate is freeing the object that was passed to us,
1910 so make a copy that we will actually keep. */
1911 memcpy (tem, mw->menu.contents, sizeof (widget_value));
1912 mw->menu.contents = tem;
1913 #endif
1915 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1916 mw->menu.cursor = mw->menu.cursor_shape;
1918 mw->menu.gray_pixmap
1919 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1920 gray_bitmap_width, gray_bitmap_height,
1921 (unsigned long)1, (unsigned long)0, 1);
1923 #ifdef HAVE_XFT
1924 if (openXftFont (mw))
1926 else
1927 #endif
1929 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1930 if (!mw->menu.font)
1932 mw->menu.font = XLoadQueryFont (display, "fixed");
1933 if (!mw->menu.font)
1935 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1936 abort ();
1941 #ifdef HAVE_X_I18N
1942 if (mw->menu.fontSet)
1943 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1944 #endif
1946 make_drawing_gcs (mw);
1947 make_shadow_gcs (mw);
1949 mw->menu.popped_up = False;
1951 mw->menu.old_depth = 1;
1952 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1953 mw->menu.old_stack_length = 1;
1954 mw->menu.old_stack [0] = mw->menu.contents;
1956 mw->menu.new_depth = 0;
1957 mw->menu.new_stack = 0;
1958 mw->menu.new_stack_length = 0;
1959 push_new_stack (mw, mw->menu.contents);
1961 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1962 mw->menu.windows_length = 1;
1963 mw->menu.windows [0].x = 0;
1964 mw->menu.windows [0].y = 0;
1965 mw->menu.windows [0].width = 0;
1966 mw->menu.windows [0].height = 0;
1967 mw->menu.windows [0].max_rest_width = 0;
1968 mw->menu.windows [0].pixmap = None;
1969 #ifdef HAVE_XFT
1970 mw->menu.windows [0].xft_draw = 0;
1971 #endif
1972 size_menu (mw, 0);
1974 mw->core.width = mw->menu.windows [0].width;
1975 mw->core.height = mw->menu.windows [0].height;
1978 static void
1979 XlwMenuClassInitialize (void)
1983 static void
1984 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1986 XlwMenuWidget mw = (XlwMenuWidget)w;
1987 XSetWindowAttributes xswa;
1988 int mask;
1990 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1991 (w, valueMask, attributes);
1993 xswa.save_under = True;
1994 xswa.cursor = mw->menu.cursor_shape;
1995 mask = CWSaveUnder | CWCursor;
1996 /* I sometimes get random BadCursor errors while creating the first
1997 frame on a display. I can not find their reason, but they are
1998 annoying so for now let's ignore any errors here. -- lorentey */
1999 #ifdef emacs
2000 x_catch_errors (XtDisplay (w));
2001 #endif
2002 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
2003 #ifdef emacs
2004 x_uncatch_errors ();
2005 #endif
2007 mw->menu.windows [0].w = w;
2008 mw->menu.windows [0].window = XtWindow (w);
2009 mw->menu.windows [0].x = w->core.x;
2010 mw->menu.windows [0].y = w->core.y;
2011 mw->menu.windows [0].width = w->core.width;
2012 mw->menu.windows [0].height = w->core.height;
2014 set_window_type (mw->menu.windows [0].w, mw);
2015 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2017 #ifdef HAVE_XFT
2018 if (mw->menu.xft_font)
2020 XColor colors[3];
2021 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
2022 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
2023 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
2024 = mw->menu.disabled_foreground;
2025 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
2026 mw->menu.xft_fg.color.alpha = 0xFFFF;
2027 mw->menu.xft_fg.color.red = colors[0].red;
2028 mw->menu.xft_fg.color.green = colors[0].green;
2029 mw->menu.xft_fg.color.blue = colors[0].blue;
2030 mw->menu.xft_bg.color.alpha = 0xFFFF;
2031 mw->menu.xft_bg.color.red = colors[1].red;
2032 mw->menu.xft_bg.color.green = colors[1].green;
2033 mw->menu.xft_bg.color.blue = colors[1].blue;
2034 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2035 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2036 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2037 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2039 #endif
2042 /* Only the toplevel menubar/popup is a widget so it's the only one that
2043 receives expose events through Xt. So we repaint all the other panes
2044 when receiving an Expose event. */
2045 static void
2046 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2048 XlwMenuWidget mw = (XlwMenuWidget)w;
2050 /* If we have a depth beyond 1, it's because a submenu was displayed.
2051 If the submenu has been destroyed, set the depth back to 1. */
2052 if (submenu_destroyed)
2054 mw->menu.old_depth = 1;
2055 submenu_destroyed = 0;
2058 display_menu (mw, 0, False, NULL, NULL, NULL);
2062 /* Part of a hack to make the menu redisplay when a tooltip frame
2063 over a menu item is unmapped. */
2065 void
2066 xlwmenu_redisplay (Widget w)
2068 XlwMenuRedisplay (w, NULL, None);
2071 static void
2072 XlwMenuDestroy (Widget w)
2074 int i;
2075 XlwMenuWidget mw = (XlwMenuWidget) w;
2077 if (pointer_grabbed)
2078 ungrab_all ((Widget)w, CurrentTime);
2079 pointer_grabbed = 0;
2081 submenu_destroyed = 1;
2083 release_drawing_gcs (mw);
2084 release_shadow_gcs (mw);
2086 /* this doesn't come from the resource db but is created explicitly
2087 so we must free it ourselves. */
2088 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2089 mw->menu.gray_pixmap = (Pixmap) -1;
2091 #if 0
2092 /* Do free mw->menu.contents because nowadays we copy it
2093 during initialization. */
2094 XtFree (mw->menu.contents);
2095 #endif
2097 /* Don't free mw->menu.contents because that comes from our creator.
2098 The `*_stack' elements are just pointers into `contents' so leave
2099 that alone too. But free the stacks themselves. */
2100 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2101 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2103 /* Remember, you can't free anything that came from the resource
2104 database. This includes:
2105 mw->menu.cursor
2106 mw->menu.top_shadow_pixmap
2107 mw->menu.bottom_shadow_pixmap
2108 mw->menu.font
2109 Also the color cells of top_shadow_color, bottom_shadow_color,
2110 foreground, and button_foreground will never be freed until this
2111 client exits. Nice, eh?
2114 #ifdef HAVE_XFT
2115 if (mw->menu.windows [0].xft_draw)
2116 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2117 if (mw->menu.xft_font)
2118 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2119 #endif
2121 if (mw->menu.windows [0].pixmap != None)
2122 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2123 /* start from 1 because the one in slot 0 is w->core.window */
2124 for (i = 1; i < mw->menu.windows_length; i++)
2126 if (mw->menu.windows [i].pixmap != None)
2127 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2128 #ifdef HAVE_XFT
2129 if (mw->menu.windows [i].xft_draw)
2130 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2131 #endif
2134 if (mw->menu.windows)
2135 XtFree ((char *) mw->menu.windows);
2138 #ifdef HAVE_XFT
2139 static int
2140 fontname_changed (XlwMenuWidget newmw,
2141 XlwMenuWidget oldmw)
2143 /* This will force a new XftFont even if the same string is set.
2144 This is good, as rendering parameters may have changed and
2145 we just want to do a redisplay. */
2146 return newmw->menu.fontName != oldmw->menu.fontName;
2148 #endif
2150 static Boolean
2151 XlwMenuSetValues (Widget current, Widget request, Widget new,
2152 ArgList args, Cardinal *num_args)
2154 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2155 XlwMenuWidget newmw = (XlwMenuWidget)new;
2156 Boolean redisplay = False;
2157 int i;
2159 if (newmw->menu.contents
2160 && newmw->menu.contents->contents
2161 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2162 redisplay = True;
2163 /* Do redisplay if the contents are entirely eliminated. */
2164 if (newmw->menu.contents
2165 && newmw->menu.contents->contents == 0
2166 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2167 redisplay = True;
2169 if (newmw->core.background_pixel != oldmw->core.background_pixel
2170 || newmw->menu.foreground != oldmw->menu.foreground
2171 #ifdef HAVE_XFT
2172 || fontname_changed (newmw, oldmw)
2173 #endif
2174 #ifdef HAVE_X_I18N
2175 || newmw->menu.fontSet != oldmw->menu.fontSet
2176 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2177 #else
2178 || newmw->menu.font != oldmw->menu.font
2179 #endif
2182 release_drawing_gcs (newmw);
2183 make_drawing_gcs (newmw);
2185 release_shadow_gcs (newmw);
2186 /* Cause the shadow colors to be recalculated. */
2187 newmw->menu.top_shadow_color = -1;
2188 newmw->menu.bottom_shadow_color = -1;
2189 make_shadow_gcs (newmw);
2191 redisplay = True;
2193 if (XtIsRealized (current))
2194 /* If the menu is currently displayed, change the display. */
2195 for (i = 0; i < oldmw->menu.windows_length; i++)
2197 XSetWindowBackground (XtDisplay (oldmw),
2198 oldmw->menu.windows [i].window,
2199 newmw->core.background_pixel);
2200 /* clear windows and generate expose events */
2201 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2202 0, 0, 0, 0, True);
2206 #ifdef HAVE_XFT
2207 if (fontname_changed (newmw, oldmw))
2209 int i;
2210 int screen = XScreenNumberOfScreen (newmw->core.screen);
2211 if (newmw->menu.xft_font)
2212 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2213 openXftFont (newmw);
2214 for (i = 0; i < newmw->menu.windows_length; i++)
2216 if (newmw->menu.windows [i].xft_draw)
2217 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2218 newmw->menu.windows [i].xft_draw = 0;
2220 if (newmw->menu.xft_font)
2221 for (i = 0; i < newmw->menu.windows_length; i++)
2222 newmw->menu.windows [i].xft_draw
2223 = XftDrawCreate (XtDisplay (newmw),
2224 newmw->menu.windows [i].window,
2225 DefaultVisual (XtDisplay (newmw), screen),
2226 newmw->core.colormap);
2228 #endif
2229 #ifdef HAVE_X_I18N
2230 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2232 redisplay = True;
2233 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2235 #endif
2237 return redisplay;
2240 static void
2241 XlwMenuResize (Widget w)
2243 XlwMenuWidget mw = (XlwMenuWidget)w;
2245 if (mw->menu.popped_up)
2247 /* Don't allow the popup menu to resize itself. */
2248 mw->core.width = mw->menu.windows [0].width;
2249 mw->core.height = mw->menu.windows [0].height;
2250 mw->core.parent->core.width = mw->core.width;
2251 mw->core.parent->core.height = mw->core.height;
2253 else
2255 mw->menu.windows [0].width = mw->core.width;
2256 mw->menu.windows [0].height = mw->core.height;
2257 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2261 \f/* Action procedures */
2262 static void
2263 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2265 widget_value* val;
2266 int level;
2268 if (!map_event_to_widget_value (mw, ev, &val, &level))
2269 pop_new_stack_if_no_contents (mw);
2270 else
2271 set_new_state (mw, val, level);
2272 remap_menubar (mw);
2274 /* Sync with the display. Makes it feel better on X terms. */
2275 XSync (XtDisplay (mw), False);
2278 static void
2279 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2281 int x = ev->x_root;
2282 int y = ev->y_root;
2283 int state = ev->state;
2284 XMotionEvent oldev = *ev;
2286 /* allow motion events to be generated again */
2287 if (ev->is_hint
2288 && XQueryPointer (XtDisplay (mw), ev->window,
2289 &ev->root, &ev->subwindow,
2290 &ev->x_root, &ev->y_root,
2291 &ev->x, &ev->y,
2292 &ev->state)
2293 && ev->state == state
2294 && (ev->x_root != x || ev->y_root != y))
2295 handle_single_motion_event (mw, ev);
2296 else
2297 handle_single_motion_event (mw, &oldev);
2300 static void
2301 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2303 XlwMenuWidget mw = (XlwMenuWidget)w;
2305 if (!mw->menu.popped_up)
2307 menu_post_event = *ev;
2308 /* If event is set to CurrentTime, get the last known time stamp.
2309 This is for calculating if (popup) menus should stay up after
2310 a fast click. */
2311 if (menu_post_event.xbutton.time == CurrentTime)
2312 menu_post_event.xbutton.time
2313 = XtLastTimestampProcessed (XtDisplay (w));
2315 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2317 else
2319 /* If we push a button while the menu is posted semipermanently,
2320 releasing the button should always pop the menu down. */
2321 next_release_must_exit = 1;
2323 /* notes the absolute position of the menubar window */
2324 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2325 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2327 /* handles the down like a move, slots are compatible */
2328 ev->xmotion.is_hint = 0;
2329 handle_motion_event (mw, &ev->xmotion);
2333 static void
2334 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2336 XlwMenuWidget mw = (XlwMenuWidget)w;
2337 if (mw->menu.popped_up)
2338 handle_motion_event (mw, &ev->xmotion);
2341 /* Do nothing.
2342 This is how we handle presses and releases of modifier keys. */
2343 static void
2344 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2348 static widget_value *
2349 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2351 widget_value *current = item;
2352 enum menu_separator separator;
2354 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2355 || (skip_titles && !current->call_data && !current->contents))
2356 if (current->next)
2357 current=current->next;
2358 else
2359 return NULL;
2361 return current;
2364 static widget_value *
2365 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2367 widget_value *current = item;
2368 enum menu_separator separator;
2370 while (current->next && (current=current->next) &&
2371 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2372 || (skip_titles && !current->call_data && !current->contents)))
2375 if (current == item)
2377 if (mw->menu.old_depth < 2)
2378 return current;
2379 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2381 while (lw_separator_p (current->name, &separator, 0)
2382 || !current->enabled
2383 || (skip_titles && !current->call_data
2384 && !current->contents))
2386 if (current->next)
2387 current=current->next;
2389 if (current == item)
2390 break;
2395 return current;
2398 static widget_value *
2399 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2401 widget_value *current = item;
2402 widget_value *prev = item;
2404 while ((current=find_next_selectable (mw, current, skip_titles))
2405 != item)
2407 if (prev == current)
2408 break;
2409 prev=current;
2412 return prev;
2415 static void
2416 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2418 XlwMenuWidget mw = (XlwMenuWidget) w;
2419 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2420 int popup_menu_p = mw->menu.top_depth == 1;
2422 /* Inside top-level menu-bar? */
2423 if (mw->menu.old_depth == mw->menu.top_depth)
2424 /* When <down> in the menu-bar is pressed, display the corresponding
2425 sub-menu and select the first selectable menu item there.
2426 If this is a popup menu, skip title item of the popup. */
2427 set_new_state (mw,
2428 find_first_selectable (mw,
2429 selected_item->contents,
2430 popup_menu_p),
2431 mw->menu.old_depth);
2432 else
2433 /* Highlight next possible (enabled and not separator) menu item. */
2434 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2435 mw->menu.old_depth - 1);
2437 remap_menubar (mw);
2440 static void
2441 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2443 XlwMenuWidget mw = (XlwMenuWidget) w;
2444 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2445 int popup_menu_p = mw->menu.top_depth == 1;
2447 /* Inside top-level menu-bar? */
2448 if (mw->menu.old_depth == mw->menu.top_depth)
2450 /* FIXME: this is tricky. <up> in the menu-bar should select the
2451 last selectable item in the list. So we select the first
2452 selectable one and find the previous selectable item. Is there
2453 a better way? */
2454 /* If this is a popup menu, skip title item of the popup. */
2455 set_new_state (mw,
2456 find_first_selectable (mw,
2457 selected_item->contents,
2458 popup_menu_p),
2459 mw->menu.old_depth);
2460 remap_menubar (mw);
2461 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2462 set_new_state (mw,
2463 find_prev_selectable (mw,
2464 selected_item,
2465 popup_menu_p),
2466 mw->menu.old_depth - 1);
2468 else
2469 /* Highlight previous (enabled and not separator) menu item. */
2470 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2471 mw->menu.old_depth - 1);
2473 remap_menubar (mw);
2476 void
2477 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2479 XlwMenuWidget mw = (XlwMenuWidget) w;
2480 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2482 /* Inside top-level menu-bar? */
2483 if (mw->menu.old_depth == mw->menu.top_depth)
2484 /* When <left> in the menu-bar is pressed, display the previous item on
2485 the menu-bar. If the current item is the first one, highlight the
2486 last item in the menubar (probably Help). */
2487 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2488 mw->menu.old_depth - 1);
2489 else if (mw->menu.old_depth == 1
2490 && selected_item->contents) /* Is this menu item expandable? */
2492 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2493 remap_menubar (mw);
2494 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2495 if (!selected_item->enabled && find_first_selectable (mw,
2496 selected_item,
2498 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2499 mw->menu.old_depth - 1);
2502 else
2504 pop_new_stack_if_no_contents (mw);
2505 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2506 mw->menu.old_depth - 2);
2509 remap_menubar (mw);
2512 void
2513 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2515 XlwMenuWidget mw = (XlwMenuWidget) w;
2516 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2518 /* Inside top-level menu-bar? */
2519 if (mw->menu.old_depth == mw->menu.top_depth)
2520 /* When <right> in the menu-bar is pressed, display the next item on
2521 the menu-bar. If the current item is the last one, highlight the
2522 first item (probably File). */
2523 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2524 mw->menu.old_depth - 1);
2525 else if (selected_item->contents) /* Is this menu item expandable? */
2527 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2528 remap_menubar (mw);
2529 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2530 if (!selected_item->enabled && find_first_selectable (mw,
2531 selected_item,
2533 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2534 mw->menu.old_depth - 1);
2536 else
2538 pop_new_stack_if_no_contents (mw);
2539 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2540 mw->menu.old_depth - 2);
2543 remap_menubar (mw);
2546 /* Handle key press and release events while menu is popped up.
2547 Our action is to get rid of the menu. */
2548 static void
2549 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2551 XlwMenuWidget mw = (XlwMenuWidget)w;
2553 /* Pop down everything. */
2554 mw->menu.new_depth = 1;
2555 remap_menubar (mw);
2557 if (mw->menu.popped_up)
2559 mw->menu.popped_up = False;
2560 ungrab_all ((Widget)mw, ev->xmotion.time);
2561 if (XtIsShell (XtParent ((Widget) mw)))
2562 XtPopdown (XtParent ((Widget) mw));
2563 else
2565 XtRemoveGrab ((Widget) mw);
2566 display_menu (mw, 0, False, NULL, NULL, NULL);
2570 /* callback */
2571 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2574 static void
2575 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2577 XlwMenuWidget mw = (XlwMenuWidget)w;
2578 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2580 /* If user releases the button quickly, without selecting anything,
2581 after the initial down-click that brought the menu up,
2582 do nothing. */
2583 if ((selected_item == 0
2584 || ((widget_value *) selected_item)->call_data == 0)
2585 && !next_release_must_exit
2586 && (ev->xbutton.time - menu_post_event.xbutton.time
2587 < XtGetMultiClickTime (XtDisplay (w))))
2588 return;
2590 /* pop down everything. */
2591 mw->menu.new_depth = 1;
2592 remap_menubar (mw);
2594 if (mw->menu.popped_up)
2596 mw->menu.popped_up = False;
2597 ungrab_all ((Widget)mw, ev->xmotion.time);
2598 if (XtIsShell (XtParent ((Widget) mw)))
2599 XtPopdown (XtParent ((Widget) mw));
2600 else
2602 XtRemoveGrab ((Widget) mw);
2603 display_menu (mw, 0, False, NULL, NULL, NULL);
2607 /* callback */
2608 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2612 \f/* Special code to pop-up a menu */
2613 static void
2614 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2616 int x = event->x_root;
2617 int y = event->y_root;
2618 int w;
2619 int h;
2620 int borderwidth = mw->menu.shadow_thickness;
2621 Screen* screen = XtScreen (mw);
2622 Display *display = XtDisplay (mw);
2624 next_release_must_exit = 0;
2626 mw->menu.inside_entry = NULL;
2627 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2629 if (XtIsShell (XtParent ((Widget)mw)))
2630 size_menu (mw, 0);
2632 w = mw->menu.windows [0].width;
2633 h = mw->menu.windows [0].height;
2635 x -= borderwidth;
2636 y -= borderwidth;
2637 if (x < borderwidth)
2638 x = borderwidth;
2639 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2640 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2641 if (y < borderwidth)
2642 y = borderwidth;
2643 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2644 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2646 mw->menu.popped_up = True;
2647 if (XtIsShell (XtParent ((Widget)mw)))
2649 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2650 XtParent ((Widget)mw)->core.border_width);
2651 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2652 display_menu (mw, 0, False, NULL, NULL, NULL);
2653 mw->menu.windows [0].x = x + borderwidth;
2654 mw->menu.windows [0].y = y + borderwidth;
2655 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2657 else
2659 XEvent *ev = (XEvent *) event;
2661 XtAddGrab ((Widget) mw, True, True);
2663 /* notes the absolute position of the menubar window */
2664 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2665 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2666 mw->menu.top_depth = 2;
2669 #ifdef emacs
2670 x_catch_errors (display);
2671 #endif
2672 if (XtGrabPointer ((Widget)mw, False,
2673 (PointerMotionMask
2674 | PointerMotionHintMask
2675 | ButtonReleaseMask
2676 | ButtonPressMask),
2677 GrabModeAsync, GrabModeAsync, None,
2678 mw->menu.cursor_shape,
2679 event->time) == Success)
2681 if (! GRAB_KEYBOARD
2682 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2683 GrabModeAsync, event->time) == Success)
2685 XtSetKeyboardFocus((Widget)mw, None);
2686 pointer_grabbed = 1;
2688 else
2689 XtUngrabPointer ((Widget)mw, event->time);
2692 #ifdef emacs
2693 if (x_had_errors_p (display))
2695 pointer_grabbed = 0;
2696 XtUngrabPointer ((Widget)mw, event->time);
2698 x_uncatch_errors ();
2699 #endif
2701 ((XMotionEvent*)event)->is_hint = 0;
2702 handle_motion_event (mw, (XMotionEvent*)event);