/* NetHack 3.6	winmisc.c	$NHDT-Date: 1554135506 2019/04/01 16:18:26 $  $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.44 $ */
/* 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/AsciiText.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xaw/List.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 = 0;
static Widget extended_command_form;
static Widget *extended_commands = 0;
static const char **command_list;
static short *command_indx;
static int extended_cmd_selected; /* index of the selected command; */
static int ps_selected;               /* index of selected role */
#define PS_RANDOM (-50)
#define PS_QUIT (-75)
/* 'r' for random won't work for role but will for race, gender, alignment */
static const char ps_randchars[] = "*@\n\rrR";
static const char ps_quitchars[] = "\033qQ";

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

boolean plsel_ask_name;

static const char plsel_dialog_translations[] = "#override\n\
     <Key>Escape: plsel_quit()\n\
     <Key>Return: plsel_play()";

static const char plsel_input_accelerators[] = "#override\n\
     <Key>Escape: plsel_quit()\n\
     <Key>Return: plsel_play()\n\
     <Key>Tab: \n";

static const char extended_command_translations[] = "#override\n\
     <Key>Left: scroll(4)\n\
     <Key>Right: scroll(6)\n\
     <Key>Up: scroll(8)\n\
     <Key>Down: scroll(2)\n\
     <Btn4Down>: scroll(8)\n\
     <Btn5Down>: scroll(2)\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 const char popup_entry_translations[] = "#override\n\
     <Btn4Down>: scroll(8)\n\
     <Btn5Down>: scroll(2)";

static void FDECL(ps_quit, (Widget, XtPointer, XtPointer));
static void FDECL(ps_random, (Widget, XtPointer, XtPointer));
static void FDECL(ps_select, (Widget, XtPointer, XtPointer));
static void FDECL(extend_select, (Widget, XtPointer, XtPointer));
static void FDECL(extend_dismiss, (Widget, XtPointer, XtPointer));
static void FDECL(extend_help, (Widget, XtPointer, XtPointer));
static void FDECL(popup_delete, (Widget, XEvent *, String *, Cardinal *));
static void NDECL(ec_dismiss);
static void FDECL(ec_scroll_to_view, (int));
static void NDECL(init_extended_commands_popup);
static Widget FDECL(make_menu, (const char *, const char *, const char *,
                                const char *, XtCallbackProc, const char *,
                                XtCallbackProc, int, const char **,
                                Widget **, XtCallbackProc, Widget *));

void NDECL(X11_player_selection_setupOthers);
void NDECL(X11_player_selection_randomize);

/* Bad Hack alert. Using integers instead of XtPointers */
XtPointer
i2xtp(i)
int i;
{
    return (XtPointer) (ptrdiff_t) i;
}

int
xtp2i(x)
XtPointer x;
{
    return (int) (ptrdiff_t) x;
}

/* Player Selection ------------------------------------------------------- */
/* ARGSUSED */
static void
ps_quit(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    nhUse(w);
    nhUse(client_data);
    nhUse(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;
{
    nhUse(w);
    nhUse(client_data);
    nhUse(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;
{
    nhUse(w);
    nhUse(call_data);

    ps_selected = (int) (ptrdiff_t) 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;

    nhUse(w);
    nhUse(params);
    nhUse(num_params);

    (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;

    nhUse(w);
    nhUse(params);
    nhUse(num_params);

    (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";

    nhUse(w);
    nhUse(params);
    nhUse(num_params);

    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";

    nhUse(w);
    nhUse(params);
    nhUse(num_params);

    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;
}

int plsel_n_races, plsel_n_roles;
Widget *plsel_race_radios = (Widget *) 0;
Widget *plsel_role_radios = (Widget *) 0;
Widget *plsel_gend_radios = (Widget *) 0;
Widget *plsel_align_radios = (Widget *) 0;

Widget plsel_name_input;

Widget plsel_btn_play;

void
plsel_dialog_acceptvalues()
{
    Arg args[2];
    String s;

    flags.initrace = xtp2i(XawToggleGetCurrent(plsel_race_radios[0])) - 1;
    flags.initrole = xtp2i(XawToggleGetCurrent(plsel_role_radios[0])) - 1;
    flags.initgend = xtp2i(XawToggleGetCurrent(plsel_gend_radios[0])) - 1;
    flags.initalign = xtp2i(XawToggleGetCurrent(plsel_align_radios[0])) - 1;

    XtSetArg(args[0], nhStr(XtNstring), &s);
    XtGetValues(plsel_name_input, args, ONE);

    (void) strncpy(plname, (char *) s, sizeof plname - 1);
    plname[sizeof plname - 1] = '\0';
    (void) mungspaces(plname);
    if (strlen(plname) < 1)
        (void) strcpy(plname, "Mumbles");
    iflags.renameinprogress = FALSE;
}

/* ARGSUSED */
void
plsel_quit(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    nhUse(w);
    nhUse(event);
    nhUse(params);
    nhUse(num_params);

    ps_selected = PS_QUIT;
    exit_x_event = TRUE; /* leave event loop */
}

/* ARGSUSED */
void
plsel_play(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    Arg args[2];
    Boolean state;

    nhUse(w);
    nhUse(event);
    nhUse(params);
    nhUse(num_params);

    XtSetArg(args[0], nhStr(XtNsensitive), &state);
    XtGetValues(plsel_btn_play, args, ONE);

    if (state) {
        plsel_dialog_acceptvalues();
        exit_x_event = TRUE; /* leave event loop */
    } else {
        X11_nhbell();
    }
}

/* ARGSUSED */
void
plsel_randomize(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    nhUse(w);
    nhUse(event);
    nhUse(params);
    nhUse(num_params);

    X11_player_selection_randomize();
}

/* enable or disable the Play button */
void
plsel_set_play_button(state)
boolean state;
{
    Arg args[2];

    XtSetArg(args[0], nhStr(XtNsensitive), !state);
    XtSetValues(plsel_btn_play, args, ONE);
}

void
plsel_set_sensitivities(setcurr)
boolean setcurr;
{
    Arg args[2];
    int j, valid;
    int c = 0;
    int ra = xtp2i(XawToggleGetCurrent(plsel_race_radios[0]))-1;
    int ro = xtp2i(XawToggleGetCurrent(plsel_role_radios[0]))-1;

    plsel_set_play_button(ra < 0 || ro < 0);

    if (ra < 0 || ro < 0)
        return;

    valid = -1;

    for (j = 0; roles[j].name.m; j++) {
        boolean v = validrace(j, ra);

        if (j == ro)
            c = j;
        XtSetArg(args[0], nhStr(XtNsensitive), v);
        XtSetValues(plsel_role_radios[j], args, ONE);
        if (valid < 0 && v)
            valid = j;
    }
    if (!validrace(ro, c))
        c = valid;

    if (setcurr)
        XawToggleSetCurrent(plsel_role_radios[0], i2xtp(c + 1));

    valid = -1;

    for (j = 0; races[j].noun; j++) {
        boolean v = validrace(ro, j);

        if (j == ra)
            c = j;
        XtSetArg(args[0], nhStr(XtNsensitive), v);
        XtSetValues(plsel_race_radios[j], args, ONE);
        if (valid < 0 && v)
            valid = j;
    }
    if (!validrace(ro, c))
        c = valid;

    if (setcurr)
        XawToggleSetCurrent(plsel_race_radios[0], i2xtp(c + 1));

    X11_player_selection_setupOthers();
}

void
X11_player_selection_randomize()
{
    int nrole = plsel_n_roles;
    int nrace = plsel_n_races;
    int ro, ra, a, g;
    boolean fully_specified_role, choose_race_first;
    boolean picksomething = (flags.initrole == ROLE_NONE
                             || flags.initrace == ROLE_NONE
                             || flags.initgend == ROLE_NONE
                             || flags.initalign == ROLE_NONE);

    if (flags.randomall && picksomething) {
        if (flags.initrole == ROLE_NONE)
            flags.initrole = ROLE_RANDOM;
        if (flags.initrace == ROLE_NONE)
            flags.initrace = ROLE_RANDOM;
        if (flags.initgend == ROLE_NONE)
            flags.initgend = ROLE_RANDOM;
        if (flags.initalign == ROLE_NONE)
            flags.initalign = ROLE_RANDOM;
    }

    rigid_role_checks();

    /* Randomize race and role, unless specified in config */
    ro = flags.initrole;
    if (ro == ROLE_NONE || ro == ROLE_RANDOM) {
        ro = rn2(nrole);
        if (flags.initrole != ROLE_RANDOM) {
            fully_specified_role = FALSE;
        }
    }
    ra = flags.initrace;
    if (ra == ROLE_NONE || ra == ROLE_RANDOM) {
        ra = rn2(nrace);
        if (flags.initrace != ROLE_RANDOM) {
            fully_specified_role = FALSE;
        }
    }

    /* make sure we have a valid combination, honoring
       the users request if possible. */
    choose_race_first = FALSE;
    if (flags.initrace >= 0 && flags.initrole < 0) {
        choose_race_first = TRUE;
    }

    while (!validrace(ro,ra)) {
        if (choose_race_first) {
            ro = rn2(nrole);
            if (flags.initrole != ROLE_RANDOM) {
                fully_specified_role = FALSE;
            }
        } else {
            ra = rn2(nrace);
            if (flags.initrace != ROLE_RANDOM) {
                fully_specified_role = FALSE;
            }
        }
    }

    g = flags.initgend;
    if (g == ROLE_NONE) {
        g = rn2(ROLE_GENDERS);
        fully_specified_role = FALSE;
    }
    while (!validgend(ro,ra,g)) {
        g = rn2(ROLE_GENDERS);
    }

    a = flags.initalign;
    if (a == ROLE_NONE) {
        a = rn2(ROLE_ALIGNS);
        fully_specified_role = FALSE;
    }
    while (!validalign(ro,ra,a)) {
        a = rn2(ROLE_ALIGNS);
    }

    XawToggleSetCurrent(plsel_gend_radios[0], i2xtp(g + 1));
    XawToggleSetCurrent(plsel_align_radios[0], i2xtp(a + 1));
    XawToggleSetCurrent(plsel_race_radios[0], i2xtp(ra + 1));
    XawToggleSetCurrent(plsel_role_radios[0], i2xtp(ro + 1));
    plsel_set_sensitivities(FALSE);
}

void
X11_player_selection_setupOthers()
{
    Arg args[2];
    int ra = xtp2i(XawToggleGetCurrent(plsel_race_radios[0])) - 1;
    int ro = xtp2i(XawToggleGetCurrent(plsel_role_radios[0])) - 1;
    int valid = -1, c = 0, j;
    int gchecked = xtp2i(XawToggleGetCurrent(plsel_gend_radios[0])) - 1;
    int achecked = xtp2i(XawToggleGetCurrent(plsel_align_radios[0])) - 1;

    if (ro < 0 || ra < 0)
        return;

    for (j = 0; j < ROLE_GENDERS; j++) {
        boolean v = validgend(ro, ra, j);

        if (j == gchecked)
            c = j;
        XtSetArg(args[0], nhStr(XtNsensitive), v);
        XtSetValues(plsel_gend_radios[j], args, ONE);
        if (valid < 0 && v)
            valid = j;
    }
    if (!validgend(ro, ra, c))
        c = valid;

    XawToggleSetCurrent(plsel_gend_radios[0], i2xtp(c + 1));

    valid = -1;

    for (j = 0; j < ROLE_ALIGNS; j++) {
        boolean v = validalign(ro, ra, j);

        if (j == achecked)
            c = j;
        XtSetArg(args[0], nhStr(XtNsensitive), v);
        XtSetValues(plsel_align_radios[j], args, ONE);
        if (valid < 0 && v)
            valid = j;
    }
    if (!validalign(ro, ra, c))
        c = valid;

    XawToggleSetCurrent(plsel_align_radios[0], i2xtp(c + 1));
}

static void
racetoggleCallback(w, client, call)
Widget w;
XtPointer client, call;
{
    Arg args[2];
    int j, valid;
    int c = 0;
    int ra = xtp2i(XawToggleGetCurrent(plsel_race_radios[0])) - 1;
    int ro = xtp2i(XawToggleGetCurrent(plsel_role_radios[0])) - 1;

    nhUse(w);
    nhUse(client);
    nhUse(call);

    plsel_set_play_button(ra < 0 || ro < 0);

    if (ra < 0 || ro < 0)
        return;

    valid = -1;

    for (j = 0; roles[j].name.m; j++) {
        boolean v = validrace(j, ra);

        if (j == ro)
            c = j;
        XtSetArg(args[0], nhStr(XtNsensitive), v);
        XtSetValues(plsel_role_radios[j], args, ONE);
        if (valid < 0 && v)
            valid = j;
    }
    if (!validrace(c, ra))
        c = valid;

    j = c + 1;
    XawToggleSetCurrent(plsel_role_radios[0], i2xtp(j));

    X11_player_selection_setupOthers();
}

static void
roletoggleCallback(w, client, call)
Widget w;
XtPointer client, call;
{
    Arg args[2];
    int j, valid;
    int c = 0;
    int ra = xtp2i(XawToggleGetCurrent(plsel_race_radios[0])) - 1;
    int ro = xtp2i(XawToggleGetCurrent(plsel_role_radios[0])) - 1;

    nhUse(w);
    nhUse(client);
    nhUse(call);

    plsel_set_play_button(ra < 0 || ro < 0);

    if (ra < 0 || ro < 0)
        return;

    valid = -1;

    for (j = 0; races[j].noun; j++) {
        boolean v = validrace(ro, j);

        if (j == ra)
            c = j;
        XtSetArg(args[0], nhStr(XtNsensitive), v);
        XtSetValues(plsel_race_radios[j], args, ONE);
        if (valid < 0 && v)
            valid = j;
    }
    if (!validrace(ro, c))
        c = valid;

    j = c + 1;
    XawToggleSetCurrent(plsel_race_radios[0], i2xtp(j));

    X11_player_selection_setupOthers();
}

static void
gendertoggleCallback(w, client, call)
Widget w;
XtPointer client, call;
{
    int i, r = xtp2i(XawToggleGetCurrent(plsel_gend_radios[0])) - 1;

    nhUse(w);
    nhUse(client);
    nhUse(call);

    plsel_set_play_button(r < 0);

    for (i = 0; roles[i].name.m; i++) {
        if (roles[i].name.f) {
            Arg args[2];

            XtSetArg(args[0], XtNlabel,
                     (r < 1) ? roles[i].name.m : roles[i].name.f);
            XtSetValues(plsel_role_radios[i], args, ONE);
        }
    }
}

static void
aligntoggleCallback(w, client, call)
Widget w;
XtPointer client, call;
{
    int r = xtp2i(XawToggleGetCurrent(plsel_align_radios[0])) - 1;

    nhUse(w);
    nhUse(client);
    nhUse(call);

    plsel_set_play_button(r < 0);
}

static void
plsel_random_btn_callback(w, client, call)
Widget w;
XtPointer client;
XtPointer call;
{
    nhUse(w);
    nhUse(client);
    nhUse(call);

    X11_player_selection_randomize();
}

static void
plsel_play_btn_callback(w, client, call)
Widget w;
XtPointer client;
XtPointer call;
{
    nhUse(w);
    nhUse(client);
    nhUse(call);

    plsel_dialog_acceptvalues();
    exit_x_event = TRUE; /* leave event loop */
}

static void
plsel_quit_btn_callback(w, client, call)
Widget w;
XtPointer client;
XtPointer call;
{
    nhUse(w);
    nhUse(client);
    nhUse(call);

    ps_selected = PS_QUIT;
    exit_x_event = TRUE; /* leave event loop */
}

Widget
X11_create_player_selection_name(form)
Widget form;
{
    Widget namelabel, name_vp, name_form;
    Arg args[10];
    Cardinal num_args;

    /* name viewport */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNtop), XtChainTop); num_args++;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainRight); num_args++;
    XtSetArg(args[num_args], XtNforceBars, False); num_args++;
    XtSetArg(args[num_args], XtNallowVert, False); num_args++;
    XtSetArg(args[num_args], XtNallowHoriz, False); num_args++;
    name_vp = XtCreateManagedWidget("name_vp", viewportWidgetClass, form,
                                    args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNtop), XtChainTop); num_args++;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainRight); num_args++;
    name_form = XtCreateManagedWidget("name_form", formWidgetClass, name_vp,
                                      args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), "Name"); num_args++;
    XtSetArg(args[num_args], nhStr(XtNjustify), XtJustifyLeft); num_args++;

    namelabel = XtCreateManagedWidget("name_label",
                                      labelWidgetClass, name_form,
                                      args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), namelabel); num_args++;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainRight); num_args++;
    XtSetArg(args[num_args], nhStr(XtNresizable), True); num_args++;
    XtSetArg(args[num_args], nhStr(XtNeditType),
             !plsel_ask_name ? XawtextRead : XawtextEdit); num_args++;
    XtSetArg(args[num_args], nhStr(XtNresize), XawtextResizeWidth); num_args++;
    XtSetArg(args[num_args], nhStr(XtNstring), plname); num_args++;
    XtSetArg(args[num_args], XtNinsertPosition, strlen(plname)); num_args++;
    XtSetArg(args[num_args], nhStr(XtNaccelerators),
             XtParseAcceleratorTable(plsel_input_accelerators)); num_args++;
    plsel_name_input = XtCreateManagedWidget("name_input",
                                             asciiTextWidgetClass, name_form,
                                             args, num_args);

    XtInstallAccelerators(plsel_name_input, plsel_name_input);
    if (plsel_ask_name) {
        XtSetKeyboardFocus(form, plsel_name_input);
    } else {
        XtSetArg(args[0], nhStr(XtNdisplayCaret), False);
        XtSetValues(plsel_name_input, args, ONE);
    }

    return name_vp;
}

void
X11_player_selection_dialog()
{
    Widget popup, popup_vp;
    Widget form;
    Widget name_vp;
    Widget racelabel, race_form, race_vp, race_form2;
    Widget rolelabel, role_form, role_vp, role_form2;
    Widget gendlabel, gend_form,
           gend_radio_m, gend_radio_f, gend_vp, gend_form2;
    Widget alignlabel, align_form,
           align_radio_l, align_radio_n, align_radio_c, align_vp, align_form2;
    Widget btn_vp, btn_form, random_btn, play_btn, quit_btn;
    Widget tmpwidget;
    Arg args[10];
    Cardinal num_args;
    int i;
    int winwid = 400;
    int cwid = (winwid / 3) - 14;

    num_args = 0;
    XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
    XtSetArg(args[num_args], XtNtitle, "Player Selection"); num_args++;
    popup = XtCreatePopupShell("player_selection_dialog",
                               transientShellWidgetClass,
                               toplevel, args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], XtNtranslations,
             XtParseTranslationTable(plsel_dialog_translations)); num_args++;
    popup_vp = XtCreateManagedWidget("plsel_vp", viewportWidgetClass,
                                     popup, args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], XtNtranslations,
             XtParseTranslationTable(plsel_dialog_translations)); num_args++;
    form = XtCreateManagedWidget("plsel_form", formWidgetClass, popup_vp,
                                 args, num_args);

    name_vp = X11_create_player_selection_name(form);

    num_args = 0;
    XtSetArg(args[num_args], XtNwidth, winwid); num_args++;
    XtSetValues(name_vp, args, num_args);

    /*
     * Layout; role is centered rather than first:
     *
     *   +------------------------------------+
     *   | name                               |
     *   +------------------------------------+
     *   +--------+   +------------+   +---------+
     *   | human  |   |archeologist|   |  male   |
     *   |  elf   |   | barbarian  |   | female  |
     *   | dwarf  |   |  caveman   |   +---------+
     *   | gnome  |   |   healer   |   +---------+
     *   |  orc   |   |   knight   |   | lawful  |
     *   +--------+   |    monk    |   | neutral |
     *                |   priest   |   | chaotic |
     *                |   rogue    |   +---------+
     *                |   ranger   |   +--------+
     *                |  samurai   |   + Random +
     *                |  tourist   |   +  Play  +
     *                |  valkyrie  |   +  Quit  +
     *                |   wizard   |   +--------+
     *                +------------+
     *
     * TODO:
     *  make name box same width as race+gap+role+gap+gender/alignment
     *  (resize it after the other boxes have been placed);
     *  make Random/Play/Quit buttons same width as gender/alignment and
     *  align bottom of them with bottom of role (they already specify
     *  the same width for the label text but different decorations--
     *  room for radio button box--of the other widgets results in the
     *  total width being different);
     *  add 'random' to each of the four boxes and Choose to the Random/
     *  Play/Quit buttons; if none of the four 'random's are currently
     *  selected, gray-out Choose; conversely, when Choose or Play is
     *  clicked on, make the random assignments for any/all of the four
     *  boxes which have 'random' selected.
     *  Maybe:  move gender box underneath race, bottom aligned with role
     *  and move alignment up to where gender currently is.  If that's
     *  done, move role column first and race+gender to middle.
     */

    /********************************************/

    /* Race */

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), name_vp); num_args++;
    race_form = XtCreateManagedWidget("race_form", formWidgetClass, form,
                                      args, num_args);

    /* race label */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNjustify), XtJustifyLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), "Race"); num_args++;
    racelabel = XtCreateManagedWidget("race_label",
                                      labelWidgetClass, race_form,
                                      args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNtopMargin), 0); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromVert), racelabel); num_args++;
    XtSetArg(args[num_args], XtNforceBars, False); num_args++;
    XtSetArg(args[num_args], XtNallowVert, True); num_args++;
    XtSetArg(args[num_args], XtNallowHoriz, False); num_args++;
    race_vp = XtCreateManagedWidget("race_vp", viewportWidgetClass, race_form,
                                    args, num_args);

    num_args = 0;
    race_form2 = XtCreateManagedWidget("race_form2", formWidgetClass, race_vp,
                                      args, num_args);

    for (i = 0; races[i].noun; i++)
        continue;
    plsel_n_races = i;

    plsel_race_radios = (Widget *) alloc(sizeof (Widget) * plsel_n_races);

    /* race radio buttons */
    for (i = 0; races[i].noun; i++) {
        Widget racewidget;

        num_args = 0;
        if (i > 0)
            XtSetArg(args[num_args], nhStr(XtNfromVert),
                     tmpwidget); num_args++;
        XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
        if (i > 0)
            XtSetArg(args[num_args], nhStr(XtNradioGroup),
                     plsel_race_radios[0]); num_args++;
        XtSetArg(args[num_args], nhStr(XtNradioData), (i + 1)); num_args++;

        racewidget = XtCreateManagedWidget(races[i].noun,
                                           toggleWidgetClass,
                                           race_form2,
                                           args, num_args);
        XtAddCallback(racewidget, XtNcallback, racetoggleCallback, i2xtp(i));
        tmpwidget = racewidget;
        plsel_race_radios[i] = racewidget;
    }

    XawToggleUnsetCurrent(plsel_race_radios[0]);

    /********************************************/

    /* Role */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), name_vp); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromHoriz), race_form); num_args++;
    role_form = XtCreateManagedWidget("role_form", formWidgetClass, form,
                                      args, num_args);

    /* role label */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNjustify), XtJustifyLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), "Role"); num_args++;
    rolelabel = XtCreateManagedWidget("role_label", labelWidgetClass,
                                      role_form, args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNtopMargin), 0); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromVert), rolelabel); num_args++;
    XtSetArg(args[num_args], XtNforceBars, False); num_args++;
    XtSetArg(args[num_args], XtNallowVert, True); num_args++;
    XtSetArg(args[num_args], XtNallowHoriz, False); num_args++;
    role_vp = XtCreateManagedWidget("role_vp", viewportWidgetClass, role_form,
                                    args, num_args);

    num_args = 0;
    role_form2 = XtCreateManagedWidget("role_form2", formWidgetClass, role_vp,
                                      args, num_args);

    for (i = 0; roles[i].name.m; i++)
        continue;
    plsel_n_roles = i;

    plsel_role_radios = (Widget *) alloc(sizeof (Widget) * plsel_n_roles);

    /* role radio buttons */
    for (i = 0; roles[i].name.m; i++) {
        Widget rolewidget;

        num_args = 0;
        if (i > 0)
            XtSetArg(args[num_args], nhStr(XtNfromVert),
                     tmpwidget); num_args++;
        XtSetArg(args[num_args], nhStr(XtNwidth), cwid); num_args++;
        if (i > 0)
            XtSetArg(args[num_args], nhStr(XtNradioGroup),
                     plsel_role_radios[0]); num_args++;
        XtSetArg(args[num_args], nhStr(XtNradioData), (i + 1)); num_args++;

        rolewidget = XtCreateManagedWidget(roles[i].name.m, toggleWidgetClass,
                                           role_form2, args, num_args);
        XtAddCallback(rolewidget, XtNcallback, roletoggleCallback, i2xtp(i));
        tmpwidget = rolewidget;
        plsel_role_radios[i] = rolewidget;
    }
    XawToggleUnsetCurrent(plsel_role_radios[0]);

    /********************************************/

    /* Gender*/

    plsel_gend_radios = (Widget *) alloc(sizeof (Widget) * ROLE_GENDERS);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), name_vp); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromHoriz), role_form); num_args++;
    gend_form = XtCreateManagedWidget("gender_form", formWidgetClass, form,
                                      args, num_args);

    /* gender label */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNjustify), XtJustifyLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), "Gender"); num_args++;
    gendlabel = XtCreateManagedWidget("gender_label", labelWidgetClass,
                                      gend_form, args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNtopMargin), 0); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromVert), gendlabel); num_args++;
    XtSetArg(args[num_args], XtNforceBars, False); num_args++;
    XtSetArg(args[num_args], XtNallowVert, True); num_args++;
    XtSetArg(args[num_args], XtNallowHoriz, False); num_args++;
    gend_vp = XtCreateManagedWidget("gender_vp", viewportWidgetClass,
                                    gend_form, args, num_args);

    num_args = 0;
    gend_form2 = XtCreateManagedWidget("gender_form2", formWidgetClass,
                                       gend_vp, args, num_args);

    /* gender radio buttons */
    num_args = 0;
    XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
    XtSetArg(args[num_args], nhStr(XtNradioData), 1); num_args++;
    plsel_gend_radios[0] = gend_radio_m
        =  XtCreateManagedWidget("Male", toggleWidgetClass,
                                 gend_form2, args, num_args);
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), gend_radio_m); num_args++;
    XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
    XtSetArg(args[num_args], nhStr(XtNradioGroup),
             plsel_gend_radios[0]); num_args++;
    XtSetArg(args[num_args], nhStr(XtNradioData), 2); num_args++;
    plsel_gend_radios[1] = gend_radio_f
        =  XtCreateManagedWidget("Female", toggleWidgetClass,
                                 gend_form2, args, num_args);

    XawToggleUnsetCurrent(plsel_gend_radios[0]);

    XtAddCallback(gend_radio_m, XtNcallback,
                  gendertoggleCallback, (XtPointer) (1));
    XtAddCallback(gend_radio_f, XtNcallback,
                  gendertoggleCallback, (XtPointer) (2));

    /********************************************/

    /* Alignment */

    plsel_align_radios = (Widget *) alloc(sizeof (Widget) * ROLE_ALIGNS);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), gend_form); num_args++;
    XtSetArg(args[num_args], nhStr(XtNvertDistance), 30); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromHoriz), role_form); num_args++;
    align_form = XtCreateManagedWidget("align_form", formWidgetClass, form,
                                       args, num_args);

    /* align label */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNjustify), XtJustifyLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), "Alignment"); num_args++;
    alignlabel = XtCreateManagedWidget("align_label", labelWidgetClass,
                                       align_form, args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNtopMargin), 0); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromVert), alignlabel); num_args++;
    XtSetArg(args[num_args], XtNforceBars, False); num_args++;
    XtSetArg(args[num_args], XtNallowVert, True); num_args++;
    XtSetArg(args[num_args], XtNallowHoriz, False); num_args++;
    align_vp = XtCreateManagedWidget("align_vp", viewportWidgetClass,
                                     align_form, args, num_args);

    num_args = 0;
    align_form2 = XtCreateManagedWidget("align_form2", formWidgetClass,
                                        align_vp, args, num_args);

    num_args = 0;
    XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
    XtSetArg(args[num_args], nhStr(XtNradioData), 1); num_args++;
    plsel_align_radios[0] = align_radio_l
        =  XtCreateManagedWidget("Lawful", toggleWidgetClass,
                                 align_form2, args, num_args);
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), align_radio_l); num_args++;
    XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
    XtSetArg(args[num_args], nhStr(XtNradioGroup),
             plsel_align_radios[0]); num_args++;
    XtSetArg(args[num_args], nhStr(XtNradioData), 2); num_args++;
    plsel_align_radios[1] = align_radio_n
        = XtCreateManagedWidget("Neutral", toggleWidgetClass,
                                align_form2, args, num_args);
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), align_radio_n); num_args++;
    XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
    XtSetArg(args[num_args], nhStr(XtNradioGroup),
             plsel_align_radios[0]); num_args++;
    XtSetArg(args[num_args], nhStr(XtNradioData), 3); num_args++;
    plsel_align_radios[2] = align_radio_c
        =  XtCreateManagedWidget("Chaotic", toggleWidgetClass,
                                 align_form2, args, num_args);

    XawToggleUnsetCurrent(plsel_align_radios[0]);

    XtAddCallback(align_radio_l, XtNcallback,
                  aligntoggleCallback, (XtPointer) (1));
    XtAddCallback(align_radio_n, XtNcallback,
                  aligntoggleCallback, (XtPointer) (2));
    XtAddCallback(align_radio_c, XtNcallback,
                  aligntoggleCallback, (XtPointer) (3));

    /********************************************/

    /* Buttons! */

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), align_form); num_args++;
    XtSetArg(args[num_args], nhStr(XtNvertDistance), 30); num_args++;
    XtSetArg(args[num_args], nhStr(XtNrightMargin), 0); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromHoriz), role_form); num_args++;
    XtSetArg(args[num_args], XtNforceBars, False); num_args++;
    XtSetArg(args[num_args], XtNallowVert, False); num_args++;
    XtSetArg(args[num_args], XtNallowHoriz, False); num_args++;
    btn_vp = XtCreateManagedWidget("btn_vp", viewportWidgetClass, form,
                                   args, num_args);

    num_args = 0;
    btn_form = XtCreateManagedWidget("btn_form", formWidgetClass, btn_vp,
                                     args, num_args);

    /* "Random" button */

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNtop), XtChainTop); num_args++;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainRight); num_args++;
    XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), "Random"); num_args++;
    random_btn = XtCreateManagedWidget("random", commandWidgetClass, btn_form,
                                       args, num_args);
    XtAddCallback(random_btn, XtNcallback, plsel_random_btn_callback, form);

    /* "Play" button */

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), random_btn); num_args++;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainRight); num_args++;
    XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), "Play"); num_args++;
    plsel_btn_play = play_btn
        = XtCreateManagedWidget("play", commandWidgetClass, btn_form,
                                args, num_args);
    XtAddCallback(play_btn, XtNcallback, plsel_play_btn_callback, form);

    /* "Quit" button */

    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), play_btn); num_args++;
    XtSetArg(args[num_args], nhStr(XtNbottom), XtChainBottom); num_args++;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainRight); num_args++;
    XtSetArg(args[num_args], XtNwidth, cwid); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), "Quit"); num_args++;
    quit_btn = XtCreateManagedWidget("quit", commandWidgetClass, btn_form,
                                     args, num_args);
    XtAddCallback(quit_btn, XtNcallback, plsel_quit_btn_callback, form);

    /********************************************/

    XtRealizeWidget(popup);
    X11_player_selection_randomize();

    if (flags.randomall) {
        plsel_dialog_acceptvalues();
    } else {
        ps_selected = -1;
        nh_XtPopup(popup, (int) XtGrabExclusive, form);
        /* The callback will enable the event loop exit. */
        (void) x_event(EXIT_ON_EXIT);
    }

    nh_XtPopdown(popup);
    XtDestroyWidget(popup);

    if (plsel_race_radios)
        free(plsel_race_radios);

    if (plsel_role_radios)
        free(plsel_role_radios);

    if (plsel_gend_radios)
        free(plsel_gend_radios);

    if (plsel_align_radios)
        free(plsel_align_radios);

    if (ps_selected == PS_QUIT || program_state.done_hup) {
        clearlocks();
        X11_exit_nhwindows((char *) 0);
        nh_terminate(0);
    }
}

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

    /* avoid unnecessary prompts further down */
    rigid_role_checks();

    (void) root_plselection_prompt(plbuf, QBUFSZ - 1, flags.initrole,
                                   flags.initrace, flags.initgend,
                                   flags.initalign);

    while (flags.initrole < 0) {
        if (flags.initrole == ROLE_RANDOM || flags.randomall) {
            flags.initrole = pick_role(flags.initrace, flags.initgend,
                                       flags.initalign, PICK_RANDOM);
            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");
        }
        Sprintf(qbuf, "Choose your %s Role", s_suffix(plbuf));
        popup =
            make_menu("player_selection", qbuf, 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 || program_state.done_hup) {
            clearlocks();
            X11_exit_nhwindows((char *) 0);
            nh_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", ps_selected);
        } else {
            flags.initrole = ps_selected;
        }
    }

    (void) root_plselection_prompt(plbuf, QBUFSZ - 1, flags.initrole,
                                   flags.initrace, flags.initgend,
                                   flags.initalign);

    while (!validrace(flags.initrole, flags.initrace)) {
        if (flags.initrace == ROLE_RANDOM || flags.randomall) {
            flags.initrace = pick_race(flags.initrole, flags.initgend,
                                       flags.initalign, PICK_RANDOM);
            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 {
            Sprintf(qbuf, "Pick your %s race", s_suffix(plbuf));
            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 || program_state.done_hup) {
                clearlocks();
                X11_exit_nhwindows((char *) 0);
                nh_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",
                      ps_selected);
            } else {
                flags.initrace = ps_selected;
            }
        } /* more than one race choice available */
    }

    (void) root_plselection_prompt(plbuf, QBUFSZ - 1, flags.initrole,
                                   flags.initrace, flags.initgend,
                                   flags.initalign);

    while (!validgend(flags.initrole, flags.initrace, flags.initgend)) {
        if (flags.initgend == ROLE_RANDOM || flags.randomall) {
            flags.initgend = pick_gend(flags.initrole, flags.initrace,
                                       flags.initalign, PICK_RANDOM);
            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 {
            Sprintf(qbuf, "Your %s gender?", s_suffix(plbuf));
            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 || program_state.done_hup) {
                clearlocks();
                X11_exit_nhwindows((char *) 0);
                nh_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",
                      ps_selected);
            } else {
                flags.initgend = ps_selected;
            }
        } /* more than one gender choice available */
    }

    (void) root_plselection_prompt(plbuf, QBUFSZ - 1, flags.initrole,
                                   flags.initrace, flags.initgend,
                                   flags.initalign);

    while (!validalign(flags.initrole, flags.initrace, flags.initalign)) {
        if (flags.initalign == ROLE_RANDOM || flags.randomall) {
            flags.initalign = pick_align(flags.initrole, flags.initrace,
                                         flags.initgend, PICK_RANDOM);
            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 {
            Sprintf(qbuf, "Your %s alignment?", s_suffix(plbuf));
            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 || program_state.done_hup) {
                clearlocks();
                X11_exit_nhwindows((char *) 0);
                nh_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",
                      ps_selected);
            } else {
                flags.initalign = ps_selected;
            }
        } /* more than one alignment choice available */
    }
}

void
X11_player_selection()
{
    if (iflags.wc_player_selection == VIA_DIALOG) {
        if (!*plname) {
#ifdef UNIX
            char *defplname = get_login_name();
#else
            char *defplname = (char *)0;
#endif
            (void) strncpy(plname, defplname ? defplname : "Mumbles",
                           sizeof plname - 1);
            plname[sizeof plname - 1] = '\0';
            iflags.renameinprogress = TRUE;
        }
        X11_player_selection_dialog();
    } else { /* iflags.wc_player_selection == VIA_PROMPTS */
        X11_player_selection_prompts();
    }
}

/* called by core to have the player pick an extended command */
int
X11_get_ext_cmd()
{
    if (iflags.extmenu != ec_full_list) {
        /* player has toggled the 'extmenu' option, toss the old widgets */
        if (extended_commands)
            release_extended_cmds(); /* will set extended_commands to Null */
        ec_full_list = iflags.extmenu;
    }
    if (!extended_commands)
        init_extended_commands_popup();

    extended_cmd_selected = -1; /* reset selected value */
    ec_scroll_to_view(-1); /* force scroll bar to top */

    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);

    if (extended_cmd_selected < 0)
        return -1;
    return command_indx[extended_cmd_selected];
}

void
release_extended_cmds()
{
    if (extended_commands) {
        XtDestroyWidget(extended_command_popup), extended_command_popup = 0;
        free((genericptr_t) extended_commands), extended_commands = 0;
        free((genericptr_t) command_list), command_list = (const char **) 0;
        free((genericptr_t) command_indx), command_indx = (short *) 0;
    }
}

/* 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) (ptrdiff_t) client_data;

    nhUse(w);
    nhUse(call_data);

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

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

    nh_XtPopdown(extended_command_popup);
    /* reset colors while popped down */
    swap_fg_bg(extended_commands[extended_cmd_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;
{
    nhUse(w);
    nhUse(client_data);
    nhUse(call_data);

    ec_dismiss();
}

/* ARGSUSED */
static void
extend_help(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    nhUse(w);
    nhUse(client_data);
    nhUse(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;
{
    if (w == extended_command_popup) {
        ec_dismiss();
    } else {
        popup_delete(w, event, params, num_params);
    }
}

/* ARGSUSED */
static void
popup_delete(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    nhUse(event);
    nhUse(params);
    nhUse(num_params);

    ps_selected = PS_QUIT;
    nh_XtPopdown(w);
    exit_x_event = TRUE; /* leave event loop */
}

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

/* scroll the extended command menu if necessary
   so that choices extended_cmd_selected through ec_indx will be visible */
static void
ec_scroll_to_view(ec_indx)
int ec_indx; /* might be greater than extended_cmd_selected */
{
    Widget viewport, scrollbar, tmpw;
    Arg args[5];
    Cardinal num_args;
    Position lo_y, hi_y; /* ext cmd label y */
    float s_shown, s_top; /* scrollbar pos */
    float s_min, s_max;
    Dimension h, hh, wh, vh; /* widget and viewport heights */
    Dimension border_width;
    int distance = 0;
    boolean force_top = (ec_indx < 0);

    /*
     * If the extended command menu needs to be scrolled in order to move
     * either the highlighted entry (extended_cmd_selected) or the target
     * entry (ec_indx) into view, we want to make both end up visible.
     * [Highligthed one is the first matching entry when the user types
     * something, such as "adjust" after typing 'a', and will be chosen
     * by pressing <return>.  Target entry is one past the last matching
     * entry (or last matching entry itself if at end of command list),
     * showing the user the other potential matches so far.]
     *
     * If that's not possible (maybe menu has been resized so that it's
     * too small), the highlighted entry takes precedence and the target
     * will be decremented until close enough to fit.
     */

    if (force_top)
        ec_indx = 0;

    /* get viewport and scrollbar widgets */
    tmpw = extended_commands[ec_indx];
    viewport = XtParent(tmpw);
    do {
        scrollbar = XtNameToWidget(tmpw, "*vertical");
        if (scrollbar)
            break;
        tmpw = XtParent(tmpw);
    } while (tmpw);

    if (scrollbar && viewport) {
        /* get selected ext command label y position and height */
        num_args = 0;
        XtSetArg(args[num_args], XtNy, &hi_y); num_args++;
        XtSetArg(args[num_args], XtNheight, &h); num_args++;
        XtSetArg(args[num_args], nhStr(XtNborderWidth), &border_width);
                                                                   num_args++;
        XtSetArg(args[num_args], nhStr(XtNdefaultDistance), &distance);
                                                                   num_args++;
        XtGetValues(extended_commands[ec_indx], args, num_args);
        if (distance < 1 || distance > 32766) /* defaultDistance is weird */
            distance = 4;
        /* vertical distance between top of one command widget and the next */
        hh = h + distance + 2 * border_width;
        /* location of the highlighted entry, if any */
        if (extended_cmd_selected >= 0) {
            XtSetArg(args[0], XtNy, &lo_y);
            XtGetValues(extended_commands[extended_cmd_selected], args, ONE);
        } else
            lo_y = hi_y;

        /* get menu widget and viewport heights */
        XtSetArg(args[0], XtNheight, &wh);
        XtGetValues(tmpw, args, ONE);
        XtSetArg(args[0], XtNheight, &vh);
        XtGetValues(viewport, args, ONE);

        /* widget might be too small if it has been resized or
           there are a very large number of ambiguous choices */
        if (hi_y - lo_y > wh) {
            ec_indx = extended_cmd_selected;
            if (wh > hh)
                ec_indx += (wh / hh);
            XtSetArg(args[0], XtNy, &hi_y);
            XtGetValues(extended_commands[ec_indx], args, num_args);
        }

        /* get scrollbar "height" and "top" position; floats between 0-1 */
        num_args = 0;
        XtSetArg(args[num_args], XtNshown, &s_shown); num_args++;
        XtSetArg(args[num_args], nhStr(XtNtopOfThumb), &s_top); num_args++;
        XtGetValues(scrollbar, args, num_args);

        s_min = s_top * vh;
        s_max = (s_top + s_shown) * vh;

        /* scroll if outside the view */
        if (force_top) {
            s_min = 0.0;
            XtCallCallbacks(scrollbar, XtNjumpProc, &s_min);
        } else if ((int) lo_y <= (int) s_min) {
            s_min = (float) (lo_y / (float) vh);
            XtCallCallbacks(scrollbar, XtNjumpProc, &s_min);
        } else if ((int) (hi_y + h) >= (int) s_max) {
            s_min = (float) ((hi_y + h) / (float) vh) - s_shown;
            XtCallCallbacks(scrollbar, XtNjumpProc, &s_min);
        }
    }
}

/* decide whether extcmdlist[idx] should be part of extended commands menu */
static boolean
ignore_extcmd(idx)
int idx;
{
    /* #shell or #suspect might not be available;
       'extmenu' option controls whether we show full list
       or just the traditional extended commands */
    if ((extcmdlist[idx].flags & CMD_NOT_AVAILABLE) != 0
        || ((extcmdlist[idx].flags & AUTOCOMPLETE) == 0 && !ec_full_list)
        || strlen(extcmdlist[idx].ef_txt) < 2) /* ignore "#" and "?" */
        return TRUE;

    return FALSE;
}

/* ARGSUSED */
void
ec_key(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    char ch;
    int i, pass;
    float shown, top;
    Arg arg[2];
    Widget hbar, vbar;
    XKeyEvent *xkey = (XKeyEvent *) event;

    nhUse(params);
    nhUse(num_params);

    ch = key_event_to_char(xkey);

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

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

        exit_x_event = TRUE; /* leave event loop */
        ec_active = FALSE;
        return;
    } else if (ch == MENU_FIRST_PAGE || ch == MENU_LAST_PAGE) {
        hbar = vbar = (Widget) 0;
        find_scrollbars(w, &hbar, &vbar);
        if (vbar) {
            top = (ch == MENU_FIRST_PAGE) ? 0.0 : 1.0;
            XtCallCallbacks(vbar, XtNjumpProc, &top);
        }
        return;
    } else if (ch == MENU_NEXT_PAGE || ch == MENU_PREVIOUS_PAGE) {
        hbar = vbar = (Widget) 0;
        find_scrollbars(w, &hbar, &vbar);
        if (vbar) {
            XtSetArg(arg[0], nhStr(XtNshown), &shown);
            XtSetArg(arg[1], nhStr(XtNtopOfThumb), &top);
            XtGetValues(vbar, arg, TWO);
            top += ((ch == MENU_NEXT_PAGE) ? shown : -shown);
            XtCallCallbacks(vbar, XtNjumpProc, &top);
        }
        return;
    }

    /*
     * If too much time has elapsed, treat current key as starting a new
     * choice, otherwise it is a continuation of the choice in progress.
     * Extra letters might be needed to disambiguate between choices
     * ("ride" vs "rub", for instance), or player may just be typing in
     * the whole word.
     */
    if (ec_active && (xkey->time - ec_time) > 2500) /* 2.5 seconds */
        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 (pass = 0; pass < 2; pass++) {
        if (pass == 1) {
            /* first pass finished, but no matching command was found */
            /* start a new one with the last char entered */
            if (extended_cmd_selected >= 0)
                swap_fg_bg(extended_commands[extended_cmd_selected]);
            extended_cmd_selected = -1; /* dismiss */
            ec_chars[0] = ec_chars[ec_nchars - 1];
            ec_nchars = 1;
        }
        for (i = 0; command_list[i]; ++i) {
            if (!strncmp(ec_chars, command_list[i], ec_nchars)) {
                if (extended_cmd_selected != i) {
                    /* I should use set() and unset() actions, but how do
                       I send the an action to the widget? */
                    if (extended_cmd_selected >= 0)
                        swap_fg_bg(extended_commands[extended_cmd_selected]);
                    extended_cmd_selected = i;
                    swap_fg_bg(extended_commands[extended_cmd_selected]);
                }
                /* advance to one past last matching entry, so that all
                   ambiguous choices, plus one to show thare aren't any
                   more such, will scroll into view */
                do {
                    if (!command_list[i + 1])
                        break; /* end of list */
                    ++i;
                } while (!strncmp(ec_chars, command_list[i], ec_nchars));

                ec_scroll_to_view(i);
                return;
            }
        }
    }
}

/*
 * 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, j, num_commands, ignore_cmds = 0;

    /* count commands */
    for (num_commands = 0; extcmdlist[num_commands].ef_txt; num_commands++)
        if (ignore_extcmd(num_commands))
            ++ignore_cmds;

    j = num_commands - ignore_cmds;
    command_list = (const char **) alloc((unsigned) (j * sizeof (char *) + 1));
    command_indx = (short *) alloc((unsigned) (j * sizeof (short) + 1));

    for (i = j = 0; i < num_commands; i++) {
        if (ignore_extcmd(i))
            continue;
        command_indx[j] = (short) i;
        command_list[j++] = extcmdlist[i].ef_txt;
    }
    command_list[j] = (char *) 0;
    command_indx[j] = -1;
    num_commands = j;

    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);
}

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

/*
 * 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, popform, form, label, above, left, right, view;
    Widget *commands, *curr;
    int i;
    Arg args[12];
    Cardinal num_args;
    Dimension width, other_width, max_width, border_width,
              height, cumulative_height, screen_height;
    int distance, skip;
    char btnname[BUFSZ];

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

    num_args = 0;
    XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
    XtSetArg(args[num_args], XtNborderWidth, 0); 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++;
    XtSetArg(args[num_args], XtNborderWidth, 0); num_args++;
    XtSetArg(args[num_args], nhStr(XtNdefaultDistance), 0); num_args++;
    popform = XtCreateManagedWidget("topmenuform", formWidgetClass, popup,
                                    args, num_args);


    num_args = 0;
    XtSetArg(args[num_args], XtNforceBars, False); num_args++;
    XtSetArg(args[num_args], XtNallowVert, True); num_args++;
    /*XtSetArg(args[num_args], XtNborderWidth, 0); num_args++;*/
    XtSetArg(args[num_args], nhStr(XtNuseBottom), True); num_args++;
    XtSetArg(args[num_args], nhStr(XtNuseRight), True); num_args++;
    XtSetArg(args[num_args], nhStr(XtNtop), XtChainTop); num_args++;
    XtSetArg(args[num_args], nhStr(XtNbottom), XtChainBottom); num_args++;
    XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++;
    XtSetArg(args[num_args], nhStr(XtNright), XtChainRight); num_args++;
    XtSetArg(args[num_args], XtNtranslations,
             XtParseTranslationTable(popup_translations)); num_args++;
    view = XtCreateManagedWidget("menuformview", viewportWidgetClass, popform,
                                 args, num_args);

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

    /*
     * Get the default distance between objects in the viewport widget.
     * (Something is fishy here:  'distance' ends up being 0 but there
     * is a non-zero gap between the borders of the internal widgets.
     * It matches exactly the default value of 4 for defaultDistance.)
     */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNdefaultDistance), &distance); num_args++;
    XtSetArg(args[num_args], nhStr(XtNborderWidth), &border_width); num_args++;
    XtGetValues(view, args, num_args);
    if (distance < 1 || distance > 32766)
        distance = 4;

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

    cumulative_height = 0;
    XtSetArg(args[0], XtNheight, &height);
    XtGetValues(label, args, ONE);
    cumulative_height += distance + height; /* no border for label */

    /*
     * Create the left button.
     */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromVert), label); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), left_name); num_args++;
#if 0
    XtSetArg(args[num_args], nhStr(XtNshapeStyle),
                              XmuShapeRoundedRectangle); num_args++;
#endif
    Sprintf(btnname, "btn_%s", left_name);
    left = XtCreateManagedWidget(btnname, commandWidgetClass, form, args,
                                 num_args);
    XtAddCallback(left, XtNcallback, left_callback, (XtPointer) 0);
    skip = (distance < 4) ? 8 : 2 * distance;

    num_args = 0;
    XtSetArg(args[0], XtNheight, &height);
    XtGetValues(left, args, ONE);
    cumulative_height += distance + height + 2 * border_width;

    /*
     * Create right button.
     */
    num_args = 0;
    XtSetArg(args[num_args], nhStr(XtNfromHoriz), left); num_args++;
    XtSetArg(args[num_args], nhStr(XtNhorizDistance), skip); num_args++;
    XtSetArg(args[num_args], nhStr(XtNfromVert), label); num_args++;
    XtSetArg(args[num_args], nhStr(XtNlabel), right_name); num_args++;
#if 0
    XtSetArg(args[num_args], nhStr(XtNshapeStyle),
                              XmuShapeRoundedRectangle); num_args++;
#endif
    Sprintf(btnname, "btn_%s", right_name);
    right = XtCreateManagedWidget(btnname, 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], XtNtranslations,
                 XtParseTranslationTable(popup_entry_translations)); num_args++;
        XtSetArg(args[num_args], nhStr(XtNfromVert), above); num_args++;
        if (above == left) {
            /* if first, we are farther apart */
            XtSetArg(args[num_args], nhStr(XtNvertDistance), skip); num_args++;
            cumulative_height += skip;
        } else
            cumulative_height += distance;
        cumulative_height += height + 2 * border_width;

        *curr = XtCreateManagedWidget(widget_names[i], commandWidgetClass,
                                      form, args, num_args);
        XtAddCallback(*curr, XtNcallback, name_callback,
                      (XtPointer) (ptrdiff_t) i);
        above = *curr++;
    }
    cumulative_height += distance; /* space at bottom of form */

    /*
     * Now find the largest width.  Start with width of left + right buttons
     * ('dismiss' + 'help' or 'quit' + 'random'), since they are adjacent.
     */
    XtSetArg(args[0], XtNwidth, &max_width);
    XtGetValues(left, args, ONE);
    XtSetArg(args[0], XtNwidth, &width);
    XtGetValues(right, args, ONE);
    /* doesn't count leftmost 'distance + border_width' and
       rightmost 'border_width + distance' since all entries have those */
    max_width = max_width + border_width + skip + border_width + width;

    /* 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++;
    }

    /*
     * Re-do the two side-by-side widgets to take up half the width each.
     *
     * With max_width and skip both having even values, we never have to
     * tweak left or right to maybe be one pixel wider than the other.
     */
    if (max_width % 2)
        ++max_width;
    XtSetArg(args[0], XtNwidth, &width);
    XtGetValues(left, args, ONE);
    XtSetArg(args[0], XtNwidth, &other_width);
    XtGetValues(right, args, ONE);
    if (width + border_width + skip / 2 < max_width / 2
        && other_width + border_width + skip / 2 < max_width / 2) {
        /* both are narrower than half */
        width = other_width = max_width / 2 - border_width - skip / 2;
        XtSetArg(args[0], XtNwidth, width);
        XtSetValues(left, args, ONE);
        XtSetArg(args[0], XtNwidth, other_width);
        XtSetValues(right, args, ONE);
    } else if (width + border_width + skip / 2 < max_width / 2) {
        /* 'other_width' (right) is half or more */
        width = max_width - other_width - 2 * border_width - skip;
        XtSetArg(args[0], XtNwidth, width);
        XtSetValues(left, args, ONE);
    } else if (other_width + border_width + skip / 2 < max_width / 2) {
        /* 'width' (left) is half or more */
        other_width = max_width - width - 2 * border_width - skip;
        XtSetArg(args[0], XtNwidth, other_width);
        XtSetValues(right, args, ONE);
    } else {
        ; /* both are exactly half... */
    }

    /*
     * 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);

    /*
     * If the menu's complete height is too big for the display,
     * forcing the height to be smaller will cause the vertical
     * scroll bar (enabled but not forced above) to be included.
     */
    screen_height = XHeightOfScreen(XtScreen(popup));
    screen_height -= appResources.extcmd_height_delta; /* NetHack.ad */
    if (cumulative_height >= screen_height) {
        /* 25 is a guesstimate for scrollbar width;
           window manager might override the request for y==1 */
        num_args = 0;
        XtSetArg(args[num_args], XtNy, 1); num_args++;
        XtSetArg(args[num_args], XtNwidth, max_width + 25); num_args++;
        XtSetArg(args[num_args], XtNheight, screen_height - 1); num_args++;
        XtSetValues(popup, args, num_args);
    }
    XtRealizeWidget(popup);
    XSetWMProtocols(XtDisplay(popup), XtWindow(popup), &wm_delete_window, 1);

    /* during role selection, highlight "random" as pre-selected choice */
    if (right_callback == ps_random && index(ps_randchars, '\n'))
        swap_fg_bg(right);

    return popup;
}

/*winmisc.c*/
