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