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