/* Implements a lightweight menubar widget.  
   Copyright (C) 1992, 1993, 1994 Lucid, Inc.
   Copyright (C) 1995 Tinker Systems and INS Engineering Corp.

This file is part of the Lucid Widget Library.

The Lucid Widget Library is free software; you can redistribute it and/or 
modify it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

The Lucid Widget Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Emacs; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Created by devin@lucid.com */

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>

#include <sys/types.h>
#include <X11/Xos.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>
#include <X11/bitmaps/gray>

#ifdef NEED_MOTIF
#include <Xm/Xm.h>
#endif
#include "xlwmenuP.h"

static char 
xlwMenuTranslations [] = 
"<BtnDown>:	start()\n\
<BtnMotion>:	drag()\n\
<BtnUp>:	select()\n\
";

#define offset(field) XtOffset(XlwMenuWidget, field)
static XtResource 
xlwMenuResources[] =
{ 
#ifdef NEED_MOTIF
  /* There are three font list resources, so that we can accept either of
     the resources *fontList: or *font:, and so that we can tell the
     difference between them being specified, and being defaulted to a
     font from the XtRString specified here.
   */
  {XmNfontList,  XmCFontList, XmRFontList, sizeof(XmFontList),
     offset(menu.font_list),  XtRImmediate, (XtPointer)0},
  {XtNfont,      XtCFont,     XmRFontList, sizeof(XmFontList),
     offset(menu.font_list_2),XtRImmediate, (XtPointer)0},
  {XmNfontList,  XmCFontList, XmRFontList, sizeof(XmFontList),
     offset(menu.fallback_font_list),
     /* We must use an iso8859-1 font here, or people without $LANG set lose.
	It's fair to assume that those who do have $LANG set also have the
	*fontList resource set, or at least know how to deal with this.
      */
     XtRString, "-*-helvetica-bold-r-*-*-*-120-*-*-*-*-iso8859-1"},
#else
  {XtNfont,  XtCFont, XtRFontStruct, sizeof(XFontStruct *),
     offset(menu.font),XtRString, "XtDefaultFont"},
#endif
  {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
     offset(menu.foreground), XtRString, "XtDefaultForeground"},
  {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
     offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
  {XtNmargin, XtCMargin, XtRDimension,  sizeof(Dimension),
     offset(menu.margin), XtRImmediate, (XtPointer)2},
  {XmNmarginWidth, XmCMarginWidth, XmRHorizontalDimension, sizeof(Dimension),
     offset(menu.horizontal_margin), XtRImmediate, (XtPointer)2},
  {XmNmarginHeight, XmCMarginHeight, XmRVerticalDimension, sizeof(Dimension),
     offset(menu.vertical_margin), XtRImmediate, (XtPointer)1},
  {XmNspacing, XmCSpacing, XmRHorizontalDimension,  sizeof(Dimension),
     offset(menu.column_spacing), XtRImmediate, (XtPointer)4},
  {XmNindicatorSize, XmCIndicatorSize, XtRDimension,  sizeof(Dimension),
     offset(menu.indicator_size), XtRImmediate, (XtPointer)0},
  {XmNshadowThickness, XmCShadowThickness, XmRHorizontalDimension,
     sizeof (Dimension), offset (menu.shadow_thickness),
     XtRImmediate, (XtPointer) 2},
  {XmNselectColor, XmCSelectColor, XtRPixel, sizeof (Pixel),
     offset (menu.select_color), XtRImmediate, (XtPointer)-1},
  {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
     offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
  {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
     offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
  {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
     offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
  {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
     offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},

  {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer), 
     offset(menu.open), XtRCallback, (XtPointer)NULL},
  {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer), 
     offset(menu.select), XtRCallback, (XtPointer)NULL},
  {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
     offset(menu.contents), XtRImmediate, (XtPointer)NULL},
  {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
     offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
  {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
     offset(menu.horizontal), XtRImmediate, (XtPointer)True},
  {XtNuseBackingStore, XtCUseBackingStore, XtRBoolean, sizeof (Boolean),
     offset (menu.use_backing_store), XtRImmediate, (XtPointer)False},
  {XtNbounceDown, XtCBounceDown, XtRBoolean, sizeof (Boolean),
     offset (menu.bounce_down), XtRImmediate, (XtPointer)True},
  {XtNresourceLabels, XtCResourceLabels, XtRBoolean, sizeof (Boolean),
     offset (menu.lookup_labels), XtRImmediate, (XtPointer)False},
};
#undef offset

static Boolean XlwMenuSetValues (Widget current, Widget request, Widget new,
				 ArgList args, Cardinal *num_args);
static void XlwMenuRealize (Widget w, Mask *valueMask,
			    XSetWindowAttributes *attributes);
static void XlwMenuRedisplay (Widget w, XEvent *ev, Region region);
static void XlwMenuResize (Widget w);
static void XlwMenuInitialize (Widget request, Widget new, ArgList args,
			       Cardinal *num_args);
static void XlwMenuDestroy (Widget w);
static void XlwMenuClassInitialize (void);
static void Start (Widget w, XEvent *ev, String *params, Cardinal *num_params);
static void Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params);
static void Select (Widget w, XEvent *ev, String *params,
 Cardinal *num_params);

#ifdef NEED_MOTIF
static XFontStruct *default_font_of_font_list (XmFontList);
#endif

static XtActionsRec 
xlwMenuActionsList [] =
{
  {"start",		Start},
  {"drag",		Drag},
  {"select",		Select},
};

#define SuperClass ((CoreWidgetClass)&coreClassRec)

XlwMenuClassRec xlwMenuClassRec =
{
  {  /* CoreClass fields initialization */
    (WidgetClass) SuperClass,		/* superclass		  */	
    "XlwMenu",				/* class_name		  */
    sizeof(XlwMenuRec),			/* size			  */
    XlwMenuClassInitialize,		/* class_initialize	  */
    NULL,				/* class_part_initialize  */
    FALSE,				/* class_inited		  */
    XlwMenuInitialize,			/* initialize		  */
    NULL,				/* initialize_hook	  */
    XlwMenuRealize,			/* realize		  */
    xlwMenuActionsList,			/* actions		  */
    XtNumber(xlwMenuActionsList),	/* num_actions		  */
    xlwMenuResources,			/* resources		  */
    XtNumber(xlwMenuResources),		/* resource_count	  */
    NULLQUARK,				/* xrm_class		  */
    TRUE,				/* compress_motion	  */
    TRUE,				/* compress_exposure	  */
    TRUE,				/* compress_enterleave    */
    FALSE,				/* visible_interest	  */
    XlwMenuDestroy,			/* destroy		  */
    XlwMenuResize,			/* resize		  */
    XlwMenuRedisplay,			/* expose		  */
    XlwMenuSetValues,			/* set_values		  */
    NULL,				/* set_values_hook	  */
    XtInheritSetValuesAlmost,		/* set_values_almost	  */
    NULL,				/* get_values_hook	  */
    NULL,			/* #### - should this be set for grabs? accept_focus		  */
    XtVersion,				/* version		  */
    NULL,				/* callback_private	  */
    xlwMenuTranslations,		/* tm_table		  */
    XtInheritQueryGeometry,		/* query_geometry	  */
    XtInheritDisplayAccelerator,	/* display_accelerator	  */
    NULL				/* extension		  */
  },  /* XlwMenuClass fields initialization */
  {
    0					/* dummy */
  },
};

WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;

/* Utilities */
#if 0 /* Apparently not used anywhere */

static char *
safe_strdup (char *s)
{
  char *result;
  if (! s) return 0;
  result = (char *) malloc (strlen (s) + 1);
  if (! result)
    return 0;
  strcpy (result, s);
  return result;
}

#endif /* 0 */

static void
push_new_stack (XlwMenuWidget mw, widget_value *val)
{
  if (!mw->menu.new_stack)
    {
      mw->menu.new_stack_length = 10;
      mw->menu.new_stack =
	(widget_value**)XtCalloc (mw->menu.new_stack_length,
				  sizeof (widget_value*));
    }
  else if (mw->menu.new_depth == mw->menu.new_stack_length)
    {
      mw->menu.new_stack_length *= 2;
      mw->menu.new_stack =
	(widget_value**)XtRealloc ((char*)mw->menu.new_stack,
				   mw->menu.new_stack_length *
				   sizeof (widget_value*));
    }
  mw->menu.new_stack [mw->menu.new_depth++] = val;
}

static void
pop_new_stack_if_no_contents (XlwMenuWidget mw)
{
  if (mw->menu.new_depth)
    {
      if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
	{
	  mw->menu.new_depth -= 1;
	}
    }
}

static void
make_old_stack_space (XlwMenuWidget mw, int n)
{
  if (!mw->menu.old_stack)
    {
      mw->menu.old_stack_length = 10;
      mw->menu.old_stack =
	(widget_value**)XtCalloc (mw->menu.old_stack_length,
				  sizeof (widget_value*));
    }
  else if (mw->menu.old_stack_length < n)
    {
      mw->menu.old_stack_length *= 2;
      mw->menu.old_stack =
	(widget_value**)XtRealloc ((char*)mw->menu.old_stack,
				   mw->menu.old_stack_length *
				   sizeof (widget_value*));
    }
}

static Boolean
close_to_reference_time(Widget w, Time reference_time, XEvent *ev)
{
  return (reference_time &&
	  (ev->xbutton.time - reference_time
	      < XtGetMultiClickTime (XtDisplay (w))));
}

/* Size code */
static int
string_width (XlwMenuWidget mw,
#ifdef NEED_MOTIF
	      XmString s
#else
	      char *s
#endif
	      )
{
#ifdef NEED_MOTIF
  Dimension width, height;
  XmStringExtent (mw->menu.font_list, s, &width, &height);
  return width;
#else
  XCharStruct xcs;
  int drop;
  XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
  return xcs.width;
#endif
}

static void
massage_resource_name (CONST char *in, char *out)
{
  /* Turn a random string into something suitable for using as a resource.
     For example:

     "Kill Buffer"		->	"killBuffer"
     "Find File..."		->	"findFile"
     "Search and Replace..."	->	"searchAndReplace"
   */

# define GOOD_CHAR(c) (((c) >= 'a' && (c) <= 'z') || \
		       ((c) >= 'A' && (c) <= 'Z') || \
		       ((c) >= '0' && (c) <= '9') || \
		       ((c) == '_') || \
		       ((c) > 0240))
  int firstp = 1;
  while (*in)
    {
      if (GOOD_CHAR ((unsigned char) *in))
	{
	  if (firstp)
	    *out = tolower (*in);
	  else
	    *out = toupper (*in);
	  firstp = 0;
	  in++;
	  out++;
	  while (GOOD_CHAR ((unsigned char) *in))
	    {
	      *out = *in;
	      in++;
	      out++;
	    }
	}
      else
	{
	  /* A bogus char between words; skip it. */
	  in++;
	}
    }
  *out = 0;
#undef GOOD_CHAR
}

static XtResource
nameResource[] =
{ 
  { "labelString",  "LabelString", XtRString, sizeof(String),
    0, XtRImmediate, 0 }
};

/*
 *    This function looks through string searching for parameter
 *    inserts of the form:
 *    %[padding]1
 *    padding is space (' ') or dash ('-') characters meaning
 *    padding to the left or right of the inserted parameter.
 *    In essence all %1 strings are replaced by value in the return
 *    value (which the caller is expected to free).
 *    %% means insert one % (like printf).
 *    %1 means insert value.
 *    %-1 means insert value followed by one space. The latter is
 *    not inserted if value is a zero length string.
 */
static char*
parameterize_string (char *string, char *value)
{
  char *percent;
  char *result;
  unsigned done = 0;
  unsigned ntimes;

  if (!string)
    string = "";

  if (!value)
    value = "";

  for (ntimes = 1, result = string; (percent = strchr(result, '%')); ntimes++)
    result = &percent[1];
  
  result = XtMalloc((ntimes * strlen(value)) + strlen(string) + 4);
  result[0] = '\0';

  while ((percent = strchr(string, '%')))
    {
      unsigned left_pad;
      unsigned right_pad;
      char *p;
      
      if (percent[1] == '%')
	{	/* it's a real % */
	  strncat(result, string, 1 + percent - string); /* incl % */
	  string = &percent[2];	/* after the second '%' */
	  continue;		/* with the while() loop */
	}
      
      left_pad = 0;
      right_pad = 0;

      for (p = &percent[1]; /* test *p inside the loop */ ; p++)
	{
	  if (*p == ' ')
	    {			/* left pad */
	      left_pad++;
	    }
	  else if (*p == '-')
	    {			/* right pad */
	      right_pad++;
	    }
	  else if (*p == '1')
	    {			/* param and terminator */
	      strncat(result, string, percent - string);
	      if (value[0] != '\0')
		{
		  unsigned i;
		  for (i = 0; i < left_pad; i++)
		    strcat(result, " ");
		  strcat(result, value);
		  for (i = 0; i < right_pad; i++)
		    strcat(result, " ");
		}
	      string = &p[1];	/* after the '1' */
	      done++;		/* no need to do old way */
	      break;		/* out of for() loop */
	    }
	  else
	    {			/* bogus, copy the format as is */
				/* out of for() loop */
	      strncat(result, string, 1 + p - string);
	      string= (*p ? &p[1] : p);
	      break;		
	    }
	}
    }

  /*
   *    Copy the tail of the string
   */
  strcat(result, string);

  /*
   *    If we have not processed a % string, and we have a value, tail it.
   */
  if (!done && value[0] != '\0')
    {
      strcat(result, " ");
      strcat(result, value);
    }

  return result;
}

#ifdef NEED_MOTIF

static XmString
resource_widget_value (XlwMenuWidget mw, widget_value *val)
{
  if (!val->toolkit_data)
    {
      char *resourced_name = NULL;
      char *converted_name, *str;
      XmString complete_name;
      char massaged_name [1024];

      if (mw->menu.lookup_labels)
	{
	  /* Convert value style name into resource style name.
	     eg: "Free Willy" becomes "freeWilly" */
	  massage_resource_name (val->name, massaged_name);
	  
	  /* If we have a value (parameter) see if we can find a "Named"
	     resource. */
	  if (val->value)
	    {
	      char named_name[1024];
	      sprintf(named_name, "%sNamed", massaged_name);
	      XtGetSubresources ((Widget) mw,
				 (XtPointer) &resourced_name,
				 named_name, named_name,
				 nameResource, 1, NULL, 0);
	    }

	  /* If nothing yet, try to load from the massaged name. */
	  if (!resourced_name)
	    {
	      XtGetSubresources ((Widget) mw,
				 (XtPointer) &resourced_name,
				 massaged_name, massaged_name,
				 nameResource, 1, NULL, 0);
	    }
	} /* if (mw->menu.lookup_labels) */

      /* Still nothing yet, use the name as the value. */
      if (!resourced_name)
	resourced_name = val->name;

      /* Parameterize the string. */
      converted_name = parameterize_string(resourced_name, val->value);

      /* nuke newline characters to prevent menubar screwups */
      for ( str = converted_name ; *str ; str++ )
	{
	  if (str[0] == '\n') str[0] = ' ';
	}

      /* Improve OSF's bottom line. */
      complete_name = XmStringCreateLtoR (converted_name,
					  XmSTRING_DEFAULT_CHARSET);
      XtFree (converted_name);

      val->toolkit_data = complete_name;
      val->free_toolkit_data = True;
    }
  return ((XmString) val->toolkit_data);
}

/* Unused */
#if 0
/*
 *    These two routines should be a seperate file..djw
 */
static char *
xlw_create_localized_string (Widget w,
			     char *name,
			     char **args,
			     unsigned nargs)
{
  char *string = NULL;
  char *arg = NULL;

  if (nargs > 0)
    arg = args[0];

  XtGetSubresources (w,
		     (XtPointer)&string,
		     name,
		     name,
		     nameResource, 1,
		     NULL, 0
		     );

  if (!string)
    string = name;

  return parameterize_string (string, arg);
}

static XmString
xlw_create_localized_xmstring (Widget w,
			       char *name,
			       char **args,
			       unsigned nargs)
{
  char *   string = xlw_create_localized_string (w, name, args, nargs);
  XmString xm_string = XmStringCreateLtoR (string, XmSTRING_DEFAULT_CHARSET);
  XtFree(string);
  return xm_string;
}
#endif /* 0 */

#else /* !Motif */

static char*
resource_widget_value (XlwMenuWidget mw, widget_value *val)
{
  if (!val->toolkit_data)
    {
      char *resourced_name = NULL;
      char *complete_name;
      char massaged_name [1024];

      if (mw->menu.lookup_labels)
	{
	  massage_resource_name (val->name, massaged_name);
	  
	  XtGetSubresources ((Widget) mw,
			     (XtPointer) &resourced_name,
			     massaged_name, massaged_name,
			     nameResource, 1, NULL, 0);
	}
      if (!resourced_name)
	resourced_name = val->name;

      complete_name = parameterize_string(resourced_name, val->value);

      val->toolkit_data = complete_name;
      /* nuke newline characters to prevent menubar screwups */
      for ( ; *complete_name ; complete_name++ )
	{
	  if (complete_name[0] == '\n')
	    complete_name[0] = ' ';
	}
      val->free_toolkit_data = True;
    }
  return (char*)val->toolkit_data;
}

#endif /* !Motif */

/*
 *    Code for drawing strings.
 */
static void
string_draw(
	    XlwMenuWidget mw,
	    Window window,
	    int x, int y,
	    GC gc,
#ifdef NEED_MOTIF
	    XmString string
#else
	    char *string
#endif
) {
#ifdef NEED_MOTIF
  XmStringDraw (XtDisplay (mw), window,
		mw->menu.font_list,
		string, gc,
		x, y,
		1000,	/* ???? width */
		XmALIGNMENT_BEGINNING,
		0, /* ???? layout_direction */
		0);
#else
  XDrawString (XtDisplay (mw), window, gc,
	       x, y + mw->menu.font_ascent, string, strlen (string));

#endif
}

static void 
binding_draw (XlwMenuWidget mw, Window w, int x, int y, GC gc, char *value)
{
#ifdef NEED_MOTIF
  XmString xm_value = XmStringCreateLtoR(value, XmSTRING_DEFAULT_CHARSET);
  string_draw (mw, w, x, y, gc, xm_value);
  XmStringFree (xm_value);
#else
  string_draw (mw, w, x, y, gc, value);
#endif
}

/*
 *    Low level code for drawing 3-D edges.
 */
static void
shadow_rectangle_draw (
		       Display *dpy,
		       Window window,
		       GC top_gc,
		       GC bottom_gc,
		       int x, int y, unsigned width, unsigned height,
		       unsigned thickness
		       )
{
  XPoint points [4];

  if (!thickness)
    return;

  points [0].x = x;
  points [0].y = y;
  points [1].x = x + width;
  points [1].y = y;
  points [2].x = x + width - thickness;
  points [2].y = y + thickness;
  points [3].x = x;
  points [3].y = y + thickness;
  XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
  points [0].x = x;
  points [0].y = y + thickness;
  points [1].x = x;
  points [1].y = y + height;
  points [2].x = x + thickness;
  points [2].y = y + height - thickness;
  points [3].x = x + thickness;
  points [3].y = y + thickness;
  XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
  points [0].x = x + width;
  points [0].y = y;
  points [1].x = x + width - thickness;
  points [1].y = y + thickness;
  points [2].x = x + width - thickness;
  points [2].y = y + height - thickness;
  points [3].x = x + width;
  points [3].y = y + height - thickness;
  XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
  points [0].x = x;
  points [0].y = y + height;
  points [1].x = x + width;
  points [1].y = y + height;
  points [2].x = x + width;
  points [2].y = y + height - thickness;
  points [3].x = x + thickness;
  points [3].y = y + height - thickness;
  XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
}

typedef enum e_shadow_type
{
  /* these are Motif compliant */
  SHADOW_BACKGROUND,
  SHADOW_OUT,
  SHADOW_IN,
  SHADOW_ETCHED_OUT,
  SHADOW_ETCHED_IN,
  SHADOW_ETCHED_OUT_DASH,
  SHADOW_ETCHED_IN_DASH,
  SHADOW_SINGLE_LINE,
  SHADOW_DOUBLE_LINE,
  SHADOW_SINGLE_DASHED_LINE,
  SHADOW_DOUBLE_DASHED_LINE,
  SHADOW_NO_LINE,
  /* these are all non-Motif */
  SHADOW_DOUBLE_ETCHED_OUT,
  SHADOW_DOUBLE_ETCHED_IN,
  SHADOW_DOUBLE_ETCHED_OUT_DASH,
  SHADOW_DOUBLE_ETCHED_IN_DASH
} shadow_type;

static void
shadow_draw (XlwMenuWidget mw,
	     Window window,
	     int x, int y, unsigned width, unsigned height,
	     shadow_type type
	     )
{
  Display *dpy = XtDisplay (mw);
  GC top_gc;
  GC bottom_gc;
  int thickness = mw->menu.shadow_thickness;
#if 0
  XPoint points [4];
#endif /* 0 */
  Boolean etched = False;

  switch (type)
    {
    case SHADOW_BACKGROUND:
      top_gc = bottom_gc = mw->menu.background_gc;
      break;
    case SHADOW_ETCHED_IN:
      top_gc = mw->menu.shadow_bottom_gc;
      bottom_gc = mw->menu.shadow_top_gc;
      etched = True;
      break;
    case SHADOW_ETCHED_OUT:
      top_gc = mw->menu.shadow_top_gc;
      bottom_gc = mw->menu.shadow_bottom_gc;
      etched = True;
      break;
    case SHADOW_IN:
      top_gc = mw->menu.shadow_bottom_gc;
      bottom_gc = mw->menu.shadow_top_gc;
      break;
    case SHADOW_OUT:
    default:
      top_gc = mw->menu.shadow_top_gc;
      bottom_gc = mw->menu.shadow_bottom_gc;
      break;
    }

  if (etched)
    {
      unsigned half = thickness/2;
      shadow_rectangle_draw (
			     dpy,
			     window,
			     top_gc,
			     top_gc,
			     x, y,
			     width - half, height - half,
			     thickness - half
			     );
      shadow_rectangle_draw (
			     dpy,
			     window,
			     bottom_gc,
			     bottom_gc,
			     x + half, y + half,
			     width - half , height - half,
			     half
			     );
    }
  else
    {
      shadow_rectangle_draw (
			     dpy,
			     window,
			     top_gc,
			     bottom_gc,
			     x, y,
			     width, height,
			     thickness
			     );
    }
}

static void 
arrow_decoration_draw (
		       XlwMenuWidget mw,
		       Window window,
		       int x, int y,
		       unsigned width,
		       Boolean raised
		       )
{
  Display *dpy = XtDisplay (mw);
  GC top_gc;
  GC bottom_gc;
  GC select_gc;
  int thickness = mw->menu.shadow_thickness;
  XPoint points [4];
  int half_width;
  int length = (int)((double)width * 0.87);
  int thick_med = (int)((double)thickness * 1.73);

  if (width & 0x1)
    half_width = width/2 + 1;
  else
    half_width = width/2;
  
  select_gc = mw->menu.background_gc;
  
  if (raised)
    {
      top_gc = mw->menu.shadow_bottom_gc;
      bottom_gc = mw->menu.shadow_top_gc;
    }
  else
    {
      top_gc = mw->menu.shadow_top_gc;
      bottom_gc = mw->menu.shadow_bottom_gc;
    }

  /*
   *    Fill internal area, we do this first so that the borders
   *    have a nice sharp edge.
   */
  points [0].x = x + thickness;
  points [0].y = y + thickness;
  points [1].x = x + length - thickness;
  points [1].y = y + half_width;
  points [2].x = x + length - thickness;
  points [2].y = y + half_width + thickness;
  points [3].x = x + thickness;
  points [3].y = y + width - thickness;
    
  XFillPolygon (
		dpy,
		window,
		select_gc,
		points,
		4, 
		Convex,
		CoordModeOrigin
		);

  /* left border */
  points [0].x = x;
  points [0].y = y;
  points [1].x = x + thickness;
  points [1].y = y + thick_med;
  points [2].x = x + thickness;
  points [2].y = y + width - thick_med;
  points [3].x = x;
  points [3].y = y + width;
  
  XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);

  /* top border */
  points [0].x = x;
  points [0].y = y + width;
  points [1].x = x + length;
  points [1].y = y + half_width;
  points [2].x = x + length - (thickness + thickness);
  points [2].y = y + half_width;
  points [3].x = x + thickness;
  points [3].y = y + width - thick_med;
  
  XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);

  /* bottom shadow */
  points [0].x = x;
  points [0].y = y;
  points [1].x = x + length;
  points [1].y = y + half_width;
  points [2].x = x + length - (thickness + thickness);
  points [2].y = y + half_width;
  points [3].x = x + thickness;
  points [3].y = y + thick_med;
  
  XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
}

static void
toggle_decoration_draw (
			XlwMenuWidget mw,
			Window window,
			int x, int y,
			unsigned width,
			Boolean set
			)
{
  Display *dpy = XtDisplay (mw);
  int thickness = mw->menu.shadow_thickness;
  shadow_type type;
  GC select_gc = mw->menu.select_gc;

  if (set)
    type = SHADOW_IN;
  else
    type = SHADOW_OUT;

  /*
   *    Fill internal area.
   */
  if (set) 
    XFillRectangle (
		    dpy,
		    window,
		    select_gc,
		    x + thickness,
		    y + thickness,
		    width - (2*thickness),
		    width - (2*thickness)
		    );
  
  shadow_draw(mw, window, x, y, width, width, type);
}

static void
radio_decoration_draw (
		       XlwMenuWidget mw,
		       Window window,
		       int x, int y,
		       unsigned width,
		       Boolean enabled
		       )
{
  Display *dpy = XtDisplay (mw);
  GC top_gc;
  GC bottom_gc;
  GC select_gc = mw->menu.select_gc;
  int thickness = mw->menu.shadow_thickness;
  XPoint points[6];
  int half_width;
#if 0
  int npoints;
#endif /* 0 */

  if (width & 0x1)
    width++;

  half_width = width/2;

  if (enabled)
    {
      top_gc = mw->menu.shadow_bottom_gc;
      bottom_gc = mw->menu.shadow_top_gc;
    }
  else
    {
      top_gc = mw->menu.shadow_top_gc;
      bottom_gc = mw->menu.shadow_bottom_gc;
    }

#if 1
  /*
   *    Draw the bottom first, just incase the regions overlap.
   *    The top should cast the longer shadow.
   */
  points [0].x = x; /* left corner */
  points [0].y = y + half_width;
  points [1].x = x + half_width; /* bottom corner */
  points [1].y = y + width;
  points [2].x = x + half_width; /* bottom inside corner */
  points [2].y = y + width - thickness;
  points [3].x = x + thickness; /* left inside corner */
  points [3].y = y + half_width;

  XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);

  points [0].x = x + half_width; /* bottom corner */
  points [0].y = y + width;
  points [1].x = x + width; /* right corner */
  points [1].y = y + half_width;
  points [2].x = x + width - thickness; /* right inside corner */
  points [2].y = y + half_width;
  points [3].x = x + half_width; /* bottom inside corner */
  points [3].y = y + width - thickness;

  XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);

  points [0].x = x; /* left corner */
  points [0].y = y + half_width;
  points [1].x = x + half_width; /* top corner */
  points [1].y = y;
  points [2].x = x + half_width; /* top inside corner */
  points [2].y = y + thickness;
  points [3].x = x + thickness; /* left inside corner */
  points [3].y = y + half_width;

  XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);

  points [0].x = x + half_width; /* top corner */
  points [0].y = y;
  points [1].x = x + width; /* right corner */
  points [1].y = y + half_width;
  points [2].x = x + width - thickness; /* right inside corner */
  points [2].y = y + half_width;
  points [3].x = x + half_width; /* top inside corner */
  points [3].y = y + thickness;

  XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
#else
  /*
   *    Draw the bottom first, just incase the regions overlap.
   *    The top should cast the longer shadow.
   */
  npoints = 0;
  points [npoints].x = x; /* left corner */
  points [npoints++].y = y + half_width;
  points [npoints].x = x + half_width; /* bottom corner */
  points [npoints++].y = y + width;
  points [npoints].x = x + width; /* right corner */
  points [npoints++].y = y + half_width;
  points [npoints].x = x + width - thickness; /* right inside corner */
  points [npoints++].y = y + half_width;
  points [npoints].x = x + half_width; /* bottom inside corner */
  points [npoints++].y = y + width - thickness;
  points [npoints].x = x + thickness; /* left inside corner */
  points [npoints++].y = y + half_width;

  XFillPolygon (dpy, window, bottom_gc, 
		points, npoints, Nonconvex, CoordModeOrigin);

  npoints = 0;

  points [npoints].x = x; /* left corner */
  points [npoints++].y = y + half_width;
  points [npoints].x = x + half_width; /* top corner */
  points [npoints++].y = y;
  points [npoints].x = x + width; /* right corner */
  points [npoints++].y = y + half_width;
  points [npoints].x = x + width - thickness; /* right inside corner */
  points [npoints++].y = y + half_width;
  points [npoints].x = x + half_width; /* top inside corner */
  points [npoints++].y = y + thickness;
  points [npoints].x = x + thickness; /* left inside corner */
  points [npoints++].y = y + half_width;

  XFillPolygon (dpy, window, top_gc, points, npoints, Nonconvex,
		CoordModeOrigin);
#endif


  /*
   *    Fill internal area.
   */
  if (enabled)
    {
      points [0].x = x + thickness;
      points [0].y = y + half_width;
      points [1].x = x + half_width;
      points [1].y = y + thickness;
      points [2].x = x + width - thickness;
      points [2].y = y + half_width;
      points [3].x = x + half_width;
      points [3].y = y + width - thickness;
      XFillPolygon (dpy,
		    window,
		    select_gc,
		    points,
		    4, 
		    Convex,
		    CoordModeOrigin
		    );
    }
}

static void
separator_decoration_draw (
			   XlwMenuWidget mw,
			   Window window,
			   int x, int y,
			   unsigned width,
			   Boolean vertical,
			   shadow_type type
			   )
{
  Display *dpy = XtDisplay (mw);
  GC top_gc;
  GC bottom_gc;
  unsigned offset = 0;
  unsigned num_separators = 1;
  unsigned top_line_thickness = 0;
  unsigned bottom_line_thickness = 0;
  Boolean dashed = False;
  int i;

  switch (type)
    {
    case SHADOW_NO_LINE: /* nothing to do */
      return; 
    case SHADOW_DOUBLE_LINE:
      num_separators = 2;
    case SHADOW_SINGLE_LINE:
      top_gc = bottom_gc = mw->menu.foreground_gc;
      top_line_thickness = 1;
      break;
    case SHADOW_DOUBLE_DASHED_LINE:
      num_separators = 2;
    case SHADOW_SINGLE_DASHED_LINE:
      top_gc = bottom_gc = mw->menu.foreground_gc;
      top_line_thickness = 1;
      dashed = True;
      break;
    case SHADOW_DOUBLE_ETCHED_OUT_DASH:
      num_separators = 2;
    case SHADOW_ETCHED_OUT_DASH:
      top_gc = mw->menu.shadow_top_gc;
      bottom_gc = mw->menu.shadow_bottom_gc;
      top_line_thickness = mw->menu.shadow_thickness/2;
      bottom_line_thickness = mw->menu.shadow_thickness - top_line_thickness;
      dashed = True;
      break;
    case SHADOW_DOUBLE_ETCHED_IN_DASH:
      num_separators = 2;
    case SHADOW_ETCHED_IN_DASH:
      top_gc = mw->menu.shadow_bottom_gc;
      bottom_gc = mw->menu.shadow_top_gc;
      top_line_thickness = mw->menu.shadow_thickness/2;
      bottom_line_thickness = mw->menu.shadow_thickness - top_line_thickness;
      dashed = True;
      break;
    case SHADOW_DOUBLE_ETCHED_OUT:
      num_separators = 2;
    case SHADOW_ETCHED_OUT:
      top_gc = mw->menu.shadow_top_gc;
      bottom_gc = mw->menu.shadow_bottom_gc;
      top_line_thickness = mw->menu.shadow_thickness/2;
      bottom_line_thickness = mw->menu.shadow_thickness - top_line_thickness;
      break;
    case SHADOW_DOUBLE_ETCHED_IN:
      num_separators = 2;
    case SHADOW_ETCHED_IN:
    default:
      top_gc = mw->menu.shadow_bottom_gc;
      bottom_gc = mw->menu.shadow_top_gc;
      top_line_thickness = mw->menu.shadow_thickness/2;
      bottom_line_thickness = mw->menu.shadow_thickness - top_line_thickness;
      break;
    }
 
  if (dashed)
    {
      XGCValues values;
      values.line_style = LineOnOffDash;
      if (top_line_thickness > 0)
	XChangeGC (dpy, top_gc, GCLineStyle, &values);
      if (bottom_line_thickness > 0 && bottom_gc != top_gc)
	XChangeGC (dpy, bottom_gc, GCLineStyle, &values);
    }
  
  while (num_separators--)
    {
      for (i = 0; i < top_line_thickness; i++)
	XDrawLine (dpy, window, top_gc, x, y + i, x + width, y + i);
      
      for (i = 0; i < bottom_line_thickness; i++)
	XDrawLine (
		   dpy, window, bottom_gc, 
		   x, y + top_line_thickness + offset + i,
		   x + width, y + top_line_thickness + offset + i
		   );
      y += (top_line_thickness + offset + bottom_line_thickness + 1);
    }

  if (dashed)
    {
      XGCValues values;
      values.line_style = LineSolid;
      if (top_line_thickness > 0)
	XChangeGC (dpy, top_gc, GCLineStyle, &values);
      if (bottom_line_thickness > 0 && bottom_gc != top_gc)
	XChangeGC (dpy, bottom_gc, GCLineStyle, &values);
    }
}

#define SLOPPY_TYPES 0		/* 0=off, 1=error check, 2=easy to please */
#if SLOPPY_TYPES
#if SLOPPY_TYPES < 2

static char *wv_types[] =
{
  "UNSPECIFIED",
  "BUTTON",
  "TOGGLE",
  "RADIO",
  "TEXT",
  "SEPARATOR",
  "CASCADE",
  "PUSHRIGHT",
  "INCREMENTAL"
};

static void
print_widget_value (widget_value *wv, int just_one, int depth)
{
  char d [200];
  int i;
  for (i = 0; i < depth; i++) d[i] = ' ';
  d[depth]=0;
  if (!wv)
    {
      printf ("%s(null widget value pointer)\n", d);
      return;
    }
  printf ("%stype:    %s\n", d, wv_types [wv->type]);
#if 0
  printf ("%sname:    %s\n", d, (wv->name ? wv->name : "(null)"));
#else
  if (wv->name)  printf ("%sname:    %s\n", d, wv->name);
#endif
  if (wv->value) printf ("%svalue:   %s\n", d, wv->value);
  if (wv->key)   printf ("%skey:     %s\n", d, wv->key);
  printf ("%senabled: %d\n", d, wv->enabled);
  if (wv->contents)
    {
      printf ("\n%scontents: \n", d);
      print_widget_value (wv->contents, 0, depth + 5);
    }
  if (!just_one && wv->next)
    {
      printf ("\n");
      print_widget_value (wv->next, 0, depth);
    }
}
#endif

static Boolean
all_dashes_p (char *s)
{
  char *p;
  if (!s || s[0] == '\0')
    return False;
  for (p = s; *p == '-'; p++);

  if (*p == '!' || *p == '\0')
    return True;
  return False;
}
#endif

static widget_value_type
menu_item_type (widget_value *val)
{
  if (val->type != UNSPECIFIED_TYPE)
    return val->type;
  else
    {
#if SLOPPY_TYPES
      if (all_dashes_p(val->name))
	return SEPARATOR_TYPE;
      else if (val->name && val->name[0] == '\0') /* push right */
	return PUSHRIGHT_TYPE;
      else if (val->contents) /* cascade */
	return CASCADE_TYPE;
      else if (val->call_data) /* push button */
	return BUTTON_TYPE;
      else
	return TEXT_TYPE;
#else 
    abort();
#endif 
    }
}

static void
label_button_size (
		   XlwMenuWidget mw,
		   widget_value *val,
		   Boolean in_menubar,
		   unsigned *toggle_width,
		   unsigned *label_width,
		   unsigned *bindings_width,
		   unsigned *height
		   )
{
  *height = (mw->menu.font_ascent + mw->menu.font_descent +
	     2 * mw->menu.vertical_margin +
	     2 * mw->menu.shadow_thickness);
  /* no left column decoration */
  *toggle_width = mw->menu.horizontal_margin + mw->menu.shadow_thickness;;
  
  *label_width  = string_width (mw, resource_widget_value (mw, val));
  *bindings_width =  mw->menu.horizontal_margin + mw->menu.shadow_thickness;
}

static void
label_button_draw (
		   XlwMenuWidget mw, 
		   widget_value *val,
		   Boolean       in_menubar,
		   Boolean       highlighted,
		   Window        window, 
		   int x, int y, 
		   unsigned width,
		   unsigned height,
		   unsigned label_offset,
		   unsigned binding_tab
		   )
{
  int y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin;
  
  if (!label_offset)
    label_offset = mw->menu.shadow_thickness + mw->menu.horizontal_margin;

  /*
   *    Draw the label string.
   */
  string_draw (
	       mw,
	       window,
	       x + label_offset, y + y_offset, 
	       mw->menu.foreground_gc,
	       resource_widget_value (mw, val)
	       );
}

static void
push_button_size (
		  XlwMenuWidget mw,
		  widget_value *val,
		  Boolean in_menubar,
		  unsigned *toggle_width,
		  unsigned *label_width,
		  unsigned *bindings_width,
		  unsigned *height
		  )
{
  /* inherit */
  label_button_size (
		     mw, val, in_menubar,
		     toggle_width, label_width, bindings_width,
		     height
		     );
  
  /* key bindings to display? */
  if (!in_menubar && val->key)
    {
      int w;
#ifdef NEED_MOTIF
      XmString key = XmStringCreateLtoR (val->key, XmSTRING_DEFAULT_CHARSET);
      w = string_width(mw, key);
      XmStringFree (key);
#else
      char *key = val->key;
      w = string_width (mw, key);
#endif
      *bindings_width += w + mw->menu.column_spacing;
    }
}

static void
push_button_draw (
		  XlwMenuWidget mw, 
		  widget_value *val,
		  Boolean       in_menubar,
		  Boolean       highlighted,
		  Window        window, 
		  int x, int y, 
		  unsigned width, unsigned height,
		  unsigned      label_offset,
		  unsigned      binding_offset
		  )
{
  int y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin;
  GC gc;
  shadow_type type;
  Boolean menu_pb = in_menubar && (menu_item_type (val) == BUTTON_TYPE);
  
  /*
   *    Draw the label string.
   */
  if (!label_offset)
    label_offset = mw->menu.shadow_thickness + mw->menu.horizontal_margin;
  
  if (menu_pb)
    {
      if (val->enabled)
	gc = mw->menu.button_gc;
      else
	gc = mw->menu.inactive_button_gc;
    }
  else
    {
      if (val->enabled)
	gc = mw->menu.foreground_gc;
      else
	gc = mw->menu.inactive_gc;
    }

  string_draw (
	       mw,
	       window,
	       x + label_offset, y + y_offset, 
	       gc,
	       resource_widget_value(mw, val)
	       );
  
  /*
   *    Draw the keybindings
   */
  if (val->key)
    {
      if (!binding_offset)
	{
	  unsigned s_width = string_width (mw, resource_widget_value(mw, val));
	  binding_offset = label_offset + s_width +  mw->menu.shadow_thickness;
	}
      binding_draw (mw, window,
		    x + binding_offset + mw->menu.column_spacing,
		    y + y_offset, gc, val->key);
    }
  
  /*
   *    Draw the shadow
   */
  if (menu_pb)
    {
      if (highlighted)
	type = SHADOW_OUT;
      else 
	type = (val->selected ? SHADOW_ETCHED_OUT : SHADOW_ETCHED_IN);
    }
  else
    {
      if (highlighted)
	type = SHADOW_OUT;
      else 
	type = SHADOW_BACKGROUND;
    }

  shadow_draw (mw, window, x, y, width, height, type);
}

static unsigned int
arrow_decoration_height (XlwMenuWidget mw)
{
  unsigned int result =
    (mw->menu.font_ascent + mw->menu.font_descent) / (unsigned int)2;
  
  result += 2 * mw->menu.shadow_thickness;

  if (result > (mw->menu.font_ascent + mw->menu.font_descent))
    result = mw->menu.font_ascent + mw->menu.font_descent;

  return result;
}

static void
cascade_button_size (
		     XlwMenuWidget mw,
		     widget_value *val,
		     Boolean in_menubar,
		     unsigned *toggle_width,
		     unsigned *label_width,
		     unsigned *arrow_width,
		     unsigned *height
		     )
{
  /* inherit */
  label_button_size (
		     mw, val, in_menubar,
		     toggle_width, label_width, arrow_width,
		     height
		     );
  /* we have a pull aside arrow */
  if (!in_menubar)
    {
      *arrow_width += arrow_decoration_height(mw) + mw->menu.column_spacing;
    }
}

static void
cascade_button_draw (
		     XlwMenuWidget mw, 
		     widget_value *val,
		     Boolean       in_menubar,
		     Boolean       highlighted,
		     Window        window, 
		     int x, int y, 
		     unsigned width, unsigned height,
		     unsigned      label_offset,
		     unsigned      binding_offset
		     )
{
  shadow_type type;

  /*
   *    Draw the label string.
   */
  label_button_draw (mw, val, in_menubar, highlighted,
		     window, x, y, width, height, label_offset,
		     binding_offset);

  /*
   *    Draw the pull aside arrow
   */
  if (!in_menubar && val->contents)
    {
      int y_offset;
      unsigned arrow_height = arrow_decoration_height (mw);
      
      y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin +
	(mw->menu.font_ascent+mw->menu.font_descent - arrow_height)/2;
      
      if (!binding_offset)
	{
	  unsigned s_width = string_width(mw, resource_widget_value (mw, val));
	  
	  if (!label_offset)
	    label_offset = mw->menu.shadow_thickness +
	      mw->menu.horizontal_margin;
	  
	  binding_offset = label_offset + s_width +  mw->menu.shadow_thickness;
	}
      
      arrow_decoration_draw (
			     mw,
			     window,
			     x + binding_offset + mw->menu.column_spacing,
			     y + y_offset,
			     arrow_height,
			     highlighted
			     );
    }
  
  /*
   *    Draw the shadow
   */
  if (highlighted)
    type = SHADOW_OUT;
  else
    type = SHADOW_BACKGROUND;
  
  shadow_draw(mw, window, x, y, width, height, type);
}

static unsigned
toggle_decoration_height(XlwMenuWidget mw)
{
  unsigned rv;
  if (mw->menu.indicator_size > 0)
    rv = mw->menu.indicator_size;
  else
    rv = mw->menu.font_ascent;
  
  if (rv > (mw->menu.font_ascent+mw->menu.font_descent))
    rv = mw->menu.font_ascent+mw->menu.font_descent;
  
  return rv;
}

static void
toggle_button_size (
		    XlwMenuWidget mw,
		    widget_value *val,
		    Boolean in_menubar,
		    unsigned *toggle_width,
		    unsigned *label_width,
		    unsigned *bindings_width,
		    unsigned *height
		    )
{
  /* inherit */
  push_button_size (
		    mw, val, in_menubar,
		    toggle_width, label_width, bindings_width,
		    height
		    );
  /* we have a toggle */
  *toggle_width += toggle_decoration_height(mw) + mw->menu.column_spacing;
}

static void
toggle_button_draw (
		    XlwMenuWidget mw, 
		    widget_value *val,
		    Boolean       in_menubar,
		    Boolean highlighted,
		    Window        window, 
		    int x, int y, 
		    unsigned width, unsigned height,
		    unsigned      label_tab,
		    unsigned      binding_tab
		    )
{
  int x_offset;
  int y_offset;
  unsigned t_height = toggle_decoration_height(mw);
  
  /*
   *    Draw a toggle.
   */
  x_offset = mw->menu.shadow_thickness + mw->menu.horizontal_margin;
  y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin;
  y_offset += (mw->menu.font_ascent + mw->menu.font_descent - t_height)/2;
  
  toggle_decoration_draw (mw, window, x + x_offset, y + y_offset,
			  t_height, val->selected);

  /*
   *    Draw the pushbutton parts.
   */
  push_button_draw (mw, val, in_menubar, highlighted, window, x, y, width,
		    height, label_tab, binding_tab);
}

static unsigned
radio_decoration_height(XlwMenuWidget mw)
{
  return toggle_decoration_height(mw);
}

static void
radio_button_draw (
		   XlwMenuWidget mw, 
		   widget_value *val,
		   Boolean       in_menubar,
		   Boolean       highlighted,
		   Window        window, 
		   int x, int y, 
		   unsigned width, unsigned height,
		   unsigned      label_tab,
		   unsigned      binding_tab
		   )
{
  int x_offset;
  int y_offset;
  unsigned r_height = radio_decoration_height(mw);
  
  /*
   *    Draw a toggle.
   */
  x_offset = mw->menu.shadow_thickness + mw->menu.horizontal_margin;
  y_offset = mw->menu.shadow_thickness + mw->menu.vertical_margin;
  y_offset += (mw->menu.font_ascent + mw->menu.font_descent - r_height)/2;

  radio_decoration_draw (mw, window, x + x_offset, y + y_offset, r_height,
			 val->selected);

  /*
   *    Draw the pushbutton parts.
   */
  push_button_draw (mw, val, in_menubar, highlighted, window, x, y, width,
		    height, label_tab, binding_tab);
}

static struct _shadow_names
{
  char *      name;
  shadow_type type;
} shadow_names[] =
{
  /* Motif */
  { "singleLine", SHADOW_SINGLE_LINE },
  { "doubleLine", SHADOW_DOUBLE_LINE },
  { "singleDashedLine", SHADOW_SINGLE_DASHED_LINE },
  { "doubleDashedLine", SHADOW_DOUBLE_DASHED_LINE },
  { "noLine", SHADOW_NO_LINE },
  { "shadowEtchedIn", SHADOW_ETCHED_IN },
  { "shadowEtchedOut", SHADOW_ETCHED_OUT },
  { "shadowEtchedInDash", SHADOW_ETCHED_IN_DASH },
  { "shadowEtchedOutDash", SHADOW_ETCHED_OUT_DASH },
  /* non-Motif */
  { "shadowDoubleEtchedIn", SHADOW_DOUBLE_ETCHED_IN },
  { "shadowDoubleEtchedOut", SHADOW_DOUBLE_ETCHED_OUT },
  { "shadowDoubleEtchedInDash", SHADOW_DOUBLE_ETCHED_IN_DASH },
  { "shadowDoubleEtchedOutDash", SHADOW_DOUBLE_ETCHED_OUT_DASH }
};

static shadow_type
separator_type (char *name)
{
  int i;

  if (name)
    {
      for (i = 0; i < XtNumber(shadow_names); i++ )
	{
	  if (strcmp (name, shadow_names[i].name) == 0)
	    return shadow_names[i].type;
	}
    }
  return SHADOW_BACKGROUND;
}

static unsigned
separator_decoration_height (XlwMenuWidget mw, widget_value *val)
{

  switch (separator_type(val->value))
    {
    case SHADOW_NO_LINE:
    case SHADOW_SINGLE_LINE:
    case SHADOW_SINGLE_DASHED_LINE:
      return 1;
    case SHADOW_DOUBLE_LINE:
    case SHADOW_DOUBLE_DASHED_LINE:
      return 3;
    case SHADOW_DOUBLE_ETCHED_OUT:
    case SHADOW_DOUBLE_ETCHED_IN:
    case SHADOW_DOUBLE_ETCHED_OUT_DASH:
    case SHADOW_DOUBLE_ETCHED_IN_DASH:
      return (1 + 2 * mw->menu.shadow_thickness);
    case SHADOW_ETCHED_OUT:
    case SHADOW_ETCHED_IN:
    default:
      return mw->menu.shadow_thickness;
    }
}

static void
separator_size (XlwMenuWidget mw,
		widget_value *val,
		Boolean in_menubar,
		unsigned *toggle_width,
		unsigned *label_width,
		unsigned *rest_width,
		unsigned *height
		)
{
  *height = separator_decoration_height (mw, val);
  *label_width = 1;
  *toggle_width = *rest_width = 0;
}

static void
separator_draw (XlwMenuWidget mw, 
		widget_value *val,
		Boolean       in_menubar,
		Boolean       highlighted,
		Window        window, 
		int x, int y, 
		unsigned width, unsigned height,
		unsigned      label_tab,
		unsigned      binding_tab
		)
{
  unsigned sep_width;

  if (in_menubar)
    sep_width = height;
  else
    sep_width = width;

  separator_decoration_draw (mw,
			     window,
			     x,
			     y,
			     sep_width,
			     in_menubar,
			     separator_type(val->value)
			     );
}

static void
pushright_size (XlwMenuWidget mw,
		widget_value *val,
		Boolean in_menubar,
		unsigned *toggle_width,
		unsigned *label_width,
		unsigned *rest_width,
		unsigned *height
)
{
  *height = *label_width = *toggle_width = *rest_width = 0;
}

static void
size_menu_item (XlwMenuWidget mw,
		widget_value *val,
		int horizontal,
		unsigned *toggle_width,
		unsigned *label_width,
		unsigned *rest_width,
		unsigned *height
)
{

  void (*function_ptr) (
			XlwMenuWidget mw,
			widget_value *val,
			Boolean in_menubar,
			unsigned *toggle_width,
			unsigned *label_width,
			unsigned *rest_width,
			unsigned *height
			);
  switch (menu_item_type (val))
    {
    case TOGGLE_TYPE:
    case RADIO_TYPE:
      function_ptr = toggle_button_size;
      break;
    case SEPARATOR_TYPE:
      function_ptr = separator_size;
      break;
    case INCREMENTAL_TYPE:
    case CASCADE_TYPE:
      function_ptr = cascade_button_size;
      break;
    case BUTTON_TYPE:
      function_ptr = push_button_size;
      break;
    case PUSHRIGHT_TYPE:
      function_ptr = pushright_size;
      break;
    case TEXT_TYPE:
    default:
      function_ptr = label_button_size;
      break;
    }

  (*function_ptr) (
		   mw,
		   val,
		   horizontal,
		   toggle_width,
		   label_width,
		   rest_width,
		   height
		   );
}

static void
display_menu_item (
		   XlwMenuWidget mw,
		   widget_value *val,
		   window_state *ws,
		   XPoint *where,
		   Boolean highlighted,
		   Boolean horizontal,
		   Boolean just_compute
		   )
{
  
  int x = where->x /* + mw->menu.shadow_thickness */ ;
  int y = where->y /* + mw->menu.shadow_thickness */ ;
  unsigned toggle_width;
  unsigned label_width;
  unsigned binding_width;
  unsigned width;
  unsigned height;
  unsigned label_tab;
  unsigned binding_tab;
  void (*function_ptr) (
			XlwMenuWidget mw,
			widget_value *val,
			Boolean in_menubar,
			Boolean highlighted,
			Window        window, 
			int x, int y, 
			unsigned width, unsigned height,
			unsigned      label_tab,
			unsigned      binding_tab
			);

  size_menu_item (
		  mw, val, horizontal,
		  &toggle_width, &label_width, &binding_width, &height
		  );

  if (horizontal)
    {
      width = toggle_width + label_width + binding_width;
      height = ws->height - 2 * mw->menu.shadow_thickness;
    }
  else
    {
      width = ws->width - 2 * mw->menu.shadow_thickness;
      toggle_width = ws->toggle_width;
      label_width = ws->label_width;
    }
  
  where->x += width;
  where->y += height;
  
  if (just_compute)
    return;
  
  label_tab = toggle_width;
  binding_tab = toggle_width + label_width;

  switch (menu_item_type (val))
    {
    case TOGGLE_TYPE:
      function_ptr = toggle_button_draw;
      break;
    case RADIO_TYPE:
      function_ptr = radio_button_draw;
      break;
    case SEPARATOR_TYPE:
      function_ptr = separator_draw;
      break;
    case INCREMENTAL_TYPE:
    case CASCADE_TYPE:
      function_ptr = cascade_button_draw;
      break;
    case BUTTON_TYPE:
      function_ptr = push_button_draw;
      break;
    case TEXT_TYPE:
      function_ptr = label_button_draw;
      break;
    default: /* do no drawing */
      return;
    }

  (*function_ptr) (
		   mw,
		   val,
		   horizontal,
		   highlighted,
		   ws->window, 
		   x, y, 
		   width, height,
		   label_tab,
		   binding_tab
		   );
}

static void
size_menu (XlwMenuWidget mw, int level)
{
  unsigned      toggle_width;
  unsigned	label_width;
  unsigned	rest_width;
  unsigned	height;
  unsigned	max_toggle_width = 0;
  unsigned	max_label_width = 0;
  unsigned	max_rest_width = 0;
  unsigned	max_height = 0;
  int		horizontal_p = mw->menu.horizontal && (level == 0);
  widget_value*	val;
  window_state*	ws;

  if (level >= mw->menu.old_depth)
    abort ();

  ws = &mw->menu.windows [level];  

  for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
    {
      size_menu_item (
		      mw,
		      val,
		      horizontal_p,
		      &toggle_width,
		      &label_width,
		      &rest_width,
		      &height
		      );
      if (horizontal_p)
	{
	  max_label_width += toggle_width + label_width + rest_width;
	  if (height > max_height)
	    max_height = height;
	}
      else
	{
	  if (toggle_width > max_toggle_width)
	    max_toggle_width = toggle_width;
	  if (label_width > max_label_width)
	    max_label_width = label_width;
	  if (rest_width > max_rest_width)
	    max_rest_width = rest_width;
	  max_height += height;
	}
    }
  
  ws->height = max_height;
  ws->width = max_label_width + max_rest_width + max_toggle_width;
  ws->toggle_width = max_toggle_width;
  ws->label_width = max_label_width;

  ws->width += 2 * mw->menu.shadow_thickness;
  ws->height += 2 * mw->menu.shadow_thickness;
}

static void
display_menu (XlwMenuWidget mw, int level, Boolean just_compute_p,
	      XPoint *highlighted_pos, XPoint *hit, widget_value **hit_return,
	      widget_value *this, widget_value *that)
{
  widget_value *val;
  widget_value *following_item;
  window_state *ws;
  XPoint	where;
  int horizontal_p = mw->menu.horizontal && (level == 0);
  int highlighted_p;
  int just_compute_this_one_p;

  if (level >= mw->menu.old_depth)
    abort ();

  if (level < mw->menu.old_depth - 1)
    following_item = mw->menu.old_stack [level + 1];
  else 
    following_item = NULL;

#if SLOPPY_TYPES == 1
  puts("===================================================================");
  print_widget_value (following_item, 1, 0);
#endif 
  if (following_item
      && following_item->type == CASCADE_TYPE
      && following_item->contents
      && following_item->contents->type == INCREMENTAL_TYPE)
    {
      /* okay, we're now doing a lisp callback to incrementally generate
	 more of the menu. */
      XtCallCallbackList ((Widget)mw,
			  mw->menu.open,
			  (XtPointer)following_item->contents);
#if SLOPPY_TYPES == 1
  puts("==== NEW ==== NEW ==== NEW ==== NEW ==== NEW ==== NEW ==== NEW ====");
  print_widget_value(following_item, 1, 0);
#endif 
    }

  if (hit)
    *hit_return = NULL;

  where.x = mw->menu.shadow_thickness;
  where.y = mw->menu.shadow_thickness;

  ws = &mw->menu.windows [level];
  for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
    {
      XPoint start;

      highlighted_p = (val == following_item);
      /* If this is the partition (the dummy item which says that menus
	 after this should be flushright) then figure out how big the
	 following items are.  This means we walk down the tail of the
	 list twice, but that's no big deal - it's short.
       */
      if (horizontal_p && (menu_item_type (val) == PUSHRIGHT_TYPE))
	{
	  widget_value *rest;
	  XPoint flushright_size;
	  int new_x;
	  flushright_size.x = 0;
	  flushright_size.y = 0;
	  for (rest = val; rest; rest = rest->next)
	    display_menu_item (mw, rest, ws, &flushright_size,
			       highlighted_p, horizontal_p, True);
	  new_x = ws->width - (flushright_size.x + mw->menu.shadow_thickness);
	  if (new_x > where.x)
	    where.x = new_x;
	  /* We know what we need; don't draw this item. */
	  continue;
	}

      if (highlighted_p && highlighted_pos)
	{
	  if (horizontal_p)
	    highlighted_pos->x = where.x;
	  else
	    highlighted_pos->y = where.y;
	}
      
      just_compute_this_one_p =
	just_compute_p || ((this || that) && val != this &&  val != that);

      start.x = where.x;
      start.y = where.y;
      display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
			 just_compute_this_one_p);

      if (highlighted_p && highlighted_pos)
	{
	  if (horizontal_p)
	    highlighted_pos->y = ws->height;
	  else
	    highlighted_pos->x = ws->width;
	}

      if (hit && !*hit_return && (val->type != SEPARATOR_TYPE))
	{
	  if (horizontal_p && hit->x > start.x && hit->x < where.x)
	    *hit_return = val;
	  else if (!horizontal_p && hit->y > start.y && hit->y < where.y)
	    *hit_return = val;
	}

      if (horizontal_p)
	where.y = mw->menu.shadow_thickness;
      else
	where.x = mw->menu.shadow_thickness;
    }

  /* Draw slab edges around menu */
  if (!just_compute_p)
    shadow_draw(mw, ws->window, 0, 0, ws->width, ws->height, SHADOW_OUT);
}

/* Motion code */
static void
set_new_state (XlwMenuWidget mw, widget_value *val, int level)
{
  int i;
  
  mw->menu.new_depth = 0;
  for (i = 0; i < level; i++)
    push_new_stack (mw, mw->menu.old_stack [i]);
  if (val)
    push_new_stack (mw, val);
}

static void
make_windows_if_needed (XlwMenuWidget mw, int n)
{
  int i;
  int start_at;
  XSetWindowAttributes xswa;
  int mask;
#define ROOT_PARENT
#ifdef ROOT_PARENT
  Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
#endif 
  window_state *windows;
  
  if (mw->menu.windows_length >= n)
    return;

  xswa.save_under = True;
  xswa.override_redirect = True;
  xswa.background_pixel = mw->core.background_pixel;
  xswa.border_pixel = mw->core.border_pixel;
  xswa.event_mask = (ExposureMask | ButtonMotionMask
		     | ButtonReleaseMask | ButtonPressMask);
  xswa.cursor = mw->menu.cursor_shape;
  mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
    | CWEventMask | CWCursor;

  if (mw->menu.use_backing_store)
    {
      xswa.backing_store = Always;
      mask |= CWBackingStore;
    }
  
  if (!mw->menu.windows)
    {
      mw->menu.windows =
	(window_state *) XtMalloc (n * sizeof (window_state));
      start_at = 0;
    }
  else
    {
      mw->menu.windows =
	(window_state *) XtRealloc ((char*)mw->menu.windows,
				    n * sizeof (window_state));
      start_at = mw->menu.windows_length;
    }
  mw->menu.windows_length = n;

  windows = mw->menu.windows;

  for (i = start_at; i < n; i++)
   {
     windows [i].x = 0;
     windows [i].y = 0;
     windows [i].width = 1;
     windows [i].height = 1;
     windows [i].window =
       XCreateWindow (XtDisplay (mw),
#ifdef ROOT_PARENT
		      root,
#else 
		      ((i >  0)
		       ? windows[0].window
		       : XtWindow (XtParent (mw))),
#endif 
		      0, 0, 1, 1,
		      0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
  }
}

/* Make the window fit in the screen */
static void
fit_to_screen (XlwMenuWidget mw, window_state *ws, window_state *previous_ws,
	       Boolean horizontal_p)
{
  int screen_width = WidthOfScreen (XtScreen (mw));
  int screen_height = HeightOfScreen (XtScreen (mw));

  if (ws->x < 0)
    ws->x = 0;
  else if ((int) (ws->x + ws->width) > screen_width)
    {
      if (!horizontal_p)
	ws->x = previous_ws->x - ws->width;
      else
	{
	  ws->x = screen_width - ws->width;

	  /* This check is to make sure we cut off the right side
             instead of the left side if the menu is wider than the
             screen. */
	  if (ws->x < 0)
	    ws->x = 0;
	}
    }
  if (ws->y < 0)
    ws->y = 0;
  else if ((int) (ws->y + ws->height) > screen_height)
    {
      if (horizontal_p)
	{
	  /* A pulldown must either be entirely above or below the menubar.
	     If we're here, the pulldown doesn't fit below the menubar, so
             let's determine if it will fit above the menubar.
             Only put it above if there is more room above than below.
	     Note shadow_thickness offset to allow for slab surround.
	     */
	  if (ws->y > (screen_height / 2))
	    ws->y = previous_ws->y - ws->height + mw->menu.shadow_thickness;
	}
      else
	{
	  ws->y = screen_height - ws->height;
	   /* if it's taller than the screen, display the topmost part
	      that will fit, beginning at the top of the screen. */
	  if (ws->y < 0)
	    ws->y = 0;
	}
    }
}

/* Updates old_stack from new_stack and redisplays. */
static void
remap_menubar (XlwMenuWidget mw)
{
  int i;
  int last_same;
  XPoint selection_position;
  int old_depth = mw->menu.old_depth;
  int new_depth = mw->menu.new_depth;
  widget_value **old_stack;
  widget_value **new_stack;
  window_state *windows;
  widget_value *old_selection;
  widget_value *new_selection;

  /* Check that enough windows and old_stack are ready. */
  make_windows_if_needed (mw, new_depth);
  make_old_stack_space (mw, new_depth);
  windows = mw->menu.windows;
  old_stack = mw->menu.old_stack;
  new_stack = mw->menu.new_stack;

  /* compute the last identical different entry */
  for (i = 1; i < old_depth && i < new_depth; i++)
    if (old_stack [i] != new_stack [i])
      break;
  last_same = i - 1;

  /* Memorize the previously selected item to be able to refresh it */
  old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
  if (old_selection && !old_selection->enabled)
    old_selection = NULL;
  new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
  if (new_selection && !new_selection->enabled)
    new_selection = NULL;

  /* updates old_state from new_state.  It has to be done now because
     display_menu (called below) uses the old_stack to know what to display. */
  for (i = last_same + 1; i < new_depth; i++)
    old_stack [i] = new_stack [i];
  mw->menu.old_depth = new_depth;

  /* refresh the last seletion */
  selection_position.x = 0;
  selection_position.y = 0;
  display_menu (mw, last_same, new_selection == old_selection,
		&selection_position, NULL, NULL, old_selection, new_selection);

  /* Now popup the new menus */
  for (i = last_same + 1; i < new_depth && new_stack [i]->contents; i++)
    {
      window_state *previous_ws = &windows [i - 1];
      window_state *ws = &windows [i];

      ws->x = previous_ws->x + selection_position.x;
      ws->y = previous_ws->y + selection_position.y;

      /* take into account the slab around the new menu */
      ws->y -= mw->menu.shadow_thickness;

      size_menu (mw, i);

      fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);

      XClearWindow (XtDisplay (mw), ws->window);
      XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
			 ws->width, ws->height);
      XMapRaised (XtDisplay (mw), ws->window);
      display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
    }

  /* unmap the menus that popped down */
  for (i = new_depth - 1; i < old_depth; i++)
    if (i >= new_depth || !new_stack [i]->contents)
      XUnmapWindow (XtDisplay (mw), windows [i].window);
}

static Boolean
motion_event_is_in_menu (XlwMenuWidget mw, XMotionEvent *ev, int level,
			 XPoint *relative_pos)
{
  window_state *ws = &mw->menu.windows [level];
  int x = level == 0 ? ws->x : ws->x + mw->menu.shadow_thickness;
  int y = level == 0 ? ws->y : ws->y + mw->menu.shadow_thickness;
  relative_pos->x = ev->x_root - x;
  relative_pos->y = ev->y_root - y;
  return (x < ev->x_root && ev->x_root < (int) (x + ws->width)
	  && y < ev->y_root && ev->y_root < (int) (y + ws->height));
}

static Boolean
map_event_to_widget_value (XlwMenuWidget mw, XMotionEvent *ev,
			   widget_value **val_ptr, int *level,
			   Boolean *inside_menu)
{
  int 		i;
  XPoint	relative_pos;
  window_state*	ws;

  *val_ptr = NULL;
  *inside_menu = False;
  
  /* Find the window */
  for (i = mw->menu.old_depth - 1; i >= 0; i--)
    {
      ws = &mw->menu.windows [i];
      if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
	{
	  *inside_menu = True;	/* special logic for menubar below... */
	  if ((ev->type == ButtonPress) ||
	      (ev->state != 0))
	    {
	      display_menu (mw, i, True, NULL, &relative_pos,
			    val_ptr, NULL, NULL);
	      if (*val_ptr)
		{
		  *level = i + 1;
		  *inside_menu = True;
		  return True;
		}
	      else if (mw->menu.horizontal || i == 0)
		{
		  /* if we're clicking on empty part of the menubar, then
		     unpost the stay-up menu */
		  *inside_menu = False;
		}
	    }
	}
    }
  return False;
}

/* Procedures */
static void
make_drawing_gcs (XlwMenuWidget mw)
{
  XGCValues xgcv;
  unsigned long flags = (GCFont | GCForeground | GCBackground);

#ifdef NEED_MOTIF
  xgcv.font = default_font_of_font_list (mw->menu.font_list)->fid;
#else
  xgcv.font = mw->menu.font->fid;
#endif

  xgcv.foreground = mw->core.background_pixel;
  xgcv.background = mw->menu.foreground;
  mw->menu.background_gc = XtGetGC ((Widget)mw, flags, &xgcv);

  xgcv.foreground = mw->menu.foreground;
  xgcv.background = mw->core.background_pixel;
  mw->menu.foreground_gc = XtGetGC ((Widget)mw, flags, &xgcv);

  if (mw->menu.select_color != (Pixel)-1)
    {
      xgcv.foreground = mw->menu.select_color;
    }
  else
    {
      Display *dpy = XtDisplay(mw);
      if (CellsOfScreen(DefaultScreenOfDisplay(dpy)) <= 2)
	{ /* mono */
	  xgcv.foreground = mw->menu.foreground;
	}
      else
	{ /* color */
	  XColor xcolor;
	  Display *dpy = XtDisplay ((Widget) mw);
	  Colormap cmap = DefaultColormapOfScreen (XtScreen ((Widget) mw));
	  xcolor.pixel = mw->core.background_pixel;
	  XQueryColor (dpy, cmap, &xcolor);
	  xcolor.red   *= 0.85;
	  xcolor.green *= 0.85;
	  xcolor.blue  *= 0.85;
	  if (XAllocColor (dpy, cmap, &xcolor))
	    xgcv.foreground = xcolor.pixel;
	}
    }
  xgcv.background = mw->core.background_pixel;
  mw->menu.select_gc = XtGetGC ((Widget)mw, flags, &xgcv);
  
  xgcv.foreground = mw->menu.foreground;
  xgcv.background = mw->core.background_pixel;
  xgcv.fill_style = FillStippled;
  xgcv.stipple = mw->menu.gray_pixmap;
  mw->menu.inactive_gc = XtGetGC ((Widget)mw,
				  (flags | GCFillStyle | GCStipple),
				  &xgcv);

  xgcv.foreground = mw->menu.button_foreground;
  xgcv.background = mw->core.background_pixel;
  mw->menu.button_gc = XtGetGC ((Widget)mw, flags, &xgcv);
  
  xgcv.fill_style = FillStippled;
  xgcv.stipple = mw->menu.gray_pixmap;
  mw->menu.inactive_button_gc = XtGetGC ((Widget)mw,
				  (flags | GCFillStyle | GCStipple),
					 &xgcv);
}

static void
release_drawing_gcs (XlwMenuWidget mw)
{
  XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
  XtReleaseGC ((Widget) mw, mw->menu.button_gc);
  XtReleaseGC ((Widget) mw, mw->menu.inactive_gc);
  XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
  XtReleaseGC ((Widget) mw, mw->menu.background_gc);
  XtReleaseGC ((Widget) mw, mw->menu.select_gc);
  /* let's get some segvs if we try to use these... */
  mw->menu.foreground_gc = (GC) -1;
  mw->menu.button_gc = (GC) -1;
  mw->menu.inactive_gc = (GC) -1;
  mw->menu.inactive_button_gc = (GC) -1;
  mw->menu.background_gc = (GC) -1;
  mw->menu.select_gc = (GC) -1;
}

#define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
		   ? ((unsigned long) (x)) : ((unsigned long) (y)))

static void
make_shadow_gcs (XlwMenuWidget mw)
{
  XGCValues xgcv;
  unsigned long pm = 0;
  Display *dpy = XtDisplay ((Widget) mw);
  Colormap cmap = DefaultColormapOfScreen (XtScreen ((Widget) mw));
  XColor topc, botc;
  int top_frobbed = 0, bottom_frobbed = 0;

  if (mw->menu.top_shadow_color == -1)
    mw->menu.top_shadow_color = mw->core.background_pixel;
  if (mw->menu.bottom_shadow_color == -1)
    mw->menu.bottom_shadow_color = mw->menu.foreground;

  if (mw->menu.top_shadow_color == mw->core.background_pixel ||
      mw->menu.top_shadow_color == mw->menu.foreground)
    {
      topc.pixel = mw->core.background_pixel;
      XQueryColor (dpy, cmap, &topc);
      /* don't overflow/wrap! */
      topc.red   = MINL (65535, topc.red   * 1.2);
      topc.green = MINL (65535, topc.green * 1.2);
      topc.blue  = MINL (65535, topc.blue  * 1.2);
      if (XAllocColor (dpy, cmap, &topc))
	{
          if (topc.pixel == mw->core.background_pixel)
	    {
	      XFreeColors( dpy, cmap, &topc.pixel, 1, 0);
	      topc.red   = MINL (65535, topc.red   + 0x8000);
	      topc.green = MINL (65535, topc.green + 0x8000);
	      topc.blue  = MINL (65535, topc.blue  + 0x8000);
	      if (XAllocColor (dpy, cmap, &topc))
		{
		  mw->menu.top_shadow_color = topc.pixel;
		}  
	    }
	  else
	    {
	      mw->menu.top_shadow_color = topc.pixel;
	    }

	  top_frobbed = 1;
	}
    }
  if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
      mw->menu.bottom_shadow_color == mw->core.background_pixel)
    {
      botc.pixel = mw->core.background_pixel;
      XQueryColor (dpy, cmap, &botc);
      botc.red   *= 0.6;
      botc.green *= 0.6;
      botc.blue  *= 0.6;
      if (XAllocColor (dpy, cmap, &botc))
	{
	  if (botc.pixel == mw->core.background_pixel)
	    {
	      XFreeColors (dpy, cmap, &botc.pixel, 1, 0);
	      botc.red   = MINL (65535, botc.red   + 0x4000);
	      botc.green = MINL (65535, botc.green + 0x4000);
	      botc.blue  = MINL (65535, botc.blue  + 0x4000);
	      if (XAllocColor (dpy, cmap, &botc))
		{
		  mw->menu.bottom_shadow_color = botc.pixel;
		}  
	    }
	  else
	    {
	      mw->menu.bottom_shadow_color = botc.pixel;
	    }

          bottom_frobbed = 1;
	}
    }

  if (top_frobbed && bottom_frobbed)
    {
      int top_avg = ((topc.red / 3) + (topc.green / 3) + (topc.blue / 3));
      int bot_avg = ((botc.red / 3) + (botc.green / 3) + (botc.blue / 3));
      if (bot_avg > top_avg)
	{
	  Pixel tmp = mw->menu.top_shadow_color;
	  mw->menu.top_shadow_color = mw->menu.bottom_shadow_color;
	  mw->menu.bottom_shadow_color = tmp;
	}
      else if (topc.pixel == botc.pixel)
	{
	  if (botc.pixel == mw->menu.foreground)
	    mw->menu.top_shadow_color = mw->core.background_pixel;
	  else
	    mw->menu.bottom_shadow_color = mw->menu.foreground;
	}
    }

  if (!mw->menu.top_shadow_pixmap &&
      mw->menu.top_shadow_color == mw->core.background_pixel)
    {
      mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
      mw->menu.top_shadow_color = mw->menu.foreground;
    }
  if (!mw->menu.bottom_shadow_pixmap &&
      mw->menu.bottom_shadow_color == mw->core.background_pixel)
    {
      mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
      mw->menu.bottom_shadow_color = mw->menu.foreground;
    }

  xgcv.fill_style = FillOpaqueStippled;
  xgcv.foreground = mw->menu.top_shadow_color;
  xgcv.background = mw->core.background_pixel;
  xgcv.stipple = mw->menu.top_shadow_pixmap;
  pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
  mw->menu.shadow_top_gc =
    XtGetGC((Widget)mw, GCForeground|GCBackground|pm, &xgcv);

  xgcv.foreground = mw->menu.bottom_shadow_color;
  xgcv.stipple = mw->menu.bottom_shadow_pixmap;
  pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
  mw->menu.shadow_bottom_gc =
    XtGetGC ((Widget)mw, GCForeground|GCBackground|pm, &xgcv);
}


static void
release_shadow_gcs (XlwMenuWidget mw)
{
  XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
  XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
}

static void
extract_font_extents (XlwMenuWidget mw)
{
#ifdef NEED_MOTIF
  /* Find the maximal ascent/descent of the fonts in the font list
     so that all menu items can be the same height... */
  mw->menu.font_ascent  = 0;
  mw->menu.font_descent = 0;
  
  {
    XmFontContext context;
#if (XmVersion >= 1002)
    XmFontListEntry fontentry;
#else    
    XmStringCharSet charset;
#endif
    XFontStruct *font;

    if (! XmFontListInitFontContext (&context, mw->menu.font_list))
      abort ();
#if (XmVersion >= 1002)
    /* There is a BUG in the 1.2 version of XmFontListGetNextFont() (or more
       specifically, in _XmGetFirstFont()) that can cause a null pointer to be
       passed to XFontsOfFontSet.  Use XmFontListNextEntry(), which is the
       newer equivalent, instead.  Also, it supports font sets, and the
       older function doesn't. */
    while ((fontentry = XmFontListNextEntry (context)))
      {
	char *one_of_them;
	XmFontType rettype; 
	
	one_of_them = XmFontListEntryGetFont (fontentry, &rettype);
	if (rettype == XmFONT_IS_FONTSET)
	  {
	    XFontSet fontset = (XFontSet) one_of_them;
	    XFontStruct **fontstruct_list;
	    char **fontname_list;
	    int fontcount = XFontsOfFontSet (fontset, &fontstruct_list,
					     &fontname_list);
	    while (--fontcount >= 0)
	      {
		font = fontstruct_list[fontcount];
		if (font->ascent > (int) mw->menu.font_ascent)
		  mw->menu.font_ascent = font->ascent;
		if (font->descent > (int) mw->menu.font_descent)
		  mw->menu.font_descent = font->descent;
	      }
	  }
	else /* XmFONT_IS_FONT */
	  {
	    font = (XFontStruct *) one_of_them;
	    if (font->ascent > (int) mw->menu.font_ascent)
	      mw->menu.font_ascent = font->ascent;
	    if (font->descent > (int) mw->menu.font_descent)
	      mw->menu.font_descent = font->descent;
	  }
      }
#else /* motif 1.1 */
    while (XmFontListGetNextFont (context, &charset, &font))
      {
	if (font->ascent > (int) mw->menu.font_ascent)
	  mw->menu.font_ascent = font->ascent;
	if (font->descent > (int) mw->menu.font_descent)
	  mw->menu.font_descent = font->descent;
	XtFree (charset);
      }
#endif	
    XmFontListFreeFontContext (context);
  }
#else
  mw->menu.font_ascent  = mw->menu.font->ascent;
  mw->menu.font_descent = mw->menu.font->descent;
#endif
}

#ifdef NEED_MOTIF
static XFontStruct *
default_font_of_font_list (XmFontList font_list)
{
  XFontStruct *font = 0;
# if 0
  /* Xm/Label.c does this: */
  _XmFontListGetDefaultFont (font_list, &font);
# else  /* !0 */
  {
    XmFontContext context;
#if (XmVersion >= 1002)
    XmFontListEntry fontentry;
    XmFontType rettype;
    char *one_of_them;
#else    
    XmStringCharSet charset;
#endif
    
    if (! XmFontListInitFontContext (&context, font_list))
      abort ();
#if (XmVersion >= 1002)
    /* There is a BUG in the 1.2 version of XmFontListGetNextFont() (or more
       specifically, in _XmGetFirstFont()) that can cause a null pointer to be
       passed to XFontsOfFontSet.  Use XmFontListNextEntry(), which is the
       newer equivalent, instead. */
    fontentry = XmFontListNextEntry (context);
    one_of_them = XmFontListEntryGetFont (fontentry, &rettype);
    if (rettype == XmFONT_IS_FONTSET)
      {
	XFontSet fontset = (XFontSet) one_of_them;
	XFontStruct **fontstruct_list;
	char **fontname_list;
	(void) XFontsOfFontSet (fontset, &fontstruct_list, &fontname_list);
	font = fontstruct_list[0];
      }
    else /* XmFONT_IS_FONT */
      {
	font = (XFontStruct *) one_of_them;
      }
#else    
    if (! XmFontListGetNextFont (context, &charset, &font))
      abort ();
    XtFree (charset);
#endif    
    XmFontListFreeFontContext (context);
  }
# endif /* !0 */

  if (! font) abort ();
  return font;
}
#endif

static void
XlwMenuInitialize (Widget request, Widget new, ArgList args,
		   Cardinal *num_args)
{
  /* Get the GCs and the widget size */
  XlwMenuWidget mw = (XlwMenuWidget)new;
  
  XSetWindowAttributes xswa;
  int mask;
  
  Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
  Display *display = XtDisplay (mw);
  
/*  mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
  mw->menu.cursor = mw->menu.cursor_shape;
  
  mw->menu.gray_pixmap = XCreatePixmapFromBitmapData (display, window,
						      (char *) gray_bits,
						      gray_width,
						      gray_height, 1, 0, 1);
  
#ifdef NEED_MOTIF
  /* The menu.font_list slot came from the *fontList resource (Motif standard.)
     The menu.font_list_2 slot came from the *font resource, for backward
     compatibility with older versions of this code, and consistency with the
     rest of emacs.  If both font and fontList are specified, we use font.
     If only one is specified, we use that.  If neither are specified, we
     use the "fallback" value.  What a kludge!!!

     Note that this has the bug that a more general wildcard like "*fontList:"
     will override a more specific resoure like "Emacs*menubar.font:".  But
     I can't think of a way around that.
   */
  if (mw->menu.font_list)	  /* if *fontList is specified, use that */
    ;
  else if (mw->menu.font_list_2)  /* else if *font is specified, use that */
    mw->menu.font_list = mw->menu.font_list_2;
  else				  /* otherwise use default */
    mw->menu.font_list = mw->menu.fallback_font_list;
#endif

  make_drawing_gcs (mw);
  make_shadow_gcs (mw);
  extract_font_extents (mw);

  xswa.background_pixel = mw->core.background_pixel;
  xswa.border_pixel = mw->core.border_pixel;
  mask = CWBackPixel | CWBorderPixel;
  
  mw->menu.popped_up = False;
  mw->menu.pointer_grabbed = False;
  mw->menu.next_release_must_exit = False;
  
  mw->menu.old_depth = 1;
  mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
  mw->menu.old_stack_length = 1;
  mw->menu.old_stack [0] = mw->menu.contents;
  
  mw->menu.new_depth = 0;
  mw->menu.new_stack = 0;
  mw->menu.new_stack_length = 0;
  push_new_stack (mw, mw->menu.contents);
  
  mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
  mw->menu.windows_length = 1;
  mw->menu.windows [0].x = 0;
  mw->menu.windows [0].y = 0;
  mw->menu.windows [0].width = 0;
  mw->menu.windows [0].height = 0;
  size_menu (mw, 0);
  
  mw->core.width = mw->menu.windows [0].width;
  mw->core.height = mw->menu.windows [0].height;
}

static void
XlwMenuClassInitialize (void)
{
}

static void
XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
{
  XlwMenuWidget mw = (XlwMenuWidget)w;
  XSetWindowAttributes xswa;
  int mask;

  (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
    (w, valueMask, attributes);

  xswa.save_under = True;
  xswa.cursor = mw->menu.cursor_shape;
  mask = CWSaveUnder | CWCursor;
  if (mw->menu.use_backing_store)
    {
      xswa.backing_store = Always;
      mask |= CWBackingStore;
    }
  XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);

  mw->menu.windows [0].window = XtWindow (w);
  mw->menu.windows [0].x = w->core.x;
  mw->menu.windows [0].y = w->core.y;
  mw->menu.windows [0].width = w->core.width;
  mw->menu.windows [0].height = w->core.height;
}

/* Only the toplevel menubar/popup is a widget so it's the only one that
   receives expose events through Xt.  So we repaint all the other panes
   when receiving an Expose event. */
static void 
XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
{
  XlwMenuWidget mw = (XlwMenuWidget)w;
  int i;

  if (mw->core.being_destroyed) return;

  for (i = 0; i < mw->menu.old_depth; i++)
    display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
  set_new_state (mw, NULL, mw->menu.old_depth); /* #### - ??? */
  remap_menubar (mw);		/* #### - do these two lines do anything? */
}

static void 
XlwMenuDestroy (Widget w)
{
  int i;
  XlwMenuWidget mw = (XlwMenuWidget) w;

  if (mw->menu.pointer_grabbed)
    {
      XtUngrabPointer (w, CurrentTime);
      mw->menu.pointer_grabbed = False;
    }
  
  release_drawing_gcs (mw);
  release_shadow_gcs (mw);

  /* this doesn't come from the resource db but is created explicitly
     so we must free it ourselves. */
  XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
  mw->menu.gray_pixmap = (Pixmap) -1;

  /* Don't free mw->menu.contents because that comes from our creator.
     The `*_stack' elements are just pointers into `contents' so leave
     that alone too.  But free the stacks themselves. */
  if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
  if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);

  /* Remember, you can't free anything that came from the resource
     database.  This includes:
         mw->menu.cursor
         mw->menu.top_shadow_pixmap
         mw->menu.bottom_shadow_pixmap
         mw->menu.font
         mw->menu.font_set
     Also the color cells of top_shadow_color, bottom_shadow_color,
     foreground, and button_foreground will never be freed until this
     client exits.  Nice, eh?
   */

  /* start from 1 because the one in slot 0 is w->core.window */
  for (i = 1; i < mw->menu.windows_length; i++)
    XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
  if (mw->menu.windows)
    XtFree ((char *) mw->menu.windows);
}

static Boolean 
XlwMenuSetValues (Widget current, Widget request, Widget new, ArgList args,
		  Cardinal *num_args)
{
  XlwMenuWidget oldmw = (XlwMenuWidget)current;
  XlwMenuWidget newmw = (XlwMenuWidget)new;
  Boolean redisplay = False;
  int i;

  if (newmw->menu.contents
      && newmw->menu.contents->contents
      && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
    redisplay = True;

  if (newmw->core.background_pixel != oldmw->core.background_pixel
      || newmw->menu.foreground != oldmw->menu.foreground
      /* For the XEditResource protocol, which may want to change the font. */
#ifdef NEED_MOTIF
      || newmw->menu.font_list != oldmw->menu.font_list
      || newmw->menu.font_list_2 != oldmw->menu.font_list_2
      || newmw->menu.fallback_font_list != oldmw->menu.fallback_font_list
#else
      || newmw->menu.font != oldmw->menu.font
#endif
      )
    {
      release_drawing_gcs (newmw);
      make_drawing_gcs (newmw);
      redisplay = True;
      
      for (i = 0; i < oldmw->menu.windows_length; i++)
	{
	  XSetWindowBackground (XtDisplay (oldmw),
				oldmw->menu.windows [i].window,
				newmw->core.background_pixel);
	  /* clear windows and generate expose events */
	  XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
		      0, 0, 0, 0, True);
	}
    }

  return redisplay;
}

static void 
XlwMenuResize (Widget w)
{
  XlwMenuWidget mw = (XlwMenuWidget)w;

  mw->menu.windows [0].width = mw->core.width;
  mw->menu.windows [0].height = mw->core.height;
}

/* Action procedures */
static void
handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev,
			    Boolean select_p)
{
  widget_value *val;
  Boolean      stay_up;
  int	       level;

  if (!map_event_to_widget_value (mw, ev, &val, &level, &stay_up))
    {
      /* we wind up here when: (a) the event is in the menubar, (b) the
	 event isn't in the menubar or any of the panes, (c) the event is on
	 a disabled menu item */
      pop_new_stack_if_no_contents (mw);
      if (select_p && !stay_up) {
	/* pop down all menus and exit */
	mw->menu.next_release_must_exit = True;
	set_new_state(mw, (val = NULL), 1); 
      }
    }
  else
    {
      /* we wind up here when: (a) the event pops up a pull_right menu,
	 (b) a menu item that is not disabled is highlighted */
      if (select_p && mw->menu.bounce_down
	       && close_to_reference_time((Widget)mw,
					  mw->menu.menu_bounce_time,
					  (XEvent *)ev))
	{
	  /* motion can cause more than one event.  Don't bounce right back
	     up if we've just bounced down. */
	  val = NULL;
	}
      else if (select_p && mw->menu.bounce_down &&
	       mw->menu.last_selected_val &&
	       (mw->menu.last_selected_val == val))
	{
	  val = NULL;		/* assigned to mw->last_selected_val below */
	  mw->menu.menu_bounce_time = ev->time;
	  /* popdown last menu if we're selecting the same menu item as we did
	     last time and the XlwMenu.bounceDown resource is set, if the
	     item is on the menubar itself, then exit. */
	  if (level == (mw->menu.popped_up ? 0 : 1))
	    mw->menu.next_release_must_exit = True;
	}    
      else
	mw->menu.menu_bounce_time = 0;
      set_new_state (mw, val, level);
    }
  mw->menu.last_selected_val = val;
  remap_menubar (mw);
  
  /* Sync with the display.  Makes it feel better on X terms. */
  XFlush (XtDisplay (mw));
}

static void
handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev,
		     Boolean select_p)
{
  int x = ev->x_root;
  int y = ev->y_root;
  int state = ev->state;
  XMotionEvent *event= ev, dummy;

  /* allow motion events to be generated again */
  dummy.window = ev->window;
  if (ev->is_hint
      && XQueryPointer (XtDisplay (mw), dummy.window,
			&dummy.root, &dummy.subwindow,
			&dummy.x_root, &dummy.y_root,
			&dummy.x, &dummy.y,
			&dummy.state)
      && dummy.state == state
      && (dummy.x_root != x || dummy.y_root != y))
    {
      /* don't handle the event twice or that breaks bounce_down.  --Stig */
      dummy.type = ev->type;
      event = &dummy;
    }

  handle_single_motion_event (mw, event, select_p);
}

static void 
Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
{
  XlwMenuWidget mw = (XlwMenuWidget)w;

  if (!mw->menu.pointer_grabbed)
    {
      mw->menu.menu_post_time = ev->xbutton.time;
      mw->menu.menu_bounce_time = 0;
      mw->menu.next_release_must_exit = True;
      mw->menu.last_selected_val = NULL;

      XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
      
      /* notes the absolute position of the menubar window */
      mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
      mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
      
      XtGrabPointer ((Widget)mw, False,
		     (ButtonMotionMask | ButtonReleaseMask | ButtonPressMask),
		     GrabModeAsync, GrabModeAsync,
		     None, mw->menu.cursor_shape,
		     ((XButtonPressedEvent*)ev)->time);
      mw->menu.pointer_grabbed = True;
    }

  /* handles the down like a move, slots are mostly compatible */
  handle_motion_event (mw, &ev->xmotion, True);
}

static void 
Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
{
  XlwMenuWidget mw = (XlwMenuWidget)w;
  handle_motion_event (mw, &ev->xmotion, False);
}

static void 
Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
{
  XlwMenuWidget mw = (XlwMenuWidget)w;
  widget_value *selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
  
  /* If user releases the button quickly, without selecting anything,
     after the initial down-click that brought the menu up,
     do nothing. */
  if ((selected_item == 0 || selected_item->call_data == 0)
      && (!mw->menu.next_release_must_exit
	  || close_to_reference_time(w, mw->menu.menu_post_time, ev)))
    {
      mw->menu.next_release_must_exit = False;
      return;
    }

  /* pop down everything */
  mw->menu.new_depth = 1;
  remap_menubar (mw);
      
      /* Destroy() only gets called for popup menus.  Menubar widgets aren't
	 destroyed when their menu panes get nuked. */
  if (mw->menu.pointer_grabbed)
    {
      XtUngrabPointer ((Widget)w, ev->xmotion.time);
      mw->menu.pointer_grabbed = False;
    }
  
  if (mw->menu.popped_up)
    {
      mw->menu.popped_up = False;
      XtPopdown (XtParent (mw));
    }
  
  /* callback */
  XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
}


/* Special code to pop-up a menu */
void
xlw_pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
{
  int		x = event->x_root;
  int		y = event->y_root;
  int		w;
  int		h;
  int		borderwidth = mw->menu.shadow_thickness;
  Screen*	screen = XtScreen (mw);

  mw->menu.menu_post_time = event->time;
  mw->menu.menu_bounce_time = 0;
  mw->menu.next_release_must_exit = True;
  mw->menu.last_selected_val = NULL;

  XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);

  size_menu (mw, 0);

  w = mw->menu.windows [0].width;
  h = mw->menu.windows [0].height;

  x -= borderwidth;
  y -= borderwidth;
  if (x < borderwidth)
    x = borderwidth;
  if (x + w + 2 * borderwidth > WidthOfScreen (screen))
    x = WidthOfScreen (screen) - w - 2 * borderwidth;
  if (y < borderwidth)
    y = borderwidth;
  if (y + h + 2 * borderwidth> HeightOfScreen (screen))
    y = HeightOfScreen (screen) - h - 2 * borderwidth;

  mw->menu.popped_up = True;
  XtConfigureWidget (XtParent (mw), x, y, w, h,
		     XtParent (mw)->core.border_width);
  XtPopup (XtParent (mw), XtGrabExclusive);
  display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
  if (!mw->menu.pointer_grabbed)
    {
      XtGrabPointer ((Widget)mw, False,
		     (ButtonMotionMask | ButtonReleaseMask | ButtonPressMask),
		     GrabModeAsync, GrabModeAsync,
		     None, mw->menu.cursor_shape, event->time);
      mw->menu.pointer_grabbed = True;
    }

  mw->menu.windows [0].x = x + borderwidth;
  mw->menu.windows [0].y = y + borderwidth;

  handle_motion_event (mw, (XMotionEvent *) event, True);
}

/* #### unused */
#if 0
/*
 *    This is a horrible function which should not be needed.
 *    use it to put the resize method back the way the XlwMenu
 *    class initializer put it. Motif screws with this when
 *    the XlwMenu class gets instantiated.
 */
void
xlw_unmunge_class_resize (Widget w)
{
  if (w->core.widget_class->core_class.resize != XlwMenuResize)
    w->core.widget_class->core_class.resize = XlwMenuResize;
}
#endif /* 0 */

