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