Follow Glenn's lead and update format of Copyright.
[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, 2009 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 /* I sometimes get random BadCursor errors while creating the first
1848 frame on a display. I can not find their reason, but they are
1849 annoying so for now let's ignore any errors here. -- lorentey */
1850 #ifdef emacs
1851 x_catch_errors (XtDisplay (w));
1852 #endif
1853 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1854 #ifdef emacs
1855 x_uncatch_errors ();
1856 #endif
1857
1858 mw->menu.windows [0].window = XtWindow (w);
1859 mw->menu.windows [0].x = w->core.x;
1860 mw->menu.windows [0].y = w->core.y;
1861 mw->menu.windows [0].width = w->core.width;
1862 mw->menu.windows [0].height = w->core.height;
1863 }
1864
1865 /* Only the toplevel menubar/popup is a widget so it's the only one that
1866 receives expose events through Xt. So we repaint all the other panes
1867 when receiving an Expose event. */
1868 static void
1869 XlwMenuRedisplay (w, ev, region)
1870 Widget w;
1871 XEvent* ev;
1872 Region region;
1873 {
1874 XlwMenuWidget mw = (XlwMenuWidget)w;
1875 int i;
1876
1877 /* If we have a depth beyond 1, it's because a submenu was displayed.
1878 If the submenu has been destroyed, set the depth back to 1. */
1879 if (submenu_destroyed)
1880 {
1881 mw->menu.old_depth = 1;
1882 submenu_destroyed = 0;
1883 }
1884
1885 for (i = 0; i < mw->menu.old_depth; i++)
1886 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1887 }
1888
1889
1890 /* Part of a hack to make the menu redisplay when a tooltip frame
1891 over a menu item is unmapped. */
1892
1893 void
1894 xlwmenu_redisplay (w)
1895 Widget w;
1896 {
1897 XlwMenuRedisplay (w, NULL, None);
1898 }
1899
1900 static void
1901 XlwMenuDestroy (w)
1902 Widget w;
1903 {
1904 int i;
1905 XlwMenuWidget mw = (XlwMenuWidget) w;
1906
1907 if (pointer_grabbed)
1908 ungrab_all ((Widget)w, CurrentTime);
1909 pointer_grabbed = 0;
1910
1911 submenu_destroyed = 1;
1912
1913 release_drawing_gcs (mw);
1914 release_shadow_gcs (mw);
1915
1916 /* this doesn't come from the resource db but is created explicitly
1917 so we must free it ourselves. */
1918 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1919 mw->menu.gray_pixmap = (Pixmap) -1;
1920
1921 #if 0
1922 /* Do free mw->menu.contents because nowadays we copy it
1923 during initialization. */
1924 XtFree (mw->menu.contents);
1925 #endif
1926
1927 /* Don't free mw->menu.contents because that comes from our creator.
1928 The `*_stack' elements are just pointers into `contents' so leave
1929 that alone too. But free the stacks themselves. */
1930 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1931 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1932
1933 /* Remember, you can't free anything that came from the resource
1934 database. This includes:
1935 mw->menu.cursor
1936 mw->menu.top_shadow_pixmap
1937 mw->menu.bottom_shadow_pixmap
1938 mw->menu.font
1939 Also the color cells of top_shadow_color, bottom_shadow_color,
1940 foreground, and button_foreground will never be freed until this
1941 client exits. Nice, eh?
1942 */
1943
1944 /* start from 1 because the one in slot 0 is w->core.window */
1945 for (i = 1; i < mw->menu.windows_length; i++)
1946 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1947 if (mw->menu.windows)
1948 XtFree ((char *) mw->menu.windows);
1949 }
1950
1951 static Boolean
1952 XlwMenuSetValues (current, request, new)
1953 Widget current;
1954 Widget request;
1955 Widget new;
1956 {
1957 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1958 XlwMenuWidget newmw = (XlwMenuWidget)new;
1959 Boolean redisplay = False;
1960 int i;
1961
1962 if (newmw->menu.contents
1963 && newmw->menu.contents->contents
1964 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1965 redisplay = True;
1966 /* Do redisplay if the contents are entirely eliminated. */
1967 if (newmw->menu.contents
1968 && newmw->menu.contents->contents == 0
1969 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1970 redisplay = True;
1971
1972 if (newmw->core.background_pixel != oldmw->core.background_pixel
1973 || newmw->menu.foreground != oldmw->menu.foreground
1974 #ifdef HAVE_X_I18N
1975 || newmw->menu.fontSet != oldmw->menu.fontSet
1976 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
1977 #else
1978 || newmw->menu.font != oldmw->menu.font
1979 #endif
1980 )
1981 {
1982 release_drawing_gcs (newmw);
1983 make_drawing_gcs (newmw);
1984
1985 release_shadow_gcs (newmw);
1986 /* Cause the shadow colors to be recalculated. */
1987 newmw->menu.top_shadow_color = -1;
1988 newmw->menu.bottom_shadow_color = -1;
1989 make_shadow_gcs (newmw);
1990
1991 redisplay = True;
1992
1993 if (XtIsRealized (current))
1994 /* If the menu is currently displayed, change the display. */
1995 for (i = 0; i < oldmw->menu.windows_length; i++)
1996 {
1997 XSetWindowBackground (XtDisplay (oldmw),
1998 oldmw->menu.windows [i].window,
1999 newmw->core.background_pixel);
2000 /* clear windows and generate expose events */
2001 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2002 0, 0, 0, 0, True);
2003 }
2004 }
2005
2006 #ifdef HAVE_X_I18N
2007 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2008 {
2009 redisplay = True;
2010 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2011 }
2012 #endif
2013
2014 return redisplay;
2015 }
2016
2017 static void
2018 XlwMenuResize (w)
2019 Widget w;
2020 {
2021 XlwMenuWidget mw = (XlwMenuWidget)w;
2022
2023 if (mw->menu.popped_up)
2024 {
2025 /* Don't allow the popup menu to resize itself. */
2026 mw->core.width = mw->menu.windows [0].width;
2027 mw->core.height = mw->menu.windows [0].height;
2028 mw->core.parent->core.width = mw->core.width ;
2029 mw->core.parent->core.height = mw->core.height ;
2030 }
2031 else
2032 {
2033 mw->menu.windows [0].width = mw->core.width;
2034 mw->menu.windows [0].height = mw->core.height;
2035 }
2036 }
2037
2038 \f/* Action procedures */
2039 static void
2040 handle_single_motion_event (mw, ev)
2041 XlwMenuWidget mw;
2042 XMotionEvent* ev;
2043 {
2044 widget_value* val;
2045 int level;
2046
2047 if (!map_event_to_widget_value (mw, ev, &val, &level))
2048 pop_new_stack_if_no_contents (mw);
2049 else
2050 set_new_state (mw, val, level);
2051 remap_menubar (mw);
2052
2053 /* Sync with the display. Makes it feel better on X terms. */
2054 XSync (XtDisplay (mw), False);
2055 }
2056
2057 static void
2058 handle_motion_event (mw, ev)
2059 XlwMenuWidget mw;
2060 XMotionEvent* ev;
2061 {
2062 int x = ev->x_root;
2063 int y = ev->y_root;
2064 int state = ev->state;
2065
2066 handle_single_motion_event (mw, ev);
2067
2068 /* allow motion events to be generated again */
2069 if (ev->is_hint
2070 && XQueryPointer (XtDisplay (mw), ev->window,
2071 &ev->root, &ev->subwindow,
2072 &ev->x_root, &ev->y_root,
2073 &ev->x, &ev->y,
2074 &ev->state)
2075 && ev->state == state
2076 && (ev->x_root != x || ev->y_root != y))
2077 handle_single_motion_event (mw, ev);
2078 }
2079
2080 static void
2081 Start (w, ev, params, num_params)
2082 Widget w;
2083 XEvent *ev;
2084 String *params;
2085 Cardinal *num_params;
2086 {
2087 XlwMenuWidget mw = (XlwMenuWidget)w;
2088
2089 if (!mw->menu.popped_up)
2090 {
2091 menu_post_event = *ev;
2092 /* If event is set to CurrentTime, get the last known time stamp.
2093 This is for calculating if (popup) menus should stay up after
2094 a fast click. */
2095 if (menu_post_event.xbutton.time == CurrentTime)
2096 menu_post_event.xbutton.time
2097 = XtLastTimestampProcessed (XtDisplay (w));
2098
2099 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2100 }
2101 else
2102 {
2103 /* If we push a button while the menu is posted semipermanently,
2104 releasing the button should always pop the menu down. */
2105 next_release_must_exit = 1;
2106
2107 /* notes the absolute position of the menubar window */
2108 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2109 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2110
2111 /* handles the down like a move, slots are compatible */
2112 ev->xmotion.is_hint = 0;
2113 handle_motion_event (mw, &ev->xmotion);
2114 }
2115 }
2116
2117 static void
2118 Drag (w, ev, params, num_params)
2119 Widget w;
2120 XEvent *ev;
2121 String *params;
2122 Cardinal *num_params;
2123 {
2124 XlwMenuWidget mw = (XlwMenuWidget)w;
2125 if (mw->menu.popped_up)
2126 handle_motion_event (mw, &ev->xmotion);
2127 }
2128
2129 /* Do nothing.
2130 This is how we handle presses and releases of modifier keys. */
2131 static void
2132 Nothing (w, ev, params, num_params)
2133 Widget w;
2134 XEvent *ev;
2135 String *params;
2136 Cardinal *num_params;
2137 {
2138 }
2139
2140 static widget_value *
2141 find_first_selectable (mw, item, skip_titles)
2142 XlwMenuWidget mw;
2143 widget_value *item;
2144 int skip_titles;
2145 {
2146 widget_value *current = item;
2147 enum menu_separator separator;
2148
2149 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2150 || (skip_titles && !current->call_data && !current->contents))
2151 if (current->next)
2152 current=current->next;
2153 else
2154 return NULL;
2155
2156 return current;
2157 }
2158
2159 static widget_value *
2160 find_next_selectable (mw, item, skip_titles)
2161 XlwMenuWidget mw;
2162 widget_value *item;
2163 int skip_titles;
2164 {
2165 widget_value *current = item;
2166 enum menu_separator separator;
2167
2168 while (current->next && (current=current->next) &&
2169 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2170 || (skip_titles && !current->call_data && !current->contents)))
2171 ;
2172
2173 if (current == item)
2174 {
2175 if (mw->menu.old_depth < 2)
2176 return current;
2177 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2178
2179 while (lw_separator_p (current->name, &separator, 0)
2180 || !current->enabled
2181 || (skip_titles && !current->call_data
2182 && !current->contents))
2183 {
2184 if (current->next)
2185 current=current->next;
2186
2187 if (current == item)
2188 break;
2189 }
2190
2191 }
2192
2193 return current;
2194 }
2195
2196 static widget_value *
2197 find_prev_selectable (mw, item, skip_titles)
2198 XlwMenuWidget mw;
2199 widget_value *item;
2200 int skip_titles;
2201 {
2202 widget_value *current = item;
2203 widget_value *prev = item;
2204
2205 while ((current=find_next_selectable (mw, current, skip_titles))
2206 != item)
2207 {
2208 if (prev == current)
2209 break;
2210 prev=current;
2211 }
2212
2213 return prev;
2214 }
2215
2216 static void
2217 Down (w, ev, params, num_params)
2218 Widget w;
2219 XEvent *ev;
2220 String *params;
2221 Cardinal *num_params;
2222 {
2223 XlwMenuWidget mw = (XlwMenuWidget) w;
2224 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2225 int popup_menu_p = mw->menu.top_depth == 1;
2226
2227 /* Inside top-level menu-bar? */
2228 if (mw->menu.old_depth == mw->menu.top_depth)
2229 /* When <down> in the menu-bar is pressed, display the corresponding
2230 sub-menu and select the first selectable menu item there.
2231 If this is a popup menu, skip title item of the popup. */
2232 set_new_state (mw,
2233 find_first_selectable (mw,
2234 selected_item->contents,
2235 popup_menu_p),
2236 mw->menu.old_depth);
2237 else
2238 /* Highlight next possible (enabled and not separator) menu item. */
2239 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2240 mw->menu.old_depth - 1);
2241
2242 remap_menubar (mw);
2243 }
2244
2245 static void
2246 Up (w, ev, params, num_params)
2247 Widget w;
2248 XEvent *ev;
2249 String *params;
2250 Cardinal *num_params;
2251 {
2252 XlwMenuWidget mw = (XlwMenuWidget) w;
2253 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2254 int popup_menu_p = mw->menu.top_depth == 1;
2255
2256 /* Inside top-level menu-bar? */
2257 if (mw->menu.old_depth == mw->menu.top_depth)
2258 {
2259 /* FIXME: this is tricky. <up> in the menu-bar should select the
2260 last selectable item in the list. So we select the first
2261 selectable one and find the previous selectable item. Is there
2262 a better way? */
2263 /* If this is a popup menu, skip title item of the popup. */
2264 set_new_state (mw,
2265 find_first_selectable (mw,
2266 selected_item->contents,
2267 popup_menu_p),
2268 mw->menu.old_depth);
2269 remap_menubar (mw);
2270 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2271 set_new_state (mw,
2272 find_prev_selectable (mw,
2273 selected_item,
2274 popup_menu_p),
2275 mw->menu.old_depth - 1);
2276 }
2277 else
2278 /* Highlight previous (enabled and not separator) menu item. */
2279 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2280 mw->menu.old_depth - 1);
2281
2282 remap_menubar (mw);
2283 }
2284
2285 void
2286 Left (w, ev, params, num_params)
2287 Widget w;
2288 XEvent *ev;
2289 String *params;
2290 Cardinal *num_params;
2291 {
2292 XlwMenuWidget mw = (XlwMenuWidget) w;
2293 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2294
2295 /* Inside top-level menu-bar? */
2296 if (mw->menu.old_depth == mw->menu.top_depth)
2297 /* When <left> in the menu-bar is pressed, display the previous item on
2298 the menu-bar. If the current item is the first one, highlight the
2299 last item in the menubar (probably Help). */
2300 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2301 mw->menu.old_depth - 1);
2302 else if (mw->menu.old_depth == 1
2303 && selected_item->contents) /* Is this menu item expandable? */
2304 {
2305 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2306 remap_menubar (mw);
2307 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2308 if (!selected_item->enabled && find_first_selectable (mw,
2309 selected_item,
2310 0))
2311 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2312 mw->menu.old_depth - 1);
2313 }
2314
2315 else
2316 {
2317 pop_new_stack_if_no_contents (mw);
2318 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2319 mw->menu.old_depth - 2);
2320 }
2321
2322 remap_menubar (mw);
2323 }
2324
2325 void
2326 Right (w, ev, params, num_params)
2327 Widget w;
2328 XEvent *ev;
2329 String *params;
2330 Cardinal *num_params;
2331 {
2332 XlwMenuWidget mw = (XlwMenuWidget) w;
2333 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2334
2335 /* Inside top-level menu-bar? */
2336 if (mw->menu.old_depth == mw->menu.top_depth)
2337 /* When <right> in the menu-bar is pressed, display the next item on
2338 the menu-bar. If the current item is the last one, highlight the
2339 first item (probably File). */
2340 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2341 mw->menu.old_depth - 1);
2342 else if (selected_item->contents) /* Is this menu item expandable? */
2343 {
2344 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2345 remap_menubar (mw);
2346 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2347 if (!selected_item->enabled && find_first_selectable (mw,
2348 selected_item,
2349 0))
2350 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2351 mw->menu.old_depth - 1);
2352 }
2353 else
2354 {
2355 pop_new_stack_if_no_contents (mw);
2356 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2357 mw->menu.old_depth - 2);
2358 }
2359
2360 remap_menubar (mw);
2361 }
2362
2363 /* Handle key press and release events while menu is popped up.
2364 Our action is to get rid of the menu. */
2365 static void
2366 Key (w, ev, params, num_params)
2367 Widget w;
2368 XEvent *ev;
2369 String *params;
2370 Cardinal *num_params;
2371 {
2372 XlwMenuWidget mw = (XlwMenuWidget)w;
2373
2374 /* Pop down everything. */
2375 mw->menu.new_depth = 1;
2376 remap_menubar (mw);
2377
2378 if (mw->menu.popped_up)
2379 {
2380 mw->menu.popped_up = False;
2381 ungrab_all ((Widget)mw, ev->xmotion.time);
2382 if (XtIsShell (XtParent ((Widget) mw)))
2383 XtPopdown (XtParent ((Widget) mw));
2384 else
2385 {
2386 XtRemoveGrab ((Widget) mw);
2387 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2388 }
2389 }
2390
2391 /* callback */
2392 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2393 }
2394
2395 static void
2396 Select (w, ev, params, num_params)
2397 Widget w;
2398 XEvent *ev;
2399 String *params;
2400 Cardinal *num_params;
2401 {
2402 XlwMenuWidget mw = (XlwMenuWidget)w;
2403 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2404
2405 /* If user releases the button quickly, without selecting anything,
2406 after the initial down-click that brought the menu up,
2407 do nothing. */
2408 if ((selected_item == 0
2409 || ((widget_value *) selected_item)->call_data == 0)
2410 && !next_release_must_exit
2411 && (ev->xbutton.time - menu_post_event.xbutton.time
2412 < XtGetMultiClickTime (XtDisplay (w))))
2413 return;
2414
2415 /* pop down everything. */
2416 mw->menu.new_depth = 1;
2417 remap_menubar (mw);
2418
2419 if (mw->menu.popped_up)
2420 {
2421 mw->menu.popped_up = False;
2422 ungrab_all ((Widget)mw, ev->xmotion.time);
2423 if (XtIsShell (XtParent ((Widget) mw)))
2424 XtPopdown (XtParent ((Widget) mw));
2425 else
2426 {
2427 XtRemoveGrab ((Widget) mw);
2428 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2429 }
2430 }
2431
2432 /* callback */
2433 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2434 }
2435
2436
2437 \f/* Special code to pop-up a menu */
2438 static void
2439 pop_up_menu (mw, event)
2440 XlwMenuWidget mw;
2441 XButtonPressedEvent* event;
2442 {
2443 int x = event->x_root;
2444 int y = event->y_root;
2445 int w;
2446 int h;
2447 int borderwidth = mw->menu.shadow_thickness;
2448 Screen* screen = XtScreen (mw);
2449 Display *display = XtDisplay (mw);
2450
2451 next_release_must_exit = 0;
2452
2453 mw->menu.inside_entry = NULL;
2454 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2455
2456 if (XtIsShell (XtParent ((Widget)mw)))
2457 size_menu (mw, 0);
2458
2459 w = mw->menu.windows [0].width;
2460 h = mw->menu.windows [0].height;
2461
2462 x -= borderwidth;
2463 y -= borderwidth;
2464 if (x < borderwidth)
2465 x = borderwidth;
2466 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2467 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2468 if (y < borderwidth)
2469 y = borderwidth;
2470 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2471 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2472
2473 mw->menu.popped_up = True;
2474 if (XtIsShell (XtParent ((Widget)mw)))
2475 {
2476 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2477 XtParent ((Widget)mw)->core.border_width);
2478 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2479 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2480 mw->menu.windows [0].x = x + borderwidth;
2481 mw->menu.windows [0].y = y + borderwidth;
2482 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2483 }
2484 else
2485 {
2486 XEvent *ev = (XEvent *) event;
2487
2488 XtAddGrab ((Widget) mw, True, True);
2489
2490 /* notes the absolute position of the menubar window */
2491 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2492 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2493 mw->menu.top_depth = 2;
2494 }
2495
2496 #ifdef emacs
2497 x_catch_errors (display);
2498 #endif
2499 if (XtGrabPointer ((Widget)mw, False,
2500 (PointerMotionMask
2501 | PointerMotionHintMask
2502 | ButtonReleaseMask
2503 | ButtonPressMask),
2504 GrabModeAsync, GrabModeAsync, None,
2505 mw->menu.cursor_shape,
2506 event->time) == Success)
2507 {
2508 if (! GRAB_KEYBOARD
2509 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2510 GrabModeAsync, event->time) == Success)
2511 {
2512 XtSetKeyboardFocus((Widget)mw, None);
2513 pointer_grabbed = 1;
2514 }
2515 else
2516 XtUngrabPointer ((Widget)mw, event->time);
2517 }
2518
2519 #ifdef emacs
2520 if (x_had_errors_p (display))
2521 {
2522 pointer_grabbed = 0;
2523 XtUngrabPointer ((Widget)mw, event->time);
2524 }
2525 x_uncatch_errors ();
2526 #endif
2527
2528 ((XMotionEvent*)event)->is_hint = 0;
2529 handle_motion_event (mw, (XMotionEvent*)event);
2530 }
2531
2532 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2533 (do not change this comment) */