Add arch taglines
[bpt/emacs.git] / 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
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
1711 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1712 Display* display = XtDisplay (mw);
1713
1714 #if 0
1715 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1716
1717 /* _XtCreate is freeing the object that was passed to us,
1718 so make a copy that we will actually keep. */
1719 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1720 mw->menu.contents = tem;
1721 #endif
1722
1723 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1724 mw->menu.cursor = mw->menu.cursor_shape;
1725
1726 mw->menu.gray_pixmap
1727 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1728 gray_bitmap_width, gray_bitmap_height,
1729 (unsigned long)1, (unsigned long)0, 1);
1730
1731 /* I don't understand why this ends up 0 sometimes,
1732 but it does. This kludge works around it.
1733 Can anyone find a real fix? -- rms. */
1734 if (mw->menu.font == 0)
1735 mw->menu.font = xlwmenu_default_font;
1736
1737 make_drawing_gcs (mw);
1738 make_shadow_gcs (mw);
1739
1740 mw->menu.popped_up = False;
1741
1742 mw->menu.old_depth = 1;
1743 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1744 mw->menu.old_stack_length = 1;
1745 mw->menu.old_stack [0] = mw->menu.contents;
1746
1747 mw->menu.new_depth = 0;
1748 mw->menu.new_stack = 0;
1749 mw->menu.new_stack_length = 0;
1750 push_new_stack (mw, mw->menu.contents);
1751
1752 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1753 mw->menu.windows_length = 1;
1754 mw->menu.windows [0].x = 0;
1755 mw->menu.windows [0].y = 0;
1756 mw->menu.windows [0].width = 0;
1757 mw->menu.windows [0].height = 0;
1758 size_menu (mw, 0);
1759
1760 mw->core.width = mw->menu.windows [0].width;
1761 mw->core.height = mw->menu.windows [0].height;
1762 }
1763
1764 static void
1765 XlwMenuClassInitialize ()
1766 {
1767 }
1768
1769 static void
1770 XlwMenuRealize (w, valueMask, attributes)
1771 Widget w;
1772 Mask *valueMask;
1773 XSetWindowAttributes *attributes;
1774 {
1775 XlwMenuWidget mw = (XlwMenuWidget)w;
1776 XSetWindowAttributes xswa;
1777 int mask;
1778
1779 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1780 (w, valueMask, attributes);
1781
1782 xswa.save_under = True;
1783 xswa.cursor = mw->menu.cursor_shape;
1784 mask = CWSaveUnder | CWCursor;
1785 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1786
1787 mw->menu.windows [0].window = XtWindow (w);
1788 mw->menu.windows [0].x = w->core.x;
1789 mw->menu.windows [0].y = w->core.y;
1790 mw->menu.windows [0].width = w->core.width;
1791 mw->menu.windows [0].height = w->core.height;
1792 }
1793
1794 /* Only the toplevel menubar/popup is a widget so it's the only one that
1795 receives expose events through Xt. So we repaint all the other panes
1796 when receiving an Expose event. */
1797 static void
1798 XlwMenuRedisplay (w, ev, region)
1799 Widget w;
1800 XEvent* ev;
1801 Region region;
1802 {
1803 XlwMenuWidget mw = (XlwMenuWidget)w;
1804 int i;
1805
1806 /* If we have a depth beyond 1, it's because a submenu was displayed.
1807 If the submenu has been destroyed, set the depth back to 1. */
1808 if (submenu_destroyed)
1809 {
1810 mw->menu.old_depth = 1;
1811 submenu_destroyed = 0;
1812 }
1813
1814 for (i = 0; i < mw->menu.old_depth; i++)
1815 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1816 }
1817
1818
1819 /* Part of a hack to make the menu redisplay when a tooltip frame
1820 over a menu item is unmapped. */
1821
1822 void
1823 xlwmenu_redisplay (w)
1824 Widget w;
1825 {
1826 XlwMenuRedisplay (w, NULL, None);
1827 }
1828
1829 static void
1830 XlwMenuDestroy (w)
1831 Widget w;
1832 {
1833 int i;
1834 XlwMenuWidget mw = (XlwMenuWidget) w;
1835
1836 if (pointer_grabbed)
1837 ungrab_all ((Widget)w, CurrentTime);
1838 pointer_grabbed = 0;
1839
1840 submenu_destroyed = 1;
1841
1842 release_drawing_gcs (mw);
1843 release_shadow_gcs (mw);
1844
1845 /* this doesn't come from the resource db but is created explicitly
1846 so we must free it ourselves. */
1847 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1848 mw->menu.gray_pixmap = (Pixmap) -1;
1849
1850 #if 0
1851 /* Do free mw->menu.contents because nowadays we copy it
1852 during initialization. */
1853 XtFree (mw->menu.contents);
1854 #endif
1855
1856 /* Don't free mw->menu.contents because that comes from our creator.
1857 The `*_stack' elements are just pointers into `contents' so leave
1858 that alone too. But free the stacks themselves. */
1859 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1860 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1861
1862 /* Remember, you can't free anything that came from the resource
1863 database. This includes:
1864 mw->menu.cursor
1865 mw->menu.top_shadow_pixmap
1866 mw->menu.bottom_shadow_pixmap
1867 mw->menu.font
1868 Also the color cells of top_shadow_color, bottom_shadow_color,
1869 foreground, and button_foreground will never be freed until this
1870 client exits. Nice, eh?
1871 */
1872
1873 /* start from 1 because the one in slot 0 is w->core.window */
1874 for (i = 1; i < mw->menu.windows_length; i++)
1875 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1876 if (mw->menu.windows)
1877 XtFree ((char *) mw->menu.windows);
1878 }
1879
1880 static Boolean
1881 XlwMenuSetValues (current, request, new)
1882 Widget current;
1883 Widget request;
1884 Widget new;
1885 {
1886 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1887 XlwMenuWidget newmw = (XlwMenuWidget)new;
1888 Boolean redisplay = False;
1889 int i;
1890
1891 if (newmw->menu.contents
1892 && newmw->menu.contents->contents
1893 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1894 redisplay = True;
1895 /* Do redisplay if the contents are entirely eliminated. */
1896 if (newmw->menu.contents
1897 && newmw->menu.contents->contents == 0
1898 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1899 redisplay = True;
1900
1901 if (newmw->core.background_pixel != oldmw->core.background_pixel
1902 || newmw->menu.foreground != oldmw->menu.foreground
1903 || newmw->menu.font != oldmw->menu.font)
1904 {
1905 release_drawing_gcs (newmw);
1906 make_drawing_gcs (newmw);
1907
1908 release_shadow_gcs (newmw);
1909 /* Cause the shadow colors to be recalculated. */
1910 newmw->menu.top_shadow_color = -1;
1911 newmw->menu.bottom_shadow_color = -1;
1912 make_shadow_gcs (newmw);
1913
1914 redisplay = True;
1915
1916 if (XtIsRealized (current))
1917 /* If the menu is currently displayed, change the display. */
1918 for (i = 0; i < oldmw->menu.windows_length; i++)
1919 {
1920 XSetWindowBackground (XtDisplay (oldmw),
1921 oldmw->menu.windows [i].window,
1922 newmw->core.background_pixel);
1923 /* clear windows and generate expose events */
1924 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1925 0, 0, 0, 0, True);
1926 }
1927 }
1928
1929 return redisplay;
1930 }
1931
1932 static void
1933 XlwMenuResize (w)
1934 Widget w;
1935 {
1936 XlwMenuWidget mw = (XlwMenuWidget)w;
1937
1938 if (mw->menu.popped_up)
1939 {
1940 /* Don't allow the popup menu to resize itself. */
1941 mw->core.width = mw->menu.windows [0].width;
1942 mw->core.height = mw->menu.windows [0].height;
1943 mw->core.parent->core.width = mw->core.width ;
1944 mw->core.parent->core.height = mw->core.height ;
1945 }
1946 else
1947 {
1948 mw->menu.windows [0].width = mw->core.width;
1949 mw->menu.windows [0].height = mw->core.height;
1950 }
1951 }
1952
1953 \f/* Action procedures */
1954 static void
1955 handle_single_motion_event (mw, ev)
1956 XlwMenuWidget mw;
1957 XMotionEvent* ev;
1958 {
1959 widget_value* val;
1960 int level;
1961
1962 if (!map_event_to_widget_value (mw, ev, &val, &level))
1963 pop_new_stack_if_no_contents (mw);
1964 else
1965 set_new_state (mw, val, level);
1966 remap_menubar (mw);
1967
1968 /* Sync with the display. Makes it feel better on X terms. */
1969 XSync (XtDisplay (mw), False);
1970 }
1971
1972 static void
1973 handle_motion_event (mw, ev)
1974 XlwMenuWidget mw;
1975 XMotionEvent* ev;
1976 {
1977 int x = ev->x_root;
1978 int y = ev->y_root;
1979 int state = ev->state;
1980
1981 handle_single_motion_event (mw, ev);
1982
1983 /* allow motion events to be generated again */
1984 if (ev->is_hint
1985 && XQueryPointer (XtDisplay (mw), ev->window,
1986 &ev->root, &ev->subwindow,
1987 &ev->x_root, &ev->y_root,
1988 &ev->x, &ev->y,
1989 &ev->state)
1990 && ev->state == state
1991 && (ev->x_root != x || ev->y_root != y))
1992 handle_single_motion_event (mw, ev);
1993 }
1994
1995 static void
1996 Start (w, ev, params, num_params)
1997 Widget w;
1998 XEvent *ev;
1999 String *params;
2000 Cardinal *num_params;
2001 {
2002 XlwMenuWidget mw = (XlwMenuWidget)w;
2003
2004 if (!mw->menu.popped_up)
2005 {
2006 menu_post_event = *ev;
2007 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2008 }
2009 else
2010 {
2011 /* If we push a button while the menu is posted semipermanently,
2012 releasing the button should always pop the menu down. */
2013 next_release_must_exit = 1;
2014
2015 /* notes the absolute position of the menubar window */
2016 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2017 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2018
2019 /* handles the down like a move, slots are compatible */
2020 handle_motion_event (mw, &ev->xmotion);
2021 }
2022 }
2023
2024 static void
2025 Drag (w, ev, params, num_params)
2026 Widget w;
2027 XEvent *ev;
2028 String *params;
2029 Cardinal *num_params;
2030 {
2031 XlwMenuWidget mw = (XlwMenuWidget)w;
2032 if (mw->menu.popped_up)
2033 handle_motion_event (mw, &ev->xmotion);
2034 }
2035
2036 /* Do nothing.
2037 This is how we handle presses and releases of modifier keys. */
2038 static void
2039 Nothing (w, ev, params, num_params)
2040 Widget w;
2041 XEvent *ev;
2042 String *params;
2043 Cardinal *num_params;
2044 {
2045 }
2046
2047 widget_value *
2048 find_first_selectable (mw, item)
2049 XlwMenuWidget mw;
2050 widget_value *item;
2051 {
2052 widget_value *current = item;
2053 enum menu_separator separator;
2054
2055 while (lw_separator_p (current->name, &separator, 0) || !current->enabled)
2056 if (current->next)
2057 current=current->next;
2058 else
2059 return NULL;
2060
2061 return current;
2062 }
2063
2064 widget_value *
2065 find_next_selectable (mw, item)
2066 XlwMenuWidget mw;
2067 widget_value *item;
2068 {
2069 widget_value *current = item;
2070 enum menu_separator separator;
2071
2072 while (current->next && (current=current->next) &&
2073 (lw_separator_p (current->name, &separator, 0) || !current->enabled))
2074 ;
2075
2076 if (current == item)
2077 {
2078 if (mw->menu.old_depth < 2)
2079 return current;
2080 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2081
2082 while (lw_separator_p (current->name, &separator, 0) || !current->enabled)
2083 {
2084 if (current->next)
2085 current=current->next;
2086
2087 if (current == item)
2088 break;
2089 }
2090
2091 }
2092
2093 return current;
2094 }
2095
2096 widget_value *
2097 find_prev_selectable (mw, item)
2098 XlwMenuWidget mw;
2099 widget_value *item;
2100 {
2101 widget_value *current = item;
2102 widget_value *prev = item;
2103
2104 while ((current=find_next_selectable (mw, current)) != item)
2105 {
2106 if (prev == current)
2107 break;
2108 prev=current;
2109 }
2110
2111 return prev;
2112 }
2113
2114 static void
2115 Down (w, ev, params, num_params)
2116 Widget w;
2117 XEvent *ev;
2118 String *params;
2119 Cardinal *num_params;
2120 {
2121 XlwMenuWidget mw = (XlwMenuWidget) w;
2122 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2123
2124 /* Inside top-level menu-bar? */
2125 if (mw->menu.old_depth == 2)
2126 /* When <down> in the menu-bar is pressed, display the corresponding
2127 sub-menu and select the first selectable menu item there. */
2128 set_new_state (mw, find_first_selectable (mw, selected_item->contents), mw->menu.old_depth);
2129 else
2130 /* Highlight next possible (enabled and not separator) menu item. */
2131 set_new_state (mw, find_next_selectable (mw, selected_item), mw->menu.old_depth - 1);
2132
2133 remap_menubar (mw);
2134 }
2135
2136 static void
2137 Up (w, ev, params, num_params)
2138 Widget w;
2139 XEvent *ev;
2140 String *params;
2141 Cardinal *num_params;
2142 {
2143 XlwMenuWidget mw = (XlwMenuWidget) w;
2144 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2145
2146 /* Inside top-level menu-bar? */
2147 if (mw->menu.old_depth == 2)
2148 {
2149 /* FIXME: this is tricky. <up> in the menu-bar should select the
2150 last selectable item in the list. So we select the first
2151 selectable one and find the previous selectable item. Is there
2152 a better way? */
2153 set_new_state (mw, find_first_selectable (mw, selected_item->contents), mw->menu.old_depth);
2154 remap_menubar (mw);
2155 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2156 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2157 }
2158 else
2159 /* Highlight previous (enabled and not separator) menu item. */
2160 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2161
2162 remap_menubar (mw);
2163 }
2164
2165 static void
2166 Left (w, ev, params, num_params)
2167 Widget w;
2168 XEvent *ev;
2169 String *params;
2170 Cardinal *num_params;
2171 {
2172 XlwMenuWidget mw = (XlwMenuWidget) w;
2173 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2174
2175 /* Inside top-level menu-bar? */
2176 if (mw->menu.old_depth == 2)
2177 /* When <left> in the menu-bar is pressed, display the previous item on
2178 the menu-bar. If the current item is the first one, highlight the
2179 last item in the menubar (probably Help). */
2180 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2181 else if (mw->menu.old_depth == 1
2182 && selected_item->contents) /* Is this menu item expandable? */
2183 {
2184 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2185 remap_menubar (mw);
2186 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2187 if (!selected_item->enabled && find_first_selectable (mw, selected_item))
2188 set_new_state (mw, find_first_selectable (mw, selected_item), mw->menu.old_depth - 1);
2189 }
2190
2191 else
2192 {
2193 pop_new_stack_if_no_contents (mw);
2194 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2], mw->menu.old_depth - 2);
2195 }
2196
2197 remap_menubar (mw);
2198 }
2199
2200 static void
2201 Right (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 == 2)
2212 /* When <right> in the menu-bar is pressed, display the next item on
2213 the menu-bar. If the current item is the last one, highlight the
2214 first item (probably File). */
2215 set_new_state (mw, find_next_selectable (mw, selected_item), mw->menu.old_depth - 1);
2216 else if (selected_item->contents) /* Is this menu item expandable? */
2217 {
2218 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2219 remap_menubar (mw);
2220 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2221 if (!selected_item->enabled && find_first_selectable (mw, selected_item))
2222 set_new_state (mw, find_first_selectable (mw, selected_item), mw->menu.old_depth - 1);
2223 }
2224 else
2225 {
2226 pop_new_stack_if_no_contents (mw);
2227 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2], mw->menu.old_depth - 2);
2228 }
2229
2230 remap_menubar (mw);
2231 }
2232
2233 /* Handle key press and release events while menu is popped up.
2234 Our action is to get rid of the menu. */
2235 static void
2236 Key (w, ev, params, num_params)
2237 Widget w;
2238 XEvent *ev;
2239 String *params;
2240 Cardinal *num_params;
2241 {
2242 XlwMenuWidget mw = (XlwMenuWidget)w;
2243
2244 /* Pop down everything. */
2245 mw->menu.new_depth = 1;
2246 remap_menubar (mw);
2247
2248 if (mw->menu.popped_up)
2249 {
2250 mw->menu.popped_up = False;
2251 ungrab_all ((Widget)mw, ev->xmotion.time);
2252 if (XtIsShell (XtParent ((Widget) mw)))
2253 XtPopdown (XtParent ((Widget) mw));
2254 else
2255 {
2256 XtRemoveGrab ((Widget) mw);
2257 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2258 }
2259 }
2260
2261 /* callback */
2262 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2263 }
2264
2265 static void
2266 Select (w, ev, params, num_params)
2267 Widget w;
2268 XEvent *ev;
2269 String *params;
2270 Cardinal *num_params;
2271 {
2272 XlwMenuWidget mw = (XlwMenuWidget)w;
2273 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2274
2275 /* If user releases the button quickly, without selecting anything,
2276 after the initial down-click that brought the menu up,
2277 do nothing. */
2278 if ((selected_item == 0
2279 || ((widget_value *) selected_item)->call_data == 0)
2280 && !next_release_must_exit
2281 && (ev->xbutton.time - menu_post_event.xbutton.time
2282 < XtGetMultiClickTime (XtDisplay (w))))
2283 return;
2284
2285 /* pop down everything. */
2286 mw->menu.new_depth = 1;
2287 remap_menubar (mw);
2288
2289 if (mw->menu.popped_up)
2290 {
2291 mw->menu.popped_up = False;
2292 ungrab_all ((Widget)mw, ev->xmotion.time);
2293 if (XtIsShell (XtParent ((Widget) mw)))
2294 XtPopdown (XtParent ((Widget) mw));
2295 else
2296 {
2297 XtRemoveGrab ((Widget) mw);
2298 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2299 }
2300 }
2301
2302 /* callback */
2303 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2304 }
2305
2306
2307 \f/* Special code to pop-up a menu */
2308 void
2309 pop_up_menu (mw, event)
2310 XlwMenuWidget mw;
2311 XButtonPressedEvent* event;
2312 {
2313 int x = event->x_root;
2314 int y = event->y_root;
2315 int w;
2316 int h;
2317 int borderwidth = mw->menu.shadow_thickness;
2318 Screen* screen = XtScreen (mw);
2319 Display *display = XtDisplay (mw);
2320 int count;
2321
2322 next_release_must_exit = 0;
2323
2324 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2325
2326 if (XtIsShell (XtParent ((Widget)mw)))
2327 size_menu (mw, 0);
2328
2329 w = mw->menu.windows [0].width;
2330 h = mw->menu.windows [0].height;
2331
2332 x -= borderwidth;
2333 y -= borderwidth;
2334 if (x < borderwidth)
2335 x = borderwidth;
2336 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2337 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2338 if (y < borderwidth)
2339 y = borderwidth;
2340 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2341 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2342
2343 mw->menu.popped_up = True;
2344 if (XtIsShell (XtParent ((Widget)mw)))
2345 {
2346 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2347 XtParent ((Widget)mw)->core.border_width);
2348 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2349 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2350 mw->menu.windows [0].x = x + borderwidth;
2351 mw->menu.windows [0].y = y + borderwidth;
2352 }
2353 else
2354 {
2355 XEvent *ev = (XEvent *) event;
2356
2357 XtAddGrab ((Widget) mw, True, True);
2358
2359 /* notes the absolute position of the menubar window */
2360 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2361 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2362 }
2363
2364 #ifdef emacs
2365 count = x_catch_errors (display);
2366 #endif
2367 if (XtGrabPointer ((Widget)mw, False,
2368 (PointerMotionMask
2369 | PointerMotionHintMask
2370 | ButtonReleaseMask
2371 | ButtonPressMask),
2372 GrabModeAsync, GrabModeAsync, None,
2373 mw->menu.cursor_shape,
2374 event->time) == Success)
2375 {
2376 if (! GRAB_KEYBOARD
2377 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2378 GrabModeAsync, event->time) == Success)
2379 {
2380 XtSetKeyboardFocus((Widget)mw, None);
2381 pointer_grabbed = 1;
2382 }
2383 else
2384 XtUngrabPointer ((Widget)mw, event->time);
2385 }
2386
2387 #ifdef emacs
2388 if (x_had_errors_p (display))
2389 {
2390 pointer_grabbed = 0;
2391 XtUngrabPointer ((Widget)mw, event->time);
2392 }
2393 x_uncatch_errors (display, count);
2394 #endif
2395
2396 handle_motion_event (mw, (XMotionEvent*)event);
2397 }
2398
2399 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2400 (do not change this comment) */