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