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