/*	SCCS Id: @(#)winmenu.c	3.4	1996/08/15	*/
/* Copyright (c) Dean Luick, 1992				  */
/* NetHack may be freely redistributed.  See license for details. */

/*
 * File for creating menus.
 *
 *	+ Global functions: start_menu, add_menu, end_menu, select_menu
 */
/*#define USE_FWF*/		/* use FWF's list widget */

#ifndef SYSV
#define PRESERVE_NO_SYSV	/* X11 include files may define SYSV */
#endif

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xatom.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xaw/Box.h>
#ifdef USE_FWF
#include <X11/Xfwf/MultiList.h>
#else
#include <X11/Xaw/List.h>
#endif
#include <X11/Xos.h>

#ifdef PRESERVE_NO_SYSV
# ifdef SYSV
#  undef SYSV
# endif
# undef PRESERVE_NO_SYSV
#endif

#include "hack.h"
#include "winX.h"
#include <ctype.h>


static void FDECL(menu_select, (Widget, XtPointer, XtPointer));
static void FDECL(invert_line, (struct xwindow *,x11_menu_item *,int,long));
static void FDECL(menu_ok, (Widget, XtPointer, XtPointer));
static void FDECL(menu_cancel, (Widget, XtPointer, XtPointer));
static void FDECL(menu_all, (Widget, XtPointer, XtPointer));
static void FDECL(menu_none, (Widget, XtPointer, XtPointer));
static void FDECL(menu_invert, (Widget, XtPointer, XtPointer));
static void FDECL(menu_search, (Widget, XtPointer, XtPointer));
static void FDECL(select_all, (struct xwindow *));
static void FDECL(select_none, (struct xwindow *));
static void FDECL(select_match, (struct xwindow *, char*));
static void FDECL(invert_all, (struct xwindow *));
static void FDECL(invert_match, (struct xwindow *, char*));
static void FDECL(menu_popdown, (struct xwindow *));
#ifdef USE_FWF
static void FDECL(sync_selected, (struct menu_info_t *, int, int *));
#endif

static void FDECL(move_menu, (struct menu *, struct menu *));
static void FDECL(free_menu, (struct menu *));
static void FDECL(reset_menu_to_default, (struct menu *));
static void FDECL(clear_old_menu, (struct xwindow *));
static char *FDECL(copy_of, (const char *));

#define reset_menu_count(mi)	((mi)->counting = FALSE, (mi)->menu_count = 0L)


static const char menu_translations[] =
    "#override\n\
     <Key>Left: scroll(4)\n\
     <Key>Right: scroll(6)\n\
     <Key>Up: scroll(8)\n\
     <Key>Down: scroll(2)\n\
     <Key>: menu_key()";

/*
 * Menu callback.
 */
/* ARGSUSED */
static void
menu_select(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    struct xwindow *wp;
    struct menu_info_t *menu_info;
#ifdef USE_FWF
    XfwfMultiListReturnStruct *lrs = (XfwfMultiListReturnStruct *) call_data;
#else
    XawListReturnStruct *lrs = (XawListReturnStruct *) call_data;
    int i;
    x11_menu_item *curr;
#endif
    long how_many;

    wp = find_widget(w);
    menu_info  = wp->menu_information;
    how_many = menu_info->counting ? menu_info->menu_count : -1L;
    reset_menu_count(menu_info);

#ifdef USE_FWF
    /* if we've reached here, we've found our selected item */
    switch (lrs->action) {
	case XfwfMultiListActionNothing:
		pline("menu_select: nothing action?");
		break;
	case XfwfMultiListActionStatus:
		pline("menu_select: status action?");
		break;
	case XfwfMultiListActionHighlight:
	case XfwfMultiListActionUnhighlight:
		sync_selected(menu_info,lrs->num_selected,lrs->selected_items);
		break;
    }
#else
    for (i = 0, curr = menu_info->curr_menu.base; i < lrs->list_index; i++) {
	if (!curr) panic("menu_select: out of menu items!");
	curr = curr->next;
    }
    XawListUnhighlight(w);	/* unhilight item */

    /* if the menu is not active or don't have an identifier, try again */
    if (!menu_info->is_active || curr->identifier.a_void == 0) {
	X11_nhbell();
	return;
    }

    /* if we've reached here, we've found our selected item */
    curr->selected = !curr->selected;
    if (curr->selected) {
	curr->str[2] = (how_many != -1L) ? '#' : '+';
	curr->pick_count = how_many;
    } else {
	curr->str[2] = '-';
	curr->pick_count = -1L;
    }
    XawListChange(wp->w, menu_info->curr_menu.list_pointer, 0, 0, True);
#endif

    if (menu_info->how == PICK_ONE)
	menu_popdown(wp);
}

/*
 * Called when menu window is deleted.
 */
/* ARGSUSED */
void
menu_delete(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    menu_cancel((Widget)None, (XtPointer) find_widget(w), (XtPointer) 0);
}

/*
 * Invert the count'th line (curr) in the given window.
 */
/*ARGSUSED*/
static void
invert_line(wp, curr, which, how_many)
    struct xwindow *wp;
    x11_menu_item *curr;
    int which;
    long how_many;
{
    reset_menu_count(wp->menu_information);
    curr->selected = !curr->selected;
    if (curr->selected) {
#ifdef USE_FWF
	XfwfMultiListHighlightItem((XfwfMultiListWidget)wp->w, which);
#else
	curr->str[2] = (how_many != -1) ? '#' : '+';
#endif
	curr->pick_count = how_many;
    } else {
#ifdef USE_FWF
	XfwfMultiListUnhighlightItem((XfwfMultiListWidget)wp->w, which);
#else
	curr->str[2] = '-';
#endif
	curr->pick_count = -1L;
    }
}

/*
 * Called when we get a key press event on a menu window.
 */
/* ARGSUSED */
void
menu_key(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    struct menu_info_t *menu_info;
    x11_menu_item *curr;
    struct xwindow *wp;
    char ch;
    int count;

    wp = find_widget(w);
    menu_info = wp->menu_information;

    ch = key_event_to_char((XKeyEvent *) event);

    if (ch == '\0') {	/* don't accept nul char/modifier event */
	/* don't beep */
	return;
    }

    if (menu_info->is_active) {		/* waiting for input */
	ch = map_menu_cmd(ch);
	if (ch == '\033') {		/* quit */
	    if (menu_info->counting) {
		/* when there's a count in progress, ESC discards it
		   rather than dismissing the whole menu */
		reset_menu_count(menu_info);
		return;
	    }
	    select_none(wp);
	} else if (ch == '\n' || ch == '\r') {
	    ;	/* accept */
	} else if (isdigit(ch)) {
	    /* special case: '0' is also the default ball class */
	    if (ch == '0' && !menu_info->counting &&
		    index(menu_info->curr_menu.gacc, ch))
		goto group_accel;
	    menu_info->menu_count *= 10L;
	    menu_info->menu_count += (long)(ch - '0');
	    if (menu_info->menu_count != 0L)	/* ignore leading zeros */
		menu_info->counting = TRUE;
	    return;
	} else if (ch == MENU_SEARCH) {		/* search */
	    if (menu_info->how == PICK_ANY || menu_info->how == PICK_ONE) {
		char buf[BUFSZ];
		X11_getlin("Search for:", buf);
		if (!*buf || *buf == '\033') return;
		if (menu_info->how == PICK_ANY) {
		    invert_match(wp, buf);
		    return;
		} else {
		    select_match(wp, buf);
		}
	    } else {
		X11_nhbell();
		return;
	    }
	} else if (ch == MENU_SELECT_ALL) {		/* select all */
	    if (menu_info->how == PICK_ANY)
		select_all(wp);
	    else
		X11_nhbell();
	    return;
	} else if (ch == MENU_UNSELECT_ALL) {		/* unselect all */
	    if (menu_info->how == PICK_ANY)
		select_none(wp);
	    else
		X11_nhbell();
	    return;
	} else if (ch == MENU_INVERT_ALL) {		/* invert all */
	    if (menu_info->how == PICK_ANY)
		invert_all(wp);
	    else
		X11_nhbell();
	    return;
	} else if (index(menu_info->curr_menu.gacc, ch)) {
 group_accel:
	    /* matched a group accelerator */
	    if (menu_info->how == PICK_ANY || menu_info->how == PICK_ONE) {
		for (count = 0, curr = menu_info->curr_menu.base; curr;
						curr = curr->next, count++) {
		    if (curr->identifier.a_void != 0 && curr->gselector == ch) {
			invert_line(wp, curr, count, -1L);
			/* for PICK_ONE, a group accelerator will
			   only be included in gacc[] if it matches
			   exactly one entry, so this must be it... */
			if (menu_info->how == PICK_ONE)
			    goto menu_done;	/* pop down */
		    }
		}
#ifndef USE_FWF
		XawListChange(wp->w, menu_info->curr_menu.list_pointer, 0, 0, True);
#endif
	    } else
		X11_nhbell();
	    return;
	} else {
	    boolean selected_something = FALSE;
	    for (count = 0, curr = menu_info->curr_menu.base; curr;
						    curr = curr->next, count++)
		if (curr->identifier.a_void != 0 && curr->selector == ch) break;

	    if (curr) {
		invert_line(wp, curr, count,
			    menu_info->counting ? menu_info->menu_count : -1L);
#ifndef USE_FWF
		XawListChange(wp->w, menu_info->curr_menu.list_pointer, 0, 0, True);
#endif
		selected_something = curr->selected;
	    } else {
		X11_nhbell();		/* no match */
	    }
	    if (!(selected_something && menu_info->how == PICK_ONE))
		return;		/* keep going */
	}
	/* pop down */
    } else {			/* permanent inventory window */
	if (ch != '\033') {
	    X11_nhbell();
	    return;
	}
	/* pop down on ESC */
    }

 menu_done:
    menu_popdown(wp);
}

/* ARGSUSED */
static void
menu_ok(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    struct xwindow *wp = (struct xwindow *) client_data;

    menu_popdown(wp);
}

/* ARGSUSED */
static void
menu_cancel(w, client_data, call_data)
    Widget w;				/* don't use - may be None */
    XtPointer client_data, call_data;
{
    struct xwindow *wp = (struct xwindow *) client_data;

    if (wp->menu_information->is_active) {
	select_none(wp);
	wp->menu_information->cancelled = TRUE;
    }
    menu_popdown(wp);
}

/* ARGSUSED */
static void
menu_all(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    select_all((struct xwindow *) client_data);
}

/* ARGSUSED */
static void
menu_none(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    select_none((struct xwindow *) client_data);
}

/* ARGSUSED */
static void
menu_invert(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    invert_all((struct xwindow *) client_data);
}

/* ARGSUSED */
static void
menu_search(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    struct xwindow *wp = (struct xwindow *) client_data;
    struct menu_info_t *menu_info = wp->menu_information;

    char buf[BUFSZ];
    X11_getlin("Search for:", buf);
    if (!*buf || *buf == '\033') return;

    if (menu_info->how == PICK_ANY)
	invert_match(wp, buf);
    else
	select_match(wp, buf);

    if (menu_info->how == PICK_ONE)
	menu_popdown(wp);
}

static void
select_all(wp)
    struct xwindow *wp;
{
    x11_menu_item *curr;
    int count;
    boolean changed = FALSE;

    reset_menu_count(wp->menu_information);
    for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
					curr = curr->next, count++)
	if (curr->identifier.a_void != 0)
	    if (!curr->selected) {
		invert_line(wp, curr, count, -1L);
		changed = TRUE;
	    }

#ifndef USE_FWF
    if (changed)
	XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
		      0, 0, True);
#endif
}

static void
select_none(wp)
    struct xwindow *wp;
{
    x11_menu_item *curr;
    int count;
    boolean changed = FALSE;

    reset_menu_count(wp->menu_information);
    for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
					curr = curr->next, count++)
	if (curr->identifier.a_void != 0)
	    if (curr->selected) {
		invert_line(wp, curr, count, -1L);
		changed = TRUE;
	    }

#ifndef USE_FWF
    if (changed)
	XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
		      0, 0, True);
#endif
}

static void
invert_all(wp)
    struct xwindow *wp;
{
    x11_menu_item *curr;
    int count;

    reset_menu_count(wp->menu_information);
    for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
					curr = curr->next, count++)
	if (curr->identifier.a_void != 0)
	    invert_line(wp, curr, count, -1L);

#ifndef USE_FWF
    XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
		  0, 0, True);
#endif
}

static void
invert_match(wp, match)
    struct xwindow *wp;
    char *match;
{
    x11_menu_item *curr;
    int count;
    boolean changed = FALSE;

    reset_menu_count(wp->menu_information);
    for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
						curr = curr->next, count++)
	if (curr->identifier.a_void != 0 && strstri(curr->str, match)) {
	    invert_line(wp, curr, count, -1L);
	    changed = TRUE;
	}

#ifndef USE_FWF
    if (changed)
	XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
		      0, 0, True);
#endif
}

static void
select_match(wp, match)
    struct xwindow *wp;
    char *match;
{
    x11_menu_item *curr;
    int count;

    reset_menu_count(wp->menu_information);
    for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
						curr = curr->next, count++)
	if (curr->identifier.a_void != 0 && strstri(curr->str, match)) {
	    if (!curr->selected) {
		invert_line(wp, curr, count, -1L);
#ifndef USE_FWF
		XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
			      0, 0, True);
#endif
	    }
	    return;
	}

    /* no match */
    X11_nhbell();
}

static void
menu_popdown(wp)
    struct xwindow *wp;
{
    nh_XtPopdown(wp->popup);			/* remove the event grab */
    if (wp->menu_information->is_active)
	exit_x_event = TRUE;			/* exit our event handler */
    wp->menu_information->is_up = FALSE;	/* menu is down */
}

#ifdef USE_FWF
/*
 * Make sure our idea of selected matches the FWF Multilist's idea of what
 * is currently selected.  The MultiList's selected list can change without
 * notifying us if one or more items are selected and then another is
 * selected (not toggled).  Then the items that were selected are deselected
 * but we are not notified.
 */
static void
sync_selected(menu_info, num_selected, items)
    struct menu_info_t *menu_info;
    int num_selected;
    int *items;
{
    int i, j, *ip;
    x11_menu_item *curr;
    Boolean found;

    for (i=0, curr = menu_info->curr_menu.base; curr; i++, curr = curr->next) {
	found = False;
	for (j = 0, ip = items; j < num_selected; j++, ip++)
	    if (*ip == i) {
		found = True;
		break;
	    }
#if 0
	if (curr->selected && !found)
	    printf("sync: deselecting %s\n", curr->str);
	else if (!curr->selected && found)
	    printf("sync: selecting %s\n", curr->str);
#endif
	curr->selected = found ? TRUE : FALSE;
    }
}
#endif /* USE_FWF */


/* Global functions ======================================================== */

void
X11_start_menu(window)
    winid window;
{
    struct xwindow *wp;
    check_winid(window);

    wp = &window_list[window];

    if (wp->menu_information->is_menu) {
	/* make sure we'ere starting with a clean slate */
	free_menu(&wp->menu_information->new_menu);
    } else {
	wp->menu_information->is_menu = TRUE;
    }
}

/*ARGSUSED*/
void
X11_add_menu(window, glyph, identifier, ch, gch, attr, str, preselected)
    winid window;
    int glyph;			/* unused (for now) */
    const anything *identifier;
    char ch;
    char gch;			/* group accelerator (0 = no group) */
    int attr;
    const char *str;
    boolean preselected;
{
    x11_menu_item *item;
    struct menu_info_t *menu_info;

    check_winid(window);
    menu_info = window_list[window].menu_information;
    if (!menu_info->is_menu) {
	impossible("add_menu:  called before start_menu");
	return;
    }

    item = (x11_menu_item *) alloc((unsigned)sizeof(x11_menu_item));
    item->next = (x11_menu_item *) 0;
    item->identifier = *identifier;
    item->attr = attr;
/*    item->selected = preselected; */
    item->selected = FALSE;
    item->pick_count = -1L;

    if (identifier->a_void) {
	char buf[4+BUFSZ];
	int len = strlen(str);

	if (!ch) {
	    /* Supply a keyboard accelerator.  Only the first 52 get one. */

	    if (menu_info->new_menu.curr_selector) {
		ch = menu_info->new_menu.curr_selector++;
		if (ch == 'z')
		    menu_info->new_menu.curr_selector = 'A';
		else if (ch == 'Z')
		    menu_info->new_menu.curr_selector = 0;	/* out */
	    }
	}

	if (len >= BUFSZ) {
	    /* We *think* everything's coming in off at most BUFSZ bufs... */
	    impossible("Menu item too long (%d).", len);
	    len = BUFSZ - 1;
	}
	Sprintf(buf, "%c - ", ch ? ch : ' ');
	(void) strncpy(buf+4, str, len);
	buf[4+len] = '\0';
	item->str = copy_of(buf);
    } else {
	/* no keyboard accelerator */
	item->str = copy_of(str);
	ch = 0;
    }

    item->selector = ch;
    item->gselector = gch;

    if (menu_info->new_menu.last) {
	menu_info->new_menu.last->next = item;
    } else {
	menu_info->new_menu.base = item;
    }
    menu_info->new_menu.last = item;
    menu_info->new_menu.count++;
}

void
X11_end_menu(window, query)
    winid window;
    const char *query;
{
    struct menu_info_t *menu_info;

    check_winid(window);
    menu_info = window_list[window].menu_information;
    if (!menu_info->is_menu) {
	impossible("end_menu:  called before start_menu");
	return;
    }
    menu_info->new_menu.query = copy_of(query);
}

int
X11_select_menu(window, how, menu_list)
    winid window;
    int how;
    menu_item **menu_list;
{
    x11_menu_item *curr;
    struct xwindow *wp;
    struct menu_info_t *menu_info;
    Arg args[10];
    Cardinal num_args;
    String *ptr;
    int retval;
    Dimension v_pixel_width, v_pixel_height;
    boolean labeled;
    Widget viewport_widget, form, label, ok, cancel, all, none, invert, search;
    Boolean sens;
#ifdef USE_FWF
    Boolean *boolp;
#endif
    char gacc[QBUFSZ], *ap;

    *menu_list = (menu_item *) 0;
    check_winid(window);
    wp = &window_list[window];
    menu_info = wp->menu_information;
    if (!menu_info->is_menu) {
	impossible("select_menu:  called before start_menu");
	return 0;
    }

    menu_info->how = (short) how;

    /* collect group accelerators; for PICK_NONE, they're ignored;
       for PICK_ONE, only those which match exactly one entry will be
       accepted; for PICK_ANY, those which match any entry are okay */
    gacc[0] = '\0';
    if (menu_info->how != PICK_NONE) {
	int i, n, gcnt[128];
#define GSELIDX(c) ((c) & 127)	/* guard against `signed char' */

	for (i = 0; i < SIZE(gcnt); i++) gcnt[i] = 0;
	for (n = 0, curr = menu_info->new_menu.base; curr; curr = curr->next)
	    if (curr->gselector && curr->gselector != curr->selector) {
		++n;
		++gcnt[GSELIDX(curr->gselector)];
	    }

	if (n > 0)	/* at least one group accelerator found */
	    for (ap = gacc, curr = menu_info->new_menu.base;
		    curr; curr = curr->next)
		if (curr->gselector && !index(gacc, curr->gselector) &&
			(menu_info->how == PICK_ANY ||
			    gcnt[GSELIDX(curr->gselector)] == 1)) {
		    *ap++ = curr->gselector;
		    *ap = '\0'; /* re-terminate for index() */
		}
    }
    menu_info->new_menu.gacc = copy_of(gacc);
    reset_menu_count(menu_info);

    /*
     * Create a string and sensitive list for the new menu.
     */
    menu_info->new_menu.list_pointer = ptr = (String *)
	    alloc((unsigned) (sizeof(String) * (menu_info->new_menu.count+1)));
    for (curr = menu_info->new_menu.base; curr; ptr++, curr = curr->next)
	*ptr = (String) curr->str;
    *ptr = 0;		/* terminate list with null */

#ifdef USE_FWF
    menu_info->new_menu.sensitive = boolp = (Boolean *)
	    alloc((unsigned) (sizeof(Boolean) * (menu_info->new_menu.count)));
    for (curr = menu_info->new_menu.base; curr; boolp++, curr = curr->next)
	*boolp = (curr->identifier.a_void != 0);
#else
    menu_info->new_menu.sensitive = (Boolean *) 0;
#endif
    labeled = (menu_info->new_menu.query && *(menu_info->new_menu.query))
	? TRUE : FALSE;

    /*
     * Menus don't appear to size components correctly, except
     * when first created.  For 3.2.0 release, just recreate
     * each time.
     */
    if (menu_info->valid_widgets
			&& (window != WIN_INVEN || !flags.perm_invent)) {
	XtDestroyWidget(wp->popup);
	menu_info->valid_widgets = FALSE;
	menu_info->is_up = FALSE;
    }

    if (!menu_info->valid_widgets) {
	Dimension row_spacing;

	num_args = 0;
	XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
	wp->popup = XtCreatePopupShell(
			window == WIN_INVEN ? "inventory" : "menu",
			how == PICK_NONE ? topLevelShellWidgetClass:
					transientShellWidgetClass,
			toplevel, args, num_args);
	XtOverrideTranslations(wp->popup,
	    XtParseTranslationTable("<Message>WM_PROTOCOLS: menu_delete()"));


	num_args = 0;
	XtSetArg(args[num_args], XtNtranslations,
		XtParseTranslationTable(menu_translations));	num_args++;
	form = XtCreateManagedWidget("mform",
				    formWidgetClass,
				    wp->popup,
				    args, num_args);

	num_args = 0;
	XtSetArg(args[num_args], XtNborderWidth, 0);		num_args++;
	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;

	if (labeled)
	    label = XtCreateManagedWidget(menu_info->new_menu.query,
				    labelWidgetClass,
				    form,
				    args, num_args);
	else label = NULL;

	/*
	 * Create ok, cancel, all, none, invert, and search buttons..
	 */
	num_args = 0;
	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
	ok = XtCreateManagedWidget("OK",
			commandWidgetClass,
			form,
			args, num_args);
	XtAddCallback(ok, XtNcallback, menu_ok, (XtPointer) wp);

	num_args = 0;
	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
	XtSetArg(args[num_args], XtNfromHoriz, ok);		num_args++;
	XtSetArg(args[num_args], XtNsensitive, how!=PICK_NONE);	num_args++;
	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
	cancel = XtCreateManagedWidget("cancel",
			commandWidgetClass,
			form,
			args, num_args);
	XtAddCallback(cancel, XtNcallback, menu_cancel, (XtPointer) wp);

	sens = (how == PICK_ANY);
	num_args = 0;
	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
	XtSetArg(args[num_args], XtNfromHoriz, cancel);		num_args++;
	XtSetArg(args[num_args], XtNsensitive, sens);		num_args++;
	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
	all = XtCreateManagedWidget("all",
			commandWidgetClass,
			form,
			args, num_args);
	XtAddCallback(all, XtNcallback, menu_all, (XtPointer) wp);

	num_args = 0;
	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
	XtSetArg(args[num_args], XtNfromHoriz, all);		num_args++;
	XtSetArg(args[num_args], XtNsensitive, sens);		num_args++;
	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
	none = XtCreateManagedWidget("none",
			commandWidgetClass,
			form,
			args, num_args);
	XtAddCallback(none, XtNcallback, menu_none, (XtPointer) wp);

	num_args = 0;
	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
	XtSetArg(args[num_args], XtNfromHoriz, none);		num_args++;
	XtSetArg(args[num_args], XtNsensitive, sens);		num_args++;
	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
	invert = XtCreateManagedWidget("invert",
			commandWidgetClass,
			form,
			args, num_args);
	XtAddCallback(invert, XtNcallback, menu_invert, (XtPointer) wp);

	num_args = 0;
	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
	XtSetArg(args[num_args], XtNfromHoriz, invert);		num_args++;
	XtSetArg(args[num_args], XtNsensitive, how!=PICK_NONE);	num_args++;
	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
	search = XtCreateManagedWidget("search",
			commandWidgetClass,
			form,
			args, num_args);
	XtAddCallback(search, XtNcallback, menu_search, (XtPointer) wp);

	num_args = 0;
	XtSetArg(args[num_args], XtNallowVert,  True);		num_args++;
	XtSetArg(args[num_args], XtNallowHoriz, False);		num_args++;
	XtSetArg(args[num_args], XtNuseBottom, True);		num_args++;
	XtSetArg(args[num_args], XtNuseRight, True);		num_args++;
/*
	XtSetArg(args[num_args], XtNforceBars, True);		num_args++;
*/
	XtSetArg(args[num_args], XtNfromVert, all);		num_args++;
	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
	XtSetArg(args[num_args], XtNbottom, XtChainBottom);	num_args++;
	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
	XtSetArg(args[num_args], XtNright, XtChainRight);	num_args++;
	viewport_widget = XtCreateManagedWidget(
		    "menu_viewport",	/* name */
		    viewportWidgetClass,
		    form,		/* parent widget */
		    args, num_args);	/* values, and number of values */

	/* make new menu the current menu */
	move_menu(&menu_info->new_menu, &menu_info->curr_menu);

	num_args = 0;
	XtSetArg(args[num_args], XtNforceColumns, True);	num_args++;
	XtSetArg(args[num_args], XtNcolumnSpacing, 1);		num_args++;
	XtSetArg(args[num_args], XtNdefaultColumns, 1);		num_args++;
	XtSetArg(args[num_args], XtNlist,
			menu_info->curr_menu.list_pointer);	num_args++;
#ifdef USE_FWF
	XtSetArg(args[num_args], XtNsensitiveArray,
			menu_info->curr_menu.sensitive);	num_args++;
	XtSetArg(args[num_args], XtNmaxSelectable,
			menu_info->curr_menu.count);		num_args++;
#endif
	wp->w = XtCreateManagedWidget(
		    "menu_list",		/* name */
#ifdef USE_FWF
		    xfwfMultiListWidgetClass,
#else
		    listWidgetClass,
#endif
		    viewport_widget,		/* parent widget */
		    args,			/* set some values */
		    num_args);			/* number of values to set */

	XtAddCallback(wp->w, XtNcallback, menu_select, (XtPointer) 0);

	/* Get the font and margin information. */
	num_args = 0;
	XtSetArg(args[num_args], XtNfont, &menu_info->fs);	num_args++;
	XtSetArg(args[num_args], XtNinternalHeight,
				&menu_info->internal_height);	num_args++;
	XtSetArg(args[num_args], XtNinternalWidth,
				&menu_info->internal_width);	num_args++;
	XtSetArg(args[num_args], XtNrowSpacing, &row_spacing);	num_args++;
	XtGetValues(wp->w, args, num_args);

	/* font height is ascent + descent */
	menu_info->line_height =
		menu_info->fs->max_bounds.ascent +
		menu_info->fs->max_bounds.descent + row_spacing;

	menu_info->valid_widgets = TRUE;

	num_args = 0;
	XtSetArg(args[num_args], XtNwidth, &v_pixel_width);	num_args++;
	XtSetArg(args[num_args], XtNheight, &v_pixel_height);	num_args++;
	XtGetValues(wp->w, args, num_args);
    } else {
	Dimension len;

	viewport_widget = XtParent(wp->w);

	/* get the longest string on new menu */
	v_pixel_width = 0;
	for (ptr = menu_info->new_menu.list_pointer; *ptr; ptr++) {
	    len = XTextWidth(menu_info->fs, *ptr, strlen(*ptr));
	    if (len > v_pixel_width) v_pixel_width = len;
	}

	/* add viewport internal border */
	v_pixel_width += 2 * menu_info->internal_width;
	v_pixel_height = (2 * menu_info->internal_height) +
	    (menu_info->new_menu.count * menu_info->line_height);

	/* make new menu the current menu */
	move_menu(&menu_info->new_menu, &menu_info->curr_menu);
#ifdef USE_FWF
	XfwfMultiListSetNewData((XfwfMultiListWidget)wp->w,
		menu_info->curr_menu.list_pointer, 0, 0, TRUE,
		menu_info->curr_menu.sensitive);
#else
	XawListChange(wp->w, menu_info->curr_menu.list_pointer, 0, 0, TRUE);
#endif
    }

    /* if viewport will be bigger than the screen, limit its height */
    num_args = 0;
    XtSetArg(args[num_args], XtNwidth, &v_pixel_width);	num_args++;
    XtSetArg(args[num_args], XtNheight, &v_pixel_height);	num_args++;
    XtGetValues(wp->w, args, num_args);
    if ((Dimension) XtScreen(wp->w)->height * 5 / 6 < v_pixel_height) {
	/* scrollbar is 14 pixels wide.  Widen the form to accommodate it. */
	v_pixel_width += 14;

	/* shrink to fit vertically */
	v_pixel_height = XtScreen(wp->w)->height * 5 / 6;

	num_args = 0;
	XtSetArg(args[num_args], XtNwidth, v_pixel_width); num_args++;
	XtSetArg(args[num_args], XtNheight, v_pixel_height); num_args++;
	XtSetValues(wp->w, args, num_args);
    }
    XtRealizeWidget(wp->popup);	/* need to realize before we position */

    /* if menu is not up, position it */
    if (!menu_info->is_up) positionpopup(wp->popup, FALSE);

    menu_info->is_up = TRUE;
    if (window == WIN_INVEN && how == PICK_NONE) {
	/* cant use nh_XtPopup() because it may try to grab the focus */
	XtPopup(wp->popup, (int)XtGrabNone);
	if (!updated_inventory)
	    XMapRaised(XtDisplay(wp->popup), XtWindow(wp->popup));
	XSetWMProtocols(XtDisplay(wp->popup), XtWindow(wp->popup),
							&wm_delete_window, 1);
	retval = 0;
    } else {
	menu_info->is_active = TRUE;	/* waiting for user response */
	menu_info->cancelled = FALSE;
	nh_XtPopup(wp->popup, (int)XtGrabExclusive, wp->w);
	(void) x_event(EXIT_ON_EXIT);
	menu_info->is_active = FALSE;
	if (menu_info->cancelled)
	    return -1;

	retval = 0;
	for (curr = menu_info->curr_menu.base; curr; curr = curr->next)
	    if (curr->selected) retval++;

	if (retval) {
	    menu_item *mi;

	    *menu_list = mi = (menu_item *) alloc(retval * sizeof(menu_item));
	    for (curr = menu_info->curr_menu.base; curr; curr = curr->next)
		if (curr->selected) {
		    mi->item = curr->identifier;
		    mi->count = curr->pick_count;
		    mi++;
		}
	}
    }

    return retval;
}

/* End global functions ==================================================== */

/*
 * Allocate a copy of the given string.  If null, return a string of
 * zero length.
 *
 * This is an exact duplicate of copy_of() in tty/wintty.c.
 */
static char *
copy_of(s)
    const char *s;
{
    if (!s) s = "";
    return strcpy((char *) alloc((unsigned) (strlen(s) + 1)), s);
}


static void
move_menu(src_menu, dest_menu)
    struct menu *src_menu, *dest_menu;
{
    free_menu(dest_menu);	/* toss old menu */
    *dest_menu = *src_menu;	/* make new menu current */
				/* leave no dangling ptrs */
    reset_menu_to_default(src_menu);
}


static void
free_menu(mp)
    struct menu *mp;
{
    while (mp->base) {
	mp->last = mp->base;
	mp->base = mp->base->next;

	free((genericptr_t)mp->last->str);
	free((genericptr_t)mp->last);
    }
    if (mp->query) free((genericptr_t) mp->query);
    if (mp->gacc) free((genericptr_t) mp->gacc);
    if (mp->list_pointer) free((genericptr_t) mp->list_pointer);
    if (mp->sensitive) free((genericptr_t) mp->sensitive);
    reset_menu_to_default(mp);
}

static void
reset_menu_to_default(mp)
    struct menu *mp;
{
    mp->base = mp->last = (x11_menu_item *)0;
    mp->query = (const char *)0;
    mp->gacc = (const char *)0;
    mp->count = 0;
    mp->list_pointer = (String *)0;
    mp->sensitive = (Boolean *)0;
    mp->curr_selector = 'a';	/* first accelerator */
}

static void
clear_old_menu(wp)
    struct xwindow *wp;
{
    struct menu_info_t *menu_info = wp->menu_information;

    free_menu(&menu_info->curr_menu);
    free_menu(&menu_info->new_menu);

    if (menu_info->valid_widgets) {
	nh_XtPopdown(wp->popup);
	menu_info->is_up = FALSE;
	XtDestroyWidget(wp->popup);
	menu_info->valid_widgets = FALSE;
	wp->w = wp->popup = (Widget) 0;
    }
}

void
create_menu_window(wp)
    struct xwindow *wp;
{
    wp->type = NHW_MENU;
    wp->menu_information =
		(struct menu_info_t *) alloc(sizeof(struct menu_info_t));
    (void) memset((genericptr_t) wp->menu_information, '\0',
						sizeof(struct menu_info_t));
    reset_menu_to_default(&wp->menu_information->curr_menu);
    reset_menu_to_default(&wp->menu_information->new_menu);
    reset_menu_count(wp->menu_information);
    wp->w = wp->popup = (Widget) 0;
}

void
destroy_menu_window(wp)
    struct xwindow *wp;
{
    clear_old_menu(wp);		/* this will also destroy the widgets */
    free((genericptr_t) wp->menu_information);
    wp->menu_information = (struct menu_info_t *) 0;
    wp->type = NHW_NONE;	/* allow re-use */
}

/*winmenu.c*/
