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