
/*
 initialize_screen()
 reset_screen()
 restart_screen()
 set_attribute(int attr)
 input_character(int timeout)
 input_line(int buflen, char *buffer, int timeout, int *read_size)
 display_char(int ch)
 clear_line()
 clear_screen()
 clear_status_window()
 clear_text_window()
 create_status_window(int lines)
 delete_status_window()
 get_cursor_position(int *row, int *col)
 move_cursor(int row, int col)
 scroll_line()
 select_status_window()
 select_text_window()
 */

#include <stdio.h>
#ifdef NEEDS_SELECT_H
#include <sys/select.h>
#endif
#include <ctype.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include "ztypes.h"
#include "xio.h"
#include "greypm.bm"

#define EVENTMASK (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | KeyPressMask | StructureNotifyMask | ExposureMask)
#define STATEVENTMASK (ButtonPressMask | KeyPressMask | StructureNotifyMask | ExposureMask)
#define TICKLENGTH (100000)

Display *xiodpy;
int xioscn;
Window xiowin, xioswin;
int xiodepth;
int xiobackstore;
GC gcblack, gcwhite, gcflip, gcgrey;
GC gcfont[NUMFONTS];
GC gcsblack, gcsflip; 
GC gcsfont[NUMFONTS], gcsnegfont[NUMFONTS];
XFontStruct *fontstr[NUMFONTS]; 
int spacewidth[NUMFONTS]; 
int lineheight, lineheightoff;
preferences prefs;

static int screen_inited = FALSE;

static int xiowinwid, xiowinhgt;
static int statusmode;
static int machineattr;

static int escapemode = FALSE;
static int modifymode = op_Cancel;

#ifdef __STDC__
static char *ansicheck = "\nThis binary was compiled with ANSI C.\n";
#else
static char *ansicheck = "\nThis binary was compiled with non-ANSI C.\n";
#endif

#ifdef __STDC__
static void loop(int stringmode, int *killflag, int timeout);
static void eventloop(int stringmode, int *killflag, int timeout);
static void redraw(int xpos, int ypos, int wid, int hgt);
static void rearrange_window();
#else
static void loop();
static void eventloop();
static void redraw();
static void rearrange_window();
#endif

#ifdef TESTING
static char *wordlist[10] = { "One ", "two ", "three ", "seventeen ", "a ", "veryvery ", "short ", "maybenot ", "I ", "stop. "};
#ifdef __STDC__
main()
#else
main()
#endif
{
    int numl;
    initialize_screen();
    SRANDOM_FUNC(getpid() + time(NULL));
    for (numl=0; numl>=0; numl++) {
	int numwords = RANDOM_FUNC() % 20 + 10;
	int ix, jx;
	char *cx;
	char buf[256];
	int pos;
	pos = 0;
	input_line(256, buf, 0, &pos);
	if (pos >= 4 && !strncmp(buf, "quit", 4))
	    break;
	for (ix=0; ix<numwords; ix++) {
	    jx = RANDOM_FUNC() % 10;
	    for (cx=wordlist[jx]; *cx; cx++) {
		display_char(*cx);
	    }
	}
	display_char('\n');
	/*ix = input_character(0);
	if (ix=='q')
	    break;*/
    }
    /*while (1) {
	char buf[256];
	int pos;
	input_line(256, buf, 0, &pos);
    }*/
    reset_screen();
    return 0;
}
#endif

#ifdef __STDC__
void display_char(int ch)
#else
void display_char(ch)
int ch;
#endif
{
    if (!statusmode) {
	xtext_add(ch, -1);
    }
    else {
	if (ch=='\n' || ch=='\r')
	    xstat_newline();
	else
	    xstat_insert(ch);
    }
}

/* timeout is in tenth-seconds; 0 means never timeout. returns char gotten, or -1 for timed-out. */
#ifdef __STDC__
int input_character(int timeout)
#else
int input_character(timeout)
int timeout;
#endif
{
    int killflag;

    if (statusmode) {
	xstat_set_dot_active(TRUE);
    }

    killflag = (-1);
    loop(FALSE, &killflag, timeout);

    if (statusmode) {
	xstat_set_dot_active(FALSE);
    }

    return killflag;
}

#ifdef __STDC__
void xio_pause()
#else
void xio_pause()
#endif
{
    int killflag;

    killflag = (-1);
    eventloop(FALSE, &killflag, 0);
}

/* timeout is in tenth-seconds; 0 means never timeout. returns -1 for timed-out (and don't affect other vars). returns '\n' if enter is hit.
 buffer and readpos are set on entry and should be set on exit.
 cannot be called reentrantly. */
#ifdef __STDC__
int input_line(int buflen, char *buffer, int timeout, int *readpos, 
  int firsttime)
#else
int input_line(buflen, buffer, timeout, readpos, firsttime)
int buflen;
char *buffer;
int timeout;
int *readpos;
int firsttime;
#endif
{
    int killflag;
    int ix;

    xted_init(buflen, buffer, readpos, &killflag, firsttime);

    loop(TRUE, &killflag, timeout);

    if (killflag == (-1)) {
	xtext_line_timeout();
    }

    return killflag;
}

#ifdef __STDC__
void initialize_screen()
#else
void initialize_screen()
#endif
{
    int ix;
    Pixmap greypm;

    XSetWindowAttributes attr;
    XGCValues gcvalues;

    xinit_openconnection();

    /* --- status window --- */

    xioswin = XCreateSimpleWindow(xiodpy, DefaultRootWindow(xiodpy), prefs.statwinx, prefs.statwiny, prefs.statwid*spacewidth[FIXED_FONT], prefs.stathgt*lineheight, 1, prefs.forecolor, prefs.backcolor);

    { 
	XSizeHints szhints;
	szhints.flags = PMinSize|PResizeInc|USPosition|USSize;
	szhints.x = prefs.statwinx;
	szhints.y = prefs.statwiny;
	szhints.width_inc = spacewidth[FIXED_FONT];
	szhints.height_inc = lineheight;
	szhints.width = prefs.statwid * szhints.width_inc;
	szhints.height = prefs.stathgt * szhints.height_inc;
	szhints.min_width = 1 * szhints.width_inc;
	szhints.min_height = 1 * szhints.height_inc;
	XSetNormalHints(xiodpy, xioswin, &szhints);
    }
    { /* make some window managers happy */
	XWMHints wmhints;
	wmhints.flags = InputHint;
	wmhints.input = True;
	XSetWMHints(xiodpy, xioswin, &wmhints);
    }

    attr.event_mask = STATEVENTMASK;
    /*attr.backing_store = WhenMapped;*/
    XChangeWindowAttributes(xiodpy, xioswin, CWEventMask /*|CWBackingStore*/, &attr);

    XStoreName(xiodpy, xioswin, "XZip Status");
    XMapWindow(xiodpy, xioswin);

    gcvalues.function = GXcopy;
    gcvalues.foreground = prefs.forecolor;
    gcvalues.background = prefs.backcolor;
    gcsblack = XCreateGC(xiodpy, xioswin, GCForeground|GCBackground, &gcvalues);
    XSetGraphicsExposures(xiodpy, gcsblack, FALSE);

    gcvalues.function = GXxor;
    gcvalues.foreground = (prefs.textcolor[FIXED_FONT])^(prefs.backcolor);
    gcvalues.background = (prefs.textcolor[FIXED_FONT])^(prefs.backcolor);
    gcsflip = XCreateGC(xiodpy, xioswin, GCFunction|GCForeground|GCBackground, &gcvalues);

    gcvalues.function = GXcopy;
    for (ix=0; ix<NUMFONTS; ix++) {
	gcvalues.font = fontstr[ix]->fid;
	gcvalues.foreground = prefs.backcolor;
	gcvalues.background = prefs.textcolor[ix];
	gcsnegfont[ix] = XCreateGC(xiodpy, xioswin, GCForeground|GCBackground|GCFont, &gcvalues);
	gcvalues.foreground = prefs.textcolor[ix];
	gcvalues.background = prefs.backcolor;
	gcsfont[ix] = XCreateGC(xiodpy, xioswin, GCForeground|GCBackground|GCFont, &gcvalues);
    }

    /* --- text window --- */

    xiowinwid = prefs.winw;
    xiowinhgt = prefs.winh;
    xiowin = XCreateSimpleWindow(xiodpy, DefaultRootWindow(xiodpy), prefs.winx, prefs.winy, xiowinwid, xiowinhgt, 1, prefs.forecolor, prefs.backcolor);

    {
	XSizeHints szhints;
	szhints.flags = PMinSize|USPosition|USSize;
	szhints.min_width = 250;
	szhints.min_height = 200;
	szhints.x = prefs.winx;
	szhints.y = prefs.winy;
	szhints.width = xiowinwid;
	szhints.height = xiowinhgt;
	XSetNormalHints(xiodpy, xiowin, &szhints);
    }
    { /* make some window managers happy */
	XWMHints wmhints;
	wmhints.flags = InputHint;
	wmhints.input = True;
	XSetWMHints(xiodpy, xiowin, &wmhints);
    }

    attr.event_mask = EVENTMASK;
    attr.backing_store = WhenMapped;
    XChangeWindowAttributes(xiodpy, xiowin, CWEventMask|CWBackingStore, &attr);

    XStoreName(xiodpy, xiowin, "XZip");
    XMapWindow(xiodpy, xiowin);

    gcvalues.function = GXcopy;
    gcvalues.foreground = prefs.forecolor;
    gcvalues.background = prefs.backcolor;
    gcblack = XCreateGC(xiodpy, xiowin, GCForeground|GCBackground, &gcvalues);
    XSetGraphicsExposures(xiodpy, gcblack, FALSE);

    gcvalues.foreground = prefs.backcolor;
    gcvalues.background = prefs.forecolor;
    gcwhite = XCreateGC(xiodpy, xiowin, GCForeground|GCBackground, &gcvalues);

    if (xiodepth==1) {
	gcvalues.fill_style = FillOpaqueStippled;
	greypm = XCreateBitmapFromData(xiodpy, xiowin, greypm_bits, greypm_width, greypm_height);
	gcvalues.foreground = prefs.forecolor;
	gcvalues.background = prefs.backcolor;
	gcvalues.stipple = greypm;
	gcgrey = XCreateGC(xiodpy, xiowin, GCForeground|GCBackground|GCFillStyle|GCStipple, &gcvalues);
    }
    else {
	gcvalues.foreground = prefs.greycolor;
	gcvalues.background = prefs.backcolor;
	gcgrey = XCreateGC(xiodpy, xiowin, GCForeground|GCBackground, &gcvalues);
    }

    gcvalues.function = GXxor;
    gcvalues.foreground = (prefs.textcolor[0])^(prefs.backcolor);
    gcvalues.background = (prefs.textcolor[0])^(prefs.backcolor);
    gcflip = XCreateGC(xiodpy, xiowin, GCFunction|GCForeground|GCBackground, &gcvalues);

    gcvalues.function = GXcopy;
    for (ix=0; ix<NUMFONTS; ix++) {
	gcvalues.font = fontstr[ix]->fid;
	if (ix & REVERSE) {
	    gcvalues.foreground = prefs.backcolor;
	    gcvalues.background = prefs.textcolor[ix];
	}
	else {
	    gcvalues.foreground = prefs.textcolor[ix];
	    gcvalues.background = prefs.backcolor;
	}
	gcfont[ix] = XCreateGC(xiodpy, xiowin, GCForeground|GCBackground|GCFont, &gcvalues);
    }

    /* --- final initing */

    screen_inited = TRUE;

    statusmode = FALSE;
    xtext_init();
    xmess_init();
    rearrange_window();
    {
	char buf[64];
	sprintf(buf, "Welcome to XZip version %s.", XZIPVERSION);
	xmess_set_message(buf, FALSE);
    }
    xstat_init(prefs.statwid, prefs.stathgt, prefs.statwinx, prefs.statwiny);

    machineattr = NORMAL;
}

#ifdef __STDC__
void restart_screen()
#else
void restart_screen()
#endif
{
  zbyte_t val;
    
  /* H_CONFIG is "flags 1"; H_FLAGS[0,1] is "flags 2". */
    
  switch (h_type) {
  case V1:
  case V2:
  case V3:
    val = get_byte (H_CONFIG);
    val |= (0x20 | 0x40); /* screen-splitting, variable-pitch font */
    set_byte (H_CONFIG, val);
    break;
  case V4:
    val = get_byte (H_CONFIG);
    val |= (0x04 | 0x08 | 0x10 | 0x80); /* bold, italic, fixed-width, timed input */
    set_byte (H_CONFIG, val);
    break;
  case V5:
  case V8:
    val = get_byte (H_CONFIG);
    val |= (0x04 | 0x08 | 0x10 | 0x80); /* bold, italic, fixed-width, timed input */
    set_byte (H_CONFIG, val);
    val = get_byte (H_FLAGS+1);
    val &= (~0x08); /* no graphics */
    val &= (~0x20); /* no mouse */
    val &= (~0x80); /* no sound */
    set_byte (H_FLAGS+1, val);
    break;
  }
}

#ifdef __STDC__
void reset_screen()
#else
void reset_screen()
#endif
{
    static char *killmessage = "[Hit any key to exit.]";

    if (!screen_inited)
	return;

    xmess_set_message(killmessage, TRUE);
    input_character(0);
    XCloseDisplay(xiodpy);
}

/* I have extended the definition of this function; a value of -1 does not affect the style, except to re-check the force-fixed bit that the game can set.
 The original Zip program never called set_attribute with a negative value, so this should be ok. */
#ifdef __STDC__
void set_attribute(int attr)
#else
void set_attribute(attr)
int attr;
#endif
{
    int finalattr;

    if (!attr)
	machineattr = NORMAL;
    else if (attr > 0)
	machineattr |= attr;
    else {
    }

    if (get_word (H_FLAGS) & FIXED_FONT_FLAG)
	finalattr = machineattr | FIXED_FONT;
    else
	finalattr = machineattr;

    if (!statusmode) {
	xtext_setstyle(-1, finalattr);
    }
    else {
	xstat_setattr(finalattr);
    }
}

#ifdef __STDC__
void clear_line()
#else
void clear_line()
#endif
{
    /* ### do something! */
}

/* clear both text and status window */
#ifdef __STDC__
void clear_screen()
#else
void clear_screen()
#endif
{
    xstat_clear_window();
    xtext_clear_window();
}

#ifdef __STDC__
void clear_status_window()
#else
void clear_status_window()
#endif
{
    xstat_clear_window();
}

#ifdef __STDC__
void clear_text_window()
#else
void clear_text_window()
#endif
{
    xtext_clear_window();
}

#ifdef __STDC__
void create_status_window(int lines)
#else
void create_status_window(lines)
int lines;
#endif
{
    xstat_set_window_size(lines);
}

#ifdef __STDC__
void delete_status_window()
#else
void delete_status_window()
#endif
{
    xstat_set_window_size(0);
}

#ifdef __STDC__
void get_cursor_position(int *row, int *col)
#else
void get_cursor_position(row, col)
int *row;
int *col;
#endif
{
    if (statusmode) {
	xstat_getpos(row, col);
    }
    else {
	*row = 1;
	*col = 1;
    }
}

#ifdef __STDC__
void move_cursor(int row, int col)
#else
void move_cursor(row, col)
int row;
int col;
#endif
{
    if (statusmode) {
	xstat_setpos(row, col);
    }
}

#ifdef __STDC__
void scroll_line()
#else
void scroll_line()
#endif
{
    if (statusmode) {
	/* ### do something? */
    }
    else
	display_char ('\n');
}

#ifdef __STDC__
void select_status_window()
#else
void select_status_window()
#endif
{
    statusmode = TRUE;
}

#ifdef __STDC__
void select_text_window()
#else
void select_text_window()
#endif
{
    statusmode = FALSE;
}


#ifdef __STDC__
void xio_bell()
#else
void xio_bell()
#endif
{
    XBell(xiodpy, 0);
}


#ifdef __STDC__
static void redraw(int xpos, int ypos, int wid, int hgt)
#else
static void redraw(xpos, ypos, wid, hgt)
int xpos;
int ypos;
int wid;
int hgt;
#endif
{
    /* yes, we ignore the exposure region. */
    XClearWindow(xiodpy, xiowin);
    xtext_redraw();
    xmess_redraw();
}

#ifdef __STDC__
static void handlekey(XKeyEvent *event, int stringmode, int *killflag)
#else
static void handlekey(event, stringmode, killflag)
XKeyEvent *event;
int stringmode;
int *killflag;
#endif
{
    int ix;
    char ch;
    KeySym ksym;

    ix = XLookupString(event, &ch, 1, &ksym, NULL);
    if (IsModifierKey(ksym) || ksym==XK_Multi_key) {
	return;
    }
    /* not yet set up to do keybindings */
    if (!stringmode) {
        if (!ix) {
	    /* ignore non-ascii characters, except arrow keys */
	    switch (ksym) {
    	        case XK_Up:
	            *killflag = 129;
		    break;
    	        case XK_Down:
	            *killflag = 130;
		    break;
    	        case XK_Left:
	            *killflag = 131;
		    break;
    	        case XK_Right:
	            *killflag = 132;
		    break;
	    }
	    return;
	}
	switch (ch) {
	    case '\014': /* ctrl-L */
		xtexted_redraw(op_AllWindows);
		break;
	    default:
		*killflag = (unsigned char)ch;
		break;
	}
    }
    else {
	cmdentry *command;
	int val, which, keynum, op;
	if (!ix) {
	    val = (ksym & 255);
	    which = keytype_sym;
	}
	else {
	    if (escapemode || (event->state & Mod1Mask)) {
		escapemode = FALSE;
		val = (ch & 255);
		which = keytype_meta;
	    }
	    else {
		val = (ch & 255);
		which = keytype_main;
	    }
	}
	keynum = (val | which);
	command = keycmds[keynum];
	if (modifymode != op_Cancel && !(command && command->ignoremods)) {
	    op = modifymode;
	    modifymode = op_Cancel;
	    xtexted_modify(keynum, op);
	}
	else if (!command) {
	    char buf[128];
	    char *cx;
	    cx = xkey_get_key_name(keynum);
	    sprintf(buf, "Key <%s> not bound", cx);
	    xmess_set_message(buf, FALSE);
	}
	else {
	    if (command->operand == (-1))
		op = keynum;
	    else
		op = command->operand;
	    (*(command->func))(op);
	}
    }
}

/* based on new xiowinwid, xiowinhgt */
#ifdef __STDC__
static void rearrange_window()
#else
static void rearrange_window()
#endif
{
    int botwidth = lineheight+3;
    xtext_resize(0, 0, xiowinwid, xiowinhgt-botwidth);
    xmess_resize(0, xiowinhgt-botwidth, xiowinwid, botwidth);
}

/* do one round of iteration -- getchar, getline, whatever is relevant. return -1 after timeout tenth-seconds (if timeout > 0).
 If stringmode==FALSE, return the first keystroke (immediately).
 Otherwise, do a line of input using loop_*, returning \n. */
#ifdef __STDC__
static void loop(int stringmode, int *killflag, int timeout)
#else
static void loop(stringmode, killflag, timeout)
int stringmode;
int *killflag;
int timeout;
#endif
{
    xstat_layout();
    xtext_layout(); /* the biggie; everything has to be right after this */
    xtext_end_visible(); 

    eventloop(stringmode, killflag, timeout);
    xtext_set_lastseen();
}

/* guts of the event loop */
#ifdef __STDC__
static void eventloop(int stringmode, int *killflag, int timeout)
#else
static void eventloop(stringmode, killflag, timeout)
int stringmode;
int *killflag;
int timeout;
#endif
{
    int ix, jx;
    int eventp;
    XEvent event;
    long evmasks;
    struct timeval tv, curtime, outtime;
    struct timezone tz;
    fd_set readbits;
    static unsigned int buttonhit;
    static unsigned int buttonmods;
    static int clicknum;
    static int lastclickx=(-999), lastclicky=(-999);

    if (timeout) {
	gettimeofday (&outtime, &tz);
	outtime.tv_sec += (timeout/10);
	outtime.tv_usec += ((timeout%10)*100000);
	if (outtime.tv_usec >= 1000000) {
	  outtime.tv_sec++;
	  outtime.tv_usec -= 1000000;
	}
    }

    while (1) {
	evmasks = ~(NoEventMask);
	eventp = XCheckMaskEvent(xiodpy, evmasks, &event);
	if (!eventp) {
	    tv.tv_sec = 0;
	    tv.tv_usec = TICKLENGTH;
	    FD_ZERO(&readbits);
	    FD_SET(ConnectionNumber(xiodpy), &readbits);
	    XFlush(xiodpy);
	    select(1+ConnectionNumber(xiodpy), &readbits, 0, 0, &tv);
	}
	else if (event.xany.window==xioswin) {
	    switch (event.type) {
		case ButtonPress:
		    break;
		case KeyPress:
		    xmess_check_timeout();
		    handlekey(&event.xkey, stringmode, killflag);
		    break;
		case ConfigureNotify:
		    xstat_newgeometry(event.xconfigure.x, event.xconfigure.y, event.xconfigure.width, event.xconfigure.height);
		    break;
		case Expose:
		    do {
			ix = XCheckWindowEvent(xiodpy, xioswin, ExposureMask, &event);
		    } while (ix);
		    xstat_redraw();
		    break;
	    }
	}
	else {
	    switch (event.type) {
		case ButtonPress:
		    xmess_check_timeout();
		    buttonhit = event.xbutton.button;
		    buttonmods = event.xbutton.state;
		    ix = lastclickx-event.xbutton.x;
		    jx = lastclicky-event.xbutton.y;
		    if (ix<=1 && ix>=(-1) && jx<=1 && jx>=(-1)) {
			clicknum++;
		    }
		    else {
			clicknum = 1;
			lastclickx = event.xbutton.x;
			lastclicky = event.xbutton.y;
		    }
		    xtext_hitdown(event.xbutton.x, event.xbutton.y, buttonhit, buttonmods, clicknum);
		    break;
		case MotionNotify:
		    xtext_hitmove(event.xbutton.x, event.xbutton.y, buttonhit, buttonmods, clicknum);
		    break;
		case ButtonRelease:
		    xtext_hitup(event.xbutton.x, event.xbutton.y, buttonhit, buttonmods, clicknum);
		    break;
		case KeyPress:
		    xmess_check_timeout();
		    handlekey(&event.xkey, stringmode, killflag);
		    break;
		case ConfigureNotify:
		    xmess_check_timeout();
		    if (event.xconfigure.width != xiowinwid || event.xconfigure.height != xiowinhgt) {
			xiowinwid = event.xconfigure.width;
			xiowinhgt = event.xconfigure.height;
			rearrange_window();
		    }
		    break;
		case Expose:
		    do {
			ix = XCheckWindowEvent(xiodpy, xiowin, ExposureMask, &event);
		    } while (ix);
		    redraw(event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height);
		    break;
		default:
		    break;
	    }
	}

	if (*killflag != (-1)) {
	    return;
	}
	if (timeout) {
	    gettimeofday (&curtime, &tz);
	    if (curtime.tv_sec > outtime.tv_sec || (curtime.tv_sec == outtime.tv_sec && curtime.tv_usec > outtime.tv_usec)) {
		/* timed out */
		*killflag = (-1);
		return;
	    }
	}
    }
}

#ifdef __STDC__
void xtexted_redraw(int op)
#else
void xtexted_redraw(op)
int op;
#endif
{
    switch (op) {
	case op_Screen:
	    redraw(0, 0, xiowinwid, xiowinhgt);
	    break;
	case op_Status:
	    xstat_redraw();
	    break;
	case op_AllWindows:
	    redraw(0, 0, xiowinwid, xiowinhgt);
	    xstat_redraw();
	    break;
    }
}

#ifdef __STDC__
void xtexted_meta(int op)
#else
void xtexted_meta(op)
int op;
#endif
{
    switch (op) {
	case op_Cancel:
	    escapemode = FALSE;
	    modifymode = op_Cancel;
	    xmess_set_message("Cancelled.", FALSE);
	    break;
	case op_DefineMacro:
	    xmess_set_message("Type a macro key to redefine.", FALSE);
	    modifymode = op_DefineMacro;
	    break;
	case op_ExplainKey:
	    xmess_set_message("Type a key to explain.", FALSE);
	    modifymode = op_ExplainKey;
	    break;
	case op_Escape:
	    escapemode = !escapemode;
	    break;
    }
}

#ifdef __STDC__
void xtexted_modify(int keynum, int op)
#else
void xtexted_modify(keynum, op)
int keynum;
int op;
#endif
{
    char buf[128];
    char *cx;
    cmdentry *command;

    switch (op) {
	case op_DefineMacro:
	    xted_define_macro(keynum);
	    break;
	case op_ExplainKey:
	    cx = xkey_get_key_name(keynum);
	    command = keycmds[keynum];
	    if (!command)
		sprintf(buf, "Key <%s> is not bound", cx);
	    else if (!keycmdargs[keynum])
		sprintf(buf, "Key <%s>: %s", cx, command->name);
	    else {
		if (strlen(keycmdargs[keynum]) < sizeof(buf) - 64)
		    sprintf(buf, "Key <%s>: %s \"%s\"", cx, command->name, keycmdargs[keynum]);
		else {
		    sprintf(buf, "Key <%s>: %s \"", cx, command->name);
		    strncat(buf, keycmdargs[keynum], sizeof(buf) - 64);
		    strcat(buf, "...\"");
		}
	    }
	    xmess_set_message(buf, FALSE);
	    break;
	default:
	    sprintf(buf, "Unknown key modifier (%d).", op);
	    xmess_set_message(buf, FALSE);
	    break;
    }
}

