/*	SCCS Id: @(#)winmisc.c	3.3	2000/05/21	*/
/* Copyright (c) Dean Luick, 1992				  */
/* NetHack may be freely redistributed.  See license for details. */

/*
 * Misc. popup windows: player selection and extended commands.
 *
 *	+ Global functions: player_selection() and get_ext_cmd().
 */

#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/Xaw/Command.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xos.h>	/* for index() */
#include <X11/Xatom.h>

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

#include "hack.h"
#include "func_tab.h"
#include "winX.h"


static Widget extended_command_popup;
static Widget extended_command_form;
static Widget *extended_commands = 0;
static int extended_command_selected;	/* index of the selected command; */
static int ps_selected;			/* index of selected role */
#define PS_RANDOM (-50)
#define PS_QUIT   (-75)
static const char ps_randchars[] = "*@";
static const char ps_quitchars[] = "\033qQ";

#define EC_NCHARS 32
static boolean ec_active = FALSE;
static int ec_nchars = 0;
static char ec_chars[EC_NCHARS];
static Time ec_time;

static const char extended_command_translations[] =
    "#override\n\
     <Key>: ec_key()";

static const char player_select_translations[] =
    "#override\n\
     <Key>: ps_key()";
static const char race_select_translations[] =
    "#override\n\
     <Key>: race_key()";
static const char gend_select_translations[] =
    "#override\n\
     <Key>: gend_key()";
static const char algn_select_translations[] =
    "#override\n\
     <Key>: algn_key()";

static void NDECL(ec_dismiss);
static Widget FDECL(make_menu, (const char *,const char *,const char *,
				const char *,XtCallbackProc,
				const char *,XtCallbackProc,
				int,const char **, Widget **,
				XtCallbackProc,Widget *));
static void NDECL(init_extended_commands_popup);
static void FDECL(ps_quit, (Widget,XtPointer,XtPointer));
static void FDECL(ps_random, (Widget,XtPointer,XtPointer));
static void FDECL(ps_select, (Widget,XtPointer,XtPointer));


/* Player Selection -------------------------------------------------------- */
/* ARGSUSED */
static void
ps_quit(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    ps_selected = PS_QUIT;
    exit_x_event = TRUE;		/* leave event loop */
}

/* ARGSUSED */
static void
ps_random(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    ps_selected = PS_RANDOM;
    exit_x_event = TRUE;		/* leave event loop */
}

/* ARGSUSED */
static void
ps_select(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    ps_selected = (int) client_data;
    exit_x_event = TRUE;		/* leave event loop */
}

/* ARGSUSED */
void
ps_key(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    char ch, *mark;
    char rolechars[QBUFSZ];
    int i;

    (void)memset(rolechars, '\0', sizeof rolechars);  /* for index() */
    for (i = 0; roles[i].name.m; ++i) {
	ch = lowc(*roles[i].name.m);
     /* if (flags.female && roles[i].name.f) ch = lowc(*roles[i].name.f); */
	/* this supports at most two roles with the same first letter */
	if (index(rolechars, ch)) ch = highc(ch);
	rolechars[i] = ch;
    }
    ch = key_event_to_char((XKeyEvent *) event);
    if (ch == '\0') {	/* don't accept nul char/modifier event */
	/* don't beep */
	return;
    }
    mark = index(rolechars, ch);
    if (!mark) mark = index(rolechars, lowc(ch));
    if (!mark) mark = index(rolechars, highc(ch));
    if (!mark) {
	if (index(ps_randchars, ch))
	    ps_selected = PS_RANDOM;
	else if (index(ps_quitchars, ch))
	    ps_selected = PS_QUIT;
	else {
	    X11_nhbell();	/* no such class */
	    return;
	}
    } else
	ps_selected = (int)(mark - rolechars);
    exit_x_event = TRUE;
}

/* ARGSUSED */
void
race_key(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    char ch, *mark;
    char racechars[QBUFSZ];
    int i;

    (void)memset(racechars, '\0', sizeof racechars);  /* for index() */
    for (i = 0; races[i].noun; ++i) {
	ch = lowc(*races[i].noun);
	/* this supports at most two races with the same first letter */
	if (index(racechars, ch)) ch = highc(ch);
	racechars[i] = ch;
    }
    ch = key_event_to_char((XKeyEvent *) event);
    if (ch == '\0') {	/* don't accept nul char/modifier event */
	/* don't beep */
	return;
    }
    mark = index(racechars, ch);
    if (!mark) mark = index(racechars, lowc(ch));
    if (!mark) mark = index(racechars, highc(ch));
    if (!mark) {
	if (index(ps_randchars, ch))
	    ps_selected = PS_RANDOM;
	else if (index(ps_quitchars, ch))
	    ps_selected = PS_QUIT;
	else {
	    X11_nhbell();	/* no such race */
	    return;
	}
    } else
	ps_selected = (int)(mark - racechars);
    exit_x_event = TRUE;
}

/* ARGSUSED */
void
gend_key(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    char ch, *mark;
    static char gendchars[] = "mf";

    ch = key_event_to_char((XKeyEvent *) event);
    if (ch == '\0') {	/* don't accept nul char/modifier event */
	/* don't beep */
	return;
    }
    mark = index(gendchars, ch);
    if (!mark) mark = index(gendchars, lowc(ch));
    if (!mark) {
	if (index(ps_randchars, ch))
	    ps_selected = PS_RANDOM;
	else if (index(ps_quitchars, ch))
	    ps_selected = PS_QUIT;
	else {
	    X11_nhbell();	/* no such gender */
	    return;
	}
    } else
	ps_selected = (int)(mark - gendchars);
    exit_x_event = TRUE;
}

/* ARGSUSED */
void
algn_key(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    char ch, *mark;
    static char algnchars[] = "LNC";

    ch = key_event_to_char((XKeyEvent *) event);
    if (ch == '\0') {	/* don't accept nul char/modifier event */
	/* don't beep */
	return;
    }
    mark = index(algnchars, ch);
    if (!mark) mark = index(algnchars, highc(ch));
    if (!mark) {
	if (index(ps_randchars, ch))
	    ps_selected = PS_RANDOM;
	else if (index(ps_quitchars, ch))
	    ps_selected = PS_QUIT;
	else {
	    X11_nhbell();	/* no such alignment */
	    return;
	}
    } else
	ps_selected = (int)(mark - algnchars);
    exit_x_event = TRUE;
}

/* Global functions ========================================================= */
void
X11_player_selection()
{
    int num_roles, num_races, num_gends, num_algns,
	i, availcount, availindex;
    Widget popup, player_form;
    const char **choices;
    const char *namep;
    char qbuf[QBUFSZ];

    while (flags.initrole < 0) {
	if (flags.initrole == ROLE_RANDOM) {
	    flags.initrole = pick_role(flags.initrace,
				       flags.initgend, flags.initalign);
	    break;
	}
	/* select a role */
	for (num_roles = 0; roles[num_roles].name.m; ++num_roles) continue;
	choices = (const char **)alloc(sizeof(char *) * num_roles);
	for (;;) {
	    availcount = 0;
	    for (i = 0; i < num_roles; i++) {
		choices[i] = 0;
		if (ok_role(i, flags.initrace,
			    flags.initgend, flags.initalign)) {
		    choices[i] = roles[i].name.m;
		    if (flags.initgend >= 0 && flags.female && roles[i].name.f)
			choices[i] = roles[i].name.f;
		    ++availcount;
		}
	    }
	    if (availcount > 0) break;
	    else if (flags.initalign >= 0) flags.initalign = -1;    /* reset */
	    else if (flags.initgend >= 0) flags.initgend = -1;
	    else if (flags.initrace >= 0) flags.initrace = -1;
	    else panic("no available ROLE+race+gender+alignment combinations");
	}
	popup = make_menu("player_selection", "Choose a Role",
		    player_select_translations,
		    "quit", ps_quit,
		    "random", ps_random,
		    num_roles, choices, (Widget **)0, ps_select, &player_form);

	ps_selected = -1;
	positionpopup(popup, FALSE);
	nh_XtPopup(popup, (int)XtGrabExclusive, player_form);

	/* The callbacks will enable the event loop exit. */
	(void) x_event(EXIT_ON_EXIT);

	nh_XtPopdown(popup);
	XtDestroyWidget(popup);
	free((genericptr_t)choices), choices = 0;

	if (ps_selected == PS_QUIT) {
	    clearlocks();
	    X11_exit_nhwindows((char *)0);
	    terminate(0);
	} else if (ps_selected == PS_RANDOM) {
	    flags.initrole = ROLE_RANDOM;
	} else if (ps_selected < 0 || ps_selected >= num_roles) {
	    panic("player_selection: bad role select value %d\n", ps_selected);
	} else {
	    flags.initrole = ps_selected;
	}
    }

    while (!validrace(flags.initrole, flags.initrace)) {
	if (flags.initrace == ROLE_RANDOM) {
	    flags.initrace = pick_race(flags.initrole,
				       flags.initgend, flags.initalign);
	    break;
	}
	/* select a race */
	for (num_races = 0; races[num_races].noun; ++num_races) continue;
	choices = (const char **)alloc(sizeof(char *) * num_races);
	for (;;) {
	    availcount = availindex = 0;
	    for (i = 0; i < num_races; i++) {
		choices[i] = 0;
		if (ok_race(flags.initrole, i,
			    flags.initgend, flags.initalign)) {
		    choices[i] = races[i].noun;
		    ++availcount;
		    availindex = i;	/* used iff only one */
		}
	    }
	    if (availcount > 0) break;
	    else if (flags.initalign >= 0) flags.initalign = -1;    /* reset */
	    else if (flags.initgend >= 0) flags.initgend = -1;
	    else panic("no available role+RACE+gender+alignment combinations");
	}

	if (availcount == 1) {
	    flags.initrace = availindex;
	    free((genericptr_t)choices), choices = 0;
	} else {
	    namep = roles[flags.initrole].name.m;
	    if (flags.initgend >= 0 && flags.female &&
		    roles[flags.initrole].name.f)
		namep = roles[flags.initrole].name.f;
	    Sprintf(qbuf, "Pick your %s race", s_suffix(namep));
	    popup = make_menu("race_selection", qbuf,
			race_select_translations,
			"quit", ps_quit,
			"random", ps_random,
			num_races, choices, (Widget **)0,
			ps_select, &player_form);

	    ps_selected = -1;
	    positionpopup(popup, FALSE);
	    nh_XtPopup(popup, (int)XtGrabExclusive, player_form);

	    /* The callbacks will enable the event loop exit. */
	    (void) x_event(EXIT_ON_EXIT);

	    nh_XtPopdown(popup);
	    XtDestroyWidget(popup);
	    free((genericptr_t)choices), choices = 0;

	    if (ps_selected == PS_QUIT) {
		clearlocks();
		X11_exit_nhwindows((char *)0);
		terminate(0);
	    } else if (ps_selected == PS_RANDOM) {
		flags.initrace = ROLE_RANDOM;
	    } else if (ps_selected < 0 || ps_selected >= num_races) {
		panic("player_selection: bad race select value %d\n", ps_selected);
	    } else {
		flags.initrace = ps_selected;
	    }
	} /* more than one race choice available */
    }

    while (!validgend(flags.initrole, flags.initrace, flags.initgend)) {
	if (flags.initgend == ROLE_RANDOM) {
	    flags.initgend = pick_gend(flags.initrole, flags.initrace,
				       flags.initalign);
	    break;
	}
	/* select a gender */
	num_gends = 2;		/* genders[2] isn't allowed */
	choices = (const char **)alloc(sizeof(char *) * num_gends);
	for (;;) {
	    availcount = availindex = 0;
	    for (i = 0; i < num_gends; i++) {
		choices[i] = 0;
		if (ok_gend(flags.initrole, flags.initrace,
			    i, flags.initalign)) {
		    choices[i] = genders[i].adj;
		    ++availcount;
		    availindex = i;	/* used iff only one */
		}
	    }
	    if (availcount > 0) break;
	    else if (flags.initalign >= 0) flags.initalign = -1;    /* reset */
	    else panic("no available role+race+GENDER+alignment combinations");
	}

	if (availcount == 1) {
	    flags.initgend = availindex;
	    free((genericptr_t)choices), choices = 0;
	} else {
	    namep = roles[flags.initrole].name.m;
	    if (flags.initgend >= 0 && flags.female &&
		    roles[flags.initrole].name.f)
		namep = roles[flags.initrole].name.f;
	    Sprintf(qbuf, "Your %s %s gender?",
		    races[flags.initrace].adj, s_suffix(namep));
	    popup = make_menu("gender_selection", qbuf,
			gend_select_translations,
			"quit", ps_quit,
			"random", ps_random,
			num_gends, choices, (Widget **)0,
			ps_select, &player_form);

	    ps_selected = -1;
	    positionpopup(popup, FALSE);
	    nh_XtPopup(popup, (int)XtGrabExclusive, player_form);

	    /* The callbacks will enable the event loop exit. */
	    (void) x_event(EXIT_ON_EXIT);

	    nh_XtPopdown(popup);
	    XtDestroyWidget(popup);
	    free((genericptr_t)choices), choices = 0;

	    if (ps_selected == PS_QUIT) {
		clearlocks();
		X11_exit_nhwindows((char *)0);
		terminate(0);
	    } else if (ps_selected == PS_RANDOM) {
		flags.initgend = ROLE_RANDOM;
	    } else if (ps_selected < 0 || ps_selected >= num_gends) {
		panic("player_selection: bad gender select value %d\n", ps_selected);
	    } else {
		flags.initgend = ps_selected;
	    }
	} /* more than one gender choice available */
    }

    while (!validalign(flags.initrole, flags.initrace, flags.initalign)) {
	if (flags.initalign == ROLE_RANDOM) {
	    flags.initalign = pick_align(flags.initrole, flags.initrace,
					 flags.initgend);
	    break;
	}
	/* select an alignment */
	num_algns = 3;		/* aligns[3] isn't allowed */
	choices = (const char **)alloc(sizeof(char *) * num_algns);
	for (;;) {
	    availcount = availindex = 0;
	    for (i = 0; i < num_algns; i++) {
		choices[i] = 0;
		if (ok_align(flags.initrole, flags.initrace,
			     flags.initgend, i)) {
		    choices[i] = aligns[i].adj;
		    ++availcount;
		    availindex = i;	/* used iff only one */
		}
	    }
	    if (availcount > 0) break;
	    else panic("no available role+race+gender+ALIGNMENT combinations");
	}

	if (availcount == 1) {
	    flags.initalign = availindex;
	    free((genericptr_t)choices), choices = 0;
	} else {
	    namep = roles[flags.initrole].name.m;
	    if (flags.initgend >= 0 && flags.female &&
		    roles[flags.initrole].name.f)
		namep = roles[flags.initrole].name.f;
	    Sprintf(qbuf, "%s %s %s alignment?",
		    genders[flags.initgend].adj,
		    races[flags.initrace].adj, s_suffix(namep));
	    qbuf[0] = highc(qbuf[0]);
	    popup = make_menu("alignment_selection", qbuf,
			algn_select_translations,
			"quit", ps_quit,
			"random", ps_random,
			num_algns, choices, (Widget **)0,
			ps_select, &player_form);

	    ps_selected = -1;
	    positionpopup(popup, FALSE);
	    nh_XtPopup(popup, (int)XtGrabExclusive, player_form);

	    /* The callbacks will enable the event loop exit. */
	    (void) x_event(EXIT_ON_EXIT);

	    nh_XtPopdown(popup);
	    XtDestroyWidget(popup);
	    free((genericptr_t)choices), choices = 0;

	    if (ps_selected == PS_QUIT) {
		clearlocks();
		X11_exit_nhwindows((char *)0);
		terminate(0);
	    } else if (ps_selected == PS_RANDOM) {
		flags.initalign = ROLE_RANDOM;
	    } else if (ps_selected < 0 || ps_selected >= num_algns) {
		panic("player_selection: bad alignment select value %d\n", ps_selected);
	    } else {
		flags.initalign = ps_selected;
	    }
	} /* more than one alignment choice available */
    }
}


int
X11_get_ext_cmd()
{
    static Boolean initialized = False;

    if (!initialized) {
	init_extended_commands_popup();
	initialized = True;
    }

    extended_command_selected = -1;		/* reset selected value */

    positionpopup(extended_command_popup, FALSE); /* center on cursor */
    nh_XtPopup(extended_command_popup, (int)XtGrabExclusive,
					extended_command_form);

    /* The callbacks will enable the event loop exit. */
    (void) x_event(EXIT_ON_EXIT);

    return extended_command_selected;
}

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

/* Extended Command -------------------------------------------------------- */
/* ARGSUSED */
static void
extend_select(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    int selected = (int) client_data;

    if (extended_command_selected != selected) {
	/* visibly deselect old one */
	if (extended_command_selected >= 0)
	    swap_fg_bg(extended_commands[extended_command_selected]);

	/* select new one */
	swap_fg_bg(extended_commands[selected]);
	extended_command_selected = selected;
    }

    nh_XtPopdown(extended_command_popup);
    /* reset colors while popped down */
    swap_fg_bg(extended_commands[extended_command_selected]);
    ec_active = FALSE;
    exit_x_event = TRUE;		/* leave event loop */
}

/* ARGSUSED */
static void
extend_dismiss(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    ec_dismiss();
}

/* ARGSUSED */
static void
extend_help(w, client_data, call_data)
    Widget w;
    XtPointer client_data, call_data;
{
    /* We might need to make it known that we already have one listed. */
    (void) doextlist();
}

/* ARGSUSED */
void
ec_delete(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    ec_dismiss();
}

static void
ec_dismiss()
{
    /* unselect while still visible */
    if (extended_command_selected >= 0)
	swap_fg_bg(extended_commands[extended_command_selected]);
    extended_command_selected = -1;	/* dismiss */
    nh_XtPopdown(extended_command_popup);
    ec_active = FALSE;
    exit_x_event = TRUE;		/* leave event loop */
}

/* ARGSUSED */
void
ec_key(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    char ch;
    int i;
    XKeyEvent *xkey = (XKeyEvent *) event;

    ch = key_event_to_char(xkey);

    if (ch == '\0') {	/* don't accept nul char/modifier event */
	/* don't beep */
	return;
    } else if (index("\033\n\r", ch)) {
	if (ch == '\033') {
	    /* unselect while still visible */
	    if (extended_command_selected >= 0)
		swap_fg_bg(extended_commands[extended_command_selected]);
	    extended_command_selected = -1;	/* dismiss */
	}

	nh_XtPopdown(extended_command_popup);
	/* unselect while invisible */
	if (extended_command_selected >= 0)
	    swap_fg_bg(extended_commands[extended_command_selected]);

	exit_x_event = TRUE;		/* leave event loop */
	ec_active = FALSE;
	return;
    }

    /* too much time has elapsed */
    if ((xkey->time - ec_time) > 500)
	ec_active = FALSE;

    if (!ec_active) {
	ec_nchars = 0;
	ec_active = TRUE;
    }

    ec_time = xkey->time;
    ec_chars[ec_nchars++] = ch;
    if (ec_nchars >= EC_NCHARS)
	ec_nchars = EC_NCHARS-1;	/* don't overflow */

    for (i = 0; extcmdlist[i].ef_txt; i++) {
	if (extcmdlist[i].ef_txt[0] == '?') continue;

	if (!strncmp(ec_chars, extcmdlist[i].ef_txt, ec_nchars)) {
	    if (extended_command_selected != i) {
		/* I should use set() and unset() actions, but how do */
		/* I send the an action to the widget? */
		if (extended_command_selected >= 0)
		    swap_fg_bg(extended_commands[extended_command_selected]);
		extended_command_selected = i;
		swap_fg_bg(extended_commands[extended_command_selected]);
	    }
	    break;
	}
    }
}

/*
 * Use our own home-brewed version menu because simpleMenu is designed to
 * be used from a menubox.
 */
static void
init_extended_commands_popup()
{
    int i, num_commands;
    const char **command_list;

    /* count commands */
    for (num_commands = 0; extcmdlist[num_commands].ef_txt; num_commands++)
	;	/* do nothing */

    /* If the last entry is "help", don't use it. */
    if (strcmp(extcmdlist[num_commands-1].ef_txt, "?") == 0)
	--num_commands;

    command_list =
		(const char **) alloc((unsigned)num_commands * sizeof(char *));

    for (i = 0; i < num_commands; i++)
	command_list[i] = extcmdlist[i].ef_txt;

    extended_command_popup = make_menu("extended_commands",
				"Extended Commands",
				extended_command_translations,
				"dismiss", extend_dismiss,
				"help", extend_help,
				num_commands, command_list, &extended_commands,
				extend_select, &extended_command_form);

    free((char *)command_list);
}

/* ------------------------------------------------------------------------- */

/*
 * Create a popup widget of the following form:
 *
 *		      popup_label
 *		----------- ------------
 *		|left_name| |right_name|
 *		----------- ------------
 *		------------------------
 *		|	name1	       |
 *		------------------------
 *		------------------------
 *		|	name2	       |
 *		------------------------
 *			  .
 *			  .
 *		------------------------
 *		|	nameN	       |
 *		------------------------
 */
static Widget
make_menu(popup_name, popup_label, popup_translations,
		left_name, left_callback,
		right_name, right_callback,
		num_names, widget_names, command_widgets, name_callback, formp)
    const char	   *popup_name;
    const char	   *popup_label;
    const char	   *popup_translations;
    const char	   *left_name;
    XtCallbackProc left_callback;
    const char	   *right_name;
    XtCallbackProc right_callback;
    int		   num_names;
    const char	   **widget_names;	/* return array of command widgets */
    Widget	   **command_widgets;
    XtCallbackProc name_callback;
    Widget	   *formp;	/* return */
{
    Widget popup, form, label, above, left, right;
    Widget *commands, *curr;
    int i;
    Arg args[8];
    Cardinal num_args;
    Dimension width, max_width;
    int distance, skip;


    commands = (Widget *) alloc((unsigned)num_names * sizeof(Widget));


    num_args = 0;
    XtSetArg(args[num_args], XtNallowShellResize, True);	num_args++;

    popup = XtCreatePopupShell(popup_name,
				transientShellWidgetClass,
				toplevel, args, num_args);
    XtOverrideTranslations(popup,
	XtParseTranslationTable("<Message>WM_PROTOCOLS: ec_delete()"));

    num_args = 0;
    XtSetArg(args[num_args], XtNtranslations,
		XtParseTranslationTable(popup_translations));	num_args++;
    *formp = form = XtCreateManagedWidget("menuform",
				formWidgetClass,
				popup,
				args, num_args);

    /* Get the default distance between objects in the form widget. */
    num_args = 0;
    XtSetArg(args[num_args], XtNdefaultDistance, &distance);	num_args++;
    XtGetValues(form, args, num_args);

    /*
     * Create the label.
     */
    num_args = 0;
    XtSetArg(args[num_args], XtNborderWidth, 0);	num_args++;
    label = XtCreateManagedWidget(popup_label,
				labelWidgetClass,
				form,
				args, num_args);

    /*
     * Create the left button.
     */
    num_args = 0;
    XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
/*
    XtSetArg(args[num_args], XtNshapeStyle,
				XmuShapeRoundedRectangle);	num_args++;
*/
    left = XtCreateManagedWidget(left_name,
		    commandWidgetClass,
		    form,
		    args, num_args);
    XtAddCallback(left, XtNcallback, left_callback, (XtPointer) 0);
    skip = 3*distance;	/* triple the spacing */
    if(!skip) skip = 3;

    /*
     * Create right button.
     */
    num_args = 0;
    XtSetArg(args[num_args], XtNfromHoriz, left);		num_args++;
    XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
/*
    XtSetArg(args[num_args], XtNshapeStyle,
				XmuShapeRoundedRectangle);	num_args++;
*/
    right = XtCreateManagedWidget(right_name,
		    commandWidgetClass,
		    form,
		    args, num_args);
    XtAddCallback(right, XtNcallback, right_callback, (XtPointer) 0);

    XtInstallAccelerators(form, left);
    XtInstallAccelerators(form, right);

    /*
     * Create and place the command widgets.
     */
    for (i = 0, above = left, curr = commands; i < num_names; i++) {
	if (!widget_names[i]) continue;
	num_args = 0;
	XtSetArg(args[num_args], XtNfromVert, above);	num_args++;
	if (above == left) {
	    /* if first, we are farther apart */
	    XtSetArg(args[num_args], XtNvertDistance, skip);	num_args++;
	}

	*curr = XtCreateManagedWidget(widget_names[i],
		    commandWidgetClass,
		    form,
		    args, num_args);
	XtAddCallback(*curr, XtNcallback, name_callback, (XtPointer) i);
	above = *curr++;
    }

    /*
     * Now find the largest width.  Start with the width dismiss + help
     * buttons, since they are adjacent.
     */
    XtSetArg(args[0], XtNwidth, &max_width);
    XtGetValues(left, args, ONE);
    XtSetArg(args[0], XtNwidth, &width);
    XtGetValues(right, args, ONE);
    max_width = max_width + width + distance;

    /* Next, the title. */
    XtSetArg(args[0], XtNwidth, &width);
    XtGetValues(label, args, ONE);
    if (width > max_width) max_width = width;

    /* Finally, the commands. */
    for (i = 0, curr = commands; i < num_names; i++) {
	if (!widget_names[i]) continue;
	XtSetArg(args[0], XtNwidth, &width);
	XtGetValues(*curr, args, ONE);
	if (width > max_width) max_width = width;
	curr++;
    }

    /*
     * Finally, set all of the single line widgets to the largest width.
     */
    XtSetArg(args[0], XtNwidth, max_width);
    XtSetValues(label, args, ONE);

    for (i = 0, curr = commands; i < num_names; i++) {
	if (!widget_names[i]) continue;
	XtSetArg(args[0], XtNwidth, max_width);
	XtSetValues(*curr, args, ONE);
	curr++;
    }

    if (command_widgets)
	*command_widgets = commands;
    else
	free((char *) commands);

    XtRealizeWidget(popup);
    XSetWMProtocols(XtDisplay(popup), XtWindow(popup), &wm_delete_window, 1);

    return popup;
}
