* lisp/emacs-lisp/pcase.el (pcase-UPAT, pcase-QPAT): New edebug specs.
[emacs.git] / lwlib / xlwmenu.c
blob4406c505392a7e86936f8cf6247364b86cd77340
1 /* Implements a lightweight menubar widget.
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2012 Free Software Foundation, Inc.
6 This file is part of the Lucid Widget Library.
8 The Lucid Widget Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2, or (at your option)
11 any later version.
13 The Lucid Widget Library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with GNU Emacs; see the file COPYING. If not, write to the
20 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA. */
23 /* Created by devin@lucid.com */
25 #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 #include <xterm.h>
53 #include "bitmaps/gray.xbm"
55 #else /* not emacs */
57 #include <X11/bitmaps/gray>
59 #endif /* not emacs */
61 static int pointer_grabbed;
62 static XEvent menu_post_event;
64 static char
65 xlwMenuTranslations [] =
66 "<BtnDown>: start()\n\
67 <Motion>: drag()\n\
68 <BtnUp>: select()\n\
69 <Key>Shift_L: nothing()\n\
70 <Key>Shift_R: nothing()\n\
71 <Key>Meta_L: nothing()\n\
72 <Key>Meta_R: nothing()\n\
73 <Key>Control_L: nothing()\n\
74 <Key>Control_R: nothing()\n\
75 <Key>Hyper_L: nothing()\n\
76 <Key>Hyper_R: nothing()\n\
77 <Key>Super_L: nothing()\n\
78 <Key>Super_R: nothing()\n\
79 <Key>Alt_L: nothing()\n\
80 <Key>Alt_R: nothing()\n\
81 <Key>Caps_Lock: nothing()\n\
82 <Key>Shift_Lock: nothing()\n\
83 <KeyUp>Shift_L: nothing()\n\
84 <KeyUp>Shift_R: nothing()\n\
85 <KeyUp>Meta_L: nothing()\n\
86 <KeyUp>Meta_R: nothing()\n\
87 <KeyUp>Control_L: nothing()\n\
88 <KeyUp>Control_R: nothing()\n\
89 <KeyUp>Hyper_L: nothing()\n\
90 <KeyUp>Hyper_R: nothing()\n\
91 <KeyUp>Super_L: nothing()\n\
92 <KeyUp>Super_R: nothing()\n\
93 <KeyUp>Alt_L: nothing()\n\
94 <KeyUp>Alt_R: nothing()\n\
95 <KeyUp>Caps_Lock: nothing()\n\
96 <KeyUp>Shift_Lock:nothing()\n\
97 <Key>Return: select()\n\
98 <Key>Down: down()\n\
99 <Key>Up: up()\n\
100 <Key>Left: left()\n\
101 <Key>Right: right()\n\
102 <Key>: key()\n\
103 <KeyUp>: key()\n\
106 /* FIXME: Space should toggle togglable menu item but not remove the menu
107 so you can toggle the next one without entering the menu again. */
109 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
111 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
113 #define offset(field) XtOffset(XlwMenuWidget, field)
114 static XtResource
115 xlwMenuResources[] =
117 #ifdef HAVE_X_I18N
118 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
119 offset(menu.fontSet), XtRFontSet, NULL},
120 #endif
121 #ifdef HAVE_XFT
122 #define DEFAULT_FONTNAME "Sans-10"
123 #else
124 #define DEFAULT_FONTNAME "XtDefaultFont"
125 #endif
126 {XtNfont, XtCFont, XtRString, sizeof(String),
127 offset(menu.fontName), XtRString, DEFAULT_FONTNAME },
128 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
129 offset(menu.foreground), XtRString, "XtDefaultForeground"},
130 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
131 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
132 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
133 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
134 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
135 offset(menu.margin), XtRImmediate, (XtPointer)1},
136 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
137 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
138 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
139 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
140 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
141 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
143 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
144 sizeof (Dimension), offset (menu.shadow_thickness),
145 XtRImmediate, (XtPointer)1},
146 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
147 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
148 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
149 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
150 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
151 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
152 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
153 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
155 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
156 offset(menu.open), XtRCallback, (XtPointer)NULL},
157 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
158 offset(menu.select), XtRCallback, (XtPointer)NULL},
159 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
160 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
161 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
162 offset(menu.enter), XtRCallback, (XtPointer)NULL},
163 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
164 offset(menu.leave), XtRCallback, (XtPointer)NULL},
165 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
166 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
167 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
168 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
169 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
170 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
172 #undef offset
174 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
175 ArgList args, Cardinal *num_args);
176 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
177 static void XlwMenuResize(Widget w);
178 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
179 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
180 static void XlwMenuDestroy(Widget w);
181 static void XlwMenuClassInitialize(void);
182 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
183 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
184 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
185 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
186 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
187 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
188 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
189 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
190 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
191 static int separator_height (enum menu_separator);
192 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
193 static void abort_gracefully (Widget w) NO_RETURN;
195 static XtActionsRec
196 xlwMenuActionsList [] =
198 {"start", Start},
199 {"drag", Drag},
200 {"down", Down},
201 {"up", Up},
202 {"left", Left},
203 {"right", Right},
204 {"select", Select},
205 {"key", Key},
206 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
207 {"nothing", Nothing},
210 #define SuperClass ((CoreWidgetClass)&coreClassRec)
212 XlwMenuClassRec xlwMenuClassRec =
214 { /* CoreClass fields initialization */
215 (WidgetClass) SuperClass, /* superclass */
216 "XlwMenu", /* class_name */
217 sizeof(XlwMenuRec), /* size */
218 XlwMenuClassInitialize, /* class_initialize */
219 NULL, /* class_part_initialize */
220 FALSE, /* class_inited */
221 XlwMenuInitialize, /* initialize */
222 NULL, /* initialize_hook */
223 XlwMenuRealize, /* realize */
224 xlwMenuActionsList, /* actions */
225 XtNumber(xlwMenuActionsList), /* num_actions */
226 xlwMenuResources, /* resources */
227 XtNumber(xlwMenuResources), /* resource_count */
228 NULLQUARK, /* xrm_class */
229 TRUE, /* compress_motion */
230 XtExposeCompressMaximal, /* compress_exposure */
231 TRUE, /* compress_enterleave */
232 FALSE, /* visible_interest */
233 XlwMenuDestroy, /* destroy */
234 XlwMenuResize, /* resize */
235 XlwMenuRedisplay, /* expose */
236 XlwMenuSetValues, /* set_values */
237 NULL, /* set_values_hook */
238 XtInheritSetValuesAlmost, /* set_values_almost */
239 NULL, /* get_values_hook */
240 NULL, /* accept_focus */
241 XtVersion, /* version */
242 NULL, /* callback_private */
243 xlwMenuTranslations, /* tm_table */
244 XtInheritQueryGeometry, /* query_geometry */
245 XtInheritDisplayAccelerator, /* display_accelerator */
246 NULL /* extension */
247 }, /* XlwMenuClass fields initialization */
249 0 /* dummy */
253 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
255 int submenu_destroyed;
257 /* For debug, if installation-directory is non-nil this is not an installed
258 Emacs. In that case we do not grab the keyboard to make it easier to
259 debug. */
260 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
262 static int next_release_must_exit;
264 \f/* Utilities */
266 /* Ungrab pointer and keyboard */
267 static void
268 ungrab_all (Widget w, Time ungrabtime)
270 XtUngrabPointer (w, ungrabtime);
271 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
274 /* Like abort, but remove grabs from widget W before. */
276 static void
277 abort_gracefully (Widget w)
279 if (XtIsShell (XtParent (w)))
280 XtRemoveGrab (w);
281 ungrab_all (w, CurrentTime);
282 abort ();
285 static void
286 push_new_stack (XlwMenuWidget mw, widget_value *val)
288 if (!mw->menu.new_stack)
290 mw->menu.new_stack_length = 10;
291 mw->menu.new_stack =
292 (widget_value**)XtCalloc (mw->menu.new_stack_length,
293 sizeof (widget_value*));
295 else if (mw->menu.new_depth == mw->menu.new_stack_length)
297 mw->menu.new_stack_length *= 2;
298 mw->menu.new_stack =
299 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
300 mw->menu.new_stack_length * sizeof (widget_value*));
302 mw->menu.new_stack [mw->menu.new_depth++] = val;
305 static void
306 pop_new_stack_if_no_contents (XlwMenuWidget mw)
308 if (mw->menu.new_depth > 1)
310 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
311 mw->menu.new_depth -= 1;
315 static void
316 make_old_stack_space (XlwMenuWidget mw, int n)
318 if (!mw->menu.old_stack)
320 mw->menu.old_stack_length = 10;
321 mw->menu.old_stack =
322 (widget_value**)XtCalloc (mw->menu.old_stack_length,
323 sizeof (widget_value*));
325 else if (mw->menu.old_stack_length < n)
327 mw->menu.old_stack_length *= 2;
328 mw->menu.old_stack =
329 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
330 mw->menu.old_stack_length * sizeof (widget_value*));
334 \f/* Size code */
335 static int
336 string_width (XlwMenuWidget mw, char *s)
338 XCharStruct xcs;
339 int drop;
340 #ifdef HAVE_XFT
341 if (mw->menu.xft_font)
343 XGlyphInfo gi;
344 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
345 (FcChar8 *) s,
346 strlen (s), &gi);
347 return gi.width;
349 #endif
350 #ifdef HAVE_X_I18N
351 if (mw->menu.fontSet)
353 XRectangle ink, logical;
354 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
355 return logical.width;
357 #endif
359 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
360 return xcs.width;
364 #ifdef HAVE_XFT
365 #define MENU_FONT_HEIGHT(mw) \
366 ((mw)->menu.xft_font != NULL \
367 ? (mw)->menu.xft_font->height \
368 : ((mw)->menu.fontSet != NULL \
369 ? (mw)->menu.font_extents->max_logical_extent.height \
370 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
371 #define MENU_FONT_ASCENT(mw) \
372 ((mw)->menu.xft_font != NULL \
373 ? (mw)->menu.xft_font->ascent \
374 : ((mw)->menu.fontSet != NULL \
375 ? - (mw)->menu.font_extents->max_logical_extent.y \
376 : (mw)->menu.font->ascent))
377 #else
378 #ifdef HAVE_X_I18N
379 #define MENU_FONT_HEIGHT(mw) \
380 ((mw)->menu.fontSet != NULL \
381 ? (mw)->menu.font_extents->max_logical_extent.height \
382 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
383 #define MENU_FONT_ASCENT(mw) \
384 ((mw)->menu.fontSet != NULL \
385 ? - (mw)->menu.font_extents->max_logical_extent.y \
386 : (mw)->menu.font->ascent)
387 #else
388 #define MENU_FONT_HEIGHT(mw) \
389 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
390 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
391 #endif
392 #endif
394 static int
395 arrow_width (XlwMenuWidget mw)
397 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
400 /* Return the width of toggle buttons of widget MW. */
402 static int
403 toggle_button_width (XlwMenuWidget mw)
405 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
409 /* Return the width of radio buttons of widget MW. */
411 static int
412 radio_button_width (XlwMenuWidget mw)
414 return toggle_button_width (mw) * 1.41;
418 static XtResource
419 nameResource[] =
421 {"labelString", "LabelString", XtRString, sizeof(String),
422 0, XtRImmediate, 0},
425 static char*
426 resource_widget_value (XlwMenuWidget mw, widget_value *val)
428 if (!val->toolkit_data)
430 char* resourced_name = NULL;
431 char* complete_name;
432 XtGetSubresources ((Widget) mw,
433 (XtPointer) &resourced_name,
434 val->name, val->name,
435 nameResource, 1, NULL, 0);
436 if (!resourced_name)
437 resourced_name = val->name;
438 if (!val->value)
440 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
441 strcpy (complete_name, resourced_name);
443 else
445 int complete_length =
446 strlen (resourced_name) + strlen (val->value) + 2;
447 complete_name = XtMalloc (complete_length);
448 *complete_name = 0;
449 strcat (complete_name, resourced_name);
450 strcat (complete_name, " ");
451 strcat (complete_name, val->value);
454 val->toolkit_data = complete_name;
455 val->free_toolkit_data = True;
457 return (char*)val->toolkit_data;
460 /* Returns the sizes of an item */
461 static void
462 size_menu_item (XlwMenuWidget mw,
463 widget_value* val,
464 int horizontal_p,
465 int* label_width,
466 int* rest_width,
467 int* button_width,
468 int* height)
470 enum menu_separator separator;
472 if (lw_separator_p (val->name, &separator, 0))
474 *height = separator_height (separator);
475 *label_width = 1;
476 *rest_width = 0;
477 *button_width = 0;
479 else
481 *height = MENU_FONT_HEIGHT (mw)
482 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
484 *label_width =
485 string_width (mw, resource_widget_value (mw, val))
486 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
488 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
489 if (!horizontal_p)
491 if (val->contents)
492 /* Add width of the arrow displayed for submenus. */
493 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
494 else if (val->key)
495 /* Add width of key equivalent string. */
496 *rest_width += (string_width (mw, val->key)
497 + mw->menu.arrow_spacing);
499 if (val->button_type == BUTTON_TYPE_TOGGLE)
500 *button_width = (toggle_button_width (mw)
501 + mw->menu.horizontal_spacing);
502 else if (val->button_type == BUTTON_TYPE_RADIO)
503 *button_width = (radio_button_width (mw)
504 + mw->menu.horizontal_spacing);
509 static void
510 size_menu (XlwMenuWidget mw, int level)
512 int label_width = 0;
513 int rest_width = 0;
514 int button_width = 0;
515 int max_rest_width = 0;
516 int max_button_width = 0;
517 int height = 0;
518 int horizontal_p = mw->menu.horizontal && (level == 0);
519 widget_value* val;
520 window_state* ws;
522 if (level >= mw->menu.old_depth)
523 abort_gracefully ((Widget) mw);
525 ws = &mw->menu.windows [level];
526 ws->width = 0;
527 ws->height = 0;
528 ws->label_width = 0;
529 ws->button_width = 0;
531 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
533 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
534 &button_width, &height);
535 if (horizontal_p)
537 ws->width += label_width + rest_width;
538 if (height > ws->height)
539 ws->height = height;
541 else
543 if (label_width > ws->label_width)
544 ws->label_width = label_width;
545 if (rest_width > max_rest_width)
546 max_rest_width = rest_width;
547 if (button_width > max_button_width)
548 max_button_width = button_width;
549 ws->height += height;
553 if (horizontal_p)
554 ws->label_width = ws->button_width = 0;
555 else
557 ws->width = ws->label_width + max_rest_width + max_button_width;
558 ws->button_width = max_button_width;
561 ws->width += 2 * mw->menu.shadow_thickness;
562 ws->height += 2 * mw->menu.shadow_thickness;
563 ws->max_rest_width = max_rest_width;
565 if (horizontal_p)
567 ws->width += 2 * mw->menu.margin;
568 ws->height += 2 * mw->menu.margin;
573 \f/* Display code */
575 static void
576 draw_arrow (XlwMenuWidget mw,
577 Window window,
578 GC gc,
579 int x,
580 int y,
581 int width,
582 int down_p)
584 Display *dpy = XtDisplay (mw);
585 GC top_gc = mw->menu.shadow_top_gc;
586 GC bottom_gc = mw->menu.shadow_bottom_gc;
587 int thickness = mw->menu.shadow_thickness;
588 int height = width;
589 XPoint pt[10];
590 /* alpha = atan (0.5)
591 factor = (1 + sin (alpha)) / cos (alpha) */
592 double factor = 1.62;
593 int thickness2 = thickness * factor;
595 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
597 if (down_p)
599 GC temp;
600 temp = top_gc;
601 top_gc = bottom_gc;
602 bottom_gc = temp;
605 pt[0].x = x;
606 pt[0].y = y + height;
607 pt[1].x = x + thickness;
608 pt[1].y = y + height - thickness2;
609 pt[2].x = x + thickness2;
610 pt[2].y = y + thickness2;
611 pt[3].x = x;
612 pt[3].y = y;
613 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
615 pt[0].x = x;
616 pt[0].y = y;
617 pt[1].x = x + thickness;
618 pt[1].y = y + thickness2;
619 pt[2].x = x + width - thickness2;
620 pt[2].y = y + height / 2;
621 pt[3].x = x + width;
622 pt[3].y = y + height / 2;
623 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
625 pt[0].x = x;
626 pt[0].y = y + height;
627 pt[1].x = x + thickness;
628 pt[1].y = y + height - 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, bottom_gc, pt, 4, Convex, CoordModeOrigin);
638 static void
639 draw_shadow_rectangle (XlwMenuWidget mw,
640 Window window,
641 int x,
642 int y,
643 int width,
644 int height,
645 int erase_p,
646 int down_p)
648 Display *dpy = XtDisplay (mw);
649 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
650 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
651 int thickness = mw->menu.shadow_thickness;
652 XPoint points [4];
654 if (!erase_p && down_p)
656 GC temp;
657 temp = top_gc;
658 top_gc = bottom_gc;
659 bottom_gc = temp;
662 points [0].x = x;
663 points [0].y = y;
664 points [1].x = x + width;
665 points [1].y = y;
666 points [2].x = x + width - thickness;
667 points [2].y = y + thickness;
668 points [3].x = x;
669 points [3].y = y + thickness;
670 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
671 points [0].x = x;
672 points [0].y = y + thickness;
673 points [1].x = x;
674 points [1].y = y + height;
675 points [2].x = x + thickness;
676 points [2].y = y + height - thickness;
677 points [3].x = x + thickness;
678 points [3].y = y + thickness;
679 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
680 points [0].x = x + width;
681 points [0].y = y;
682 points [1].x = x + width - thickness;
683 points [1].y = y + thickness;
684 points [2].x = x + width - thickness;
685 points [2].y = y + height - thickness;
686 points [3].x = x + width;
687 points [3].y = y + height - thickness;
688 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
689 points [0].x = x;
690 points [0].y = y + height;
691 points [1].x = x + width;
692 points [1].y = y + height;
693 points [2].x = x + width;
694 points [2].y = y + height - thickness;
695 points [3].x = x + thickness;
696 points [3].y = y + height - thickness;
697 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
701 static void
702 draw_shadow_rhombus (XlwMenuWidget mw,
703 Window window,
704 int x,
705 int y,
706 int width,
707 int height,
708 int erase_p,
709 int down_p)
711 Display *dpy = XtDisplay (mw);
712 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
713 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
714 int thickness = mw->menu.shadow_thickness;
715 XPoint points [4];
717 if (!erase_p && down_p)
719 GC temp;
720 temp = top_gc;
721 top_gc = bottom_gc;
722 bottom_gc = temp;
725 points [0].x = x;
726 points [0].y = y + height / 2;
727 points [1].x = x + thickness;
728 points [1].y = y + height / 2;
729 points [2].x = x + width / 2;
730 points [2].y = y + thickness;
731 points [3].x = x + width / 2;
732 points [3].y = y;
733 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
734 points [0].x = x + width / 2;
735 points [0].y = y;
736 points [1].x = x + width / 2;
737 points [1].y = y + thickness;
738 points [2].x = x + width - thickness;
739 points [2].y = y + height / 2;
740 points [3].x = x + width;
741 points [3].y = y + height / 2;
742 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
743 points [0].x = x;
744 points [0].y = y + height / 2;
745 points [1].x = x + thickness;
746 points [1].y = y + height / 2;
747 points [2].x = x + width / 2;
748 points [2].y = y + height - thickness;
749 points [3].x = x + width / 2;
750 points [3].y = y + height;
751 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
752 points [0].x = x + width / 2;
753 points [0].y = y + height;
754 points [1].x = x + width / 2;
755 points [1].y = y + height - thickness;
756 points [2].x = x + width - thickness;
757 points [2].y = y + height / 2;
758 points [3].x = x + width;
759 points [3].y = y + height / 2;
760 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
764 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
765 top-left corner of the menu item. SELECTED_P non-zero means the
766 toggle button is selected. */
768 static void
769 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
771 int width, height;
773 width = toggle_button_width (mw);
774 height = width;
775 x += mw->menu.horizontal_spacing;
776 y += (MENU_FONT_ASCENT (mw) - height) / 2;
777 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
781 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
782 top-left corner of the menu item. SELECTED_P non-zero means the
783 toggle button is selected. */
785 static void
786 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
788 int width, height;
790 width = radio_button_width (mw);
791 height = width;
792 x += mw->menu.horizontal_spacing;
793 y += (MENU_FONT_ASCENT (mw) - height) / 2;
794 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
798 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
799 top-left corner of the menu item. WIDTH is the width of the
800 separator to draw. TYPE is the separator type. */
802 static void
803 draw_separator (XlwMenuWidget mw,
804 Window window,
805 int x,
806 int y,
807 int width,
808 enum menu_separator type)
810 Display *dpy = XtDisplay (mw);
811 XGCValues xgcv;
813 switch (type)
815 case SEPARATOR_NO_LINE:
816 break;
818 case SEPARATOR_SINGLE_LINE:
819 XDrawLine (dpy, window, mw->menu.foreground_gc,
820 x, y, x + width, y);
821 break;
823 case SEPARATOR_DOUBLE_LINE:
824 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
825 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
826 break;
828 case SEPARATOR_SINGLE_DASHED_LINE:
829 xgcv.line_style = LineOnOffDash;
830 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
831 XDrawLine (dpy, window, mw->menu.foreground_gc,
832 x, y, x + width, y);
833 xgcv.line_style = LineSolid;
834 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
835 break;
837 case SEPARATOR_DOUBLE_DASHED_LINE:
838 draw_separator (mw, window, x, y, width,
839 SEPARATOR_SINGLE_DASHED_LINE);
840 draw_separator (mw, window, x, y + 2, width,
841 SEPARATOR_SINGLE_DASHED_LINE);
842 break;
844 case SEPARATOR_SHADOW_ETCHED_IN:
845 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
846 x, y, x + width, y);
847 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
848 x, y + 1, x + width, y + 1);
849 break;
851 case SEPARATOR_SHADOW_ETCHED_OUT:
852 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
853 x, y, x + width, y);
854 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
855 x, y + 1, x + width, y + 1);
856 break;
858 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
859 xgcv.line_style = LineOnOffDash;
860 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
861 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
862 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
863 xgcv.line_style = LineSolid;
864 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
865 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
866 break;
868 case SEPARATOR_SHADOW_ETCHED_OUT_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_OUT);
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_DOUBLE_ETCHED_IN:
879 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
880 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
881 break;
883 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
884 draw_separator (mw, window, x, y, width,
885 SEPARATOR_SHADOW_ETCHED_OUT);
886 draw_separator (mw, window, x, y + 3, width,
887 SEPARATOR_SHADOW_ETCHED_OUT);
888 break;
890 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
891 xgcv.line_style = LineOnOffDash;
892 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
893 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
894 draw_separator (mw, window, x, y, width,
895 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
896 xgcv.line_style = LineSolid;
897 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
898 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
899 break;
901 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
902 xgcv.line_style = LineOnOffDash;
903 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
904 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
905 draw_separator (mw, window, x, y, width,
906 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
907 xgcv.line_style = LineSolid;
908 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
909 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
910 break;
912 default:
913 abort ();
918 /* Return the pixel height of menu separator SEPARATOR. */
920 static int
921 separator_height (enum menu_separator separator)
923 switch (separator)
925 case SEPARATOR_NO_LINE:
926 return 2;
928 case SEPARATOR_SINGLE_LINE:
929 case SEPARATOR_SINGLE_DASHED_LINE:
930 return 1;
932 case SEPARATOR_DOUBLE_LINE:
933 case SEPARATOR_DOUBLE_DASHED_LINE:
934 return 3;
936 case SEPARATOR_SHADOW_ETCHED_IN:
937 case SEPARATOR_SHADOW_ETCHED_OUT:
938 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
939 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
940 return 2;
942 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
943 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
944 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
945 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
946 return 5;
948 default:
949 abort ();
954 /* Display the menu item and increment where.x and where.y to show how large
955 the menu item was. */
957 static void
958 display_menu_item (XlwMenuWidget mw,
959 widget_value* val,
960 window_state* ws,
961 XPoint* where,
962 Boolean highlighted_p,
963 Boolean horizontal_p,
964 Boolean just_compute_p)
966 GC deco_gc;
967 GC text_gc;
968 int font_height = MENU_FONT_HEIGHT (mw);
969 int font_ascent = MENU_FONT_ASCENT (mw);
970 int shadow = mw->menu.shadow_thickness;
971 int margin = mw->menu.margin;
972 int h_spacing = mw->menu.horizontal_spacing;
973 int v_spacing = mw->menu.vertical_spacing;
974 int label_width;
975 int rest_width;
976 int button_width;
977 int height;
978 int width;
979 enum menu_separator separator;
980 int separator_p = lw_separator_p (val->name, &separator, 0);
981 #ifdef HAVE_XFT
982 XftColor *xftfg;
983 #endif
985 /* compute the sizes of the item */
986 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
987 &button_width, &height);
989 if (horizontal_p)
990 width = label_width + rest_width;
991 else
993 label_width = ws->label_width;
994 width = ws->width - 2 * shadow;
997 /* Only highlight an enabled item that has a callback. */
998 if (highlighted_p)
999 if (!val->enabled || !(val->call_data || val->contents))
1000 highlighted_p = 0;
1002 /* do the drawing. */
1003 if (!just_compute_p)
1005 /* Add the shadow border of the containing menu */
1006 int x = where->x + shadow;
1007 int y = where->y + shadow;
1009 if (horizontal_p)
1011 x += margin;
1012 y += margin;
1015 /* pick the foreground and background GC. */
1016 if (val->enabled)
1017 text_gc = mw->menu.foreground_gc;
1018 else
1019 text_gc = mw->menu.disabled_gc;
1020 deco_gc = mw->menu.foreground_gc;
1021 #ifdef HAVE_XFT
1022 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1023 #endif
1025 if (separator_p)
1027 draw_separator (mw, ws->pixmap, x, y, width, separator);
1029 else
1031 int x_offset = x + h_spacing + shadow;
1032 char* display_string = resource_widget_value (mw, val);
1033 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1034 False);
1036 /* Deal with centering a menu title. */
1037 if (!horizontal_p && !val->contents && !val->call_data)
1039 int l = string_width (mw, display_string);
1041 if (width > l)
1042 x_offset = (width - l) >> 1;
1044 else if (!horizontal_p && ws->button_width)
1045 x_offset += ws->button_width;
1048 #ifdef HAVE_XFT
1049 if (ws->xft_draw)
1051 int draw_y = y + v_spacing + shadow;
1052 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1053 mw->menu.xft_font,
1054 x_offset, draw_y + font_ascent,
1055 (unsigned char *) display_string,
1056 strlen (display_string));
1058 else
1059 #endif
1060 #ifdef HAVE_X_I18N
1061 if (mw->menu.fontSet)
1062 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1063 text_gc, x_offset,
1064 y + v_spacing + shadow + font_ascent,
1065 display_string, strlen (display_string));
1066 else
1067 #endif
1068 XDrawString (XtDisplay (mw), ws->pixmap,
1069 text_gc, x_offset,
1070 y + v_spacing + shadow + font_ascent,
1071 display_string, strlen (display_string));
1073 if (!horizontal_p)
1075 if (val->button_type == BUTTON_TYPE_TOGGLE)
1076 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1077 val->selected);
1078 else if (val->button_type == BUTTON_TYPE_RADIO)
1079 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1080 val->selected);
1082 if (val->contents)
1084 int a_w = arrow_width (mw);
1085 draw_arrow (mw, ws->pixmap, deco_gc,
1086 x + width - a_w
1087 - mw->menu.horizontal_spacing
1088 - mw->menu.shadow_thickness,
1089 y + v_spacing + shadow, a_w,
1090 highlighted_p);
1092 else if (val->key)
1094 #ifdef HAVE_XFT
1095 if (ws->xft_draw)
1097 int draw_x = ws->width - ws->max_rest_width
1098 + mw->menu.arrow_spacing;
1099 int draw_y = y + v_spacing + shadow + font_ascent;
1100 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1101 mw->menu.xft_font,
1102 draw_x, draw_y,
1103 (unsigned char *) val->key,
1104 strlen (val->key));
1106 else
1107 #endif
1108 #ifdef HAVE_X_I18N
1109 if (mw->menu.fontSet)
1110 XmbDrawString (XtDisplay (mw), ws->pixmap,
1111 mw->menu.fontSet,
1112 text_gc,
1113 x + label_width + mw->menu.arrow_spacing,
1114 y + v_spacing + shadow + font_ascent,
1115 val->key, strlen (val->key));
1116 else
1117 #endif
1118 XDrawString (XtDisplay (mw), ws->pixmap,
1119 text_gc,
1120 x + label_width + mw->menu.arrow_spacing,
1121 y + v_spacing + shadow + font_ascent,
1122 val->key, strlen (val->key));
1125 else
1127 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1128 mw->menu.background_gc,
1129 x + shadow, y + shadow,
1130 label_width + h_spacing - 1,
1131 font_height + 2 * v_spacing - 1);
1132 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1133 True, False);
1136 if (highlighted_p)
1137 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1138 False);
1142 where->x += width;
1143 where->y += height;
1146 static void
1147 display_menu (XlwMenuWidget mw,
1148 int level,
1149 Boolean just_compute_p,
1150 XPoint *highlighted_pos,
1151 XPoint *hit,
1152 widget_value **hit_return)
1154 widget_value* val;
1155 widget_value* following_item;
1156 window_state* ws;
1157 XPoint where;
1158 int horizontal_p = mw->menu.horizontal && (level == 0);
1159 int highlighted_p;
1160 int no_return = 0;
1161 enum menu_separator separator;
1163 if (level >= mw->menu.old_depth)
1164 abort_gracefully ((Widget) mw);
1166 if (level < mw->menu.old_depth - 1)
1167 following_item = mw->menu.old_stack [level + 1];
1168 else
1169 following_item = NULL;
1171 if (hit)
1172 *hit_return = NULL;
1174 where.x = 0;
1175 where.y = 0;
1177 ws = &mw->menu.windows [level];
1179 if (!just_compute_p)
1180 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1181 0, 0, ws->width, ws->height);
1183 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1185 highlighted_p = val == following_item;
1186 if (highlighted_p && highlighted_pos)
1188 if (horizontal_p)
1189 highlighted_pos->x = where.x;
1190 else
1191 highlighted_pos->y = where.y;
1194 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1195 just_compute_p);
1197 if (highlighted_p && highlighted_pos)
1199 if (horizontal_p)
1200 highlighted_pos->y = where.y;
1201 else
1202 highlighted_pos->x = where.x;
1205 if (hit
1206 && !*hit_return
1207 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1208 && !lw_separator_p (val->name, &separator, 0)
1209 && !no_return)
1211 if (val->enabled)
1212 *hit_return = val;
1213 else
1214 no_return = 1;
1215 if (mw->menu.inside_entry != val)
1217 if (mw->menu.inside_entry)
1218 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1219 (XtPointer) mw->menu.inside_entry);
1220 mw->menu.inside_entry = val;
1221 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1222 (XtPointer) mw->menu.inside_entry);
1226 if (horizontal_p)
1227 where.y = 0;
1228 else
1229 where.x = 0;
1232 if (!just_compute_p)
1234 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1235 False, False);
1236 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1237 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1241 \f/* Motion code */
1242 static void
1243 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1245 int i;
1247 mw->menu.new_depth = 0;
1248 for (i = 0; i < level; i++)
1249 push_new_stack (mw, mw->menu.old_stack [i]);
1250 push_new_stack (mw, val);
1253 static void
1254 expose_cb (Widget widget,
1255 XtPointer closure,
1256 XEvent* event,
1257 Boolean* continue_to_dispatch)
1259 XlwMenuWidget mw = (XlwMenuWidget) closure;
1260 int i;
1262 *continue_to_dispatch = False;
1263 for (i = 0; i < mw->menu.windows_length; ++i)
1264 if (mw->menu.windows [i].w == widget) break;
1265 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1266 display_menu (mw, i, False, NULL, NULL, NULL);
1269 static void
1270 set_window_type (Widget w, XlwMenuWidget mw)
1272 int popup_menu_p = mw->menu.top_depth == 1;
1273 Atom type = XInternAtom (XtDisplay (w),
1274 popup_menu_p
1275 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1276 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1277 False);
1279 XChangeProperty (XtDisplay (w), XtWindow (w),
1280 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1281 XA_ATOM, 32, PropModeReplace,
1282 (unsigned char *)&type, 1);
1286 static void
1287 make_windows_if_needed (XlwMenuWidget mw, int n)
1289 int i;
1290 int start_at;
1291 window_state* windows;
1293 if (mw->menu.windows_length >= n)
1294 return;
1296 if (!mw->menu.windows)
1298 mw->menu.windows =
1299 (window_state*)XtMalloc (n * sizeof (window_state));
1300 start_at = 0;
1302 else
1304 mw->menu.windows =
1305 (window_state*)XtRealloc ((char*)mw->menu.windows,
1306 n * sizeof (window_state));
1307 start_at = mw->menu.windows_length;
1309 mw->menu.windows_length = n;
1311 windows = mw->menu.windows;
1313 for (i = start_at; i < n; i++)
1315 Arg av[10];
1316 int ac = 0;
1317 windows [i].x = 0;
1318 windows [i].y = 0;
1319 windows [i].width = 1;
1320 windows [i].height = 1;
1321 windows [i].max_rest_width = 0;
1322 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1323 XtSetArg (av[ac], XtNheight, 1); ++ac;
1324 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1325 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1326 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1327 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1328 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1329 windows [i].w =
1330 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1331 (Widget) mw, av, ac);
1332 XtRealizeWidget (windows [i].w);
1333 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1334 windows [i].window = XtWindow (windows [i].w);
1335 windows [i].pixmap = None;
1336 #ifdef HAVE_XFT
1337 windows [i].xft_draw = 0;
1338 #endif
1339 set_window_type (windows [i].w, mw);
1341 XFlush (XtDisplay (mw));
1344 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1347 xlwmenu_window_p (Widget w, Window window)
1349 XlwMenuWidget mw = (XlwMenuWidget) w;
1350 int i;
1352 for (i = 0; i < mw->menu.windows_length; ++i)
1353 if (window == mw->menu.windows[i].window)
1354 break;
1356 return i < mw->menu.windows_length;
1359 /* Make the window fit in the screen */
1360 static void
1361 fit_to_screen (XlwMenuWidget mw,
1362 window_state *ws,
1363 window_state *previous_ws,
1364 Boolean horizontal_p)
1366 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1367 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1368 /* 1 if we are unable to avoid an overlap between
1369 this menu and the parent menu in the X dimension. */
1370 int horizontal_overlap = 0;
1372 if (ws->x < 0)
1373 ws->x = 0;
1374 else if (ws->x + ws->width > screen_width)
1376 if (!horizontal_p)
1377 /* The addition of shadow-thickness for a sub-menu's position is
1378 to reflect a similar adjustment when the menu is displayed to
1379 the right of the invoking menu-item; it makes the sub-menu
1380 look more `attached' to the menu-item. */
1381 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1382 else
1383 ws->x = screen_width - ws->width;
1384 if (ws->x < 0)
1386 ws->x = 0;
1387 horizontal_overlap = 1;
1390 /* If we overlap in X, try to avoid overlap in Y. */
1391 if (horizontal_overlap
1392 && ws->y < previous_ws->y + previous_ws->height
1393 && previous_ws->y < ws->y + ws->height)
1395 /* Put this menu right below or right above PREVIOUS_WS
1396 if there's room. */
1397 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1398 ws->y = previous_ws->y + previous_ws->height;
1399 else if (previous_ws->y - ws->height > 0)
1400 ws->y = previous_ws->y - ws->height;
1403 if (ws->y < 0)
1404 ws->y = 0;
1405 else if (ws->y + ws->height > screen_height)
1407 if (horizontal_p)
1408 ws->y = previous_ws->y - ws->height;
1409 else
1410 ws->y = screen_height - ws->height;
1411 if (ws->y < 0)
1412 ws->y = 0;
1416 static void
1417 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1419 if (ws->pixmap != None)
1421 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1422 ws->pixmap = None;
1424 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1425 ws->width, ws->height,
1426 DefaultDepthOfScreen (XtScreen (ws->w)));
1427 #ifdef HAVE_XFT
1428 if (ws->xft_draw)
1429 XftDrawDestroy (ws->xft_draw);
1430 if (mw->menu.xft_font)
1432 int screen = XScreenNumberOfScreen (mw->core.screen);
1433 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1434 ws->pixmap,
1435 DefaultVisual (XtDisplay (ws->w), screen),
1436 mw->core.colormap);
1438 else
1439 ws->xft_draw = 0;
1440 #endif
1443 /* Updates old_stack from new_stack and redisplays. */
1444 static void
1445 remap_menubar (XlwMenuWidget mw)
1447 int i;
1448 int last_same;
1449 XPoint selection_position;
1450 int old_depth = mw->menu.old_depth;
1451 int new_depth = mw->menu.new_depth;
1452 widget_value** old_stack;
1453 widget_value** new_stack;
1454 window_state* windows;
1455 widget_value* old_selection;
1456 widget_value* new_selection;
1458 /* Check that enough windows and old_stack are ready. */
1459 make_windows_if_needed (mw, new_depth);
1460 make_old_stack_space (mw, new_depth);
1461 windows = mw->menu.windows;
1462 old_stack = mw->menu.old_stack;
1463 new_stack = mw->menu.new_stack;
1465 /* compute the last identical different entry */
1466 for (i = 1; i < old_depth && i < new_depth; i++)
1467 if (old_stack [i] != new_stack [i])
1468 break;
1469 last_same = i - 1;
1471 /* Memorize the previously selected item to be able to refresh it */
1472 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1473 if (old_selection && !old_selection->enabled)
1474 old_selection = NULL;
1475 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1476 if (new_selection && !new_selection->enabled)
1477 new_selection = NULL;
1479 /* Call callback when the highlighted item changes. */
1480 if (old_selection || new_selection)
1481 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1482 (XtPointer) new_selection);
1484 /* updates old_state from new_state. It has to be done now because
1485 display_menu (called below) uses the old_stack to know what to display. */
1486 for (i = last_same + 1; i < new_depth; i++)
1488 XtPopdown (mw->menu.windows [i].w);
1489 old_stack [i] = new_stack [i];
1491 mw->menu.old_depth = new_depth;
1493 /* refresh the last selection */
1494 selection_position.x = 0;
1495 selection_position.y = 0;
1496 display_menu (mw, last_same, new_selection == old_selection,
1497 &selection_position, NULL, NULL);
1499 /* Now place the new menus. */
1500 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1502 window_state *previous_ws = &windows[i - 1];
1503 window_state *ws = &windows[i];
1505 ws->x = (previous_ws->x + selection_position.x
1506 + mw->menu.shadow_thickness);
1507 if (mw->menu.horizontal && i == 1)
1508 ws->x += mw->menu.margin;
1510 #if 0
1511 if (!mw->menu.horizontal || i > 1)
1512 ws->x += mw->menu.shadow_thickness;
1513 #endif
1515 ws->y = (previous_ws->y + selection_position.y
1516 + mw->menu.shadow_thickness);
1517 if (mw->menu.horizontal && i == 1)
1518 ws->y += mw->menu.margin;
1520 size_menu (mw, i);
1522 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1524 create_pixmap_for_menu (ws, mw);
1525 XtMoveWidget (ws->w, ws->x, ws->y);
1526 XtPopup (ws->w, XtGrabNone);
1527 XtResizeWidget (ws->w, ws->width, ws->height,
1528 mw->core.border_width);
1529 XtResizeWindow (ws->w);
1530 display_menu (mw, i, False, &selection_position, NULL, NULL);
1533 /* unmap the menus that popped down */
1534 for (i = new_depth - 1; i < old_depth; i++)
1535 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1536 XtPopdown (windows[i].w);
1539 static Boolean
1540 motion_event_is_in_menu (XlwMenuWidget mw,
1541 XMotionEvent *ev,
1542 int level,
1543 XPoint *relative_pos)
1545 window_state* ws = &mw->menu.windows [level];
1546 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1547 int x = ws->x + shadow;
1548 int y = ws->y + shadow;
1549 relative_pos->x = ev->x_root - x;
1550 relative_pos->y = ev->y_root - y;
1551 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1552 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1555 static Boolean
1556 map_event_to_widget_value (XlwMenuWidget mw,
1557 XMotionEvent *ev,
1558 widget_value **val,
1559 int *level)
1561 int i;
1562 XPoint relative_pos;
1563 window_state* ws;
1564 int inside = 0;
1566 *val = NULL;
1568 /* Find the window */
1569 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1571 ws = &mw->menu.windows [i];
1572 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1574 inside = 1;
1575 display_menu (mw, i, True, NULL, &relative_pos, val);
1577 if (*val)
1579 *level = i + 1;
1580 return True;
1585 if (!inside)
1587 if (mw->menu.inside_entry != NULL)
1588 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1589 (XtPointer) mw->menu.inside_entry);
1590 mw->menu.inside_entry = NULL;
1593 return False;
1596 \f/* Procedures */
1597 static void
1598 make_drawing_gcs (XlwMenuWidget mw)
1600 XGCValues xgcv;
1601 float scale;
1602 XtGCMask mask = GCForeground | GCBackground;
1604 #ifdef HAVE_X_I18N
1605 if (!mw->menu.fontSet && mw->menu.font)
1607 xgcv.font = mw->menu.font->fid;
1608 mask |= GCFont;
1610 #else
1611 if (mw->menu.font)
1613 xgcv.font = mw->menu.font->fid;
1614 mask |= GCFont;
1616 #endif
1617 xgcv.foreground = mw->menu.foreground;
1618 xgcv.background = mw->core.background_pixel;
1619 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1621 xgcv.foreground = mw->menu.button_foreground;
1622 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1624 xgcv.background = mw->core.background_pixel;
1626 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1628 /* Allocate color for disabled menu-items. */
1629 mw->menu.disabled_foreground = mw->menu.foreground;
1630 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1631 scale = 2.3;
1632 else
1633 scale = 0.55;
1635 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1636 mw->core.colormap,
1637 &mw->menu.disabled_foreground,
1638 scale,
1639 0x8000);
1641 if (mw->menu.foreground == mw->menu.disabled_foreground
1642 || mw->core.background_pixel == mw->menu.disabled_foreground)
1644 /* Too few colors, use stipple. */
1645 xgcv.foreground = mw->menu.foreground;
1646 xgcv.fill_style = FillStippled;
1647 xgcv.stipple = mw->menu.gray_pixmap;
1648 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1649 | GCFillStyle | GCStipple, &xgcv);
1651 else
1653 /* Many colors available, use disabled pixel. */
1654 xgcv.foreground = mw->menu.disabled_foreground;
1655 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1658 xgcv.foreground = mw->menu.button_foreground;
1659 xgcv.background = mw->core.background_pixel;
1660 xgcv.fill_style = FillStippled;
1661 xgcv.stipple = mw->menu.gray_pixmap;
1662 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1663 | GCFillStyle | GCStipple, &xgcv);
1665 xgcv.foreground = mw->core.background_pixel;
1666 xgcv.background = mw->menu.foreground;
1667 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1670 static void
1671 release_drawing_gcs (XlwMenuWidget mw)
1673 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1674 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1675 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1676 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1677 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1678 /* let's get some segvs if we try to use these... */
1679 mw->menu.foreground_gc = (GC) -1;
1680 mw->menu.button_gc = (GC) -1;
1681 mw->menu.disabled_gc = (GC) -1;
1682 mw->menu.inactive_button_gc = (GC) -1;
1683 mw->menu.background_gc = (GC) -1;
1686 #ifndef emacs
1687 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1688 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1689 #endif
1691 static void
1692 make_shadow_gcs (XlwMenuWidget mw)
1694 XGCValues xgcv;
1695 unsigned long pm = 0;
1696 Display *dpy = XtDisplay ((Widget) mw);
1697 Screen *screen = XtScreen ((Widget) mw);
1698 Colormap cmap = mw->core.colormap;
1699 XColor topc, botc;
1700 int top_frobbed = 0, bottom_frobbed = 0;
1702 mw->menu.free_top_shadow_color_p = 0;
1703 mw->menu.free_bottom_shadow_color_p = 0;
1705 if (mw->menu.top_shadow_color == -1)
1706 mw->menu.top_shadow_color = mw->core.background_pixel;
1707 else
1708 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1710 if (mw->menu.bottom_shadow_color == -1)
1711 mw->menu.bottom_shadow_color = mw->menu.foreground;
1712 else
1713 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1715 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1716 mw->menu.top_shadow_color == mw->menu.foreground)
1718 topc.pixel = mw->core.background_pixel;
1719 #ifdef emacs
1720 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1721 &topc.pixel,
1722 1.2, 0x8000))
1723 #else
1724 XQueryColor (dpy, cmap, &topc);
1725 /* don't overflow/wrap! */
1726 topc.red = MINL (65535, topc.red * 1.2);
1727 topc.green = MINL (65535, topc.green * 1.2);
1728 topc.blue = MINL (65535, topc.blue * 1.2);
1729 if (XAllocColor (dpy, cmap, &topc))
1730 #endif
1732 mw->menu.top_shadow_color = topc.pixel;
1733 mw->menu.free_top_shadow_color_p = 1;
1734 top_frobbed = 1;
1737 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1738 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1740 botc.pixel = mw->core.background_pixel;
1741 #ifdef emacs
1742 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1743 &botc.pixel,
1744 0.6, 0x4000))
1745 #else
1746 XQueryColor (dpy, cmap, &botc);
1747 botc.red *= 0.6;
1748 botc.green *= 0.6;
1749 botc.blue *= 0.6;
1750 if (XAllocColor (dpy, cmap, &botc))
1751 #endif
1753 mw->menu.bottom_shadow_color = botc.pixel;
1754 mw->menu.free_bottom_shadow_color_p = 1;
1755 bottom_frobbed = 1;
1759 if (top_frobbed && bottom_frobbed)
1761 if (topc.pixel == botc.pixel)
1763 if (botc.pixel == mw->menu.foreground)
1765 if (mw->menu.free_top_shadow_color_p)
1767 x_free_dpy_colors (dpy, screen, cmap,
1768 &mw->menu.top_shadow_color, 1);
1769 mw->menu.free_top_shadow_color_p = 0;
1771 mw->menu.top_shadow_color = mw->core.background_pixel;
1773 else
1775 if (mw->menu.free_bottom_shadow_color_p)
1777 x_free_dpy_colors (dpy, screen, cmap,
1778 &mw->menu.bottom_shadow_color, 1);
1779 mw->menu.free_bottom_shadow_color_p = 0;
1781 mw->menu.bottom_shadow_color = mw->menu.foreground;
1786 if (!mw->menu.top_shadow_pixmap &&
1787 mw->menu.top_shadow_color == mw->core.background_pixel)
1789 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1790 if (mw->menu.free_top_shadow_color_p)
1792 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1793 mw->menu.free_top_shadow_color_p = 0;
1795 mw->menu.top_shadow_color = mw->menu.foreground;
1797 if (!mw->menu.bottom_shadow_pixmap &&
1798 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1800 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1801 if (mw->menu.free_bottom_shadow_color_p)
1803 x_free_dpy_colors (dpy, screen, cmap,
1804 &mw->menu.bottom_shadow_color, 1);
1805 mw->menu.free_bottom_shadow_color_p = 0;
1807 mw->menu.bottom_shadow_color = mw->menu.foreground;
1810 xgcv.fill_style = FillStippled;
1811 xgcv.foreground = mw->menu.top_shadow_color;
1812 xgcv.stipple = mw->menu.top_shadow_pixmap;
1813 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1814 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1816 xgcv.foreground = mw->menu.bottom_shadow_color;
1817 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1818 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1819 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1823 static void
1824 release_shadow_gcs (XlwMenuWidget mw)
1826 Display *dpy = XtDisplay ((Widget) mw);
1827 Screen *screen = XtScreen ((Widget) mw);
1828 Colormap cmap = mw->core.colormap;
1829 Pixel px[2];
1830 int i = 0;
1832 if (mw->menu.free_top_shadow_color_p)
1833 px[i++] = mw->menu.top_shadow_color;
1834 if (mw->menu.free_bottom_shadow_color_p)
1835 px[i++] = mw->menu.bottom_shadow_color;
1836 if (i > 0)
1837 x_free_dpy_colors (dpy, screen, cmap, px, i);
1839 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1840 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1843 #ifdef HAVE_XFT
1844 static XftFont *
1845 getDefaultXftFont (XlwMenuWidget mw)
1847 int screen = XScreenNumberOfScreen (mw->core.screen);
1848 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1851 static int
1852 openXftFont (XlwMenuWidget mw)
1854 char *fname = mw->menu.fontName;
1856 mw->menu.xft_font = 0;
1857 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1859 if (fname && strcmp (fname, "none") != 0)
1861 int screen = XScreenNumberOfScreen (mw->core.screen);
1862 int len = strlen (fname), i = len-1;
1863 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1864 while (i > 0 && isdigit (fname[i]))
1865 --i;
1866 if (fname[i] == ' ')
1868 fname = xstrdup (mw->menu.fontName);
1869 fname[i] = '-';
1872 mw->menu.font = XLoadQueryFont (XtDisplay (mw), fname);
1873 if (!mw->menu.font)
1875 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1876 if (!mw->menu.xft_font)
1878 fprintf (stderr, "Can't find font '%s'\n", fname);
1879 mw->menu.xft_font = getDefaultXftFont (mw);
1884 if (fname != mw->menu.fontName) xfree (fname);
1886 return mw->menu.xft_font != 0;
1888 #endif
1890 static void
1891 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1893 /* Get the GCs and the widget size */
1894 XlwMenuWidget mw = (XlwMenuWidget) w;
1895 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1896 Display* display = XtDisplay (mw);
1898 #if 0
1899 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1901 /* _XtCreate is freeing the object that was passed to us,
1902 so make a copy that we will actually keep. */
1903 memcpy (tem, mw->menu.contents, sizeof (widget_value));
1904 mw->menu.contents = tem;
1905 #endif
1907 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1908 mw->menu.cursor = mw->menu.cursor_shape;
1910 mw->menu.gray_pixmap
1911 = XCreatePixmapFromBitmapData (display, window, gray_bits,
1912 gray_width, gray_height,
1913 (unsigned long)1, (unsigned long)0, 1);
1915 #ifdef HAVE_XFT
1916 if (openXftFont (mw))
1918 else
1919 #endif
1921 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1922 if (!mw->menu.font)
1924 mw->menu.font = XLoadQueryFont (display, "fixed");
1925 if (!mw->menu.font)
1927 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1928 abort ();
1933 #ifdef HAVE_X_I18N
1934 if (mw->menu.fontSet)
1935 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1936 #endif
1938 make_drawing_gcs (mw);
1939 make_shadow_gcs (mw);
1941 mw->menu.popped_up = False;
1943 mw->menu.old_depth = 1;
1944 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1945 mw->menu.old_stack_length = 1;
1946 mw->menu.old_stack [0] = mw->menu.contents;
1948 mw->menu.new_depth = 0;
1949 mw->menu.new_stack = 0;
1950 mw->menu.new_stack_length = 0;
1951 push_new_stack (mw, mw->menu.contents);
1953 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1954 mw->menu.windows_length = 1;
1955 mw->menu.windows [0].x = 0;
1956 mw->menu.windows [0].y = 0;
1957 mw->menu.windows [0].width = 0;
1958 mw->menu.windows [0].height = 0;
1959 mw->menu.windows [0].max_rest_width = 0;
1960 mw->menu.windows [0].pixmap = None;
1961 #ifdef HAVE_XFT
1962 mw->menu.windows [0].xft_draw = 0;
1963 #endif
1964 size_menu (mw, 0);
1966 mw->core.width = mw->menu.windows [0].width;
1967 mw->core.height = mw->menu.windows [0].height;
1970 static void
1971 XlwMenuClassInitialize (void)
1975 static void
1976 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1978 XlwMenuWidget mw = (XlwMenuWidget)w;
1979 XSetWindowAttributes xswa;
1980 int mask;
1982 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1983 (w, valueMask, attributes);
1985 xswa.save_under = True;
1986 xswa.cursor = mw->menu.cursor_shape;
1987 mask = CWSaveUnder | CWCursor;
1988 /* I sometimes get random BadCursor errors while creating the first
1989 frame on a display. I can not find their reason, but they are
1990 annoying so for now let's ignore any errors here. -- lorentey */
1991 #ifdef emacs
1992 x_catch_errors (XtDisplay (w));
1993 #endif
1994 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1995 #ifdef emacs
1996 x_uncatch_errors ();
1997 #endif
1999 mw->menu.windows [0].w = w;
2000 mw->menu.windows [0].window = XtWindow (w);
2001 mw->menu.windows [0].x = w->core.x;
2002 mw->menu.windows [0].y = w->core.y;
2003 mw->menu.windows [0].width = w->core.width;
2004 mw->menu.windows [0].height = w->core.height;
2006 set_window_type (mw->menu.windows [0].w, mw);
2007 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2009 #ifdef HAVE_XFT
2010 if (mw->menu.xft_font)
2012 XColor colors[3];
2013 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
2014 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
2015 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
2016 = mw->menu.disabled_foreground;
2017 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
2018 mw->menu.xft_fg.color.alpha = 0xFFFF;
2019 mw->menu.xft_fg.color.red = colors[0].red;
2020 mw->menu.xft_fg.color.green = colors[0].green;
2021 mw->menu.xft_fg.color.blue = colors[0].blue;
2022 mw->menu.xft_bg.color.alpha = 0xFFFF;
2023 mw->menu.xft_bg.color.red = colors[1].red;
2024 mw->menu.xft_bg.color.green = colors[1].green;
2025 mw->menu.xft_bg.color.blue = colors[1].blue;
2026 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2027 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2028 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2029 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2031 #endif
2034 /* Only the toplevel menubar/popup is a widget so it's the only one that
2035 receives expose events through Xt. So we repaint all the other panes
2036 when receiving an Expose event. */
2037 static void
2038 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2040 XlwMenuWidget mw = (XlwMenuWidget)w;
2042 /* If we have a depth beyond 1, it's because a submenu was displayed.
2043 If the submenu has been destroyed, set the depth back to 1. */
2044 if (submenu_destroyed)
2046 mw->menu.old_depth = 1;
2047 submenu_destroyed = 0;
2050 display_menu (mw, 0, False, NULL, NULL, NULL);
2054 /* Part of a hack to make the menu redisplay when a tooltip frame
2055 over a menu item is unmapped. */
2057 void
2058 xlwmenu_redisplay (Widget w)
2060 XlwMenuRedisplay (w, NULL, None);
2063 static void
2064 XlwMenuDestroy (Widget w)
2066 int i;
2067 XlwMenuWidget mw = (XlwMenuWidget) w;
2069 if (pointer_grabbed)
2070 ungrab_all ((Widget)w, CurrentTime);
2071 pointer_grabbed = 0;
2073 submenu_destroyed = 1;
2075 release_drawing_gcs (mw);
2076 release_shadow_gcs (mw);
2078 /* this doesn't come from the resource db but is created explicitly
2079 so we must free it ourselves. */
2080 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2081 mw->menu.gray_pixmap = (Pixmap) -1;
2083 #if 0
2084 /* Do free mw->menu.contents because nowadays we copy it
2085 during initialization. */
2086 XtFree (mw->menu.contents);
2087 #endif
2089 /* Don't free mw->menu.contents because that comes from our creator.
2090 The `*_stack' elements are just pointers into `contents' so leave
2091 that alone too. But free the stacks themselves. */
2092 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2093 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2095 /* Remember, you can't free anything that came from the resource
2096 database. This includes:
2097 mw->menu.cursor
2098 mw->menu.top_shadow_pixmap
2099 mw->menu.bottom_shadow_pixmap
2100 mw->menu.font
2101 Also the color cells of top_shadow_color, bottom_shadow_color,
2102 foreground, and button_foreground will never be freed until this
2103 client exits. Nice, eh?
2106 #ifdef HAVE_XFT
2107 if (mw->menu.windows [0].xft_draw)
2108 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2109 if (mw->menu.xft_font)
2110 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2111 #endif
2113 if (mw->menu.windows [0].pixmap != None)
2114 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2115 /* start from 1 because the one in slot 0 is w->core.window */
2116 for (i = 1; i < mw->menu.windows_length; i++)
2118 if (mw->menu.windows [i].pixmap != None)
2119 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2120 #ifdef HAVE_XFT
2121 if (mw->menu.windows [i].xft_draw)
2122 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2123 #endif
2126 if (mw->menu.windows)
2127 XtFree ((char *) mw->menu.windows);
2130 #ifdef HAVE_XFT
2131 static int
2132 fontname_changed (XlwMenuWidget newmw,
2133 XlwMenuWidget oldmw)
2135 /* This will force a new XftFont even if the same string is set.
2136 This is good, as rendering parameters may have changed and
2137 we just want to do a redisplay. */
2138 return newmw->menu.fontName != oldmw->menu.fontName;
2140 #endif
2142 static Boolean
2143 XlwMenuSetValues (Widget current, Widget request, Widget new,
2144 ArgList args, Cardinal *num_args)
2146 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2147 XlwMenuWidget newmw = (XlwMenuWidget)new;
2148 Boolean do_redisplay = False;
2150 if (newmw->menu.contents
2151 && newmw->menu.contents->contents
2152 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2153 do_redisplay = True;
2154 /* Do redisplay if the contents are entirely eliminated. */
2155 if (newmw->menu.contents
2156 && newmw->menu.contents->contents == 0
2157 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2158 do_redisplay = True;
2160 if (newmw->core.background_pixel != oldmw->core.background_pixel
2161 || newmw->menu.foreground != oldmw->menu.foreground
2162 #ifdef HAVE_XFT
2163 || fontname_changed (newmw, oldmw)
2164 #endif
2165 #ifdef HAVE_X_I18N
2166 || newmw->menu.fontSet != oldmw->menu.fontSet
2167 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2168 #else
2169 || newmw->menu.font != oldmw->menu.font
2170 #endif
2173 int i;
2174 release_drawing_gcs (newmw);
2175 make_drawing_gcs (newmw);
2177 release_shadow_gcs (newmw);
2178 /* Cause the shadow colors to be recalculated. */
2179 newmw->menu.top_shadow_color = -1;
2180 newmw->menu.bottom_shadow_color = -1;
2181 make_shadow_gcs (newmw);
2183 do_redisplay = True;
2185 if (XtIsRealized (current))
2186 /* If the menu is currently displayed, change the display. */
2187 for (i = 0; i < oldmw->menu.windows_length; i++)
2189 XSetWindowBackground (XtDisplay (oldmw),
2190 oldmw->menu.windows [i].window,
2191 newmw->core.background_pixel);
2192 /* clear windows and generate expose events */
2193 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2194 0, 0, 0, 0, True);
2198 #ifdef HAVE_XFT
2199 if (fontname_changed (newmw, oldmw))
2201 int i;
2202 int screen = XScreenNumberOfScreen (newmw->core.screen);
2203 if (newmw->menu.xft_font)
2204 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2205 openXftFont (newmw);
2206 for (i = 0; i < newmw->menu.windows_length; i++)
2208 if (newmw->menu.windows [i].xft_draw)
2209 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2210 newmw->menu.windows [i].xft_draw = 0;
2212 if (newmw->menu.xft_font)
2213 for (i = 0; i < newmw->menu.windows_length; i++)
2214 newmw->menu.windows [i].xft_draw
2215 = XftDrawCreate (XtDisplay (newmw),
2216 newmw->menu.windows [i].window,
2217 DefaultVisual (XtDisplay (newmw), screen),
2218 newmw->core.colormap);
2220 #endif
2221 #ifdef HAVE_X_I18N
2222 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2224 do_redisplay = True;
2225 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2227 #endif
2229 return do_redisplay;
2232 static void
2233 XlwMenuResize (Widget w)
2235 XlwMenuWidget mw = (XlwMenuWidget)w;
2237 if (mw->menu.popped_up)
2239 /* Don't allow the popup menu to resize itself. */
2240 mw->core.width = mw->menu.windows [0].width;
2241 mw->core.height = mw->menu.windows [0].height;
2242 mw->core.parent->core.width = mw->core.width;
2243 mw->core.parent->core.height = mw->core.height;
2245 else
2247 mw->menu.windows [0].width = mw->core.width;
2248 mw->menu.windows [0].height = mw->core.height;
2249 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2253 \f/* Action procedures */
2254 static void
2255 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2257 widget_value* val;
2258 int level;
2260 if (!map_event_to_widget_value (mw, ev, &val, &level))
2261 pop_new_stack_if_no_contents (mw);
2262 else
2263 set_new_state (mw, val, level);
2264 remap_menubar (mw);
2266 /* Sync with the display. Makes it feel better on X terms. */
2267 XSync (XtDisplay (mw), False);
2270 static void
2271 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2273 int x = ev->x_root;
2274 int y = ev->y_root;
2275 int state = ev->state;
2276 XMotionEvent oldev = *ev;
2278 /* allow motion events to be generated again */
2279 if (ev->is_hint
2280 && XQueryPointer (XtDisplay (mw), ev->window,
2281 &ev->root, &ev->subwindow,
2282 &ev->x_root, &ev->y_root,
2283 &ev->x, &ev->y,
2284 &ev->state)
2285 && ev->state == state
2286 && (ev->x_root != x || ev->y_root != y))
2287 handle_single_motion_event (mw, ev);
2288 else
2289 handle_single_motion_event (mw, &oldev);
2292 static void
2293 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2295 XlwMenuWidget mw = (XlwMenuWidget)w;
2297 if (!mw->menu.popped_up)
2299 menu_post_event = *ev;
2300 /* If event is set to CurrentTime, get the last known time stamp.
2301 This is for calculating if (popup) menus should stay up after
2302 a fast click. */
2303 if (menu_post_event.xbutton.time == CurrentTime)
2304 menu_post_event.xbutton.time
2305 = XtLastTimestampProcessed (XtDisplay (w));
2307 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2309 else
2311 /* If we push a button while the menu is posted semipermanently,
2312 releasing the button should always pop the menu down. */
2313 next_release_must_exit = 1;
2315 /* notes the absolute position of the menubar window */
2316 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2317 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2319 /* handles the down like a move, slots are compatible */
2320 ev->xmotion.is_hint = 0;
2321 handle_motion_event (mw, &ev->xmotion);
2325 static void
2326 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2328 XlwMenuWidget mw = (XlwMenuWidget)w;
2329 if (mw->menu.popped_up)
2330 handle_motion_event (mw, &ev->xmotion);
2333 /* Do nothing.
2334 This is how we handle presses and releases of modifier keys. */
2335 static void
2336 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2340 static widget_value *
2341 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2343 widget_value *current = item;
2344 enum menu_separator separator;
2346 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2347 || (skip_titles && !current->call_data && !current->contents))
2348 if (current->next)
2349 current=current->next;
2350 else
2351 return NULL;
2353 return current;
2356 static widget_value *
2357 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2359 widget_value *current = item;
2360 enum menu_separator separator;
2362 while (current->next && (current=current->next) &&
2363 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2364 || (skip_titles && !current->call_data && !current->contents)))
2367 if (current == item)
2369 if (mw->menu.old_depth < 2)
2370 return current;
2371 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2373 while (lw_separator_p (current->name, &separator, 0)
2374 || !current->enabled
2375 || (skip_titles && !current->call_data
2376 && !current->contents))
2378 if (current->next)
2379 current=current->next;
2381 if (current == item)
2382 break;
2387 return current;
2390 static widget_value *
2391 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2393 widget_value *current = item;
2394 widget_value *prev = item;
2396 while ((current=find_next_selectable (mw, current, skip_titles))
2397 != item)
2399 if (prev == current)
2400 break;
2401 prev=current;
2404 return prev;
2407 static void
2408 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2410 XlwMenuWidget mw = (XlwMenuWidget) w;
2411 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2412 int popup_menu_p = mw->menu.top_depth == 1;
2414 /* Inside top-level menu-bar? */
2415 if (mw->menu.old_depth == mw->menu.top_depth)
2416 /* When <down> in the menu-bar is pressed, display the corresponding
2417 sub-menu and select the first selectable menu item there.
2418 If this is a popup menu, skip title item of the popup. */
2419 set_new_state (mw,
2420 find_first_selectable (mw,
2421 selected_item->contents,
2422 popup_menu_p),
2423 mw->menu.old_depth);
2424 else
2425 /* Highlight next possible (enabled and not separator) menu item. */
2426 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2427 mw->menu.old_depth - 1);
2429 remap_menubar (mw);
2432 static void
2433 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2435 XlwMenuWidget mw = (XlwMenuWidget) w;
2436 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2437 int popup_menu_p = mw->menu.top_depth == 1;
2439 /* Inside top-level menu-bar? */
2440 if (mw->menu.old_depth == mw->menu.top_depth)
2442 /* FIXME: this is tricky. <up> in the menu-bar should select the
2443 last selectable item in the list. So we select the first
2444 selectable one and find the previous selectable item. Is there
2445 a better way? */
2446 /* If this is a popup menu, skip title item of the popup. */
2447 set_new_state (mw,
2448 find_first_selectable (mw,
2449 selected_item->contents,
2450 popup_menu_p),
2451 mw->menu.old_depth);
2452 remap_menubar (mw);
2453 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2454 set_new_state (mw,
2455 find_prev_selectable (mw,
2456 selected_item,
2457 popup_menu_p),
2458 mw->menu.old_depth - 1);
2460 else
2461 /* Highlight previous (enabled and not separator) menu item. */
2462 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2463 mw->menu.old_depth - 1);
2465 remap_menubar (mw);
2468 void
2469 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2471 XlwMenuWidget mw = (XlwMenuWidget) w;
2472 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2474 /* Inside top-level menu-bar? */
2475 if (mw->menu.old_depth == mw->menu.top_depth)
2476 /* When <left> in the menu-bar is pressed, display the previous item on
2477 the menu-bar. If the current item is the first one, highlight the
2478 last item in the menubar (probably Help). */
2479 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2480 mw->menu.old_depth - 1);
2481 else if (mw->menu.old_depth == 1
2482 && selected_item->contents) /* Is this menu item expandable? */
2484 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2485 remap_menubar (mw);
2486 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2487 if (!selected_item->enabled && find_first_selectable (mw,
2488 selected_item,
2490 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2491 mw->menu.old_depth - 1);
2494 else
2496 pop_new_stack_if_no_contents (mw);
2497 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2498 mw->menu.old_depth - 2);
2501 remap_menubar (mw);
2504 void
2505 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2507 XlwMenuWidget mw = (XlwMenuWidget) w;
2508 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2510 /* Inside top-level menu-bar? */
2511 if (mw->menu.old_depth == mw->menu.top_depth)
2512 /* When <right> in the menu-bar is pressed, display the next item on
2513 the menu-bar. If the current item is the last one, highlight the
2514 first item (probably File). */
2515 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2516 mw->menu.old_depth - 1);
2517 else if (selected_item->contents) /* Is this menu item expandable? */
2519 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2520 remap_menubar (mw);
2521 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2522 if (!selected_item->enabled && find_first_selectable (mw,
2523 selected_item,
2525 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2526 mw->menu.old_depth - 1);
2528 else
2530 pop_new_stack_if_no_contents (mw);
2531 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2532 mw->menu.old_depth - 2);
2535 remap_menubar (mw);
2538 /* Handle key press and release events while menu is popped up.
2539 Our action is to get rid of the menu. */
2540 static void
2541 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2543 XlwMenuWidget mw = (XlwMenuWidget)w;
2545 /* Pop down everything. */
2546 mw->menu.new_depth = 1;
2547 remap_menubar (mw);
2549 if (mw->menu.popped_up)
2551 mw->menu.popped_up = False;
2552 ungrab_all ((Widget)mw, ev->xmotion.time);
2553 if (XtIsShell (XtParent ((Widget) mw)))
2554 XtPopdown (XtParent ((Widget) mw));
2555 else
2557 XtRemoveGrab ((Widget) mw);
2558 display_menu (mw, 0, False, NULL, NULL, NULL);
2562 /* callback */
2563 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2566 static void
2567 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2569 XlwMenuWidget mw = (XlwMenuWidget)w;
2570 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2572 /* If user releases the button quickly, without selecting anything,
2573 after the initial down-click that brought the menu up,
2574 do nothing. */
2575 if ((selected_item == 0
2576 || ((widget_value *) selected_item)->call_data == 0)
2577 && !next_release_must_exit
2578 && (ev->xbutton.time - menu_post_event.xbutton.time
2579 < XtGetMultiClickTime (XtDisplay (w))))
2580 return;
2582 /* pop down everything. */
2583 mw->menu.new_depth = 1;
2584 remap_menubar (mw);
2586 if (mw->menu.popped_up)
2588 mw->menu.popped_up = False;
2589 ungrab_all ((Widget)mw, ev->xmotion.time);
2590 if (XtIsShell (XtParent ((Widget) mw)))
2591 XtPopdown (XtParent ((Widget) mw));
2592 else
2594 XtRemoveGrab ((Widget) mw);
2595 display_menu (mw, 0, False, NULL, NULL, NULL);
2599 /* callback */
2600 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2604 \f/* Special code to pop-up a menu */
2605 static void
2606 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2608 int x = event->x_root;
2609 int y = event->y_root;
2610 int w;
2611 int h;
2612 int borderwidth = mw->menu.shadow_thickness;
2613 Screen* screen = XtScreen (mw);
2614 Display *display = XtDisplay (mw);
2616 next_release_must_exit = 0;
2618 mw->menu.inside_entry = NULL;
2619 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2621 if (XtIsShell (XtParent ((Widget)mw)))
2622 size_menu (mw, 0);
2624 w = mw->menu.windows [0].width;
2625 h = mw->menu.windows [0].height;
2627 x -= borderwidth;
2628 y -= borderwidth;
2629 if (x < borderwidth)
2630 x = borderwidth;
2631 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2632 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2633 if (y < borderwidth)
2634 y = borderwidth;
2635 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2636 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2638 mw->menu.popped_up = True;
2639 if (XtIsShell (XtParent ((Widget)mw)))
2641 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2642 XtParent ((Widget)mw)->core.border_width);
2643 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2644 display_menu (mw, 0, False, NULL, NULL, NULL);
2645 mw->menu.windows [0].x = x + borderwidth;
2646 mw->menu.windows [0].y = y + borderwidth;
2647 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2649 else
2651 XEvent *ev = (XEvent *) event;
2653 XtAddGrab ((Widget) mw, True, True);
2655 /* notes the absolute position of the menubar window */
2656 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2657 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2658 mw->menu.top_depth = 2;
2661 #ifdef emacs
2662 x_catch_errors (display);
2663 #endif
2664 if (XtGrabPointer ((Widget)mw, False,
2665 (PointerMotionMask
2666 | PointerMotionHintMask
2667 | ButtonReleaseMask
2668 | ButtonPressMask),
2669 GrabModeAsync, GrabModeAsync, None,
2670 mw->menu.cursor_shape,
2671 event->time) == Success)
2673 if (! GRAB_KEYBOARD
2674 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2675 GrabModeAsync, event->time) == Success)
2677 XtSetKeyboardFocus((Widget)mw, None);
2678 pointer_grabbed = 1;
2680 else
2681 XtUngrabPointer ((Widget)mw, event->time);
2684 #ifdef emacs
2685 if (x_had_errors_p (display))
2687 pointer_grabbed = 0;
2688 XtUngrabPointer ((Widget)mw, event->time);
2690 x_uncatch_errors ();
2691 #endif
2693 ((XMotionEvent*)event)->is_hint = 0;
2694 handle_motion_event (mw, (XMotionEvent*)event);