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