e675fdf457ccd275c7f3afecbc3458ac00975f3e
[bpt/emacs.git] / lwlib / xlwmenu.c
1 /* Implements a lightweight menubar widget.
2
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2012 Free Software Foundation, Inc.
5
6 This file is part of the Lucid Widget Library.
7
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.
12
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.
17
18 You should have received a copy of the GNU General Public License
19 along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
20
21 /* Created by devin@lucid.com */
22
23 #include <config.h>
24
25 #include <setjmp.h>
26 #include <lisp.h>
27
28 #include <stdio.h>
29
30 #include <sys/types.h>
31 #if (defined __sun) && !(defined SUNOS41)
32 #define SUNOS41
33 #include <X11/Xos.h>
34 #undef SUNOS41
35 #else
36 #include <X11/Xos.h>
37 #endif
38 #include <X11/IntrinsicP.h>
39 #include <X11/ObjectP.h>
40 #include <X11/StringDefs.h>
41 #include <X11/cursorfont.h>
42 #include <X11/Shell.h>
43 #include "xlwmenuP.h"
44
45 #ifdef emacs
46
47 #include <xterm.h>
48 #include "bitmaps/gray.xbm"
49
50 #else /* not emacs */
51
52 #include <X11/bitmaps/gray>
53
54 #endif /* not emacs */
55
56 static int pointer_grabbed;
57 static XEvent menu_post_event;
58
59 static char
60 xlwMenuTranslations [] =
61 "<BtnDown>: start()\n\
62 <Motion>: drag()\n\
63 <BtnUp>: select()\n\
64 <Key>Shift_L: nothing()\n\
65 <Key>Shift_R: nothing()\n\
66 <Key>Meta_L: nothing()\n\
67 <Key>Meta_R: nothing()\n\
68 <Key>Control_L: nothing()\n\
69 <Key>Control_R: nothing()\n\
70 <Key>Hyper_L: nothing()\n\
71 <Key>Hyper_R: nothing()\n\
72 <Key>Super_L: nothing()\n\
73 <Key>Super_R: nothing()\n\
74 <Key>Alt_L: nothing()\n\
75 <Key>Alt_R: nothing()\n\
76 <Key>Caps_Lock: nothing()\n\
77 <Key>Shift_Lock: nothing()\n\
78 <KeyUp>Shift_L: nothing()\n\
79 <KeyUp>Shift_R: nothing()\n\
80 <KeyUp>Meta_L: nothing()\n\
81 <KeyUp>Meta_R: nothing()\n\
82 <KeyUp>Control_L: nothing()\n\
83 <KeyUp>Control_R: nothing()\n\
84 <KeyUp>Hyper_L: nothing()\n\
85 <KeyUp>Hyper_R: nothing()\n\
86 <KeyUp>Super_L: nothing()\n\
87 <KeyUp>Super_R: nothing()\n\
88 <KeyUp>Alt_L: nothing()\n\
89 <KeyUp>Alt_R: nothing()\n\
90 <KeyUp>Caps_Lock: nothing()\n\
91 <KeyUp>Shift_Lock:nothing()\n\
92 <Key>Return: select()\n\
93 <Key>Down: down()\n\
94 <Key>Up: up()\n\
95 <Key>Left: left()\n\
96 <Key>Right: right()\n\
97 <Key>: key()\n\
98 <KeyUp>: key()\n\
99 ";
100
101 /* FIXME: Space should toggle togglable menu item but not remove the menu
102 so you can toggle the next one without entering the menu again. */
103
104 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
105
106 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
107
108 #define offset(field) XtOffset(XlwMenuWidget, field)
109 static XtResource
110 xlwMenuResources[] =
111 {
112 #ifdef HAVE_X_I18N
113 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
114 offset(menu.fontSet), XtRFontSet, NULL},
115 #endif
116 #ifdef HAVE_XFT
117 #define DEFAULT_FONTNAME "Sans-10"
118 #else
119 #define DEFAULT_FONTNAME "XtDefaultFont"
120 #endif
121 {XtNfont, XtCFont, XtRString, sizeof(String),
122 offset(menu.fontName), XtRString, DEFAULT_FONTNAME },
123 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
124 offset(menu.foreground), XtRString, "XtDefaultForeground"},
125 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
126 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
127 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
128 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
129 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
130 offset(menu.margin), XtRImmediate, (XtPointer)1},
131 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
132 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
133 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
134 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
135 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
136 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
137
138 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
139 sizeof (Dimension), offset (menu.shadow_thickness),
140 XtRImmediate, (XtPointer)1},
141 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
142 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
143 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
144 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
145 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
146 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
147 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
148 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
149
150 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
151 offset(menu.open), XtRCallback, (XtPointer)NULL},
152 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
153 offset(menu.select), XtRCallback, (XtPointer)NULL},
154 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
155 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
156 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
157 offset(menu.enter), XtRCallback, (XtPointer)NULL},
158 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
159 offset(menu.leave), XtRCallback, (XtPointer)NULL},
160 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
161 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
162 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
163 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
164 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
165 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
166 };
167 #undef offset
168
169 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
170 ArgList args, Cardinal *num_args);
171 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
172 static void XlwMenuResize(Widget w);
173 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
174 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
175 static void XlwMenuDestroy(Widget w);
176 static void XlwMenuClassInitialize(void);
177 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
178 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
179 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
180 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
181 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
182 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
183 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
184 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
185 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
186 static int separator_height (enum menu_separator);
187 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
188
189 static XtActionsRec
190 xlwMenuActionsList [] =
191 {
192 {"start", Start},
193 {"drag", Drag},
194 {"down", Down},
195 {"up", Up},
196 {"left", Left},
197 {"right", Right},
198 {"select", Select},
199 {"key", Key},
200 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
201 {"nothing", Nothing},
202 };
203
204 #define SuperClass ((CoreWidgetClass)&coreClassRec)
205
206 XlwMenuClassRec xlwMenuClassRec =
207 {
208 { /* CoreClass fields initialization */
209 (WidgetClass) SuperClass, /* superclass */
210 "XlwMenu", /* class_name */
211 sizeof(XlwMenuRec), /* size */
212 XlwMenuClassInitialize, /* class_initialize */
213 NULL, /* class_part_initialize */
214 FALSE, /* class_inited */
215 XlwMenuInitialize, /* initialize */
216 NULL, /* initialize_hook */
217 XlwMenuRealize, /* realize */
218 xlwMenuActionsList, /* actions */
219 XtNumber(xlwMenuActionsList), /* num_actions */
220 xlwMenuResources, /* resources */
221 XtNumber(xlwMenuResources), /* resource_count */
222 NULLQUARK, /* xrm_class */
223 TRUE, /* compress_motion */
224 XtExposeCompressMaximal, /* compress_exposure */
225 TRUE, /* compress_enterleave */
226 FALSE, /* visible_interest */
227 XlwMenuDestroy, /* destroy */
228 XlwMenuResize, /* resize */
229 XlwMenuRedisplay, /* expose */
230 XlwMenuSetValues, /* set_values */
231 NULL, /* set_values_hook */
232 XtInheritSetValuesAlmost, /* set_values_almost */
233 NULL, /* get_values_hook */
234 NULL, /* accept_focus */
235 XtVersion, /* version */
236 NULL, /* callback_private */
237 xlwMenuTranslations, /* tm_table */
238 XtInheritQueryGeometry, /* query_geometry */
239 XtInheritDisplayAccelerator, /* display_accelerator */
240 NULL /* extension */
241 }, /* XlwMenuClass fields initialization */
242 {
243 0 /* dummy */
244 },
245 };
246
247 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
248
249 int submenu_destroyed;
250
251 /* For debug, if installation-directory is non-nil this is not an installed
252 Emacs. In that case we do not grab the keyboard to make it easier to
253 debug. */
254 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
255
256 static int next_release_must_exit;
257
258 \f/* Utilities */
259
260 /* Ungrab pointer and keyboard */
261 static void
262 ungrab_all (Widget w, Time ungrabtime)
263 {
264 XtUngrabPointer (w, ungrabtime);
265 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
266 }
267
268 /* Like abort, but remove grabs from widget W before. */
269
270 static _Noreturn void
271 abort_gracefully (Widget w)
272 {
273 if (XtIsShell (XtParent (w)))
274 XtRemoveGrab (w);
275 ungrab_all (w, CurrentTime);
276 abort ();
277 }
278
279 static void
280 push_new_stack (XlwMenuWidget mw, widget_value *val)
281 {
282 if (!mw->menu.new_stack)
283 {
284 mw->menu.new_stack_length = 10;
285 mw->menu.new_stack =
286 (widget_value**)XtCalloc (mw->menu.new_stack_length,
287 sizeof (widget_value*));
288 }
289 else if (mw->menu.new_depth == mw->menu.new_stack_length)
290 {
291 mw->menu.new_stack_length *= 2;
292 mw->menu.new_stack =
293 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
294 mw->menu.new_stack_length * sizeof (widget_value*));
295 }
296 mw->menu.new_stack [mw->menu.new_depth++] = val;
297 }
298
299 static void
300 pop_new_stack_if_no_contents (XlwMenuWidget mw)
301 {
302 if (mw->menu.new_depth > 1)
303 {
304 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
305 mw->menu.new_depth -= 1;
306 }
307 }
308
309 static void
310 make_old_stack_space (XlwMenuWidget mw, int n)
311 {
312 if (!mw->menu.old_stack)
313 {
314 mw->menu.old_stack_length = 10;
315 mw->menu.old_stack =
316 (widget_value**)XtCalloc (mw->menu.old_stack_length,
317 sizeof (widget_value*));
318 }
319 else if (mw->menu.old_stack_length < n)
320 {
321 mw->menu.old_stack_length *= 2;
322 mw->menu.old_stack =
323 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
324 mw->menu.old_stack_length * sizeof (widget_value*));
325 }
326 }
327
328 \f/* Size code */
329 static int
330 string_width (XlwMenuWidget mw, char *s)
331 {
332 XCharStruct xcs;
333 int drop;
334 #ifdef HAVE_XFT
335 if (mw->menu.xft_font)
336 {
337 XGlyphInfo gi;
338 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
339 (FcChar8 *) s,
340 strlen (s), &gi);
341 return gi.width;
342 }
343 #endif
344 #ifdef HAVE_X_I18N
345 if (mw->menu.fontSet)
346 {
347 XRectangle ink, logical;
348 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
349 return logical.width;
350 }
351 #endif
352
353 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
354 return xcs.width;
355
356 }
357
358 #ifdef HAVE_XFT
359 #define MENU_FONT_HEIGHT(mw) \
360 ((mw)->menu.xft_font != NULL \
361 ? (mw)->menu.xft_font->height \
362 : ((mw)->menu.fontSet != NULL \
363 ? (mw)->menu.font_extents->max_logical_extent.height \
364 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
365 #define MENU_FONT_ASCENT(mw) \
366 ((mw)->menu.xft_font != NULL \
367 ? (mw)->menu.xft_font->ascent \
368 : ((mw)->menu.fontSet != NULL \
369 ? - (mw)->menu.font_extents->max_logical_extent.y \
370 : (mw)->menu.font->ascent))
371 #else
372 #ifdef HAVE_X_I18N
373 #define MENU_FONT_HEIGHT(mw) \
374 ((mw)->menu.fontSet != NULL \
375 ? (mw)->menu.font_extents->max_logical_extent.height \
376 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
377 #define MENU_FONT_ASCENT(mw) \
378 ((mw)->menu.fontSet != NULL \
379 ? - (mw)->menu.font_extents->max_logical_extent.y \
380 : (mw)->menu.font->ascent)
381 #else
382 #define MENU_FONT_HEIGHT(mw) \
383 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
384 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
385 #endif
386 #endif
387
388 static int
389 arrow_width (XlwMenuWidget mw)
390 {
391 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
392 }
393
394 /* Return the width of toggle buttons of widget MW. */
395
396 static int
397 toggle_button_width (XlwMenuWidget mw)
398 {
399 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
400 }
401
402
403 /* Return the width of radio buttons of widget MW. */
404
405 static int
406 radio_button_width (XlwMenuWidget mw)
407 {
408 return toggle_button_width (mw) * 1.41;
409 }
410
411
412 static XtResource
413 nameResource[] =
414 {
415 {"labelString", "LabelString", XtRString, sizeof(String),
416 0, XtRImmediate, 0},
417 };
418
419 static char*
420 resource_widget_value (XlwMenuWidget mw, widget_value *val)
421 {
422 if (!val->toolkit_data)
423 {
424 char* resourced_name = NULL;
425 char* complete_name;
426 XtGetSubresources ((Widget) mw,
427 (XtPointer) &resourced_name,
428 val->name, val->name,
429 nameResource, 1, NULL, 0);
430 if (!resourced_name)
431 resourced_name = val->name;
432 if (!val->value)
433 {
434 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
435 strcpy (complete_name, resourced_name);
436 }
437 else
438 {
439 int complete_length =
440 strlen (resourced_name) + strlen (val->value) + 2;
441 complete_name = XtMalloc (complete_length);
442 *complete_name = 0;
443 strcat (complete_name, resourced_name);
444 strcat (complete_name, " ");
445 strcat (complete_name, val->value);
446 }
447
448 val->toolkit_data = complete_name;
449 val->free_toolkit_data = True;
450 }
451 return (char*)val->toolkit_data;
452 }
453
454 /* Returns the sizes of an item */
455 static void
456 size_menu_item (XlwMenuWidget mw,
457 widget_value* val,
458 int horizontal_p,
459 int* label_width,
460 int* rest_width,
461 int* button_width,
462 int* height)
463 {
464 enum menu_separator separator;
465
466 if (lw_separator_p (val->name, &separator, 0))
467 {
468 *height = separator_height (separator);
469 *label_width = 1;
470 *rest_width = 0;
471 *button_width = 0;
472 }
473 else
474 {
475 *height = MENU_FONT_HEIGHT (mw)
476 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
477
478 *label_width =
479 string_width (mw, resource_widget_value (mw, val))
480 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
481
482 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
483 if (!horizontal_p)
484 {
485 if (val->contents)
486 /* Add width of the arrow displayed for submenus. */
487 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
488 else if (val->key)
489 /* Add width of key equivalent string. */
490 *rest_width += (string_width (mw, val->key)
491 + mw->menu.arrow_spacing);
492
493 if (val->button_type == BUTTON_TYPE_TOGGLE)
494 *button_width = (toggle_button_width (mw)
495 + mw->menu.horizontal_spacing);
496 else if (val->button_type == BUTTON_TYPE_RADIO)
497 *button_width = (radio_button_width (mw)
498 + mw->menu.horizontal_spacing);
499 }
500 }
501 }
502
503 static void
504 size_menu (XlwMenuWidget mw, int level)
505 {
506 int label_width = 0;
507 int rest_width = 0;
508 int button_width = 0;
509 int max_rest_width = 0;
510 int max_button_width = 0;
511 int height = 0;
512 int horizontal_p = mw->menu.horizontal && (level == 0);
513 widget_value* val;
514 window_state* ws;
515
516 if (level >= mw->menu.old_depth)
517 abort_gracefully ((Widget) mw);
518
519 ws = &mw->menu.windows [level];
520 ws->width = 0;
521 ws->height = 0;
522 ws->label_width = 0;
523 ws->button_width = 0;
524
525 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
526 {
527 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
528 &button_width, &height);
529 if (horizontal_p)
530 {
531 ws->width += label_width + rest_width;
532 if (height > ws->height)
533 ws->height = height;
534 }
535 else
536 {
537 if (label_width > ws->label_width)
538 ws->label_width = label_width;
539 if (rest_width > max_rest_width)
540 max_rest_width = rest_width;
541 if (button_width > max_button_width)
542 max_button_width = button_width;
543 ws->height += height;
544 }
545 }
546
547 if (horizontal_p)
548 ws->label_width = ws->button_width = 0;
549 else
550 {
551 ws->width = ws->label_width + max_rest_width + max_button_width;
552 ws->button_width = max_button_width;
553 }
554
555 ws->width += 2 * mw->menu.shadow_thickness;
556 ws->height += 2 * mw->menu.shadow_thickness;
557 ws->max_rest_width = max_rest_width;
558
559 if (horizontal_p)
560 {
561 ws->width += 2 * mw->menu.margin;
562 ws->height += 2 * mw->menu.margin;
563 }
564 }
565
566
567 \f/* Display code */
568
569 static void
570 draw_arrow (XlwMenuWidget mw,
571 Window window,
572 GC gc,
573 int x,
574 int y,
575 int width,
576 int down_p)
577 {
578 Display *dpy = XtDisplay (mw);
579 GC top_gc = mw->menu.shadow_top_gc;
580 GC bottom_gc = mw->menu.shadow_bottom_gc;
581 int thickness = mw->menu.shadow_thickness;
582 int height = width;
583 XPoint pt[10];
584 /* alpha = atan (0.5)
585 factor = (1 + sin (alpha)) / cos (alpha) */
586 double factor = 1.62;
587 int thickness2 = thickness * factor;
588
589 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
590
591 if (down_p)
592 {
593 GC temp;
594 temp = top_gc;
595 top_gc = bottom_gc;
596 bottom_gc = temp;
597 }
598
599 pt[0].x = x;
600 pt[0].y = y + height;
601 pt[1].x = x + thickness;
602 pt[1].y = y + height - thickness2;
603 pt[2].x = x + thickness2;
604 pt[2].y = y + thickness2;
605 pt[3].x = x;
606 pt[3].y = y;
607 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
608
609 pt[0].x = x;
610 pt[0].y = y;
611 pt[1].x = x + thickness;
612 pt[1].y = y + thickness2;
613 pt[2].x = x + width - thickness2;
614 pt[2].y = y + height / 2;
615 pt[3].x = x + width;
616 pt[3].y = y + height / 2;
617 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
618
619 pt[0].x = x;
620 pt[0].y = y + height;
621 pt[1].x = x + thickness;
622 pt[1].y = y + height - thickness2;
623 pt[2].x = x + width - thickness2;
624 pt[2].y = y + height / 2;
625 pt[3].x = x + width;
626 pt[3].y = y + height / 2;
627 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
628 }
629
630
631
632 static void
633 draw_shadow_rectangle (XlwMenuWidget mw,
634 Window window,
635 int x,
636 int y,
637 int width,
638 int height,
639 int erase_p,
640 int down_p)
641 {
642 Display *dpy = XtDisplay (mw);
643 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
644 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
645 int thickness = mw->menu.shadow_thickness;
646 XPoint points [4];
647
648 if (!erase_p && down_p)
649 {
650 GC temp;
651 temp = top_gc;
652 top_gc = bottom_gc;
653 bottom_gc = temp;
654 }
655
656 points [0].x = x;
657 points [0].y = y;
658 points [1].x = x + width;
659 points [1].y = y;
660 points [2].x = x + width - thickness;
661 points [2].y = y + thickness;
662 points [3].x = x;
663 points [3].y = y + thickness;
664 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
665 points [0].x = x;
666 points [0].y = y + thickness;
667 points [1].x = x;
668 points [1].y = y + height;
669 points [2].x = x + thickness;
670 points [2].y = y + height - thickness;
671 points [3].x = x + thickness;
672 points [3].y = y + thickness;
673 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
674 points [0].x = x + width;
675 points [0].y = y;
676 points [1].x = x + width - thickness;
677 points [1].y = y + thickness;
678 points [2].x = x + width - thickness;
679 points [2].y = y + height - thickness;
680 points [3].x = x + width;
681 points [3].y = y + height - thickness;
682 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
683 points [0].x = x;
684 points [0].y = y + height;
685 points [1].x = x + width;
686 points [1].y = y + height;
687 points [2].x = x + width;
688 points [2].y = y + height - thickness;
689 points [3].x = x + thickness;
690 points [3].y = y + height - thickness;
691 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
692 }
693
694
695 static void
696 draw_shadow_rhombus (XlwMenuWidget mw,
697 Window window,
698 int x,
699 int y,
700 int width,
701 int height,
702 int erase_p,
703 int down_p)
704 {
705 Display *dpy = XtDisplay (mw);
706 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
707 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
708 int thickness = mw->menu.shadow_thickness;
709 XPoint points [4];
710
711 if (!erase_p && down_p)
712 {
713 GC temp;
714 temp = top_gc;
715 top_gc = bottom_gc;
716 bottom_gc = temp;
717 }
718
719 points [0].x = x;
720 points [0].y = y + height / 2;
721 points [1].x = x + thickness;
722 points [1].y = y + height / 2;
723 points [2].x = x + width / 2;
724 points [2].y = y + thickness;
725 points [3].x = x + width / 2;
726 points [3].y = y;
727 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
728 points [0].x = x + width / 2;
729 points [0].y = y;
730 points [1].x = x + width / 2;
731 points [1].y = y + thickness;
732 points [2].x = x + width - thickness;
733 points [2].y = y + height / 2;
734 points [3].x = x + width;
735 points [3].y = y + height / 2;
736 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
737 points [0].x = x;
738 points [0].y = y + height / 2;
739 points [1].x = x + thickness;
740 points [1].y = y + height / 2;
741 points [2].x = x + width / 2;
742 points [2].y = y + height - thickness;
743 points [3].x = x + width / 2;
744 points [3].y = y + height;
745 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
746 points [0].x = x + width / 2;
747 points [0].y = y + height;
748 points [1].x = x + width / 2;
749 points [1].y = y + height - thickness;
750 points [2].x = x + width - thickness;
751 points [2].y = y + height / 2;
752 points [3].x = x + width;
753 points [3].y = y + height / 2;
754 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
755 }
756
757
758 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
759 top-left corner of the menu item. SELECTED_P non-zero means the
760 toggle button is selected. */
761
762 static void
763 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
764 {
765 int width, height;
766
767 width = toggle_button_width (mw);
768 height = width;
769 x += mw->menu.horizontal_spacing;
770 y += (MENU_FONT_ASCENT (mw) - height) / 2;
771 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
772 }
773
774
775 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
776 top-left corner of the menu item. SELECTED_P non-zero means the
777 toggle button is selected. */
778
779 static void
780 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
781 {
782 int width, height;
783
784 width = radio_button_width (mw);
785 height = width;
786 x += mw->menu.horizontal_spacing;
787 y += (MENU_FONT_ASCENT (mw) - height) / 2;
788 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
789 }
790
791
792 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
793 top-left corner of the menu item. WIDTH is the width of the
794 separator to draw. TYPE is the separator type. */
795
796 static void
797 draw_separator (XlwMenuWidget mw,
798 Window window,
799 int x,
800 int y,
801 int width,
802 enum menu_separator type)
803 {
804 Display *dpy = XtDisplay (mw);
805 XGCValues xgcv;
806
807 switch (type)
808 {
809 case SEPARATOR_NO_LINE:
810 break;
811
812 case SEPARATOR_SINGLE_LINE:
813 XDrawLine (dpy, window, mw->menu.foreground_gc,
814 x, y, x + width, y);
815 break;
816
817 case SEPARATOR_DOUBLE_LINE:
818 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
819 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
820 break;
821
822 case SEPARATOR_SINGLE_DASHED_LINE:
823 xgcv.line_style = LineOnOffDash;
824 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
825 XDrawLine (dpy, window, mw->menu.foreground_gc,
826 x, y, x + width, y);
827 xgcv.line_style = LineSolid;
828 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
829 break;
830
831 case SEPARATOR_DOUBLE_DASHED_LINE:
832 draw_separator (mw, window, x, y, width,
833 SEPARATOR_SINGLE_DASHED_LINE);
834 draw_separator (mw, window, x, y + 2, width,
835 SEPARATOR_SINGLE_DASHED_LINE);
836 break;
837
838 case SEPARATOR_SHADOW_ETCHED_IN:
839 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
840 x, y, x + width, y);
841 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
842 x, y + 1, x + width, y + 1);
843 break;
844
845 case SEPARATOR_SHADOW_ETCHED_OUT:
846 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
847 x, y, x + width, y);
848 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
849 x, y + 1, x + width, y + 1);
850 break;
851
852 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
853 xgcv.line_style = LineOnOffDash;
854 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
855 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
856 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
857 xgcv.line_style = LineSolid;
858 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
859 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
860 break;
861
862 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
863 xgcv.line_style = LineOnOffDash;
864 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
865 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
866 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
867 xgcv.line_style = LineSolid;
868 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
869 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
870 break;
871
872 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
873 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
874 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
875 break;
876
877 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
878 draw_separator (mw, window, x, y, width,
879 SEPARATOR_SHADOW_ETCHED_OUT);
880 draw_separator (mw, window, x, y + 3, width,
881 SEPARATOR_SHADOW_ETCHED_OUT);
882 break;
883
884 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
885 xgcv.line_style = LineOnOffDash;
886 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
887 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
888 draw_separator (mw, window, x, y, width,
889 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
890 xgcv.line_style = LineSolid;
891 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
892 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
893 break;
894
895 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
896 xgcv.line_style = LineOnOffDash;
897 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
898 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
899 draw_separator (mw, window, x, y, width,
900 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
901 xgcv.line_style = LineSolid;
902 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
903 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
904 break;
905
906 default:
907 abort ();
908 }
909 }
910
911
912 /* Return the pixel height of menu separator SEPARATOR. */
913
914 static int
915 separator_height (enum menu_separator separator)
916 {
917 switch (separator)
918 {
919 case SEPARATOR_NO_LINE:
920 return 2;
921
922 case SEPARATOR_SINGLE_LINE:
923 case SEPARATOR_SINGLE_DASHED_LINE:
924 return 1;
925
926 case SEPARATOR_DOUBLE_LINE:
927 case SEPARATOR_DOUBLE_DASHED_LINE:
928 return 3;
929
930 case SEPARATOR_SHADOW_ETCHED_IN:
931 case SEPARATOR_SHADOW_ETCHED_OUT:
932 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
933 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
934 return 2;
935
936 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
937 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
938 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
939 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
940 return 5;
941
942 default:
943 abort ();
944 }
945 }
946
947
948 /* Display the menu item and increment where.x and where.y to show how large
949 the menu item was. */
950
951 static void
952 display_menu_item (XlwMenuWidget mw,
953 widget_value* val,
954 window_state* ws,
955 XPoint* where,
956 Boolean highlighted_p,
957 Boolean horizontal_p,
958 Boolean just_compute_p)
959 {
960 GC deco_gc;
961 GC text_gc;
962 int font_height = MENU_FONT_HEIGHT (mw);
963 int font_ascent = MENU_FONT_ASCENT (mw);
964 int shadow = mw->menu.shadow_thickness;
965 int margin = mw->menu.margin;
966 int h_spacing = mw->menu.horizontal_spacing;
967 int v_spacing = mw->menu.vertical_spacing;
968 int label_width;
969 int rest_width;
970 int button_width;
971 int height;
972 int width;
973 enum menu_separator separator;
974 int separator_p = lw_separator_p (val->name, &separator, 0);
975 #ifdef HAVE_XFT
976 XftColor *xftfg;
977 #endif
978
979 /* compute the sizes of the item */
980 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
981 &button_width, &height);
982
983 if (horizontal_p)
984 width = label_width + rest_width;
985 else
986 {
987 label_width = ws->label_width;
988 width = ws->width - 2 * shadow;
989 }
990
991 /* Only highlight an enabled item that has a callback. */
992 if (highlighted_p)
993 if (!val->enabled || !(val->call_data || val->contents))
994 highlighted_p = 0;
995
996 /* do the drawing. */
997 if (!just_compute_p)
998 {
999 /* Add the shadow border of the containing menu */
1000 int x = where->x + shadow;
1001 int y = where->y + shadow;
1002
1003 if (horizontal_p)
1004 {
1005 x += margin;
1006 y += margin;
1007 }
1008
1009 /* pick the foreground and background GC. */
1010 if (val->enabled)
1011 text_gc = mw->menu.foreground_gc;
1012 else
1013 text_gc = mw->menu.disabled_gc;
1014 deco_gc = mw->menu.foreground_gc;
1015 #ifdef HAVE_XFT
1016 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1017 #endif
1018
1019 if (separator_p)
1020 {
1021 draw_separator (mw, ws->pixmap, x, y, width, separator);
1022 }
1023 else
1024 {
1025 int x_offset = x + h_spacing + shadow;
1026 char* display_string = resource_widget_value (mw, val);
1027 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1028 False);
1029
1030 /* Deal with centering a menu title. */
1031 if (!horizontal_p && !val->contents && !val->call_data)
1032 {
1033 int l = string_width (mw, display_string);
1034
1035 if (width > l)
1036 x_offset = (width - l) >> 1;
1037 }
1038 else if (!horizontal_p && ws->button_width)
1039 x_offset += ws->button_width;
1040
1041
1042 #ifdef HAVE_XFT
1043 if (ws->xft_draw)
1044 {
1045 int draw_y = y + v_spacing + shadow;
1046 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1047 mw->menu.xft_font,
1048 x_offset, draw_y + font_ascent,
1049 (unsigned char *) display_string,
1050 strlen (display_string));
1051 }
1052 else
1053 #endif
1054 #ifdef HAVE_X_I18N
1055 if (mw->menu.fontSet)
1056 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1057 text_gc, x_offset,
1058 y + v_spacing + shadow + font_ascent,
1059 display_string, strlen (display_string));
1060 else
1061 #endif
1062 XDrawString (XtDisplay (mw), ws->pixmap,
1063 text_gc, x_offset,
1064 y + v_spacing + shadow + font_ascent,
1065 display_string, strlen (display_string));
1066
1067 if (!horizontal_p)
1068 {
1069 if (val->button_type == BUTTON_TYPE_TOGGLE)
1070 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1071 val->selected);
1072 else if (val->button_type == BUTTON_TYPE_RADIO)
1073 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1074 val->selected);
1075
1076 if (val->contents)
1077 {
1078 int a_w = arrow_width (mw);
1079 draw_arrow (mw, ws->pixmap, deco_gc,
1080 x + width - a_w
1081 - mw->menu.horizontal_spacing
1082 - mw->menu.shadow_thickness,
1083 y + v_spacing + shadow, a_w,
1084 highlighted_p);
1085 }
1086 else if (val->key)
1087 {
1088 #ifdef HAVE_XFT
1089 if (ws->xft_draw)
1090 {
1091 int draw_x = ws->width - ws->max_rest_width
1092 + mw->menu.arrow_spacing;
1093 int draw_y = y + v_spacing + shadow + font_ascent;
1094 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1095 mw->menu.xft_font,
1096 draw_x, draw_y,
1097 (unsigned char *) val->key,
1098 strlen (val->key));
1099 }
1100 else
1101 #endif
1102 #ifdef HAVE_X_I18N
1103 if (mw->menu.fontSet)
1104 XmbDrawString (XtDisplay (mw), ws->pixmap,
1105 mw->menu.fontSet,
1106 text_gc,
1107 x + label_width + mw->menu.arrow_spacing,
1108 y + v_spacing + shadow + font_ascent,
1109 val->key, strlen (val->key));
1110 else
1111 #endif
1112 XDrawString (XtDisplay (mw), ws->pixmap,
1113 text_gc,
1114 x + label_width + mw->menu.arrow_spacing,
1115 y + v_spacing + shadow + font_ascent,
1116 val->key, strlen (val->key));
1117 }
1118 }
1119 else
1120 {
1121 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1122 mw->menu.background_gc,
1123 x + shadow, y + shadow,
1124 label_width + h_spacing - 1,
1125 font_height + 2 * v_spacing - 1);
1126 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1127 True, False);
1128 }
1129
1130 if (highlighted_p)
1131 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1132 False);
1133 }
1134 }
1135
1136 where->x += width;
1137 where->y += height;
1138 }
1139
1140 static void
1141 display_menu (XlwMenuWidget mw,
1142 int level,
1143 Boolean just_compute_p,
1144 XPoint *highlighted_pos,
1145 XPoint *hit,
1146 widget_value **hit_return)
1147 {
1148 widget_value* val;
1149 widget_value* following_item;
1150 window_state* ws;
1151 XPoint where;
1152 int horizontal_p = mw->menu.horizontal && (level == 0);
1153 int highlighted_p;
1154 int no_return = 0;
1155 enum menu_separator separator;
1156
1157 if (level >= mw->menu.old_depth)
1158 abort_gracefully ((Widget) mw);
1159
1160 if (level < mw->menu.old_depth - 1)
1161 following_item = mw->menu.old_stack [level + 1];
1162 else
1163 following_item = NULL;
1164
1165 if (hit)
1166 *hit_return = NULL;
1167
1168 where.x = 0;
1169 where.y = 0;
1170
1171 ws = &mw->menu.windows [level];
1172
1173 if (!just_compute_p)
1174 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1175 0, 0, ws->width, ws->height);
1176
1177 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1178 {
1179 highlighted_p = val == following_item;
1180 if (highlighted_p && highlighted_pos)
1181 {
1182 if (horizontal_p)
1183 highlighted_pos->x = where.x;
1184 else
1185 highlighted_pos->y = where.y;
1186 }
1187
1188 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1189 just_compute_p);
1190
1191 if (highlighted_p && highlighted_pos)
1192 {
1193 if (horizontal_p)
1194 highlighted_pos->y = where.y;
1195 else
1196 highlighted_pos->x = where.x;
1197 }
1198
1199 if (hit
1200 && !*hit_return
1201 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1202 && !lw_separator_p (val->name, &separator, 0)
1203 && !no_return)
1204 {
1205 if (val->enabled)
1206 *hit_return = val;
1207 else
1208 no_return = 1;
1209 if (mw->menu.inside_entry != val)
1210 {
1211 if (mw->menu.inside_entry)
1212 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1213 (XtPointer) mw->menu.inside_entry);
1214 mw->menu.inside_entry = val;
1215 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1216 (XtPointer) mw->menu.inside_entry);
1217 }
1218 }
1219
1220 if (horizontal_p)
1221 where.y = 0;
1222 else
1223 where.x = 0;
1224 }
1225
1226 if (!just_compute_p)
1227 {
1228 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1229 False, False);
1230 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1231 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1232 }
1233 }
1234
1235 \f/* Motion code */
1236 static void
1237 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1238 {
1239 int i;
1240
1241 mw->menu.new_depth = 0;
1242 for (i = 0; i < level; i++)
1243 push_new_stack (mw, mw->menu.old_stack [i]);
1244 push_new_stack (mw, val);
1245 }
1246
1247 static void
1248 expose_cb (Widget widget,
1249 XtPointer closure,
1250 XEvent* event,
1251 Boolean* continue_to_dispatch)
1252 {
1253 XlwMenuWidget mw = (XlwMenuWidget) closure;
1254 int i;
1255
1256 *continue_to_dispatch = False;
1257 for (i = 0; i < mw->menu.windows_length; ++i)
1258 if (mw->menu.windows [i].w == widget) break;
1259 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1260 display_menu (mw, i, False, NULL, NULL, NULL);
1261 }
1262
1263 static void
1264 set_window_type (Widget w, XlwMenuWidget mw)
1265 {
1266 int popup_menu_p = mw->menu.top_depth == 1;
1267 Atom type = XInternAtom (XtDisplay (w),
1268 popup_menu_p
1269 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1270 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1271 False);
1272
1273 XChangeProperty (XtDisplay (w), XtWindow (w),
1274 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1275 XA_ATOM, 32, PropModeReplace,
1276 (unsigned char *)&type, 1);
1277 }
1278
1279
1280 static void
1281 make_windows_if_needed (XlwMenuWidget mw, int n)
1282 {
1283 int i;
1284 int start_at;
1285 window_state* windows;
1286
1287 if (mw->menu.windows_length >= n)
1288 return;
1289
1290 if (!mw->menu.windows)
1291 {
1292 mw->menu.windows =
1293 (window_state*)XtMalloc (n * sizeof (window_state));
1294 start_at = 0;
1295 }
1296 else
1297 {
1298 mw->menu.windows =
1299 (window_state*)XtRealloc ((char*)mw->menu.windows,
1300 n * sizeof (window_state));
1301 start_at = mw->menu.windows_length;
1302 }
1303 mw->menu.windows_length = n;
1304
1305 windows = mw->menu.windows;
1306
1307 for (i = start_at; i < n; i++)
1308 {
1309 Arg av[10];
1310 int ac = 0;
1311 windows [i].x = 0;
1312 windows [i].y = 0;
1313 windows [i].width = 1;
1314 windows [i].height = 1;
1315 windows [i].max_rest_width = 0;
1316 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1317 XtSetArg (av[ac], XtNheight, 1); ++ac;
1318 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1319 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1320 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1321 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1322 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1323 windows [i].w =
1324 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1325 (Widget) mw, av, ac);
1326 XtRealizeWidget (windows [i].w);
1327 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1328 windows [i].window = XtWindow (windows [i].w);
1329 windows [i].pixmap = None;
1330 #ifdef HAVE_XFT
1331 windows [i].xft_draw = 0;
1332 #endif
1333 set_window_type (windows [i].w, mw);
1334 }
1335 XFlush (XtDisplay (mw));
1336 }
1337
1338 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1339
1340 int
1341 xlwmenu_window_p (Widget w, Window window)
1342 {
1343 XlwMenuWidget mw = (XlwMenuWidget) w;
1344 int i;
1345
1346 for (i = 0; i < mw->menu.windows_length; ++i)
1347 if (window == mw->menu.windows[i].window)
1348 break;
1349
1350 return i < mw->menu.windows_length;
1351 }
1352
1353 /* Make the window fit in the screen */
1354 static void
1355 fit_to_screen (XlwMenuWidget mw,
1356 window_state *ws,
1357 window_state *previous_ws,
1358 Boolean horizontal_p)
1359 {
1360 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1361 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1362 /* 1 if we are unable to avoid an overlap between
1363 this menu and the parent menu in the X dimension. */
1364 int horizontal_overlap = 0;
1365
1366 if (ws->x < 0)
1367 ws->x = 0;
1368 else if (ws->x + ws->width > screen_width)
1369 {
1370 if (!horizontal_p)
1371 /* The addition of shadow-thickness for a sub-menu's position is
1372 to reflect a similar adjustment when the menu is displayed to
1373 the right of the invoking menu-item; it makes the sub-menu
1374 look more `attached' to the menu-item. */
1375 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1376 else
1377 ws->x = screen_width - ws->width;
1378 if (ws->x < 0)
1379 {
1380 ws->x = 0;
1381 horizontal_overlap = 1;
1382 }
1383 }
1384 /* If we overlap in X, try to avoid overlap in Y. */
1385 if (horizontal_overlap
1386 && ws->y < previous_ws->y + previous_ws->height
1387 && previous_ws->y < ws->y + ws->height)
1388 {
1389 /* Put this menu right below or right above PREVIOUS_WS
1390 if there's room. */
1391 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1392 ws->y = previous_ws->y + previous_ws->height;
1393 else if (previous_ws->y - ws->height > 0)
1394 ws->y = previous_ws->y - ws->height;
1395 }
1396
1397 if (ws->y < 0)
1398 ws->y = 0;
1399 else if (ws->y + ws->height > screen_height)
1400 {
1401 if (horizontal_p)
1402 ws->y = previous_ws->y - ws->height;
1403 else
1404 ws->y = screen_height - ws->height;
1405 if (ws->y < 0)
1406 ws->y = 0;
1407 }
1408 }
1409
1410 static void
1411 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1412 {
1413 if (ws->pixmap != None)
1414 {
1415 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1416 ws->pixmap = None;
1417 }
1418 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1419 ws->width, ws->height,
1420 DefaultDepthOfScreen (XtScreen (ws->w)));
1421 #ifdef HAVE_XFT
1422 if (ws->xft_draw)
1423 XftDrawDestroy (ws->xft_draw);
1424 if (mw->menu.xft_font)
1425 {
1426 int screen = XScreenNumberOfScreen (mw->core.screen);
1427 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1428 ws->pixmap,
1429 DefaultVisual (XtDisplay (ws->w), screen),
1430 mw->core.colormap);
1431 }
1432 else
1433 ws->xft_draw = 0;
1434 #endif
1435 }
1436
1437 /* Updates old_stack from new_stack and redisplays. */
1438 static void
1439 remap_menubar (XlwMenuWidget mw)
1440 {
1441 int i;
1442 int last_same;
1443 XPoint selection_position;
1444 int old_depth = mw->menu.old_depth;
1445 int new_depth = mw->menu.new_depth;
1446 widget_value** old_stack;
1447 widget_value** new_stack;
1448 window_state* windows;
1449 widget_value* old_selection;
1450 widget_value* new_selection;
1451
1452 /* Check that enough windows and old_stack are ready. */
1453 make_windows_if_needed (mw, new_depth);
1454 make_old_stack_space (mw, new_depth);
1455 windows = mw->menu.windows;
1456 old_stack = mw->menu.old_stack;
1457 new_stack = mw->menu.new_stack;
1458
1459 /* compute the last identical different entry */
1460 for (i = 1; i < old_depth && i < new_depth; i++)
1461 if (old_stack [i] != new_stack [i])
1462 break;
1463 last_same = i - 1;
1464
1465 /* Memorize the previously selected item to be able to refresh it */
1466 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1467 if (old_selection && !old_selection->enabled)
1468 old_selection = NULL;
1469 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1470 if (new_selection && !new_selection->enabled)
1471 new_selection = NULL;
1472
1473 /* Call callback when the highlighted item changes. */
1474 if (old_selection || new_selection)
1475 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1476 (XtPointer) new_selection);
1477
1478 /* updates old_state from new_state. It has to be done now because
1479 display_menu (called below) uses the old_stack to know what to display. */
1480 for (i = last_same + 1; i < new_depth; i++)
1481 {
1482 XtPopdown (mw->menu.windows [i].w);
1483 old_stack [i] = new_stack [i];
1484 }
1485 mw->menu.old_depth = new_depth;
1486
1487 /* refresh the last selection */
1488 selection_position.x = 0;
1489 selection_position.y = 0;
1490 display_menu (mw, last_same, new_selection == old_selection,
1491 &selection_position, NULL, NULL);
1492
1493 /* Now place the new menus. */
1494 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1495 {
1496 window_state *previous_ws = &windows[i - 1];
1497 window_state *ws = &windows[i];
1498
1499 ws->x = (previous_ws->x + selection_position.x
1500 + mw->menu.shadow_thickness);
1501 if (mw->menu.horizontal && i == 1)
1502 ws->x += mw->menu.margin;
1503
1504 #if 0
1505 if (!mw->menu.horizontal || i > 1)
1506 ws->x += mw->menu.shadow_thickness;
1507 #endif
1508
1509 ws->y = (previous_ws->y + selection_position.y
1510 + mw->menu.shadow_thickness);
1511 if (mw->menu.horizontal && i == 1)
1512 ws->y += mw->menu.margin;
1513
1514 size_menu (mw, i);
1515
1516 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1517
1518 create_pixmap_for_menu (ws, mw);
1519 XtMoveWidget (ws->w, ws->x, ws->y);
1520 XtPopup (ws->w, XtGrabNone);
1521 XtResizeWidget (ws->w, ws->width, ws->height,
1522 mw->core.border_width);
1523 XtResizeWindow (ws->w);
1524 display_menu (mw, i, False, &selection_position, NULL, NULL);
1525 }
1526
1527 /* unmap the menus that popped down */
1528 for (i = new_depth - 1; i < old_depth; i++)
1529 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1530 XtPopdown (windows[i].w);
1531 }
1532
1533 static Boolean
1534 motion_event_is_in_menu (XlwMenuWidget mw,
1535 XMotionEvent *ev,
1536 int level,
1537 XPoint *relative_pos)
1538 {
1539 window_state* ws = &mw->menu.windows [level];
1540 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1541 int x = ws->x + shadow;
1542 int y = ws->y + shadow;
1543 relative_pos->x = ev->x_root - x;
1544 relative_pos->y = ev->y_root - y;
1545 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1546 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1547 }
1548
1549 static Boolean
1550 map_event_to_widget_value (XlwMenuWidget mw,
1551 XMotionEvent *ev,
1552 widget_value **val,
1553 int *level)
1554 {
1555 int i;
1556 XPoint relative_pos;
1557 window_state* ws;
1558 int inside = 0;
1559
1560 *val = NULL;
1561
1562 /* Find the window */
1563 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1564 {
1565 ws = &mw->menu.windows [i];
1566 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1567 {
1568 inside = 1;
1569 display_menu (mw, i, True, NULL, &relative_pos, val);
1570
1571 if (*val)
1572 {
1573 *level = i + 1;
1574 return True;
1575 }
1576 }
1577 }
1578
1579 if (!inside)
1580 {
1581 if (mw->menu.inside_entry != NULL)
1582 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1583 (XtPointer) mw->menu.inside_entry);
1584 mw->menu.inside_entry = NULL;
1585 }
1586
1587 return False;
1588 }
1589
1590 \f/* Procedures */
1591 static void
1592 make_drawing_gcs (XlwMenuWidget mw)
1593 {
1594 XGCValues xgcv;
1595 float scale;
1596 XtGCMask mask = GCForeground | GCBackground;
1597
1598 #ifdef HAVE_X_I18N
1599 if (!mw->menu.fontSet && mw->menu.font)
1600 {
1601 xgcv.font = mw->menu.font->fid;
1602 mask |= GCFont;
1603 }
1604 #else
1605 if (mw->menu.font)
1606 {
1607 xgcv.font = mw->menu.font->fid;
1608 mask |= GCFont;
1609 }
1610 #endif
1611 xgcv.foreground = mw->menu.foreground;
1612 xgcv.background = mw->core.background_pixel;
1613 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1614
1615 xgcv.foreground = mw->menu.button_foreground;
1616 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1617
1618 xgcv.background = mw->core.background_pixel;
1619
1620 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1621
1622 /* Allocate color for disabled menu-items. */
1623 mw->menu.disabled_foreground = mw->menu.foreground;
1624 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1625 scale = 2.3;
1626 else
1627 scale = 0.55;
1628
1629 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1630 mw->core.colormap,
1631 &mw->menu.disabled_foreground,
1632 scale,
1633 0x8000);
1634
1635 if (mw->menu.foreground == mw->menu.disabled_foreground
1636 || mw->core.background_pixel == mw->menu.disabled_foreground)
1637 {
1638 /* Too few colors, use stipple. */
1639 xgcv.foreground = mw->menu.foreground;
1640 xgcv.fill_style = FillStippled;
1641 xgcv.stipple = mw->menu.gray_pixmap;
1642 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1643 | GCFillStyle | GCStipple, &xgcv);
1644 }
1645 else
1646 {
1647 /* Many colors available, use disabled pixel. */
1648 xgcv.foreground = mw->menu.disabled_foreground;
1649 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1650 }
1651
1652 xgcv.foreground = mw->menu.button_foreground;
1653 xgcv.background = mw->core.background_pixel;
1654 xgcv.fill_style = FillStippled;
1655 xgcv.stipple = mw->menu.gray_pixmap;
1656 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1657 | GCFillStyle | GCStipple, &xgcv);
1658
1659 xgcv.foreground = mw->core.background_pixel;
1660 xgcv.background = mw->menu.foreground;
1661 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1662 }
1663
1664 static void
1665 release_drawing_gcs (XlwMenuWidget mw)
1666 {
1667 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1668 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1669 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1670 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1671 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1672 /* let's get some segvs if we try to use these... */
1673 mw->menu.foreground_gc = (GC) -1;
1674 mw->menu.button_gc = (GC) -1;
1675 mw->menu.disabled_gc = (GC) -1;
1676 mw->menu.inactive_button_gc = (GC) -1;
1677 mw->menu.background_gc = (GC) -1;
1678 }
1679
1680 #ifndef emacs
1681 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1682 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1683 #endif
1684
1685 static void
1686 make_shadow_gcs (XlwMenuWidget mw)
1687 {
1688 XGCValues xgcv;
1689 unsigned long pm = 0;
1690 Display *dpy = XtDisplay ((Widget) mw);
1691 Screen *screen = XtScreen ((Widget) mw);
1692 Colormap cmap = mw->core.colormap;
1693 XColor topc, botc;
1694 int top_frobbed = 0, bottom_frobbed = 0;
1695
1696 mw->menu.free_top_shadow_color_p = 0;
1697 mw->menu.free_bottom_shadow_color_p = 0;
1698
1699 if (mw->menu.top_shadow_color == -1)
1700 mw->menu.top_shadow_color = mw->core.background_pixel;
1701 else
1702 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1703
1704 if (mw->menu.bottom_shadow_color == -1)
1705 mw->menu.bottom_shadow_color = mw->menu.foreground;
1706 else
1707 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1708
1709 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1710 mw->menu.top_shadow_color == mw->menu.foreground)
1711 {
1712 topc.pixel = mw->core.background_pixel;
1713 #ifdef emacs
1714 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1715 &topc.pixel,
1716 1.2, 0x8000))
1717 #else
1718 XQueryColor (dpy, cmap, &topc);
1719 /* don't overflow/wrap! */
1720 topc.red = MINL (65535, topc.red * 1.2);
1721 topc.green = MINL (65535, topc.green * 1.2);
1722 topc.blue = MINL (65535, topc.blue * 1.2);
1723 if (XAllocColor (dpy, cmap, &topc))
1724 #endif
1725 {
1726 mw->menu.top_shadow_color = topc.pixel;
1727 mw->menu.free_top_shadow_color_p = 1;
1728 top_frobbed = 1;
1729 }
1730 }
1731 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1732 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1733 {
1734 botc.pixel = mw->core.background_pixel;
1735 #ifdef emacs
1736 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1737 &botc.pixel,
1738 0.6, 0x4000))
1739 #else
1740 XQueryColor (dpy, cmap, &botc);
1741 botc.red *= 0.6;
1742 botc.green *= 0.6;
1743 botc.blue *= 0.6;
1744 if (XAllocColor (dpy, cmap, &botc))
1745 #endif
1746 {
1747 mw->menu.bottom_shadow_color = botc.pixel;
1748 mw->menu.free_bottom_shadow_color_p = 1;
1749 bottom_frobbed = 1;
1750 }
1751 }
1752
1753 if (top_frobbed && bottom_frobbed)
1754 {
1755 if (topc.pixel == botc.pixel)
1756 {
1757 if (botc.pixel == mw->menu.foreground)
1758 {
1759 if (mw->menu.free_top_shadow_color_p)
1760 {
1761 x_free_dpy_colors (dpy, screen, cmap,
1762 &mw->menu.top_shadow_color, 1);
1763 mw->menu.free_top_shadow_color_p = 0;
1764 }
1765 mw->menu.top_shadow_color = mw->core.background_pixel;
1766 }
1767 else
1768 {
1769 if (mw->menu.free_bottom_shadow_color_p)
1770 {
1771 x_free_dpy_colors (dpy, screen, cmap,
1772 &mw->menu.bottom_shadow_color, 1);
1773 mw->menu.free_bottom_shadow_color_p = 0;
1774 }
1775 mw->menu.bottom_shadow_color = mw->menu.foreground;
1776 }
1777 }
1778 }
1779
1780 if (!mw->menu.top_shadow_pixmap &&
1781 mw->menu.top_shadow_color == mw->core.background_pixel)
1782 {
1783 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1784 if (mw->menu.free_top_shadow_color_p)
1785 {
1786 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1787 mw->menu.free_top_shadow_color_p = 0;
1788 }
1789 mw->menu.top_shadow_color = mw->menu.foreground;
1790 }
1791 if (!mw->menu.bottom_shadow_pixmap &&
1792 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1793 {
1794 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1795 if (mw->menu.free_bottom_shadow_color_p)
1796 {
1797 x_free_dpy_colors (dpy, screen, cmap,
1798 &mw->menu.bottom_shadow_color, 1);
1799 mw->menu.free_bottom_shadow_color_p = 0;
1800 }
1801 mw->menu.bottom_shadow_color = mw->menu.foreground;
1802 }
1803
1804 xgcv.fill_style = FillStippled;
1805 xgcv.foreground = mw->menu.top_shadow_color;
1806 xgcv.stipple = mw->menu.top_shadow_pixmap;
1807 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1808 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1809
1810 xgcv.foreground = mw->menu.bottom_shadow_color;
1811 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1812 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1813 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1814 }
1815
1816
1817 static void
1818 release_shadow_gcs (XlwMenuWidget mw)
1819 {
1820 Display *dpy = XtDisplay ((Widget) mw);
1821 Screen *screen = XtScreen ((Widget) mw);
1822 Colormap cmap = mw->core.colormap;
1823 Pixel px[2];
1824 int i = 0;
1825
1826 if (mw->menu.free_top_shadow_color_p)
1827 px[i++] = mw->menu.top_shadow_color;
1828 if (mw->menu.free_bottom_shadow_color_p)
1829 px[i++] = mw->menu.bottom_shadow_color;
1830 if (i > 0)
1831 x_free_dpy_colors (dpy, screen, cmap, px, i);
1832
1833 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1834 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1835 }
1836
1837 #ifdef HAVE_XFT
1838 static XftFont *
1839 getDefaultXftFont (XlwMenuWidget mw)
1840 {
1841 int screen = XScreenNumberOfScreen (mw->core.screen);
1842 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1843 }
1844
1845 static int
1846 openXftFont (XlwMenuWidget mw)
1847 {
1848 char *fname = mw->menu.fontName;
1849
1850 mw->menu.xft_font = 0;
1851 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1852
1853 if (fname && strcmp (fname, "none") != 0)
1854 {
1855 int screen = XScreenNumberOfScreen (mw->core.screen);
1856 int len = strlen (fname), i = len-1;
1857 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1858 while (i > 0 && '0' <= fname[i] && fname[i] <= '9')
1859 --i;
1860 if (fname[i] == ' ')
1861 {
1862 fname = xstrdup (mw->menu.fontName);
1863 fname[i] = '-';
1864 }
1865
1866 mw->menu.font = XLoadQueryFont (XtDisplay (mw), fname);
1867 if (!mw->menu.font)
1868 {
1869 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1870 if (!mw->menu.xft_font)
1871 {
1872 fprintf (stderr, "Can't find font '%s'\n", fname);
1873 mw->menu.xft_font = getDefaultXftFont (mw);
1874 }
1875 }
1876 }
1877
1878 if (fname != mw->menu.fontName) xfree (fname);
1879
1880 return mw->menu.xft_font != 0;
1881 }
1882 #endif
1883
1884 static void
1885 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1886 {
1887 /* Get the GCs and the widget size */
1888 XlwMenuWidget mw = (XlwMenuWidget) w;
1889 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1890 Display* display = XtDisplay (mw);
1891
1892 #if 0
1893 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1894
1895 /* _XtCreate is freeing the object that was passed to us,
1896 so make a copy that we will actually keep. */
1897 memcpy (tem, mw->menu.contents, sizeof (widget_value));
1898 mw->menu.contents = tem;
1899 #endif
1900
1901 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1902 mw->menu.cursor = mw->menu.cursor_shape;
1903
1904 mw->menu.gray_pixmap
1905 = XCreatePixmapFromBitmapData (display, window, gray_bits,
1906 gray_width, gray_height,
1907 (unsigned long)1, (unsigned long)0, 1);
1908
1909 #ifdef HAVE_XFT
1910 if (openXftFont (mw))
1911 ;
1912 else
1913 #endif
1914 {
1915 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1916 if (!mw->menu.font)
1917 {
1918 mw->menu.font = XLoadQueryFont (display, "fixed");
1919 if (!mw->menu.font)
1920 {
1921 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1922 abort ();
1923 }
1924 }
1925 }
1926
1927 #ifdef HAVE_X_I18N
1928 if (mw->menu.fontSet)
1929 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1930 #endif
1931
1932 make_drawing_gcs (mw);
1933 make_shadow_gcs (mw);
1934
1935 mw->menu.popped_up = False;
1936
1937 mw->menu.old_depth = 1;
1938 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1939 mw->menu.old_stack_length = 1;
1940 mw->menu.old_stack [0] = mw->menu.contents;
1941
1942 mw->menu.new_depth = 0;
1943 mw->menu.new_stack = 0;
1944 mw->menu.new_stack_length = 0;
1945 push_new_stack (mw, mw->menu.contents);
1946
1947 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1948 mw->menu.windows_length = 1;
1949 mw->menu.windows [0].x = 0;
1950 mw->menu.windows [0].y = 0;
1951 mw->menu.windows [0].width = 0;
1952 mw->menu.windows [0].height = 0;
1953 mw->menu.windows [0].max_rest_width = 0;
1954 mw->menu.windows [0].pixmap = None;
1955 #ifdef HAVE_XFT
1956 mw->menu.windows [0].xft_draw = 0;
1957 #endif
1958 size_menu (mw, 0);
1959
1960 mw->core.width = mw->menu.windows [0].width;
1961 mw->core.height = mw->menu.windows [0].height;
1962 }
1963
1964 static void
1965 XlwMenuClassInitialize (void)
1966 {
1967 }
1968
1969 static void
1970 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1971 {
1972 XlwMenuWidget mw = (XlwMenuWidget)w;
1973 XSetWindowAttributes xswa;
1974 int mask;
1975
1976 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1977 (w, valueMask, attributes);
1978
1979 xswa.save_under = True;
1980 xswa.cursor = mw->menu.cursor_shape;
1981 mask = CWSaveUnder | CWCursor;
1982 /* I sometimes get random BadCursor errors while creating the first
1983 frame on a display. I can not find their reason, but they are
1984 annoying so for now let's ignore any errors here. -- lorentey */
1985 #ifdef emacs
1986 x_catch_errors (XtDisplay (w));
1987 #endif
1988 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1989 #ifdef emacs
1990 x_uncatch_errors ();
1991 #endif
1992
1993 mw->menu.windows [0].w = w;
1994 mw->menu.windows [0].window = XtWindow (w);
1995 mw->menu.windows [0].x = w->core.x;
1996 mw->menu.windows [0].y = w->core.y;
1997 mw->menu.windows [0].width = w->core.width;
1998 mw->menu.windows [0].height = w->core.height;
1999
2000 set_window_type (mw->menu.windows [0].w, mw);
2001 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2002
2003 #ifdef HAVE_XFT
2004 if (mw->menu.xft_font)
2005 {
2006 XColor colors[3];
2007 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
2008 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
2009 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
2010 = mw->menu.disabled_foreground;
2011 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
2012 mw->menu.xft_fg.color.alpha = 0xFFFF;
2013 mw->menu.xft_fg.color.red = colors[0].red;
2014 mw->menu.xft_fg.color.green = colors[0].green;
2015 mw->menu.xft_fg.color.blue = colors[0].blue;
2016 mw->menu.xft_bg.color.alpha = 0xFFFF;
2017 mw->menu.xft_bg.color.red = colors[1].red;
2018 mw->menu.xft_bg.color.green = colors[1].green;
2019 mw->menu.xft_bg.color.blue = colors[1].blue;
2020 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2021 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2022 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2023 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2024 }
2025 #endif
2026 }
2027
2028 /* Only the toplevel menubar/popup is a widget so it's the only one that
2029 receives expose events through Xt. So we repaint all the other panes
2030 when receiving an Expose event. */
2031 static void
2032 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2033 {
2034 XlwMenuWidget mw = (XlwMenuWidget)w;
2035
2036 /* If we have a depth beyond 1, it's because a submenu was displayed.
2037 If the submenu has been destroyed, set the depth back to 1. */
2038 if (submenu_destroyed)
2039 {
2040 mw->menu.old_depth = 1;
2041 submenu_destroyed = 0;
2042 }
2043
2044 display_menu (mw, 0, False, NULL, NULL, NULL);
2045 }
2046
2047
2048 /* Part of a hack to make the menu redisplay when a tooltip frame
2049 over a menu item is unmapped. */
2050
2051 void
2052 xlwmenu_redisplay (Widget w)
2053 {
2054 XlwMenuRedisplay (w, NULL, None);
2055 }
2056
2057 static void
2058 XlwMenuDestroy (Widget w)
2059 {
2060 int i;
2061 XlwMenuWidget mw = (XlwMenuWidget) w;
2062
2063 if (pointer_grabbed)
2064 ungrab_all ((Widget)w, CurrentTime);
2065 pointer_grabbed = 0;
2066
2067 submenu_destroyed = 1;
2068
2069 release_drawing_gcs (mw);
2070 release_shadow_gcs (mw);
2071
2072 /* this doesn't come from the resource db but is created explicitly
2073 so we must free it ourselves. */
2074 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2075 mw->menu.gray_pixmap = (Pixmap) -1;
2076
2077 #if 0
2078 /* Do free mw->menu.contents because nowadays we copy it
2079 during initialization. */
2080 XtFree (mw->menu.contents);
2081 #endif
2082
2083 /* Don't free mw->menu.contents because that comes from our creator.
2084 The `*_stack' elements are just pointers into `contents' so leave
2085 that alone too. But free the stacks themselves. */
2086 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2087 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2088
2089 /* Remember, you can't free anything that came from the resource
2090 database. This includes:
2091 mw->menu.cursor
2092 mw->menu.top_shadow_pixmap
2093 mw->menu.bottom_shadow_pixmap
2094 mw->menu.font
2095 Also the color cells of top_shadow_color, bottom_shadow_color,
2096 foreground, and button_foreground will never be freed until this
2097 client exits. Nice, eh?
2098 */
2099
2100 #ifdef HAVE_XFT
2101 if (mw->menu.windows [0].xft_draw)
2102 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2103 if (mw->menu.xft_font)
2104 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2105 #endif
2106
2107 if (mw->menu.windows [0].pixmap != None)
2108 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2109 /* start from 1 because the one in slot 0 is w->core.window */
2110 for (i = 1; i < mw->menu.windows_length; i++)
2111 {
2112 if (mw->menu.windows [i].pixmap != None)
2113 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2114 #ifdef HAVE_XFT
2115 if (mw->menu.windows [i].xft_draw)
2116 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2117 #endif
2118 }
2119
2120 if (mw->menu.windows)
2121 XtFree ((char *) mw->menu.windows);
2122 }
2123
2124 #ifdef HAVE_XFT
2125 static int
2126 fontname_changed (XlwMenuWidget newmw,
2127 XlwMenuWidget oldmw)
2128 {
2129 /* This will force a new XftFont even if the same string is set.
2130 This is good, as rendering parameters may have changed and
2131 we just want to do a redisplay. */
2132 return newmw->menu.fontName != oldmw->menu.fontName;
2133 }
2134 #endif
2135
2136 static Boolean
2137 XlwMenuSetValues (Widget current, Widget request, Widget new,
2138 ArgList args, Cardinal *num_args)
2139 {
2140 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2141 XlwMenuWidget newmw = (XlwMenuWidget)new;
2142 Boolean do_redisplay = False;
2143
2144 if (newmw->menu.contents
2145 && newmw->menu.contents->contents
2146 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2147 do_redisplay = True;
2148 /* Do redisplay if the contents are entirely eliminated. */
2149 if (newmw->menu.contents
2150 && newmw->menu.contents->contents == 0
2151 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2152 do_redisplay = True;
2153
2154 if (newmw->core.background_pixel != oldmw->core.background_pixel
2155 || newmw->menu.foreground != oldmw->menu.foreground
2156 #ifdef HAVE_XFT
2157 || fontname_changed (newmw, oldmw)
2158 #endif
2159 #ifdef HAVE_X_I18N
2160 || newmw->menu.fontSet != oldmw->menu.fontSet
2161 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2162 #else
2163 || newmw->menu.font != oldmw->menu.font
2164 #endif
2165 )
2166 {
2167 int i;
2168 release_drawing_gcs (newmw);
2169 make_drawing_gcs (newmw);
2170
2171 release_shadow_gcs (newmw);
2172 /* Cause the shadow colors to be recalculated. */
2173 newmw->menu.top_shadow_color = -1;
2174 newmw->menu.bottom_shadow_color = -1;
2175 make_shadow_gcs (newmw);
2176
2177 do_redisplay = True;
2178
2179 if (XtIsRealized (current))
2180 /* If the menu is currently displayed, change the display. */
2181 for (i = 0; i < oldmw->menu.windows_length; i++)
2182 {
2183 XSetWindowBackground (XtDisplay (oldmw),
2184 oldmw->menu.windows [i].window,
2185 newmw->core.background_pixel);
2186 /* clear windows and generate expose events */
2187 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2188 0, 0, 0, 0, True);
2189 }
2190 }
2191
2192 #ifdef HAVE_XFT
2193 if (fontname_changed (newmw, oldmw))
2194 {
2195 int i;
2196 int screen = XScreenNumberOfScreen (newmw->core.screen);
2197 if (newmw->menu.xft_font)
2198 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2199 openXftFont (newmw);
2200 for (i = 0; i < newmw->menu.windows_length; i++)
2201 {
2202 if (newmw->menu.windows [i].xft_draw)
2203 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2204 newmw->menu.windows [i].xft_draw = 0;
2205 }
2206 if (newmw->menu.xft_font)
2207 for (i = 0; i < newmw->menu.windows_length; i++)
2208 newmw->menu.windows [i].xft_draw
2209 = XftDrawCreate (XtDisplay (newmw),
2210 newmw->menu.windows [i].window,
2211 DefaultVisual (XtDisplay (newmw), screen),
2212 newmw->core.colormap);
2213 }
2214 #endif
2215 #ifdef HAVE_X_I18N
2216 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2217 {
2218 do_redisplay = True;
2219 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2220 }
2221 #endif
2222
2223 return do_redisplay;
2224 }
2225
2226 static void
2227 XlwMenuResize (Widget w)
2228 {
2229 XlwMenuWidget mw = (XlwMenuWidget)w;
2230
2231 if (mw->menu.popped_up)
2232 {
2233 /* Don't allow the popup menu to resize itself. */
2234 mw->core.width = mw->menu.windows [0].width;
2235 mw->core.height = mw->menu.windows [0].height;
2236 mw->core.parent->core.width = mw->core.width;
2237 mw->core.parent->core.height = mw->core.height;
2238 }
2239 else
2240 {
2241 mw->menu.windows [0].width = mw->core.width;
2242 mw->menu.windows [0].height = mw->core.height;
2243 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2244 }
2245 }
2246
2247 \f/* Action procedures */
2248 static void
2249 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2250 {
2251 widget_value* val;
2252 int level;
2253
2254 if (!map_event_to_widget_value (mw, ev, &val, &level))
2255 pop_new_stack_if_no_contents (mw);
2256 else
2257 set_new_state (mw, val, level);
2258 remap_menubar (mw);
2259
2260 /* Sync with the display. Makes it feel better on X terms. */
2261 XSync (XtDisplay (mw), False);
2262 }
2263
2264 static void
2265 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2266 {
2267 int x = ev->x_root;
2268 int y = ev->y_root;
2269 int state = ev->state;
2270 XMotionEvent oldev = *ev;
2271
2272 /* allow motion events to be generated again */
2273 if (ev->is_hint
2274 && XQueryPointer (XtDisplay (mw), ev->window,
2275 &ev->root, &ev->subwindow,
2276 &ev->x_root, &ev->y_root,
2277 &ev->x, &ev->y,
2278 &ev->state)
2279 && ev->state == state
2280 && (ev->x_root != x || ev->y_root != y))
2281 handle_single_motion_event (mw, ev);
2282 else
2283 handle_single_motion_event (mw, &oldev);
2284 }
2285
2286 static void
2287 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2288 {
2289 XlwMenuWidget mw = (XlwMenuWidget)w;
2290
2291 if (!mw->menu.popped_up)
2292 {
2293 menu_post_event = *ev;
2294 /* If event is set to CurrentTime, get the last known time stamp.
2295 This is for calculating if (popup) menus should stay up after
2296 a fast click. */
2297 if (menu_post_event.xbutton.time == CurrentTime)
2298 menu_post_event.xbutton.time
2299 = XtLastTimestampProcessed (XtDisplay (w));
2300
2301 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2302 }
2303 else
2304 {
2305 /* If we push a button while the menu is posted semipermanently,
2306 releasing the button should always pop the menu down. */
2307 next_release_must_exit = 1;
2308
2309 /* notes the absolute position of the menubar window */
2310 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2311 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2312
2313 /* handles the down like a move, slots are compatible */
2314 ev->xmotion.is_hint = 0;
2315 handle_motion_event (mw, &ev->xmotion);
2316 }
2317 }
2318
2319 static void
2320 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2321 {
2322 XlwMenuWidget mw = (XlwMenuWidget)w;
2323 if (mw->menu.popped_up)
2324 handle_motion_event (mw, &ev->xmotion);
2325 }
2326
2327 /* Do nothing.
2328 This is how we handle presses and releases of modifier keys. */
2329 static void
2330 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2331 {
2332 }
2333
2334 static widget_value *
2335 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2336 {
2337 widget_value *current = item;
2338 enum menu_separator separator;
2339
2340 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2341 || (skip_titles && !current->call_data && !current->contents))
2342 if (current->next)
2343 current=current->next;
2344 else
2345 return NULL;
2346
2347 return current;
2348 }
2349
2350 static widget_value *
2351 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2352 {
2353 widget_value *current = item;
2354 enum menu_separator separator;
2355
2356 while (current->next && (current=current->next) &&
2357 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2358 || (skip_titles && !current->call_data && !current->contents)))
2359 ;
2360
2361 if (current == item)
2362 {
2363 if (mw->menu.old_depth < 2)
2364 return current;
2365 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2366
2367 while (lw_separator_p (current->name, &separator, 0)
2368 || !current->enabled
2369 || (skip_titles && !current->call_data
2370 && !current->contents))
2371 {
2372 if (current->next)
2373 current=current->next;
2374
2375 if (current == item)
2376 break;
2377 }
2378
2379 }
2380
2381 return current;
2382 }
2383
2384 static widget_value *
2385 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2386 {
2387 widget_value *current = item;
2388 widget_value *prev = item;
2389
2390 while ((current=find_next_selectable (mw, current, skip_titles))
2391 != item)
2392 {
2393 if (prev == current)
2394 break;
2395 prev=current;
2396 }
2397
2398 return prev;
2399 }
2400
2401 static void
2402 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2403 {
2404 XlwMenuWidget mw = (XlwMenuWidget) w;
2405 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2406 int popup_menu_p = mw->menu.top_depth == 1;
2407
2408 /* Inside top-level menu-bar? */
2409 if (mw->menu.old_depth == mw->menu.top_depth)
2410 /* When <down> in the menu-bar is pressed, display the corresponding
2411 sub-menu and select the first selectable menu item there.
2412 If this is a popup menu, skip title item of the popup. */
2413 set_new_state (mw,
2414 find_first_selectable (mw,
2415 selected_item->contents,
2416 popup_menu_p),
2417 mw->menu.old_depth);
2418 else
2419 /* Highlight next possible (enabled and not separator) menu item. */
2420 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2421 mw->menu.old_depth - 1);
2422
2423 remap_menubar (mw);
2424 }
2425
2426 static void
2427 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2428 {
2429 XlwMenuWidget mw = (XlwMenuWidget) w;
2430 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2431 int popup_menu_p = mw->menu.top_depth == 1;
2432
2433 /* Inside top-level menu-bar? */
2434 if (mw->menu.old_depth == mw->menu.top_depth)
2435 {
2436 /* FIXME: this is tricky. <up> in the menu-bar should select the
2437 last selectable item in the list. So we select the first
2438 selectable one and find the previous selectable item. Is there
2439 a better way? */
2440 /* If this is a popup menu, skip title item of the popup. */
2441 set_new_state (mw,
2442 find_first_selectable (mw,
2443 selected_item->contents,
2444 popup_menu_p),
2445 mw->menu.old_depth);
2446 remap_menubar (mw);
2447 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2448 set_new_state (mw,
2449 find_prev_selectable (mw,
2450 selected_item,
2451 popup_menu_p),
2452 mw->menu.old_depth - 1);
2453 }
2454 else
2455 /* Highlight previous (enabled and not separator) menu item. */
2456 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2457 mw->menu.old_depth - 1);
2458
2459 remap_menubar (mw);
2460 }
2461
2462 void
2463 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2464 {
2465 XlwMenuWidget mw = (XlwMenuWidget) w;
2466 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2467
2468 /* Inside top-level menu-bar? */
2469 if (mw->menu.old_depth == mw->menu.top_depth)
2470 /* When <left> in the menu-bar is pressed, display the previous item on
2471 the menu-bar. If the current item is the first one, highlight the
2472 last item in the menubar (probably Help). */
2473 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2474 mw->menu.old_depth - 1);
2475 else if (mw->menu.old_depth == 1
2476 && selected_item->contents) /* Is this menu item expandable? */
2477 {
2478 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2479 remap_menubar (mw);
2480 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2481 if (!selected_item->enabled && find_first_selectable (mw,
2482 selected_item,
2483 0))
2484 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2485 mw->menu.old_depth - 1);
2486 }
2487
2488 else
2489 {
2490 pop_new_stack_if_no_contents (mw);
2491 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2492 mw->menu.old_depth - 2);
2493 }
2494
2495 remap_menubar (mw);
2496 }
2497
2498 void
2499 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2500 {
2501 XlwMenuWidget mw = (XlwMenuWidget) w;
2502 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2503
2504 /* Inside top-level menu-bar? */
2505 if (mw->menu.old_depth == mw->menu.top_depth)
2506 /* When <right> in the menu-bar is pressed, display the next item on
2507 the menu-bar. If the current item is the last one, highlight the
2508 first item (probably File). */
2509 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2510 mw->menu.old_depth - 1);
2511 else if (selected_item->contents) /* Is this menu item expandable? */
2512 {
2513 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2514 remap_menubar (mw);
2515 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2516 if (!selected_item->enabled && find_first_selectable (mw,
2517 selected_item,
2518 0))
2519 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2520 mw->menu.old_depth - 1);
2521 }
2522 else
2523 {
2524 pop_new_stack_if_no_contents (mw);
2525 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2526 mw->menu.old_depth - 2);
2527 }
2528
2529 remap_menubar (mw);
2530 }
2531
2532 /* Handle key press and release events while menu is popped up.
2533 Our action is to get rid of the menu. */
2534 static void
2535 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2536 {
2537 XlwMenuWidget mw = (XlwMenuWidget)w;
2538
2539 /* Pop down everything. */
2540 mw->menu.new_depth = 1;
2541 remap_menubar (mw);
2542
2543 if (mw->menu.popped_up)
2544 {
2545 mw->menu.popped_up = False;
2546 ungrab_all ((Widget)mw, ev->xmotion.time);
2547 if (XtIsShell (XtParent ((Widget) mw)))
2548 XtPopdown (XtParent ((Widget) mw));
2549 else
2550 {
2551 XtRemoveGrab ((Widget) mw);
2552 display_menu (mw, 0, False, NULL, NULL, NULL);
2553 }
2554 }
2555
2556 /* callback */
2557 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2558 }
2559
2560 static void
2561 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2562 {
2563 XlwMenuWidget mw = (XlwMenuWidget)w;
2564 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2565
2566 /* If user releases the button quickly, without selecting anything,
2567 after the initial down-click that brought the menu up,
2568 do nothing. */
2569 if ((selected_item == 0
2570 || ((widget_value *) selected_item)->call_data == 0)
2571 && !next_release_must_exit
2572 && (ev->xbutton.time - menu_post_event.xbutton.time
2573 < XtGetMultiClickTime (XtDisplay (w))))
2574 return;
2575
2576 /* pop down everything. */
2577 mw->menu.new_depth = 1;
2578 remap_menubar (mw);
2579
2580 if (mw->menu.popped_up)
2581 {
2582 mw->menu.popped_up = False;
2583 ungrab_all ((Widget)mw, ev->xmotion.time);
2584 if (XtIsShell (XtParent ((Widget) mw)))
2585 XtPopdown (XtParent ((Widget) mw));
2586 else
2587 {
2588 XtRemoveGrab ((Widget) mw);
2589 display_menu (mw, 0, False, NULL, NULL, NULL);
2590 }
2591 }
2592
2593 /* callback */
2594 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2595 }
2596
2597
2598 \f/* Special code to pop-up a menu */
2599 static void
2600 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2601 {
2602 int x = event->x_root;
2603 int y = event->y_root;
2604 int w;
2605 int h;
2606 int borderwidth = mw->menu.shadow_thickness;
2607 Screen* screen = XtScreen (mw);
2608 Display *display = XtDisplay (mw);
2609
2610 next_release_must_exit = 0;
2611
2612 mw->menu.inside_entry = NULL;
2613 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2614
2615 if (XtIsShell (XtParent ((Widget)mw)))
2616 size_menu (mw, 0);
2617
2618 w = mw->menu.windows [0].width;
2619 h = mw->menu.windows [0].height;
2620
2621 x -= borderwidth;
2622 y -= borderwidth;
2623 if (x < borderwidth)
2624 x = borderwidth;
2625 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2626 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2627 if (y < borderwidth)
2628 y = borderwidth;
2629 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2630 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2631
2632 mw->menu.popped_up = True;
2633 if (XtIsShell (XtParent ((Widget)mw)))
2634 {
2635 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2636 XtParent ((Widget)mw)->core.border_width);
2637 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2638 display_menu (mw, 0, False, NULL, NULL, NULL);
2639 mw->menu.windows [0].x = x + borderwidth;
2640 mw->menu.windows [0].y = y + borderwidth;
2641 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2642 }
2643 else
2644 {
2645 XEvent *ev = (XEvent *) event;
2646
2647 XtAddGrab ((Widget) mw, True, True);
2648
2649 /* notes the absolute position of the menubar window */
2650 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2651 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2652 mw->menu.top_depth = 2;
2653 }
2654
2655 #ifdef emacs
2656 x_catch_errors (display);
2657 #endif
2658 if (XtGrabPointer ((Widget)mw, False,
2659 (PointerMotionMask
2660 | PointerMotionHintMask
2661 | ButtonReleaseMask
2662 | ButtonPressMask),
2663 GrabModeAsync, GrabModeAsync, None,
2664 mw->menu.cursor_shape,
2665 event->time) == Success)
2666 {
2667 if (! GRAB_KEYBOARD
2668 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2669 GrabModeAsync, event->time) == Success)
2670 {
2671 XtSetKeyboardFocus((Widget)mw, None);
2672 pointer_grabbed = 1;
2673 }
2674 else
2675 XtUngrabPointer ((Widget)mw, event->time);
2676 }
2677
2678 #ifdef emacs
2679 if (x_had_errors_p (display))
2680 {
2681 pointer_grabbed = 0;
2682 XtUngrabPointer ((Widget)mw, event->time);
2683 }
2684 x_uncatch_errors ();
2685 #endif
2686
2687 ((XMotionEvent*)event)->is_hint = 0;
2688 handle_motion_event (mw, (XMotionEvent*)event);
2689 }