/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is	*
 * provided to you without charge, and with no warranty.  You may give	*
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.						*
 ************************************************************************/

#include "jove.h"

RCS("$Id: term.c,v 14.32.0.2 1993/07/14 23:40:12 tom Exp tom $")

#include "io.h"
#define Extern	public	/* to force definition of items in termcap.h. */
#include "termcap.h"

DEF_STR( "term", TermName, 20, V_STRING|V_CONST ) ZERO;
DEF_INT( "lines", LI, V_BASE10|V_CONST );
DEF_INT( "columns", CO, V_BASE10|V_CONST );

#ifdef TERMCAP

#ifdef COLOR
/*
 * The following non-standard termcap capabilities are used to implement
 * the color option of JOVE.
 *
 *	"c0" - "c7" = set text color 0-7;		(AIX scheme)
 *	"d0" - "d7" = set background color 0-7;
 * or
 *	"Sb", "Sf" = set background, text color #1;	(SCO scheme)
 *	"sp" = set color pair #1;
 * or, alternatively:
 *	"Sc" = set color, takes 2 parameters (text color, background color);
 *
 * Color remap:
 *	"Mb", "Mf" = background, text color map:
 *		contains a series of offsets, separated by commas, to be indexed
 *		by the color index before it is handed to "Sb", "Sf" or "Sc".
 *		an empty offset implies the next higher integer.
 *
 * Default color:
 *	"op" = original pair; set startup colors.	(SCO scheme)
 *		(if this exists it overrides exit-color).
 */
#   include "screen.h"
#endif

#ifndef TINY
#   define MIN_TSPACE	512
#else
#   define MIN_TSPACE	256
#endif

#if (TSPACE - 0) < MIN_TSPACE	/* so we can set it from the Makefile */
#   define TSPACE	MIN_TSPACE	/* at least this much */
#endif

private char	tspace[TSPACE];

#ifdef VARTERM
/*
 * The following non-standard termcap capabilities are used to implement
 * the switchable screen size option of JOVE.
 *
 *	"li" # standard screen height;	"zl" = set term to `li' lines;
 *	"LI" # alternate screen height;	"zL" = set term to `LI' lines;
 *	"co" # standard screen width;	"zc" = set term to `co' columns;
 *	"CO" # alternate screen width;	"zC" = set term to `CO' columns;
 *
 * TODO: make it cooperate with arbitrary-sizeable terminal windows.
 */

/* indices for screen dimension array. */
#   define NARROW	0
#   define WIDE		1
#   define INITIAL	2

#   define WIDTH	0
#   define HEIGHT	1

public short		newdim[HEIGHT+1];

private short		tdim[INITIAL+1][HEIGHT+1] ZERO;	/* the dimensions */
private const char	*TD[INITIAL+1][HEIGHT+1];  /* the switch commands */
private const char	tdcap[HEIGHT+1][2] = { 'C','O',  'L','I' };

#   define TC_VARTERM	'z','c',		'z','l',		\
			'z','C',		'z','L',

#   define MEAS_VARTERM &TD[NARROW][WIDTH],	&TD[NARROW][HEIGHT],	\
			&TD[WIDE][WIDTH],	&TD[WIDE][HEIGHT],

#   define INIT_VARTERM() do { \
		register int	i = WIDE;				\
		do {							\
			tdim[i][WIDTH] = CO;				\
			tdim[i][HEIGHT] = LI;				\
		} while (--i >= 0);					\
		ttsize(); /* get ACTUAL size from the system */		\
		tdim[INITIAL][WIDTH] = CO;				\
		tdim[INITIAL][HEIGHT] = LI;				\
		i = HEIGHT;						\
		do {							\
			register int	n;				\
			if (TD[WIDE][i] && TD[NARROW][i] &&		\
			    (n = tgetnum(tdcap[i])) > 0) {		\
				if (n < tdim[NARROW][i]) {		\
					tdim[NARROW][i] = n;		\
					/* swap */			\
					t = (char *) TD[WIDE][i];	\
					TD[WIDE][i] = TD[NARROW][i];	\
					TD[NARROW][i] = t;		\
				}					\
				else {					\
					tdim[WIDE][i] = n;		\
				}					\
				if (tdim[INITIAL][i] == tdim[NARROW][i])\
					TD[INITIAL][i] = TD[NARROW][i];	\
				else if (tdim[INITIAL][i] == tdim[WIDE][i])\
					TD[INITIAL][i] = TD[WIDE][i];	\
				/* else actual screen size differs from
				   the sizes given in termcap. */	\
			}						\
			/* Kludge to keep last size of window intact */	\
			if (TD[INITIAL][i] == NULL)			\
				tdim[INITIAL][i] = 0;			\
		} while (--i >= 0);					\
	} while (0)

/* change screen dimension `which' according to `how'.
   how < 0: NARROW, how > 0: WIDE; how == 0: Initial setup */
int
set_tdim(how, which)
register int	how, which;
{
	register int	i = 0;

	if (how == 0) {
		if ((i = tdim[WIDE][HEIGHT] - tdim[NARROW][HEIGHT]) &&
		    (LI != (newdim[HEIGHT] = tdim[INITIAL][HEIGHT])))
			putp(TD[INITIAL][HEIGHT]);

		which = WIDTH;
		how = INITIAL;
	}
	else if (how < 0)
		how = NARROW;
	else
		how = WIDE;

	if ((tdim[WIDE][which] != tdim[NARROW][which]) &&
	    (++i,
	     (which == WIDTH ? CO : LI) != (newdim[which] = tdim[how][which])))
		putp(TD[how][which]);
	return i;
}

DEF_CMD( "screen-wide",	  ScrDim, ARG(0) ); _IF(def VARTERM)
DEF_CMD( "screen-narrow", ScrDim, ARG(0)|NEGATE ); _IF(def VARTERM)
DEF_CMD( "screen-dense",  ScrDim, ARG(1) ); _IF(def VARTERM)
DEF_CMD( "screen-normal", ScrDim, ARG(1)|NEGATE ) _IF(def VARTERM)
{
	if (!set_tdim(exp, ObjArg(LastCmd)))
		complain("[%f is not supported by terminal \"%s\"]", TermName);
	win_reshape();
}

#else /* !VARTERM */
#   define TC_VARTERM
#   define MEAS_VARTERM
#   define INIT_VARTERM()
#endif /* VARTERM */

/* The ordering of `ts' and `meas' must agree !! */

private const char ts[] = {	/* thank God (Jon?) for keyboard macros! */
  'b','c',  'b','l',  'c','e',  'c','l',  'c','m',  'k','e',  'k','s',  'm','m',
  'm','o',  's','e',  's','o',  't','e',  't','i',  'v','b',  'v','e',  'v','i',
  'v','s',
#ifdef ANSICODES
  's','p',
#endif
#ifdef CURSOPT
 'h','o',  'l','l',
#endif
#ifndef FAST_IDLINE
  'a','l',  'A','L',  'd','l',  'D','L',  's','f',  'S','F',  's','r',  'S','R',
  'c','s',
#endif
#ifndef FAST_FLASH
  'v','b',
#endif
#ifdef ID_CHAR
  'i','c',  'I','C',  'd','c',  'D','C',  'i','m',  'e','i',  'i','p',
#endif
#ifdef LSRHS
  'r','s',  'r','e',
#endif
#if (HIGHLIGHT)
  'm','d',  'm','r',  'm','e',
#endif
#ifdef COLOR
  'S','c',  'S','b',  'S','f',  's','p',  'M','b',  'M','f',  'o','p',
#endif
  TC_VARTERM
  'l','e',  'u','p',  'p','c',
  NIL
};

private const char ** const meas[] = {
	&BC,	&BL,	&CE,	&CL,	&CM,	&KE,	&KS,	&MM,
	&MO,	&SE,	&SO,	&TE,	&TI,	&VB,	&VE,	&VI,
	&VS,
#ifdef ANSICODES
	&SP,
#endif
#ifdef CURSOPT
	&HO,	&LL,
#endif
#ifndef FAST_IDLINE
	&AL,	&M_AL,	&DL,	&M_DL,	&SF,	&M_SF,	&SR,	&M_SR,
	&CS,
#endif
#ifndef FAST_FLASH
	&VB,
#endif
#ifdef ID_CHAR
	&IC,	&M_IC,	&DC,	&M_DC,	&IM,	&EI,	&IP,
#endif
#ifdef LSRHS
	&RS,	&RE,
#endif
#if (HIGHLIGHT)
	&BO,	&MR,	&ME,
#endif
#ifdef COLOR
	&COlor,	&CObg,	&COfg,	&COpair, &CObgmap, &COfgmap, &COorig,
#endif
	MEAS_VARTERM
	&LE,	&UP,	&lPC
};

/* Same trick for flags: The ordering of `tf' and `meaf' must agree !! */

private const char tf[] = {
 'a','m',  'x','o',  'x','n',  'x','s',
#ifdef DUMBTERMS
 'u','l',  'h','z',
#endif
#ifdef ID_CHAR
 'm','i',
#endif
 NIL
};

static int * const meaf[] = {
	&AM,	&OKXonXoff,	&XN,	&XS,
#ifdef DUMBTERMS
	&UL,	&Hz,
#endif
#ifdef ID_CHAR
	&MI,
#endif
};

private void TermError __(( const char *_(mesg) ));
private void
TermError(mesg)
const char	*mesg;
{
	printf("\rI cannot handle terminal \"%s\": %s.\n", TermName, mesg);
	flusho();
}

private int etgetnum __(( const char *_(env), const char *_(cap) ));
private int
etgetnum(env, cap)
const char	*env, *cap;
{
	register char	*t;
	register int	result;

	if ((t = getenv(env)) == NULL || (result = chr_to_int(t, 10, YES)) <= 0)
		if ((result = tgetnum(cap)) <= 0)
			TermError(env);
	return result;
}

public void
getTERM()
{
	register char	*t;
	char		*termp,
			tnamebuf[20],
			tbuff[2048];	/* Good grief! */

	t = getenv("TERM");

	while (TermName = t, tgetent(tbuff, t) < 1) {
		TermError("unknown type");
    retry:
		putstr("Enter terminal name: ");
		flusho();
		t = tnamebuf + 1;
		t[read(0, t, sizeof(tnamebuf)-1) - 1] = '\0';
		if (!*t)
			_exit(0);	/* do not be too persistent... */
	}

	if ((CO = etgetnum("COLUMNS", "co")) <= 0 ||
	    (LI = etgetnum("LINES", "li")) <= 0)
		goto retry;

	if (CO > MAXCOLS)
		CO = MAXCOLS;

	if ((SG = tgetnum("sg")) < 0)
		SG = 0; 		/* Used for mode line only */

	termp = tspace;

	/* Load string capabilities in one fell swoop. */
    {
	register const char	** const *measp = meas;

	t = (char *) ts;
	do  **measp++ = tgetstr(t, &termp);  while (*(t += 2));
    }
	/* Load flag capabilities in one fell swoop. */
    {
	register int	* const *meafp = meaf;

	t = (char *) tf;
	do  **meafp++ = tgetflag(t);  while (*(t += 2));
    }
#ifdef COLOR
    {
	/*
	 * initialize color capabilities (according to AIX terminfo):
	 *   `c0'..`c7' foreground colors
	 *   `d0'..`d7' background colors
	 */
	extern char	*Colors[2][NCOLORS];

	if (!COlor && !COfg && !CObg && !COpair) {
		register char	**col_p = &Colors[0][0];
		char		tinybuf[2];
		t = tinybuf;
		t[0] = 'c', t[1] = '0';

		do {
			*col_p++ = tgetstr(t, &termp);
			if (++t[1] == '0' + NCOLORS) {
				t[0]++;			/* i.e. 'd' */
				t[1] -= NCOLORS;	/* i.e. '0' */
			}
		} while (col_p < &Colors[2][0]);
	}
    }
    {
	/*
	 * Set default color, if given in termcap (by `Cb#' and `Cf#', resp.)
	 * else assume DEF_COLOR.
	 */
	register int	bg_color, fg_color;

	if ((bg_color = tgetnum("Cb")) < 0)
		bg_color = BG_COLOR(DEF_COLOR);
	if ((fg_color = tgetnum("Cf")) < 0)
		fg_color = FG_COLOR(DEF_COLOR);
	if (fg_color == bg_color)
		if (bg_color == BG_COLOR(DEF_COLOR))
			/* Cb undefined and Cf=BLACK implies Cb=WHITE */
			bg_color ^= WHITE;
		else	/* Cf undefined and Cb=WHITE implies Cf=BLACK. */
			fg_color ^= WHITE;

	DefColor = MK_COLOR(fg_color, bg_color);
    }
    {
	/* Set color standout glitch. */
	register int	ncv_mask;

	if ((ncv_mask = tgetnum("NC")) >= 0) {
		/* (Now this is SCO black color-termcap magic:-) */
		register int	mask = 0;

		if (SO)
			mask = Bit(0);
#   if (HIGHLIGHT)
		if (ME) {
			if (MR)	/* Reverse video replaces standout. */
				mask = Bit(2);
			if (BO)
				mask |= Bit(5);
		}
#   endif
		if (ncv_mask & mask) {
			extern int	ColorStandoutGlitch;

			ColorStandoutGlitch = YES;
		}
	}
    }
#   ifdef SCO_SYSV
	/* The following is valid only for SCO termcap, since JOVE assumes
	   current background color erase as default:
	   ``if (NOT back_color_erase AND any color defined)'' */
	if (!tgetflag("be") && (tgetnum("Co") > 2 || tgetnum("pa") > 2)) {
		extern int	ColorEraseGlitch;

		ColorEraseGlitch = YES;
	}
#   endif
#endif /* COLOR */

	INIT_VARTERM();

	/* sanity check */
	if (termp > &tspace[sizeof tspace]) {
		TermError("(recompile with larger tspace)");
		_exit(1);	/* this is serious! */
	}

#if 0	/* If your terminal really needs it, let it set BC properly! */
	if (BC == NULL && (t = LE) && t[1] == '\0')
		BC = LE;
#endif
	if (lPC)
		PC = *lPC;
	/*
	 * handle XN glitch as if terminal were in no-automargin mode
	 */
	if (XN)
		AM = NO;
#ifndef FAST_IDLINE
	/*
	 * be sure not to use the scrolling region if we can't scroll.
	 */
	if (CS)
		if (SR == NULL)
			CS = SR = SF = NULL;
		else if (SF == NULL)
			SF = "\n";
#endif
	/*
	 * [TRH] ignore cursor invisible if cursor visible not found,
	 * and at lower baud rates
	 */
	if ((VS == NULL && (VS = VE) == NULL) || CharsPerSec < 480)
		VI = NULL;

	/* [TRH] use termcap to test presence of Meta key */
	if (tgetflag("km"))
		MetaKey = ESC;

	OKXonXoff ^= 1;

#ifndef DUMBTERMS
	if (CE == NULL) {
		TermError("(recompile with DUMBTERMS)");
		goto retry;
	}
#endif
	if (CL == NULL) {
		TermError("too dumb, sorry");
		goto retry;
	}
	if (BL == NULL)	/* set a reasonable default bell */
		BL = "\7";

#ifdef LSRHS		/* We, at the high school, are the only ones who
			   do SO right in termcap, but unfortunately the
			   right SO doesn't look as good with modelines. */
	if (RS)
		SO = RS;
	if (RE)
		SE = RE;
			/* I only ever use SO for the modeline anyway. */

/* SO is really BOLDFACE!  Why is LS always right and the rest of the
   world wrong? [sounds like Calimero to me...] */
#endif
#if (HIGHLIGHT)
	/* Replace Standout with reverse video, if we have it.
	   This is official termcap, unlike LSRHS above. */
	if (MR && ME)
		SO = MR, SE = ME;
#endif
	/*
	 * [TRH] if we can't erase braindamaged standout,
	 * just forget standout altogether
	 */
	if (XS && CE == NULL)
#if (HIGHLIGHT)
		BO = ME =
#endif
		SO = SE = NULL;
#ifdef ID_CHAR
	if (IM && (*IM == '\0'))
		IM = NULL;

	disp_opt_init();
#endif
#ifdef CURSOPT
    {
	register int it;

	/* Don't set phystab directly from "it", because it is missing from
	   too many existing termcap entries; just assume the default. */
	if ((it = tgetnum("it")) >= 0)
		phystab = it;
    }
	if (HO)
		HOlen = strlen(HO);
	if (LL)
		LLlen = strlen(LL);
#endif
	if (UP)
		UPlen = strlen(UP);

	/* stow away termname to a safe place */

	if ((t = TermName) == tnamebuf)
		TermName = copystr(t);

#ifndef FAST_IDLINE
	if ((AL && DL) || CS)
		IDline_setup(t);
#endif
#ifdef FUNCKEYS
	FKeyInit();
#endif
#ifdef COLOR
	COLOR_setup(t);
#endif
}
#endif /* TERMCAP */

/* Deals with output to the terminal, setting up the amount of characters
   to be buffered depending on the output baud rate.  */

#undef putch	/* unhide the function */

void
putch(c)
{
	register File	*f = stdout;
	putc(c, f);
}

#ifndef putpad	/* could be #defined in termcap.h for systems
		   that don't need padding. */
void
putpad(str, lines)
const char	*str;
{
	register const char	*s;

	if (s = str)
		tputs(s, lines, (int (*)__((int))) putch);
}
#else
#   define	tputs(s,l,f)	putstr(s)
#endif

void
putp(str)
const char	*str;
{
	register const char	*s;

	if (s = str)
		tputs(s, 1, (int (*)__((int))) putch);
}

/*
 * [TRH] output a capability num times, or alternatively output
 * its parameterized variant (with parameter num)
 */
#ifdef ID_CHAR		/* this kludgery for dumb preprocessors (sigh) */
#   undef FAST_IDLINE
#endif
#ifndef FAST_IDLINE	/* !defined(FAST_IDLINE) || defined(ID_CHAR) */
void
Nputpad(single, multi, num, n_affected)
const char	*single,
		*multi;
register int	num,
		n_affected;
{
	if (multi && (num > 1))
		putpad(tgoto(multi, 0, num), n_affected);
	else
		while (--num >= 0)
			putpad(single, n_affected);
}
#endif

/* Determine the number of characters to buffer at each baud rate.  The
   lower the number, the quicker the response when new input arrives.  Of
   course the lower the number, the more prone the program is to stop in
   output.  Decide what matters most to you. This initializes `stdout'.  */

void
settout(ttbuf)
char	*ttbuf;
{
#ifndef TERMCAP
#   define size (LI <= 25 ? TTBUFSIZ/2 : TTBUFSIZ)
	/* seems reasonable: about 1/5 of a screen */
#else
	static const int speeds[] = {
			/*   /1 */
		960,	/*   /2 */
		720,	/*   /4 */
		360,	/*   /8 */
		240,	/*  /16 */
		180,	/*  /32 */
		120,	/*  /64 */
		60,	/* /128 */
#   if 0
		30,	/* /256 */
		20,	/* /512 */
#   endif
		-1	/* sentinel */
	};
	register const int	*spp = speeds;
	register int		size = TTBUFSIZ;

	while (CharsPerSec <= *spp++)
		size >>= 1;
#endif
	stdout = fd_open(DevTty, F_WRITE|F_LOCKED|F_BINARY, 1, ttbuf, size);
}

#ifdef TGETSTR_BUG
/*
 * Interactive 2.0.2 has an empty-headed implementation of
 * tgetstr() which advances the area pointer even when the
 * capability is not found, thus causing our keybuf to overflow
 * (sigh).
 */
char *
tgetstr(cap, area)
const char	*cap;
char		**area;
{
	register char	*old_area = *area,
			*result;

#undef tgetstr	/* unmask "real" tgetstr */

	if ((result = tgetstr(cap, area)) == NULL)
		*area = old_area;
	return result;
}
#endif /* TGETSTR_BUG */

/*======================================================================
 * $Log: term.c,v $
 * Revision 14.32.0.2  1993/07/14  23:40:12  tom
 * (getTERM): don't initialize `phystab' from "it" directly.
 *
 * Revision 14.32  1993/06/23  00:08:44  tom
 * COLOR: get op= (SCO default color), NC# (SCO) for ColorStandoutGlitch,
 * be (SCO only) for ColorEraseGlitch;
 * HIGHLIGHT: use mr= (reverse video) instead of so= if present.
 *
 * Revision 14.31  1993/02/13  23:20:18  tom
 * one random optimization.
 *
 * Revision 14.30  1993/01/27  01:33:22  tom
 * cleanup whitespace; parenthize || && expr to avoid compiler warnings.
 *
 * Revision 14.28  1992/10/02  15:38:58  tom
 * add color-remap support through (non-standard) termcapabilities Mb, Mf.
 *
 * Revision 14.27  1992/09/22  02:03:52  tom
 * some typecasts to suppress compiler warnings.
 *
 * Revision 14.26  1992/08/26  23:56:59  tom
 * add RCS directives.
 *
 */
