Add forgotten ChangeLog entry.
[emacs.git] / lwlib / xlwmenu.c
blobe9ec604ae7f124524069bec70bbfebc8c33014e2
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 #ifndef emacs
1697 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1698 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1699 #endif
1701 static void
1702 make_shadow_gcs (XlwMenuWidget mw)
1704 XGCValues xgcv;
1705 unsigned long pm = 0;
1706 Display *dpy = XtDisplay ((Widget) mw);
1707 Screen *screen = XtScreen ((Widget) mw);
1708 Colormap cmap = mw->core.colormap;
1709 XColor topc, botc;
1710 int top_frobbed = 0, bottom_frobbed = 0;
1712 mw->menu.free_top_shadow_color_p = 0;
1713 mw->menu.free_bottom_shadow_color_p = 0;
1715 if (mw->menu.top_shadow_color == -1)
1716 mw->menu.top_shadow_color = mw->core.background_pixel;
1717 else
1718 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1720 if (mw->menu.bottom_shadow_color == -1)
1721 mw->menu.bottom_shadow_color = mw->menu.foreground;
1722 else
1723 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1725 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1726 mw->menu.top_shadow_color == mw->menu.foreground)
1728 topc.pixel = mw->core.background_pixel;
1729 #ifdef emacs
1730 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1731 &topc.pixel,
1732 1.2, 0x8000))
1733 #else
1734 XQueryColor (dpy, cmap, &topc);
1735 /* don't overflow/wrap! */
1736 topc.red = MINL (65535, topc.red * 1.2);
1737 topc.green = MINL (65535, topc.green * 1.2);
1738 topc.blue = MINL (65535, topc.blue * 1.2);
1739 if (XAllocColor (dpy, cmap, &topc))
1740 #endif
1742 mw->menu.top_shadow_color = topc.pixel;
1743 mw->menu.free_top_shadow_color_p = 1;
1744 top_frobbed = 1;
1747 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1748 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1750 botc.pixel = mw->core.background_pixel;
1751 #ifdef emacs
1752 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1753 &botc.pixel,
1754 0.6, 0x4000))
1755 #else
1756 XQueryColor (dpy, cmap, &botc);
1757 botc.red *= 0.6;
1758 botc.green *= 0.6;
1759 botc.blue *= 0.6;
1760 if (XAllocColor (dpy, cmap, &botc))
1761 #endif
1763 mw->menu.bottom_shadow_color = botc.pixel;
1764 mw->menu.free_bottom_shadow_color_p = 1;
1765 bottom_frobbed = 1;
1769 if (top_frobbed && bottom_frobbed)
1771 if (topc.pixel == botc.pixel)
1773 if (botc.pixel == mw->menu.foreground)
1775 if (mw->menu.free_top_shadow_color_p)
1777 x_free_dpy_colors (dpy, screen, cmap,
1778 &mw->menu.top_shadow_color, 1);
1779 mw->menu.free_top_shadow_color_p = 0;
1781 mw->menu.top_shadow_color = mw->core.background_pixel;
1783 else
1785 if (mw->menu.free_bottom_shadow_color_p)
1787 x_free_dpy_colors (dpy, screen, cmap,
1788 &mw->menu.bottom_shadow_color, 1);
1789 mw->menu.free_bottom_shadow_color_p = 0;
1791 mw->menu.bottom_shadow_color = mw->menu.foreground;
1796 if (!mw->menu.top_shadow_pixmap &&
1797 mw->menu.top_shadow_color == mw->core.background_pixel)
1799 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1800 if (mw->menu.free_top_shadow_color_p)
1802 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1803 mw->menu.free_top_shadow_color_p = 0;
1805 mw->menu.top_shadow_color = mw->menu.foreground;
1807 if (!mw->menu.bottom_shadow_pixmap &&
1808 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1810 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1811 if (mw->menu.free_bottom_shadow_color_p)
1813 x_free_dpy_colors (dpy, screen, cmap,
1814 &mw->menu.bottom_shadow_color, 1);
1815 mw->menu.free_bottom_shadow_color_p = 0;
1817 mw->menu.bottom_shadow_color = mw->menu.foreground;
1820 xgcv.fill_style = FillStippled;
1821 xgcv.foreground = mw->menu.top_shadow_color;
1822 xgcv.stipple = mw->menu.top_shadow_pixmap;
1823 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1824 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1826 xgcv.foreground = mw->menu.bottom_shadow_color;
1827 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1828 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1829 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1833 static void
1834 release_shadow_gcs (XlwMenuWidget mw)
1836 Display *dpy = XtDisplay ((Widget) mw);
1837 Screen *screen = XtScreen ((Widget) mw);
1838 Colormap cmap = mw->core.colormap;
1839 Pixel px[2];
1840 int i = 0;
1842 if (mw->menu.free_top_shadow_color_p)
1843 px[i++] = mw->menu.top_shadow_color;
1844 if (mw->menu.free_bottom_shadow_color_p)
1845 px[i++] = mw->menu.bottom_shadow_color;
1846 if (i > 0)
1847 x_free_dpy_colors (dpy, screen, cmap, px, i);
1849 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1850 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1853 #ifdef HAVE_XFT
1854 static XftFont *
1855 getDefaultXftFont (XlwMenuWidget mw)
1857 int screen = XScreenNumberOfScreen (mw->core.screen);
1858 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1861 static int
1862 openXftFont (XlwMenuWidget mw)
1864 char *fname = mw->menu.fontName;
1866 mw->menu.xft_font = 0;
1867 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1869 if (fname && strcmp (fname, "none") != 0)
1871 int screen = XScreenNumberOfScreen (mw->core.screen);
1872 int len = strlen (fname), i = len-1;
1873 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1874 while (i > 0 && isdigit (fname[i]))
1875 --i;
1876 if (fname[i] == ' ')
1878 fname = xstrdup (mw->menu.fontName);
1879 fname[i] = '-';
1882 mw->menu.font = XLoadQueryFont (XtDisplay (mw), fname);
1883 if (!mw->menu.font)
1885 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1886 if (!mw->menu.xft_font)
1888 fprintf (stderr, "Can't find font '%s'\n", fname);
1889 mw->menu.xft_font = getDefaultXftFont (mw);
1894 if (fname != mw->menu.fontName) free (fname);
1896 return mw->menu.xft_font != 0;
1898 #endif
1900 static void
1901 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1903 /* Get the GCs and the widget size */
1904 XlwMenuWidget mw = (XlwMenuWidget) w;
1905 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1906 Display* display = XtDisplay (mw);
1908 #if 0
1909 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1911 /* _XtCreate is freeing the object that was passed to us,
1912 so make a copy that we will actually keep. */
1913 memcpy (tem, mw->menu.contents, sizeof (widget_value));
1914 mw->menu.contents = tem;
1915 #endif
1917 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1918 mw->menu.cursor = mw->menu.cursor_shape;
1920 mw->menu.gray_pixmap
1921 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1922 gray_bitmap_width, gray_bitmap_height,
1923 (unsigned long)1, (unsigned long)0, 1);
1925 #ifdef HAVE_XFT
1926 if (openXftFont (mw))
1928 else
1929 #endif
1931 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1932 if (!mw->menu.font)
1934 mw->menu.font = XLoadQueryFont (display, "fixed");
1935 if (!mw->menu.font)
1937 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1938 abort ();
1943 #ifdef HAVE_X_I18N
1944 if (mw->menu.fontSet)
1945 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1946 #endif
1948 make_drawing_gcs (mw);
1949 make_shadow_gcs (mw);
1951 mw->menu.popped_up = False;
1953 mw->menu.old_depth = 1;
1954 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1955 mw->menu.old_stack_length = 1;
1956 mw->menu.old_stack [0] = mw->menu.contents;
1958 mw->menu.new_depth = 0;
1959 mw->menu.new_stack = 0;
1960 mw->menu.new_stack_length = 0;
1961 push_new_stack (mw, mw->menu.contents);
1963 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1964 mw->menu.windows_length = 1;
1965 mw->menu.windows [0].x = 0;
1966 mw->menu.windows [0].y = 0;
1967 mw->menu.windows [0].width = 0;
1968 mw->menu.windows [0].height = 0;
1969 mw->menu.windows [0].max_rest_width = 0;
1970 mw->menu.windows [0].pixmap = None;
1971 #ifdef HAVE_XFT
1972 mw->menu.windows [0].xft_draw = 0;
1973 #endif
1974 size_menu (mw, 0);
1976 mw->core.width = mw->menu.windows [0].width;
1977 mw->core.height = mw->menu.windows [0].height;
1980 static void
1981 XlwMenuClassInitialize (void)
1985 static void
1986 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1988 XlwMenuWidget mw = (XlwMenuWidget)w;
1989 XSetWindowAttributes xswa;
1990 int mask;
1992 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1993 (w, valueMask, attributes);
1995 xswa.save_under = True;
1996 xswa.cursor = mw->menu.cursor_shape;
1997 mask = CWSaveUnder | CWCursor;
1998 /* I sometimes get random BadCursor errors while creating the first
1999 frame on a display. I can not find their reason, but they are
2000 annoying so for now let's ignore any errors here. -- lorentey */
2001 #ifdef emacs
2002 x_catch_errors (XtDisplay (w));
2003 #endif
2004 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
2005 #ifdef emacs
2006 x_uncatch_errors ();
2007 #endif
2009 mw->menu.windows [0].w = w;
2010 mw->menu.windows [0].window = XtWindow (w);
2011 mw->menu.windows [0].x = w->core.x;
2012 mw->menu.windows [0].y = w->core.y;
2013 mw->menu.windows [0].width = w->core.width;
2014 mw->menu.windows [0].height = w->core.height;
2016 set_window_type (mw->menu.windows [0].w, mw);
2017 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2019 #ifdef HAVE_XFT
2020 if (mw->menu.xft_font)
2022 XColor colors[3];
2023 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
2024 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
2025 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
2026 = mw->menu.disabled_foreground;
2027 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
2028 mw->menu.xft_fg.color.alpha = 0xFFFF;
2029 mw->menu.xft_fg.color.red = colors[0].red;
2030 mw->menu.xft_fg.color.green = colors[0].green;
2031 mw->menu.xft_fg.color.blue = colors[0].blue;
2032 mw->menu.xft_bg.color.alpha = 0xFFFF;
2033 mw->menu.xft_bg.color.red = colors[1].red;
2034 mw->menu.xft_bg.color.green = colors[1].green;
2035 mw->menu.xft_bg.color.blue = colors[1].blue;
2036 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2037 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2038 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2039 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2041 #endif
2044 /* Only the toplevel menubar/popup is a widget so it's the only one that
2045 receives expose events through Xt. So we repaint all the other panes
2046 when receiving an Expose event. */
2047 static void
2048 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2050 XlwMenuWidget mw = (XlwMenuWidget)w;
2052 /* If we have a depth beyond 1, it's because a submenu was displayed.
2053 If the submenu has been destroyed, set the depth back to 1. */
2054 if (submenu_destroyed)
2056 mw->menu.old_depth = 1;
2057 submenu_destroyed = 0;
2060 display_menu (mw, 0, False, NULL, NULL, NULL);
2064 /* Part of a hack to make the menu redisplay when a tooltip frame
2065 over a menu item is unmapped. */
2067 void
2068 xlwmenu_redisplay (Widget w)
2070 XlwMenuRedisplay (w, NULL, None);
2073 static void
2074 XlwMenuDestroy (Widget w)
2076 int i;
2077 XlwMenuWidget mw = (XlwMenuWidget) w;
2079 if (pointer_grabbed)
2080 ungrab_all ((Widget)w, CurrentTime);
2081 pointer_grabbed = 0;
2083 submenu_destroyed = 1;
2085 release_drawing_gcs (mw);
2086 release_shadow_gcs (mw);
2088 /* this doesn't come from the resource db but is created explicitly
2089 so we must free it ourselves. */
2090 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2091 mw->menu.gray_pixmap = (Pixmap) -1;
2093 #if 0
2094 /* Do free mw->menu.contents because nowadays we copy it
2095 during initialization. */
2096 XtFree (mw->menu.contents);
2097 #endif
2099 /* Don't free mw->menu.contents because that comes from our creator.
2100 The `*_stack' elements are just pointers into `contents' so leave
2101 that alone too. But free the stacks themselves. */
2102 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2103 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2105 /* Remember, you can't free anything that came from the resource
2106 database. This includes:
2107 mw->menu.cursor
2108 mw->menu.top_shadow_pixmap
2109 mw->menu.bottom_shadow_pixmap
2110 mw->menu.font
2111 Also the color cells of top_shadow_color, bottom_shadow_color,
2112 foreground, and button_foreground will never be freed until this
2113 client exits. Nice, eh?
2116 #ifdef HAVE_XFT
2117 if (mw->menu.windows [0].xft_draw)
2118 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2119 if (mw->menu.xft_font)
2120 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2121 #endif
2123 if (mw->menu.windows [0].pixmap != None)
2124 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2125 /* start from 1 because the one in slot 0 is w->core.window */
2126 for (i = 1; i < mw->menu.windows_length; i++)
2128 if (mw->menu.windows [i].pixmap != None)
2129 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2130 #ifdef HAVE_XFT
2131 if (mw->menu.windows [i].xft_draw)
2132 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2133 #endif
2136 if (mw->menu.windows)
2137 XtFree ((char *) mw->menu.windows);
2140 #ifdef HAVE_XFT
2141 static int
2142 fontname_changed (XlwMenuWidget newmw,
2143 XlwMenuWidget oldmw)
2145 /* This will force a new XftFont even if the same string is set.
2146 This is good, as rendering parameters may have changed and
2147 we just want to do a redisplay. */
2148 return newmw->menu.fontName != oldmw->menu.fontName;
2150 #endif
2152 static Boolean
2153 XlwMenuSetValues (Widget current, Widget request, Widget new,
2154 ArgList args, Cardinal *num_args)
2156 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2157 XlwMenuWidget newmw = (XlwMenuWidget)new;
2158 Boolean do_redisplay = False;
2160 if (newmw->menu.contents
2161 && newmw->menu.contents->contents
2162 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2163 do_redisplay = True;
2164 /* Do redisplay if the contents are entirely eliminated. */
2165 if (newmw->menu.contents
2166 && newmw->menu.contents->contents == 0
2167 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2168 do_redisplay = True;
2170 if (newmw->core.background_pixel != oldmw->core.background_pixel
2171 || newmw->menu.foreground != oldmw->menu.foreground
2172 #ifdef HAVE_XFT
2173 || fontname_changed (newmw, oldmw)
2174 #endif
2175 #ifdef HAVE_X_I18N
2176 || newmw->menu.fontSet != oldmw->menu.fontSet
2177 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2178 #else
2179 || newmw->menu.font != oldmw->menu.font
2180 #endif
2183 int i;
2184 release_drawing_gcs (newmw);
2185 make_drawing_gcs (newmw);
2187 release_shadow_gcs (newmw);
2188 /* Cause the shadow colors to be recalculated. */
2189 newmw->menu.top_shadow_color = -1;
2190 newmw->menu.bottom_shadow_color = -1;
2191 make_shadow_gcs (newmw);
2193 do_redisplay = True;
2195 if (XtIsRealized (current))
2196 /* If the menu is currently displayed, change the display. */
2197 for (i = 0; i < oldmw->menu.windows_length; i++)
2199 XSetWindowBackground (XtDisplay (oldmw),
2200 oldmw->menu.windows [i].window,
2201 newmw->core.background_pixel);
2202 /* clear windows and generate expose events */
2203 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2204 0, 0, 0, 0, True);
2208 #ifdef HAVE_XFT
2209 if (fontname_changed (newmw, oldmw))
2211 int i;
2212 int screen = XScreenNumberOfScreen (newmw->core.screen);
2213 if (newmw->menu.xft_font)
2214 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2215 openXftFont (newmw);
2216 for (i = 0; i < newmw->menu.windows_length; i++)
2218 if (newmw->menu.windows [i].xft_draw)
2219 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2220 newmw->menu.windows [i].xft_draw = 0;
2222 if (newmw->menu.xft_font)
2223 for (i = 0; i < newmw->menu.windows_length; i++)
2224 newmw->menu.windows [i].xft_draw
2225 = XftDrawCreate (XtDisplay (newmw),
2226 newmw->menu.windows [i].window,
2227 DefaultVisual (XtDisplay (newmw), screen),
2228 newmw->core.colormap);
2230 #endif
2231 #ifdef HAVE_X_I18N
2232 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2234 do_redisplay = True;
2235 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2237 #endif
2239 return do_redisplay;
2242 static void
2243 XlwMenuResize (Widget w)
2245 XlwMenuWidget mw = (XlwMenuWidget)w;
2247 if (mw->menu.popped_up)
2249 /* Don't allow the popup menu to resize itself. */
2250 mw->core.width = mw->menu.windows [0].width;
2251 mw->core.height = mw->menu.windows [0].height;
2252 mw->core.parent->core.width = mw->core.width;
2253 mw->core.parent->core.height = mw->core.height;
2255 else
2257 mw->menu.windows [0].width = mw->core.width;
2258 mw->menu.windows [0].height = mw->core.height;
2259 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2263 \f/* Action procedures */
2264 static void
2265 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2267 widget_value* val;
2268 int level;
2270 if (!map_event_to_widget_value (mw, ev, &val, &level))
2271 pop_new_stack_if_no_contents (mw);
2272 else
2273 set_new_state (mw, val, level);
2274 remap_menubar (mw);
2276 /* Sync with the display. Makes it feel better on X terms. */
2277 XSync (XtDisplay (mw), False);
2280 static void
2281 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2283 int x = ev->x_root;
2284 int y = ev->y_root;
2285 int state = ev->state;
2286 XMotionEvent oldev = *ev;
2288 /* allow motion events to be generated again */
2289 if (ev->is_hint
2290 && XQueryPointer (XtDisplay (mw), ev->window,
2291 &ev->root, &ev->subwindow,
2292 &ev->x_root, &ev->y_root,
2293 &ev->x, &ev->y,
2294 &ev->state)
2295 && ev->state == state
2296 && (ev->x_root != x || ev->y_root != y))
2297 handle_single_motion_event (mw, ev);
2298 else
2299 handle_single_motion_event (mw, &oldev);
2302 static void
2303 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2305 XlwMenuWidget mw = (XlwMenuWidget)w;
2307 if (!mw->menu.popped_up)
2309 menu_post_event = *ev;
2310 /* If event is set to CurrentTime, get the last known time stamp.
2311 This is for calculating if (popup) menus should stay up after
2312 a fast click. */
2313 if (menu_post_event.xbutton.time == CurrentTime)
2314 menu_post_event.xbutton.time
2315 = XtLastTimestampProcessed (XtDisplay (w));
2317 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2319 else
2321 /* If we push a button while the menu is posted semipermanently,
2322 releasing the button should always pop the menu down. */
2323 next_release_must_exit = 1;
2325 /* notes the absolute position of the menubar window */
2326 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2327 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2329 /* handles the down like a move, slots are compatible */
2330 ev->xmotion.is_hint = 0;
2331 handle_motion_event (mw, &ev->xmotion);
2335 static void
2336 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2338 XlwMenuWidget mw = (XlwMenuWidget)w;
2339 if (mw->menu.popped_up)
2340 handle_motion_event (mw, &ev->xmotion);
2343 /* Do nothing.
2344 This is how we handle presses and releases of modifier keys. */
2345 static void
2346 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2350 static widget_value *
2351 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2353 widget_value *current = item;
2354 enum menu_separator separator;
2356 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2357 || (skip_titles && !current->call_data && !current->contents))
2358 if (current->next)
2359 current=current->next;
2360 else
2361 return NULL;
2363 return current;
2366 static widget_value *
2367 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2369 widget_value *current = item;
2370 enum menu_separator separator;
2372 while (current->next && (current=current->next) &&
2373 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2374 || (skip_titles && !current->call_data && !current->contents)))
2377 if (current == item)
2379 if (mw->menu.old_depth < 2)
2380 return current;
2381 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2383 while (lw_separator_p (current->name, &separator, 0)
2384 || !current->enabled
2385 || (skip_titles && !current->call_data
2386 && !current->contents))
2388 if (current->next)
2389 current=current->next;
2391 if (current == item)
2392 break;
2397 return current;
2400 static widget_value *
2401 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2403 widget_value *current = item;
2404 widget_value *prev = item;
2406 while ((current=find_next_selectable (mw, current, skip_titles))
2407 != item)
2409 if (prev == current)
2410 break;
2411 prev=current;
2414 return prev;
2417 static void
2418 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2420 XlwMenuWidget mw = (XlwMenuWidget) w;
2421 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2422 int popup_menu_p = mw->menu.top_depth == 1;
2424 /* Inside top-level menu-bar? */
2425 if (mw->menu.old_depth == mw->menu.top_depth)
2426 /* When <down> in the menu-bar is pressed, display the corresponding
2427 sub-menu and select the first selectable menu item there.
2428 If this is a popup menu, skip title item of the popup. */
2429 set_new_state (mw,
2430 find_first_selectable (mw,
2431 selected_item->contents,
2432 popup_menu_p),
2433 mw->menu.old_depth);
2434 else
2435 /* Highlight next possible (enabled and not separator) menu item. */
2436 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2437 mw->menu.old_depth - 1);
2439 remap_menubar (mw);
2442 static void
2443 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2445 XlwMenuWidget mw = (XlwMenuWidget) w;
2446 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2447 int popup_menu_p = mw->menu.top_depth == 1;
2449 /* Inside top-level menu-bar? */
2450 if (mw->menu.old_depth == mw->menu.top_depth)
2452 /* FIXME: this is tricky. <up> in the menu-bar should select the
2453 last selectable item in the list. So we select the first
2454 selectable one and find the previous selectable item. Is there
2455 a better way? */
2456 /* If this is a popup menu, skip title item of the popup. */
2457 set_new_state (mw,
2458 find_first_selectable (mw,
2459 selected_item->contents,
2460 popup_menu_p),
2461 mw->menu.old_depth);
2462 remap_menubar (mw);
2463 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2464 set_new_state (mw,
2465 find_prev_selectable (mw,
2466 selected_item,
2467 popup_menu_p),
2468 mw->menu.old_depth - 1);
2470 else
2471 /* Highlight previous (enabled and not separator) menu item. */
2472 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2473 mw->menu.old_depth - 1);
2475 remap_menubar (mw);
2478 void
2479 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2481 XlwMenuWidget mw = (XlwMenuWidget) w;
2482 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2484 /* Inside top-level menu-bar? */
2485 if (mw->menu.old_depth == mw->menu.top_depth)
2486 /* When <left> in the menu-bar is pressed, display the previous item on
2487 the menu-bar. If the current item is the first one, highlight the
2488 last item in the menubar (probably Help). */
2489 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2490 mw->menu.old_depth - 1);
2491 else if (mw->menu.old_depth == 1
2492 && selected_item->contents) /* Is this menu item expandable? */
2494 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2495 remap_menubar (mw);
2496 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2497 if (!selected_item->enabled && find_first_selectable (mw,
2498 selected_item,
2500 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2501 mw->menu.old_depth - 1);
2504 else
2506 pop_new_stack_if_no_contents (mw);
2507 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2508 mw->menu.old_depth - 2);
2511 remap_menubar (mw);
2514 void
2515 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2517 XlwMenuWidget mw = (XlwMenuWidget) w;
2518 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2520 /* Inside top-level menu-bar? */
2521 if (mw->menu.old_depth == mw->menu.top_depth)
2522 /* When <right> in the menu-bar is pressed, display the next item on
2523 the menu-bar. If the current item is the last one, highlight the
2524 first item (probably File). */
2525 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2526 mw->menu.old_depth - 1);
2527 else if (selected_item->contents) /* Is this menu item expandable? */
2529 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2530 remap_menubar (mw);
2531 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2532 if (!selected_item->enabled && find_first_selectable (mw,
2533 selected_item,
2535 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2536 mw->menu.old_depth - 1);
2538 else
2540 pop_new_stack_if_no_contents (mw);
2541 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2542 mw->menu.old_depth - 2);
2545 remap_menubar (mw);
2548 /* Handle key press and release events while menu is popped up.
2549 Our action is to get rid of the menu. */
2550 static void
2551 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2553 XlwMenuWidget mw = (XlwMenuWidget)w;
2555 /* Pop down everything. */
2556 mw->menu.new_depth = 1;
2557 remap_menubar (mw);
2559 if (mw->menu.popped_up)
2561 mw->menu.popped_up = False;
2562 ungrab_all ((Widget)mw, ev->xmotion.time);
2563 if (XtIsShell (XtParent ((Widget) mw)))
2564 XtPopdown (XtParent ((Widget) mw));
2565 else
2567 XtRemoveGrab ((Widget) mw);
2568 display_menu (mw, 0, False, NULL, NULL, NULL);
2572 /* callback */
2573 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2576 static void
2577 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2579 XlwMenuWidget mw = (XlwMenuWidget)w;
2580 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2582 /* If user releases the button quickly, without selecting anything,
2583 after the initial down-click that brought the menu up,
2584 do nothing. */
2585 if ((selected_item == 0
2586 || ((widget_value *) selected_item)->call_data == 0)
2587 && !next_release_must_exit
2588 && (ev->xbutton.time - menu_post_event.xbutton.time
2589 < XtGetMultiClickTime (XtDisplay (w))))
2590 return;
2592 /* pop down everything. */
2593 mw->menu.new_depth = 1;
2594 remap_menubar (mw);
2596 if (mw->menu.popped_up)
2598 mw->menu.popped_up = False;
2599 ungrab_all ((Widget)mw, ev->xmotion.time);
2600 if (XtIsShell (XtParent ((Widget) mw)))
2601 XtPopdown (XtParent ((Widget) mw));
2602 else
2604 XtRemoveGrab ((Widget) mw);
2605 display_menu (mw, 0, False, NULL, NULL, NULL);
2609 /* callback */
2610 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2614 \f/* Special code to pop-up a menu */
2615 static void
2616 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2618 int x = event->x_root;
2619 int y = event->y_root;
2620 int w;
2621 int h;
2622 int borderwidth = mw->menu.shadow_thickness;
2623 Screen* screen = XtScreen (mw);
2624 Display *display = XtDisplay (mw);
2626 next_release_must_exit = 0;
2628 mw->menu.inside_entry = NULL;
2629 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2631 if (XtIsShell (XtParent ((Widget)mw)))
2632 size_menu (mw, 0);
2634 w = mw->menu.windows [0].width;
2635 h = mw->menu.windows [0].height;
2637 x -= borderwidth;
2638 y -= borderwidth;
2639 if (x < borderwidth)
2640 x = borderwidth;
2641 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2642 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2643 if (y < borderwidth)
2644 y = borderwidth;
2645 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2646 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2648 mw->menu.popped_up = True;
2649 if (XtIsShell (XtParent ((Widget)mw)))
2651 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2652 XtParent ((Widget)mw)->core.border_width);
2653 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2654 display_menu (mw, 0, False, NULL, NULL, NULL);
2655 mw->menu.windows [0].x = x + borderwidth;
2656 mw->menu.windows [0].y = y + borderwidth;
2657 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2659 else
2661 XEvent *ev = (XEvent *) event;
2663 XtAddGrab ((Widget) mw, True, True);
2665 /* notes the absolute position of the menubar window */
2666 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2667 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2668 mw->menu.top_depth = 2;
2671 #ifdef emacs
2672 x_catch_errors (display);
2673 #endif
2674 if (XtGrabPointer ((Widget)mw, False,
2675 (PointerMotionMask
2676 | PointerMotionHintMask
2677 | ButtonReleaseMask
2678 | ButtonPressMask),
2679 GrabModeAsync, GrabModeAsync, None,
2680 mw->menu.cursor_shape,
2681 event->time) == Success)
2683 if (! GRAB_KEYBOARD
2684 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2685 GrabModeAsync, event->time) == Success)
2687 XtSetKeyboardFocus((Widget)mw, None);
2688 pointer_grabbed = 1;
2690 else
2691 XtUngrabPointer ((Widget)mw, event->time);
2694 #ifdef emacs
2695 if (x_had_errors_p (display))
2697 pointer_grabbed = 0;
2698 XtUngrabPointer ((Widget)mw, event->time);
2700 x_uncatch_errors ();
2701 #endif
2703 ((XMotionEvent*)event)->is_hint = 0;
2704 handle_motion_event (mw, (XMotionEvent*)event);