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