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