Fix crash due to incorrect resolution of type of NSM characters (bug#5858).
[emacs.git] / lwlib / xlwmenu.c
blob3e8259f9a19e023db37da874c159453ded772599
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 1994, 1995, 1997, 1999, 2000, 2001, 2002, 2003, 2004,
4 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
6 This file is part of the Lucid Widget Library.
8 The Lucid Widget Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2, or (at your option)
11 any later version.
13 The Lucid Widget Library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with GNU Emacs; see the file COPYING. If not, write to the
20 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA. */
23 /* Created by devin@lucid.com */
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include <setjmp.h>
30 #include "lisp.h"
32 #include <stdio.h>
33 #include <ctype.h>
35 #include <sys/types.h>
36 #if (defined __sun) && !(defined SUNOS41)
37 #define SUNOS41
38 #include <X11/Xos.h>
39 #undef SUNOS41
40 #else
41 #include <X11/Xos.h>
42 #endif
43 #include <X11/IntrinsicP.h>
44 #include <X11/ObjectP.h>
45 #include <X11/StringDefs.h>
46 #include <X11/cursorfont.h>
47 #include "xlwmenuP.h"
49 #ifdef emacs
51 /* Defined in xfns.c. When config.h defines `static' as empty, we get
52 redefinition errors when gray_bitmap is included more than once, so
53 we're referring to the one include in xfns.c here. */
55 extern int gray_bitmap_width;
56 extern int gray_bitmap_height;
57 extern char *gray_bitmap_bits;
59 #include "xterm.h"
61 #else /* not emacs */
63 #include <X11/bitmaps/gray>
64 #define gray_bitmap_width gray_width
65 #define gray_bitmap_height gray_height
66 #define gray_bitmap_bits gray_bits
68 #endif /* not emacs */
70 static int pointer_grabbed;
71 static XEvent menu_post_event;
73 static XFontStruct *xlwmenu_default_font;
75 static char
76 xlwMenuTranslations [] =
77 "<BtnDown>: start()\n\
78 <Motion>: drag()\n\
79 <BtnUp>: select()\n\
80 <Key>Shift_L: nothing()\n\
81 <Key>Shift_R: nothing()\n\
82 <Key>Meta_L: nothing()\n\
83 <Key>Meta_R: nothing()\n\
84 <Key>Control_L: nothing()\n\
85 <Key>Control_R: nothing()\n\
86 <Key>Hyper_L: nothing()\n\
87 <Key>Hyper_R: nothing()\n\
88 <Key>Super_L: nothing()\n\
89 <Key>Super_R: nothing()\n\
90 <Key>Alt_L: nothing()\n\
91 <Key>Alt_R: nothing()\n\
92 <Key>Caps_Lock: nothing()\n\
93 <Key>Shift_Lock: nothing()\n\
94 <KeyUp>Shift_L: nothing()\n\
95 <KeyUp>Shift_R: nothing()\n\
96 <KeyUp>Meta_L: nothing()\n\
97 <KeyUp>Meta_R: nothing()\n\
98 <KeyUp>Control_L: nothing()\n\
99 <KeyUp>Control_R: nothing()\n\
100 <KeyUp>Hyper_L: nothing()\n\
101 <KeyUp>Hyper_R: nothing()\n\
102 <KeyUp>Super_L: nothing()\n\
103 <KeyUp>Super_R: nothing()\n\
104 <KeyUp>Alt_L: nothing()\n\
105 <KeyUp>Alt_R: nothing()\n\
106 <KeyUp>Caps_Lock: nothing()\n\
107 <KeyUp>Shift_Lock:nothing()\n\
108 <Key>Return: select()\n\
109 <Key>Down: down()\n\
110 <Key>Up: up()\n\
111 <Key>Left: left()\n\
112 <Key>Right: right()\n\
113 <Key>: key()\n\
114 <KeyUp>: key()\n\
117 /* FIXME: Space should toggle toggleable menu item but not remove the menu
118 so you can toggle the next one without entering the menu again. */
120 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
122 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
124 #define offset(field) XtOffset(XlwMenuWidget, field)
125 static XtResource
126 xlwMenuResources[] =
128 #ifdef HAVE_X_I18N
129 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
130 offset(menu.fontSet), XtRFontSet, NULL},
131 #endif
132 #ifdef HAVE_XFT
133 #define DEFAULT_FACENAME "Sans-10"
134 {XtNfaceName, XtCFaceName, XtRString, sizeof(String),
135 offset(menu.faceName), XtRString, DEFAULT_FACENAME},
136 {XtNdefaultFace, XtCDefaultFace, XtRInt, sizeof(int),
137 offset(menu.default_face), XtRImmediate, (XtPointer)1},
138 #endif
139 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
140 offset(menu.font), XtRString, "XtDefaultFont"},
141 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
142 offset(menu.foreground), XtRString, "XtDefaultForeground"},
143 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
144 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
145 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
146 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
147 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
148 offset(menu.margin), XtRImmediate, (XtPointer)1},
149 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
150 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
151 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
152 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
153 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
154 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
156 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
157 sizeof (Dimension), offset (menu.shadow_thickness),
158 XtRImmediate, (XtPointer)1},
159 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
160 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
161 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
162 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
163 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
164 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
165 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
166 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
168 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
169 offset(menu.open), XtRCallback, (XtPointer)NULL},
170 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
171 offset(menu.select), XtRCallback, (XtPointer)NULL},
172 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
173 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
174 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
175 offset(menu.enter), XtRCallback, (XtPointer)NULL},
176 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
177 offset(menu.leave), XtRCallback, (XtPointer)NULL},
178 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
179 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
180 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
181 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
182 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
183 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
185 #undef offset
187 static Boolean XlwMenuSetValues();
188 static void XlwMenuRealize();
189 static void XlwMenuRedisplay();
190 static void XlwMenuResize();
191 static void XlwMenuInitialize();
192 static void XlwMenuRedisplay();
193 static void XlwMenuDestroy();
194 static void XlwMenuClassInitialize();
195 static void Start();
196 static void Drag();
197 static void Down();
198 static void Up();
199 static void Left();
200 static void Right();
201 static void Select();
202 static void Key();
203 static void Nothing();
204 static int separator_height __P ((enum menu_separator));
205 static void pop_up_menu __P ((XlwMenuWidget, XButtonPressedEvent *));
208 static XtActionsRec
209 xlwMenuActionsList [] =
211 {"start", Start},
212 {"drag", Drag},
213 {"down", Down},
214 {"up", Up},
215 {"left", Left},
216 {"right", Right},
217 {"select", Select},
218 {"key", Key},
219 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
220 {"nothing", Nothing},
223 #define SuperClass ((CoreWidgetClass)&coreClassRec)
225 XlwMenuClassRec xlwMenuClassRec =
227 { /* CoreClass fields initialization */
228 (WidgetClass) SuperClass, /* superclass */
229 "XlwMenu", /* class_name */
230 sizeof(XlwMenuRec), /* size */
231 XlwMenuClassInitialize, /* class_initialize */
232 NULL, /* class_part_initialize */
233 FALSE, /* class_inited */
234 XlwMenuInitialize, /* initialize */
235 NULL, /* initialize_hook */
236 XlwMenuRealize, /* realize */
237 xlwMenuActionsList, /* actions */
238 XtNumber(xlwMenuActionsList), /* num_actions */
239 xlwMenuResources, /* resources */
240 XtNumber(xlwMenuResources), /* resource_count */
241 NULLQUARK, /* xrm_class */
242 TRUE, /* compress_motion */
243 XtExposeCompressMaximal, /* compress_exposure */
244 TRUE, /* compress_enterleave */
245 FALSE, /* visible_interest */
246 XlwMenuDestroy, /* destroy */
247 XlwMenuResize, /* resize */
248 XlwMenuRedisplay, /* expose */
249 XlwMenuSetValues, /* set_values */
250 NULL, /* set_values_hook */
251 XtInheritSetValuesAlmost, /* set_values_almost */
252 NULL, /* get_values_hook */
253 NULL, /* accept_focus */
254 XtVersion, /* version */
255 NULL, /* callback_private */
256 xlwMenuTranslations, /* tm_table */
257 XtInheritQueryGeometry, /* query_geometry */
258 XtInheritDisplayAccelerator, /* display_accelerator */
259 NULL /* extension */
260 }, /* XlwMenuClass fields initialization */
262 0 /* dummy */
266 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
268 int submenu_destroyed;
270 /* For debug, if installation-directory is non-nil this is not an installed
271 Emacs. In that case we do not grab the keyboard to make it easier to
272 debug. */
273 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
275 static int next_release_must_exit;
277 \f/* Utilities */
279 /* Ungrab pointer and keyboard */
280 static void
281 ungrab_all (w, ungrabtime)
282 Widget w;
283 Time ungrabtime;
285 XtUngrabPointer (w, ungrabtime);
286 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
289 /* Like abort, but remove grabs from widget W before. */
291 static void
292 abort_gracefully (w)
293 Widget w;
295 if (XtIsShell (XtParent (w)))
296 XtRemoveGrab (w);
297 ungrab_all (w, CurrentTime);
298 abort ();
301 static void
302 push_new_stack (mw, val)
303 XlwMenuWidget mw;
304 widget_value* val;
306 if (!mw->menu.new_stack)
308 mw->menu.new_stack_length = 10;
309 mw->menu.new_stack =
310 (widget_value**)XtCalloc (mw->menu.new_stack_length,
311 sizeof (widget_value*));
313 else if (mw->menu.new_depth == mw->menu.new_stack_length)
315 mw->menu.new_stack_length *= 2;
316 mw->menu.new_stack =
317 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
318 mw->menu.new_stack_length * sizeof (widget_value*));
320 mw->menu.new_stack [mw->menu.new_depth++] = val;
323 static void
324 pop_new_stack_if_no_contents (mw)
325 XlwMenuWidget mw;
327 if (mw->menu.new_depth > 1)
329 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
330 mw->menu.new_depth -= 1;
334 static void
335 make_old_stack_space (mw, n)
336 XlwMenuWidget mw;
337 int n;
339 if (!mw->menu.old_stack)
341 mw->menu.old_stack_length = 10;
342 mw->menu.old_stack =
343 (widget_value**)XtCalloc (mw->menu.old_stack_length,
344 sizeof (widget_value*));
346 else if (mw->menu.old_stack_length < n)
348 mw->menu.old_stack_length *= 2;
349 mw->menu.old_stack =
350 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
351 mw->menu.old_stack_length * sizeof (widget_value*));
355 \f/* Size code */
356 static int
357 string_width (mw, s)
358 XlwMenuWidget mw;
359 char *s;
361 XCharStruct xcs;
362 int drop;
363 #ifdef HAVE_XFT
364 if (mw->menu.xft_font)
366 XGlyphInfo gi;
367 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
368 (FcChar8 *) s,
369 strlen (s), &gi);
370 return gi.width;
372 #endif
373 #ifdef HAVE_X_I18N
374 if (mw->menu.fontSet)
376 XRectangle ink, logical;
377 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
378 return logical.width;
380 #endif
382 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
383 return xcs.width;
387 #ifdef HAVE_XFT
388 #define MENU_FONT_HEIGHT(mw) \
389 ((mw)->menu.xft_font != NULL \
390 ? (mw)->menu.xft_font->height \
391 : ((mw)->menu.fontSet != NULL \
392 ? (mw)->menu.font_extents->max_logical_extent.height \
393 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
394 #define MENU_FONT_ASCENT(mw) \
395 ((mw)->menu.xft_font != NULL \
396 ? (mw)->menu.xft_font->ascent \
397 : ((mw)->menu.fontSet != NULL \
398 ? - (mw)->menu.font_extents->max_logical_extent.y \
399 : (mw)->menu.font->ascent))
400 #else
401 #ifdef HAVE_X_I18N
402 #define MENU_FONT_HEIGHT(mw) \
403 ((mw)->menu.fontSet != NULL \
404 ? (mw)->menu.font_extents->max_logical_extent.height \
405 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
406 #define MENU_FONT_ASCENT(mw) \
407 ((mw)->menu.fontSet != NULL \
408 ? - (mw)->menu.font_extents->max_logical_extent.y \
409 : (mw)->menu.font->ascent)
410 #else
411 #define MENU_FONT_HEIGHT(mw) \
412 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
413 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
414 #endif
415 #endif
417 static int
418 arrow_width (mw)
419 XlwMenuWidget mw;
421 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
424 /* Return the width of toggle buttons of widget MW. */
426 static int
427 toggle_button_width (mw)
428 XlwMenuWidget mw;
430 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
434 /* Return the width of radio buttons of widget MW. */
436 static int
437 radio_button_width (mw)
438 XlwMenuWidget mw;
440 return toggle_button_width (mw) * 1.41;
444 static XtResource
445 nameResource[] =
447 {"labelString", "LabelString", XtRString, sizeof(String),
448 0, XtRImmediate, 0},
451 static char*
452 resource_widget_value (mw, val)
453 XlwMenuWidget mw;
454 widget_value *val;
456 if (!val->toolkit_data)
458 char* resourced_name = NULL;
459 char* complete_name;
460 XtGetSubresources ((Widget) mw,
461 (XtPointer) &resourced_name,
462 val->name, val->name,
463 nameResource, 1, NULL, 0);
464 if (!resourced_name)
465 resourced_name = val->name;
466 if (!val->value)
468 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
469 strcpy (complete_name, resourced_name);
471 else
473 int complete_length =
474 strlen (resourced_name) + strlen (val->value) + 2;
475 complete_name = XtMalloc (complete_length);
476 *complete_name = 0;
477 strcat (complete_name, resourced_name);
478 strcat (complete_name, " ");
479 strcat (complete_name, val->value);
482 val->toolkit_data = complete_name;
483 val->free_toolkit_data = True;
485 return (char*)val->toolkit_data;
488 /* Returns the sizes of an item */
489 static void
490 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
491 height)
492 XlwMenuWidget mw;
493 widget_value* val;
494 int horizontal_p;
495 int* label_width;
496 int* rest_width;
497 int* button_width;
498 int* height;
500 enum menu_separator separator;
502 if (lw_separator_p (val->name, &separator, 0))
504 *height = separator_height (separator);
505 *label_width = 1;
506 *rest_width = 0;
507 *button_width = 0;
509 else
511 *height = MENU_FONT_HEIGHT (mw)
512 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
514 *label_width =
515 string_width (mw, resource_widget_value (mw, val))
516 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
518 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
519 if (!horizontal_p)
521 if (val->contents)
522 /* Add width of the arrow displayed for submenus. */
523 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
524 else if (val->key)
525 /* Add width of key equivalent string. */
526 *rest_width += (string_width (mw, val->key)
527 + mw->menu.arrow_spacing);
529 if (val->button_type == BUTTON_TYPE_TOGGLE)
530 *button_width = (toggle_button_width (mw)
531 + mw->menu.horizontal_spacing);
532 else if (val->button_type == BUTTON_TYPE_RADIO)
533 *button_width = (radio_button_width (mw)
534 + mw->menu.horizontal_spacing);
539 static void
540 size_menu (mw, level)
541 XlwMenuWidget mw;
542 int level;
544 unsigned int label_width = 0;
545 int rest_width = 0;
546 int button_width = 0;
547 int max_rest_width = 0;
548 int max_button_width = 0;
549 unsigned int height = 0;
550 int horizontal_p = mw->menu.horizontal && (level == 0);
551 widget_value* val;
552 window_state* ws;
554 if (level >= mw->menu.old_depth)
555 abort_gracefully ((Widget) mw);
557 ws = &mw->menu.windows [level];
558 ws->width = 0;
559 ws->height = 0;
560 ws->label_width = 0;
561 ws->button_width = 0;
563 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
565 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
566 &button_width, &height);
567 if (horizontal_p)
569 ws->width += label_width + rest_width;
570 if (height > ws->height)
571 ws->height = height;
573 else
575 if (label_width > ws->label_width)
576 ws->label_width = label_width;
577 if (rest_width > max_rest_width)
578 max_rest_width = rest_width;
579 if (button_width > max_button_width)
580 max_button_width = button_width;
581 ws->height += height;
585 if (horizontal_p)
586 ws->label_width = ws->button_width = 0;
587 else
589 ws->width = ws->label_width + max_rest_width + max_button_width;
590 ws->button_width = max_button_width;
593 ws->width += 2 * mw->menu.shadow_thickness;
594 ws->height += 2 * mw->menu.shadow_thickness;
595 ws->max_rest_width = max_rest_width;
597 if (horizontal_p)
599 ws->width += 2 * mw->menu.margin;
600 ws->height += 2 * mw->menu.margin;
605 \f/* Display code */
607 static void
608 draw_arrow (mw, window, gc, x, y, width, down_p)
609 XlwMenuWidget mw;
610 Window window;
611 GC gc;
612 int x;
613 int y;
614 int width;
615 int down_p;
617 Display *dpy = XtDisplay (mw);
618 GC top_gc = mw->menu.shadow_top_gc;
619 GC bottom_gc = mw->menu.shadow_bottom_gc;
620 int thickness = mw->menu.shadow_thickness;
621 int height = width;
622 XPoint pt[10];
623 /* alpha = atan (0.5)
624 factor = (1 + sin (alpha)) / cos (alpha) */
625 double factor = 1.62;
626 int thickness2 = thickness * factor;
628 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
630 if (down_p)
632 GC temp;
633 temp = top_gc;
634 top_gc = bottom_gc;
635 bottom_gc = temp;
638 pt[0].x = x;
639 pt[0].y = y + height;
640 pt[1].x = x + thickness;
641 pt[1].y = y + height - thickness2;
642 pt[2].x = x + thickness2;
643 pt[2].y = y + thickness2;
644 pt[3].x = x;
645 pt[3].y = y;
646 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
648 pt[0].x = x;
649 pt[0].y = y;
650 pt[1].x = x + thickness;
651 pt[1].y = y + thickness2;
652 pt[2].x = x + width - thickness2;
653 pt[2].y = y + height / 2;
654 pt[3].x = x + width;
655 pt[3].y = y + height / 2;
656 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
658 pt[0].x = x;
659 pt[0].y = y + height;
660 pt[1].x = x + thickness;
661 pt[1].y = y + height - thickness2;
662 pt[2].x = x + width - thickness2;
663 pt[2].y = y + height / 2;
664 pt[3].x = x + width;
665 pt[3].y = y + height / 2;
666 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
671 static void
672 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
673 XlwMenuWidget mw;
674 Window window;
675 int x;
676 int y;
677 int width;
678 int height;
679 int erase_p;
680 int down_p;
682 Display *dpy = XtDisplay (mw);
683 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
684 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
685 int thickness = mw->menu.shadow_thickness;
686 XPoint points [4];
688 if (!erase_p && down_p)
690 GC temp;
691 temp = top_gc;
692 top_gc = bottom_gc;
693 bottom_gc = temp;
696 points [0].x = x;
697 points [0].y = y;
698 points [1].x = x + width;
699 points [1].y = y;
700 points [2].x = x + width - thickness;
701 points [2].y = y + thickness;
702 points [3].x = x;
703 points [3].y = y + thickness;
704 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
705 points [0].x = x;
706 points [0].y = y + thickness;
707 points [1].x = x;
708 points [1].y = y + height;
709 points [2].x = x + thickness;
710 points [2].y = y + height - thickness;
711 points [3].x = x + thickness;
712 points [3].y = y + thickness;
713 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
714 points [0].x = x + width;
715 points [0].y = y;
716 points [1].x = x + width - thickness;
717 points [1].y = y + thickness;
718 points [2].x = x + width - thickness;
719 points [2].y = y + height - thickness;
720 points [3].x = x + width;
721 points [3].y = y + height - thickness;
722 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
723 points [0].x = x;
724 points [0].y = y + height;
725 points [1].x = x + width;
726 points [1].y = y + height;
727 points [2].x = x + width;
728 points [2].y = y + height - thickness;
729 points [3].x = x + thickness;
730 points [3].y = y + height - thickness;
731 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
735 static void
736 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
737 XlwMenuWidget mw;
738 Window window;
739 int x;
740 int y;
741 int width;
742 int height;
743 int erase_p;
744 int down_p;
746 Display *dpy = XtDisplay (mw);
747 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
748 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
749 int thickness = mw->menu.shadow_thickness;
750 XPoint points [4];
752 if (!erase_p && down_p)
754 GC temp;
755 temp = top_gc;
756 top_gc = bottom_gc;
757 bottom_gc = temp;
760 points [0].x = x;
761 points [0].y = y + height / 2;
762 points [1].x = x + thickness;
763 points [1].y = y + height / 2;
764 points [2].x = x + width / 2;
765 points [2].y = y + thickness;
766 points [3].x = x + width / 2;
767 points [3].y = y;
768 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
769 points [0].x = x + width / 2;
770 points [0].y = y;
771 points [1].x = x + width / 2;
772 points [1].y = y + thickness;
773 points [2].x = x + width - thickness;
774 points [2].y = y + height / 2;
775 points [3].x = x + width;
776 points [3].y = y + height / 2;
777 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
778 points [0].x = x;
779 points [0].y = y + height / 2;
780 points [1].x = x + thickness;
781 points [1].y = y + height / 2;
782 points [2].x = x + width / 2;
783 points [2].y = y + height - thickness;
784 points [3].x = x + width / 2;
785 points [3].y = y + height;
786 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
787 points [0].x = x + width / 2;
788 points [0].y = y + height;
789 points [1].x = x + width / 2;
790 points [1].y = y + height - thickness;
791 points [2].x = x + width - thickness;
792 points [2].y = y + height / 2;
793 points [3].x = x + width;
794 points [3].y = y + height / 2;
795 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
799 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
800 top-left corner of the menu item. SELECTED_P non-zero means the
801 toggle button is selected. */
803 static void
804 draw_toggle (mw, window, x, y, selected_p)
805 XlwMenuWidget mw;
806 Window window;
807 int x, y, selected_p;
809 int width, height;
811 width = toggle_button_width (mw);
812 height = width;
813 x += mw->menu.horizontal_spacing;
814 y += (MENU_FONT_ASCENT (mw) - height) / 2;
815 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
819 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
820 top-left corner of the menu item. SELECTED_P non-zero means the
821 toggle button is selected. */
823 static void
824 draw_radio (mw, window, x, y, selected_p)
825 XlwMenuWidget mw;
826 Window window;
827 int x, y, selected_p;
829 int width, height;
831 width = radio_button_width (mw);
832 height = width;
833 x += mw->menu.horizontal_spacing;
834 y += (MENU_FONT_ASCENT (mw) - height) / 2;
835 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
839 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
840 top-left corner of the menu item. WIDTH is the width of the
841 separator to draw. TYPE is the separator type. */
843 static void
844 draw_separator (mw, window, x, y, width, type)
845 XlwMenuWidget mw;
846 Window window;
847 int x, y, width;
848 enum menu_separator type;
850 Display *dpy = XtDisplay (mw);
851 XGCValues xgcv;
853 switch (type)
855 case SEPARATOR_NO_LINE:
856 break;
858 case SEPARATOR_SINGLE_LINE:
859 XDrawLine (dpy, window, mw->menu.foreground_gc,
860 x, y, x + width, y);
861 break;
863 case SEPARATOR_DOUBLE_LINE:
864 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
865 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
866 break;
868 case SEPARATOR_SINGLE_DASHED_LINE:
869 xgcv.line_style = LineOnOffDash;
870 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
871 XDrawLine (dpy, window, mw->menu.foreground_gc,
872 x, y, x + width, y);
873 xgcv.line_style = LineSolid;
874 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
875 break;
877 case SEPARATOR_DOUBLE_DASHED_LINE:
878 draw_separator (mw, window, x, y, width,
879 SEPARATOR_SINGLE_DASHED_LINE);
880 draw_separator (mw, window, x, y + 2, width,
881 SEPARATOR_SINGLE_DASHED_LINE);
882 break;
884 case SEPARATOR_SHADOW_ETCHED_IN:
885 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
886 x, y, x + width, y);
887 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
888 x, y + 1, x + width, y + 1);
889 break;
891 case SEPARATOR_SHADOW_ETCHED_OUT:
892 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
893 x, y, x + width, y);
894 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
895 x, y + 1, x + width, y + 1);
896 break;
898 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
899 xgcv.line_style = LineOnOffDash;
900 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
901 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
902 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
903 xgcv.line_style = LineSolid;
904 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
905 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
906 break;
908 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
909 xgcv.line_style = LineOnOffDash;
910 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
911 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
912 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
913 xgcv.line_style = LineSolid;
914 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
915 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
916 break;
918 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
919 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
920 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
921 break;
923 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
924 draw_separator (mw, window, x, y, width,
925 SEPARATOR_SHADOW_ETCHED_OUT);
926 draw_separator (mw, window, x, y + 3, width,
927 SEPARATOR_SHADOW_ETCHED_OUT);
928 break;
930 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
931 xgcv.line_style = LineOnOffDash;
932 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
933 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
934 draw_separator (mw, window, x, y, width,
935 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
936 xgcv.line_style = LineSolid;
937 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
938 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
939 break;
941 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
942 xgcv.line_style = LineOnOffDash;
943 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
944 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
945 draw_separator (mw, window, x, y, width,
946 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
947 xgcv.line_style = LineSolid;
948 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
949 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
950 break;
952 default:
953 abort ();
958 /* Return the pixel height of menu separator SEPARATOR. */
960 static int
961 separator_height (separator)
962 enum menu_separator separator;
964 switch (separator)
966 case SEPARATOR_NO_LINE:
967 return 2;
969 case SEPARATOR_SINGLE_LINE:
970 case SEPARATOR_SINGLE_DASHED_LINE:
971 return 1;
973 case SEPARATOR_DOUBLE_LINE:
974 case SEPARATOR_DOUBLE_DASHED_LINE:
975 return 3;
977 case SEPARATOR_SHADOW_ETCHED_IN:
978 case SEPARATOR_SHADOW_ETCHED_OUT:
979 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
980 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
981 return 2;
983 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
984 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
985 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
986 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
987 return 5;
989 default:
990 abort ();
995 /* Display the menu item and increment where.x and where.y to show how large
996 the menu item was. */
998 static void
999 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
1000 just_compute_p)
1001 XlwMenuWidget mw;
1002 widget_value* val;
1003 window_state* ws;
1004 XPoint* where;
1005 Boolean highlighted_p;
1006 Boolean horizontal_p;
1007 Boolean just_compute_p;
1009 GC deco_gc;
1010 GC text_gc;
1011 int font_height = MENU_FONT_HEIGHT (mw);
1012 int font_ascent = MENU_FONT_ASCENT (mw);
1013 int shadow = mw->menu.shadow_thickness;
1014 int margin = mw->menu.margin;
1015 int h_spacing = mw->menu.horizontal_spacing;
1016 int v_spacing = mw->menu.vertical_spacing;
1017 int label_width;
1018 int rest_width;
1019 int button_width;
1020 int height;
1021 int width;
1022 enum menu_separator separator;
1023 int separator_p = lw_separator_p (val->name, &separator, 0);
1024 #ifdef HAVE_XFT
1025 XftColor *xftfg;
1026 #endif
1028 /* compute the sizes of the item */
1029 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
1030 &button_width, &height);
1032 if (horizontal_p)
1033 width = label_width + rest_width;
1034 else
1036 label_width = ws->label_width;
1037 width = ws->width - 2 * shadow;
1040 /* Only highlight an enabled item that has a callback. */
1041 if (highlighted_p)
1042 if (!val->enabled || !(val->call_data || val->contents))
1043 highlighted_p = 0;
1045 /* do the drawing. */
1046 if (!just_compute_p)
1048 /* Add the shadow border of the containing menu */
1049 int x = where->x + shadow;
1050 int y = where->y + shadow;
1052 if (horizontal_p)
1054 x += margin;
1055 y += margin;
1058 /* pick the foreground and background GC. */
1059 if (val->enabled)
1060 text_gc = mw->menu.foreground_gc;
1061 else
1062 text_gc = mw->menu.disabled_gc;
1063 deco_gc = mw->menu.foreground_gc;
1064 #ifdef HAVE_XFT
1065 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1066 #endif
1068 if (separator_p)
1070 draw_separator (mw, ws->window, x, y, width, separator);
1072 else
1074 int x_offset = x + h_spacing + shadow;
1075 char* display_string = resource_widget_value (mw, val);
1076 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1077 False);
1079 /* Deal with centering a menu title. */
1080 if (!horizontal_p && !val->contents && !val->call_data)
1082 int l = string_width (mw, display_string);
1084 if (width > l)
1085 x_offset = (width - l) >> 1;
1087 else if (!horizontal_p && ws->button_width)
1088 x_offset += ws->button_width;
1091 #ifdef HAVE_XFT
1092 if (ws->xft_draw)
1094 int draw_y = y + v_spacing + shadow;
1095 XftDrawRect (ws->xft_draw, &mw->menu.xft_bg,
1096 x_offset, draw_y,
1097 ws->width, font_height);
1098 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1099 mw->menu.xft_font,
1100 x_offset, draw_y + font_ascent,
1101 (unsigned char *) display_string,
1102 strlen (display_string));
1104 else
1105 #endif
1106 #ifdef HAVE_X_I18N
1107 if (mw->menu.fontSet)
1108 XmbDrawString (XtDisplay (mw), ws->window, mw->menu.fontSet,
1109 text_gc, x_offset,
1110 y + v_spacing + shadow + font_ascent,
1111 display_string, strlen (display_string));
1112 else
1113 #endif
1114 XDrawString (XtDisplay (mw), ws->window,
1115 text_gc, x_offset,
1116 y + v_spacing + shadow + font_ascent,
1117 display_string, strlen (display_string));
1119 if (!horizontal_p)
1121 if (val->button_type == BUTTON_TYPE_TOGGLE)
1122 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1123 val->selected);
1124 else if (val->button_type == BUTTON_TYPE_RADIO)
1125 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1126 val->selected);
1128 if (val->contents)
1130 int a_w = arrow_width (mw);
1131 draw_arrow (mw, ws->window, deco_gc,
1132 x + width - a_w
1133 - mw->menu.horizontal_spacing
1134 - mw->menu.shadow_thickness,
1135 y + v_spacing + shadow, a_w,
1136 highlighted_p);
1138 else if (val->key)
1140 #ifdef HAVE_XFT
1141 if (ws->xft_draw)
1143 XGlyphInfo gi;
1144 int draw_x = ws->width - ws->max_rest_width
1145 + mw->menu.arrow_spacing;
1146 int draw_y = y + v_spacing + shadow + font_ascent;
1147 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1148 mw->menu.xft_font,
1149 draw_x, draw_y,
1150 (unsigned char *) val->key,
1151 strlen (val->key));
1153 else
1154 #endif
1155 #ifdef HAVE_X_I18N
1156 if (mw->menu.fontSet)
1157 XmbDrawString (XtDisplay (mw), ws->window,
1158 mw->menu.fontSet,
1159 text_gc,
1160 x + label_width + mw->menu.arrow_spacing,
1161 y + v_spacing + shadow + font_ascent,
1162 val->key, strlen (val->key));
1163 else
1164 #endif
1165 XDrawString (XtDisplay (mw), ws->window,
1166 text_gc,
1167 x + label_width + mw->menu.arrow_spacing,
1168 y + v_spacing + shadow + font_ascent,
1169 val->key, strlen (val->key));
1172 else
1174 XDrawRectangle (XtDisplay (mw), ws->window,
1175 mw->menu.background_gc,
1176 x + shadow, y + shadow,
1177 label_width + h_spacing - 1,
1178 font_height + 2 * v_spacing - 1);
1179 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1180 True, False);
1183 if (highlighted_p)
1184 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1185 False);
1189 where->x += width;
1190 where->y += height;
1193 static void
1194 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1195 this, that)
1196 XlwMenuWidget mw;
1197 int level;
1198 Boolean just_compute_p;
1199 XPoint* highlighted_pos;
1200 XPoint* hit;
1201 widget_value** hit_return;
1202 widget_value* this;
1203 widget_value* that;
1205 widget_value* val;
1206 widget_value* following_item;
1207 window_state* ws;
1208 XPoint where;
1209 int horizontal_p = mw->menu.horizontal && (level == 0);
1210 int highlighted_p;
1211 int just_compute_this_one_p;
1212 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1213 is disabled, so that we do not return any subsequent element either. */
1214 int no_return = 0;
1215 enum menu_separator separator;
1217 if (level >= mw->menu.old_depth)
1218 abort_gracefully ((Widget) mw);
1220 if (level < mw->menu.old_depth - 1)
1221 following_item = mw->menu.old_stack [level + 1];
1222 else
1223 following_item = NULL;
1225 if (hit)
1226 *hit_return = NULL;
1228 where.x = 0;
1229 where.y = 0;
1231 ws = &mw->menu.windows [level];
1232 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1234 highlighted_p = val == following_item;
1235 if (highlighted_p && highlighted_pos)
1237 if (horizontal_p)
1238 highlighted_pos->x = where.x;
1239 else
1240 highlighted_pos->y = where.y;
1243 just_compute_this_one_p =
1244 just_compute_p || ((this || that) && val != this && val != that);
1246 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1247 just_compute_this_one_p);
1249 if (highlighted_p && highlighted_pos)
1251 if (horizontal_p)
1252 highlighted_pos->y = where.y;
1253 else
1254 highlighted_pos->x = where.x;
1257 if (hit
1258 && !*hit_return
1259 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1260 && !lw_separator_p (val->name, &separator, 0)
1261 && !no_return)
1263 if (val->enabled)
1264 *hit_return = val;
1265 else
1266 no_return = 1;
1267 if (mw->menu.inside_entry != val)
1269 if (mw->menu.inside_entry)
1270 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1271 (XtPointer) mw->menu.inside_entry);
1272 mw->menu.inside_entry = val;
1273 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1274 (XtPointer) mw->menu.inside_entry);
1278 if (horizontal_p)
1279 where.y = 0;
1280 else
1281 where.x = 0;
1284 if (!just_compute_p)
1285 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1286 False, False);
1289 \f/* Motion code */
1290 static void
1291 set_new_state (mw, val, level)
1292 XlwMenuWidget mw;
1293 widget_value* val;
1294 int level;
1296 int i;
1298 mw->menu.new_depth = 0;
1299 for (i = 0; i < level; i++)
1300 push_new_stack (mw, mw->menu.old_stack [i]);
1301 push_new_stack (mw, val);
1304 static void
1305 make_windows_if_needed (mw, n)
1306 XlwMenuWidget mw;
1307 int n;
1309 int i;
1310 int start_at;
1311 XSetWindowAttributes xswa;
1312 int mask;
1313 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1314 window_state* windows;
1315 #ifdef HAVE_XFT
1316 int screen = XScreenNumberOfScreen (mw->core.screen);
1317 #endif
1319 if (mw->menu.windows_length >= n)
1320 return;
1322 xswa.save_under = True;
1323 xswa.override_redirect = True;
1324 xswa.background_pixel = mw->core.background_pixel;
1325 xswa.border_pixel = mw->core.border_pixel;
1326 xswa.event_mask =
1327 ExposureMask | PointerMotionMask | PointerMotionHintMask
1328 | ButtonReleaseMask | ButtonPressMask;
1329 xswa.cursor = mw->menu.cursor_shape;
1330 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1331 | CWEventMask | CWCursor;
1333 if (!mw->menu.windows)
1335 mw->menu.windows =
1336 (window_state*)XtMalloc (n * sizeof (window_state));
1337 start_at = 0;
1339 else
1341 mw->menu.windows =
1342 (window_state*)XtRealloc ((char*)mw->menu.windows,
1343 n * sizeof (window_state));
1344 start_at = mw->menu.windows_length;
1346 mw->menu.windows_length = n;
1348 windows = mw->menu.windows;
1350 for (i = start_at; i < n; i++)
1352 windows [i].x = 0;
1353 windows [i].y = 0;
1354 windows [i].width = 1;
1355 windows [i].height = 1;
1356 windows [i].max_rest_width = 0;
1357 windows [i].window =
1358 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1359 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1360 #ifdef HAVE_XFT
1361 if (mw->menu.xft_font)
1362 mw->menu.windows [i].xft_draw
1363 = XftDrawCreate (XtDisplay (mw),
1364 windows [i].window,
1365 DefaultVisual (XtDisplay (mw), screen),
1366 mw->core.colormap);
1367 else
1368 mw->menu.windows [i].xft_draw = 0;
1369 #endif
1373 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1376 xlwmenu_window_p (w, window)
1377 Widget w;
1378 Window window;
1380 XlwMenuWidget mw = (XlwMenuWidget) w;
1381 int i;
1383 for (i = 0; i < mw->menu.windows_length; ++i)
1384 if (window == mw->menu.windows[i].window)
1385 break;
1387 return i < mw->menu.windows_length;
1390 /* Make the window fit in the screen */
1391 static void
1392 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1393 XlwMenuWidget mw;
1394 window_state* ws;
1395 window_state* previous_ws;
1396 Boolean horizontal_p;
1398 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1399 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1400 /* 1 if we are unable to avoid an overlap between
1401 this menu and the parent menu in the X dimension. */
1402 int horizontal_overlap = 0;
1404 if (ws->x < 0)
1405 ws->x = 0;
1406 else if (ws->x + ws->width > screen_width)
1408 if (!horizontal_p)
1409 /* The addition of shadow-thickness for a sub-menu's position is
1410 to reflect a similar adjustment when the menu is displayed to
1411 the right of the invoking menu-item; it makes the sub-menu
1412 look more `attached' to the menu-item. */
1413 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1414 else
1415 ws->x = screen_width - ws->width;
1416 if (ws->x < 0)
1418 ws->x = 0;
1419 horizontal_overlap = 1;
1422 /* If we overlap in X, try to avoid overlap in Y. */
1423 if (horizontal_overlap
1424 && ws->y < previous_ws->y + previous_ws->height
1425 && previous_ws->y < ws->y + ws->height)
1427 /* Put this menu right below or right above PREVIOUS_WS
1428 if there's room. */
1429 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1430 ws->y = previous_ws->y + previous_ws->height;
1431 else if (previous_ws->y - ws->height > 0)
1432 ws->y = previous_ws->y - ws->height;
1435 if (ws->y < 0)
1436 ws->y = 0;
1437 else if (ws->y + ws->height > screen_height)
1439 if (horizontal_p)
1440 ws->y = previous_ws->y - ws->height;
1441 else
1442 ws->y = screen_height - ws->height;
1443 if (ws->y < 0)
1444 ws->y = 0;
1448 /* Updates old_stack from new_stack and redisplays. */
1449 static void
1450 remap_menubar (mw)
1451 XlwMenuWidget mw;
1453 int i;
1454 int last_same;
1455 XPoint selection_position;
1456 int old_depth = mw->menu.old_depth;
1457 int new_depth = mw->menu.new_depth;
1458 widget_value** old_stack;
1459 widget_value** new_stack;
1460 window_state* windows;
1461 widget_value* old_selection;
1462 widget_value* new_selection;
1464 /* Check that enough windows and old_stack are ready. */
1465 make_windows_if_needed (mw, new_depth);
1466 make_old_stack_space (mw, new_depth);
1467 windows = mw->menu.windows;
1468 old_stack = mw->menu.old_stack;
1469 new_stack = mw->menu.new_stack;
1471 /* compute the last identical different entry */
1472 for (i = 1; i < old_depth && i < new_depth; i++)
1473 if (old_stack [i] != new_stack [i])
1474 break;
1475 last_same = i - 1;
1477 /* Memorize the previously selected item to be able to refresh it */
1478 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1479 if (old_selection && !old_selection->enabled)
1480 old_selection = NULL;
1481 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1482 if (new_selection && !new_selection->enabled)
1483 new_selection = NULL;
1485 /* Call callback when the hightlighted item changes. */
1486 if (old_selection || new_selection)
1487 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1488 (XtPointer) new_selection);
1490 /* updates old_state from new_state. It has to be done now because
1491 display_menu (called below) uses the old_stack to know what to display. */
1492 for (i = last_same + 1; i < new_depth; i++)
1493 old_stack [i] = new_stack [i];
1494 mw->menu.old_depth = new_depth;
1496 /* refresh the last selection */
1497 selection_position.x = 0;
1498 selection_position.y = 0;
1499 display_menu (mw, last_same, new_selection == old_selection,
1500 &selection_position, NULL, NULL, old_selection, new_selection);
1502 /* Now place the new menus. */
1503 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1505 window_state *previous_ws = &windows[i - 1];
1506 window_state *ws = &windows[i];
1508 ws->x = (previous_ws->x + selection_position.x
1509 + mw->menu.shadow_thickness);
1510 if (mw->menu.horizontal && i == 1)
1511 ws->x += mw->menu.margin;
1513 #if 0
1514 if (!mw->menu.horizontal || i > 1)
1515 ws->x += mw->menu.shadow_thickness;
1516 #endif
1518 ws->y = (previous_ws->y + selection_position.y
1519 + mw->menu.shadow_thickness);
1520 if (mw->menu.horizontal && i == 1)
1521 ws->y += mw->menu.margin;
1523 size_menu (mw, i);
1525 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1527 XClearWindow (XtDisplay (mw), ws->window);
1528 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1529 ws->width, ws->height);
1530 XMapRaised (XtDisplay (mw), ws->window);
1531 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1534 /* unmap the menus that popped down */
1535 for (i = new_depth - 1; i < old_depth; i++)
1536 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1537 XUnmapWindow (XtDisplay (mw), windows[i].window);
1540 static Boolean
1541 motion_event_is_in_menu (mw, ev, level, relative_pos)
1542 XlwMenuWidget mw;
1543 XMotionEvent* ev;
1544 int level;
1545 XPoint* relative_pos;
1547 window_state* ws = &mw->menu.windows [level];
1548 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1549 int x = ws->x + shadow;
1550 int y = ws->y + shadow;
1551 relative_pos->x = ev->x_root - x;
1552 relative_pos->y = ev->y_root - y;
1553 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1554 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1557 static Boolean
1558 map_event_to_widget_value (mw, ev, val, level)
1559 XlwMenuWidget mw;
1560 XMotionEvent* ev;
1561 widget_value** val;
1562 int* level;
1564 int i;
1565 XPoint relative_pos;
1566 window_state* ws;
1567 int inside = 0;
1569 *val = NULL;
1571 /* Find the window */
1572 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1574 ws = &mw->menu.windows [i];
1575 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1577 inside = 1;
1578 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1580 if (*val)
1582 *level = i + 1;
1583 return True;
1588 if (!inside)
1590 if (mw->menu.inside_entry != NULL)
1591 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1592 (XtPointer) mw->menu.inside_entry);
1593 mw->menu.inside_entry = NULL;
1596 return False;
1599 \f/* Procedures */
1600 static void
1601 make_drawing_gcs (mw)
1602 XlwMenuWidget mw;
1604 XGCValues xgcv;
1605 float scale;
1606 XtGCMask mask = GCForeground | GCBackground;
1608 #ifdef HAVE_X_I18N
1609 if (!mw->menu.fontSet)
1611 xgcv.font = mw->menu.font->fid;
1612 mask |= GCFont;
1614 #else
1615 xgcv.font = mw->menu.font->fid;
1616 mask |= GCFont;
1617 #endif
1618 xgcv.foreground = mw->menu.foreground;
1619 xgcv.background = mw->core.background_pixel;
1620 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1622 xgcv.foreground = mw->menu.button_foreground;
1623 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1625 xgcv.background = mw->core.background_pixel;
1627 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1629 /* Allocate color for disabled menu-items. */
1630 mw->menu.disabled_foreground = mw->menu.foreground;
1631 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1632 scale = 2.3;
1633 else
1634 scale = 0.55;
1636 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1637 mw->core.colormap,
1638 &mw->menu.disabled_foreground,
1639 scale,
1640 0x8000);
1642 if (mw->menu.foreground == mw->menu.disabled_foreground
1643 || mw->core.background_pixel == mw->menu.disabled_foreground)
1645 /* Too few colors, use stipple. */
1646 xgcv.foreground = mw->menu.foreground;
1647 xgcv.fill_style = FillStippled;
1648 xgcv.stipple = mw->menu.gray_pixmap;
1649 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1650 | GCFillStyle | GCStipple, &xgcv);
1652 else
1654 /* Many colors available, use disabled pixel. */
1655 xgcv.foreground = mw->menu.disabled_foreground;
1656 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1659 xgcv.foreground = mw->menu.button_foreground;
1660 xgcv.background = mw->core.background_pixel;
1661 xgcv.fill_style = FillStippled;
1662 xgcv.stipple = mw->menu.gray_pixmap;
1663 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1664 | GCFillStyle | GCStipple, &xgcv);
1666 xgcv.foreground = mw->core.background_pixel;
1667 xgcv.background = mw->menu.foreground;
1668 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1671 static void
1672 release_drawing_gcs (mw)
1673 XlwMenuWidget mw;
1675 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1676 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1677 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1678 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1679 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1680 /* let's get some segvs if we try to use these... */
1681 mw->menu.foreground_gc = (GC) -1;
1682 mw->menu.button_gc = (GC) -1;
1683 mw->menu.disabled_gc = (GC) -1;
1684 mw->menu.inactive_button_gc = (GC) -1;
1685 mw->menu.background_gc = (GC) -1;
1688 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1689 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1691 static void
1692 make_shadow_gcs (mw)
1693 XlwMenuWidget mw;
1695 XGCValues xgcv;
1696 unsigned long pm = 0;
1697 Display *dpy = XtDisplay ((Widget) mw);
1698 Screen *screen = XtScreen ((Widget) mw);
1699 Colormap cmap = mw->core.colormap;
1700 XColor topc, botc;
1701 int top_frobbed = 0, bottom_frobbed = 0;
1703 mw->menu.free_top_shadow_color_p = 0;
1704 mw->menu.free_bottom_shadow_color_p = 0;
1706 if (mw->menu.top_shadow_color == -1)
1707 mw->menu.top_shadow_color = mw->core.background_pixel;
1708 else
1709 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1711 if (mw->menu.bottom_shadow_color == -1)
1712 mw->menu.bottom_shadow_color = mw->menu.foreground;
1713 else
1714 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1716 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1717 mw->menu.top_shadow_color == mw->menu.foreground)
1719 topc.pixel = mw->core.background_pixel;
1720 #ifdef emacs
1721 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1722 &topc.pixel,
1723 1.2, 0x8000))
1724 #else
1725 XQueryColor (dpy, cmap, &topc);
1726 /* don't overflow/wrap! */
1727 topc.red = MINL (65535, topc.red * 1.2);
1728 topc.green = MINL (65535, topc.green * 1.2);
1729 topc.blue = MINL (65535, topc.blue * 1.2);
1730 if (XAllocColor (dpy, cmap, &topc))
1731 #endif
1733 mw->menu.top_shadow_color = topc.pixel;
1734 mw->menu.free_top_shadow_color_p = 1;
1735 top_frobbed = 1;
1738 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1739 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1741 botc.pixel = mw->core.background_pixel;
1742 #ifdef emacs
1743 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1744 &botc.pixel,
1745 0.6, 0x4000))
1746 #else
1747 XQueryColor (dpy, cmap, &botc);
1748 botc.red *= 0.6;
1749 botc.green *= 0.6;
1750 botc.blue *= 0.6;
1751 if (XAllocColor (dpy, cmap, &botc))
1752 #endif
1754 mw->menu.bottom_shadow_color = botc.pixel;
1755 mw->menu.free_bottom_shadow_color_p = 1;
1756 bottom_frobbed = 1;
1760 if (top_frobbed && bottom_frobbed)
1762 if (topc.pixel == botc.pixel)
1764 if (botc.pixel == mw->menu.foreground)
1766 if (mw->menu.free_top_shadow_color_p)
1768 x_free_dpy_colors (dpy, screen, cmap,
1769 &mw->menu.top_shadow_color, 1);
1770 mw->menu.free_top_shadow_color_p = 0;
1772 mw->menu.top_shadow_color = mw->core.background_pixel;
1774 else
1776 if (mw->menu.free_bottom_shadow_color_p)
1778 x_free_dpy_colors (dpy, screen, cmap,
1779 &mw->menu.bottom_shadow_color, 1);
1780 mw->menu.free_bottom_shadow_color_p = 0;
1782 mw->menu.bottom_shadow_color = mw->menu.foreground;
1787 if (!mw->menu.top_shadow_pixmap &&
1788 mw->menu.top_shadow_color == mw->core.background_pixel)
1790 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1791 if (mw->menu.free_top_shadow_color_p)
1793 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1794 mw->menu.free_top_shadow_color_p = 0;
1796 mw->menu.top_shadow_color = mw->menu.foreground;
1798 if (!mw->menu.bottom_shadow_pixmap &&
1799 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1801 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1802 if (mw->menu.free_bottom_shadow_color_p)
1804 x_free_dpy_colors (dpy, screen, cmap,
1805 &mw->menu.bottom_shadow_color, 1);
1806 mw->menu.free_bottom_shadow_color_p = 0;
1808 mw->menu.bottom_shadow_color = mw->menu.foreground;
1811 xgcv.fill_style = FillStippled;
1812 xgcv.foreground = mw->menu.top_shadow_color;
1813 xgcv.stipple = mw->menu.top_shadow_pixmap;
1814 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1815 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1817 xgcv.foreground = mw->menu.bottom_shadow_color;
1818 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1819 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1820 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1824 static void
1825 release_shadow_gcs (mw)
1826 XlwMenuWidget mw;
1828 Display *dpy = XtDisplay ((Widget) mw);
1829 Screen *screen = XtScreen ((Widget) mw);
1830 Colormap cmap = mw->core.colormap;
1831 Pixel px[2];
1832 int i = 0;
1834 if (mw->menu.free_top_shadow_color_p)
1835 px[i++] = mw->menu.top_shadow_color;
1836 if (mw->menu.free_bottom_shadow_color_p)
1837 px[i++] = mw->menu.bottom_shadow_color;
1838 if (i > 0)
1839 x_free_dpy_colors (dpy, screen, cmap, px, i);
1841 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1842 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1845 #ifdef HAVE_XFT
1846 static int
1847 openXftFont (mw)
1848 XlwMenuWidget mw;
1850 char *fname = mw->menu.faceName;
1852 mw->menu.xft_font = 0;
1853 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FACENAME) == 0;
1855 if (fname && strcmp (fname, "none") != 0)
1857 int screen = XScreenNumberOfScreen (mw->core.screen);
1858 int len = strlen (fname), i = len-1;
1859 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1860 while (i > 0 && isdigit (fname[i]))
1861 --i;
1862 if (fname[i] == ' ')
1864 fname = xstrdup (mw->menu.faceName);
1865 fname[i] = '-';
1868 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1869 if (!mw->menu.xft_font)
1871 fprintf (stderr, "Can't find font '%s'\n", fname);
1872 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen,
1873 DEFAULT_FACENAME);
1877 if (fname != mw->menu.faceName) free (fname);
1879 return mw->menu.xft_font != 0;
1881 #endif
1883 static void
1884 XlwMenuInitialize (request, mw, args, num_args)
1885 Widget request;
1886 XlwMenuWidget mw;
1887 ArgList args;
1888 Cardinal *num_args;
1890 /* Get the GCs and the widget size */
1892 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1893 Display* display = XtDisplay (mw);
1895 #if 0
1896 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1898 /* _XtCreate is freeing the object that was passed to us,
1899 so make a copy that we will actually keep. */
1900 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1901 mw->menu.contents = tem;
1902 #endif
1904 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1905 mw->menu.cursor = mw->menu.cursor_shape;
1907 mw->menu.gray_pixmap
1908 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1909 gray_bitmap_width, gray_bitmap_height,
1910 (unsigned long)1, (unsigned long)0, 1);
1912 #ifdef HAVE_XFT
1913 if (openXftFont (mw))
1915 else
1916 #endif
1918 if (!mw->menu.font)
1920 if (!xlwmenu_default_font)
1921 xlwmenu_default_font = XLoadQueryFont (display, "fixed");
1922 mw->menu.font = xlwmenu_default_font;
1923 if (!mw->menu.font)
1925 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1926 abort ();
1930 #ifdef HAVE_X_I18N
1931 if (mw->menu.fontSet)
1932 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1933 #endif
1935 make_drawing_gcs (mw);
1936 make_shadow_gcs (mw);
1938 mw->menu.popped_up = False;
1940 mw->menu.old_depth = 1;
1941 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1942 mw->menu.old_stack_length = 1;
1943 mw->menu.old_stack [0] = mw->menu.contents;
1945 mw->menu.new_depth = 0;
1946 mw->menu.new_stack = 0;
1947 mw->menu.new_stack_length = 0;
1948 push_new_stack (mw, mw->menu.contents);
1950 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1951 mw->menu.windows_length = 1;
1952 mw->menu.windows [0].x = 0;
1953 mw->menu.windows [0].y = 0;
1954 mw->menu.windows [0].width = 0;
1955 mw->menu.windows [0].height = 0;
1956 mw->menu.windows [0].max_rest_width = 0;
1957 #ifdef HAVE_XFT
1958 mw->menu.windows [0].xft_draw = 0;
1959 #endif
1960 size_menu (mw, 0);
1962 mw->core.width = mw->menu.windows [0].width;
1963 mw->core.height = mw->menu.windows [0].height;
1966 static void
1967 XlwMenuClassInitialize ()
1969 xlwmenu_default_font = 0;
1972 static void
1973 XlwMenuRealize (w, valueMask, attributes)
1974 Widget w;
1975 Mask *valueMask;
1976 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].window = XtWindow (w);
2000 mw->menu.windows [0].x = w->core.x;
2001 mw->menu.windows [0].y = w->core.y;
2002 mw->menu.windows [0].width = w->core.width;
2003 mw->menu.windows [0].height = w->core.height;
2005 #ifdef HAVE_XFT
2006 if (mw->menu.xft_font)
2008 XColor colors[3];
2009 int screen = XScreenNumberOfScreen (mw->core.screen);
2010 mw->menu.windows [0].xft_draw
2011 = XftDrawCreate (XtDisplay (w),
2012 mw->menu.windows [0].window,
2013 DefaultVisual (XtDisplay (w), screen),
2014 mw->core.colormap);
2015 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
2016 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
2017 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
2018 = mw->menu.disabled_foreground;
2019 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
2020 mw->menu.xft_fg.color.alpha = 0xFFFF;
2021 mw->menu.xft_fg.color.red = colors[0].red;
2022 mw->menu.xft_fg.color.green = colors[0].green;
2023 mw->menu.xft_fg.color.blue = colors[0].blue;
2024 mw->menu.xft_bg.color.alpha = 0xFFFF;
2025 mw->menu.xft_bg.color.red = colors[1].red;
2026 mw->menu.xft_bg.color.green = colors[1].green;
2027 mw->menu.xft_bg.color.blue = colors[1].blue;
2028 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2029 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2030 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2031 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2033 else
2034 mw->menu.windows [0].xft_draw = 0;
2035 #endif
2038 /* Only the toplevel menubar/popup is a widget so it's the only one that
2039 receives expose events through Xt. So we repaint all the other panes
2040 when receiving an Expose event. */
2041 static void
2042 XlwMenuRedisplay (w, ev, region)
2043 Widget w;
2044 XEvent* ev;
2045 Region region;
2047 XlwMenuWidget mw = (XlwMenuWidget)w;
2048 int i;
2050 /* If we have a depth beyond 1, it's because a submenu was displayed.
2051 If the submenu has been destroyed, set the depth back to 1. */
2052 if (submenu_destroyed)
2054 mw->menu.old_depth = 1;
2055 submenu_destroyed = 0;
2058 for (i = 0; i < mw->menu.old_depth; i++)
2059 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
2063 /* Part of a hack to make the menu redisplay when a tooltip frame
2064 over a menu item is unmapped. */
2066 void
2067 xlwmenu_redisplay (w)
2068 Widget w;
2070 XlwMenuRedisplay (w, NULL, None);
2073 static void
2074 XlwMenuDestroy (w)
2075 Widget w;
2077 int i;
2078 XlwMenuWidget mw = (XlwMenuWidget) w;
2080 if (pointer_grabbed)
2081 ungrab_all ((Widget)w, CurrentTime);
2082 pointer_grabbed = 0;
2084 submenu_destroyed = 1;
2086 release_drawing_gcs (mw);
2087 release_shadow_gcs (mw);
2089 /* this doesn't come from the resource db but is created explicitly
2090 so we must free it ourselves. */
2091 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2092 mw->menu.gray_pixmap = (Pixmap) -1;
2094 #if 0
2095 /* Do free mw->menu.contents because nowadays we copy it
2096 during initialization. */
2097 XtFree (mw->menu.contents);
2098 #endif
2100 /* Don't free mw->menu.contents because that comes from our creator.
2101 The `*_stack' elements are just pointers into `contents' so leave
2102 that alone too. But free the stacks themselves. */
2103 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2104 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2106 /* Remember, you can't free anything that came from the resource
2107 database. This includes:
2108 mw->menu.cursor
2109 mw->menu.top_shadow_pixmap
2110 mw->menu.bottom_shadow_pixmap
2111 mw->menu.font
2112 Also the color cells of top_shadow_color, bottom_shadow_color,
2113 foreground, and button_foreground will never be freed until this
2114 client exits. Nice, eh?
2117 #ifdef HAVE_XFT
2118 if (mw->menu.windows [0].xft_draw)
2119 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2120 if (mw->menu.xft_font)
2121 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2122 #endif
2124 /* start from 1 because the one in slot 0 is w->core.window */
2125 for (i = 1; i < mw->menu.windows_length; i++)
2127 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
2128 #ifdef HAVE_XFT
2129 if (mw->menu.windows [i].xft_draw)
2130 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2131 #endif
2134 if (mw->menu.windows)
2135 XtFree ((char *) mw->menu.windows);
2138 static int
2139 facename_changed (XlwMenuWidget newmw,
2140 XlwMenuWidget oldmw)
2142 /* This will fore a new XftFont even if the same sting is set.
2143 This is good, as rendering parameters may have changed and
2144 we just want to do a redisplay. */
2145 return newmw->menu.faceName != oldmw->menu.faceName;
2148 static Boolean
2149 XlwMenuSetValues (current, request, new)
2150 Widget current;
2151 Widget request;
2152 Widget new;
2154 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2155 XlwMenuWidget newmw = (XlwMenuWidget)new;
2156 Boolean redisplay = False;
2157 int i;
2159 if (newmw->menu.contents
2160 && newmw->menu.contents->contents
2161 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2162 redisplay = True;
2163 /* Do redisplay if the contents are entirely eliminated. */
2164 if (newmw->menu.contents
2165 && newmw->menu.contents->contents == 0
2166 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2167 redisplay = True;
2169 if (newmw->core.background_pixel != oldmw->core.background_pixel
2170 || newmw->menu.foreground != oldmw->menu.foreground
2171 #ifdef HAVE_XFT
2172 || facename_changed (newmw, oldmw)
2173 #endif
2174 #ifdef HAVE_X_I18N
2175 || newmw->menu.fontSet != oldmw->menu.fontSet
2176 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2177 #else
2178 || newmw->menu.font != oldmw->menu.font
2179 #endif
2182 release_drawing_gcs (newmw);
2183 make_drawing_gcs (newmw);
2185 release_shadow_gcs (newmw);
2186 /* Cause the shadow colors to be recalculated. */
2187 newmw->menu.top_shadow_color = -1;
2188 newmw->menu.bottom_shadow_color = -1;
2189 make_shadow_gcs (newmw);
2191 redisplay = True;
2193 if (XtIsRealized (current))
2194 /* If the menu is currently displayed, change the display. */
2195 for (i = 0; i < oldmw->menu.windows_length; i++)
2197 XSetWindowBackground (XtDisplay (oldmw),
2198 oldmw->menu.windows [i].window,
2199 newmw->core.background_pixel);
2200 /* clear windows and generate expose events */
2201 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2202 0, 0, 0, 0, True);
2206 #ifdef HAVE_XFT
2207 if (facename_changed (newmw, oldmw))
2209 int i;
2210 int screen = XScreenNumberOfScreen (newmw->core.screen);
2211 if (newmw->menu.xft_font)
2212 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2213 openXftFont (newmw);
2214 for (i = 0; i < newmw->menu.windows_length; i++)
2216 if (newmw->menu.windows [i].xft_draw)
2217 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2218 newmw->menu.windows [i].xft_draw = 0;
2220 if (newmw->menu.xft_font)
2221 for (i = 0; i < newmw->menu.windows_length; i++)
2222 newmw->menu.windows [i].xft_draw
2223 = XftDrawCreate (XtDisplay (newmw),
2224 newmw->menu.windows [i].window,
2225 DefaultVisual (XtDisplay (newmw), screen),
2226 newmw->core.colormap);
2228 #endif
2229 #ifdef HAVE_X_I18N
2230 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2232 redisplay = True;
2233 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2235 #endif
2237 return redisplay;
2240 static void
2241 XlwMenuResize (w)
2242 Widget w;
2244 XlwMenuWidget mw = (XlwMenuWidget)w;
2246 if (mw->menu.popped_up)
2248 /* Don't allow the popup menu to resize itself. */
2249 mw->core.width = mw->menu.windows [0].width;
2250 mw->core.height = mw->menu.windows [0].height;
2251 mw->core.parent->core.width = mw->core.width ;
2252 mw->core.parent->core.height = mw->core.height ;
2254 else
2256 mw->menu.windows [0].width = mw->core.width;
2257 mw->menu.windows [0].height = mw->core.height;
2261 \f/* Action procedures */
2262 static void
2263 handle_single_motion_event (mw, ev)
2264 XlwMenuWidget mw;
2265 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 (mw, ev)
2282 XlwMenuWidget mw;
2283 XMotionEvent* ev;
2285 int x = ev->x_root;
2286 int y = ev->y_root;
2287 int state = ev->state;
2289 handle_single_motion_event (mw, ev);
2291 /* allow motion events to be generated again */
2292 if (ev->is_hint
2293 && XQueryPointer (XtDisplay (mw), ev->window,
2294 &ev->root, &ev->subwindow,
2295 &ev->x_root, &ev->y_root,
2296 &ev->x, &ev->y,
2297 &ev->state)
2298 && ev->state == state
2299 && (ev->x_root != x || ev->y_root != y))
2300 handle_single_motion_event (mw, ev);
2303 static void
2304 Start (w, ev, params, num_params)
2305 Widget w;
2306 XEvent *ev;
2307 String *params;
2308 Cardinal *num_params;
2310 XlwMenuWidget mw = (XlwMenuWidget)w;
2312 if (!mw->menu.popped_up)
2314 menu_post_event = *ev;
2315 /* If event is set to CurrentTime, get the last known time stamp.
2316 This is for calculating if (popup) menus should stay up after
2317 a fast click. */
2318 if (menu_post_event.xbutton.time == CurrentTime)
2319 menu_post_event.xbutton.time
2320 = XtLastTimestampProcessed (XtDisplay (w));
2322 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2324 else
2326 /* If we push a button while the menu is posted semipermanently,
2327 releasing the button should always pop the menu down. */
2328 next_release_must_exit = 1;
2330 /* notes the absolute position of the menubar window */
2331 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2332 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2334 /* handles the down like a move, slots are compatible */
2335 ev->xmotion.is_hint = 0;
2336 handle_motion_event (mw, &ev->xmotion);
2340 static void
2341 Drag (w, ev, params, num_params)
2342 Widget w;
2343 XEvent *ev;
2344 String *params;
2345 Cardinal *num_params;
2347 XlwMenuWidget mw = (XlwMenuWidget)w;
2348 if (mw->menu.popped_up)
2349 handle_motion_event (mw, &ev->xmotion);
2352 /* Do nothing.
2353 This is how we handle presses and releases of modifier keys. */
2354 static void
2355 Nothing (w, ev, params, num_params)
2356 Widget w;
2357 XEvent *ev;
2358 String *params;
2359 Cardinal *num_params;
2363 static widget_value *
2364 find_first_selectable (mw, item, skip_titles)
2365 XlwMenuWidget mw;
2366 widget_value *item;
2367 int skip_titles;
2369 widget_value *current = item;
2370 enum menu_separator separator;
2372 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2373 || (skip_titles && !current->call_data && !current->contents))
2374 if (current->next)
2375 current=current->next;
2376 else
2377 return NULL;
2379 return current;
2382 static widget_value *
2383 find_next_selectable (mw, item, skip_titles)
2384 XlwMenuWidget mw;
2385 widget_value *item;
2386 int skip_titles;
2388 widget_value *current = item;
2389 enum menu_separator separator;
2391 while (current->next && (current=current->next) &&
2392 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2393 || (skip_titles && !current->call_data && !current->contents)))
2396 if (current == item)
2398 if (mw->menu.old_depth < 2)
2399 return current;
2400 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2402 while (lw_separator_p (current->name, &separator, 0)
2403 || !current->enabled
2404 || (skip_titles && !current->call_data
2405 && !current->contents))
2407 if (current->next)
2408 current=current->next;
2410 if (current == item)
2411 break;
2416 return current;
2419 static widget_value *
2420 find_prev_selectable (mw, item, skip_titles)
2421 XlwMenuWidget mw;
2422 widget_value *item;
2423 int skip_titles;
2425 widget_value *current = item;
2426 widget_value *prev = item;
2428 while ((current=find_next_selectable (mw, current, skip_titles))
2429 != item)
2431 if (prev == current)
2432 break;
2433 prev=current;
2436 return prev;
2439 static void
2440 Down (w, ev, params, num_params)
2441 Widget w;
2442 XEvent *ev;
2443 String *params;
2444 Cardinal *num_params;
2446 XlwMenuWidget mw = (XlwMenuWidget) w;
2447 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2448 int popup_menu_p = mw->menu.top_depth == 1;
2450 /* Inside top-level menu-bar? */
2451 if (mw->menu.old_depth == mw->menu.top_depth)
2452 /* When <down> in the menu-bar is pressed, display the corresponding
2453 sub-menu and select the first selectable menu item there.
2454 If this is a popup menu, skip title item of the popup. */
2455 set_new_state (mw,
2456 find_first_selectable (mw,
2457 selected_item->contents,
2458 popup_menu_p),
2459 mw->menu.old_depth);
2460 else
2461 /* Highlight next possible (enabled and not separator) menu item. */
2462 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2463 mw->menu.old_depth - 1);
2465 remap_menubar (mw);
2468 static void
2469 Up (w, ev, params, num_params)
2470 Widget w;
2471 XEvent *ev;
2472 String *params;
2473 Cardinal *num_params;
2475 XlwMenuWidget mw = (XlwMenuWidget) w;
2476 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2477 int popup_menu_p = mw->menu.top_depth == 1;
2479 /* Inside top-level menu-bar? */
2480 if (mw->menu.old_depth == mw->menu.top_depth)
2482 /* FIXME: this is tricky. <up> in the menu-bar should select the
2483 last selectable item in the list. So we select the first
2484 selectable one and find the previous selectable item. Is there
2485 a better way? */
2486 /* If this is a popup menu, skip title item of the popup. */
2487 set_new_state (mw,
2488 find_first_selectable (mw,
2489 selected_item->contents,
2490 popup_menu_p),
2491 mw->menu.old_depth);
2492 remap_menubar (mw);
2493 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2494 set_new_state (mw,
2495 find_prev_selectable (mw,
2496 selected_item,
2497 popup_menu_p),
2498 mw->menu.old_depth - 1);
2500 else
2501 /* Highlight previous (enabled and not separator) menu item. */
2502 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2503 mw->menu.old_depth - 1);
2505 remap_menubar (mw);
2508 void
2509 Left (w, ev, params, num_params)
2510 Widget w;
2511 XEvent *ev;
2512 String *params;
2513 Cardinal *num_params;
2515 XlwMenuWidget mw = (XlwMenuWidget) w;
2516 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2518 /* Inside top-level menu-bar? */
2519 if (mw->menu.old_depth == mw->menu.top_depth)
2520 /* When <left> in the menu-bar is pressed, display the previous item on
2521 the menu-bar. If the current item is the first one, highlight the
2522 last item in the menubar (probably Help). */
2523 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2524 mw->menu.old_depth - 1);
2525 else if (mw->menu.old_depth == 1
2526 && selected_item->contents) /* Is this menu item expandable? */
2528 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2529 remap_menubar (mw);
2530 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2531 if (!selected_item->enabled && find_first_selectable (mw,
2532 selected_item,
2534 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2535 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 void
2549 Right (w, ev, params, num_params)
2550 Widget w;
2551 XEvent *ev;
2552 String *params;
2553 Cardinal *num_params;
2555 XlwMenuWidget mw = (XlwMenuWidget) w;
2556 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2558 /* Inside top-level menu-bar? */
2559 if (mw->menu.old_depth == mw->menu.top_depth)
2560 /* When <right> in the menu-bar is pressed, display the next item on
2561 the menu-bar. If the current item is the last one, highlight the
2562 first item (probably File). */
2563 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2564 mw->menu.old_depth - 1);
2565 else if (selected_item->contents) /* Is this menu item expandable? */
2567 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2568 remap_menubar (mw);
2569 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2570 if (!selected_item->enabled && find_first_selectable (mw,
2571 selected_item,
2573 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2574 mw->menu.old_depth - 1);
2576 else
2578 pop_new_stack_if_no_contents (mw);
2579 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2580 mw->menu.old_depth - 2);
2583 remap_menubar (mw);
2586 /* Handle key press and release events while menu is popped up.
2587 Our action is to get rid of the menu. */
2588 static void
2589 Key (w, ev, params, num_params)
2590 Widget w;
2591 XEvent *ev;
2592 String *params;
2593 Cardinal *num_params;
2595 XlwMenuWidget mw = (XlwMenuWidget)w;
2597 /* Pop down everything. */
2598 mw->menu.new_depth = 1;
2599 remap_menubar (mw);
2601 if (mw->menu.popped_up)
2603 mw->menu.popped_up = False;
2604 ungrab_all ((Widget)mw, ev->xmotion.time);
2605 if (XtIsShell (XtParent ((Widget) mw)))
2606 XtPopdown (XtParent ((Widget) mw));
2607 else
2609 XtRemoveGrab ((Widget) mw);
2610 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2614 /* callback */
2615 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2618 static void
2619 Select (w, ev, params, num_params)
2620 Widget w;
2621 XEvent *ev;
2622 String *params;
2623 Cardinal *num_params;
2625 XlwMenuWidget mw = (XlwMenuWidget)w;
2626 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2628 /* If user releases the button quickly, without selecting anything,
2629 after the initial down-click that brought the menu up,
2630 do nothing. */
2631 if ((selected_item == 0
2632 || ((widget_value *) selected_item)->call_data == 0)
2633 && !next_release_must_exit
2634 && (ev->xbutton.time - menu_post_event.xbutton.time
2635 < XtGetMultiClickTime (XtDisplay (w))))
2636 return;
2638 /* pop down everything. */
2639 mw->menu.new_depth = 1;
2640 remap_menubar (mw);
2642 if (mw->menu.popped_up)
2644 mw->menu.popped_up = False;
2645 ungrab_all ((Widget)mw, ev->xmotion.time);
2646 if (XtIsShell (XtParent ((Widget) mw)))
2647 XtPopdown (XtParent ((Widget) mw));
2648 else
2650 XtRemoveGrab ((Widget) mw);
2651 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2655 /* callback */
2656 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2660 \f/* Special code to pop-up a menu */
2661 static void
2662 pop_up_menu (mw, event)
2663 XlwMenuWidget mw;
2664 XButtonPressedEvent* event;
2666 int x = event->x_root;
2667 int y = event->y_root;
2668 int w;
2669 int h;
2670 int borderwidth = mw->menu.shadow_thickness;
2671 Screen* screen = XtScreen (mw);
2672 Display *display = XtDisplay (mw);
2674 next_release_must_exit = 0;
2676 mw->menu.inside_entry = NULL;
2677 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2679 if (XtIsShell (XtParent ((Widget)mw)))
2680 size_menu (mw, 0);
2682 w = mw->menu.windows [0].width;
2683 h = mw->menu.windows [0].height;
2685 x -= borderwidth;
2686 y -= borderwidth;
2687 if (x < borderwidth)
2688 x = borderwidth;
2689 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2690 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2691 if (y < borderwidth)
2692 y = borderwidth;
2693 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2694 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2696 mw->menu.popped_up = True;
2697 if (XtIsShell (XtParent ((Widget)mw)))
2699 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2700 XtParent ((Widget)mw)->core.border_width);
2701 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2702 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2703 mw->menu.windows [0].x = x + borderwidth;
2704 mw->menu.windows [0].y = y + borderwidth;
2705 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2707 else
2709 XEvent *ev = (XEvent *) event;
2711 XtAddGrab ((Widget) mw, True, True);
2713 /* notes the absolute position of the menubar window */
2714 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2715 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2716 mw->menu.top_depth = 2;
2719 #ifdef emacs
2720 x_catch_errors (display);
2721 #endif
2722 if (XtGrabPointer ((Widget)mw, False,
2723 (PointerMotionMask
2724 | PointerMotionHintMask
2725 | ButtonReleaseMask
2726 | ButtonPressMask),
2727 GrabModeAsync, GrabModeAsync, None,
2728 mw->menu.cursor_shape,
2729 event->time) == Success)
2731 if (! GRAB_KEYBOARD
2732 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2733 GrabModeAsync, event->time) == Success)
2735 XtSetKeyboardFocus((Widget)mw, None);
2736 pointer_grabbed = 1;
2738 else
2739 XtUngrabPointer ((Widget)mw, event->time);
2742 #ifdef emacs
2743 if (x_had_errors_p (display))
2745 pointer_grabbed = 0;
2746 XtUngrabPointer ((Widget)mw, event->time);
2748 x_uncatch_errors ();
2749 #endif
2751 ((XMotionEvent*)event)->is_hint = 0;
2752 handle_motion_event (mw, (XMotionEvent*)event);
2755 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2756 (do not change this comment) */