Build Fix -- compatibility issue with newer autoconf
[sxemacs] / src / ui / lwlib / xlwmenu.c
1 /* Implements a lightweight menubar widget.
2    Copyright (C) 1992, 1993, 1994 Lucid, Inc.
3    Copyright (C) 1995 Tinker Systems and INS Engineering Corp.
4
5 This file is part of the Lucid Widget Library.
6
7 The Lucid Widget Library is free software: you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation, either version 3 of the
10 License, or (at your option) any later version.
11
12 The Lucid Widget Library is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
19
20 /* Created by devin@lucid.com */
21
22 #include <config.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include <limits.h>
29 #ifdef HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32
33 #include <X11/IntrinsicP.h>
34 #include <X11/ShellP.h>
35 #include <X11/StringDefs.h>
36 #include <X11/cursorfont.h>
37 #include <X11/bitmaps/gray>
38
39 #ifdef NEED_MOTIF
40 #include <Xm/Xm.h>
41 #if XmVersion < 1002            /* 1.1 or ancient */
42 #undef XmFONTLIST_DEFAULT_TAG
43 #define XmFONTLIST_DEFAULT_TAG XmSTRING_DEFAULT_CHARSET
44 #endif                          /* XmVersion < 1.2 */
45 #endif
46 #include "xlwmenuP.h"
47 #include "lwlib-internal.h"
48
49 #include <sxe-utils.h>
50 #include <sxe-memory.h>
51
52 #ifdef USE_DEBUG_MALLOC
53 #include <dmalloc.h>
54 #endif
55
56 static char
57  xlwMenuTranslations[] = "<BtnDown>:    start()\n\
58 <BtnMotion>:    drag()\n\
59 <BtnUp>:        select()\n\
60 ";
61
62 extern Widget lw_menubar_widget;
63
64 #define offset(field) XtOffset(XlwMenuWidget, field)
65 static XtResource xlwMenuResources[] = {
66 #ifdef NEED_MOTIF
67         /* There are three font list resources, so that we can accept either of
68            the resources *fontList: or *font:, and so that we can tell the
69            difference between them being specified, and being defaulted to a
70            font from the XtRString specified here. */
71         {XmNfontList, XmCFontList, XmRFontList, sizeof(XmFontList),
72          offset(menu.font_list), XtRImmediate, (XtPointer) 0}
73         ,
74         {XtNfont, XtCFont, XmRFontList, sizeof(XmFontList),
75          offset(menu.font_list_2), XtRImmediate, (XtPointer) 0}
76         ,
77         {XmNfontList, XmCFontList, XmRFontList, sizeof(XmFontList),
78          offset(menu.fallback_font_list),
79          /* We must use an iso8859-1 font here, or people without $LANG set lose.
80             It's fair to assume that those who do have $LANG set also have the
81             *fontList resource set, or at least know how to deal with this. */
82          XtRString,
83          (XtPointer) "-*-helvetica-bold-r-*-*-*-120-*-*-*-*-iso8859-1"}
84         ,
85 #else
86         {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
87          offset(menu.font), XtRString, (XtPointer) "XtDefaultFont"}
88         ,
89 # ifdef USE_XFONTSET
90         /* #### Consider using the same method as for Motif; see the comment in
91            XlwMenuInitialize(). */
92         {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
93          offset(menu.font_set), XtRString, (XtPointer) "XtDefaultFontSet"}
94         ,
95 # endif
96 #endif
97         {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
98          offset(menu.foreground), XtRString, (XtPointer) "XtDefaultForeground"}
99         ,
100         {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
101          offset(menu.button_foreground), XtRString,
102          (XtPointer) "XtDefaultForeground"}
103         ,
104         {XtNhighlightForeground, XtCHighlightForeground, XtRPixel,
105          sizeof(Pixel),
106          offset(menu.highlight_foreground), XtRString,
107          (XtPointer) "XtDefaultForeground"}
108         ,
109         {XtNtitleForeground, XtCTitleForeground, XtRPixel, sizeof(Pixel),
110          offset(menu.title_foreground), XtRString,
111          (XtPointer) "XtDefaultForeground"}
112         ,
113         {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
114          offset(menu.margin), XtRImmediate, (XtPointer) 2}
115         ,
116         {XmNmarginWidth, XmCMarginWidth, XmRHorizontalDimension,
117          sizeof(Dimension),
118          offset(menu.horizontal_margin), XtRImmediate, (XtPointer) 2}
119         ,
120         {XmNmarginHeight, XmCMarginHeight, XmRVerticalDimension,
121          sizeof(Dimension),
122          offset(menu.vertical_margin), XtRImmediate, (XtPointer) 1}
123         ,
124         {XmNspacing, XmCSpacing, XmRHorizontalDimension, sizeof(Dimension),
125          offset(menu.column_spacing), XtRImmediate, (XtPointer) 4}
126         ,
127         {XmNindicatorSize, XmCIndicatorSize, XtRDimension, sizeof(Dimension),
128          offset(menu.indicator_size), XtRImmediate, (XtPointer) 0}
129         ,
130 #if 0
131         {XmNshadowThickness, XmCShadowThickness, XmRHorizontalDimension,
132          sizeof(Dimension), offset(menu.shadow_thickness),
133          XtRImmediate, (XtPointer) 2}
134         ,
135 #else
136         {XmNshadowThickness, XmCShadowThickness, XtRDimension,
137          sizeof(Dimension), offset(menu.shadow_thickness),
138          XtRImmediate, (XtPointer) 2}
139         ,
140 #endif
141         {XmNselectColor, XmCSelectColor, XtRPixel, sizeof(Pixel),
142          offset(menu.select_color), XtRImmediate, (XtPointer) - 1}
143         ,
144         {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof(Pixel),
145          offset(menu.top_shadow_color), XtRImmediate, (XtPointer) - 1}
146         ,
147         {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof(Pixel),
148          offset(menu.bottom_shadow_color), XtRImmediate, (XtPointer) - 1}
149         ,
150         {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof(Pixmap),
151          offset(menu.top_shadow_pixmap), XtRImmediate, (XtPointer) None}
152         ,
153         {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap,
154          sizeof(Pixmap),
155          offset(menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer) None}
156         ,
157
158         {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
159          offset(menu.open), XtRCallback, (XtPointer) NULL}
160         ,
161         {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
162          offset(menu.select), XtRCallback, (XtPointer) NULL}
163         ,
164         {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
165          offset(menu.contents), XtRImmediate, (XtPointer) NULL}
166         ,
167         {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
168          offset(menu.cursor_shape), XtRString, (XtPointer) "right_ptr"}
169         ,
170         {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
171          offset(menu.horizontal), XtRImmediate, (XtPointer) True},
172         {XtNuseBackingStore, XtCUseBackingStore, XtRBoolean, sizeof(Boolean),
173          offset(menu.use_backing_store), XtRImmediate, (XtPointer) False}
174         ,
175         {XtNbounceDown, XtCBounceDown, XtRBoolean, sizeof(Boolean),
176          offset(menu.bounce_down), XtRImmediate, (XtPointer) True}
177         ,
178         {XtNresourceLabels, XtCResourceLabels, XtRBoolean, sizeof(Boolean),
179          offset(menu.lookup_labels), XtRImmediate, (XtPointer) False}
180         ,
181 };
182 #undef offset
183
184 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
185                                 ArgList args, Cardinal * num_args);
186 static void XlwMenuRealize(Widget w, Mask * valueMask,
187                            XSetWindowAttributes * attributes);
188 static void XlwMenuRedisplay(Widget w, XEvent * ev, Region region);
189 static void XlwMenuResize(Widget w);
190 static void XlwMenuInitialize(Widget request, Widget new, ArgList args,
191                               Cardinal * num_args);
192 static void XlwMenuDestroy(Widget w);
193 static void XlwMenuClassInitialize(void);
194 static void Start(Widget w, XEvent * ev, String * params,
195                   Cardinal * num_params);
196 static void Drag(Widget w, XEvent * ev, String * params, Cardinal * num_params);
197 static void Select(Widget w, XEvent * ev, String * params,
198                    Cardinal * num_params);
199
200 #ifdef NEED_MOTIF
201 static XFontStruct *default_font_of_font_list(XmFontList);
202 #endif
203
204 static XtActionsRec xlwMenuActionsList[] = {
205         {"start", Start},
206         {"drag", Drag},
207         {"select", Select},
208 };
209
210 #define SuperClass ((CoreWidgetClass)&coreClassRec)
211
212 XlwMenuClassRec xlwMenuClassRec = {
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,                  /* #### - should this be set for grabs? 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          }
247         ,                       /* XlwMenuClass fields initialization */
248         {
249          0                      /* dummy */
250          }
251         ,
252 };
253
254 WidgetClass xlwMenuWidgetClass = (WidgetClass) & xlwMenuClassRec;
255
256 extern int lw_menu_accelerate;
257 \f
258 /* Utilities */
259 #if 0                           /* Apparently not used anywhere */
260
261 static char *safe_strdup(char *s)
262 {
263         char *result;
264         if (!s)
265                 return 0;
266         result = (char *)malloc(strlen(s) + 1);
267         if (!result)
268                 return 0;
269         strcpy(result, s);
270         return result;
271 }
272
273 #endif                          /* 0 */
274
275 /* Replacement for XAllocColor() that tries to return the nearest
276    available color if the colormap is full.  From FSF Emacs. */
277
278 static int
279 allocate_nearest_color(Display * display, Colormap screen_colormap,
280                        XColor * color_def)
281 {
282         int status = XAllocColor(display, screen_colormap, color_def);
283         if (status)
284                 return status;
285
286         {
287                 /* If we got to this point, the colormap is full, so we're
288                    going to try to get the next closest color.
289                    The algorithm used is a least-squares matching, which is
290                    what X uses for closest color matching with StaticColor visuals.  */
291
292                 int nearest, x;
293                 unsigned long nearest_delta = ULONG_MAX;
294
295                 int no_cells = XDisplayCells(display, XDefaultScreen(display));
296                 /* Don't use alloca here because lwlib doesn't have the
297                    necessary configuration information that src does. */
298                 XColor *cells = (XColor *) malloc(sizeof(XColor) * no_cells);
299
300                 for (x = 0; x < no_cells; x++)
301                         cells[x].pixel = x;
302
303                 XQueryColors(display, screen_colormap, cells, no_cells);
304
305                 for (nearest = 0, x = 0; x < no_cells; x++) {
306                         long dred = (color_def->red >> 8) - (cells[x].red >> 8);
307                         long dgreen =
308                             (color_def->green >> 8) - (cells[x].green >> 8);
309                         long dblue =
310                             (color_def->blue >> 8) - (cells[x].blue >> 8);
311                         unsigned long delta =
312                             dred * dred + dgreen * dgreen + dblue * dblue;
313
314                         if (delta < nearest_delta) {
315                                 nearest = x;
316                                 nearest_delta = delta;
317                         }
318                 }
319                 color_def->red = cells[nearest].red;
320                 color_def->green = cells[nearest].green;
321                 color_def->blue = cells[nearest].blue;
322                 free(cells);
323                 return XAllocColor(display, screen_colormap, color_def);
324         }
325 }
326
327 static void push_new_stack(XlwMenuWidget mw, widget_value * val)
328 {
329         if (!mw->menu.new_stack) {
330                 mw->menu.new_stack_length = 10;
331                 mw->menu.new_stack =
332                     (widget_value **) XtCalloc(mw->menu.new_stack_length,
333                                                sizeof(widget_value *));
334         } else if (mw->menu.new_depth == mw->menu.new_stack_length) {
335                 mw->menu.new_stack_length *= 2;
336                 mw->menu.new_stack =
337                     (widget_value **) XtRealloc((char *)mw->menu.new_stack,
338                                                 mw->menu.new_stack_length *
339                                                 sizeof(widget_value *));
340         }
341         mw->menu.new_stack[mw->menu.new_depth++] = val;
342 }
343
344 static void pop_new_stack_if_no_contents(XlwMenuWidget mw)
345 {
346         if (mw->menu.new_depth &&
347             !mw->menu.new_stack[mw->menu.new_depth - 1]->contents)
348                 mw->menu.new_depth -= 1;
349 }
350
351 static void make_old_stack_space(XlwMenuWidget mw, int n)
352 {
353         if (!mw->menu.old_stack) {
354                 mw->menu.old_stack_length = max(10, n);
355                 mw->menu.old_stack =
356                     (widget_value **) XtCalloc(mw->menu.old_stack_length,
357                                                sizeof(widget_value *));
358         } else if (mw->menu.old_stack_length < n) {
359                 while (mw->menu.old_stack_length < n)
360                         mw->menu.old_stack_length *= 2;
361
362                 mw->menu.old_stack =
363                     (widget_value **) XtRealloc((char *)mw->menu.old_stack,
364                                                 mw->menu.old_stack_length *
365                                                 sizeof(widget_value *));
366         }
367 }
368
369 static Boolean
370 close_to_reference_time(Widget w, Time reference_time, XEvent * ev)
371 {
372         return
373             reference_time &&
374             (ev->xbutton.time - reference_time
375              < (Time) XtGetMultiClickTime(XtDisplay(w)));
376 }
377 \f
378 /* Size code */
379 static int string_width(XlwMenuWidget mw,
380 #ifdef NEED_MOTIF
381                         XmString s
382 #else
383                         char *s
384 #endif
385     )
386 {
387 #ifdef NEED_MOTIF
388         Dimension width, height;
389         XmStringExtent(mw->menu.font_list, s, &width, &height);
390         return width;
391 #else
392 # ifdef USE_XFONTSET
393         XRectangle ri, rl;
394         XmbTextExtents(mw->menu.font_set, s, strlen(s), &ri, &rl);
395         return rl.width;
396 # else
397         XCharStruct xcs;
398         int drop;
399         XTextExtents(mw->menu.font, s, strlen(s), &drop, &drop, &drop, &xcs);
400         return xcs.width;
401 # endif                         /* USE_XFONTSET */
402 #endif
403 }
404
405 static char massaged_resource_char[256];
406
407 static void initialize_massaged_resource_char(void)
408 {
409         int j;
410         for (j = 0; j < (int)sizeof(massaged_resource_char); j++) {
411                 if ((j >= 'a' && j <= 'z') ||
412                     (j >= 'A' && j <= 'Z') ||
413                     (j >= '0' && j <= '9') || (j == '_') || (j >= 0xa0))
414                         massaged_resource_char[j] = (char)j;
415         }
416         massaged_resource_char['_'] = '_';
417         massaged_resource_char['+'] = 'P';      /* Convert C++ to cPP */
418         massaged_resource_char['.'] = '_';      /* Convert Buffers... to buffers___ */
419 }
420
421 static int string_width_u(XlwMenuWidget mw,
422 #ifdef NEED_MOTIF
423                           XmString string
424 #else
425                           char *string
426 #endif
427     )
428 {
429 #ifdef NEED_MOTIF
430         Dimension width, height;
431         XmString newstring;
432 #else
433 # ifdef USE_XFONTSET
434         XRectangle ri, rl;
435 # else                          /* ! USE_XFONTSET */
436         XCharStruct xcs;
437         int drop;
438 # endif
439 #endif
440         char *newchars;
441         int charslength;
442         char *chars;
443         int i, j;
444
445 #ifdef NEED_MOTIF
446         chars = "";
447         if (!XmStringGetLtoR(string, XmFONTLIST_DEFAULT_TAG, &chars))
448                 chars = "";
449 #else
450         chars = string;
451 #endif
452         charslength = strlen(chars);
453         newchars = (char *)alloca(charslength + 1);
454
455         for (i = j = 0; chars[i] && (j < charslength); i++)
456                 if (chars[i] == '%' && chars[i + 1] == '_')
457                         i++;
458                 else
459                         newchars[j++] = chars[i];
460         newchars[j] = '\0';
461
462 #ifdef NEED_MOTIF
463         newstring = XmStringLtoRCreate(newchars, XmFONTLIST_DEFAULT_TAG);
464         XmStringExtent(mw->menu.font_list, newstring, &width, &height);
465         XmStringFree(newstring);
466         XtFree(chars);
467         return width;
468 #else
469 # ifdef USE_XFONTSET
470         XmbTextExtents(mw->menu.font_set, newchars, j, &ri, &rl);
471         return rl.width;
472 # else                          /* ! USE_XFONTSET */
473         XTextExtents(mw->menu.font, newchars, j, &drop, &drop, &drop, &xcs);
474         return xcs.width;
475 # endif                         /* USE_XFONTSET */
476 #endif
477 }
478
479 static void massage_resource_name(const char *in, char *out)
480 {
481         /* Turn a random string into something suitable for using as a resource.
482            For example:
483
484            "Kill Buffer"              ->      "killBuffer"
485            "Find File..."             ->      "findFile___"
486            "Search and Replace..."    ->      "searchAndReplace___"
487            "C++ Mode Commands"        ->      "cppModeCommands"
488
489            Valid characters in a resource NAME component are:  a-zA-Z0-9_
490          */
491
492 #ifdef PRINT_XLWMENU_RESOURCE_CONVERSIONS
493         /* Compile with -DPRINT_XLWMENU_RESOURCE_CONVERSIONS to generate a
494            translation file for menu localizations. */
495         char *save_in = in, *save_out = out;
496 #endif
497
498         Boolean firstp = True;
499         while (*in) {
500                 if (*in == '%' && *(in + 1) == '_')
501                         in += 2;
502                 else {
503                         char ch;
504
505                         if (*in == '%' && *(in + 1) == '%')
506                                 in++;
507                         ch = massaged_resource_char[(unsigned char)*in++];
508                         if (ch) {
509                                 int int_ch = (int)(unsigned char)ch;
510                                 *out++ =
511                                     firstp ? tolower(int_ch) : toupper(int_ch);
512                                 firstp = False;
513                                 while ((ch =
514                                         massaged_resource_char[(unsigned char)
515                                                                *in++])
516                                        != '\0')
517                                         *out++ = ch;
518                                 if (!*(in - 1)) /* Overshot the NULL byte? */
519                                         break;
520                         }
521                 }
522         }
523         *out = 0;
524
525 #ifdef PRINT_XLWMENU_RESOURCE_CONVERSIONS
526         printf("! Emacs*XlwMenu.%s.labelString:\t%s\n", save_out, save_in);
527         printf("Emacs*XlwMenu.%s.labelString:\n", save_out);
528 #endif
529 }
530
531 static XtResource nameResource[] = {
532         {"labelString", "LabelString", XtRString, sizeof(String),
533          0, XtRImmediate, 0}
534 };
535
536 /* This function searches STRING for parameter inserts of the form:
537        %[padding]1
538    padding is either space (' ') or dash ('-') meaning
539    padding to the left or right of the inserted parameter.
540    In essence, all %1 strings are replaced by VALUE in the return value.
541    The caller is expected to free the return value using XtFree().
542    %% means insert one % (like printf).
543    %1 means insert VALUE.
544    %-1 means insert VALUE followed by one space. The latter is
545    not inserted if VALUE is a zero length string.
546 */
547 static char *parameterize_string(const char *string, const char *value)
548 {
549         const char *percent;
550         char *result;
551         unsigned int done = 0;
552         unsigned int ntimes;
553         size_t res_left = 0;
554
555         if (!string) {
556                 result = XtMalloc(1);
557                 result[0] = '\0';
558                 return result;
559         }
560
561         if (!value)
562                 value = "";
563
564         for (ntimes = 1, percent = string;
565              (percent = strchr(percent, '%')); ntimes++)
566                 percent++;
567
568         res_left = (ntimes * strlen(value)) + strlen(string) + 3;
569         result = XtMalloc(res_left+1);
570         result[res_left] = result[0] = '\0';
571
572         while ((percent = strchr(string, '%'))) {
573                 unsigned int left_pad;
574                 unsigned int right_pad;
575                 const char *p;
576                 off_t offset = percent-string;
577
578                 if (percent[1] == '%') {        /* it's a real % */
579                         assert( offset >=0 &&
580                                 res_left >= (size_t)(1 + offset));
581                                                 /* incl % */
582                         strncat(result, string, 1 + offset);
583                         res_left -= 1 + offset;
584                         string = &percent[2];   /* after the second '%' */
585                         continue;       /* with the while() loop */
586                 }
587
588                 left_pad = 0;
589                 right_pad = 0;
590
591                 for (p = &percent[1]; /* test *p inside the loop */ ; p++) {
592                         if (*p == ' ') {        /* left pad */
593                                 left_pad++;
594                         } else if (*p == '-') { /* right pad */
595                                 right_pad++;
596                         } else if (*p == '1') { /* param and terminator */
597                                 assert( offset >= 0 &&
598                                         res_left >= (size_t)offset );
599                                 res_left -= offset;
600                                 strncat(result, string, offset);
601                                 if (value[0] != '\0') {
602                                         unsigned int i;
603                                         for (i = 0; i < left_pad; i++)
604                                                 strncat(result, " ", res_left --);
605                                         strncat(result, value, res_left);
606                                         res_left -= strlen(value);
607                                         for (i = 0; i < right_pad; i++)
608                                                 strncat(result, " ", res_left--);
609                                 }
610                                 string = &p[1]; /* after the '1' */
611                                 done++; /* no need to do old way */
612                                 break;  /* out of for() loop */
613                         } else {        /* bogus, copy the format as is */
614                                 /* out of for() loop */
615                                 off_t remain = 1 + p - string;
616                                 assert( remain >=0 &&
617                                         res_left >= (size_t)remain );
618                                 strncat(result, string, remain);
619                                 res_left -= remain;
620                                 string = (*p ? &p[1] : p);
621                                 offset = percent - string;
622                                 break;
623                         }
624                 }
625         }
626
627         /* Copy the tail of the string */
628         strncat(result, string, res_left);
629         res_left -= strlen(string);
630
631         /* If we have not processed a % string, and we have a value, tail it. */
632         if (!done && value[0] != '\0') {
633                 strncat(result, " ", res_left--);
634                 strncat(result, value, res_left);
635         }
636
637         return result;
638 }
639
640 #ifdef NEED_MOTIF
641
642 static XmString resource_widget_value(XlwMenuWidget mw, widget_value * val)
643 {
644         if (!val->toolkit_data) {
645                 char *resourced_name = NULL;
646                 char *converted_name, *str;
647                 XmString complete_name;
648                 char massaged_name[1024];
649
650                 if (mw->menu.lookup_labels) {
651                         /* Convert value style name into resource style name.
652                            eg: "Free Willy" becomes "freeWilly" */
653                         massage_resource_name(val->name, massaged_name);
654
655                         /* If we have a value (parameter) see if we can find a "Named"
656                            resource. */
657                         if (val->value) {
658                                 char named_name[1024];
659                                 int sz = snprintf(named_name, sizeof(named_name),
660                                                   "%sNamed", massaged_name);
661                                 assert(sz >= 0 && sz < sizeof(named_name));
662                                 XtGetSubresources((Widget) mw,
663                                                   (XtPointer) & resourced_name,
664                                                   named_name, named_name,
665                                                   nameResource, 1, NULL, 0);
666                         }
667
668                         /* If nothing yet, try to load from the massaged name. */
669                         if (!resourced_name) {
670                                 XtGetSubresources((Widget) mw,
671                                                   (XtPointer) & resourced_name,
672                                                   massaged_name, massaged_name,
673                                                   nameResource, 1, NULL, 0);
674                         }
675                 }
676
677                 /* if (mw->menu.lookup_labels) */
678                 /* Still nothing yet, use the name as the value. */
679                 if (!resourced_name)
680                         resourced_name = val->name;
681
682                 /* Parameterize the string. */
683                 converted_name =
684                     parameterize_string(resourced_name, val->value);
685
686                 /* nuke newline characters to prevent menubar screwups */
687                 for (str = converted_name; *str; str++) {
688                         if (str[0] == '\n')
689                                 str[0] = ' ';
690                 }
691
692                 /* Improve OSF's bottom line. */
693 #if (XmVersion >= 1002)
694                 complete_name = XmStringCreateLocalized(converted_name);
695 #else
696                 complete_name = XmStringCreateLtoR(converted_name,
697                                                    XmSTRING_DEFAULT_CHARSET);
698 #endif
699                 XtFree(converted_name);
700
701                 val->toolkit_data = complete_name;
702                 val->free_toolkit_data = True;
703         }
704         return (XmString) val->toolkit_data;
705 }
706
707 /* Unused */
708 #if 0
709 /* These two routines should be a separate file..djw */
710 static char *xlw_create_localized_string(Widget w,
711                                          char *name,
712                                          char **args, unsigned int nargs)
713 {
714         char *string = NULL;
715         char *arg = NULL;
716
717         if (nargs > 0)
718                 arg = args[0];
719
720         XtGetSubresources(w,
721                           (XtPointer) & string,
722                           name, name, nameResource, 1, NULL, 0);
723
724         if (!string)
725                 string = name;
726
727         return parameterize_string(string, arg);
728 }
729
730 static XmString
731 xlw_create_localized_xmstring(Widget w,
732                               char *name, char **args, unsigned int nargs)
733 {
734         char *string = xlw_create_localized_string(w, name, args, nargs);
735         XmString xm_string =
736             XmStringCreateLtoR(string, XmSTRING_DEFAULT_CHARSET);
737         XtFree(string);
738         return xm_string;
739 }
740 #endif                          /* 0 */
741
742 #else                           /* !Motif */
743
744 static char *resource_widget_value(XlwMenuWidget mw, widget_value * val)
745 {
746         if (!val->toolkit_data) {
747                 char *resourced_name = NULL;
748                 char *complete_name;
749                 char massaged_name[1024];
750
751                 if (mw->menu.lookup_labels) {
752                         massage_resource_name(val->name, massaged_name);
753
754                         XtGetSubresources((Widget) mw,
755                                           (XtPointer) & resourced_name,
756                                           massaged_name, massaged_name,
757                                           nameResource, 1, NULL, 0);
758                 }
759                 if (!resourced_name)
760                         resourced_name = val->name;
761
762                 complete_name = parameterize_string(resourced_name, val->value);
763
764                 val->toolkit_data = complete_name;
765                 /* nuke newline characters to prevent menubar screwups */
766                 for (; *complete_name; complete_name++) {
767                         if (complete_name[0] == '\n')
768                                 complete_name[0] = ' ';
769                 }
770                 val->free_toolkit_data = True;
771         }
772         return (char *)val->toolkit_data;
773 }
774
775 #endif                          /* !Motif */
776
777 /* Code for drawing strings. */
778 static void string_draw(XlwMenuWidget mw, Window window, int x, int y, GC gc,
779 #ifdef NEED_MOTIF
780                         XmString string
781 #else
782                         char *string
783 #endif
784     )
785 {
786 #ifdef NEED_MOTIF
787         XmStringDraw(XtDisplay(mw), window, mw->menu.font_list, string, gc, x, y, 1000, /* ???? width */
788                      XmALIGNMENT_BEGINNING, 0,  /* ???? layout_direction */
789                      0);
790 #else
791 # ifdef USE_XFONTSET
792         XmbDrawString(XtDisplay(mw), window, mw->menu.font_set, gc,
793                       x, y + mw->menu.font_ascent, string, strlen(string));
794 # else
795         XDrawString(XtDisplay(mw), window, gc,
796                     x, y + mw->menu.font_ascent, string, strlen(string));
797 # endif                         /* USE_XFONTSET */
798
799 #endif
800 }
801
802 static int
803 string_draw_range(XlwMenuWidget mw,
804                   Window window,
805                   int x, int y, GC gc, char *string, int start, int end)
806 {
807 #ifdef NEED_MOTIF
808         Dimension width, height;
809         XmString newstring;
810         int c;
811
812         if (end <= start)
813                 return 0;
814         c = string[end];
815         string[end] = '\0';
816         newstring = XmStringLtoRCreate(&string[start], XmFONTLIST_DEFAULT_TAG);
817         XmStringDraw(XtDisplay(mw), window, mw->menu.font_list, newstring, gc, x, y, 1000,      /* ???? width */
818                      XmALIGNMENT_BEGINNING, 0,  /* ???? layout_direction */
819                      0);
820         XmStringExtent(mw->menu.font_list, newstring, &width, &height);
821         XmStringFree(newstring);
822         string[end] = c;
823         return width;
824 #else
825 # ifdef USE_XFONTSET
826         XRectangle ri, rl;
827
828         if (end <= start)
829                 return 0;
830         XmbDrawString(XtDisplay(mw), window, mw->menu.font_set, gc,
831                       x, y + mw->menu.font_ascent, &string[start], end - start);
832         XmbTextExtents(mw->menu.font_set, &string[start], end - start, &ri,
833                        &rl);
834         return rl.width;
835 # else
836         XCharStruct xcs;
837         int drop;
838
839         if (end <= start)
840                 return 0;
841         XDrawString(XtDisplay(mw), window, gc,
842                     x, y + mw->menu.font_ascent, &string[start], end - start);
843         XTextExtents(mw->menu.font, &string[start], end - start,
844                      &drop, &drop, &drop, &xcs);
845         return xcs.width;
846 # endif
847 #endif
848 }
849
850 static void string_draw_u(XlwMenuWidget mw, Window window, int x, int y, GC gc,
851 #ifdef NEED_MOTIF
852                           XmString string
853 #else
854                           char *string
855 #endif
856     )
857 {
858         int i, s = 0;
859         char *chars;
860
861 #ifdef NEED_MOTIF
862         chars = "";
863         if (!XmStringGetLtoR(string, XmFONTLIST_DEFAULT_TAG, &chars))
864                 chars = "";
865 #else
866         chars = string;
867 #endif
868         for (i = 0; chars[i]; ++i) {
869                 if (chars[i] == '%' && chars[i + 1] == '_') {
870                         int w;
871
872                         x += string_draw_range(mw, window, x, y, gc, chars, s,
873                                                i);
874                         w = string_draw_range(mw, window, x, y, gc, chars,
875                                               i + 2, i + 3);
876
877                         /* underline next character */
878                         XDrawLine(XtDisplay(mw), window, gc, x - 1,
879                                   y + mw->menu.font_ascent + 1,
880                                   x + w - 1, y + mw->menu.font_ascent + 1);
881                         x += w;
882                         s = i + 3;
883                         i += 2;
884                 }
885         }
886         x += string_draw_range(mw, window, x, y, gc, chars, s, i);
887 #ifdef NEED_MOTIF
888         XtFree(chars);
889 #endif
890 }
891
892 static void
893 binding_draw(XlwMenuWidget mw, Window w, int x, int y, GC gc, char *value)
894 {
895 #ifdef NEED_MOTIF
896         XmString xm_value = XmStringCreateLtoR(value, XmSTRING_DEFAULT_CHARSET);
897         string_draw(mw, w, x, y, gc, xm_value);
898         XmStringFree(xm_value);
899 #else
900         string_draw(mw, w, x, y, gc, value);
901 #endif
902 }
903
904 /* Low level code for drawing 3-D edges. */
905 static void
906 shadow_rectangle_draw(Display * dpy,
907                       Window window,
908                       GC top_gc,
909                       GC bottom_gc,
910                       int x, int y,
911                       unsigned int width,
912                       unsigned int height, unsigned int thickness)
913 {
914         XPoint points[4];
915
916         if (!thickness)
917                 return;
918
919         points[0].x = x;
920         points[0].y = y;
921         points[1].x = x + width;
922         points[1].y = y;
923         points[2].x = x + width - thickness;
924         points[2].y = y + thickness;
925         points[3].x = x;
926         points[3].y = y + thickness;
927         XFillPolygon(dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
928         points[0].x = x;
929         points[0].y = y + thickness;
930         points[1].x = x;
931         points[1].y = y + height;
932         points[2].x = x + thickness;
933         points[2].y = y + height - thickness;
934         points[3].x = x + thickness;
935         points[3].y = y + thickness;
936         XFillPolygon(dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
937         points[0].x = x + width;
938         points[0].y = y;
939         points[1].x = x + width - thickness;
940         points[1].y = y + thickness;
941         points[2].x = x + width - thickness;
942         points[2].y = y + height - thickness;
943         points[3].x = x + width;
944         points[3].y = y + height - thickness;
945         XFillPolygon(dpy, window, bottom_gc, points, 4, Convex,
946                      CoordModeOrigin);
947         points[0].x = x;
948         points[0].y = y + height;
949         points[1].x = x + width;
950         points[1].y = y + height;
951         points[2].x = x + width;
952         points[2].y = y + height - thickness;
953         points[3].x = x + thickness;
954         points[3].y = y + height - thickness;
955         XFillPolygon(dpy, window, bottom_gc, points, 4, Convex,
956                      CoordModeOrigin);
957 }
958
959 typedef enum e_shadow_type {
960         /* these are Motif compliant */
961         SHADOW_BACKGROUND,
962         SHADOW_OUT,
963         SHADOW_IN,
964         SHADOW_ETCHED_OUT,
965         SHADOW_ETCHED_IN,
966         SHADOW_ETCHED_OUT_DASH,
967         SHADOW_ETCHED_IN_DASH,
968         SHADOW_SINGLE_LINE,
969         SHADOW_DOUBLE_LINE,
970         SHADOW_SINGLE_DASHED_LINE,
971         SHADOW_DOUBLE_DASHED_LINE,
972         SHADOW_NO_LINE,
973         /* these are all non-Motif */
974         SHADOW_DOUBLE_ETCHED_OUT,
975         SHADOW_DOUBLE_ETCHED_IN,
976         SHADOW_DOUBLE_ETCHED_OUT_DASH,
977         SHADOW_DOUBLE_ETCHED_IN_DASH
978 } shadow_type;
979
980 static void
981 shadow_draw(XlwMenuWidget mw,
982             Window window,
983             int x, int y,
984             unsigned int width, unsigned int height, shadow_type type)
985 {
986         Display *dpy = XtDisplay(mw);
987         GC top_gc;
988         GC bottom_gc;
989         int thickness = mw->menu.shadow_thickness;
990 #if 0
991         XPoint points[4];
992 #endif                          /* 0 */
993         Boolean etched = False;
994
995         switch ((unsigned int)type) {
996         case SHADOW_BACKGROUND:
997                 top_gc = bottom_gc = mw->menu.background_gc;
998                 break;
999         case SHADOW_ETCHED_IN:
1000                 top_gc = mw->menu.shadow_bottom_gc;
1001                 bottom_gc = mw->menu.shadow_top_gc;
1002                 etched = True;
1003                 break;
1004         case SHADOW_ETCHED_OUT:
1005                 top_gc = mw->menu.shadow_top_gc;
1006                 bottom_gc = mw->menu.shadow_bottom_gc;
1007                 etched = True;
1008                 break;
1009         case SHADOW_IN:
1010                 top_gc = mw->menu.shadow_bottom_gc;
1011                 bottom_gc = mw->menu.shadow_top_gc;
1012                 break;
1013         case SHADOW_OUT:
1014         default:
1015                 top_gc = mw->menu.shadow_top_gc;
1016                 bottom_gc = mw->menu.shadow_bottom_gc;
1017                 break;
1018         }
1019
1020         if (etched) {
1021                 unsigned int half = thickness / 2;
1022                 shadow_rectangle_draw(dpy,
1023                                       window,
1024                                       top_gc,
1025                                       top_gc,
1026                                       x, y,
1027                                       width - half, height - half,
1028                                       thickness - half);
1029                 shadow_rectangle_draw(dpy,
1030                                       window,
1031                                       bottom_gc,
1032                                       bottom_gc,
1033                                       x + half, y + half,
1034                                       width - half, height - half, half);
1035         } else {
1036                 shadow_rectangle_draw(dpy,
1037                                       window,
1038                                       top_gc,
1039                                       bottom_gc,
1040                                       x, y, width, height, thickness);
1041         }
1042 }
1043
1044 static void
1045 arrow_decoration_draw(XlwMenuWidget mw,
1046                       Window window,
1047                       int x, int y, unsigned int width, Boolean raised)
1048 {
1049         Display *dpy = XtDisplay(mw);
1050         GC top_gc;
1051         GC bottom_gc;
1052         GC select_gc;
1053         int thickness = mw->menu.shadow_thickness;
1054         XPoint points[4];
1055         int half_width;
1056         int length = (int)((double)width * 0.87);
1057         int thick_med = (int)((double)thickness * 1.73);
1058
1059         if (width & 0x1)
1060                 half_width = width / 2 + 1;
1061         else
1062                 half_width = width / 2;
1063
1064         select_gc = mw->menu.background_gc;
1065
1066         if (raised) {
1067                 top_gc = mw->menu.shadow_bottom_gc;
1068                 bottom_gc = mw->menu.shadow_top_gc;
1069         } else {
1070                 top_gc = mw->menu.shadow_top_gc;
1071                 bottom_gc = mw->menu.shadow_bottom_gc;
1072         }
1073
1074         /* Fill internal area.  We do this first so that the borders have a
1075            nice sharp edge.  */
1076         points[0].x = x + thickness;
1077         points[0].y = y + thickness;
1078         points[1].x = x + length - thickness;
1079         points[1].y = y + half_width;
1080         points[2].x = x + length - thickness;
1081         points[2].y = y + half_width + thickness;
1082         points[3].x = x + thickness;
1083         points[3].y = y + width - thickness;
1084
1085         XFillPolygon(dpy,
1086                      window, select_gc, points, 4, Convex, CoordModeOrigin);
1087
1088         /* left border */
1089         points[0].x = x;
1090         points[0].y = y;
1091         points[1].x = x + thickness;
1092         points[1].y = y + thick_med;
1093         points[2].x = x + thickness;
1094         points[2].y = y + width - thick_med;
1095         points[3].x = x;
1096         points[3].y = y + width;
1097
1098         XFillPolygon(dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
1099
1100         /* top border */
1101         points[0].x = x;
1102         points[0].y = y + width;
1103         points[1].x = x + length;
1104         points[1].y = y + half_width;
1105         points[2].x = x + length - (thickness + thickness);
1106         points[2].y = y + half_width;
1107         points[3].x = x + thickness;
1108         points[3].y = y + width - thick_med;
1109
1110         XFillPolygon(dpy, window, bottom_gc, points, 4, Convex,
1111                      CoordModeOrigin);
1112
1113         /* bottom shadow */
1114         points[0].x = x;
1115         points[0].y = y;
1116         points[1].x = x + length;
1117         points[1].y = y + half_width;
1118         points[2].x = x + length - (thickness + thickness);
1119         points[2].y = y + half_width;
1120         points[3].x = x + thickness;
1121         points[3].y = y + thick_med;
1122
1123         XFillPolygon(dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
1124 }
1125
1126 static void
1127 toggle_decoration_draw(XlwMenuWidget mw,
1128                        Window window,
1129                        int x, int y, unsigned int width, Boolean set)
1130 {
1131         Display *dpy = XtDisplay(mw);
1132         int thickness = mw->menu.shadow_thickness;
1133         shadow_type type;
1134         GC select_gc = mw->menu.select_gc;
1135
1136         if (set)
1137                 type = SHADOW_IN;
1138         else
1139                 type = SHADOW_OUT;
1140
1141         /* Fill internal area. */
1142         if (set)
1143                 XFillRectangle(dpy,
1144                                window,
1145                                select_gc,
1146                                x + thickness,
1147                                y + thickness,
1148                                width - (2 * thickness),
1149                                width - (2 * thickness));
1150
1151         shadow_draw(mw, window, x, y, width, width, type);
1152 }
1153
1154 static void
1155 radio_decoration_draw(XlwMenuWidget mw,
1156                       Window window,
1157                       int x, int y, unsigned int width, Boolean enabled)
1158 {
1159         Display *dpy = XtDisplay(mw);
1160         GC top_gc;
1161         GC bottom_gc;
1162         GC select_gc = mw->menu.select_gc;
1163         int thickness = mw->menu.shadow_thickness;
1164         XPoint points[6];
1165         int half_width;
1166 #if 0
1167         int npoints;
1168 #endif                          /* 0 */
1169
1170         if (width & 0x1)
1171                 width++;
1172
1173         half_width = width / 2;
1174
1175         if (enabled) {
1176                 top_gc = mw->menu.shadow_bottom_gc;
1177                 bottom_gc = mw->menu.shadow_top_gc;
1178         } else {
1179                 top_gc = mw->menu.shadow_top_gc;
1180                 bottom_gc = mw->menu.shadow_bottom_gc;
1181         }
1182
1183 #if 1
1184         /*  Draw the bottom first, just in case the regions overlap.
1185            The top should cast the longer shadow. */
1186         points[0].x = x;        /* left corner */
1187         points[0].y = y + half_width;
1188         points[1].x = x + half_width;   /* bottom corner */
1189         points[1].y = y + width;
1190         points[2].x = x + half_width;   /* bottom inside corner */
1191         points[2].y = y + width - thickness;
1192         points[3].x = x + thickness;    /* left inside corner */
1193         points[3].y = y + half_width;
1194
1195         XFillPolygon(dpy, window, bottom_gc, points, 4, Convex,
1196                      CoordModeOrigin);
1197
1198         points[0].x = x + half_width;   /* bottom corner */
1199         points[0].y = y + width;
1200         points[1].x = x + width;        /* right corner */
1201         points[1].y = y + half_width;
1202         points[2].x = x + width - thickness;    /* right inside corner */
1203         points[2].y = y + half_width;
1204         points[3].x = x + half_width;   /* bottom inside corner */
1205         points[3].y = y + width - thickness;
1206
1207         XFillPolygon(dpy, window, bottom_gc, points, 4, Convex,
1208                      CoordModeOrigin);
1209
1210         points[0].x = x;        /* left corner */
1211         points[0].y = y + half_width;
1212         points[1].x = x + half_width;   /* top corner */
1213         points[1].y = y;
1214         points[2].x = x + half_width;   /* top inside corner */
1215         points[2].y = y + thickness;
1216         points[3].x = x + thickness;    /* left inside corner */
1217         points[3].y = y + half_width;
1218
1219         XFillPolygon(dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
1220
1221         points[0].x = x + half_width;   /* top corner */
1222         points[0].y = y;
1223         points[1].x = x + width;        /* right corner */
1224         points[1].y = y + half_width;
1225         points[2].x = x + width - thickness;    /* right inside corner */
1226         points[2].y = y + half_width;
1227         points[3].x = x + half_width;   /* top inside corner */
1228         points[3].y = y + thickness;
1229
1230         XFillPolygon(dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
1231 #else
1232         /* Draw the bottom first, just in case the regions overlap.
1233            The top should cast the longer shadow. */
1234         npoints = 0;
1235         points[npoints].x = x;  /* left corner */
1236         points[npoints++].y = y + half_width;
1237         points[npoints].x = x + half_width;     /* bottom corner */
1238         points[npoints++].y = y + width;
1239         points[npoints].x = x + width;  /* right corner */
1240         points[npoints++].y = y + half_width;
1241         points[npoints].x = x + width - thickness;      /* right inside corner */
1242         points[npoints++].y = y + half_width;
1243         points[npoints].x = x + half_width;     /* bottom inside corner */
1244         points[npoints++].y = y + width - thickness;
1245         points[npoints].x = x + thickness;      /* left inside corner */
1246         points[npoints++].y = y + half_width;
1247
1248         XFillPolygon(dpy, window, bottom_gc,
1249                      points, npoints, Nonconvex, CoordModeOrigin);
1250
1251         npoints = 0;
1252
1253         points[npoints].x = x;  /* left corner */
1254         points[npoints++].y = y + half_width;
1255         points[npoints].x = x + half_width;     /* top corner */
1256         points[npoints++].y = y;
1257         points[npoints].x = x + width;  /* right corner */
1258         points[npoints++].y = y + half_width;
1259         points[npoints].x = x + width - thickness;      /* right inside corner */
1260         points[npoints++].y = y + half_width;
1261         points[npoints].x = x + half_width;     /* top inside corner */
1262         points[npoints++].y = y + thickness;
1263         points[npoints].x = x + thickness;      /* left inside corner */
1264         points[npoints++].y = y + half_width;
1265
1266         XFillPolygon(dpy, window, top_gc, points, npoints, Nonconvex,
1267                      CoordModeOrigin);
1268 #endif
1269
1270         /* Fill internal area. */
1271         if (enabled) {
1272                 points[0].x = x + thickness;
1273                 points[0].y = y + half_width;
1274                 points[1].x = x + half_width;
1275                 points[1].y = y + thickness;
1276                 points[2].x = x + width - thickness;
1277                 points[2].y = y + half_width;
1278                 points[3].x = x + half_width;
1279                 points[3].y = y + width - thickness;
1280                 XFillPolygon(dpy,
1281                              window,
1282                              select_gc, points, 4, Convex, CoordModeOrigin);
1283         }
1284 }
1285
1286 static void
1287 separator_decoration_draw(XlwMenuWidget mw,
1288                           Window window,
1289                           int x, int y,
1290                           unsigned int width,
1291                           Boolean vertical, shadow_type type)
1292 {
1293         Display *dpy = XtDisplay(mw);
1294         GC top_gc;
1295         GC bottom_gc;
1296         unsigned int offset = 0;
1297         unsigned int num_separators = 1;
1298         unsigned int top_line_thickness = 0;
1299         unsigned int bottom_line_thickness = 0;
1300         Boolean dashed = False;
1301
1302         switch ((unsigned int)type) {
1303         case SHADOW_NO_LINE:    /* nothing to do */
1304                 return;
1305         case SHADOW_DOUBLE_LINE:
1306                 num_separators = 2;
1307         case SHADOW_SINGLE_LINE:
1308                 top_gc = bottom_gc = mw->menu.foreground_gc;
1309                 top_line_thickness = 1;
1310                 break;
1311         case SHADOW_DOUBLE_DASHED_LINE:
1312                 num_separators = 2;
1313         case SHADOW_SINGLE_DASHED_LINE:
1314                 top_gc = bottom_gc = mw->menu.foreground_gc;
1315                 top_line_thickness = 1;
1316                 dashed = True;
1317                 break;
1318         case SHADOW_DOUBLE_ETCHED_OUT_DASH:
1319                 num_separators = 2;
1320         case SHADOW_ETCHED_OUT_DASH:
1321                 top_gc = mw->menu.shadow_top_gc;
1322                 bottom_gc = mw->menu.shadow_bottom_gc;
1323                 top_line_thickness = mw->menu.shadow_thickness / 2;
1324                 bottom_line_thickness =
1325                     mw->menu.shadow_thickness - top_line_thickness;
1326                 dashed = True;
1327                 break;
1328         case SHADOW_DOUBLE_ETCHED_IN_DASH:
1329                 num_separators = 2;
1330         case SHADOW_ETCHED_IN_DASH:
1331                 top_gc = mw->menu.shadow_bottom_gc;
1332                 bottom_gc = mw->menu.shadow_top_gc;
1333                 top_line_thickness = mw->menu.shadow_thickness / 2;
1334                 bottom_line_thickness =
1335                     mw->menu.shadow_thickness - top_line_thickness;
1336                 dashed = True;
1337                 break;
1338         case SHADOW_DOUBLE_ETCHED_OUT:
1339                 num_separators = 2;
1340         case SHADOW_ETCHED_OUT:
1341                 top_gc = mw->menu.shadow_top_gc;
1342                 bottom_gc = mw->menu.shadow_bottom_gc;
1343                 top_line_thickness = mw->menu.shadow_thickness / 2;
1344                 bottom_line_thickness =
1345                     mw->menu.shadow_thickness - top_line_thickness;
1346                 break;
1347         case SHADOW_DOUBLE_ETCHED_IN:
1348                 num_separators = 2;
1349         case SHADOW_ETCHED_IN:
1350         default:
1351                 top_gc = mw->menu.shadow_bottom_gc;
1352                 bottom_gc = mw->menu.shadow_top_gc;
1353                 top_line_thickness = mw->menu.shadow_thickness / 2;
1354                 bottom_line_thickness =
1355                     mw->menu.shadow_thickness - top_line_thickness;
1356                 break;
1357         }
1358
1359         if (dashed) {
1360                 XGCValues values;
1361                 values.line_style = LineOnOffDash;
1362                 if (top_line_thickness > 0)
1363                         XChangeGC(dpy, top_gc, GCLineStyle, &values);
1364                 if (bottom_line_thickness > 0 && bottom_gc != top_gc)
1365                         XChangeGC(dpy, bottom_gc, GCLineStyle, &values);
1366         }
1367
1368         while (num_separators--) {
1369                 unsigned int i;
1370                 for (i = 0; i < top_line_thickness; i++)
1371                         XDrawLine(dpy, window, top_gc, x, y + i, x + width,
1372                                   y + i);
1373
1374                 for (i = 0; i < bottom_line_thickness; i++)
1375                         XDrawLine(dpy, window, bottom_gc,
1376                                   x, y + top_line_thickness + offset + i,
1377                                   x + width,
1378                                   y + top_line_thickness + offset + i);
1379                 y += (top_line_thickness + offset + bottom_line_thickness + 1);
1380         }
1381
1382         if (dashed) {
1383                 XGCValues values;
1384                 values.line_style = LineSolid;
1385                 if (top_line_thickness > 0)
1386                         XChangeGC(dpy, top_gc, GCLineStyle, &values);
1387                 if (bottom_line_thickness > 0 && bottom_gc != top_gc)
1388                         XChangeGC(dpy, bottom_gc, GCLineStyle, &values);
1389         }
1390 }
1391
1392 #define SLOPPY_TYPES 0          /* 0=off, 1=error check, 2=easy to please */
1393 #if SLOPPY_TYPES
1394 #if SLOPPY_TYPES < 2
1395
1396 static char *wv_types[] = {
1397         "UNSPECIFIED",
1398         "BUTTON",
1399         "TOGGLE",
1400         "RADIO",
1401         "TEXT",
1402         "SEPARATOR",
1403         "CASCADE",
1404         "PUSHRIGHT",
1405         "INCREMENTAL"
1406 };
1407
1408 static void print_widget_value(widget_value * wv, int just_one, int depth)
1409 {
1410         char d[200];
1411         int i;
1412         for (i = 0; i < depth; i++)
1413                 d[i] = ' ';
1414         d[depth] = 0;
1415         if (!wv) {
1416                 printf("%s(null widget value pointer)\n", d);
1417                 return;
1418         }
1419         printf("%stype:    %s\n", d, wv_types[wv->type]);
1420 #if 0
1421         printf("%sname:    %s\n", d, (wv->name ? wv->name : "(null)"));
1422 #else
1423         if (wv->name)
1424                 printf("%sname:    %s\n", d, wv->name);
1425 #endif
1426         if (wv->value)
1427                 printf("%svalue:   %s\n", d, wv->value);
1428         if (wv->key)
1429                 printf("%skey:     %s\n", d, wv->key);
1430         printf("%senabled: %d\n", d, wv->enabled);
1431         if (wv->contents) {
1432                 printf("\n%scontents: \n", d);
1433                 print_widget_value(wv->contents, 0, depth + 5);
1434         }
1435         if (!just_one && wv->next) {
1436                 printf("\n");
1437                 print_widget_value(wv->next, 0, depth);
1438         }
1439 }
1440 #endif                          /* SLOPPY_TYPES < 2 */
1441
1442 static Boolean all_dashes_p(char *s)
1443 {
1444         char *p;
1445         if (!s || s[0] == '\0')
1446                 return False;
1447         for (p = s; *p == '-'; p++) ;
1448
1449         if (*p == '!' || *p == '\0')
1450                 return True;
1451         return False;
1452 }
1453 #endif                          /* SLOPPY_TYPES */
1454
1455 static widget_value_type menu_item_type(widget_value * val)
1456 {
1457         if (val->type != UNSPECIFIED_TYPE)
1458                 return val->type;
1459 #if SLOPPY_TYPES
1460         else if (all_dashes_p(val->name))
1461                 return SEPARATOR_TYPE;
1462         else if (val->name && val->name[0] == '\0')     /* push right */
1463                 return PUSHRIGHT_TYPE;
1464         else if (val->contents) /* cascade */
1465                 return CASCADE_TYPE;
1466         else if (val->call_data)        /* push button */
1467                 return BUTTON_TYPE;
1468         else
1469                 return TEXT_TYPE;
1470 #else
1471         else
1472                 abort();
1473         return UNSPECIFIED_TYPE;        /* Not reached */
1474 #endif
1475 }
1476
1477 static void
1478 label_button_size(XlwMenuWidget mw,
1479                   widget_value * val,
1480                   Boolean in_menubar,
1481                   unsigned int *toggle_width,
1482                   unsigned int *label_width,
1483                   unsigned int *bindings_width, unsigned int *height)
1484 {
1485         *height = (mw->menu.font_ascent + mw->menu.font_descent +
1486                    2 * mw->menu.vertical_margin +
1487                    2 * mw->menu.shadow_thickness);
1488         /* no left column decoration */
1489         *toggle_width = mw->menu.horizontal_margin + mw->menu.shadow_thickness;
1490
1491         *label_width = string_width_u(mw, resource_widget_value(mw, val));
1492         *bindings_width =
1493             mw->menu.horizontal_margin + mw->menu.shadow_thickness;
1494 }
1495
1496 static void
1497 label_button_draw(XlwMenuWidget mw,
1498                   widget_value * val,
1499                   Boolean in_menubar,
1500                   Boolean highlighted,
1501                   Window window,
1502                   int x, int y,
1503                   unsigned int width,
1504                   unsigned int height,
1505                   unsigned int label_offset, unsigned int binding_tab)
1506 {
1507         int y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin;
1508         GC gc;
1509
1510         if (!label_offset)
1511                 label_offset =
1512                     mw->menu.shadow_thickness + mw->menu.horizontal_margin;
1513
1514         if (highlighted && (in_menubar || val->contents))
1515                 gc = mw->menu.highlight_gc;
1516         else if (in_menubar || val->contents)
1517                 gc = mw->menu.foreground_gc;
1518         else
1519                 gc = mw->menu.title_gc;
1520
1521         /*  Draw the label string. */
1522         string_draw_u(mw,
1523                       window,
1524                       x + label_offset, y + y_offset,
1525                       gc, resource_widget_value(mw, val));
1526 }
1527
1528 static void
1529 push_button_size(XlwMenuWidget mw,
1530                  widget_value * val,
1531                  Boolean in_menubar,
1532                  unsigned int *toggle_width,
1533                  unsigned int *label_width,
1534                  unsigned int *bindings_width, unsigned int *height)
1535 {
1536         /* inherit */
1537         label_button_size(mw, val, in_menubar,
1538                           toggle_width, label_width, bindings_width, height);
1539
1540         /* key bindings to display? */
1541         if (!in_menubar && val->key) {
1542                 int w;
1543 #ifdef NEED_MOTIF
1544                 XmString key =
1545                     XmStringCreateLtoR(val->key, XmSTRING_DEFAULT_CHARSET);
1546                 w = string_width(mw, key);
1547                 XmStringFree(key);
1548 #else
1549                 char *key = val->key;
1550                 w = string_width(mw, key);
1551 #endif
1552                 *bindings_width += w + mw->menu.column_spacing;
1553         }
1554 }
1555
1556 static void
1557 push_button_draw(XlwMenuWidget mw,
1558                  widget_value * val,
1559                  Boolean in_menubar,
1560                  Boolean highlighted,
1561                  Window window,
1562                  int x, int y,
1563                  unsigned int width,
1564                  unsigned int height,
1565                  unsigned int label_offset, unsigned int binding_offset)
1566 {
1567         int y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin;
1568         GC gc;
1569         shadow_type type;
1570         Boolean menu_pb = in_menubar && (menu_item_type(val) == BUTTON_TYPE);
1571
1572         /* Draw the label string. */
1573         if (!label_offset)
1574                 label_offset =
1575                     mw->menu.shadow_thickness + mw->menu.horizontal_margin;
1576
1577         if (highlighted) {
1578                 if (val->enabled)
1579                         gc = mw->menu.highlight_gc;
1580                 else
1581                         gc = mw->menu.inactive_gc;
1582         } else if (menu_pb) {
1583                 if (val->enabled)
1584                         gc = mw->menu.button_gc;
1585                 else
1586                         gc = mw->menu.inactive_button_gc;
1587         } else {
1588                 if (val->enabled)
1589                         gc = mw->menu.foreground_gc;
1590                 else
1591                         gc = mw->menu.inactive_gc;
1592         }
1593
1594         string_draw_u(mw,
1595                       window,
1596                       x + label_offset, y + y_offset,
1597                       gc, resource_widget_value(mw, val));
1598
1599         /* Draw the keybindings */
1600         if (val->key) {
1601                 if (!binding_offset) {
1602                         unsigned int s_width =
1603                             string_width(mw, resource_widget_value(mw, val));
1604                         binding_offset =
1605                             label_offset + s_width + mw->menu.shadow_thickness;
1606                 }
1607                 binding_draw(mw, window,
1608                              x + binding_offset + mw->menu.column_spacing,
1609                              y + y_offset, gc, val->key);
1610         }
1611
1612         /* Draw the shadow */
1613         if (menu_pb) {
1614                 if (highlighted)
1615                         type = SHADOW_OUT;
1616                 else
1617                         type =
1618                             (val->
1619                              selected ? SHADOW_ETCHED_OUT : SHADOW_ETCHED_IN);
1620         } else {
1621                 if (highlighted)
1622                         type = SHADOW_OUT;
1623                 else
1624                         type = SHADOW_BACKGROUND;
1625         }
1626
1627         shadow_draw(mw, window, x, y, width, height, type);
1628 }
1629
1630 static unsigned int arrow_decoration_height(XlwMenuWidget mw)
1631 {
1632         int result = (mw->menu.font_ascent + mw->menu.font_descent) / 2;
1633
1634         result += 2 * mw->menu.shadow_thickness;
1635
1636         if (result > (mw->menu.font_ascent + mw->menu.font_descent))
1637                 result = mw->menu.font_ascent + mw->menu.font_descent;
1638
1639         return result;
1640 }
1641
1642 static void
1643 cascade_button_size(XlwMenuWidget mw,
1644                     widget_value * val,
1645                     Boolean in_menubar,
1646                     unsigned int *toggle_width,
1647                     unsigned int *label_width,
1648                     unsigned int *arrow_width, unsigned int *height)
1649 {
1650         /* inherit */
1651         label_button_size(mw, val, in_menubar,
1652                           toggle_width, label_width, arrow_width, height);
1653         /* we have a pull aside arrow */
1654         if (!in_menubar) {
1655                 *arrow_width +=
1656                     arrow_decoration_height(mw) + mw->menu.column_spacing;
1657         }
1658 }
1659
1660 static void
1661 cascade_button_draw(XlwMenuWidget mw,
1662                     widget_value * val,
1663                     Boolean in_menubar,
1664                     Boolean highlighted,
1665                     Window window,
1666                     int x, int y,
1667                     unsigned int width,
1668                     unsigned int height,
1669                     unsigned int label_offset, unsigned int binding_offset)
1670 {
1671         shadow_type type;
1672
1673         /* Draw the label string. */
1674         label_button_draw(mw, val, in_menubar, highlighted,
1675                           window, x, y, width, height, label_offset,
1676                           binding_offset);
1677
1678         /* Draw the pull aside arrow */
1679         if (!in_menubar && val->contents) {
1680                 int y_offset;
1681                 unsigned int arrow_height = arrow_decoration_height(mw);
1682
1683                 y_offset =
1684                     mw->menu.shadow_thickness + mw->menu.vertical_margin +
1685                     (mw->menu.font_ascent + mw->menu.font_descent -
1686                      arrow_height) / 2;
1687
1688                 if (!binding_offset) {
1689                         unsigned int s_width =
1690                             string_width(mw, resource_widget_value(mw, val));
1691
1692                         if (!label_offset)
1693                                 label_offset = mw->menu.shadow_thickness +
1694                                     mw->menu.horizontal_margin;
1695
1696                         binding_offset =
1697                             label_offset + s_width + mw->menu.shadow_thickness;
1698                 }
1699
1700                 arrow_decoration_draw(mw,
1701                                       window,
1702                                       x + binding_offset +
1703                                       mw->menu.column_spacing, y + y_offset,
1704                                       arrow_height, highlighted);
1705         }
1706
1707         /* Draw the shadow */
1708         if (highlighted)
1709                 type = SHADOW_OUT;
1710         else
1711                 type = SHADOW_BACKGROUND;
1712
1713         shadow_draw(mw, window, x, y, width, height, type);
1714 }
1715
1716 static unsigned int toggle_decoration_height(XlwMenuWidget mw)
1717 {
1718         int rv;
1719         if (mw->menu.indicator_size > 0)
1720                 rv = mw->menu.indicator_size;
1721         else
1722                 rv = mw->menu.font_ascent;
1723
1724         if (rv > (mw->menu.font_ascent + mw->menu.font_descent))
1725                 rv = mw->menu.font_ascent + mw->menu.font_descent;
1726
1727         /* radio button can't be smaller than its border or a filling
1728            error will occur. */
1729         if (rv < 2 * mw->menu.shadow_thickness)
1730                 rv = 2 * mw->menu.shadow_thickness;
1731
1732         return rv;
1733 }
1734
1735 static void
1736 toggle_button_size(XlwMenuWidget mw,
1737                    widget_value * val,
1738                    Boolean in_menubar,
1739                    unsigned int *toggle_width,
1740                    unsigned int *label_width,
1741                    unsigned int *bindings_width, unsigned int *height)
1742 {
1743         /* inherit */
1744         push_button_size(mw, val, in_menubar,
1745                          toggle_width, label_width, bindings_width, height);
1746         /* we have a toggle */
1747         *toggle_width += toggle_decoration_height(mw) + mw->menu.column_spacing;
1748 }
1749
1750 static void
1751 toggle_button_draw(XlwMenuWidget mw,
1752                    widget_value * val,
1753                    Boolean in_menubar,
1754                    Boolean highlighted,
1755                    Window window,
1756                    int x, int y,
1757                    unsigned int width,
1758                    unsigned int height,
1759                    unsigned int label_tab, unsigned int binding_tab)
1760 {
1761         int x_offset;
1762         int y_offset;
1763         unsigned int t_height = toggle_decoration_height(mw);
1764
1765         /* Draw a toggle. */
1766         x_offset = mw->menu.shadow_thickness + mw->menu.horizontal_margin;
1767         y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin;
1768         y_offset +=
1769             (mw->menu.font_ascent + mw->menu.font_descent - t_height) / 2;
1770
1771         toggle_decoration_draw(mw, window, x + x_offset, y + y_offset,
1772                                t_height, val->selected);
1773
1774         /* Draw the pushbutton parts. */
1775         push_button_draw(mw, val, in_menubar, highlighted, window, x, y, width,
1776                          height, label_tab, binding_tab);
1777 }
1778
1779 static unsigned int radio_decoration_height(XlwMenuWidget mw)
1780 {
1781         return toggle_decoration_height(mw);
1782 }
1783
1784 static void
1785 radio_button_draw(XlwMenuWidget mw,
1786                   widget_value * val,
1787                   Boolean in_menubar,
1788                   Boolean highlighted,
1789                   Window window,
1790                   int x, int y,
1791                   unsigned int width,
1792                   unsigned int height,
1793                   unsigned int label_tab, unsigned int binding_tab)
1794 {
1795         int x_offset;
1796         int y_offset;
1797         unsigned int r_height = radio_decoration_height(mw);
1798
1799         /* Draw a toggle. */
1800         x_offset = mw->menu.shadow_thickness + mw->menu.horizontal_margin;
1801         y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin;
1802         y_offset +=
1803             (mw->menu.font_ascent + mw->menu.font_descent - r_height) / 2;
1804
1805         radio_decoration_draw(mw, window, x + x_offset, y + y_offset, r_height,
1806                               val->selected);
1807
1808         /* Draw the pushbutton parts. */
1809         push_button_draw(mw, val, in_menubar, highlighted, window, x, y, width,
1810                          height, label_tab, binding_tab);
1811 }
1812
1813 static struct _shadow_names {
1814         const char *name;
1815         shadow_type type;
1816 } shadow_names[] = {
1817         /* Motif */
1818         {
1819         "singleLine", SHADOW_SINGLE_LINE}, {
1820         "doubleLine", SHADOW_DOUBLE_LINE}, {
1821         "singleDashedLine", SHADOW_SINGLE_DASHED_LINE}, {
1822         "doubleDashedLine", SHADOW_DOUBLE_DASHED_LINE}, {
1823         "noLine", SHADOW_NO_LINE}, {
1824         "shadowEtchedIn", SHADOW_ETCHED_IN}, {
1825         "shadowEtchedOut", SHADOW_ETCHED_OUT}, {
1826         "shadowEtchedInDash", SHADOW_ETCHED_IN_DASH}, {
1827         "shadowEtchedOutDash", SHADOW_ETCHED_OUT_DASH},
1828             /* non-Motif */
1829         {
1830         "shadowDoubleEtchedIn", SHADOW_DOUBLE_ETCHED_IN}, {
1831         "shadowDoubleEtchedOut", SHADOW_DOUBLE_ETCHED_OUT}, {
1832         "shadowDoubleEtchedInDash", SHADOW_DOUBLE_ETCHED_IN_DASH}, {
1833         "shadowDoubleEtchedOutDash", SHADOW_DOUBLE_ETCHED_OUT_DASH}
1834 };
1835
1836 static shadow_type separator_type(char *name)
1837 {
1838         if (name) {
1839                 int i;
1840                 for (i = 0; i < (int)(XtNumber(shadow_names)); i++) {
1841                         if (strcmp(name, shadow_names[i].name) == 0)
1842                                 return shadow_names[i].type;
1843                 }
1844         }
1845         return SHADOW_BACKGROUND;
1846 }
1847
1848 static unsigned int
1849 separator_decoration_height(XlwMenuWidget mw, widget_value * val)
1850 {
1851         shadow_type st = separator_type(val->value);
1852         switch ((unsigned int)st) {
1853         case SHADOW_NO_LINE:
1854         case SHADOW_SINGLE_LINE:
1855         case SHADOW_SINGLE_DASHED_LINE:
1856                 return 1;
1857         case SHADOW_DOUBLE_LINE:
1858         case SHADOW_DOUBLE_DASHED_LINE:
1859                 return 3;
1860         case SHADOW_DOUBLE_ETCHED_OUT:
1861         case SHADOW_DOUBLE_ETCHED_IN:
1862         case SHADOW_DOUBLE_ETCHED_OUT_DASH:
1863         case SHADOW_DOUBLE_ETCHED_IN_DASH:
1864                 return (1 + 2 * mw->menu.shadow_thickness);
1865         case SHADOW_ETCHED_OUT:
1866         case SHADOW_ETCHED_IN:
1867         default:
1868                 return mw->menu.shadow_thickness;
1869         }
1870 }
1871
1872 static void
1873 separator_size(XlwMenuWidget mw,
1874                widget_value * val,
1875                Boolean in_menubar,
1876                unsigned int *toggle_width,
1877                unsigned int *label_width,
1878                unsigned int *rest_width, unsigned int *height)
1879 {
1880         *height = separator_decoration_height(mw, val);
1881         *label_width = 1;
1882         *toggle_width = *rest_width = 0;
1883 }
1884
1885 static void
1886 separator_draw(XlwMenuWidget mw,
1887                widget_value * val,
1888                Boolean in_menubar,
1889                Boolean highlighted,
1890                Window window,
1891                int x, int y,
1892                unsigned int width,
1893                unsigned int height,
1894                unsigned int label_tab, unsigned int binding_tab)
1895 {
1896         unsigned int sep_width;
1897
1898         if (in_menubar)
1899                 sep_width = height;
1900         else
1901                 sep_width = width;
1902
1903         separator_decoration_draw(mw,
1904                                   window,
1905                                   x,
1906                                   y,
1907                                   sep_width,
1908                                   in_menubar, separator_type(val->value));
1909 }
1910
1911 static void
1912 pushright_size(XlwMenuWidget mw,
1913                widget_value * val,
1914                Boolean in_menubar,
1915                unsigned int *toggle_width,
1916                unsigned int *label_width,
1917                unsigned int *rest_width, unsigned int *height)
1918 {
1919         *height = *label_width = *toggle_width = *rest_width = 0;
1920 }
1921
1922 static void
1923 size_menu_item(XlwMenuWidget mw,
1924                widget_value * val,
1925                int horizontal,
1926                unsigned int *toggle_width,
1927                unsigned int *label_width,
1928                unsigned int *rest_width, unsigned int *height)
1929 {
1930         void (*function_ptr) (XlwMenuWidget _mw,
1931                               widget_value * _val,
1932                               Boolean _in_menubar,
1933                               unsigned int *_toggle_width,
1934                               unsigned int *_label_width,
1935                               unsigned int *_rest_width, unsigned int *_height);
1936         widget_value_type mt = menu_item_type(val);
1937
1938         switch ((unsigned int)mt) {
1939         case TOGGLE_TYPE:
1940         case RADIO_TYPE:
1941                 function_ptr = toggle_button_size;
1942                 break;
1943         case SEPARATOR_TYPE:
1944                 function_ptr = separator_size;
1945                 break;
1946         case INCREMENTAL_TYPE:
1947         case CASCADE_TYPE:
1948                 function_ptr = cascade_button_size;
1949                 break;
1950         case BUTTON_TYPE:
1951                 function_ptr = push_button_size;
1952                 break;
1953         case PUSHRIGHT_TYPE:
1954                 function_ptr = pushright_size;
1955                 break;
1956         case TEXT_TYPE:
1957         default:
1958                 function_ptr = label_button_size;
1959                 break;
1960         }
1961
1962         (*function_ptr) (mw,
1963                          val,
1964                          horizontal,
1965                          toggle_width, label_width, rest_width, height);
1966 }
1967
1968 static void
1969 display_menu_item(XlwMenuWidget mw,
1970                   widget_value * val,
1971                   window_state * ws,
1972                   XPoint * where,
1973                   Boolean highlighted, Boolean horizontal, Boolean just_compute)
1974 {
1975         int x = where->x /* + mw->menu.shadow_thickness */ ;
1976         int y = where->y /* + mw->menu.shadow_thickness */ ;
1977         unsigned int toggle_width;
1978         unsigned int label_width;
1979         unsigned int binding_width;
1980         unsigned int width;
1981         unsigned int height;
1982         unsigned int label_tab;
1983         unsigned int binding_tab;
1984         void (*function_ptr) (XlwMenuWidget _mw,
1985                               widget_value * _val,
1986                               Boolean _in_menubar,
1987                               Boolean _highlighted,
1988                               Window _window,
1989                               int _x, int _y,
1990                               unsigned int _width,
1991                               unsigned int _height,
1992                               unsigned int _label_tab,
1993                               unsigned int _binding_tab);
1994         widget_value_type mt;
1995
1996         size_menu_item(mw, val, horizontal,
1997                        &toggle_width, &label_width, &binding_width, &height);
1998
1999         if (horizontal) {
2000                 width = toggle_width + label_width + binding_width;
2001                 height = ws->height - 2 * mw->menu.shadow_thickness;
2002         } else {
2003                 width = ws->width - 2 * mw->menu.shadow_thickness;
2004                 toggle_width = ws->toggle_width;
2005                 label_width = ws->label_width;
2006         }
2007
2008         where->x += width;
2009         where->y += height;
2010
2011         if (just_compute)
2012                 return;
2013
2014         label_tab = toggle_width;
2015         binding_tab = toggle_width + label_width;
2016
2017         mt = menu_item_type(val);
2018         switch ((unsigned int)mt) {
2019         case TOGGLE_TYPE:
2020                 function_ptr = toggle_button_draw;
2021                 break;
2022         case RADIO_TYPE:
2023                 function_ptr = radio_button_draw;
2024                 break;
2025         case SEPARATOR_TYPE:
2026                 function_ptr = separator_draw;
2027                 break;
2028         case INCREMENTAL_TYPE:
2029         case CASCADE_TYPE:
2030                 function_ptr = cascade_button_draw;
2031                 break;
2032         case BUTTON_TYPE:
2033                 function_ptr = push_button_draw;
2034                 break;
2035         case TEXT_TYPE:
2036                 function_ptr = label_button_draw;
2037                 break;
2038         default:                /* do no drawing */
2039                 return;
2040         }
2041
2042         (*function_ptr) (mw,
2043                          val,
2044                          horizontal,
2045                          highlighted,
2046                          ws->window,
2047                          x, y, width, height, label_tab, binding_tab);
2048 }
2049
2050 static void size_menu(XlwMenuWidget mw, int level)
2051 {
2052         unsigned int toggle_width;
2053         unsigned int label_width;
2054         unsigned int rest_width;
2055         unsigned int height;
2056         unsigned int max_toggle_width = 0;
2057         unsigned int max_label_width = 0;
2058         unsigned int max_rest_width = 0;
2059         unsigned int max_height = 0;
2060         int horizontal_p = mw->menu.horizontal && (level == 0);
2061         widget_value *val;
2062         window_state *ws;
2063
2064         if (level >= mw->menu.old_depth)
2065                 abort();
2066
2067         ws = &mw->menu.windows[level];
2068
2069         for (val = mw->menu.old_stack[level]->contents; val; val = val->next) {
2070                 size_menu_item(mw,
2071                                val,
2072                                horizontal_p,
2073                                &toggle_width,
2074                                &label_width, &rest_width, &height);
2075                 if (horizontal_p) {
2076                         max_label_width +=
2077                             toggle_width + label_width + rest_width;
2078                         if (height > max_height)
2079                                 max_height = height;
2080                 } else {
2081                         if (max_toggle_width < toggle_width)
2082                                 max_toggle_width = toggle_width;
2083                         if (max_label_width < label_width)
2084                                 max_label_width = label_width;
2085                         if (max_rest_width < rest_width)
2086                                 max_rest_width = rest_width;
2087                         max_height += height;
2088                 }
2089         }
2090
2091         ws->height = max_height;
2092         ws->width = max_label_width + max_rest_width + max_toggle_width;
2093         ws->toggle_width = max_toggle_width;
2094         ws->label_width = max_label_width;
2095
2096         ws->width += 2 * mw->menu.shadow_thickness;
2097         ws->height += 2 * mw->menu.shadow_thickness;
2098 }
2099
2100 static void
2101 display_menu(XlwMenuWidget mw, int level, Boolean just_compute_p,
2102              XPoint * highlighted_pos, XPoint * hit, widget_value ** hit_return,
2103              widget_value * this, widget_value * that)
2104 {
2105         widget_value *val;
2106         widget_value *following_item;
2107         window_state *ws;
2108         XPoint where;
2109         int horizontal_p = mw->menu.horizontal && (level == 0);
2110         int highlighted_p;
2111         int just_compute_this_one_p;
2112
2113         if (level >= mw->menu.old_depth)
2114                 abort();
2115
2116         if (level < mw->menu.old_depth - 1)
2117                 following_item = mw->menu.old_stack[level + 1];
2118         else {
2119                 if (lw_menu_accelerate
2120                     && level == mw->menu.old_depth - 1
2121                     && mw->menu.old_stack[level]->type == CASCADE_TYPE)
2122                         just_compute_p = True;
2123                 following_item = NULL;
2124         }
2125
2126 #if SLOPPY_TYPES == 1
2127         puts("===================================================================");
2128         print_widget_value(following_item, 1, 0);
2129 #endif
2130
2131         if (hit)
2132                 *hit_return = NULL;
2133
2134         where.x = mw->menu.shadow_thickness;
2135         where.y = mw->menu.shadow_thickness;
2136
2137         ws = &mw->menu.windows[level];
2138         for (val = mw->menu.old_stack[level]->contents; val; val = val->next) {
2139                 XPoint start;
2140
2141                 highlighted_p = (val == following_item);
2142                 /* If this is the partition (the dummy item which says that menus
2143                    after this should be flushright) then figure out how big the
2144                    following items are.  This means we walk down the tail of the
2145                    list twice, but that's no big deal - it's short.
2146                  */
2147                 if (horizontal_p && (menu_item_type(val) == PUSHRIGHT_TYPE)) {
2148                         widget_value *rest;
2149                         XPoint flushright_size;
2150                         int new_x;
2151                         flushright_size.x = 0;
2152                         flushright_size.y = 0;
2153                         for (rest = val; rest; rest = rest->next)
2154                                 display_menu_item(mw, rest, ws,
2155                                                   &flushright_size,
2156                                                   highlighted_p, horizontal_p,
2157                                                   True);
2158                         new_x =
2159                             ws->width - (flushright_size.x +
2160                                          mw->menu.shadow_thickness);
2161                         if (new_x > where.x)
2162                                 where.x = new_x;
2163                         /* We know what we need; don't draw this item. */
2164                         continue;
2165                 }
2166
2167                 if (highlighted_p && highlighted_pos) {
2168                         if (horizontal_p)
2169                                 highlighted_pos->x = where.x;
2170                         else
2171                                 highlighted_pos->y = where.y;
2172                 }
2173
2174                 just_compute_this_one_p =
2175                     just_compute_p || ((this || that) && val != this
2176                                        && val != that);
2177
2178                 start.x = where.x;
2179                 start.y = where.y;
2180                 display_menu_item(mw, val, ws, &where, highlighted_p,
2181                                   horizontal_p, just_compute_this_one_p);
2182
2183                 if (highlighted_p && highlighted_pos) {
2184                         if (horizontal_p)
2185                                 highlighted_pos->y = ws->height;
2186                         else
2187                                 highlighted_pos->x = ws->width;
2188                 }
2189
2190                 if (hit && !*hit_return) {
2191                         if (horizontal_p && hit->x > start.x
2192                             && hit->x <= where.x)
2193                                 *hit_return = val;
2194                         else if (!horizontal_p && hit->y > start.y
2195                                  && hit->y <= where.y)
2196                                 *hit_return = val;
2197                 }
2198
2199                 if (horizontal_p)
2200                         where.y = mw->menu.shadow_thickness;
2201                 else
2202                         where.x = mw->menu.shadow_thickness;
2203         }
2204
2205         /* Draw slab edges around menu */
2206         if (!just_compute_p)
2207                 shadow_draw(mw, ws->window, 0, 0, ws->width, ws->height,
2208                             SHADOW_OUT);
2209 }
2210 \f
2211 /* Motion code */
2212 static void set_new_state(XlwMenuWidget mw, widget_value * val, int level)
2213 {
2214         int i;
2215
2216         mw->menu.new_depth = 0;
2217         for (i = 0; i < level; i++)
2218                 push_new_stack(mw, mw->menu.old_stack[i]);
2219         if (val)
2220                 push_new_stack(mw, val);
2221 }
2222
2223 static void make_windows_if_needed(XlwMenuWidget mw, int n)
2224 {
2225         int i;
2226         int start_at;
2227         XSetWindowAttributes xswa;
2228         Widget p;
2229         unsigned long mask;
2230         int depth;
2231         Visual *visual;
2232         window_state *windows;
2233         Window root;
2234
2235         if (mw->menu.windows_length >= n)
2236                 return;
2237
2238         root = RootWindowOfScreen(XtScreen(mw));
2239         /* grab the visual and depth from the nearest shell ancestor */
2240         visual = CopyFromParent;
2241         depth = CopyFromParent;
2242         p = XtParent(mw);
2243         while (visual == CopyFromParent && p) {
2244                 if (XtIsShell(p)) {
2245                         visual = ((ShellWidget) p)->shell.visual;
2246                         depth = p->core.depth;
2247                 }
2248                 p = XtParent(p);
2249         }
2250
2251         xswa.save_under = True;
2252         xswa.override_redirect = True;
2253         xswa.background_pixel = mw->core.background_pixel;
2254         xswa.border_pixel = mw->core.border_pixel;
2255         xswa.event_mask = (ExposureMask | ButtonMotionMask
2256                            | ButtonReleaseMask | ButtonPressMask);
2257         xswa.cursor = mw->menu.cursor_shape;
2258         xswa.colormap = mw->core.colormap;
2259         mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
2260             | CWEventMask | CWCursor | CWColormap;
2261
2262         if (mw->menu.use_backing_store) {
2263                 xswa.backing_store = Always;
2264                 mask |= CWBackingStore;
2265         }
2266
2267         if (!mw->menu.windows) {
2268                 mw->menu.windows =
2269                     (window_state *) XtMalloc(n * sizeof(window_state));
2270                 start_at = 0;
2271         } else {
2272                 mw->menu.windows =
2273                     (window_state *) XtRealloc((char *)mw->menu.windows,
2274                                                n * sizeof(window_state));
2275                 start_at = mw->menu.windows_length;
2276         }
2277         mw->menu.windows_length = n;
2278
2279         windows = mw->menu.windows;
2280
2281         for (i = start_at; i < n; i++) {
2282                 windows[i].x = 0;
2283                 windows[i].y = 0;
2284                 windows[i].width = 1;
2285                 windows[i].height = 1;
2286                 windows[i].window =
2287                     XCreateWindow(XtDisplay(mw),
2288                                   root,
2289                                   0, 0, 1, 1,
2290                                   0, depth, CopyFromParent, visual, mask,
2291                                   &xswa);
2292         }
2293 }
2294
2295 /* Make the window fit in the screen */
2296 static void
2297 fit_to_screen(XlwMenuWidget mw, window_state * ws, window_state * previous_ws,
2298               Boolean horizontal_p)
2299 {
2300         int screen_width = WidthOfScreen(XtScreen(mw));
2301         int screen_height = HeightOfScreen(XtScreen(mw));
2302
2303         if (ws->x < 0)
2304                 ws->x = 0;
2305         else if ((int)(ws->x + ws->width) > screen_width) {
2306                 if (!horizontal_p)
2307                         ws->x = previous_ws->x - ws->width;
2308                 else {
2309                         ws->x = screen_width - ws->width;
2310
2311                         /* This check is to make sure we cut off the right side
2312                            instead of the left side if the menu is wider than the
2313                            screen. */
2314                         if (ws->x < 0)
2315                                 ws->x = 0;
2316                 }
2317         }
2318         if (ws->y < 0)
2319                 ws->y = 0;
2320         else if ((int)(ws->y + ws->height) > screen_height) {
2321                 if (horizontal_p) {
2322                         /* A pulldown must either be entirely above or below the menubar.
2323                            If we're here, the pulldown doesn't fit below the menubar, so
2324                            let's determine if it will fit above the menubar.
2325                            Only put it above if there is more room above than below.
2326                            Note shadow_thickness offset to allow for slab surround.
2327                          */
2328                         if (ws->y > (screen_height / 2))
2329                                 ws->y =
2330                                     previous_ws->y - ws->height +
2331                                     mw->menu.shadow_thickness;
2332                 } else {
2333                         ws->y = screen_height - ws->height;
2334                         /* if it's taller than the screen, display the topmost part
2335                            that will fit, beginning at the top of the screen. */
2336                         if (ws->y < 0)
2337                                 ws->y = 0;
2338                 }
2339         }
2340 }
2341
2342 /* Updates old_stack from new_stack and redisplays. */
2343 static void remap_menubar(XlwMenuWidget mw)
2344 {
2345         int i;
2346         int last_same;
2347         XPoint selection_position;
2348         int old_depth = mw->menu.old_depth;
2349         int new_depth = mw->menu.new_depth;
2350         widget_value **old_stack;
2351         widget_value **new_stack;
2352         window_state *windows;
2353         widget_value *old_selection;
2354         widget_value *new_selection;
2355
2356         /* Check that enough windows and old_stack are ready. */
2357         make_windows_if_needed(mw, new_depth);
2358         make_old_stack_space(mw, new_depth);
2359         windows = mw->menu.windows;
2360         old_stack = mw->menu.old_stack;
2361         new_stack = mw->menu.new_stack;
2362
2363         /* compute the last identical different entry */
2364         for (i = 1; i < old_depth && i < new_depth; i++)
2365                 if (old_stack[i] != new_stack[i])
2366                         break;
2367         last_same = i - 1;
2368
2369         if (lw_menu_accelerate
2370             && last_same
2371             && last_same == old_depth - 1 && old_stack[last_same]->contents)
2372                 last_same--;
2373
2374         /* Memorize the previously selected item to be able to refresh it */
2375         old_selection =
2376             last_same + 1 < old_depth ? old_stack[last_same + 1] : NULL;
2377         new_selection =
2378             last_same + 1 < new_depth ? new_stack[last_same + 1] : NULL;
2379
2380         /* updates old_state from new_state.  It has to be done now because
2381            display_menu (called below) uses the old_stack to know what to display. */
2382         for (i = last_same + 1; i < new_depth; i++)
2383                 old_stack[i] = new_stack[i];
2384
2385         mw->menu.old_depth = new_depth;
2386
2387         /* refresh the last selection */
2388         selection_position.x = 0;
2389         selection_position.y = 0;
2390         display_menu(mw, last_same, new_selection == old_selection,
2391                      &selection_position, NULL, NULL, old_selection,
2392                      new_selection);
2393
2394         /* Now popup the new menus */
2395         for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++) {
2396                 window_state *previous_ws = &windows[i - 1];
2397                 window_state *ws = &windows[i];
2398
2399                 if (lw_menu_accelerate && i == new_depth - 1)
2400                         break;
2401
2402                 ws->x = previous_ws->x + selection_position.x;
2403                 ws->y = previous_ws->y + selection_position.y;
2404
2405                 /* take into account the slab around the new menu */
2406                 ws->y -= mw->menu.shadow_thickness;
2407
2408                 {
2409                         widget_value *val = mw->menu.old_stack[i];
2410                         if (val->contents->type == INCREMENTAL_TYPE) {
2411                                 /* okay, we're now doing a lisp callback to incrementally generate
2412                                    more of the menu. */
2413                                 XtCallCallbackList((Widget) mw,
2414                                                    mw->menu.open,
2415                                                    (XtPointer) val->contents);
2416                         }
2417                 }
2418
2419                 size_menu(mw, i);
2420
2421                 fit_to_screen(mw, ws, previous_ws, mw->menu.horizontal
2422                               && i == 1);
2423
2424                 XClearWindow(XtDisplay(mw), ws->window);
2425                 XMoveResizeWindow(XtDisplay(mw), ws->window, ws->x, ws->y,
2426                                   ws->width, ws->height);
2427                 XMapRaised(XtDisplay(mw), ws->window);
2428                 display_menu(mw, i, False, &selection_position, NULL, NULL,
2429                              NULL, NULL);
2430         }
2431
2432         /* unmap the menus that popped down */
2433
2434         last_same = new_depth;
2435         if (lw_menu_accelerate
2436             && last_same > 1 && new_stack[last_same - 1]->contents)
2437                 last_same--;
2438
2439         for (i = last_same - 1; i < old_depth; i++)
2440                 if (i >= last_same || !new_stack[i]->contents)
2441                         XUnmapWindow(XtDisplay(mw), windows[i].window);
2442 }
2443
2444 static Boolean
2445 motion_event_is_in_menu(XlwMenuWidget mw, XMotionEvent * ev, int level,
2446                         XPoint * relative_pos)
2447 {
2448         window_state *ws = &mw->menu.windows[level];
2449         int x = level == 0 ? ws->x : ws->x + mw->menu.shadow_thickness;
2450         int y = level == 0 ? ws->y : ws->y + mw->menu.shadow_thickness;
2451         relative_pos->x = ev->x_root - x;
2452         relative_pos->y = ev->y_root - y;
2453         return (x < ev->x_root && ev->x_root < (int)(x + ws->width) &&
2454                 y < ev->y_root && ev->y_root < (int)(y + ws->height));
2455 }
2456
2457 static Boolean
2458 map_event_to_widget_value(XlwMenuWidget mw, XMotionEvent * ev,
2459                           widget_value ** val_ptr, int *level,
2460                           Boolean * inside_menu)
2461 {
2462         int i;
2463         XPoint relative_pos;
2464         window_state *ws;
2465
2466         *val_ptr = NULL;
2467         *inside_menu = False;
2468
2469         /* Find the window */
2470 #if 1
2471         for (i = mw->menu.old_depth - 1; i >= 0; i--)
2472 #else
2473         for (i = 0; i <= mw->menu.old_depth - 1; i++)
2474 #endif
2475         {
2476                 ws = &mw->menu.windows[i];
2477                 if (ws && motion_event_is_in_menu(mw, ev, i, &relative_pos)) {
2478                         *inside_menu = True;    /* special logic for menubar below... */
2479                         if ((ev->type == ButtonPress) || (ev->state != 0)) {
2480                                 display_menu(mw, i, True, NULL, &relative_pos,
2481                                              val_ptr, NULL, NULL);
2482                                 if (*val_ptr) {
2483                                         *level = i + 1;
2484                                         *inside_menu = True;
2485                                         return True;
2486                                 } else if (mw->menu.horizontal || i == 0) {
2487                                         /* if we're clicking on empty part of the menubar, then
2488                                            unpost the stay-up menu */
2489                                         *inside_menu = False;
2490                                 }
2491                         }
2492                 }
2493         }
2494         return False;
2495 }
2496 \f
2497 /* Procedures */
2498 static void make_drawing_gcs(XlwMenuWidget mw)
2499 {
2500         XGCValues xgcv;
2501         unsigned long flags = (GCFont | GCForeground | GCBackground);
2502
2503 #ifdef NEED_MOTIF
2504         xgcv.font = default_font_of_font_list(mw->menu.font_list)->fid;
2505 #else
2506         xgcv.font = mw->menu.font->fid;
2507 #endif
2508
2509         xgcv.foreground = mw->core.background_pixel;
2510         xgcv.background = mw->menu.foreground;
2511         mw->menu.background_gc = XtGetGC((Widget) mw, flags, &xgcv);
2512
2513         xgcv.foreground = mw->menu.foreground;
2514         xgcv.background = mw->core.background_pixel;
2515         mw->menu.foreground_gc = XtGetGC((Widget) mw, flags, &xgcv);
2516
2517         if (mw->menu.select_color != (Pixel) - 1) {
2518                 xgcv.foreground = mw->menu.select_color;
2519         } else {
2520                 Display *dpy = XtDisplay(mw);
2521                 if (CellsOfScreen(DefaultScreenOfDisplay(dpy)) <= 2) {  /* mono */
2522                         xgcv.foreground = mw->menu.foreground;
2523                 } else {        /* color */
2524                         XColor xcolor;
2525                         Colormap cmap = mw->core.colormap;
2526                         xcolor.pixel = mw->core.background_pixel;
2527                         XQueryColor(dpy, cmap, &xcolor);
2528                         xcolor.red = (xcolor.red * 17) / 20;
2529                         xcolor.green = (xcolor.green * 17) / 20;
2530                         xcolor.blue = (xcolor.blue * 17) / 20;
2531                         if (allocate_nearest_color(dpy, cmap, &xcolor))
2532                                 xgcv.foreground = xcolor.pixel;
2533                 }
2534         }
2535         xgcv.background = mw->core.background_pixel;
2536         mw->menu.select_gc = XtGetGC((Widget) mw, flags, &xgcv);
2537
2538         xgcv.foreground = mw->menu.foreground;
2539         xgcv.background = mw->core.background_pixel;
2540         xgcv.fill_style = FillStippled;
2541         xgcv.stipple = mw->menu.gray_pixmap;
2542         mw->menu.inactive_gc = XtGetGC((Widget) mw,
2543                                        (flags | GCFillStyle | GCStipple),
2544                                        &xgcv);
2545
2546         xgcv.foreground = mw->menu.highlight_foreground;
2547         xgcv.background = mw->core.background_pixel;
2548         mw->menu.highlight_gc = XtGetGC((Widget) mw, flags, &xgcv);
2549
2550         xgcv.foreground = mw->menu.title_foreground;
2551         xgcv.background = mw->core.background_pixel;
2552         mw->menu.title_gc = XtGetGC((Widget) mw, flags, &xgcv);
2553
2554         xgcv.foreground = mw->menu.button_foreground;
2555         xgcv.background = mw->core.background_pixel;
2556         mw->menu.button_gc = XtGetGC((Widget) mw, flags, &xgcv);
2557
2558         xgcv.fill_style = FillStippled;
2559         xgcv.stipple = mw->menu.gray_pixmap;
2560         mw->menu.inactive_button_gc = XtGetGC((Widget) mw,
2561                                               (flags | GCFillStyle | GCStipple),
2562                                               &xgcv);
2563 }
2564
2565 static void release_drawing_gcs(XlwMenuWidget mw)
2566 {
2567         XtReleaseGC((Widget) mw, mw->menu.foreground_gc);
2568         XtReleaseGC((Widget) mw, mw->menu.button_gc);
2569         XtReleaseGC((Widget) mw, mw->menu.highlight_gc);
2570         XtReleaseGC((Widget) mw, mw->menu.title_gc);
2571         XtReleaseGC((Widget) mw, mw->menu.inactive_gc);
2572         XtReleaseGC((Widget) mw, mw->menu.inactive_button_gc);
2573         XtReleaseGC((Widget) mw, mw->menu.background_gc);
2574         XtReleaseGC((Widget) mw, mw->menu.select_gc);
2575         /* let's get some segvs if we try to use these... */
2576         mw->menu.foreground_gc = (GC) - 1;
2577         mw->menu.button_gc = (GC) - 1;
2578         mw->menu.highlight_gc = (GC) - 1;
2579         mw->menu.title_gc = (GC) - 1;
2580         mw->menu.inactive_gc = (GC) - 1;
2581         mw->menu.inactive_button_gc = (GC) - 1;
2582         mw->menu.background_gc = (GC) - 1;
2583         mw->menu.select_gc = (GC) - 1;
2584 }
2585
2586 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
2587                    ? ((unsigned long) (x)) : ((unsigned long) (y)))
2588
2589 static void make_shadow_gcs(XlwMenuWidget mw)
2590 {
2591         XGCValues xgcv;
2592         unsigned long pm = 0;
2593         Display *dpy = XtDisplay((Widget) mw);
2594         Colormap cmap = mw->core.colormap;
2595         XColor topc, botc;
2596         int top_frobbed = 0, bottom_frobbed = 0;
2597
2598         if (mw->menu.top_shadow_color == (Pixel) (-1))
2599                 mw->menu.top_shadow_color = mw->core.background_pixel;
2600         if (mw->menu.bottom_shadow_color == (Pixel) (-1))
2601                 mw->menu.bottom_shadow_color = mw->menu.foreground;
2602
2603         if (mw->menu.top_shadow_color == mw->core.background_pixel ||
2604             mw->menu.top_shadow_color == mw->menu.foreground) {
2605                 topc.pixel = mw->core.background_pixel;
2606                 XQueryColor(dpy, cmap, &topc);
2607                 /* don't overflow/wrap! */
2608                 topc.red = MINL(65535, topc.red * 1.2);
2609                 topc.green = MINL(65535, topc.green * 1.2);
2610                 topc.blue = MINL(65535, topc.blue * 1.2);
2611                 if (allocate_nearest_color(dpy, cmap, &topc)) {
2612                         if (topc.pixel == mw->core.background_pixel) {
2613                                 XFreeColors(dpy, cmap, &topc.pixel, 1, 0);
2614                                 topc.red = MINL(65535, topc.red + 0x8000);
2615                                 topc.green = MINL(65535, topc.green + 0x8000);
2616                                 topc.blue = MINL(65535, topc.blue + 0x8000);
2617                                 if (allocate_nearest_color(dpy, cmap, &topc)) {
2618                                         mw->menu.top_shadow_color = topc.pixel;
2619                                 }
2620                         } else {
2621                                 mw->menu.top_shadow_color = topc.pixel;
2622                         }
2623
2624                         top_frobbed = 1;
2625                 }
2626         }
2627         if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
2628             mw->menu.bottom_shadow_color == mw->core.background_pixel) {
2629                 botc.pixel = mw->core.background_pixel;
2630                 XQueryColor(dpy, cmap, &botc);
2631                 botc.red = (botc.red * 3) / 5;
2632                 botc.green = (botc.green * 3) / 5;
2633                 botc.blue = (botc.blue * 3) / 5;
2634                 if (allocate_nearest_color(dpy, cmap, &botc)) {
2635                         if (botc.pixel == mw->core.background_pixel) {
2636                                 XFreeColors(dpy, cmap, &botc.pixel, 1, 0);
2637                                 botc.red = MINL(65535, botc.red + 0x4000);
2638                                 botc.green = MINL(65535, botc.green + 0x4000);
2639                                 botc.blue = MINL(65535, botc.blue + 0x4000);
2640                                 if (allocate_nearest_color(dpy, cmap, &botc)) {
2641                                         mw->menu.bottom_shadow_color =
2642                                             botc.pixel;
2643                                 }
2644                         } else {
2645                                 mw->menu.bottom_shadow_color = botc.pixel;
2646                         }
2647
2648                         bottom_frobbed = 1;
2649                 }
2650         }
2651
2652         if (top_frobbed && bottom_frobbed) {
2653                 int top_avg =
2654                     ((topc.red / 3) + (topc.green / 3) + (topc.blue / 3));
2655                 int bot_avg =
2656                     ((botc.red / 3) + (botc.green / 3) + (botc.blue / 3));
2657                 if (bot_avg > top_avg) {
2658                         Pixel tmp = mw->menu.top_shadow_color;
2659                         mw->menu.top_shadow_color =
2660                             mw->menu.bottom_shadow_color;
2661                         mw->menu.bottom_shadow_color = tmp;
2662                 } else if (topc.pixel == botc.pixel) {
2663                         if (botc.pixel == mw->menu.foreground)
2664                                 mw->menu.top_shadow_color =
2665                                     mw->core.background_pixel;
2666                         else
2667                                 mw->menu.bottom_shadow_color =
2668                                     mw->menu.foreground;
2669                 }
2670         }
2671
2672         if (!mw->menu.top_shadow_pixmap &&
2673             mw->menu.top_shadow_color == mw->core.background_pixel) {
2674                 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
2675                 mw->menu.top_shadow_color = mw->menu.foreground;
2676         }
2677         if (!mw->menu.bottom_shadow_pixmap &&
2678             mw->menu.bottom_shadow_color == mw->core.background_pixel) {
2679                 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
2680                 mw->menu.bottom_shadow_color = mw->menu.foreground;
2681         }
2682
2683         xgcv.fill_style = FillOpaqueStippled;
2684         xgcv.foreground = mw->menu.top_shadow_color;
2685         xgcv.background = mw->core.background_pixel;
2686 /*  xgcv.stipple = mw->menu.top_shadow_pixmap; gtb */
2687         if (mw->menu.top_shadow_pixmap &&
2688             mw->menu.top_shadow_pixmap != XmUNSPECIFIED_PIXMAP)
2689                 xgcv.stipple = mw->menu.top_shadow_pixmap;
2690         else
2691                 xgcv.stipple = 0;
2692         pm = (xgcv.stipple ? GCStipple | GCFillStyle : 0);
2693         mw->menu.shadow_top_gc =
2694             XtGetGC((Widget) mw, GCForeground | GCBackground | pm, &xgcv);
2695
2696         xgcv.foreground = mw->menu.bottom_shadow_color;
2697 /*  xgcv.stipple = mw->menu.bottom_shadow_pixmap; gtb */
2698         if (mw->menu.bottom_shadow_pixmap &&
2699             mw->menu.bottom_shadow_pixmap != XmUNSPECIFIED_PIXMAP)
2700                 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
2701         else
2702                 xgcv.stipple = 0;
2703         pm = (xgcv.stipple ? GCStipple | GCFillStyle : 0);
2704         mw->menu.shadow_bottom_gc =
2705             XtGetGC((Widget) mw, GCForeground | GCBackground | pm, &xgcv);
2706 }
2707
2708 static void release_shadow_gcs(XlwMenuWidget mw)
2709 {
2710         XtReleaseGC((Widget) mw, mw->menu.shadow_top_gc);
2711         XtReleaseGC((Widget) mw, mw->menu.shadow_bottom_gc);
2712 }
2713
2714 static void extract_font_extents(XlwMenuWidget mw)
2715 {
2716 #ifdef NEED_MOTIF
2717         /* Find the maximal ascent/descent of the fonts in the font list
2718            so that all menu items can be the same height... */
2719         mw->menu.font_ascent = 0;
2720         mw->menu.font_descent = 0;
2721
2722         {
2723                 XmFontContext context;
2724 #if (XmVersion >= 1002)
2725                 XmFontListEntry fontentry;
2726 #else
2727                 XmStringCharSet charset;
2728 #endif
2729                 XFontStruct *font;
2730
2731                 if (!XmFontListInitFontContext(&context, mw->menu.font_list))
2732                         abort();
2733 #if (XmVersion >= 1002)
2734                 /* There is a BUG in the 1.2 version of XmFontListGetNextFont() (or more
2735                    specifically, in _XmGetFirstFont()) that can cause a null pointer to be
2736                    passed to XFontsOfFontSet.  Use XmFontListNextEntry(), which is the
2737                    newer equivalent, instead.  Also, it supports font sets, and the
2738                    older function doesn't. */
2739                 while ((fontentry = XmFontListNextEntry(context))) {
2740                         XmFontType rettype;
2741
2742                         XtPointer one_of_them =
2743                             XmFontListEntryGetFont(fontentry, &rettype);
2744                         if (rettype == XmFONT_IS_FONTSET) {
2745                                 XFontSet fontset = (XFontSet) one_of_them;
2746                                 XFontStruct **fontstruct_list;
2747                                 char **fontname_list;
2748                                 int fontcount =
2749                                     XFontsOfFontSet(fontset, &fontstruct_list,
2750                                                     &fontname_list);
2751                                 while (--fontcount >= 0) {
2752                                         font = fontstruct_list[fontcount];
2753                                         if (font->ascent >
2754                                             (int)mw->menu.font_ascent)
2755                                                 mw->menu.font_ascent =
2756                                                     font->ascent;
2757                                         if (font->descent >
2758                                             (int)mw->menu.font_descent)
2759                                                 mw->menu.font_descent =
2760                                                     font->descent;
2761                                 }
2762                         } else {        /* XmFONT_IS_FONT */
2763
2764                                 font = (XFontStruct *) one_of_them;
2765                                 if (font->ascent > (int)mw->menu.font_ascent)
2766                                         mw->menu.font_ascent = font->ascent;
2767                                 if (font->descent > (int)mw->menu.font_descent)
2768                                         mw->menu.font_descent = font->descent;
2769                         }
2770                 }
2771 #else                           /* motif 1.1 */
2772                 while (XmFontListGetNextFont(context, &charset, &font)) {
2773                         if (font->ascent > (int)mw->menu.font_ascent)
2774                                 mw->menu.font_ascent = font->ascent;
2775                         if (font->descent > (int)mw->menu.font_descent)
2776                                 mw->menu.font_descent = font->descent;
2777                         XtFree(charset);
2778                 }
2779 #endif                          /* Motif version */
2780                 XmFontListFreeFontContext(context);
2781         }
2782 #else                           /* Not Motif */
2783 # ifdef USE_XFONTSET
2784         XFontStruct **fontstruct_list;
2785         char **fontname_list;
2786         XFontStruct *font;
2787         int fontcount = XFontsOfFontSet(mw->menu.font_set, &fontstruct_list,
2788                                         &fontname_list);
2789         mw->menu.font_ascent = 0;
2790         mw->menu.font_descent = 0;
2791 #  if 0                         /* nasty, personal debug, Kazz */
2792         fprintf(stderr, "fontSet count is %d\n", fontcount);
2793 #  endif
2794         while (--fontcount >= 0) {
2795                 font = fontstruct_list[fontcount];
2796                 if (font->ascent > (int)mw->menu.font_ascent)
2797                         mw->menu.font_ascent = font->ascent;
2798                 if (font->descent > (int)mw->menu.font_descent)
2799                         mw->menu.font_descent = font->descent;
2800         }
2801 # else                          /* ! USE_XFONTSET */
2802         mw->menu.font_ascent = mw->menu.font->ascent;
2803         mw->menu.font_descent = mw->menu.font->descent;
2804 # endif
2805 #endif                          /* NEED_MOTIF */
2806 }
2807
2808 #ifdef NEED_MOTIF
2809 static XFontStruct *default_font_of_font_list(XmFontList font_list)
2810 {
2811         XFontStruct *font = 0;
2812 # if 0
2813         /* Xm/Label.c does this: */
2814         _XmFontListGetDefaultFont(font_list, &font);
2815 # else                          /* !0 */
2816         {
2817                 XmFontContext context;
2818 #if (XmVersion >= 1002)
2819                 XmFontListEntry fontentry;
2820                 XmFontType rettype;
2821                 XtPointer one_of_them;
2822 #else
2823                 XmStringCharSet charset;
2824 #endif
2825
2826                 if (!XmFontListInitFontContext(&context, font_list))
2827                         abort();
2828 #if (XmVersion >= 1002)
2829                 /* There is a BUG in the 1.2 version of XmFontListGetNextFont() (or more
2830                    specifically, in _XmGetFirstFont()) that can cause a null pointer to be
2831                    passed to XFontsOfFontSet.  Use XmFontListNextEntry(), which is the
2832                    newer equivalent, instead. */
2833                 fontentry = XmFontListNextEntry(context);
2834                 one_of_them = XmFontListEntryGetFont(fontentry, &rettype);
2835                 if (rettype == XmFONT_IS_FONTSET) {
2836                         XFontSet fontset = (XFontSet) one_of_them;
2837                         XFontStruct **fontstruct_list;
2838                         char **fontname_list;
2839                         (void)XFontsOfFontSet(fontset, &fontstruct_list,
2840                                               &fontname_list);
2841                         font = fontstruct_list[0];
2842                 } else {        /* XmFONT_IS_FONT */
2843
2844                         font = (XFontStruct *) one_of_them;
2845                 }
2846 #else
2847                 if (!XmFontListGetNextFont(context, &charset, &font))
2848                         abort();
2849                 XtFree(charset);
2850 #endif
2851                 XmFontListFreeFontContext(context);
2852         }
2853 # endif                         /* !0 */
2854
2855         if (!font)
2856                 abort();
2857         return font;
2858 }
2859 #endif                          /* NEED_MOTIF */
2860
2861 static void
2862 XlwMenuInitialize(Widget request, Widget new, ArgList args, Cardinal * num_args)
2863 {
2864         /* Get the GCs and the widget size */
2865         XlwMenuWidget mw = (XlwMenuWidget) new;
2866         Window window =
2867             RootWindowOfScreen(DefaultScreenOfDisplay(XtDisplay(mw)));
2868         Display *display = XtDisplay(mw);
2869
2870 /*  mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
2871         mw->menu.cursor = mw->menu.cursor_shape;
2872
2873         mw->menu.gray_pixmap =
2874             XCreatePixmapFromBitmapData(display, window, (char *)gray_bits,
2875                                         gray_width, gray_height, 1, 0, 1);
2876
2877 #ifdef NEED_MOTIF
2878         /* #### Even if it's a kludge!!!, we should consider doing the same for
2879            X Font Sets. */
2880         /* The menu.font_list slot came from the *fontList resource (Motif standard.)
2881            The menu.font_list_2 slot came from the *font resource, for backward
2882            compatibility with older versions of this code, and consistency with the
2883            rest of emacs.  If both font and fontList are specified, we use fontList.
2884            If only one is specified, we use that.  If neither are specified, we
2885            use the "fallback" value.  What a kludge!!!
2886
2887            Note that this has the bug that a more general wildcard like "*fontList:"
2888            will override a more specific resource like "Emacs*menubar.font:".  But
2889            I can't think of a way around that.
2890          */
2891         if (mw->menu.font_list) /* if *fontList is specified, use that */
2892                 ;
2893         else if (mw->menu.font_list_2)  /* else if *font is specified, use that */
2894                 mw->menu.font_list = mw->menu.font_list_2;
2895         else                    /* otherwise use default */
2896                 mw->menu.font_list = mw->menu.fallback_font_list;
2897 #endif
2898
2899         make_drawing_gcs(mw);
2900         make_shadow_gcs(mw);
2901         extract_font_extents(mw);
2902
2903         mw->menu.popped_up = False;
2904         mw->menu.pointer_grabbed = False;
2905         mw->menu.next_release_must_exit = False;
2906
2907         mw->menu.old_depth = 1;
2908         mw->menu.old_stack = XtNew(widget_value *);
2909         mw->menu.old_stack_length = 1;
2910         mw->menu.old_stack[0] = mw->menu.contents;
2911
2912         mw->menu.new_depth = 0;
2913         mw->menu.new_stack = 0;
2914         mw->menu.new_stack_length = 0;
2915         push_new_stack(mw, mw->menu.contents);
2916
2917         mw->menu.windows = XtNew(window_state);
2918         mw->menu.windows_length = 1;
2919         mw->menu.windows[0].x = 0;
2920         mw->menu.windows[0].y = 0;
2921         mw->menu.windows[0].width = 0;
2922         mw->menu.windows[0].height = 0;
2923         size_menu(mw, 0);
2924
2925         mw->core.width = mw->menu.windows[0].width;
2926         mw->core.height = mw->menu.windows[0].height;
2927 }
2928
2929 static void XlwMenuClassInitialize(void)
2930 {
2931         initialize_massaged_resource_char();
2932 }
2933
2934 static void
2935 XlwMenuRealize(Widget w, Mask * valueMask, XSetWindowAttributes * attributes)
2936 {
2937         XlwMenuWidget mw = (XlwMenuWidget) w;
2938         XSetWindowAttributes xswa;
2939         unsigned long mask;
2940
2941         (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
2942             (w, valueMask, attributes);
2943
2944         xswa.save_under = True;
2945         xswa.cursor = mw->menu.cursor_shape;
2946         mask = CWSaveUnder | CWCursor;
2947         if (mw->menu.use_backing_store) {
2948                 xswa.backing_store = Always;
2949                 mask |= CWBackingStore;
2950         }
2951         XChangeWindowAttributes(XtDisplay(w), XtWindow(w), mask, &xswa);
2952
2953         mw->menu.windows[0].window = XtWindow(w);
2954         mw->menu.windows[0].x = w->core.x;
2955         mw->menu.windows[0].y = w->core.y;
2956         mw->menu.windows[0].width = w->core.width;
2957         mw->menu.windows[0].height = w->core.height;
2958 }
2959
2960 /* Only the toplevel menubar/popup is a widget so it's the only one that
2961    receives expose events through Xt.  So we repaint all the other panes
2962    when receiving an Expose event. */
2963 static void XlwMenuRedisplay(Widget w, XEvent * ev, Region region)
2964 {
2965         XlwMenuWidget mw = (XlwMenuWidget) w;
2966         int i;
2967
2968         if (mw->core.being_destroyed)
2969                 return;
2970
2971         for (i = 0; i < mw->menu.old_depth; i++)
2972                 display_menu(mw, i, False, NULL, NULL, NULL, NULL, NULL);
2973         set_new_state(mw, NULL, mw->menu.old_depth);    /* #### - ??? */
2974         remap_menubar(mw);      /* #### - do these two lines do anything? */
2975 }
2976
2977 static void XlwMenuDestroy(Widget w)
2978 {
2979         int i;
2980         XlwMenuWidget mw = (XlwMenuWidget) w;
2981
2982         if (mw->menu.pointer_grabbed) {
2983                 XtUngrabPointer(w, CurrentTime);
2984                 mw->menu.pointer_grabbed = False;
2985         }
2986
2987         release_drawing_gcs(mw);
2988         release_shadow_gcs(mw);
2989
2990         /* this doesn't come from the resource db but is created explicitly
2991            so we must free it ourselves. */
2992         XFreePixmap(XtDisplay(mw), mw->menu.gray_pixmap);
2993         mw->menu.gray_pixmap = (Pixmap) - 1;
2994
2995         /* Don't free mw->menu.contents because that comes from our creator.
2996            The `*_stack' elements are just pointers into `contents' so leave
2997            that alone too.  But free the stacks themselves. */
2998         if (mw->menu.old_stack)
2999                 XtFree((char *)mw->menu.old_stack);
3000         if (mw->menu.new_stack)
3001                 XtFree((char *)mw->menu.new_stack);
3002
3003         /* Remember, you can't free anything that came from the resource
3004            database.  This includes:
3005            mw->menu.cursor
3006            mw->menu.top_shadow_pixmap
3007            mw->menu.bottom_shadow_pixmap
3008            mw->menu.font
3009            mw->menu.font_set
3010            Also the color cells of top_shadow_color, bottom_shadow_color,
3011            foreground, and button_foreground will never be freed until this
3012            client exits.  Nice, eh?
3013          */
3014
3015         /* start from 1 because the one in slot 0 is w->core.window */
3016         for (i = 1; i < mw->menu.windows_length; i++)
3017                 XDestroyWindow(XtDisplay(mw), mw->menu.windows[i].window);
3018         if (mw->menu.windows)
3019                 XtFree((char *)mw->menu.windows);
3020 }
3021
3022 static Boolean
3023 XlwMenuSetValues(Widget current, Widget request, Widget new, ArgList args,
3024                  Cardinal * num_args)
3025 {
3026         XlwMenuWidget oldmw = (XlwMenuWidget) current;
3027         XlwMenuWidget newmw = (XlwMenuWidget) new;
3028         Boolean redisplay = False;
3029         int i;
3030
3031         if (newmw->menu.contents
3032             && newmw->menu.contents->contents
3033             && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
3034                 redisplay = True;
3035
3036         if (newmw->core.background_pixel != oldmw->core.background_pixel
3037             || newmw->menu.foreground != oldmw->menu.foreground
3038             /* For the XEditResource protocol, which may want to change the font. */
3039 #ifdef NEED_MOTIF
3040             || newmw->menu.font_list != oldmw->menu.font_list
3041             || newmw->menu.font_list_2 != oldmw->menu.font_list_2
3042             || newmw->menu.fallback_font_list != oldmw->menu.fallback_font_list
3043 #else
3044             || newmw->menu.font != oldmw->menu.font
3045 #endif
3046             ) {
3047                 release_drawing_gcs(newmw);
3048                 make_drawing_gcs(newmw);
3049                 redisplay = True;
3050
3051                 for (i = 0; i < oldmw->menu.windows_length; i++) {
3052                         XSetWindowBackground(XtDisplay(oldmw),
3053                                              oldmw->menu.windows[i].window,
3054                                              newmw->core.background_pixel);
3055                         /* clear windows and generate expose events */
3056                         XClearArea(XtDisplay(oldmw),
3057                                    oldmw->menu.windows[i].window, 0, 0, 0, 0,
3058                                    True);
3059                 }
3060         }
3061
3062         return redisplay;
3063 }
3064
3065 static void XlwMenuResize(Widget w)
3066 {
3067         XlwMenuWidget mw = (XlwMenuWidget) w;
3068
3069         mw->menu.windows[0].width = mw->core.width;
3070         mw->menu.windows[0].height = mw->core.height;
3071 }
3072 \f
3073 /* Action procedures */
3074 static void
3075 handle_single_motion_event(XlwMenuWidget mw, XMotionEvent * ev,
3076                            Boolean select_p)
3077 {
3078         widget_value *val;
3079         Boolean stay_up;
3080         int level=0;
3081
3082         if (!map_event_to_widget_value(mw, ev, &val, &level, &stay_up)) {
3083                 /* we wind up here when: (a) the event is in the menubar, (b) the
3084                    event isn't in the menubar or any of the panes, (c) the event is on
3085                    a disabled menu item */
3086                 pop_new_stack_if_no_contents(mw);
3087                 if (select_p && !stay_up) {
3088                         /* pop down all menus and exit */
3089                         mw->menu.next_release_must_exit = True;
3090                         set_new_state(mw, (val = NULL), 1);
3091                 }
3092         } else {
3093                 /* we wind up here when: (a) the event pops up a pull_right menu,
3094                    (b) a menu item that is not disabled is highlighted */
3095                 if (select_p && mw->menu.bounce_down
3096                     && close_to_reference_time((Widget) mw,
3097                                                mw->menu.menu_bounce_time,
3098                                                (XEvent *) ev)) {
3099                         /* motion can cause more than one event.  Don't bounce right back
3100                            up if we've just bounced down. */
3101                         val = NULL;
3102                 } else if (select_p && mw->menu.bounce_down &&
3103                            mw->menu.last_selected_val &&
3104                            (mw->menu.last_selected_val == val)) {
3105                         val = NULL;     /* assigned to mw->last_selected_val below */
3106                         mw->menu.menu_bounce_time = ev->time;
3107                         /* popdown last menu if we're selecting the same menu item as we did
3108                            last time and the XlwMenu.bounceDown resource is set, if the
3109                            item is on the menubar itself, then exit. */
3110                         if (level == (mw->menu.popped_up ? 0 : 1))
3111                                 mw->menu.next_release_must_exit = True;
3112                 } else
3113                         mw->menu.menu_bounce_time = 0;
3114                 set_new_state(mw, val, level);
3115         }
3116         mw->menu.last_selected_val = val;
3117         remap_menubar(mw);
3118
3119         /* Sync with the display.  Makes it feel better on X terms. */
3120         XFlush(XtDisplay(mw));
3121 }
3122
3123 static void
3124 handle_motion_event(XlwMenuWidget mw, XMotionEvent * ev, Boolean select_p)
3125 {
3126         int x = ev->x_root;
3127         int y = ev->y_root;
3128         unsigned int state = ev->state;
3129         XMotionEvent *event = ev, dummy;
3130
3131         /* allow motion events to be generated again */
3132         dummy.window = ev->window;
3133         if (ev->is_hint
3134             && XQueryPointer(XtDisplay(mw), dummy.window,
3135                              &dummy.root, &dummy.subwindow,
3136                              &dummy.x_root, &dummy.y_root,
3137                              &dummy.x, &dummy.y, &dummy.state)
3138             && dummy.state == state && (dummy.x_root != x || dummy.y_root != y)) {
3139                 /* don't handle the event twice or that breaks bounce_down.  --Stig */
3140                 dummy.type = ev->type;
3141                 event = &dummy;
3142         }
3143
3144         lw_menu_accelerate = False;
3145         handle_single_motion_event(mw, event, select_p);
3146 }
3147
3148 Time x_focus_timestamp_really_sucks_fix_me_better;
3149
3150 static void Start(Widget w, XEvent * ev, String * params, Cardinal * num_params)
3151 {
3152         XlwMenuWidget mw = (XlwMenuWidget) w;
3153
3154         lw_menubar_widget = w;
3155
3156         lw_menu_active = True;
3157
3158         if (!mw->menu.pointer_grabbed) {
3159                 mw->menu.menu_post_time = ev->xbutton.time;
3160                 mw->menu.menu_bounce_time = 0;
3161                 mw->menu.next_release_must_exit = True;
3162                 mw->menu.last_selected_val = NULL;
3163                 x_focus_timestamp_really_sucks_fix_me_better =
3164                     ((XButtonPressedEvent *) ev)->time;
3165                 XtCallCallbackList((Widget) mw, mw->menu.open, NULL);
3166
3167                 /* notes the absolute position of the menubar window */
3168                 mw->menu.windows[0].x = ev->xmotion.x_root - ev->xmotion.x;
3169                 mw->menu.windows[0].y = ev->xmotion.y_root - ev->xmotion.y;
3170
3171                 XtGrabPointer((Widget) mw, False,
3172                               (ButtonMotionMask | ButtonReleaseMask |
3173                                ButtonPressMask), GrabModeAsync, GrabModeAsync,
3174                               None, mw->menu.cursor_shape,
3175                               ((XButtonPressedEvent *) ev)->time);
3176                 mw->menu.pointer_grabbed = True;
3177         }
3178
3179         /* handles the down like a move, slots are mostly compatible */
3180         handle_motion_event(mw, &ev->xmotion, True);
3181 }
3182
3183 static void Drag(Widget w, XEvent * ev, String * params, Cardinal * num_params)
3184 {
3185         XlwMenuWidget mw = (XlwMenuWidget) w;
3186         handle_motion_event(mw, &ev->xmotion, False);
3187 }
3188
3189 static void
3190 Select(Widget w, XEvent * ev, String * params, Cardinal * num_params)
3191 {
3192         XlwMenuWidget mw = (XlwMenuWidget) w;
3193         widget_value *selected_item =
3194             mw->menu.old_stack[mw->menu.old_depth - 1];
3195
3196         lw_menu_accelerate = False;
3197
3198         /* If user releases the button quickly, without selecting anything,
3199            after the initial down-click that brought the menu up,
3200            do nothing. */
3201         if ((selected_item == 0 || selected_item->call_data == 0)
3202             && (!mw->menu.next_release_must_exit
3203                 || close_to_reference_time(w, mw->menu.menu_post_time, ev))) {
3204                 mw->menu.next_release_must_exit = False;
3205                 return;
3206         }
3207
3208         /* pop down everything */
3209         mw->menu.new_depth = 1;
3210         remap_menubar(mw);
3211
3212         /* Destroy() only gets called for popup menus.  Menubar widgets aren't
3213            destroyed when their menu panes get nuked. */
3214         if (mw->menu.pointer_grabbed) {
3215                 XtUngrabPointer((Widget) w, ev->xmotion.time);
3216                 mw->menu.pointer_grabbed = False;
3217         }
3218
3219         if (mw->menu.popped_up) {
3220                 mw->menu.popped_up = False;
3221                 XtPopdown(XtParent(mw));
3222         }
3223
3224         lw_menu_active = False;
3225
3226         x_focus_timestamp_really_sucks_fix_me_better =
3227             ((XButtonPressedEvent *) ev)->time;
3228
3229         /* callback */
3230         XtCallCallbackList((Widget) mw, mw->menu.select,
3231                            (XtPointer) selected_item);
3232 }
3233 \f
3234 /* Action procedures for keyboard accelerators */
3235
3236 /* set the menu */
3237 void xlw_set_menu(Widget w, widget_value * val)
3238 {
3239         lw_menubar_widget = w;
3240         set_new_state((XlwMenuWidget) w, val, 1);
3241 }
3242
3243 /* prepare the menu structure via the call-backs */
3244 void xlw_map_menu(Time t)
3245 {
3246         XlwMenuWidget mw = (XlwMenuWidget) lw_menubar_widget;
3247
3248         lw_menu_accelerate = True;
3249
3250         if (!mw->menu.pointer_grabbed) {
3251                 XWindowAttributes ret;
3252                 Window parent = 0UL, root = 0UL;
3253                 Window *waste = NULL;
3254                 unsigned int num_waste;
3255
3256                 lw_menu_active = True;
3257
3258                 mw->menu.menu_post_time = t;
3259                 mw->menu.menu_bounce_time = 0;
3260
3261                 mw->menu.next_release_must_exit = True;
3262                 mw->menu.last_selected_val = NULL;
3263
3264                 XtCallCallbackList((Widget) mw, mw->menu.open, NULL);
3265
3266                 /* do this for keyboards too! */
3267                 /* notes the absolute position of the menubar window */
3268                 /*
3269                    mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
3270                    mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
3271                  */
3272
3273                 /* get the geometry of the menubar */
3274
3275                 /* there has to be a better way than this. */
3276
3277                 mw->menu.windows[0].x = 0;
3278                 mw->menu.windows[0].y = 0;
3279
3280                 parent = XtWindow(lw_menubar_widget);
3281                 do {
3282                         XGetWindowAttributes(XtDisplay(lw_menubar_widget),
3283                                              parent, &ret);
3284                         mw->menu.windows[0].x += ret.x;
3285                         mw->menu.windows[0].y += ret.y;
3286
3287                         if (parent)
3288                                 XQueryTree(XtDisplay(lw_menubar_widget), parent,
3289                                            &root, &parent, &waste, &num_waste);
3290                         if (waste) {
3291                                 XFree(waste);
3292                         }
3293                 }
3294                 while (parent != root);
3295
3296                 XtGrabPointer((Widget) mw, False,
3297                               (ButtonMotionMask | ButtonReleaseMask |
3298                                ButtonPressMask), GrabModeAsync, GrabModeAsync,
3299                               None, mw->menu.cursor_shape, t);
3300                 mw->menu.pointer_grabbed = True;
3301         }
3302 }
3303
3304 /* display the stupid menu already */
3305 void xlw_display_menu(Time t)
3306 {
3307         XlwMenuWidget mw = (XlwMenuWidget) lw_menubar_widget;
3308
3309         lw_menu_accelerate = True;
3310
3311         remap_menubar(mw);
3312
3313         /* Sync with the display.  Makes it feel better on X terms. */
3314         XFlush(XtDisplay(mw));
3315 }
3316
3317 /* push a sub menu */
3318 void xlw_push_menu(widget_value * val)
3319 {
3320         push_new_stack((XlwMenuWidget) lw_menubar_widget, val);
3321 }
3322
3323 /* pop a sub menu */
3324 int xlw_pop_menu(void)
3325 {
3326         if (((XlwMenuWidget) lw_menubar_widget)->menu.new_depth > 0)
3327                 ((XlwMenuWidget) lw_menubar_widget)->menu.new_depth--;
3328         else
3329                 return 0;
3330         return 1;
3331 }
3332
3333 void xlw_kill_menus(widget_value * val)
3334 {
3335         XlwMenuWidget mw = (XlwMenuWidget) lw_menubar_widget;
3336
3337         lw_menu_accelerate = False;
3338
3339         mw->menu.new_depth = 1;
3340         remap_menubar(mw);
3341
3342         if (mw->menu.pointer_grabbed) {
3343                 XtUngrabPointer(lw_menubar_widget, CurrentTime);
3344                 mw->menu.pointer_grabbed = False;
3345         }
3346
3347         lw_menu_active = False;
3348         XtCallCallbackList(lw_menubar_widget, mw->menu.select, (XtPointer) val);
3349 }
3350
3351 /* set the menu item */
3352 void xlw_set_item(widget_value * val)
3353 {
3354         if (((XlwMenuWidget) lw_menubar_widget)->menu.new_depth > 0)
3355                 ((XlwMenuWidget) lw_menubar_widget)->menu.new_depth--;
3356         push_new_stack((XlwMenuWidget) lw_menubar_widget, val);
3357 }
3358
3359 /* get either the current entry or a list of all entries in the current submenu */
3360 widget_value *xlw_get_entries(int allp)
3361 {
3362         XlwMenuWidget mw = (XlwMenuWidget) lw_menubar_widget;
3363         if (allp) {
3364                 if (mw->menu.new_depth >= 2)
3365                         return mw->menu.new_stack[mw->menu.new_depth -
3366                                                   2]->contents;
3367                 else
3368                         return mw->menu.new_stack[0];
3369         } else if (mw->menu.new_depth >= 1)
3370                 return mw->menu.new_stack[mw->menu.new_depth - 1];
3371
3372         return NULL;
3373 }
3374
3375 int xlw_menu_level(void)
3376 {
3377         return ((XlwMenuWidget) lw_menubar_widget)->menu.new_depth;
3378 }
3379 \f
3380 /* Special code to pop-up a menu */
3381 void xlw_pop_up_menu(XlwMenuWidget mw, XButtonPressedEvent * event)
3382 {
3383         int x = event->x_root;
3384         int y = event->y_root;
3385         int w;
3386         int h;
3387         int borderwidth = mw->menu.shadow_thickness;
3388         Screen *screen = XtScreen(mw);
3389
3390         mw->menu.menu_post_time = event->time;
3391         mw->menu.menu_bounce_time = 0;
3392         mw->menu.next_release_must_exit = True;
3393         mw->menu.last_selected_val = NULL;
3394
3395         XtCallCallbackList((Widget) mw, mw->menu.open, NULL);
3396
3397         size_menu(mw, 0);
3398
3399         w = mw->menu.windows[0].width;
3400         h = mw->menu.windows[0].height;
3401
3402         x -= borderwidth;
3403         y -= borderwidth;
3404
3405         if (x < borderwidth)
3406                 x = borderwidth;
3407
3408         if (x > WidthOfScreen(screen) - w - 2 * borderwidth)
3409                 x = WidthOfScreen(screen) - w - 2 * borderwidth;
3410
3411         if (y < borderwidth)
3412                 y = borderwidth;
3413
3414         if (y > HeightOfScreen(screen) - h - 2 * borderwidth)
3415                 y = HeightOfScreen(screen) - h - 2 * borderwidth;
3416
3417         mw->menu.popped_up = True;
3418         XtConfigureWidget(XtParent(mw), x, y, w, h,
3419                           XtParent(mw)->core.border_width);
3420         XtPopup(XtParent(mw), XtGrabExclusive);
3421         display_menu(mw, 0, False, NULL, NULL, NULL, NULL, NULL);
3422         if (!mw->menu.pointer_grabbed) {
3423                 XtGrabPointer((Widget) mw, False,
3424                               (ButtonMotionMask | ButtonReleaseMask |
3425                                ButtonPressMask), GrabModeAsync, GrabModeAsync,
3426                               None, mw->menu.cursor_shape, event->time);
3427                 mw->menu.pointer_grabbed = True;
3428         }
3429
3430         mw->menu.windows[0].x = x + borderwidth;
3431         mw->menu.windows[0].y = y + borderwidth;
3432
3433         handle_motion_event(mw, (XMotionEvent *) event, True);
3434 }
3435
3436 /* #### unused */
3437 #if 0
3438 /*
3439  *    This is a horrible function which should not be needed.
3440  *    use it to put the resize method back the way the XlwMenu
3441  *    class initializer put it. Motif screws with this when
3442  *    the XlwMenu class gets instantiated.
3443  */
3444 void xlw_unmunge_class_resize(Widget w)
3445 {
3446         if (w->core.widget_class->core_class.resize != XlwMenuResize)
3447                 w->core.widget_class->core_class.resize = XlwMenuResize;
3448 }
3449 #endif                          /* 0 */