/*****************************************************************************/
/**       Copyright 1988 by Evans & Sutherland Computer Corporation,        **/
/**                          Salt Lake City, Utah                           **/
/**  Portions Copyright 1989 by the Massachusetts Institute of Technology   **/
/**                        Cambridge, Massachusetts                         **/
/**                                                                         **/
/**                           All Rights Reserved                           **/
/**                                                                         **/
/**    Permission to use, copy, modify, and distribute this software and    **/
/**    its documentation  for  any  purpose  and  without  fee is hereby    **/
/**    granted, provided that the above copyright notice appear  in  all    **/
/**    copies and that both  that  copyright  notice  and  this  permis-    **/
/**    sion  notice appear in supporting  documentation,  and  that  the    **/
/**    names of Evans & Sutherland and M.I.T. not be used in advertising    **/
/**    in publicity pertaining to distribution of the  software  without    **/
/**    specific, written prior permission.                                  **/
/**                                                                         **/
/**    EVANS & SUTHERLAND AND M.I.T. DISCLAIM ALL WARRANTIES WITH REGARD    **/
/**    TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES  OF  MERCHANT-    **/
/**    ABILITY  AND  FITNESS,  IN  NO  EVENT SHALL EVANS & SUTHERLAND OR    **/
/**    M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL  DAM-    **/
/**    AGES OR  ANY DAMAGES WHATSOEVER  RESULTING FROM LOSS OF USE, DATA    **/
/**    OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER    **/
/**    TORTIOUS ACTION, ARISING OUT OF OR IN  CONNECTION  WITH  THE  USE    **/
/**    OR PERFORMANCE OF THIS SOFTWARE.                                     **/
/*****************************************************************************/
/****************************************************************************
 * This module is based on Twm, but has been siginificantly modified 
 * by Rob Nation 
 ****************************************************************************/
/***********************************************************************
 * The rest of it is all my fault -- MLM
 * mwm - "LessTif Window Manager"
 ***********************************************************************/

#include "mwm.h"

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#ifdef TIME_WITH_SYS_TIME
#include <time.h>
#endif
#endif
#include <X11/Xfuncs.h>

static int             menu_on = 0;
static MenuRoot       *ActiveMenu = NULL;	/* the active menu */
static MenuItem       *ActiveItem = NULL;	/* the active menu item */
int             menuFromFrameOrWindowOrTitlebar = False;
int             Stashed_X, Stashed_Y, MenuY = 0;
MenuRoot       *PrevMenu = NULL;
MenuItem       *PrevItem = NULL;
int             PrevY = 0;

/*
 * Calculate the pixel offsets to the start of the character position we
 * want to underline and to the next character in the string.  Shrink by
 * one pixel from each end and the draw a line that long two pixels below
 * the character...
 */
static void
draw_underline(ScreenInfo *scr, Window w, GC gc, int x, int y,
	       char *txt, int posn)
{
    int             off1 = XTextWidth(scr->components[MWM_MENU].font,
				      txt, posn);
    int             off2 = XTextWidth(scr->components[MWM_MENU].font,
				      txt, posn + 1) - 1;

    XDrawLine(dpy, w, gc, x + off1, y + 2, x + off2, y + 2);
}

/*
 * Draws two horizontal lines to form a separator
 */
static void
draw_separator(Window w, GC TopGC, GC BottomGC, int x1, int y1, int x2, int y2,
	      int extra_off)
{
    XDrawLine(dpy, w, TopGC, x1, y1, x2, y2);
    XDrawLine(dpy, w, BottomGC, x1 - extra_off, y1 + 1, x2 + extra_off, y2 + 1);
}

/*
 *  Draws a little Triangle pattern within a window
 */
void
draw_arrow(Window w, GC GC1, GC GC2, GC GC3, int l, int u, int r, int b)
{
    int             m;

    m = (u + b) / 2;

    XDrawLine(dpy, w, GC1, l, u, l, b);

    XDrawLine(dpy, w, GC2, l, b, r, m);
    XDrawLine(dpy, w, GC3, r, m, l, u);
}

/*
 * add relief lines to a rectangular window
 */
static void
relieve_rectangle(Window win, int x, int y, int w, int h, GC Hilite, GC Shadow)
{
    XDrawLine(dpy, win, Hilite, x, y, w + x - 1, y);
    XDrawLine(dpy, win, Hilite, x, y, x, h + y - 1);

    XDrawLine(dpy, win, Shadow, x, h + y - 1, w + x - 1, h + y - 1);
    XDrawLine(dpy, win, Shadow, w + x - 1, y, w + x - 1, h + y - 1);
}

/*
 * add relief lines to the sides only of a rectangular window
 */
static void
relieve_half_rectangle(Window win, int x, int y, int w, int h,
		       GC Hilite, GC Shadow)
{
    XDrawLine(dpy, win, Hilite, x, y - 1, x, h + y);
    XDrawLine(dpy, win, Hilite, x + 1, y, x + 1, h + y - 1);

    XDrawLine(dpy, win, Shadow, w + x - 1, y - 1, w + x - 1, h + y);
    XDrawLine(dpy, win, Shadow, w + x - 2, y, w + x - 2, h + y - 1);
}

/*
 * Checks the function described in menuItem mi, and sees if it
 * is an allowed function for window Tmp_Win,
 * according to the motif way of life.
 * 
 * This routine is used to determine whether or not to grey out menu items.
 * FIXME: This needs to be beefed up to handle other functions (like
 * f.send_msg) -- MLM
 */
static Boolean
function_allowed(MwmWindow *tmp, MenuItem * mi)
{
    /* Complex functions are a little tricky... ignore them for now */

    /* Move is a funny hint. Keeps it out of the menu, but you're still
     * allowed * to move. */
    if ((mi->func == F_MOVE) && tmp && !(tmp->functions & MWM_FUNC_MOVE))
	return False;

    if (mi->func == F_RESIZE && tmp && !(tmp->functions & MWM_FUNC_RESIZE))
	return False;

    if (mi->func == F_ICONIFY && tmp && !(tmp->flags & ICONIFIED) &&
	!(tmp->functions & MWM_FUNC_MINIMIZE))
	return False;

    if (mi->func == F_MAXIMIZE && tmp && !(tmp->functions & MWM_FUNC_MAXIMIZE))
	return False;

    if (mi->func == F_CLOSE && tmp && !(tmp->functions & MWM_FUNC_CLOSE))
	return False;

    return True;
}


/*
 * draws a single entry in a poped up menu
 */
static void
paint_entry(ScreenInfo *scr, MwmWindow *tmp, MenuRoot *mr, MenuItem *mi)
{
    int             y_offset, text_y, d, y_height;
    GC              ShadowGC, ReliefGC, currentGC;

    y_offset = mi->y_offset;
    y_height = mi->y_height;
    text_y = y_offset + scr->components[MWM_MENU].f_y;

    ShadowGC = scr->components[MWM_MENU].bot_GC;
    ReliefGC = scr->components[MWM_MENU].top_GC;

    if ((!mi->prev) || (!mi->prev->state))
	XClearArea(dpy, mr->w, 0, y_offset - 1, mr->width, y_height + 2, 0);
    else
	XClearArea(dpy, mr->w, 0, y_offset + 1, mr->width, y_height - 1, 0);
    if ((mi->state) && (mi->func != F_TITLE) &&
	(mi->func != F_NOP) && *mi->item) {
	relieve_rectangle(mr->w, 3, y_offset, mr->width - 5, mi->y_height,
			 ReliefGC, ShadowGC);
	relieve_rectangle(mr->w, 2, y_offset - 1,
			  mr->width - 3, mi->y_height + 2,
			  ReliefGC, ShadowGC);
    }
    relieve_half_rectangle(mr->w, 0, y_offset - 1, mr->width,
			 y_height + 2, ReliefGC, ShadowGC);

    if (mi->func == F_TITLE) {
	text_y += HEIGHT_EXTRA >> 1;
	XDrawLine(dpy, mr->w, ShadowGC, 2, y_offset + y_height - 2,
		  mr->width - 3, y_offset + y_height - 2);
	XDrawLine(dpy, mr->w, ShadowGC, 2, y_offset + y_height - 4,
		  mr->width - 3, y_offset + y_height - 4);
    }
    else
	text_y += HEIGHT_EXTRA >> 1;

    if (mi->func == F_NOP && *mi->item == 0) {
	draw_separator(mr->w, ShadowGC, ReliefGC,
		       2, y_offset - 1 + HEIGHT_SEPARATOR / 2,
		       mr->width - 3, y_offset - 1 + HEIGHT_SEPARATOR / 2, 0);
    }
    if (mi->next == NULL)
	draw_separator(mr->w, ShadowGC, ShadowGC, 1, mr->height - 2,
		      mr->width - 2, mr->height - 2, 1);

    if (mi == mr->first)
	draw_separator(mr->w, ReliefGC, ReliefGC, 0, 0, mr->width - 1, 0, -1);

    if (function_allowed(tmp, mi))
	currentGC = scr->components[MWM_MENU].normal_GC;
    /* MLM: FIXME: SHOULD GRAY OUT ITEM */
    else
	currentGC = scr->components[MWM_MENU].normal_GC;

    if (*mi->item)
	XDrawString(dpy, mr->w, currentGC, mi->x, text_y,
		    mi->item, mi->strlen);

    if (mi->strlen2 > 0 && mi->item2 && *mi->item2)
	XDrawString(dpy, mr->w, currentGC, mi->x2, text_y,
		    mi->item2, mi->strlen2);

    /* pete@tecc.co.uk: If the item has a hot key, underline it */
    if (mi->hotkey > 0)
	draw_underline(scr, mr->w, currentGC, mi->x, text_y,
		       mi->item, mi->hotkey - 1);

    if (mi->hotkey < 0)
	draw_underline(scr, mr->w, currentGC, mi->x2, text_y,
		       mi->item2, -1 - mi->hotkey);

    d = (scr->components[MWM_MENU].f_height + HEIGHT_EXTRA - 7) / 2;
    if (mi->func == F_POPUP)
	if (mi->state)
	    draw_arrow(mr->w, ShadowGC, ReliefGC, ShadowGC, mr->width - d - 8,
		     y_offset + d - 1, mr->width - d - 1, y_offset + d + 7);
	else
	    draw_arrow(mr->w, ReliefGC, ShadowGC, ReliefGC, mr->width - d - 8,
		     y_offset + d - 1, mr->width - d - 1, y_offset + d + 7);
    return;
}

/*
 * draws the entire menu
 */
static void
paint_menu(ScreenInfo *scr, MwmWindow *tmp, MenuRoot * mr, XEvent * e)
{
    MenuItem       *mi;

    for (mi = mr->first; mi != NULL; mi = mi->next) {
	/* be smart about handling the expose, redraw only the entries
	 * that we need to
	 */
	if (e->xexpose.y < (mi->y_offset + mi->y_height) &&
	    (e->xexpose.y + e->xexpose.height) > mi->y_offset) {
	    paint_entry(scr, tmp, mr, mi);
	}
    }
    XSync(dpy, 0);
    return;
}

/*
 * Updates menu display to reflect the highlighted item
 */
static int
find_entry(ScreenInfo *scr, MwmWindow *tmp)
{
    MenuItem       *mi;
    MenuRoot       *actual_mr;
    int             retval = MENU_NOP;
    MenuRoot       *PrevPrevMenu;
    MenuItem       *PrevPrevItem;
    int             PrevPrevY;
    int             x, y, ChildY;
    Window          Child;

    XQueryPointer(dpy, scr->root_win, &JunkRoot, &Child,
		  &JunkX, &ChildY, &x, &y, &JunkMask);
    XQueryPointer(dpy, ActiveMenu->w, &JunkRoot, &JunkChild,
		  &JunkX, &ChildY, &x, &y, &JunkMask);

    /* look for the entry that the mouse is in */
    for (mi = ActiveMenu->first; mi; mi = mi->next)
	if (y >= mi->y_offset && y < mi->y_offset + mi->y_height)
	    break;
    if (x < 0 || x > ActiveMenu->width)
	mi = NULL;


    /* if we weren't on the active entry, let's turn the old active one off */
    if ((ActiveItem) && (mi != ActiveItem)) {
	ActiveItem->state = 0;
	paint_entry(scr, tmp, ActiveMenu, ActiveItem);
    }

    /* if we weren't on the active item, change the active item and turn it
     * on */
    if ((mi != ActiveItem) && (mi != NULL)) {
	mi->state = 1;
	paint_entry(scr, tmp, ActiveMenu, mi);
    }

    ActiveItem = mi;

    if (ActiveItem) {
	/* create a new sub-menu */
	if ((ActiveItem->func == F_POPUP) &&
	    (x > (3 * ActiveMenu->width >> 2))) {

	    PrevPrevMenu = PrevMenu;
	    PrevPrevItem = PrevItem;
	    PrevPrevY = PrevY;
	    PrevY = MenuY;
	    PrevMenu = ActiveMenu;
	    PrevItem = ActiveItem;
	    retval = MENU_PopupMenu(scr, ActiveItem->menu);

	    /* Unfortunately, this is needed (why?) for multi-screen
	     * operation */
	    MISC_FlushExpose(ActiveMenu->w);
	    for (mi = ActiveMenu->first; mi != NULL; mi = mi->next)
		paint_entry(scr, tmp, ActiveMenu, mi);

	    XSync(dpy, 0);
	    MenuY = PrevY;
	    PrevMenu = PrevPrevMenu;
	    PrevItem = PrevPrevItem;
	    PrevY = PrevPrevY;
	}
    }
    /* end a sub-menu */
    if (XFindContext(dpy, Child,
		     MenuContext, (XPointer *) &actual_mr) == XCNOENT) {
	return retval;
    }

    if (actual_mr != ActiveMenu) {
	if (actual_mr == PrevMenu) {
	    if ((PrevItem->y_offset + PrevY > ChildY) ||
	     ((PrevItem->y_offset + PrevItem->y_height + PrevY) < ChildY)) {
		return SUBMENU_DONE;
	    }
	}
	else
	    return SUBMENU_DONE;
    }
    return retval;
}

/*
 * Function called from update_menu instead of Keyboard_Shortcuts()
 * when a KeyPress event is received.  If the key is alphanumeric,
 * then the menu is scanned for a matching hot key.  Otherwise if
 * it was the escape key then the menu processing is aborted.
 * If none of these conditions are true, then the default processing
 * routine is called.
 */
static void
menu_shortcuts(ScreenInfo *scr, XEvent * ev)
{
    MenuItem       *mi;
    KeySym          keysym = XLookupKeysym(&ev->xkey, 0);

    /* Try to match hot keys */
    if (((keysym >= XK_a) && (keysym <= XK_z)) ||  /* consider alphabetic */
	((keysym >= XK_0) && (keysym <= XK_9))) {  /* ...or numeric keys  */
	/* Search menu for matching hotkey */
	for (mi = ActiveMenu->first; mi; mi = mi->next) {
	    char            key;

	    if (mi->hotkey == 0)
		continue;	/* Item has no hotkey   */
	    key = (mi->hotkey > 0) ?	/* Extract hot character */
		mi->item[mi->hotkey - 1] : mi->item2[-1 - mi->hotkey];

	    /* Convert to lower case to match the keysym */
	    if (isupper(key))
		key = tolower(key);

	    if (keysym == key) {
		/* Force a menu exit */
		ActiveItem = mi;
		ev->type = ButtonRelease;
		return;
	    }
	}
    }

    switch (keysym) {
    case XK_Escape:
	/* escape exits menu */
	ActiveItem = NULL;
	ev->type = ButtonRelease;
	break;

	/* Nothing special --- Allow other shortcuts (cursor movement)    */
    default:
	MISC_KeyboardShortcut(scr, ev, ButtonRelease);
	break;
    }
}

/*
 * pop up a pull down menu
 */
static Boolean
pop_up_menu(ScreenInfo *scr, MenuRoot * menu, int x, int y)
{

    if ((!menu) || (menu->w == None) || (menu->items == 0) || (menu->in_use))
	return False;

    menu_on++;
    COLOR_PushRootColorMap(scr);

    Stashed_X = x;
    Stashed_Y = y;

    /* pop up the menu */
    ActiveMenu = menu;
    ActiveItem = NULL;

    /* clip to screen */
    if (x + menu->width > scr->d_width - 2)
	x = scr->d_width - menu->width - 2;
    if (x < 0)
	x = 0;

    if (y + menu->height > scr->d_height - 2)
	y = scr->d_height - menu->height - 2;
    if (y < 0)
	y = 0;

    MenuY = y;
    XMoveWindow(dpy, menu->w, x, y);
    XMapRaised(dpy, menu->w);
    menu->in_use = True;
    return True;
}

/*
 * unhighlight the current menu selection and take down the menus
 */
static void
pop_down_menu(ScreenInfo *scr)
{
    if (ActiveMenu == NULL)
	return;

    menu_on--;
    if (ActiveItem)
	ActiveItem->state = 0;

    XUnmapWindow(dpy, ActiveMenu->w);

    COLOR_PopRootColorMap(scr);
    XFlush(dpy);
    if (scr->event_context & (C_WINDOW | C_FRAME | C_TITLE | C_FRAME))
	menuFromFrameOrWindowOrTitlebar = True;
    else
	menuFromFrameOrWindowOrTitlebar = False;
    ActiveMenu->in_use = False;
}

/*
 * Updates menu display to reflect the highlighted item
 * 
 *  Returns:
 *      0 on error condition
 *      1 on return from submenu to parent menu
 *      2 on button release return
 */
static int
update_menu(ScreenInfo *scr, MwmWindow *tmp)
{
    int             done, func;
    int             retval;
    MenuRoot       *actual_mr;
    XEvent          oevent;
    int             x, y;

    find_entry(scr, tmp);
    while (True) {
	/* block until there is an event */
	XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask | ExposureMask |
	    KeyPressMask | VisibilityChangeMask | ButtonMotionMask, &oevent);
	MISC_StashEventTime(&oevent);
	done = 0;
	if (oevent.type == MotionNotify) {
	    /* discard any extra motion events before a release */
	    while ((XCheckMaskEvent(dpy, ButtonMotionMask | ButtonReleaseMask,
				 &oevent)) && (oevent.type != ButtonRelease));
	}

	/* Handle a limited number of key press events to allow mouseless
	 * operation */
	if (oevent.type == KeyPress)
	    menu_shortcuts(scr, &oevent);

	switch (oevent.type) {
	case ButtonRelease:
	    /* The following lines holds the menu when the button is released */
	    if (!ActiveItem && (menu_on > 1)) {

		XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
			      &JunkX, &JunkY, &x, &y, &JunkMask);
		if ((XFindContext(dpy, JunkChild, MenuContext,
				  (XPointer *) & actual_mr) != XCNOENT) &&
		    (actual_mr != ActiveMenu)) {
		    done = 1;
		    break;
		}
	    }
	    pop_down_menu(scr);
	    if (ActiveItem) {
		func = ActiveItem->func;
		done = 1;
		if (scr->mwm_event) {
		    FUNC_Execute(scr, func, ActiveItem->action,
				 scr->mwm_event->frame, scr->mwm_event, &oevent,
				 scr->event_context,
				 ActiveItem->val1, ActiveItem->val2,
			         ActiveItem->val1_unit, ActiveItem->val2_unit,
				 ActiveItem->menu);
		}
		else {
		    FUNC_Execute(scr, func, ActiveItem->action,
				 None, None, &oevent, scr->event_context,
				 ActiveItem->val1, ActiveItem->val2,
				 ActiveItem->val1_unit, ActiveItem->val2_unit,
				 ActiveItem->menu);
		}
	    }
	    ActiveItem = NULL;
	    ActiveMenu = NULL;
	    menuFromFrameOrWindowOrTitlebar = False;
	    return MENU_DONE;

	case ButtonPress:
	    XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
			  &JunkX, &JunkY, &x, &y, &JunkMask);
	    if ((XFindContext(dpy, JunkChild, MenuContext,
			      (XPointer *)&actual_mr) == XCNOENT)) {
		pop_down_menu(scr);
		ActiveItem = NULL;
		ActiveMenu = NULL;
		menuFromFrameOrWindowOrTitlebar = False;
		XPutBackEvent(dpy, &oevent);
		return MENU_DONE;
	    }
	    done = 1;
	    break;

	case KeyPress:
	case VisibilityNotify:
	    done = 1;
	    break;

	case MotionNotify:
	    done = 1;

	    retval = find_entry(scr, tmp);
	    if ((retval == MENU_DONE) || (retval == SUBMENU_DONE)) {
		pop_down_menu(scr);
		ActiveItem = NULL;
		ActiveMenu = NULL;
		menuFromFrameOrWindowOrTitlebar = False;
	    }

	    if (retval == MENU_DONE)
		return MENU_DONE;
	    else if (retval == SUBMENU_DONE)
		return MENU_NOP;

	    break;

	case Expose:
	    /* grab our expose events, let the rest go through */
	    if ((XFindContext(dpy, oevent.xany.window, MenuContext,
			      (XPointer *) & actual_mr) != XCNOENT)) {
		paint_menu(scr, tmp, actual_mr, &oevent);
		done = 1;
	    }
	    break;

	default:
	    break;
	}

	if (!done)
	    EVENT_Dispatch(&oevent);
	XFlush(dpy);
    }
}

/*
 * Look for hotkey markers in a MenuItem (pete@tecc.co.uk)
 */
void
MENU_FindHotKey(MenuItem *it, KeySym key)
{
    char *str, *ptr;

    if (key != AnyKey) {
	str = XKeysymToString(key);
	for (ptr = it->item; ptr && *ptr && *ptr != *str; ptr++)
	    ;
	if (ptr) {
	    if (!*ptr)
		it->hotkey = 0;
	    else
		it->hotkey = ptr - it->item + 1;
	}
	else it->hotkey = 0;
    }
    else
	it->hotkey = 0;
}

/*
 * create a new menu root
 */
MenuRoot *
MENU_Create(char *name)
{
    MenuRoot       *tmp;

    tmp = (MenuRoot *)XtMalloc(sizeof(MenuRoot));
    tmp->name = name;
    tmp->first = NULL;
    tmp->last = NULL;
    tmp->items = 0;
    tmp->width = 0;
    tmp->width2 = 0;
    tmp->w = None;
    return (tmp);
}

/*
 * add a menu to the menu list
 */
void
MENU_Add(ScreenInfo *scr, MenuRoot *menu)
{
    if (scr->max_popups == 0) {
	scr->max_popups = 8;
	scr->num_popups = 0;
	scr->popups = (MenuRoot **)XtMalloc(scr->max_popups *
						sizeof(MenuRoot *));
    }
    else if (scr->num_popups == scr->max_popups) {
	scr->max_popups *= 2;
	scr->popups = (MenuRoot **)XtRealloc((char *)scr->popups,
					     scr->max_popups *
						 sizeof(MenuRoot *));
    }
    scr->popups[scr->num_popups] = menu;
    scr->num_popups++;
}

/*
 * remove a menu from the screen's list of popups
 */
void
MENU_Remove(ScreenInfo *scr, MenuRoot *menu)
{
    int i;

    for (i = 0; i < scr->num_popups; i++) {
	if (menu == scr->popups[i]) {
	    if (scr->num_popups != (i + 1))
		bcopy(&scr->popups[i+1], &scr->popups[i],
		      (scr->num_popups - i - 1) * sizeof(MenuRoot *));
	    scr->num_popups--;
	}
    }
}

/*
 * add an item to a root menu
 */
void
MENU_AddItem(ScreenInfo *scr, MenuRoot * menu, char *item, char *item2,
	     char *action, int func, long func_val_1, long func_val_2,
	     char unit_1, char unit_2)
{
    MenuItem       *tmp;
    int             width;


    if (item == NULL)
	return;
    tmp = (MenuItem *) XtMalloc(sizeof(MenuItem));
    if (menu->first == NULL) {
	menu->first = tmp;
	tmp->prev = NULL;
    }
    else {
	menu->last->next = tmp;
	tmp->prev = menu->last;
    }
    menu->last = tmp;

    tmp->item = item;
    if (item != (char *) 0) {
	MENU_FindHotKey(tmp, AnyKey);
	tmp->strlen = strlen(item);
    }
    else
	tmp->strlen = 0;
    tmp->item2 = item2;
    if (item2)
	tmp->strlen2 = strlen(item2);
    else
	tmp->strlen2 = 0;

    tmp->menu = 0;

    if (func == F_POPUP) {
	unsigned        i;

	if (action != (char *) 0) {
	    for (i = 0; i < scr->num_popups; i++)
		if (strcasecmp(scr->popups[i]->name, action) == 0) {
		    tmp->menu = scr->popups[i];
		    break;
		}
	}
	if (tmp->menu == (MenuRoot *) 0) {
	    fprintf(stderr, "No popup menu %s.", action);
	    func = F_NOP;
	}
    }
    tmp->action = action;
    tmp->next = NULL;
    tmp->state = 0;
    tmp->func = func;
    tmp->val1 = func_val_1;
    tmp->val2 = func_val_2;
    if ((unit_1 == 'p') || (unit_1 == 'P'))
	tmp->val1_unit = 100;
    else
	tmp->val1_unit = scr->d_width;
    if ((unit_2 == 'p') || (unit_2 == 'P'))
	tmp->val2_unit = 100;
    else
	tmp->val2_unit = scr->d_height;

    width = XTextWidth(scr->components[MWM_MENU].font, item, tmp->strlen);
    if (tmp->func == F_POPUP)
	width += 15;
    if (width <= 0)
	width = 1;
    if (width > menu->width)
	menu->width = width;
    width = XTextWidth(scr->components[MWM_MENU].font, item2, tmp->strlen2);
    if (width < 0)
	width = 0;
    if (width > menu->width2)
	menu->width2 = width;
    if ((width == 0) && (tmp->strlen2 > 0))
	menu->width2 = 1;

    menu->items++;
}

/*
 * Generates the window for a menu
 */
void
MENU_Realize(ScreenInfo *scr, MenuRoot *mr)
{
    MenuItem       *cur;
    unsigned long   valuemask;
    XSetWindowAttributes attributes;
    int             y;

    /* lets first size the window accordingly */
    mr->width += 10;
    if (mr->width2 > 0)
	mr->width += 5;

    /* allow two pixels for top border */
    for (y = 2, cur = mr->first; cur != NULL; cur = cur->next) {
	cur->y_offset = y;
	cur->x = 5;
	if (cur->func == F_TITLE) {
	    /* Title */
	    if (cur->strlen2 == 0)
		cur->x = (mr->width - XTextWidth(scr->components[MWM_MENU].font,
						 cur->item,
						 cur->strlen)) >> 1;

	    cur->y_height = scr->components[MWM_MENU].f_height +
				HEIGHT_EXTRA + HEIGHT_EXTRA_TITLE;
	}
	else if (cur->func == F_NOP && *cur->item == 0)
	    /* Separator */
	    cur->y_height = HEIGHT_SEPARATOR;
	else
	    /* Normal text entry */
	    cur->y_height = scr->components[MWM_MENU].f_height + HEIGHT_EXTRA;
	y += cur->y_height;
	if (mr->width2 == 0) {
	    cur->x2 = cur->x;
	}
	else {
	    cur->x2 = mr->width - 5;
	}
    }
    mr->in_use = 0;
    mr->height = y + 2;

    valuemask = (CWBackPixel | CWEventMask | CWCursor | CWSaveUnder);
    attributes.background_pixel = scr->components[MWM_MENU].background;
    attributes.event_mask = (ExposureMask | EnterWindowMask);
    attributes.cursor = scr->cursors[MENU_CURS];
    attributes.save_under = True;
    mr->width = mr->width + mr->width2;
    mr->w = XCreateWindow(dpy, scr->root_win, 0, 0, (unsigned int) (mr->width),
			  (unsigned int) mr->height, (unsigned int) 0,
			  CopyFromParent, (unsigned int) InputOutput,
			  (Visual *) CopyFromParent,
			  valuemask, &attributes);
    XSaveContext(dpy, mr->w, MenuContext, (_Xconst char *) mr);

    return;
}

/*
 * realize all the menus
 */
void
MENU_RealizeMenus(ScreenInfo *scr)
{
    int i;

    for (i = 0; i < scr->num_popups; i++)
	MENU_Realize(scr, scr->popups[i]);
}

/*
 * menu ordering isn't required by mwm (meaning you can use f.menu <menu-name>
 * before <menu-name> has been declared).  Therefore, we have to defer linking
 * menus together until after the parse phase.
 */
void
MENU_LinkUp(ScreenInfo *scr)
{
    int i, k;
    MenuRoot *mr = NULL;
    MenuItem *mi = NULL;
    FuncKey *fk = NULL;
    MouseButton *mb = NULL;

    for (i = 0; i < scr->num_popups; i++) {

	mr = scr->popups[i];

	mi = mr->first; 
	while (mi != NULL) {

	    if (mi->func == F_POPUP) {

		for (k = 0; k < scr->num_popups; k++) {
		    if (strcmp(scr->popups[k]->name, mi->action) == 0) {
			mi->menu = scr->popups[k];
			break;
		    }
		}
		if (mi->menu == NULL) {
		    fprintf(stderr, "Can't find requested menu: %s", mi->action);
		    mi->func = F_NOP;
		}
	    }

	    if (mi == mr->last)
		break;
	    else
		mi = mi->next;
	}
    }

    for (mb = scr->buttons; mb != NULL; mb = mb->next) {
	if (mb->func == F_POPUP || mb->func == F_W_POPUP) {

	    for (k = 0; k < scr->num_popups; k++) {
		if (strcmp(scr->popups[k]->name, (char *)mb->menu) == 0) {
		    mb->menu = scr->popups[k];
		    break;
		}
	    }
	    if (mb->menu == NULL) {
		fprintf(stderr, "Can't find requested menu: %s", mi->action);
		mb->func = F_NOP;
	    }
	}
    }

    for (fk = scr->keys; fk != NULL; fk = fk->next) {
	if (fk->func == F_POPUP || fk->func == F_W_POPUP) {

	    for (k = 0; k < scr->num_popups; k++) {
		if (strcmp(scr->popups[k]->name, fk->action) == 0) {
		    fk->menu = scr->popups[k];
		    break;
		}
	    }
	    if (fk->menu == NULL) {
		fprintf(stderr, "Can't find requested menu: %s", mi->action);
		fk->func = F_NOP;
	    }
	}
    }
}

/*
 * do a window menu popup
 */
int
MENU_WinMenu(ScreenInfo *scr, MenuRoot *menu, MwmWindow *win, Boolean button)
{
    int             prevStashedX = 0, prevStashedY = 0;
    MenuRoot       *PrevActiveMenu = 0;
    MenuItem       *PrevActiveItem = 0;
    int             retval = MENU_NOP;
    int             x, y;

    /* this condition could get ugly */
    if (menu->in_use)
	return MENU_ERROR;

    if (!win)
	return MENU_ERROR;

    /*
     * In case we wind up with a move from a menu which is
     * from a window border, we'll return to here to start
     * the move.  At least, if we came from a button press
     */
    x = y = 0;
    if (button) {
	XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
		      &x, &y, &JunkX, &JunkY, &JunkMask);
    }
    else if ((win->flags & ICONIFIED) && win->icon_w != None) {
	/* the icon window had better be a child of the root window */
	XGetGeometry(dpy, win->icon_w, &JunkRoot, &x, &y,
		     &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth);
    }
    else {
	XGetGeometry(dpy, win->w, &JunkRoot, &JunkX, &JunkY,
		     &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth);
	XTranslateCoordinates(dpy, win->w, scr->root_win, JunkX, JunkY,
			      &x, &y, &JunkChild);
	x -= win->matte_width;
	y -= win->matte_width;
    }

    if (!MISC_Grab(scr, MENU_CURS)) {
	XBell(dpy, scr->screen);
	return MENU_DONE;
    }

    if (pop_up_menu(scr, menu, x, y)) {
	retval = update_menu(scr, win);
    }
    else
	XBell(dpy, scr->screen);

    ActiveMenu = PrevActiveMenu;
    ActiveItem = PrevActiveItem;
    if ((ActiveItem) && (menu_on))
	ActiveItem->state = 1;
    Stashed_X = prevStashedX;
    Stashed_Y = prevStashedY;


    if (!menu_on)
	MISC_Ungrab(scr);

    return retval;
}

/*
 * Initiates a menu pop-up
 */
int
MENU_PopupMenu(ScreenInfo *scr, MenuRoot *menu)
{
    int             prevStashedX = 0, prevStashedY = 0;
    MenuRoot       *PrevActiveMenu = 0;
    MenuItem       *PrevActiveItem = 0;
    int             retval = MENU_NOP;
    int             x, y;

    /* this condition could get ugly */
    if (menu->in_use)
	return MENU_ERROR;

    /* In case we wind up with a move from a menu which is
     * from a window border, we'll return to here to start
     * the move */
    XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
		  &x, &y, &JunkX, &JunkY, &JunkMask);

    x -= (menu->width >> 1);
    y -= ((scr->components[MWM_MENU].f_height + HEIGHT_EXTRA) >> 1);

    if (menu_on) {
	prevStashedX = Stashed_X;
	prevStashedY = Stashed_Y;

	PrevActiveMenu = ActiveMenu;
	PrevActiveItem = ActiveItem;
	if (ActiveMenu)
	    x = Stashed_X + (ActiveMenu->width >> 1) + (menu->width >> 1) - 3;
	if (ActiveItem)
	    y = ActiveItem->y_offset + MenuY +
		((scr->components[MWM_MENU].f_height + HEIGHT_EXTRA) >> 1);
    }
    else {
	if (!MISC_Grab(scr, MENU_CURS)) {
	    XBell(dpy, scr->screen);
	    return MENU_DONE;
	}
	x += (menu->width >> 1) - 3;
    }
    if (pop_up_menu(scr, menu, x, y)) {
	retval = update_menu(scr, NULL);
    }
    else
	XBell(dpy, scr->screen);

    ActiveMenu = PrevActiveMenu;
    ActiveItem = PrevActiveItem;
    if ((ActiveItem) && (menu_on))
	ActiveItem->state = 1;
    Stashed_X = prevStashedX;
    Stashed_Y = prevStashedY;


    if (!menu_on)
	MISC_Ungrab(scr);

    return retval;
}

/*
 * destroy one menu
 */
void
MENU_Destroy(MenuRoot *menu)
{
    MenuItem *mi, *tmp;
    if (!menu)
	return;

    XDestroyWindow(dpy, menu->w);

    for (mi = menu->first; mi != NULL; mi = tmp) {
	tmp = mi->next;
	XtFree((char *)mi);
	mi = tmp;
    }

    XtFree((char *)menu);
}

/*
 * destroy all menus
 */
void
MENU_DestroyMenus(ScreenInfo *scr)
{
    int i;

    for (i = 0; i < scr->num_popups; i++)
	if (scr->popups[i]->w != None)
	    XDestroyWindow(dpy, scr->popups[i]->w);
}

/*
 * reset the menuing system
 */
void
MENU_Reset()
{
    ActiveItem = NULL;
    ActiveMenu = NULL;
}

#define INTERNAL_MENU "INTERNAL_MWM_WIN_MENU_"
/*
 * if the user has a custom window menu, build it
 */
void
MENU_BuildWindowMenu(ScreenInfo *scr, MwmWindow *win)
{
    MenuRoot *mr, *def;
    char *buf;
    int i;
    MenuItem *mi, *tmp;

    mr = NULL;

    if (win->mwm_menu && strlen(win->mwm_menu) != 0) {

	buf = XtMalloc(strlen(win->mwm_menu) + 64);
	sprintf(buf, "Menu %s {\n", INTERNAL_MENU);
	strcat(buf, win->mwm_menu);
	strcat(buf, "\n}");

	PARSE_buf(scr, buf);

	for (i = 0; i < scr->num_popups; i++) {
	    if (strcmp(scr->popups[i]->name, INTERNAL_MENU) == 0)
		break;
	}
	if (i == scr->num_popups) {
	    fprintf(stderr, "Couldn't find user defined menu!\n");
	    return;
	}

	mr = scr->popups[i];
	MENU_Remove(scr, mr);
	win->custom_menu = mr;
    }

    for (i = 0; i < scr->num_popups; i++) {
	if (strcmp(scr->popups[i]->name, win->window_menu) == 0)
	    break;
    }
    if (i == scr->num_popups) {
	fprintf(stderr, "Couldn't find default window menu!\n");
	return;
    }

    def = scr->popups[i];

    if (mr) {
	if (def->width > mr->width)
	    mr->width = def->width;
	if (def->width2 > mr->width2)
	    mr->width2 = def->width2;
    }
    else {
	mr = (MenuRoot *)XtMalloc(sizeof(MenuRoot));
	bcopy(def, mr, sizeof(MenuRoot));
	mr->first = mr->last = NULL;
    }

    for (mi = def->last; mi != NULL; mi = mi->prev) {
	tmp = (MenuItem *)XtMalloc(sizeof(MenuItem));
	bcopy(mi, tmp, sizeof(MenuItem));
	if (mr->first == NULL) {
	    mr->first = tmp;
	    tmp->prev = NULL;
	    tmp->next = NULL;
	}
	else {
	    tmp->next = mr->first;
	    mr->first->prev = tmp;
	    mr->first = tmp;
	}
	if (mr->last == NULL)
	    mr->last = tmp;
	mr->items++;
    }

    MENU_Realize(scr, mr);
}

/*
 * if a window has a custom window menu, destroy it
 */
void
MENU_DestroyWindowMenu(ScreenInfo *scr, MwmWindow *win)
{
    if (!win->custom_menu)
	return;

    MENU_Destroy(win->custom_menu);

    win->custom_menu = NULL;
}

/*
 * convert a keysym and a mod mask into a string
 */
char
*MENU_AcceleratorString(ScreenInfo *scr, KeySym keysym, int modifiers)
{
    Boolean haveone = False;
    static char buf[128];
    char *key;

    buf[0] = 0;

    if (modifiers & ControlMask) {
	strcat(buf, "<Ctrl");
	haveone = True;
    }
    if (modifiers & ShiftMask) {
	if (haveone)
	    strcat(buf, "+");
	else
	    strcat(buf, "<");
	strcat(buf, "Shift");
	haveone = True;
    }
    if (modifiers & scr->alt_mask) {
	if (haveone)
	    strcat(buf, "+");
	else
	    strcat(buf, "<");
	strcat(buf, "Alt");
	haveone = True;
    }
    if (modifiers & LockMask) {
	if (haveone)
	    strcat(buf, "+");
	else
	    strcat(buf, "<");
	strcat(buf, "Lock");
	haveone = True;
    }
    if (modifiers & Mod1Mask && scr->alt_mask != Mod1Mask) {
	if (haveone)
	    strcat(buf, "+");
	else
	    strcat(buf, "<");
	strcat(buf, "Mod1");
	haveone = True;
    }
    if (modifiers & Mod2Mask && scr->alt_mask != Mod2Mask) {
	if (haveone)
	    strcat(buf, "+");
	else
	    strcat(buf, "<");
	strcat(buf, "Mod2");
	haveone = True;
    }
    if (modifiers & Mod3Mask && scr->alt_mask != Mod3Mask) {
	if (haveone)
	    strcat(buf, "+");
	else
	    strcat(buf, "<");
	strcat(buf, "Mod3");
	haveone = True;
    }
    if (modifiers & Mod4Mask && scr->alt_mask != Mod4Mask) {
	if (haveone)
	    strcat(buf, "+");
	else
	    strcat(buf, "<");
	strcat(buf, "Mod4");
	haveone = True;
    }
    if (modifiers & Mod5Mask && scr->alt_mask != Mod5Mask) {
	if (haveone)
	    strcat(buf, "+");
	else
	    strcat(buf, "<");
	strcat(buf, "Mod5");
	haveone = True;
    }
    if ((key = XKeysymToString(keysym)) != NULL) {
	if (haveone)
	    strcat(buf, ">");
	strcat(buf, key);
    }
    return buf;
}
