/* guitcap.c */

#include "elvis.h"
#ifdef GUI_TERMCAP
#include <signal.h>


/* This file contains a termcap-based user interface.  It is derived from the
 * "curses.c" file of elvis 1.8.
 */

#define	MINHEIGHT	4

/* Some termcap packages require the application code to supply a "BC"
 * variable.  Others (particularly ncurses) forbid it.  The nice ones
 * supply one if you don't, so they'll work either way.
 */
#ifdef NEED_BC
       char	*BC;	/* :bc=: move cursor left */
#else
extern char	*BC;	/* :bc=: move cursor left */
#endif

/* HP-UX, and maybe some others, require the application code to supply
 * an "ospeed" variable.
 */
#ifdef NEED_OSPEED
# ifdef NEED_SPEED_T
	speed_t ospeed;
# else
	short	ospeed;
# endif
#endif

/* Structs of this type are used to remember the location and size of each 
 * window.  In the termcap interface, all windows must be as wide as the
 * screen, and the sum of all windows' heights must equal the screen size.
 */
typedef struct twin_s
{
	struct twin_s	*next;		/* some other window on this screen */
	int		height;		/* size of the window */
	int		pos;		/* position of the window */
	int		newheight;	/* height after screen is rearranged */
	int		newpos;		/* position after screen is rearranged */
	int		cursx, cursy;	/* logical cursor position */
	ELVCURSOR	shape;		/* logical cursor shape */

	/* The following buffers hold the escape codes that switch
	 * between fonts.  The initial values are taken from the termcap
	 * strings above, but after colors have been set they'll all
	 * contain computed strings which switch colors in addition to
	 * fonts.
	 *
	 * Note that there is no endnormal[] array.  This is because
	 * starnormal[] is used in a special way.  Before any colors are
	 * set, startnormal[] contains an empty string, and the endXXX
	 * strings point to termcap strings.  After colors have been set,
	 * though, startnormal is loaded with the escape sequence for
	 * switching to the normal colors, and the endXXX strings are
	 * loaded with copies of this (except for endunderline, which
	 * contains that string plus the regular UE termcap string).
	 */
	char	startnormal[30];
	char	startfixed[30], endfixed[30];
	char	startbold[30], endbold[30];
	char	startemph[30], endemph[30];
	char	startitalic[30], enditalic[30];
	char	startunderline[30], endunderline[30];
	char	starthilite[30], endhilite[30]; /* regardless of font */
} TWIN;


#if USE_PROTOTYPES
static BOOLEAN ansi_color(TWIN *tw, _char_ font, char *fgname, char *bgname);
static BOOLEAN clrtoeol(GUIWIN *gw);
static BOOLEAN color(GUIWIN *gw, _char_ font, CHAR *fg, CHAR *bg);
static BOOLEAN creategw(char *name,char * attributes);
static BOOLEAN focusgw(GUIWIN *gw);
static BOOLEAN scroll(GUIWIN *gw, int qty, BOOLEAN notlast);
static BOOLEAN shift(GUIWIN *gw, int qty, int rows);
static BOOLEAN tabcmd(GUIWIN *gw, _CHAR_ key2, long count);
static char *manynames(char *names);
static int init(int argc, char **argv);
static int keylabel(CHAR *given, int givenlen, CHAR **label, CHAR **rawptr);
static int test(void);
static int ttych(int ch);
static void beep(GUIWIN *gw);
static void destroygw(GUIWIN *gw, BOOLEAN force);
static void drawgraphic(GUIWIN *gw, _char_ font, CHAR *text, int len);
static void draw(GUIWIN *gw, _char_ font, CHAR *text, int len);
static void drawborder(TWIN *tw);
static void endtcap(void);
static void flush(void);
static void loop(void);
static void mayhave(char **T, char *s);
static void movecurs(TWIN *tw);
static void moveto(GUIWIN *gw, int column, int row);
static void musthave(char **T, char *s);
static void pair(char **T, char **U, char *sT, char *sU);
static void revert(TWIN *tw);
static void starttcap(void);
static void term(void);
static void ttyflush(void);
static void ttygetsize(void);
static BOOLEAN ttyprgopen(char *command, BOOLEAN willwrite, BOOLEAN willread);
static int ttyprgclose(void);
static RESULT stop(BOOLEAN alwaysfork);
#endif

static void reset P_((void));
static void chgsize P_((TWIN *tw, int newheight, BOOLEAN winch));
static void cursorshape P_((ELVCURSOR shape));

/* termcap values */
static BOOLEAN	AM;		/* :am:  boolean: auto margins? */
static BOOLEAN	PT;		/* :pt:  boolean: physical tabs? */
       char	PC;		/* :pc=: pad character (not a string var!) */
static char	*VB;		/* :vb=: visible bell */
       char	*UP;		/* :up=: move cursor up */
static char	*SO;		/* :so=: standout start */
static char	*SE;		/* :se=: standout end */
static char	*US;		/* :us=: underline start */
static char	*UE;		/* :ue=: underline end */
static char	*MD;		/* :md=: bold start */
static char	*ME;		/* :me=: bold end */
static char	*MH;		/* :mh=: half-bright start (end with :me=:) */
static char	*CM;		/* :cm=: cursor movement */
static char	*DO;		/* :do=: move down one line */
static char	*DOmany;	/* :DO=: move down many lines */
static char	*CE;		/* :ce=: clear to end of line */
static char	*AL;		/* :al=: add a line */
static char	*ALmany;	/* :AL=: add many lines */
static char	*DL;		/* :dl=: delete a line */
static char	*DLmany;	/* :DL=: delete many lines */
static char	*SRev;		/* :sr=: scroll reverse */
static char	*KS;		/* :ks=: init string for cursor */
static char	*KE;		/* :ke=: restore string for cursor */
static char	*IC;		/* :ic=: insert following char */
static char	*ICmany;	/* :IC=: insert many characters */
static char	*DC;		/* :dc=: delete a character */
static char	*DCmany;	/* :DC=: delete many characters */
static char	*TI;		/* :ti=: terminal init */
static char	*TE;		/* :te=: terminal exit */
static char	*SC;		/* :sc=: save cursor position & attribute */
static char	*RC;		/* :rc=: restore cursor position & attribute */
static char	*CQ;		/* :cQ=: normal cursor */
static char	*CX;		/* :cX=: cursor used for EX command/entry */
static char	*CV;		/* :cV=: cursor used for VI command mode */
static char	*CI;		/* :cI=: cursor used for VI input mode */
static char	*CR;		/* :cR=: cursor used for VI replace mode */
static char	*GS;		/* :GS=:as=: start graphic character mode */
static char	*GE;		/* :GE=:ae=: end graphic character mode */
static char	GC_V;		/* vertical bar character */
static char	GC_H;		/* horizontal bar character */
static char	GC_1;		/* lower left corner character */
static char	GC_2;		/* horizontal line with up-tick character */
static char	GC_3;		/* lower right corner character */
static char	GC_4;		/* vertical line with right-tick character */
static char	GC_5;		/* center cross character */
static char	GC_6;		/* vertical line with left-tick character */
static char	GC_7;		/* upper left corner character */
static char	GC_8;		/* horizontal line with down-tick character */
static char	GC_9;		/* upper right corner character */



/* This is a table of keys which should be mapped, if present */
static struct
{
	char	*label;		/* keytop legend of the key */
	char	*capnames;	/* name(s) of the key's capability */
	char	*cooked;	/* what the key should map to (if anything) */
	MAPFLAGS flags;		/* when the map should be effective */
	char	*rawin;		/* raw characters sent by key */
}
	keys[] =
{
	{"<Up>",	"ku",		"k",	MAP_ALL},
	{"<Down>",	"kd",		"j",	MAP_ALL},
	{"<Left>",	"kl",		"h",	MAP_ALL},
	{"<Right>",	"kr",		"l",	MAP_ALL},
	{"<PgUp>",	"PUkPk2",	"\002", MAP_ALL},
	{"<PgDn>",	"PDkNk5",	"\006", MAP_ALL},
	{"<Home>",	"HMkhK1",	"^",	MAP_ALL},
	{"<End>",	"ENkHK5@7",	"$",	MAP_ALL},
	{"<Insert>",	"kI",		"i",	MAP_ALL},
	{"<Delete>",	"kD",		"x",	MAP_ALL},
	{"<Compose>",	"k+",		"\013",	MAP_INPUT},
	{"<CLeft>",	"#4KL",		"B",	MAP_ALL},
	{"<CRight>",	"%iKR",		"W",	MAP_ALL},
	{"#1",		"k1"},
	{"#2",		"k2"},
	{"#3",		"k3"},
	{"#4",		"k4"},
	{"#5",		"k5"},
	{"#6",		"k6"},
	{"#7",		"k7"},
	{"#8",		"k8"},
	{"#9",		"k9"},
	{"#10",		"k0kak;"},
	{"#1s",		"s1"},
	{"#2s",		"s2"},
	{"#3s",		"s3"},
	{"#4s",		"s4"},
	{"#5s",		"s5"},
	{"#6s",		"s6"},
	{"#7s",		"s7"},
	{"#8s",		"s8"},
	{"#9s",		"s9"},
	{"#10s",	"s0"},
	{"#1c",		"c1"},
	{"#2c",		"c2"},
	{"#3c",		"c3"},
	{"#4c",		"c4"},
	{"#5c",		"c5"},
	{"#6c",		"c6"},
	{"#7c",		"c7"},
	{"#8c",		"c8"},
	{"#9c",		"c9"},
	{"#10c",	"c0"},
	{"#1a",		"a1"},
	{"#2a",		"a2"},
	{"#3a",		"a3"},
	{"#4a",		"a4"},
	{"#5a",		"a5"},
	{"#6a",		"a6"},
	{"#7a",		"a7"},
	{"#8a",		"a8"},
	{"#9a",		"a9"},
	{"#10a",	"a0"}
};

/* These are GUI-dependent global options */
static OPTDESC goptdesc[] =
{
	{"term", "ttytype",	optsstring,	optisstring},
	{"ttyrows", "ttylines",	optnstring,	optisnumber},
	{"ttycolumns", "ttycolumns",optnstring,	optisnumber},
	{"ttyunderline", "ttyu",NULL,		NULL	   },
};
struct ttygoptvals_s ttygoptvals;
#define o_term		ttygoptvals.term.value.string
#define o_ttyrows	ttygoptvals.ttyrows.value.number
#define o_ttycolumns	ttygoptvals.ttycolumns.value.number
#define o_ttyunderline	ttygoptvals.ttyunderline.value.boolean


/*----------------------------------------------------------------------------*/
/* These are mid-level terminal I/O functions.  They buffer the output, but
 * don't do much more than that.
 */
static char ttybuf[1500];	/* the output buffer */
static int  ttycount;		/* number of characters in ttybuf */
static char ttyerasekey;	/* taken from the ioctl structure */
static char *startfont="";	/* string for starting current font */
static char *endfont="";	/* string for ending current font */
long        ttycaught;		/* bitmap of recently-received signals */

/* This function writes the contents of ttybuf() to the screen */
static void ttyflush()
{
	if (ttycount > 0)
	{
		ttywrite(ttybuf, ttycount);
		ttycount = 0;
	}
}

/* This function is used internally.  It is passed to the tputs() function
 * which uses it to output individual characters.  This function saves the
 * characters in a buffer and outputs them in a bunch.
 */
static int ttych(ch)
	int	ch;	/* character to be output */
{
	ttybuf[ttycount++] = ch;
	if (ttycount >= QTY(ttybuf))
		ttyflush();
#ifndef NDEBUG
	ttybuf[ttycount] = '\0';
#endif
	return ch;
}


/* Revert to the normal font for a given window... or just end any special
 * fonts if no window is given.
 */
static void revert(tw)
	TWIN	*tw;	/* window whose normal font is to be used, or NULL */
{
	/* revert to the normal font */
	if (*endfont)
	{
		tputs(endfont, 0, ttych);
		startfont = endfont = "";
	}

	/* If a window is specified, and its normal font string isn't empty,
	 * then output it.
	 */
	if (tw && tw->startnormal[0] != '\0')
	{
		tputs(tw->startnormal, 0, ttych);
	}
}


/* Send any required termination strings.  Turn off "raw" mode. */
void ttysuspend()
{
	/* revert to the normal font */
	revert(NULL);

	/* restore some things */
	if (CQ) tputs(CQ, 1, ttych);	/* restore cursor shape */
	if (TE) tputs(TE, 1, ttych);	/* restore terminal mode/page */
	if (KE) tputs(KE, 1, ttych);	/* restore keypad mode */
	if (RC) tputs(RC, 1, ttych);	/* restore cursor & attributes */

	/* Move the cursor to the bottom of the screen */
	tputs(tgoto(CM, 0, (int)o_ttyrows - 1), 0, ttych);
	ttych('\n');
	ttyflush();

	/* change the terminal mode back the way it was */
	ttynormal();
}

/* Put the terminal in RAW mode.  Send any required strings */
void ttyresume(sendstr)
	BOOLEAN	sendstr;	/* send strings? */
{
	/* change the terminal mode to cbreak/noecho */
	ttyerasekey = ELVCTRL('H');/* the default */
	ttyraw(&ttyerasekey);

	/* send the initialization strings */
	if (sendstr)
	{
		ttych('\r');
		tputs(CE, (int)o_ttycolumns, ttych);
		if (TI) tputs(TI, 1, ttych);
		if (KS) tputs(KS, 1, ttych);
	}

	/* reset, so we don't try any suspicious optimizations */
	reset();
}

/* This function determines the screen size.  It does this by calling the
 * OS-dependent ttysize() function if possible, or from the termcap entry
 * otherwise.
 */
static void ttygetsize()
{
	int	lines;
	int	cols;
	char	*tmp;

	/* get the window size, one way or another. */
	lines = cols = 0;
	if (!ttysize(&lines, &cols) || lines < 2 || cols < 30)
	{
		/* get lines from $LINES or :li#: */
		tmp = getenv("LINES");
		if (tmp)
			lines = atoi(tmp);
		else
			lines = tgetnum("li");
		if (lines <= 0)
			lines = 24;

		/* get columns from $COLUMNS or :co#: */
		tmp = getenv("COLUMNS");
		if (tmp)
			cols = atoi(tmp);
		else
			cols = tgetnum("co");
		if (cols <= 0)
			cols = 80;
	}

	/* did we get a realistic value? */
	if (lines >= 2 && cols >= 30)
	{
		o_ttyrows = lines;
		o_ttycolumns = cols;
	}
}


/* end of low-level terminal control */
/*----------------------------------------------------------------------------*/
/* start of termcap operations */

static char	*capbuf;/* used for allocation space for termcap strings */

/* This function fetches an optional string from termcap */
static void mayhave(T, s)
	char	**T;	/* where to store the returned pointer */
	char	*s;	/* name of the capability */
{
	char	*val;

	val = tgetstr(s, &capbuf);
	if (val)
	{
		*T = val;
	}
}


/* This function fetches a required string from termcap */
static void musthave(T, s)
	char	**T;	/* where to store the returned pointer */
	char	*s;	/* name of the capability */
{
	mayhave(T, s);
	if (!*T)
	{
		msg(MSG_FATAL, "[s]termcap needs $1", s);
	}
}


/* This function fetches a pair of strings from termcap.  If one of them is
 * missing, then the other one is ignored.
 */
static void pair(T, U, sT, sU)
	char	**T;	/* where to store the first pointer */
	char	**U;	/* where to store the second pointer */
	char	*sT;	/* name of the first capability */
	char	*sU;	/* name of the second capability */
{
	mayhave(T, sT);
	mayhave(U, sU);
	if (!*T || !*U)
	{
		*T = *U = "";
	}
}

/* This function gets a single termcap string in a special static buffer.
 * Returns the string if successful, or NULL if unsuccessful.
 */
static char *manynames(names)
	char	*names; /* possible names (each pair of chars is one name) */
{
	char	name[3];
	int	i;
	char	*value;

	/* for each possible name... */
	for (i = 0; names[i]; i += 2)
	{
		/* see if the termcap string can be found */
		name[0] = names[i];
		name[1] = names[i + 1];
		name[2] = '\0';
		value = tgetstr(name, &capbuf);
		if (value)
		{
			/* found! */
			return value;
		}
	}
	return NULL;
}


/* get termcap values */
static void starttcap()
{
	static char	cbmem[800];
	char		*str;
	int		i;

	/* make sure TERM variable is set */
	o_term = toCHAR(ttytermtype());
	if (!o_term)
	{
		o_term = toCHAR("unknown");
	}
	optflags(o_term) |= OPT_SET|OPT_LOCK|OPT_NODFLT;

	/* allocate memory for capbuf */
	capbuf = cbmem;

	/* get the termcap entry */
	switch (tgetent(ttybuf, tochar8(o_term)))
	{
	  case -1:	msg(MSG_FATAL, "termcap database unreadable");
	  case 0:	msg(MSG_FATAL, "[S]TERM=$1 unknown", o_term);
	}

	/* get strings */
	musthave(&UP, "up");
	BC = "\b";
	mayhave(&BC, "bc");
	mayhave(&VB, "vb");
	musthave(&CM, "cm");
	DO = "\n";
	mayhave(&DO, "do");
	mayhave(&DOmany, "DO");
	mayhave(&TI, "ti");
	mayhave(&TE, "te");
	pair(&SC, &RC, "sc", "rc");	/* cursor save/restore */
	pair(&KS, &KE, "ks", "ke");	/* keypad enable/disable */
	if (tgetnum("sg") <= 0)
	{
		pair(&SO, &SE, "so", "se");
	}
	if (tgetnum("ug") <= 0)
	{
		pair(&US, &UE, "us", "ue");
		/* bor: some terminals don't have :md:, but _do_ have :me:
		   Check, that we can switch attribute off before using it
		 */
		mayhave(&ME, "me");
		if (ME && *ME)
		{
			mayhave(&MD, "md");
			mayhave(&MH, "mh");
		}
	}
	mayhave(&ICmany, "IC");
	mayhave(&IC, "ic");
	mayhave(&DCmany, "DC");
	mayhave(&DC, "dc");
	mayhave(&ALmany, "AL");
	mayhave(&AL, "al");
	mayhave(&DLmany, "DL");
	mayhave(&DL, "dl");
	musthave(&CE, "ce");
	mayhave(&SRev, "sr");

	/* cursor shapes */
	CQ = tgetstr("cQ", &capbuf);
	if (CQ)
	{
		CX = tgetstr("cX", &capbuf);
		if (!CX) CX = CQ;
		CV = tgetstr("cV", &capbuf);
		if (!CV) CV = CQ;
		CI = tgetstr("cI", &capbuf);
		if (!CI) CI = CQ;
		CR = tgetstr("cR", &capbuf);
		if (!CR) CR = CQ;
	}
	else
	{
		CQ = CV = "";
		pair(&CQ, &CV, "ve", "vs");
		CX = CI = CQ;
		CR = CV;
	}

	/* graphic characters */
	str = tgetstr("ac", &capbuf);
	if (str)
	{
		/* apparently we're using the :as=:ae=:ac=: style */
		pair(&GS, &GE, "as", "ae");
		for (i = 0; str[i] && str[i + 1]; i += 2)
		{
			switch (str[i])
			{
			  case 'q':	GC_H = str[i + 1];	break;
			  case 'x':	GC_V = str[i + 1];	break;
			  case 'm':	GC_1 = str[i + 1];	break;
			  case 'v':	GC_2 = str[i + 1];	break;
			  case 'j':	GC_3 = str[i + 1];	break;
			  case 't':	GC_4 = str[i + 1];	break;
			  case 'n':	GC_5 = str[i + 1];	break;
			  case 'u':	GC_6 = str[i + 1];	break;
			  case 'l':	GC_7 = str[i + 1];	break;
			  case 'w':	GC_8 = str[i + 1];	break;
			  case 'k':	GC_9 = str[i + 1];	break;
			}
		}
	}
	else
	{
		/* maybe we have :GH=:GV=:... strings? */
		if ((str = tgetstr("GH", &capbuf)) != NULL)	GC_H = *str;
		if ((str = tgetstr("GV", &capbuf)) != NULL)	GC_V = *str;
		if ((str = tgetstr("G3", &capbuf)) != NULL)	GC_1 = *str;
		if ((str = tgetstr("GU", &capbuf)) != NULL)	GC_2 = *str;
		if ((str = tgetstr("G4", &capbuf)) != NULL)	GC_3 = *str;
		if ((str = tgetstr("GR", &capbuf)) != NULL)	GC_4 = *str;
		if ((str = tgetstr("GC", &capbuf)) != NULL)	GC_5 = *str;
		if ((str = tgetstr("GL", &capbuf)) != NULL)	GC_6 = *str;
		if ((str = tgetstr("G2", &capbuf)) != NULL)	GC_7 = *str;
		if ((str = tgetstr("GD", &capbuf)) != NULL)	GC_8 = *str;
		if ((str = tgetstr("G1", &capbuf)) != NULL)	GC_9 = *str;
		pair(&GS, &GE, "GS", "GE");

		/* if we have no :GS=:GE=: strings, then set MSB of chars */
		if (!GS || !*GS)
		{
			if (GC_H) GC_H |= 0x80;
			if (GC_V) GC_V |= 0x80;
			if (GC_1) GC_1 |= 0x80;
			if (GC_2) GC_2 |= 0x80;
			if (GC_3) GC_3 |= 0x80;
			if (GC_4) GC_4 |= 0x80;
			if (GC_5) GC_5 |= 0x80;
			if (GC_6) GC_6 |= 0x80;
			if (GC_7) GC_7 |= 0x80;
			if (GC_8) GC_8 |= 0x80;
			if (GC_9) GC_9 |= 0x80;
		}
	}

	/* key strings */
	for (i = 0; i < QTY(keys); i++)
	{
		keys[i].rawin = manynames(keys[i].capnames);
	}

	/* other termcap stuff */
	AM = (BOOLEAN)(tgetflag("am") && !tgetflag("xn"));
	PT = (BOOLEAN)tgetflag("pt");

	/* change the terminal mode to cbreak/noecho */
	ttyinit();
	if (SC) tputs(SC, 1, ttych);
	ttyresume(True);

	/* try to get true screen size, from the operating system */
	ttygetsize();
}

static void endtcap()
{
	/* change the terminal mode back the way it was */
	ttysuspend();
}


/* end of termcap operations */
/*----------------------------------------------------------------------------*/
/* start of GUI functions */


static int	afterprg;	/* expose windows (after running prg) */
static int	afterscrl;	/* number of status lines (after running prg) */
static BOOLEAN	fgcolored;	/* have foreground colors been set? */
static BOOLEAN	bgcolored;	/* have background colors been set? */
static int	physx, physy;	/* physical cursor position */
static TWIN	*twins;		/* list of windows */
static TWIN	*current;	/* window with keyboard focus */
static TWIN	defcolors;	/* default color strings */
static int	nwindows;	/* number of windows allocated */

/*----------------------------------------------------------------------------*/
/* The following are used for replacing character attributes with colors.
 * Currently only "ansi" colors are supported.  This is the color scheme used
 * by the ANSI.SYS MS-DOS driver, and by most other PC operating systems.
 */

static struct
{
	char	*name;	/* name of the color */
	int	ansi;	/* ANSI.SYS color; if >10, then set "bold" attribute */
} colors[] =
{
	{"black",	0},
	{"red",		1},
	{"green",	2},
	{"brown",	3},
	{"blue",	4},
	{"magenta",	5},
	{"cyan",	6},
	{"white",	7},
	{"gray",	10},
	{"grey",	10},
	{"yellow",	13},
	{(char *)0}
};

static BOOLEAN ansi_color(tw, font, fgname, bgname)
	TWIN	*tw;	/* window whose colors are to be set */
	_char_	font;	/* font code, one of n/b/i/u/e/o/N */
	char	*fgname;/* foreground color */
	char	*bgname;/* background color name, or NULL */
{
	BOOLEAN	bright;	/* set the brightness bit? */
	int	fg, bg;
	char	*build;
	static	int	normbg = -1;

	/* see if we're supposed to set the brightness bit */
	bright = False;
	if (!strncmp(fgname, "light", 5)) bright = True, fgname += 5;
	if (!strncmp(fgname, "lt", 2)) bright = True, fgname += 2;
	if (!strncmp(fgname, "bright", 6)) bright = True, fgname += 6;

	/* skip leading garbage characters */
	while (*fgname && !isalpha(*fgname))
	{
		fgname++;
	}

	/* try to find the foreground color */
	for (fg = 0; colors[fg].name && strcmp(colors[fg].name, fgname); fg++)
	{
	}
	if (!colors[fg].name)
	{
		msg(MSG_ERROR, "[s]invalid color $1", fgname);
		return False;
	}
	fg = colors[fg].ansi;

	/* try to find the background color, if given */
	if (bgname && *bgname)
	{
		for (bg = 0; colors[bg].name && strcmp(colors[bg].name, bgname); bg++)
		{
		}
		if (!colors[bg].name)
		{
			msg(MSG_ERROR, "[s]invalid color $1", bgname);
			return False;
		}
		bg = colors[bg].ansi;
	}
	else /* no background specified */
	{
		/* use "normal" background color, if defined */
		bg = normbg;
	}

	/* if foreground color implies "bold", remember that! */
	if (fg >= 10)
	{
		bright = True;
		fg -= 10;
	}

	/* background can't be bold */
	if (bg >= 0)
	{
		if (colors[bg].ansi >= 10)
		{
			msg(MSG_ERROR, "background can't be bright");
			return False;
		}
	}

	/* build the string */
	switch (font)
	{
	  case 'n':	build = tw->startnormal;	break;
	  case 'f':	build = tw->startfixed;		break;
	  case 'b':	build = tw->startbold;		break;
	  case 'e':	build = tw->startemph;		break;
	  case 'i':	build = tw->startitalic;	break;
	  case 'u':	build = tw->startunderline;	break;
	  default:	build = tw->starthilite;	break;
	}
	if (bg >= 0)
	{
		sprintf(build, "\033[0;%dm\033[%s%dm",
			40 + bg,		/* background color */
			bright ? "1;" : "",	/* brightness */
			30 + fg);		/* foreground color */
		if (o_ttyunderline && US && font == 'u')
			strcat(build, US);
	}
	else /* no background specified; only affect foreground */
	{
		sprintf(build, "\033[0;%s%dm",
			bright ? "1;" : "",	/* brightness */
			30 + fg);		/* foreground color */
		if (o_ttyunderline && US && font == 'u')
			strcat(build, US);
	}

	/* if 'n' font, copy startnormal into endXXX */
	if (font == 'n')
	{
		/* but first! if this the first color setting, then choose
		 * defaults for all other attributes.
		 */
		if (!fgcolored || (bg >= 0 && !bgcolored))
		{
			/* If we have a background color... */
			if (bg >= 0)
			{
				/* if normal is bright, then bold must be bright white */
				if (bright)
				{
					sprintf(tw->startbold, "\033[0;%dm\033[1;37m", 40 + bg);
				}
				else
				{
					sprintf(tw->startbold, "\033[0;%dm\033[1;%dm", 40 + fg, 30 + bg);
				}

				/* emphasized is same as bold */
				strcpy(tw->startemph, tw->startbold);

				/* italic is a dim version of normal */
				sprintf(tw->startitalic, "\033[0;%d;%dm", 30 + fg, 40 + bg);

				/* underline is dim underlined version of normal */
				sprintf(tw->startunderline, "\033[0;%d;%dm%s",
					30 + fg, 40 + bg, (o_ttyunderline && US) ? US : "");

				/* reverse video swaps foreground & background; always dim */
				sprintf(tw->starthilite, "\033[0;%d;%dm", 30 + bg, 40 + fg);

				/* other font is same as normal */
				strcpy(tw->startfixed, tw->startnormal);
			}
			else /* no background color */
			{
				/* if normal is bright, then bold must be bright white */
				if (bright)
				{
					strcpy(tw->startbold, "\033[0;1;37m");
				}
				else
				{
					sprintf(tw->startbold, "\033[0;1;%dm", 30 + fg);
				}

				/* emphasized is same as bold */
				strcpy(tw->startemph, tw->startbold);

				/* italic is a dim version of normal */
				sprintf(tw->startitalic, "\033[0;%dm", 30 + fg);

				/* underline is dim underlined version of normal */
				sprintf(tw->startunderline, "\033[0;%dm%s",
					30 + fg, (o_ttyunderline && US) ? US : "");

				/* reverse video is reverse+normal colors */
				sprintf(tw->starthilite, "\033[0;%d;7m", 30 + fg);

				/* other font is same as normal */
				strcpy(tw->startfixed, tw->startnormal);
			}
		}

		/* remember the new background color (if any) */
		normbg = bg;

		/* copy the normal string to each font's endXXX string */
		strcpy(tw->endbold, tw->startnormal);
		strcpy(tw->endemph, tw->startnormal);
		strcpy(tw->enditalic, tw->startnormal);
		*tw->endunderline = '\0';
			if (UE) strcpy(tw->endunderline, UE);
			strcat(tw->endunderline, tw->startnormal);
		strcpy(tw->endhilite, tw->startnormal);
		strcpy(tw->endfixed, tw->startnormal);
	}

	/* success! */
	return True;
}

/* This is an internal function which moves the physical cursor to the logical
 * position of the cursor in a given window, if it isn't there already.
 */
static void movecurs(tw)
	TWIN	*tw;	/* window whose cursor is to be moved */
{
	int	y = tw->pos + tw->cursy;
	int	i;

	/* maybe we don't need to move at all? */
	/* JohnW 13/08/96 > 1 was > 0 */
	if ((afterprg > 1 && y <= o_ttyrows - afterscrl)
		|| (y == physy && tw->cursx == physx))
	{
		/* already there */
		return;
	}

        if (afterprg == 1)  /* JohnW 13/08/96 - only use the bottom line */
        {
		/* We get the 'bottom' line by adding the difference between
		 * the window's bottom line and the screen's bottom line. This
		 * then allows scrolling of an input line and moving back onto
		 * the penultimate line */
		tputs(tgoto(CM, tw->cursx, y + ((int)o_ttyrows - (tw->pos + tw->height))), 1, ttych);
                physx = tw->cursx;
                return;
	}
	else
	/* Try some simple alternatives to the CM string */
	if (y >= physy
	 && (y - physy < gui->movecost || DOmany)
	 && (tw->cursx == 0 || tw->cursx == physx))
	{
		/* output a bunch of newlines, and maybe a carriage return */
		if (y - physy > 1 && DOmany)
			tputs(tgoto(DOmany, y - physy, y - physy), 1, ttych);
		else
			for (i = y - physy; i > 0; i--)
				tputs(DO, 1, ttych);
		if (tw->cursx != physx)
			ttych('\r');
	}
	else if (y == physy && tw->cursx < physx && physx - tw->cursx < gui->movecost)
	{
		/* output a bunch of backspaces */
		for (i = physx - tw->cursx; i > 0; i--)
			tputs(BC, 0, ttych);
	}
	/* many other special cases could be handled here */
	else
	{
		/* revert to the normal font */
		revert(tw);

		/* move it the hard way */
		tputs(tgoto(CM, tw->cursx, y), 1, ttych);
	}

	/* done! */
	physx = tw->cursx;
	physy = tw->cursy + tw->pos;
}

/* clear to end of line */
static BOOLEAN clrtoeol(gw)
	GUIWIN	*gw;	/* window whose row is to be cleared */
{
	TWIN	*tw = (TWIN *)gw;

	/* after running a program, disable the :ce: string for a while. */
	/* JohnW 13/08/96 Not disabled for the bottom line of window */
	if (afterprg && tw->cursy != (int)tw->height - 1)
		return True;

	/* if we're on the bottom row of a window which doesn't end at the
	 * bottom of the screen, then fail.  This will cause elvis to output
	 * a bunch of spaces instead.  The draw() function will convert those
	 * spaces to underscore characters so the window has a border.
	 */
	if ( !afterprg && /* JohnW 13/08/96 Not after a prog... */
	    tw->cursy == tw->height - 1 && tw->pos + tw->height != o_ttyrows)
	{
		return False;
	}

	/* revert to the normal font */
	revert(tw);

	/* move the physical cursor to where the window thinks it should be */
	movecurs(tw);

	/* output the clear-to-eol string */
	tputs(CE, (int)(o_ttycolumns - tw->cursx), ttych);

	return True;
}

/* insert or delete columns */
static BOOLEAN shift(gw, qty, rows)
	GUIWIN	*gw;	/* window to be shifted */
	int	qty;	/* columns to insert (may be negative to delete) */
	int	rows;	/* number of rows affected (always 1 for this GUI) */
{
	/* revert to the normal font */
	revert((TWIN *)gw);

	/* move the physical cursor to where this window thinks it is */
	movecurs((TWIN *)gw);

	if (qty > 0)
	{
		/* can we do many at once? */
		if (qty > 1 && ICmany)
		{
			tputs(tgoto(ICmany, qty, qty), 1, ttych);
		}
		else if (IC)
		{
			for (; qty > 0; qty--)
			{
				tputs(IC, 1, ttych);
			}
		}
		else
		{
			/* don't know how to insert */
			return False;
		}
	}
	else
	{
		/* take the absolute value of qty */
		qty = -qty;

		/* can we do many deletions at once? */
		if (qty > 1 && DCmany)
		{
			tputs(tgoto(DCmany, qty, qty), 1, ttych);
		}
		else if (DC)
		{
			for (; qty > 0; qty--)
			{
				tputs(DC, 1, ttych);
			}
		}
		else
		{
			/* don't know how to delete */
			return False;
		}
	}
	return True;
}

/* insert or delete rows.  qty is positive to insert, negative to delete */
static BOOLEAN scroll(gw, qty, notlast)
	GUIWIN	*gw;	/* window to be scrolled */
	int	qty;	/* rows to insert (may be nagative to delete) */
	BOOLEAN notlast;/* if True, then leave last row unchanged */
{
	TWIN	*tw = (TWIN *)gw;
	char	*op;

	/* Mentally adjust the number of rows used for messages.  This is only
	 * significant immediately after running an external program, and is
	 * used for hiding any premature attempts to redraw the window's text
	 * but still show the window's messages.
	 */
	afterscrl -= qty;

	/* If this window isn't the only window, then fail.
	 * Later, this function may be smart enough to use scrolling regions,
	 * or do the idlok() kind of thing, but not yet.
	 */
	if (twins->next)
	{
		return False;
	}

	/* revert to the normal font */
	revert(tw);

	/* move the physical cursor to where the window thinks it should be */
	movecurs(tw);

	if (qty > 0)
	{
		/* we'll be inserting.  Can we do it all at once? */
		if (ALmany && !(AL && qty == 1))
		{
			/* all at once */
			tputs(tgoto(ALmany, qty, qty), tw->height - tw->cursy, ttych);
		}
		else
		{
			/* try to use SRev */
			op = (tw->cursy == 0 && tw->pos == 0 && SRev) ? SRev : AL;

			/* if we don't know how to do this, we're screwed */
			if (!op || qty > o_ttyrows / 2)
			{
				return False;
			}

			/* a bunch of little insertions */
			while (qty > 0)
			{
				tputs(op, tw->height - tw->cursy, ttych);
				qty--;
			}
		}
	}
	else
	{
		/* take the absolute value of qty */
		qty = -qty;

		/* we'll be deleting.  Can we do it all at once? */
		if (DLmany && !(DL && qty == 1))
		{
			/* all at once */
			tputs(tgoto(DLmany, qty, qty), tw->height - tw->cursy, ttych);
		}
		else
		{
			/* try to use newline */
			op = DL; /* but don't try very hard, for now! */

			/* if we don't know how to do this, we're screwed */
			if (!op || qty > o_ttyrows / 2)
			{
				return False;
			}

			/* a bunch of little deletions */
			while (qty > 0)
			{
				tputs(op, tw->height - tw->cursy, ttych);
				qty--;
			}
		}
	}
	return True;
}

/* Forget where the cursor is, and which mode we're in */
static void reset P_((void))
{
	physx = physy = 9999;
	revert(&defcolors);
}


/* Flush any changes out to the display */
static void flush P_((void))
{
	if (current)
	{
		movecurs(current);
	}
	ttyflush();
}

/* Set a window's cursor position. */
static void moveto(gw, column, row)
	GUIWIN	*gw;	/* window whose cursor is to be moved */
	int	column;	/* new column of cursor */
	int	row;	/* new row of cursor */
{
	assert(twins);
	((TWIN *)gw)->cursx = column;
	((TWIN *)gw)->cursy = row;
}


/* put graphic characters.  This function is called only from draw() */
static void drawgraphic(gw, font, text, len)
	GUIWIN	*gw;	/* window where text should be drawn */
	_char_	font;	/* font to use for drawing this text - 'g' or 'G' */
	CHAR	*text;	/* plain chars to be mapped to graphic chars */
	int	len;	/* length of text */
{
	TWIN	*tw = (TWIN *)gw;
	int	i;
	BOOLEAN	graf;
	char	gc;

	/* if this is supposed to be hilighted, and we aren't already in
	 * standout mode, then switch to standout mode now.
	 */
	if (font == 'G' && startfont != tw->starthilite)
	{
		/* end the previous */
		if (*endfont)
			tputs(endfont, 1, ttych); 
			
		/* start the new one */
		if (*tw->starthilite)
			tputs(tw->starthilite, 1, ttych);

		/* remember the font */
		startfont = tw->starthilite;
		endfont = tw->endhilite;
	}
	else if (font == 'g' && *endfont)
	{
		/* end the previous */
		tputs(endfont, 1, ttych);
		startfont = endfont = "";
	}

	/* draw each character */
	for (graf = False, i = 0; i < len; i++)
	{
		/* try to convert plain character to graphic character */
		switch (text[i])
		{
		  case '-':	gc = GC_H;	break;
		  case '|':	gc = GC_V;	break;
		  case '1':	gc = GC_1;	break;
		  case '2':	gc = GC_2;	break;
		  case '3':	gc = GC_3;	break;
		  case '4':	gc = GC_4;	break;
		  case '5':	gc = GC_5;	break;
		  case '6':	gc = GC_6;	break;
		  case '7':	gc = GC_7;	break;
		  case '8':	gc = GC_8;	break;
		  case '9':	gc = GC_9;	break;
		  default:	gc = 0;
		}
		
		/* did we get a graphic character? */
		if (gc)
		{
			/* output the graphic character in graphic mode */
			if (!graf && *GS)
			{
				tputs(GS, 1, ttych);
				graf = True;
			}
			ttych(gc);
		}
		else
		{
			/* output elvis' plain character in text mode */
			if (graf)
			{
				tputs(GE, 1, ttych);
				graf = False;
			}
			if (isdigit(text[i]))
				ttych('+');
			else
				ttych(text[i]);
		}
	}

	/* if still in graphic mode, then revert to text mode now */
	if (graf && GE)
	{
		tputs(GE, 1, ttych);
	}

	/* drawing the characters has the side-effect of moving the cursor */
	tw->cursx += len;
	physx += len;
	if (physx == o_ttycolumns && AM)
	{
		physx = 0;
		physy++;
	}
}


/* put characters: first move, then set attribute, then execute char.
 */
static void draw(gw, font, text, len)
	GUIWIN	*gw;	/* window where text should be drawn */
	_char_	font;	/* font to use for drawing this text */
	CHAR	*text;	/* text to draw */
	int	len;	/* length of text */
{
	TWIN	*tw = (TWIN *)gw;
	char	*startf, *endf; /* font control strings */
	int	i;
#ifndef NDEBUG
	TWIN	*scan;

	for (scan = twins; scan != tw && scan; scan = scan->next)
	{
	}
	assert(scan);
#endif

	/* After a program, don't output any text except messages for a while.
	 * This is mostly an optimization; the window is about to be redrawn
	 * from scratch anyway.  But it also prevents the screen from doing
	 * strange, unexpected things.
	 */
	if (afterprg > 0)
	{
		if (tw->cursy < tw->height - afterscrl - 1)
			return;
		else if (tw->cursx > 0 && afterprg == 2) /* JohnW not for 1 */
		{
			ttych('\r');
			ttych('\n');
			ttyflush();
			font = 'n';
		}
	}

	/* if this terminal has :am: automargins (without :xm:), then we
	 * must never draw a character in the last column of the last row.
	 */
	if (AM
		&& tw->pos + tw->cursy == o_ttyrows - 1
		&& tw->cursx + len >= o_ttycolumns)
	{
		len = o_ttycolumns - 1 - tw->cursx;
		if (len <= 0)
		{
			return;
		}
	}

	/* move the cursor to where this window thinks it is */
	movecurs(tw);

	/* if graphic characters, then handle specially */
	if (font == 'g' || font == 'G')
	{
		drawgraphic(gw, font, text, len);
		return;
	}

	/* If we're on the bottom row of a window (except the last window)
	 * then any normal characters should be underlined.  This will give
	 * us the effect of a window border.
	 */
	if (tw->cursy == tw->height - 1 && physy < o_ttyrows - 1 && !isupper(font))
	{
		font = 'u';
	}

	/* find the font strings */
	if (isupper(font))
	{
		startf = tw->starthilite;
		endf = tw->endhilite;
	}
	else
	{
		switch (font)
		{
		  case 'b':	startf = tw->startbold; endf = tw->endbold; break;
		  case 'e':	startf = tw->startemph; endf = tw->endemph; break;
		  case 'i':	startf = tw->startitalic; endf = tw->enditalic; break;
		  case 'u':	startf = tw->startunderline; endf = tw->endunderline; break;
		  case 'f':	startf = tw->startfixed; endf = tw->endfixed; break;
			/* 'g' is handled separately, above */
		  case 'p':	startf = tw->starthilite; endf = tw->endhilite; break;
		  default:	startf = endf = "";
		}
	}

	/* if font is different, then change it */
	if ((startf != startfont && (*startf != '\0' || *startfont != '\0'))
	 || (endf != endfont && (*endf != '\0' || *endfont != '\0')))
	{
		/* end the previous */
		if (*endfont)
			tputs(endfont, 1, ttych); 

		/* start the new one */
		if (*startf)
			tputs(startf, 1, ttych);

		/* remember the font */
		startfont = startf;
		startfont = endfont = endf;
	}

	/* draw each character.  If this is the bottom row of any window except
	 * the bottom window, then also replace any blanks with '_' characters.
	 * This will provide a window border in case the terminal can't do
	 * real underlining.
	 */
	if (tw->cursy == tw->height - 1 && physy < o_ttyrows - 1 && !isupper(font))
	{
		for (i = 0; i < len; i++)
		{
			ttych(text[i] == ' ' ? '_' : text[i]);
		}
	}
	else /* normal row */
	{
		for (i = 0; i < len; i++)
		{
			ttych(text[i]);
		}
	}

	/* drawing the characters has the side-effect of moving the cursor */
	tw->cursx += len;
	physx += len;
	if (physx == o_ttycolumns && AM)
	{
		physx = 0;
		physy++;
	}
}



/* return True if termcap is available. */
static int test P_((void))
{
	return ttytermtype() ? 1 : 0;
}


/* initialize the PC BIOS interface. */
static int init(argc, argv)
	int	argc;	/* number of command-line arguments */
	char	**argv;	/* values of command-line arguments */
{
	int	i;

	/* add the global options to the list known to :set */
	o_ttyunderline = True;
	optinsert("tcap", QTY(goptdesc), goptdesc, &ttygoptvals.term);

	/* initialize the termcap stuff */
	starttcap();

	/* pretend the cursor is in an impossible place, so we're guaranteed
	 * to move it on the first moveto() or draw() call.
	 */
	physx = physy = -100;

	/* map the arrow keys */
	for (i = 0; i < QTY(keys); i++)
	{
		if (keys[i].cooked && keys[i].rawin)
		{
			mapinsert(toCHAR(keys[i].rawin), (int)strlen(keys[i].rawin),
				toCHAR(keys[i].cooked), (int)strlen(keys[i].cooked),
				toCHAR(keys[i].label),
				keys[i].flags);
		}
	}

	return argc;
}

/* change the shape of the cursor */
static void cursorshape(shape)
	ELVCURSOR	shape;	/* desired cursor shape */
{
	char		*esc;		/* desired shape */
 static char		*prevesc;	/* current shape */

	/* find the desired shape's escape sequence */
	switch (shape)
	{
	  case CURSOR_INSERT:	esc = CI;	break;
	  case CURSOR_REPLACE:	esc = CR;	break;
	  case CURSOR_COMMAND:	esc = CV;	break;
	  case CURSOR_QUOTE:	esc = CX;	break;
	  default:		esc = CQ;	break;
	}

	/* output it, if changed and non-empty */
	if (esc != prevesc && *esc)
	{
		tputs(esc, 1, ttych);
		prevesc = esc;
	}
}

/* Repeatedly get events (keystrokes), and call elvis' event functions */
static void loop P_((void))
{
	char	buf[20];
	int	len;
	int	timeout = 0;
	MAPSTATE mst = MAP_CLEAR;
	TWIN	*scan;

	while (twins)
	{
		/* reset the ttycaught bitmap */
		ttycaught = 0;

		/* if no window is current, then make the newest current */
		if (!current)
		{
			current = twins;
		}

		/* redraw the window(s) */
		{
			/* redraw each window; the current one last */
			for (scan = twins; scan; scan = scan->next)
			{ 
				if (scan != current)
				{
					scan->shape = eventdraw((GUIWIN *)scan);
				}
			}
			current->shape = eventdraw((GUIWIN *)current);
			movecurs(current);

			/* make the cursor be this window's shape */
			cursorshape(current->shape);
		}

		/* choose a timeout value */
		switch (mst)
		{
		  case MAP_CLEAR:	timeout = 0;		break;
		  case MAP_USER:	timeout = o_usertime;	break;
		  case MAP_KEY:		timeout = o_keytime;	break;
		}

		/* read events */
		ttyflush();
		len = ttyread(buf, sizeof buf, timeout);

		/* process keystroke data */
		if (len == -2)
		{
			/* ttyread() itself did something.  We don't need to
			 * do anything except the usual screen updates.
			 */
		}
		else if (len == -1)
		{
			/* Maybe the screen was resized?  Get new size */
			ttygetsize();

			/* Resize the windows to match the new screen.  The
			 * easiest way to do this is to "change" the size of the
			 * current window to its original size and force the
			 * other windows to compensate.  If there is only one
			 * window, then should be resized to the screen size.
			 */
			chgsize(current, twins->next ? current->height : (int)o_ttyrows, True);
		}
		else
		{
			/* If this is the erase key (as specified by the
			 * serial port's configuration) then make sure elvis
			 * sees it as a backspace character.
			 */
			if (len == 1 && buf[0] == ttyerasekey)
				buf[0] = '\b';

			mst = eventkeys((GUIWIN *)current, toCHAR(buf), len);

			/* if first keystroke after running an external
			 * program, then we need to expose every window.
			 *
			 * JohnW 13/08/96 - try to sort out keeping program
			 * output on screen until vi mode is entered again.
                         * We have some program output on the screen and we
                         * may want to keep looking at it while we do another
                         * command, so don't do a big refresh until we're
                         * out of bottom-line mode
			 *
			 * Steve 9/12/96 - switched it back, partially, because
			 * moving the ttyresume() call to the afterprg==2
			 * state caused problems with terminals which switch
			 * screens.
			 */
			if (afterprg == 1 && /* JohnW added next bit */
				  windefault && windefault->state &&
				  !(windefault->state->flags & ELVIS_BOTTOM))
			{
				/* reset the flag BEFORE exposing windows,
				 * or else the eventexpose() won't work right.
				 */
				afterprg = 0;
				ttyresume(True);
				for (scan = twins; scan; scan = scan->next)
				{ 
					eventexpose((GUIWIN *)scan, 0, 0,
					    scan->height - 1, (int)(o_ttycolumns - 1));
				}
			}
			else if (afterprg == 2)
			{
				/* it became 2 while processing the earlier
				 * keystrokes.  Set it to 1 now, so we'll
				 * read one more keystroke before exposing
				 * all the windows.
				 */
				ttyresume(False);
				afterprg = 1;
			}
		}
	}
}

/* shut down the termcap interface */
static void term P_((void))
{
	cursorshape(CURSOR_NONE);
	endtcap();
}


/* This draws a bunch of underscores on the physical screen on the bottom
 * row of a window, if that window doesn't end at the bottom of the screen.
 * This should be called after a window is resized or moved.
 */
static void drawborder(tw)
	TWIN	*tw;	/* window whose border needs to be redrawn */
{
	int	col;

	/* if this window ends at the bottom of the screen, then do nothing */
	if (tw->pos + tw->height == o_ttyrows)
	{
		return;
	}

	/* move the physical cursor to the bottom of the window */
	tw->cursx = 0;
	tw->cursy = tw->height - 1;
	movecurs(tw);
	if (startfont != tw->startunderline)
	{
		if (*endfont)
		{
			tputs(endfont, 1, ttych);
		}
		startfont = tw->startunderline;
		endfont = tw->endunderline;
		tputs(startfont, 1, ttych);
	}
	for (col = 0; col < o_ttycolumns; col++)
	{
		ttych('_');
	}

	/* figure out where the physical cursor would be after that */
	if (AM)
	{
		physy++;
	}
	else
	{
		physx = o_ttycolumns - 1;
	}
}

/* This function changes the height of a given window.  The total heights of
 * all windows must be o_ttyrows and the minimum height of each window is
 * MINHEIGHT.
 */
static void chgsize(tw, newheight, winch)
	TWIN	*tw;		/* window to be resized */
	int	newheight;	/* desired height of window */
	BOOLEAN	winch;		/* Did the whole screen change size? */
{
	TWIN	*scan;
	int	pos;
	int	otherheight;
	int	oldheight;
	int	toosmall;

	/* if the current window can't be as large as requested, then reduce
	 * the requested size.
	 */
	if ((nwindows - 1) * MINHEIGHT + newheight > o_ttyrows)
	{
		newheight = o_ttyrows - (nwindows - 1) * MINHEIGHT;
	}

	/* if window is already the requested height, we're done */
	if (tw->height == newheight && !winch)
	{
		return;
	}

	/* Set the size of the current window.  Also, adjust the sizes of other
	 * windows, and maybe their positions.  If any window other than the
	 * requested one is moved, expose it.  If any window other than the
	 * requested one is resized, then resize it.
	 */
	toosmall = 0;
	do
	{
		for (oldheight = tw->height, pos = 0, scan = twins;
		     scan;
		     pos += scan->newheight, scan = scan->next)
		{
			/* the requested window? */
			if (scan == tw)
			{
				/* yes, set it */
				scan->newpos = pos;
				scan->newheight = newheight;
			}
			else
			{
				/* no, some other window */

				/* compute the size that this window should be */
				if (!scan->next)
				{
					scan->newheight = o_ttyrows - pos;
					toosmall = MINHEIGHT - scan->newheight;
				}
				else if (scan->next == tw && !scan->next->next)
				{
					scan->newheight = o_ttyrows - pos - newheight;
					toosmall = MINHEIGHT - scan->newheight;
				}
				else
				{
					if (winch)
						otherheight = MINHEIGHT;
					else
						otherheight = scan->height * (o_ttyrows - newheight) / (o_ttyrows - oldheight);
					if (otherheight < MINHEIGHT)
					{
						scan->newheight = MINHEIGHT;
					}
					else
					{
						scan->newheight = otherheight - toosmall;
						if (scan->newheight < MINHEIGHT)
						{
							scan->newheight = MINHEIGHT;
						}
						toosmall -= otherheight - scan->newheight;
					}
				}
				scan->newpos = pos;
			}
		}
	} while (toosmall > 0);

	/* resize/redraw the windows, as necessary */
	for (scan = twins; scan; scan = scan->next)
	{
		/* set the size & position of this window.  If its
		 * size has changed then resize the window; else if
		 * its position has changed resize the window.
		 */
		if (scan == tw && !winch)
		{
			/* just remember new stats.  Calling function will
			 * call eventredraw() or eventdraw(), as necessary.
			 */
			scan->height = scan->newheight;
			scan->pos = scan->newpos;
		}
		else if (scan->newheight != scan->height || winch)
		{
			scan->height = scan->newheight;
			scan->pos = scan->newpos;
			if (scan->pos + scan->height < o_ttyrows)
			{
				drawborder(scan);
			}
			else
			{
				/* draw the border the hard way: erase last row */
				if (*endfont)
				{
					tputs(endfont, 1, ttych);
					startfont = endfont = "";
				}
				physy = o_ttyrows - 1;
				physx = 0;
				tputs(tgoto(CM, physx, physy), 1, ttych);
				tputs(CE, 1, ttych);
			}
			eventresize((GUIWIN *)scan, scan->height, (int)o_ttycolumns);
		}
		else if (scan->newpos != scan->pos)
		{
			scan->pos = scan->newpos;
			drawborder(scan);
			eventexpose((GUIWIN *)scan, 0, 0, scan->height - 1, (int)(o_ttycolumns - 1));
		}
	}
}


/* This function creates a window */
static BOOLEAN creategw(name, firstcmd)
	char	*name;		/* name of new window's buffer */
	char	*firstcmd;	/* first command to run in window */
{
	TWIN	*newp;

	/* if we don't have room for any more windows, then fail */
	if (o_ttyrows / (nwindows + 1) < MINHEIGHT)
	{
		return False;
	}

	/* create a window */
	newp = (TWIN *)safealloc(1, sizeof(TWIN));

	/* initialize the window */
	if (twins)
	{
		newp->height = 0;
		newp->pos = o_ttyrows;
	}
	else
	{
		newp->height = o_ttyrows;
		newp->pos = 0;
	}
	newp->cursx = newp->cursy = 0;
	newp->shape = CURSOR_NONE;
	if (fgcolored)
	{
		/* copy font-switch strings from current window */
		strcpy(newp->startnormal, defcolors.startnormal);
		strcpy(newp->startbold, defcolors.startbold);
		strcpy(newp->endbold, defcolors.endbold);
		strcpy(newp->startitalic, defcolors.startitalic);
		strcpy(newp->enditalic, defcolors.enditalic);
		strcpy(newp->startunderline, defcolors.startunderline);
		strcpy(newp->endunderline, defcolors.endunderline);
		strcpy(newp->starthilite, defcolors.starthilite);
		strcpy(newp->endhilite, defcolors.endhilite);
	}
	else
	{
		/* set the initial font-switch strings from termcap strings */
		if (MD) strcpy(newp->startbold, MD), strcpy(newp->endbold, ME);
		if (US) strcpy(newp->startunderline, US), strcpy(newp->endunderline, UE);
		if (MH) strcpy(newp->startitalic, MH), strcpy(newp->enditalic, ME);
		if (SO) strcpy(newp->starthilite, SO), strcpy(newp->endhilite, SE);
	}
	strcpy(newp->startfixed, defcolors.startfixed);
	strcpy(newp->endfixed, defcolors.endfixed);
	strcpy(newp->startemph, defcolors.startemph);
	strcpy(newp->endemph, defcolors.endemph);

	/* insert the new window into the list of windows */
	newp->next = twins;
	twins = newp;
	nwindows++;

	/* adjust the heights of the other windows to make room for this one */
	chgsize(newp, (int)(o_ttyrows / nwindows), False);
	drawborder(newp);

	/* some versions of tcaphelp.c support retitle() */
	if (gui->retitle)
		(*gui->retitle)((GUIWIN *)newp, name);

	/* make elvis do its own initialization */
	if (!eventcreate((GUIWIN *)newp, NULL, name, newp->height, (int)o_ttycolumns))
	{
		/* elvis can't make it -- fail */
		safefree(newp);
		return False;
	}

	/* make the new window be the current window */
	current = newp;

	/* execute the first command, if any */
	if (firstcmd)
	{
		winoptions(winofgw((GUIWIN *)newp));
		exstring(windefault, toCHAR(firstcmd));
	}

	return True;
}


/* This function deletes a window */
static void destroygw(gw, force)
	GUIWIN	*gw;	/* window to be destroyed */
	BOOLEAN	force;	/* if True, try harder */
{
	TWIN	*scan, *lag;
	WINDOW	win;

	/* delete the window from the list of windows */
	for (lag = NULL, scan = twins; scan != (TWIN *)gw; lag = scan, scan = scan->next)
	{
	}
	if (lag)
	{
		lag->next = scan->next;
	}
	else
	{
		twins = scan->next;
	}

	/* if it was the current window, it isn't now */
	if (scan == current)
	{
		current = twins;
	}

	/* adjust the sizes of other windows (if any) */
	nwindows--;
	if (nwindows > 0)
	{
		chgsize((TWIN *)gw, 0, False);
	}

	/* If this is the last window, move the cursor to the last line, and
	 * erase it.  If the buffer is going to be written, this is where the
	 * "wrote..." message will appear.
	 */
	if (nwindows == 0)
	{
		revert(NULL);
		tputs(tgoto(CM, 0, (int)(o_ttyrows - 1)), 1, ttych);
		tputs(CE, 1, ttych);
	}

	/* simulate a "destroy" event */
	eventdestroy(gw);

	/* free the storage */
	safefree(gw);

	/* some versions of tcaphelp.c support retitle() */
	if (current && gui->retitle && (win=winofgw((GUIWIN *)current)) != NULL)
	{
		(*gui->retitle)((GUIWIN *)current,
			tochar8(o_bufname(markbuffer(win->cursor))));
	}
}


/* This function changes window focus */
static BOOLEAN focusgw(gw)
	GUIWIN	*gw;	/* window to be the new "current" window */
{
	WINDOW	win;

	/* make this window current */
	current = (TWIN *)gw;

	/* some versions of tcaphelp.c support retitle() */
	if (gui->retitle && (win = winofgw(gw)) != NULL)
		(*gui->retitle)(gw, tochar8(o_bufname(markbuffer(win->cursor))));

	return True;
}


/* This function handles the visual <Tab> command */
static BOOLEAN tabcmd(gw, key2, count)
	GUIWIN	*gw;	/* window that the command should affect */
	_CHAR_	key2;	/* second key of <Tab> command */
	long	count;	/* argument of the <Tab> command */
{
	TWIN	*tw = (TWIN *)gw;
	int	newheight;
	int	oldheight;
	int	oldpos;

	/* if only one window, then we can't change its size */
	if (nwindows == 1)
		return False;

	/* remember the old position */
	newheight = oldheight = tw->height;
	oldpos = tw->pos;

	switch (key2)
	{
	  case '=':
		if (count >= MINHEIGHT)
		{
			newheight = count;
			break;
		}
		/* else fall through... */

	  case '+':
		newheight += (count ? count : 1);
		break;

	  case '-':
		newheight -= (count ? count : 1);
		if (newheight < MINHEIGHT)
		{
			newheight = MINHEIGHT;
		}
		break;

	  case '\\':
		newheight = o_ttyrows; /* will be reduced later */
		break;

	  default:	return False;
	}

	/* try to change the heights of other windows to make this one fit */
	chgsize(tw, newheight, False);
	newheight = tw->height;

	/* resize/expose this window */
	if (newheight != oldheight)
	{
		drawborder(tw);
		eventresize(tw, tw->height, (int)o_ttycolumns);
	}
	else if (tw->pos != oldpos)
	{
		drawborder(tw);
		eventexpose(tw, 0, 0, newheight - 1, (int)(o_ttycolumns - 1));
	}
	return True;
}

/* This function rings the bell */
static void beep(gw)
	GUIWIN	*gw;	/* window that generated the beep */
{
	if (o_flash && VB)
		tputs(VB, 0, ttych);
	else
		ttych('\007');
}

/* This function converts key labels to raw codes */
static int keylabel(given, givenlen, label, rawptr)
	CHAR	*given;		/* what the user typed in as the key name */
	int	givenlen;	/* length of the "given" string */
	CHAR	**label;	/* standard name for that key */
	CHAR	**rawptr;	/* control code sent by that key */
{
	int	i;

	/* compare the given text to each key's strings */
	for (i = 0; i < QTY(keys); i++)
	{
		/* ignore unsupported keys */
		if (!keys[i].rawin)
			continue;

		/* does given string match key label or raw characters? */
		if ((!strncmp(keys[i].label, tochar8(given), (size_t)givenlen) && !keys[i].label[givenlen])
		 || (!strncmp(keys[i].rawin, tochar8(given), (size_t)givenlen) && !keys[i].rawin[givenlen]))
		{

			/* Set the label and rawptr pointers, return rawlen */
			*label = toCHAR(keys[i].label);
			*rawptr = toCHAR(keys[i].rawin);
			return CHARlen(*rawptr);
		}
	}

	/* We reached the end of the keys[] array without finding a match,
	 * so this given string is not a key.
	 */
	return 0;
}

/* This function defines colors for fonts */
static BOOLEAN color(gw, font, fg, bg)
	GUIWIN	*gw;	/* window whose colors are being set */
	_char_	font;	/* font being changed: n/b/i/u else highlighted */
	CHAR	*fg;	/* name of desired foreground color */
	CHAR	*bg;	/* name of desired background color */
{
	TWIN	*tw = (TWIN *)gw;
	TWIN	*other; /* some other window */
	BOOLEAN	ret;	/* return code -- True if successful */

	/* we must set normal colors first */
	if ((!fgcolored || (bg && !bgcolored)) && font != 'n')
	{
		msg(MSG_ERROR, "must set normal colors first");
		return False;
	}

	/* if no window specified, or this is the first :color command,
	 * then we're setting the default colors.
	 */
	if (!tw || !fgcolored)
	{
		tw = &defcolors;
	}

	/* revert to normal font now; if we wait until after setting colors,
	 * we might not know how to do it anymore!
	 */
	revert(tw);

	/* set the colors */
	ret = ansi_color(tw, font, tochar8(fg), tochar8(bg));

	/* if colors weren't set before, then copy colors to all windows */
	if (!fgcolored || (!bgcolored && bg && *bg))
	{
		for (other = twins; other; other = other->next)
		{
			/* skip the window that we just set */
			if (other == tw)
			{
				continue;
			}

			/* copy the colors */
			strcpy(other->startnormal, tw->startnormal);
			strcpy(other->startfixed, tw->startfixed);
			strcpy(other->endfixed, tw->endfixed);
			strcpy(other->startbold, tw->startbold);
			strcpy(other->endbold, tw->endbold);
			strcpy(other->startemph, tw->startemph);
			strcpy(other->endemph, tw->endemph);
			strcpy(other->startitalic, tw->startitalic);
			strcpy(other->enditalic, tw->enditalic);
			strcpy(other->startunderline, tw->startunderline);
			strcpy(other->endunderline, tw->endunderline);
			strcpy(other->starthilite, tw->starthilite);
			strcpy(other->endhilite, tw->endhilite);
		}

		/* we've set colors now! */
		fgcolored = True;
		if (bg && *bg)
		{
			bgcolored = True;
		}
	}

	/* remember the current window's colors, to use them as the default
	 * for any window that gets created after this.
	 */
	if (tw != &defcolors)
	{
		defcolors = *tw;
	}

	/* We probably need to reset the screen's current attribute */
	revert(tw);

	return ret;
}


static BOOLEAN isread;

/* Suspend curses while running an external program */
static BOOLEAN ttyprgopen(command, willwrite, willread)
	char	*command;	/* the shell command to run */
	BOOLEAN	willwrite;	/* redirect stdin from elvis */
	BOOLEAN	willread;	/* redirect stdiout back to elvis */
{
	/* unless stdout/stderr is going to be redirected, move the cursor
	 * to the bottom of the screen before running program.
	 */
	isread = willread;
	if (!isread)
	{
		/* suspend curses */
		ttysuspend();
	}

	/* try to call the regular prgopen(); if it fails, then clean up */
	if (!prgopen(command, willwrite, willread))
	{
		if (!isread)
			ttyresume(True);
		return False;
	}

	return True;
}


/* After running a program, resume curses and redraw all screens */
static int ttyprgclose P_((void))
{
	int	status;
	WINDOW	win;

	/* wait for the program to terminate */
	status = prgclose();

	/* resume curses */
	if (!isread)
	{
		ttyresume(False);

		/* Okay, now we're in a weird sort of situation.  The screen is
		 * about to be forced to display "Hit <Enter> to continue" on
		 * the bottom of the window in open mode, and then wait for a
		 * keystroke.  That's a Good Thing.  But there are two problems
		 * we need to address:
		 *    * We want that prompt to appear at the bottom of the
		 *	screen, not the bottom of the window.
		 *    * After the user hits a key, we want to redraw all
		 *	windows.
		 *
		 * We'll set a flag indicating this situation.  The movecurs()
		 * function will test for that flag, and merely pretend to move
		 * the cursor when it is set.  The loop() function will test
		 * that flag after each keystroke, and expose all windows if
		 * it is set.
		 */
		afterprg = 2;
		afterscrl = 0;
	}

	/* some versions of tcaphelp.c set the title */
	if (gui->retitle && (win = winofgw((GUIWIN *)current)) != NULL)
		(*gui->retitle)((GUIWIN *)current,
				tochar8(o_bufname(markbuffer(win->cursor))));

	return status;
}


#ifdef SIGSTOP
/* This function starts an interactive shell.  It is called with the argument
 * (True) for the :sh command, or (False) for a :stop or :suspend command.
 * If successful it returns RESULT_COMPLETE after the shell exits; if
 * unsuccessful it issues an error message and returns RESULT_ERROR.  It
 * could also return RESULT_MORE to defer processing to the portable code
 * in ex_suspend().
 */
static RESULT stop(alwaysfork)
	BOOLEAN	alwaysfork;	/* fork even if SIGSTOP would work? */
{
	RESULT	result;

	/* move the cursor to the bottom of the screen, and scroll up */
	tputs(tgoto(CM, 0, (int)o_ttyrows - 1), 0, ttych);
	ttych('\n');
	reset();
	ttyflush();

	/* if we want to fork, then the default processing is good enough */
	if (alwaysfork)
		return RESULT_MORE;

	/* call the OS-dependent function for stopping the process */
	result = ttystop();
	if (result == RESULT_MORE)
		return RESULT_MORE;

	/* arrange for all windows to be refreshed */
	afterprg = 1;
	return result;
}
#endif


/* This function converts screen coordinates into a window, and coordinates
 * within that window.
 */
GUIWIN *ttywindow(ttyrow, ttycol, winrow, wincol)
	int	ttyrow, ttycol;		/* screen coordinates in */
	int	*winrow, *wincol;	/* window coordinates out */
{
	TWIN	*tw;

	if (ttycol < 0 || ttycol >= o_ttycolumns)
		return NULL;
	for (tw = twins;
	     tw && (tw->pos > ttyrow || ttyrow >= tw->pos + tw->height);
	     tw = tw->next)
	{
	}
	if (tw)
	{
		*winrow = ttyrow - tw->pos;
		*wincol = ttycol;
	}
	return (GUIWIN *)tw;
}


/* structs of this type are used to describe each available GUI */
GUI guitermcap =
{
	"termcap",	/* name */
	"Termcap/Terminfo interface with windows & color",
	False,		/* exonly */
	False,		/* newblank */
	False,		/* minimizeclr */
	True,		/* scrolllast */
	False,		/* shiftrows */
	3,		/* movecost */
	0,		/* opts */
	NULL,		/* optdescs */
	test,
	init,
	NULL,		/* usage */
	loop,
	ttypoll,
	term,
	creategw,
	destroygw,
	focusgw,
	NULL,		/* retitle */
	reset,
	flush,
	moveto,
	draw,
	shift,
	scroll,
	clrtoeol,
	NULL,		/* newline */
	beep,		/* beep */
	NULL,		/* msg */
	NULL,		/* scrollbar */
	NULL,		/* status */
	keylabel,
	NULL,		/* clipopen */
	NULL,		/* clipwrite */
	NULL,		/* clipread */
	NULL,		/* clipclose */
	color,		/* color */
	NULL,		/* guicmd */
	tabcmd,
	NULL,		/* save */
	NULL,		/* wildcard */
	ttyprgopen,
	ttyprgclose,
#ifdef SIGSTOP
	stop
#else
	NULL		/* stop */
#endif
};
#endif
