/*	SCCS Id: @(#)winX.c	3.4	1999/12/21	*/
/* Copyright (c) Dean Luick, 1992				  */
/* NetHack may be freely redistributed.  See license for details. */

/*
 * "Main" file for the X window-port.  This contains most of the interface
 * routines.  Please see doc/window.doc for an description of the window
 * interface.
 */

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

#ifdef MSDOS			/* from compiler */
#define SHORT_FILENAMES
#endif

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xatom.h>
#include <X11/Xos.h>

/* for color support */
#ifdef SHORT_FILENAMES
#include <X11/IntrinsP.h>
#else
#include <X11/IntrinsicP.h>
#endif

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

#ifdef SHORT_FILENAMES
#undef SHORT_FILENAMES	/* hack.h will reset via global.h if necessary */
#endif

#include "hack.h"
#include "winX.h"
#include "dlb.h"
#ifdef SHORT_FILENAMES
#include "patchlev.h"
#else
#include "patchlevel.h"
#endif

/* Should be defined in <X11/Intrinsic.h> but you never know */
#ifndef XtSpecificationRelease
#define XtSpecificationRelease 0
#endif

/*
 * Icons.
 */
#include "../win/X11/nh72icon"
#include "../win/X11/nh56icon"
#include "../win/X11/nh32icon"

static struct icon_info {
	const char *name;
	unsigned char *bits;
	unsigned width, height;
} icon_data[] = {
	{ "nh72", nh72icon_bits, nh72icon_width, nh72icon_height },
	{ "nh56", nh56icon_bits, nh56icon_width, nh56icon_height },
	{ "nh32", nh32icon_bits, nh32icon_width, nh32icon_height },
	{ (const char *)0, (unsigned char *)0, 0, 0 }
};

/*
 * Private global variables (shared among the window port files).
 */
struct xwindow window_list[MAX_WINDOWS];
AppResources appResources;
void FDECL((*input_func), (Widget,XEvent *,String *,Cardinal *));
int click_x, click_y, click_button;	/* Click position on a map window   */
					/* (filled by set_button_values()). */
int updated_inventory;


/* Interface definition, for windows.c */
struct window_procs X11_procs = {
    "X11",
    WC_COLOR|WC_HILITE_PET,
    0L,
    X11_init_nhwindows,
    X11_player_selection,
    X11_askname,
    X11_get_nh_event,
    X11_exit_nhwindows,
    X11_suspend_nhwindows,
    X11_resume_nhwindows,
    X11_create_nhwindow,
    X11_clear_nhwindow,
    X11_display_nhwindow,
    X11_destroy_nhwindow,
    X11_curs,
    X11_putstr,
    X11_display_file,
    X11_start_menu,
    X11_add_menu,
    X11_end_menu,
    X11_select_menu,
    genl_message_menu,		/* no need for X-specific handling */
    X11_update_inventory,
    X11_mark_synch,
    X11_wait_synch,
#ifdef CLIPPING
    X11_cliparound,
#endif
#ifdef POSITIONBAR
    donull,
#endif
    X11_print_glyph,
    X11_raw_print,
    X11_raw_print_bold,
    X11_nhgetch,
    X11_nh_poskey,
    X11_nhbell,
    X11_doprev_message,
    X11_yn_function,
    X11_getlin,
    X11_get_ext_cmd,
    X11_number_pad,
    X11_delay_output,
#ifdef CHANGE_COLOR	/* only a Mac option currently */
    donull,
    donull,
#endif
    /* other defs that really should go away (they're tty specific) */
    X11_start_screen,
    X11_end_screen,
#ifdef GRAPHIC_TOMBSTONE
    X11_outrip,
#else
    genl_outrip,
#endif
    genl_preference_update,
};

/*
 * Local functions.
 */
static void FDECL(dismiss_file, (Widget, XEvent*, String*, Cardinal*));
static void FDECL(delete_file, (Widget, XEvent*, String*, Cardinal*));
static void FDECL(yn_key, (Widget, XEvent*, String*, Cardinal*));
static void FDECL(yn_delete, (Widget, XEvent*, String*, Cardinal*));
static void FDECL(askname_delete, (Widget, XEvent*, String*, Cardinal*));
static void FDECL(getline_delete, (Widget, XEvent*, String*, Cardinal*));
static void FDECL(X11_hangup, (Widget, XEvent*, String*, Cardinal*));
static int FDECL(input_event, (int));
static void FDECL(win_visible, (Widget,XtPointer,XEvent *,Boolean *));
static void NDECL(init_standard_windows);


/*
 * Local variables.
 */
static boolean x_inited = FALSE;	/* TRUE if window system is set up. */
static winid message_win = WIN_ERR,	/* These are the winids of the	    */
	     map_win     = WIN_ERR,	/*   message, map, and status	    */
	     status_win  = WIN_ERR;	/*   windows, when they are created */
					/*   in init_windows().		    */
static Pixmap icon_pixmap = None;	/* Pixmap for icon.		    */

/*
 * Find the window structure that corresponds to the given widget.  Note
 * that this is not the popup widget, nor the viewport, but the child.
 */
struct xwindow *
find_widget(w)
    Widget w;
{
    int windex;
    struct xwindow *wp;

    /* Search to find the corresponding window.  Look at the main widget, */
    /* popup, the parent of the main widget, then parent of the widget. */
    for (windex = 0, wp = window_list; windex < MAX_WINDOWS; windex++, wp++)
	if (wp->type != NHW_NONE &&
	    (wp->w == w || wp->popup == w || (wp->w && (XtParent(wp->w)) == w)
		|| (wp->popup == XtParent(w))))
	    break;

    if (windex == MAX_WINDOWS) panic("find_widget:  can't match widget");
    return wp;
}

/*
 * Find a free window slot for use.
 */
static winid
find_free_window()
{
    int windex;
    struct xwindow *wp;

    for (windex = 0, wp = &window_list[0]; windex < MAX_WINDOWS; windex++, wp++)
	if (wp->type == NHW_NONE) break;

    if (windex == MAX_WINDOWS)
	panic("find_free_window: no free windows!");
    return (winid) windex;
}

/*
 * Color conversion.  The default X11 color converters don't try very
 * hard to find matching colors in PseudoColor visuals.  If they can't
 * allocate the exact color, they puke and give you something stupid.
 * This is an attempt to find some close readonly cell and use it.
 */
XtConvertArgRec const nhcolorConvertArgs[] = {
    {XtWidgetBaseOffset, (XtPointer)XtOffset(Widget, core.screen),
     sizeof(Screen *)},
    {XtWidgetBaseOffset, (XtPointer)XtOffset(Widget, core.colormap),
     sizeof(Colormap)}
};

#define done(type, value) \
	{							\
	    if (toVal->addr != 0) {				\
		if (toVal->size < sizeof(type)) {		\
		    toVal->size = sizeof(type);			\
		    return False;				\
		}						\
		*(type*)(toVal->addr) = (value);		\
	    }							\
	    else {						\
		static type static_val;				\
		static_val = (value);				\
		toVal->addr = (genericptr_t)&static_val;	\
	    }							\
	    toVal->size = sizeof(type);				\
	    return True;					\
	}

/* decl.h declares these, but it screws up structure references -dlc */
#undef red
#undef green
#undef blue

/*
 * Find a color that approximates the color named in "str".  The "str" color
 * may be a color name ("red") or number ("#7f0000").  If str == NULL, then
 * "color" is assumed to contain the RGB color wanted.
 * The approximate color found is returned in color as well.
 * Return True if something close was found.
 */
Boolean
nhApproxColor(screen, colormap, str, color)
Screen	 *screen;	/* screen to use */
Colormap colormap;	/* the colormap to use */
char     *str;		/* color name */
XColor   *color;	/* the X color structure; changed only if successful */
{
    int		ncells;
    long	cdiff = 16777216; /* 2^24; hopefully our map is smaller */
    XColor	tmp;
    static	XColor *table = 0;
    register int i, j;
    register long tdiff;

    /* if the screen doesn't have a big colormap, don't waste our time */
    /* or if it's huge, and _some_ match should have been possible */
    if((ncells = CellsOfScreen(screen)) < 256 || ncells > 4096)
	return False;

    if (str != (char *)0) {
	if (!XParseColor(DisplayOfScreen(screen), colormap, str, &tmp))
	    return False;
    } else {
	tmp = *color;
	tmp.flags = 7;	/* force to use all 3 of RGB */
    }

    if (!table) {
	table = (XColor *) XtCalloc(ncells, sizeof(XColor));
	for(i=0; i<ncells; i++)
	    table[i].pixel = i;
	XQueryColors(DisplayOfScreen(screen), colormap, table, ncells);
    }

    /* go thru cells and look for the one with smallest diff */
    /* diff is calculated abs(reddiff)+abs(greendiff)+abs(bluediff) */
    /* a more knowledgeable color person might improve this -dlc */
try_again:
    for(i=0; i<ncells; i++) {
	if(table[i].flags == tmp.flags) {
	    j = (int)table[i].red - (int)tmp.red;
	    if(j < 0) j = -j;
	    tdiff = j;
	    j = (int)table[i].green - (int)tmp.green;
	    if(j < 0) j = -j;
	    tdiff += j;
	    j = (int)table[i].blue - (int)tmp.blue;
	    if(j < 0) j = -j;
	    tdiff += j;
	    if(tdiff < cdiff) {
		cdiff = tdiff;
		tmp.pixel = i; /* table[i].pixel == i */
	    }
	}
    }

    if(cdiff == 16777216) return False;	/* nothing found?! */

    /*
     * Found something.  Return it and mark this color as used to avoid
     * reuse.  Reuse causes major contrast problems :-)
     */
    *color = table[tmp.pixel];
    table[tmp.pixel].flags = 0;
    /* try to alloc the color, so no one else can change it */
    if(!XAllocColor(DisplayOfScreen(screen), colormap, color)) {
	cdiff = 16777216;
	goto try_again;
    }
    return True;
}

Boolean
nhCvtStringToPixel(dpy, args, num_args, fromVal, toVal, closure_ret)
Display*	dpy;
XrmValuePtr	args;
Cardinal	*num_args;
XrmValuePtr	fromVal;
XrmValuePtr	toVal;
XtPointer	*closure_ret;
{
    String	    str = (String)fromVal->addr;
    XColor	    screenColor;
    XColor	    exactColor;
    Screen	    *screen;
    XtAppContext    app = XtDisplayToApplicationContext(dpy);
    Colormap	    colormap;
    Status	    status;
    String          params[1];
    Cardinal	    num_params=1;

    if (*num_args != 2) {
     XtAppWarningMsg(app, "wrongParameters", "cvtStringToPixel",
	"XtToolkitError",
	"String to pixel conversion needs screen and colormap arguments",
	(String *)0, (Cardinal *)0);
     return False;
    }

    screen = *((Screen **) args[0].addr);
    colormap = *((Colormap *) args[1].addr);

    /* If Xt colors, use the Xt routine and hope for the best */
#if (XtSpecificationRelease >= 5)
    if ((strcmpi(str, XtDefaultBackground) == 0) ||
	(strcmpi(str, XtDefaultForeground) == 0)) {
	return
	  XtCvtStringToPixel(dpy, args, num_args, fromVal, toVal, closure_ret);
    }
#else
    if (strcmpi(str, XtDefaultBackground) == 0) {
	*closure_ret = (char*)False;
	done(Pixel, WhitePixelOfScreen(screen));
    }
    if (strcmpi(str, XtDefaultForeground) == 0) {
	*closure_ret = (char*)False;
	done(Pixel, BlackPixelOfScreen(screen));
    }
#endif

    status = XAllocNamedColor(DisplayOfScreen(screen), colormap,
			      (char*)str, &screenColor, &exactColor);
    if (status == 0) {
	String msg, type;

	/* some versions of XAllocNamedColor don't allow #xxyyzz names */
	if (str[0] == '#' &&
	    XParseColor(DisplayOfScreen(screen), colormap, str, &exactColor) &&
	    XAllocColor(DisplayOfScreen(screen), colormap, &exactColor)) {
		*closure_ret = (char*)True;
		done(Pixel, exactColor.pixel);
	}

	params[0] = str;
	/* Server returns a specific error code but Xlib discards it.  Ugh */
	if (XLookupColor(DisplayOfScreen(screen), colormap, (char*)str,
			 &exactColor, &screenColor)) {
	    /* try to find another color that will do */
	    if (nhApproxColor(screen, colormap, (char*) str, &screenColor)) {
		*closure_ret = (char*)True;
		done(Pixel, screenColor.pixel);
	    }
	    type = "noColormap";
	    msg = "Cannot allocate colormap entry for \"%s\"";
	}
	else {
	    /* some versions of XLookupColor also don't allow #xxyyzz names */
	    if(str[0] == '#' &&
	       (nhApproxColor(screen, colormap, (char*) str, &screenColor))) {
		*closure_ret = (char*)True;
		done(Pixel, screenColor.pixel);
	    }
	    type = "badValue";
	    msg = "Color name \"%s\" is not defined";
	}

	XtAppWarningMsg(app, type, "cvtStringToPixel",
			"XtToolkitError", msg, params, &num_params);
	*closure_ret = False;
	return False;
    } else {
	*closure_ret = (char*)True;
	done(Pixel, screenColor.pixel);
    }
}

/* ARGSUSED */
static void
nhFreePixel(app, toVal, closure, args, num_args)
XtAppContext	app;
XrmValuePtr	toVal;
XtPointer	closure;
XrmValuePtr	args;
Cardinal	*num_args;
{
    Screen	    *screen;
    Colormap	    colormap;

    if (*num_args != 2) {
     XtAppWarningMsg(app, "wrongParameters",
		     "freePixel", "XtToolkitError",
		     "Freeing a pixel requires screen and colormap arguments",
		     (String *)0, (Cardinal *)0);
     return;
    }

    screen = *((Screen **) args[0].addr);
    colormap = *((Colormap *) args[1].addr);

    if (closure) {
	XFreeColors( DisplayOfScreen(screen), colormap,
		     (unsigned long*)toVal->addr, 1, (unsigned long)0
		    );
    }
}

/* [ALI] Utility function to ask Xaw for font height, since the previous
 * assumption of ascent + descent is not always valid.
 */
Dimension
nhFontHeight(w)
Widget w;
#ifdef _XawTextSink_h
{
    Widget sink;
    XawTextPosition pos = 0;
    int resWidth, resHeight;
    Arg args[1];

    XtSetArg(args[0], XtNtextSink, &sink);
    XtGetValues(w, args, 1);

    XawTextSinkFindPosition(sink, pos, 0, 0, 0, &pos, &resWidth, &resHeight);
    return resHeight;
}
#else
{
    XFontStruct *fs;
    Arg args[1];

    XtSetArg(args[0], XtNfont, &fs);
    XtGetValues(w, args, 1);

    /* Assume font height is ascent + descent. */
    return = fs->ascent + fs->descent;
}
#endif

/* Global Functions ======================================================== */
void
X11_raw_print(str)
    const char *str;
{
    (void) puts(str);
}

void
X11_raw_print_bold(str)
    const char *str;
{
    (void) puts(str);
}

void
X11_curs(window, x, y)
    winid window;
    int x, y;
{
    check_winid(window);

    if (x < 0 || x >= COLNO) {
	impossible("curs:  bad x value [%d]", x);
	x = 0;
    }
    if (y < 0 || y >= ROWNO) {
	impossible("curs:  bad y value [%d]", y);
	y = 0;
    }

    window_list[window].cursx = x;
    window_list[window].cursy = y;
}

void
X11_putstr(window, attr, str)
    winid window;
    int attr;
    const char *str;
{
    winid new_win;
    struct xwindow *wp;

    check_winid(window);
    wp = &window_list[window];

    switch (wp->type) {
	case NHW_MESSAGE:
	    (void) strncpy(toplines, str, TBUFSZ);	/* for Norep(). */
	    toplines[TBUFSZ - 1] = 0;
	    append_message(wp, str);
	    break;
	case NHW_STATUS:
	    adjust_status(wp, str);
	    break;
	case NHW_MAP:
	    impossible("putstr: called on map window \"%s\"", str);
	    break;
	case NHW_MENU:
	    if (wp->menu_information->is_menu) {
		impossible(
			"putstr:  called on a menu window, \"%s\" discarded",
			str);
		break;
	    }
	    /*
	     * Change this menu window into a text window by creating a
	     * new text window, then copying it to this winid.
	     */
	    new_win = X11_create_nhwindow(NHW_TEXT);
	    X11_destroy_nhwindow(window);
	    *wp = window_list[new_win];
	    window_list[new_win].type = NHW_NONE;	/* allow re-use */
	    /* fall though to add text */
	case NHW_TEXT:
	    add_to_text_window(wp, attr, str);
	    break;
	default:
	    impossible("putstr: unknown window type [%d] \"%s\"",
							    wp->type, str);
    }
}

/* We do event processing as a callback, so this is a null routine. */
void X11_get_nh_event() { return; }

int
X11_nhgetch()
{
    return input_event(EXIT_ON_KEY_PRESS);
}


int
X11_nh_poskey(x, y, mod)
    int *x, *y, *mod;
{
    int val = input_event(EXIT_ON_KEY_OR_BUTTON_PRESS);

    if (val == 0) {	/* user clicked on a map window */
	*x   = click_x;
	*y   = click_y;
	*mod = click_button;
    }
    return val;
}


winid
X11_create_nhwindow(type)
    int type;
{
    winid window;
    struct xwindow *wp;

    if (!x_inited)
	panic("create_nhwindow:  windows not initialized");

    /*
     * We have already created the standard message, map, and status
     * windows in the window init routine.  The first window of that
     * type to be created becomes the standard.
     *
     * A better way to do this would be to say that init_nhwindows()
     * has already defined these three windows.
     */
    if (type == NHW_MAP && map_win != WIN_ERR) {
	window = map_win;
	map_win = WIN_ERR;
	return window;
    }
    if (type == NHW_MESSAGE && message_win != WIN_ERR) {
	window = message_win;
	message_win = WIN_ERR;
	return window;
    }
    if (type == NHW_STATUS && status_win != WIN_ERR) {
	window = status_win;
	status_win = WIN_ERR;
	return window;
    }

    window = find_free_window();
    wp = &window_list[window];

    /* The create routines will set type, popup, w, and Win_info. */
    wp->prevx = wp->prevy = wp->cursx = wp->cursy =
				wp->pixel_width = wp->pixel_height = 0;
    wp->keep_window = FALSE;

    switch (type) {
	case NHW_MAP:
	    create_map_window(wp, TRUE, (Widget) 0);
	    break;
	case NHW_MESSAGE:
	    create_message_window(wp, TRUE, (Widget) 0);
	    break;
	case NHW_STATUS:
	    create_status_window(wp, TRUE, (Widget) 0);
	    break;
	case NHW_MENU:
	    create_menu_window(wp);
	    break;
	case NHW_TEXT:
	    create_text_window(wp);
	    break;
	default:
	    panic("create_nhwindow: unknown type [%d]", type);
	    break;
    }
    return window;
}

void
X11_clear_nhwindow(window)
    winid window;
{
    struct xwindow *wp;

    check_winid(window);
    wp = &window_list[window];

    switch (wp->type) {
	case NHW_MAP:	clear_map_window(wp); break;
	case NHW_TEXT:	clear_text_window(wp); break;
	case NHW_STATUS:
	case NHW_MENU:
	case NHW_MESSAGE:
	    /* do nothing for these window types */
	    break;
	default:
	    panic("clear_nhwindow: unknown window type [%d]", wp->type);
	    break;
    }
}

void
X11_display_nhwindow(window, blocking)
    winid window;
    boolean blocking;
{
    struct xwindow *wp;

    check_winid(window);
    wp = &window_list[window];

    switch (wp->type) {
	case NHW_MAP:
	    if (wp->popup)
		nh_XtPopup(wp->popup, (int)XtGrabNone, wp->w);
	    display_map_window(wp);	/* flush map */
	    /*
	     * We need to flush the message window here due to the way the tty
	     * port is set up.  To flush a window, you need to call this
	     * routine.  However, the tty port _pauses_ with a --more-- if we
	     * do a display_nhwindow(WIN_MESSAGE, FALSE).  Thus, we can't call
	     * display_nhwindow(WIN_MESSAGE,FALSE) in parse() because then we
	     * get a --more-- after every line.
	     *
	     * Perhaps the window document should mention that when the map
	     * is flushed, everything on the three main windows should be
	     * flushed.  Note: we don't need to flush the status window
	     * because we don't buffer changes.
	     */
	    if (WIN_MESSAGE != WIN_ERR)
		display_message_window(&window_list[WIN_MESSAGE]);
	    if (blocking)
		(void) x_event(EXIT_ON_KEY_OR_BUTTON_PRESS);
	    break;
	case NHW_MESSAGE:
	    if (wp->popup)
		 nh_XtPopup(wp->popup, (int)XtGrabNone, wp->w);
	    display_message_window(wp);	/* flush messages */
	    break;
	case NHW_STATUS:
	    if (wp->popup)
		nh_XtPopup(wp->popup, (int)XtGrabNone, wp->w);
	    break;			/* no flushing necessary */
	case NHW_MENU: {
	    int n;
	    menu_item *selected;

	    /* pop up menu */
	    n = X11_select_menu(window, PICK_NONE, &selected);
	    if (n) {
		impossible("perminvent: %d selected??", n);
		free((genericptr_t)selected);
	    }
	    break;
	}
	case NHW_TEXT:
	    display_text_window(wp, blocking);	/* pop up text window */
	    break;
	default:
	    panic("display_nhwindow: unknown window type [%d]", wp->type);
	    break;
    }
}

void
X11_destroy_nhwindow(window)
    winid window;
{
    struct xwindow *wp;

    check_winid(window);
    wp = &window_list[window];
    /*
     * "Zap" known windows, but don't destroy them.  We need to keep the
     * toplevel widget popped up so that later windows (e.g. tombstone)
     * are visible on DECWindow systems.  This is due to the virtual
     * roots that the DECWindow wm creates.
     */
    if (window == WIN_MESSAGE) {
	wp->keep_window = TRUE;
	WIN_MESSAGE = WIN_ERR;
	iflags.window_inited = 0;
    } else if (window == WIN_MAP) {
	wp->keep_window = TRUE;
	WIN_MAP = WIN_ERR;
    } else if (window == WIN_STATUS) {
	wp->keep_window = TRUE;
	WIN_STATUS = WIN_ERR;
    } else if (window == WIN_INVEN) {
	/* don't need to keep this one */
	WIN_INVEN = WIN_ERR;
    }

    switch (wp->type) {
	case NHW_MAP:
	    destroy_map_window(wp);
	    break;
	case NHW_MENU:
	    destroy_menu_window(wp);
	    break;
	case NHW_TEXT:
	    destroy_text_window(wp);
	    break;
	case NHW_STATUS:
	    destroy_status_window(wp);
	    break;
	case NHW_MESSAGE:
	    destroy_message_window(wp);
	    break;
	default:
	    panic("destroy_nhwindow: unknown window type [%d]", wp->type);
	    break;
    }
}

void
X11_update_inventory()
{
    if (x_inited && window_list[WIN_INVEN].menu_information->is_up) {
	updated_inventory = 1;	/* hack to avoid mapping&raising window */
	(void) display_inventory((char *)0, FALSE);
	updated_inventory = 0;
    }
}

/* The current implementation has all of the saved lines on the screen. */
int X11_doprev_message() { return 0; }

void
X11_nhbell()
{
    /* We can't use XBell until toplevel has been initialized. */
    if (x_inited)
	XBell(XtDisplay(toplevel), 0);
    /* else print ^G ?? */
}

void X11_mark_synch()
{
    if (x_inited) {
	/*
	 * The window document is unclear about the status of text
	 * that has been pline()d but not displayed w/display_nhwindow().
	 * Both the main and tty code assume that a pline() followed
	 * by mark_synch() results in the text being seen, even if
	 * display_nhwindow() wasn't called.  Duplicate this behavior.
	 */
	if (WIN_MESSAGE != WIN_ERR)
	    display_message_window(&window_list[WIN_MESSAGE]);
	XSync(XtDisplay(toplevel), False);
    }
}

void X11_wait_synch() { if (x_inited) XFlush(XtDisplay(toplevel)); }


/* Both resume_ and suspend_ are called from ioctl.c and unixunix.c. */
void X11_resume_nhwindows() { return; }

/* ARGSUSED */
void X11_suspend_nhwindows(str) const char *str; { return; }

/* Under X, we don't need to initialize the number pad. */
/* ARGSUSED */
void X11_number_pad(state) int state; { return; } /* called from options.c */


void X11_start_screen() { return; } /* called from setftty() in unixtty.c */
void X11_end_screen() { return; }   /* called from settty() in unixtty.c */

#ifdef GRAPHIC_TOMBSTONE
void X11_outrip(window, how)
    winid window;
    int how;
{
    struct xwindow *wp;

    check_winid(window);
    wp = &window_list[window];

    if (wp->type == NHW_TEXT) {
	wp->text_information->is_rip = TRUE;
    } else {
	panic("ripout on non-text window (window type [%d])", wp->type);
    }

    calculate_rip_text(how);
}
#endif

/* init and exit nhwindows ------------------------------------------------- */

XtAppContext app_context;		/* context of application */
Widget	     toplevel = (Widget) 0;	/* toplevel widget */
Atom         wm_delete_window;		/* pop down windows */

static XtActionsRec actions[] = {
    {"dismiss_file",	dismiss_file},	/* action for file viewing widget */
    {"delete_file",	delete_file},	/* action for file delete-window */
    {"dismiss_text",	dismiss_text},	/* button action for text widget */
    {"delete_text",	delete_text},	/* delete action for text widget */
    {"key_dismiss_text",key_dismiss_text},/* key action for text widget */
#ifdef GRAPHIC_TOMBSTONE
    {"rip_dismiss_text",rip_dismiss_text},/* action for rip in text widget */
#endif
    {"menu_key",	menu_key},	/* action for menu accelerators */
    {"yn_key",		yn_key},	/* action for yn accelerators */
    {"yn_delete",	yn_delete},	/* action for yn delete-window */
    {"askname_delete",	askname_delete},/* action for askname delete-window */
    {"getline_delete",	getline_delete},/* action for getline delete-window */
    {"menu_delete",	menu_delete},	/* action for menu delete-window */
    {"ec_key",		ec_key},	/* action for extended commands */
    {"ec_delete",	ec_delete},	/* action for ext-com menu delete */
    {"ps_key",		ps_key},	/* action for player selection */
    {"race_key",	race_key},	/* action for race selection */
    {"gend_key",	gend_key},	/* action for gender selection */
    {"algn_key",	algn_key},	/* action for alignment selection */
    {"X11_hangup",	X11_hangup},	/* action for delete of top-level */
    {"input",		map_input},	/* action for key input */
    {"scroll",		nh_keyscroll},	/* action for scrolling by keys */
};

static XtResource resources[] = {
    { "slow", "Slow", XtRBoolean, sizeof(Boolean),
      XtOffset(AppResources *,slow), XtRString, "True" },
    { "autofocus", "AutoFocus", XtRBoolean, sizeof(Boolean),
      XtOffset(AppResources *,autofocus), XtRString, "False" },
    { "message_line", "Message_line", XtRBoolean, sizeof(Boolean),
      XtOffset(AppResources *,message_line), XtRString, "False" },
    { "double_tile_size", "Double_tile_size", XtRBoolean, sizeof(Boolean),
      XtOffset(AppResources *,double_tile_size), XtRString, "False" },
    { "tile_file", "Tile_file", XtRString, sizeof(String),
      XtOffset(AppResources *,tile_file), XtRString, "" },
    { "icon", "Icon", XtRString, sizeof(String),
      XtOffset(AppResources *,icon), XtRString, "nh72" },
    { "message_lines", "Message_lines", XtRInt, sizeof(int),
      XtOffset(AppResources *,message_lines), XtRString, "12" },
    { "pet_mark_bitmap", "Pet_mark_bitmap", XtRString, sizeof(String),
      XtOffset(AppResources *,pet_mark_bitmap), XtRString, "pet_mark.xbm" },
    { "pet_mark_color", "Pet_mark_color", XtRPixel, sizeof(XtRPixel),
      XtOffset(AppResources *,pet_mark_color), XtRString, "Red" },
#ifdef GRAPHIC_TOMBSTONE
    { "tombstone", "Tombstone", XtRString, sizeof(String),
      XtOffset(AppResources *,tombstone), XtRString, "rip.xpm" },
    { "tombtext_x", "Tombtext_x", XtRInt, sizeof(int),
      XtOffset(AppResources *,tombtext_x), XtRString, "155" },
    { "tombtext_y", "Tombtext_y", XtRInt, sizeof(int),
      XtOffset(AppResources *,tombtext_y), XtRString, "78" },
    { "tombtext_dx", "Tombtext_dx", XtRInt, sizeof(int),
      XtOffset(AppResources *,tombtext_dx), XtRString, "0" },
    { "tombtext_dy", "Tombtext_dy", XtRInt, sizeof(int),
      XtOffset(AppResources *,tombtext_dy), XtRString, "13" },
#endif
};

void
X11_init_nhwindows(argcp,argv)
int* argcp;
char** argv;
{
    static const char *banner_text[] = {
	COPYRIGHT_BANNER_A,
	COPYRIGHT_BANNER_B,
	COPYRIGHT_BANNER_C,
	"",
	"",
	0
    };
    register const char **pp;
    int i;
    Cardinal num_args;
    Arg args[4];
    uid_t savuid;

    /* Init windows to nothing. */
    for (i = 0; i < MAX_WINDOWS; i++)
	window_list[i].type = NHW_NONE;

    /*
     * setuid hack: make sure that if nethack is setuid, to use real uid
     * when opening X11 connections, in case the user is using xauth, since
     * the "games" or whatever user probably doesn't have permission to open
     * a window on the user's display.  This code is harmless if the binary
     * is not installed setuid.  See include/system.h on compilation failures.
     */
    savuid = geteuid();
    (void) seteuid(getuid());

    XSetIOErrorHandler((XIOErrorHandler) hangup);

    num_args = 0;
    XtSetArg(args[num_args], XtNallowShellResize, True);	num_args++;
    toplevel = XtAppInitialize(
		    &app_context,
		    DEF_GAME_NAME,		/* application class */
		    (XrmOptionDescList)0, 0,	/* options list */
		    argcp, (String *)argv,	/* command line args */
		    (String *)0,		/* fallback resources */
		    (ArgList)args, num_args);
    XtOverrideTranslations(toplevel,
	XtParseTranslationTable("<Message>WM_PROTOCOLS: X11_hangup()"));

    /* We don't need to realize the top level widget. */

#ifdef TEXTCOLOR
    /* add new color converter to deal with overused colormaps */
    XtSetTypeConverter(XtRString, XtRPixel, nhCvtStringToPixel,
		       (XtConvertArgList)nhcolorConvertArgs,
		       XtNumber(nhcolorConvertArgs),
		       XtCacheByDisplay, nhFreePixel);
#endif /* TEXTCOLOR */

    /* Register the actions mentioned in "actions". */
    XtAppAddActions(app_context, actions, XtNumber(actions));

    /* Get application-wide resources */
    XtGetApplicationResources(toplevel, (XtPointer)&appResources,
			      resources, XtNumber(resources),
			      (ArgList)0, ZERO);

    /* Initialize other things. */
    init_standard_windows();

    /* Give the window manager an icon to use;  toplevel must be realized. */
    if (appResources.icon && *appResources.icon) {
	struct icon_info *ip;

	for (ip = icon_data; ip->name; ip++)
	    if (!strcmp(appResources.icon, ip->name)) {
		icon_pixmap = XCreateBitmapFromData(XtDisplay(toplevel),
				XtWindow(toplevel),
				(genericptr_t)ip->bits, ip->width, ip->height);
		if (icon_pixmap != None) {
		    XWMHints hints;

		    (void) memset((genericptr_t)&hints, 0, sizeof(XWMHints));
		    hints.flags = IconPixmapHint;
		    hints.icon_pixmap = icon_pixmap;
		    XSetWMHints(XtDisplay(toplevel),
				XtWindow(toplevel), &hints);
		}
		break;
	    }
    }

    /* end of setuid hack: reset uid back to the "games" uid */
    (void) seteuid(savuid);

    x_inited = TRUE;	/* X is now initialized */

    /* Display the startup banner in the message window. */
    for (pp = banner_text; *pp; pp++)
	X11_putstr(WIN_MESSAGE, 0, *pp);
}

/*
 * All done.
 */
/* ARGSUSED */
void X11_exit_nhwindows(dummy)
    const char *dummy;
{
    extern Pixmap tile_pixmap;	/* from winmap.c */

    /* explicitly free the icon and tile pixmaps */
    if (icon_pixmap != None) {
	XFreePixmap(XtDisplay(toplevel), icon_pixmap);
	icon_pixmap = None;
    }
    if (tile_pixmap != None) {
	XFreePixmap(XtDisplay(toplevel), tile_pixmap);
	tile_pixmap = None;
    }
    if (WIN_INVEN != WIN_ERR)
	X11_destroy_nhwindow(WIN_INVEN);
    if (WIN_STATUS != WIN_ERR)
	X11_destroy_nhwindow(WIN_STATUS);
    if (WIN_MAP != WIN_ERR)
	X11_destroy_nhwindow(WIN_MAP);
    if (WIN_MESSAGE != WIN_ERR)
	X11_destroy_nhwindow(WIN_MESSAGE);
}


/* delay_output ------------------------------------------------------------ */

/*
 * Timeout callback for delay_output().  Send a fake message to the map
 * window.
 */
/* ARGSUSED */
static void
d_timeout(client_data, id)
    XtPointer client_data;
    XtIntervalId *id;
{
    XEvent event;
    XClientMessageEvent *mesg;

    /* Set up a fake message to the event handler. */
    mesg = (XClientMessageEvent *) &event;
    mesg->type = ClientMessage;
    mesg->message_type = XA_STRING;
    mesg->format = 8;
    XSendEvent(XtDisplay(window_list[WIN_MAP].w),
		XtWindow(window_list[WIN_MAP].w),
		False,
		NoEventMask,
		(XEvent*) mesg);
}

/*
 * Delay for 50ms.  This is not implemented asynch.  Maybe later.
 * Start the timeout, then wait in the event loop.  The timeout
 * function will send an event to the map window which will be waiting
 * for a sent event.
 */
void
X11_delay_output()
{
    if (!x_inited) return;

    (void) XtAppAddTimeOut(app_context, 30L, d_timeout, (XtPointer) 0);

    /* The timeout function will enable the event loop exit. */
    (void) x_event(EXIT_ON_SENT_EVENT);
}

/* X11_hangup -------------------------------------------------------------- */
/* ARGSUSED */
static void
X11_hangup(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    hangup(1);		/* 1 is commonly SIGHUP, but ignored anyway */
}

/* askname ----------------------------------------------------------------- */
/* ARGSUSED */
static void
askname_delete(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    nh_XtPopdown(w);
    (void) strcpy(plname, "Mumbles");	/* give them a name... ;-) */
    exit_x_event = TRUE;
}

/* Callback for askname dialog widget. */
/* ARGSUSED */
static void
askname_done(w, client_data, call_data)
    Widget w;
    XtPointer client_data;
    XtPointer call_data;
{
    int len;
    char *s;
    Widget dialog = (Widget) client_data;

    s = (char *) GetDialogResponse(dialog);

    len = strlen(s);
    if (len == 0) {
	X11_nhbell();
	return;
    }

    /* Truncate name if necessary */
    if (len >= sizeof(plname)-1)
	len = sizeof(plname)-1;

    (void) strncpy(plname, s, len);
    plname[len] = '\0';
    XtFree(s);

    nh_XtPopdown(XtParent(dialog));
    exit_x_event = TRUE;
}

void
X11_askname()
{
    Widget popup, dialog;
    Arg args[1];

    XtSetArg(args[0], XtNallowShellResize, True);

    popup = XtCreatePopupShell("askname", transientShellWidgetClass,
				   toplevel, args, ONE);
    XtOverrideTranslations(popup,
	XtParseTranslationTable("<Message>WM_PROTOCOLS: askname_delete()"));

    dialog = CreateDialog(popup, "dialog",
				    askname_done, (XtCallbackProc) 0);

    SetDialogPrompt(dialog, "What is your name?");	/* set prompt */
    SetDialogResponse(dialog, "");		/* set default answer */

    XtRealizeWidget(popup);
    positionpopup(popup, TRUE);		/* center,bottom */

    nh_XtPopup(popup, (int)XtGrabExclusive, dialog);

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


/* getline ----------------------------------------------------------------- */
/* This uses Tim Theisen's dialog widget set (from GhostView). */

static Widget getline_popup, getline_dialog;

#define CANCEL_STR "\033"
static char *getline_input;


/* Callback for getline dialog widget. */
/* ARGSUSED */
static void
done_button(w, client_data, call_data)
    Widget w;
    XtPointer client_data;
    XtPointer call_data;
{
    int len;
    char *s;
    Widget dialog = (Widget) client_data;

    s = (char *) GetDialogResponse(dialog);
    len = strlen(s);

    /* Truncate input if necessary */
    if (len >= BUFSZ) len = BUFSZ - 1;

    (void) strncpy(getline_input, s, len);
    getline_input[len] = '\0';
    XtFree(s);

    nh_XtPopdown(XtParent(dialog));
    exit_x_event = TRUE;
}

/* ARGSUSED */
static void
getline_delete(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    Strcpy(getline_input, CANCEL_STR);
    nh_XtPopdown(w);
    exit_x_event = TRUE;
}

/* Callback for getline dialog widget. */
/* ARGSUSED */
static void
abort_button(w, client_data, call_data)
    Widget w;
    XtPointer client_data;
    XtPointer call_data;
{
    Widget dialog = (Widget) client_data;

    Strcpy(getline_input, CANCEL_STR);
    nh_XtPopdown(XtParent(dialog));
    exit_x_event = TRUE;
}


void
X11_getlin(question, input)
    const char *question;
    char *input;
{
    static boolean need_to_init = True;

    getline_input = input;

    flush_screen(1);
    if (need_to_init) {
	Arg args[1];

	need_to_init = False;

	XtSetArg(args[0], XtNallowShellResize, True);

	getline_popup = XtCreatePopupShell("getline",transientShellWidgetClass,
				   toplevel, args, ONE);
	XtOverrideTranslations(getline_popup,
	    XtParseTranslationTable("<Message>WM_PROTOCOLS: getline_delete()"));

	getline_dialog = CreateDialog(getline_popup, "dialog",
				    done_button, abort_button);

	XtRealizeWidget(getline_popup);
	XSetWMProtocols(XtDisplay(getline_popup), XtWindow(getline_popup),
			&wm_delete_window, 1);
    }
    SetDialogPrompt(getline_dialog, (String)question);	/* set prompt */
    SetDialogResponse(getline_dialog, "");	/* set default answer */
    positionpopup(getline_popup, TRUE);		/* center,bottom */

    nh_XtPopup(getline_popup, (int)XtGrabExclusive, getline_dialog);

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


/* Display file ------------------------------------------------------------ */
static const char display_translations[] =
    "#override\n\
     <Key>q: dismiss_file()\n\
     <Key>Escape: dismiss_file()\n\
     <BtnDown>: dismiss_file()";


/* WM_DELETE_WINDOW callback for file dismissal. */
/*ARGSUSED*/
static void
delete_file(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    nh_XtPopdown(w);
    XtDestroyWidget(w);
}

/* Callback for file dismissal. */
/*ARGSUSED*/
static void
dismiss_file(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    Widget popup = XtParent(w);
    nh_XtPopdown(popup);
    XtDestroyWidget(popup);
}

void
#ifdef FILE_AREAS
X11_display_file(area, str, complain)
    const char *area;
#else
X11_display_file(str, complain)
#endif
    const char *str;
    boolean complain;
{
    dlb *fp;
    Arg args[12];
    Cardinal num_args;
    Widget popup, dispfile;
    Position top_margin, bottom_margin, left_margin, right_margin;
    XFontStruct *fs;
    int new_width, new_height;
#define LLEN 128
    char line[LLEN];
    int num_lines;
    char *textlines;
    int charcount;

    /* Use the port-independent file opener to see if the file exists. */
#ifdef FILE_AREAS
    fp = dlb_fopen_area(area, str, RDTMODE);
#else
    fp = dlb_fopen(str, RDTMODE);
#endif

    if (!fp) {
	if(complain) pline("Cannot open %s.  Sorry.", str);

	return;	/* it doesn't exist, ignore */
    }

    /*
     * Count the number of lines and characters in the file.
     */
    num_lines = 0;
    charcount = 1;
    while (dlb_fgets(line, LLEN, fp)) {
	num_lines++;
	charcount += strlen(line);
    }

    (void) dlb_fclose(fp);

    /* Ignore empty files */
    if (num_lines == 0) return;

    /* If over the max window size, truncate the window size to the max */
    if (num_lines >= DISPLAY_FILE_SIZE)
	num_lines = DISPLAY_FILE_SIZE;

    /*
     * Re-open the file and read the data into a buffer.  Cannot use
     * the XawAsciiFile type of widget, because that is not DLB-aware.
     */
    textlines = (char *) alloc((unsigned int) charcount);
    textlines[0] = '\0';

#ifdef FILE_AREAS
    fp = dlb_fopen_area(area, str, RDTMODE);
#else
    fp = dlb_fopen(str, RDTMODE);
#endif

    while (dlb_fgets(line, LLEN, fp)) {
	(void) strcat(textlines, line);
    }

    (void) dlb_fclose(fp);

    num_args = 0;
    XtSetArg(args[num_args], XtNtitle, str);	num_args++;

    popup = XtCreatePopupShell("display_file", topLevelShellWidgetClass,
					       toplevel, args, num_args);
    XtOverrideTranslations(popup,
	XtParseTranslationTable("<Message>WM_PROTOCOLS: delete_file()"));

    num_args = 0;
    XtSetArg(args[num_args], XtNscrollHorizontal,
				XawtextScrollWhenNeeded);	num_args++;
    XtSetArg(args[num_args], XtNscrollVertical,
				XawtextScrollWhenNeeded);	num_args++;
    XtSetArg(args[num_args], XtNtype, XawAsciiString);		num_args++;
    XtSetArg(args[num_args], XtNstring, textlines);		num_args++;
    XtSetArg(args[num_args], XtNdisplayCaret, False);		num_args++;
    XtSetArg(args[num_args], XtNtranslations,
	XtParseTranslationTable(display_translations));		num_args++;

    dispfile = XtCreateManagedWidget(
			"text",			/* name */
			asciiTextWidgetClass,
			popup,			/* parent widget */
			args,			/* set some values */
			num_args);		/* number of values to set */

    /* Get font and border information. */
    num_args = 0;
    XtSetArg(args[num_args], XtNfont,	      &fs);	       num_args++;
    XtSetArg(args[num_args], XtNtopMargin,    &top_margin);    num_args++;
    XtSetArg(args[num_args], XtNbottomMargin, &bottom_margin); num_args++;
    XtSetArg(args[num_args], XtNleftMargin,   &left_margin);   num_args++;
    XtSetArg(args[num_args], XtNrightMargin,  &right_margin);  num_args++;
    XtGetValues(dispfile, args, num_args);

    /*
     * The data files are currently set up assuming an 80 char wide window
     * and a fixed width font.  Soo..
     */
    new_height = num_lines * nhFontHeight(dispfile) +
						top_margin + bottom_margin;
    new_width  = 80 * fs->max_bounds.width + left_margin + right_margin;

    /* Set the new width and height. */
    num_args = 0;
    XtSetArg(args[num_args], XtNwidth,  new_width);  num_args++;
    XtSetArg(args[num_args], XtNheight, new_height); num_args++;
    XtSetValues(dispfile, args, num_args);

    nh_XtPopup(popup, (int)XtGrabNone, (Widget)0);
    free(textlines);
}


/* yn_function ------------------------------------------------------------- */
/* (not threaded) */

static const char *yn_quitchars = " \n\r";
static const char *yn_choices;	/* string of acceptable input */
static char yn_def;
static char yn_return;		/* return value */
static char yn_esc_map;		/* ESC maps to this char. */
static Widget yn_popup;		/* popup for the yn fuction (created once) */
static Widget yn_label;		/* label for yn function (created once) */
static boolean yn_getting_num;	/* TRUE if accepting digits */
static int yn_ndigits;		/* digit count */
static long yn_val;		/* accumulated value */

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

/*
 * Convert the given key event into a character.  If the key maps to
 * more than one character only the first is returned.  If there is
 * no conversion (i.e. just the CTRL key hit) a NUL is returned.
 */
char
key_event_to_char(key)
    XKeyEvent *key;
{
    char keystring[MAX_KEY_STRING];
    int nbytes;
    boolean meta = !!(key->state & Mod1Mask);

    nbytes = XLookupString(key, keystring, MAX_KEY_STRING,
			   (KeySym *)0, (XComposeStatus *)0);

    /* Modifier keys return a zero lengh string when pressed. */
    if (nbytes == 0) return '\0';

    return (char) (((int) keystring[0]) + (meta ? 0x80 : 0));
}

/*
 * Called when we get a WM_DELETE_WINDOW event on a yn window.
 */
/* ARGSUSED */
static void
yn_delete(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    yn_getting_num = FALSE;
    /* Only use yn_esc_map if we have choices.  Otherwise, return ESC. */
    yn_return = yn_choices ? yn_esc_map : '\033';
    exit_x_event = TRUE;	/* exit our event handler */
}

/*
 * Called when we get a key press event on a yn window.
 */
/* ARGSUSED */
static void
yn_key(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    char ch;

    if(appResources.slow && !input_func)
	map_input(w, event, params, num_params);

    ch = key_event_to_char((XKeyEvent *) event);

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

    if (!yn_choices) {			/* accept any input */
	yn_return = ch;
    } else {
	ch = lowc(ch);			/* move to lower case */

	if (ch == '\033') {
	    yn_getting_num = FALSE;
	    yn_return = yn_esc_map;
	} else if (index(yn_quitchars, ch)) {
	    yn_return = yn_def;
	} else if (index(yn_choices, ch)) {
	    if (ch == '#') {
		if (yn_getting_num) {	/* don't select again */
		    X11_nhbell();
		    return;
		}
		yn_getting_num = TRUE;
		yn_ndigits = 0;
		yn_val = 0;
		return;			/* wait for more input */
	    }
	    yn_return = ch;
	    if (ch != 'y') yn_getting_num = FALSE;
	} else {
	    if (yn_getting_num) {
		if (digit(ch)) {
		    yn_ndigits++;
		    yn_val = (yn_val * 10) + (long) (ch - '0');
		    return;			/* wait for more input */
		}
		if (yn_ndigits && (ch == '\b' || ch == 127/*DEL*/)) {
		    yn_ndigits--;
		    yn_val = yn_val/ 10;
		    return;			/* wait for more input */
		}
	    }
	    X11_nhbell();		/* no match */
	    return;
	}

	if (yn_getting_num) {
	    yn_return = '#';
	    if (yn_val < 0) yn_val = 0;
	    yn_number = yn_val;	/* assign global */
	}
    }
    exit_x_event = TRUE;	/* exit our event handler */
}


char
X11_yn_function(ques, choices, def)
    const char *ques;
    const char *choices;
    char def;
{
    static Boolean need_to_init = True;
    char buf[QBUFSZ];
    Arg args[4];
    Cardinal num_args;

    yn_choices = choices;	/* set up globals for callback to use */
    yn_def     = def;

    /*
     * This is sort of a kludge.  There are quite a few places in the main
     * nethack code where a pline containing information is followed by a
     * call to yn_function().  There is no flush of the message window
     * (it is implicit in the tty window port), so the line never shows
     * up for us!  Solution: do our own flush.
     */
    if (WIN_MESSAGE != WIN_ERR)
	display_message_window(&window_list[WIN_MESSAGE]);

    if (choices) {
	char *cb, choicebuf[QBUFSZ];

	Strcpy(choicebuf, choices);	/* anything beyond <esc> is hidden */
	if ((cb = index(choicebuf, '\033')) != 0) *cb = '\0';
	/* ques [choices] (def) */
	if ((int)(1 + strlen(ques) + 2 + strlen(choicebuf) + 4) >= QBUFSZ)
	    panic("yn_function:  question too long");
	Sprintf(buf, "%s [%s] ", ques, choicebuf);
	if (def) Sprintf(eos(buf), "(%c) ", def);

	/* escape maps to 'q' or 'n' or default, in that order */
	yn_esc_map = (index(choices, 'q') ? 'q' :
		     (index(choices, 'n') ? 'n' :
					    def));
    } else {
	if ((int)(1 + strlen(ques)) >= QBUFSZ)
	    panic("yn_function:  question too long");
	Strcpy(buf, ques);
    }

    if (!appResources.slow && need_to_init) {
	need_to_init = False;

	XtSetArg(args[0], XtNallowShellResize, True);
	yn_popup = XtCreatePopupShell("query", transientShellWidgetClass,
					toplevel, args, ONE);
	XtOverrideTranslations(yn_popup,
	    XtParseTranslationTable("<Message>WM_PROTOCOLS: yn_delete()"));

	num_args = 0;
	XtSetArg(args[num_args], XtNtranslations,
		XtParseTranslationTable(yn_translations));	num_args++;
	yn_label = XtCreateManagedWidget("yn_label",
				labelWidgetClass,
				yn_popup,
				args, num_args);

	XtRealizeWidget(yn_popup);
	XSetWMProtocols(XtDisplay(yn_popup), XtWindow(yn_popup),
			&wm_delete_window, 1);
    }

    if(appResources.slow)
	input_func = yn_key;

    num_args = 0;
    XtSetArg(args[num_args], XtNlabel, buf);	num_args++;
    XtSetValues(yn_label, args, num_args);

    if(!appResources.slow) {
	/*
	 * Due to some kind of weird bug in the X11R4 and X11R5 shell, we
	 * need to set the label twice to get the size to change.
	 */
	num_args = 0;
	XtSetArg(args[num_args], XtNlabel, buf); num_args++;
	XtSetValues(yn_label, args, num_args);

	positionpopup(yn_popup, TRUE);
	nh_XtPopup(yn_popup, (int)XtGrabExclusive, yn_label);
    }

    yn_getting_num = FALSE;
    (void) x_event(EXIT_ON_EXIT);

    if(appResources.slow) {
	input_func = 0;
	num_args = 0;
	XtSetArg(args[num_args], XtNlabel, " ");	num_args++;
	XtSetValues(yn_label, args, num_args);
    } else {
	nh_XtPopdown(yn_popup);	/* this removes the event grab */
    }

    return yn_return;
}

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

/*
 * Before we wait for input via nhgetch() and nh_poskey(), we need to
 * do some pre-processing.
 */
static int
input_event(exit_condition)
    int exit_condition;
{
    if (WIN_STATUS != WIN_ERR)	/* hilighting on the fancy status window */
	check_turn_events();
    if (WIN_MAP != WIN_ERR)	/* make sure cursor is not clipped */
	check_cursor_visibility(&window_list[WIN_MAP]);
    if (WIN_MESSAGE != WIN_ERR)	/* reset pause line */
	set_last_pause(&window_list[WIN_MESSAGE]);

    return x_event(exit_condition);
}


/*ARGSUSED*/
void
msgkey(w, data, event)
    Widget w;
    XtPointer data;
    XEvent *event;
{
    Cardinal num = 0;
    map_input(window_list[WIN_MAP].w, event, (String*) 0, &num);
}

/*ARGSUSED*/
static void
win_visible(w, data, event, flag)	/* only called for autofocus */
    Widget w;
    XtPointer data;	/* client_data not used */
    XEvent *event;
    Boolean *flag;	/* continue_to_dispatch flag not used */
{
    XVisibilityEvent *vis_event = (XVisibilityEvent *)event;

    if (vis_event->state != VisibilityFullyObscured) {
	/* one-time operation; cancel ourself */
	XtRemoveEventHandler(toplevel, VisibilityChangeMask, False,
			     win_visible, (XtPointer) 0);
	/* grab initial input focus */
	XSetInputFocus(XtDisplay(w), XtWindow(w), RevertToNone, CurrentTime);
    }
}

/*
 * Set up the playing console.  This has three major parts:  the
 * message window, the map, and the status window.
 */
static void
init_standard_windows()
{
    Widget form, message_viewport, map_viewport, status;
    Arg args[8];
    Cardinal num_args;
    Dimension message_vp_width, map_vp_width, status_width, max_width;
    int map_vp_hd, status_hd;
    struct xwindow *wp;


    num_args = 0;
    XtSetArg(args[num_args], XtNallowShellResize, True);	num_args++;
    form = XtCreateManagedWidget(DEF_GAME_NAME,
				panedWidgetClass,
				toplevel, args, num_args);

    XtAddEventHandler(form, KeyPressMask, False,
		      (XtEventHandler) msgkey, (XtPointer) 0);

    if (appResources.autofocus)
	XtAddEventHandler(toplevel, VisibilityChangeMask, False,
			  win_visible, (XtPointer) 0);

    /*
     * Create message window.
     */
    WIN_MESSAGE = message_win = find_free_window();
    wp = &window_list[message_win];
    wp->cursx = wp->cursy = wp->pixel_width = wp->pixel_height = 0;
    wp->popup = (Widget) 0;
    create_message_window(wp, FALSE, form);
    message_viewport = XtParent(wp->w);


    /* Tell the form that contains it that resizes are OK. */
    num_args = 0;
    XtSetArg(args[num_args], XtNresizable, True);		num_args++;
    XtSetArg(args[num_args], XtNleft,	   XtChainLeft);	num_args++;
    XtSetArg(args[num_args], XtNtop,	   XtChainTop);		num_args++;
    XtSetValues(message_viewport, args, num_args);

    if(appResources.slow) {
	num_args = 0;
	XtSetArg(args[num_args], XtNtranslations,
		 XtParseTranslationTable(yn_translations)); num_args++;
	yn_label = XtCreateManagedWidget("yn_label",
					 labelWidgetClass,
					 form,
					 args, num_args);
	num_args = 0;
	XtSetArg(args[num_args], XtNfromVert, message_viewport); num_args++;
	XtSetArg(args[num_args], XtNjustify, XtJustifyLeft);	num_args++;
	XtSetArg(args[num_args], XtNresizable, True);	num_args++;
	XtSetArg(args[num_args], XtNlabel, " ");	num_args++;
	XtSetValues(yn_label, args, num_args);
    }

    /*
     * Create the map window & viewport and chain the viewport beneath the
     * message_viewport.
     */
    map_win = find_free_window();
    wp = &window_list[map_win];
    wp->cursx = wp->cursy = wp->pixel_width = wp->pixel_height = 0;
    wp->popup = (Widget) 0;
    create_map_window(wp, FALSE, form);
    map_viewport = XtParent(wp->w);

    /* Chain beneath message_viewport or yn window. */
    num_args = 0;
    if(appResources.slow) {
	XtSetArg(args[num_args], XtNfromVert, yn_label);	num_args++;
    } else {
	XtSetArg(args[num_args], XtNfromVert, message_viewport);num_args++;
    }
    XtSetArg(args[num_args], XtNbottom, XtChainBottom);		num_args++;
    XtSetValues(map_viewport, args, num_args);

    /* Create the status window, with the form as it's parent. */
    status_win = find_free_window();
    wp = &window_list[status_win];
    wp->cursx = wp->cursy = wp->pixel_width = wp->pixel_height = 0;
    wp->popup = (Widget) 0;
    create_status_window(wp, FALSE, form);
    status = wp->w;

    /*
     * Chain the status window beneath the viewport.  Mark the left and right
     * edges so that they stay a fixed distance from the left edge of the
     * parent, as well as the top and bottom edges so that they stay a fixed
     * distance from the bottom of the parent.  We do this so that the status
     * will never expand or contract.
     */
    num_args = 0;
    XtSetArg(args[num_args], XtNfromVert, map_viewport);	num_args++;
    XtSetArg(args[num_args], XtNleft,	  XtChainLeft);		num_args++;
    XtSetArg(args[num_args], XtNright,	  XtChainLeft);		num_args++;
    XtSetArg(args[num_args], XtNtop,	  XtChainBottom);	num_args++;
    XtSetArg(args[num_args], XtNbottom,	  XtChainBottom);	num_args++;
    XtSetValues(status, args, num_args);


    /*
     * Realize the popup so that the status widget knows it's size.
     *
     * If we unset MappedWhenManaged then the DECwindow driver doesn't
     * attach the nethack toplevel to the highest virtual root window.
     * So don't do it.
     */
    /* XtSetMappedWhenManaged(toplevel, False); */
    XtRealizeWidget(toplevel);
    wm_delete_window = XInternAtom(XtDisplay(toplevel),
				   "WM_DELETE_WINDOW", False);
    XSetWMProtocols(XtDisplay(toplevel), XtWindow(toplevel),
		    &wm_delete_window, 1);

    /*
     * Resize to at most full-screen.
     */
    {
#define TITLEBAR_SPACE 18 /* Leave SOME screen for window decorations */

	int screen_width  = WidthOfScreen(XtScreen(wp->w));
	int screen_height = HeightOfScreen(XtScreen(wp->w)) - TITLEBAR_SPACE;
	Dimension form_width, form_height;

	XtSetArg(args[0], XtNwidth, &form_width);
	XtSetArg(args[1], XtNheight, &form_height);
	XtGetValues(toplevel, args, TWO);
	
	if (form_width > screen_width || form_height > screen_height) {
	    XtSetArg(args[0], XtNwidth, min(form_width,screen_width));
	    XtSetArg(args[1], XtNheight, min(form_height,screen_height));
	    XtSetValues(toplevel, args, TWO);
	    XMoveWindow(XtDisplay(toplevel),XtWindow(toplevel),
		0, TITLEBAR_SPACE);
	}
#undef TITLEBAR_SPACE
    }

    post_process_tiles();	/* after toplevel is realized */

    /*
     * Now get the default widths of the windows.
     */
    XtSetArg(args[0], XtNwidth, &message_vp_width);
    XtGetValues(message_viewport, args, ONE);
    XtSetArg(args[0], XtNwidth, &map_vp_width);
    XtSetArg(args[1], XtNhorizDistance, &map_vp_hd);
    XtGetValues(map_viewport, args, TWO);
    XtSetArg(args[0], XtNwidth, &status_width);
    XtSetArg(args[1], XtNhorizDistance, &status_hd);
    XtGetValues(status, args, TWO);

    /*
     * Adjust positions and sizes.  The message viewport widens out to the
     * widest width.  Both the map and status are centered by adjusting
     * their horizDistance.
     */
    if (map_vp_width < status_width || map_vp_width < message_vp_width) {
	if (status_width > message_vp_width) {
	    XtSetArg(args[0], XtNwidth, status_width);
	    XtSetValues(message_viewport, args, ONE);
	    max_width = status_width;
	} else {
/***** The status display looks better when left justified.
	    XtSetArg(args[0], XtNhorizDistance,
				status_hd+((message_vp_width-status_width)/2));
	    XtSetValues(status, args, ONE);
*****/
	    max_width = message_vp_width;
	}
	XtSetArg(args[0], XtNhorizDistance, map_vp_hd+((int)(max_width-map_vp_width)/2));
	XtSetValues(map_viewport, args, ONE);

    } else {	/* map is widest */
	XtSetArg(args[0], XtNwidth, map_vp_width);
	XtSetValues(message_viewport, args, ONE);

/***** The status display looks better when left justified.
	XtSetArg(args[0], XtNhorizDistance,
				status_hd+((map_vp_width-status_width)/2));

	XtSetValues(status, args, ONE);
*****/
    }
    /*
     * Clear all data values on the fancy status widget so that the values
     * used for spacing don't appear.  This needs to be called some time
     * after the fancy status widget is realized (above, with the game popup),
     * but before it is popped up.
     */
    null_out_status();
    /*
     * Set the map size to its standard size.  As with the message window
     * above, the map window needs to be set to its constrained size until
     * its parent (the viewport widget) was realized.
     *
     * Move the message window's slider to the bottom.
     */
    set_map_size(&window_list[map_win], COLNO, ROWNO);
    set_message_slider(&window_list[message_win]);

    /* attempt to catch fatal X11 errors before the program quits */
    (void) XtAppSetErrorHandler(app_context, (XtErrorHandler) hangup);

    /* We can now print to the message window. */
    iflags.window_inited = 1;
}


void
nh_XtPopup(w, g, childwid)
    Widget w;		/* widget */
    int    g;		/* type of grab */
    Widget childwid;	/* child to recieve focus (can be None) */
{
    XtPopup(w, (XtGrabKind)g);
    XSetWMProtocols(XtDisplay(w), XtWindow(w), &wm_delete_window, 1);
    if (appResources.autofocus) XtSetKeyboardFocus(toplevel, childwid);
}

void
nh_XtPopdown(w)
    Widget w;
{
    XtPopdown(w);
    if (appResources.autofocus) XtSetKeyboardFocus(toplevel, None);
}

void
win_X11_init()
{
#ifdef OPENWINBUG
    /* With the OpenWindows 3.0 libraries and the SunOS 4.1.2 ld, these
     * two routines will not be found when linking.  An apparently correct
     * executable is produced, along with nasty messages and a failure code
     * returned to make.  The routines are in the static libXmu.a and
     * libXmu.sa.4.0, but not in libXmu.so.4.0.  Rather than fiddle with
     * static linking, we do this.
     */
    if (rn2(2) > 2) {
	/* i.e., FALSE that an optimizer probably can't find */
	get_wmShellWidgetClass();
	get_applicationShellWidgetClass();
    }
#endif
    return;
}

/* Callback
 * Scroll a viewport, using standard NH 1,2,3,4,6,7,8,9 directions.
 */
/*ARGSUSED*/
void
nh_keyscroll(viewport, event, params, num_params)
    Widget   viewport;
    XEvent   *event;
    String   *params;
    Cardinal *num_params;
{
    Arg arg[2];
    Widget horiz_sb, vert_sb;
    float top, shown;
    Boolean do_call;
    int direction;
    Cardinal in_nparams = (num_params ? *num_params : 0);

    if (in_nparams != 1) return; /* bad translation */

    direction=atoi(params[0]);

    horiz_sb = XtNameToWidget(viewport, "*horizontal");
    vert_sb  = XtNameToWidget(viewport, "*vertical");

    if (!horiz_sb && !vert_sb) {
	/* Perhaps the widget enclosing this has scrollbars (could use while) */
	Widget parent=XtParent(viewport);
	if (parent) {
	    horiz_sb = XtNameToWidget(parent, "horizontal");
	    vert_sb  = XtNameToWidget(parent, "vertical");
	}
    }

#define H_DELTA 0.25		/* distance of horiz shift */
				/* vert shift is half of curr distance */
/* The V_DELTA is 1/2 the value of shown. */

    if (horiz_sb) {
	XtSetArg(arg[0], XtNshown,	&shown);
	XtSetArg(arg[1], XtNtopOfThumb, &top);
	XtGetValues(horiz_sb, arg, TWO);

	do_call = True;

	switch (direction) {
	  case 1: case 4: case 7:
	    top -= H_DELTA;
	    if (top < 0.0) top = 0.0;
	break; case 3: case 6: case 9:
	    top += H_DELTA;
	    if (top + shown > 1.0) top = 1.0 - shown;
	break; default:
	    do_call = False;
	}

	if (do_call) {
	    XtCallCallbacks(horiz_sb, XtNjumpProc, &top);
	}
    }

    if (vert_sb) {
	XtSetArg(arg[0], XtNshown,      &shown);
	XtSetArg(arg[1], XtNtopOfThumb, &top);
	XtGetValues(vert_sb, arg, TWO);

	do_call = True;

	switch (direction) {
	  case 7: case 8: case 9:
	    top -= shown / 2.0;
	    if (top < 0.0) top = 0;
	break; case 1: case 2: case 3:
	    top += shown / 2.0;
	    if (top + shown > 1.0) top = 1.0 - shown;
	break; default:
	    do_call = False;
	}

	if (do_call) {
	    XtCallCallbacks(vert_sb, XtNjumpProc, &top);
	}
    }
}

/*winX.c*/
