[alt.sources] STDWIN 0.9.5, Part 03/19

guido@cwi.nl (Guido van Rossum) (03/04/91)

Archive-name: stdwin/part03

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 3 (of 19)."
# Contents:  Appls/bed/2x2 Appls/tetris/tetris.c Ports/vtrm/vtrm.c
# Wrapped by guido@voorn.cwi.nl on Mon Mar  4 12:37:23 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'Appls/bed/2x2' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Appls/bed/2x2'\"
else
echo shar: Extracting \"'Appls/bed/2x2'\" \(272 characters\)
sed "s/^X//" >'Appls/bed/2x2' <<'END_OF_FILE'
X#define 2x2_width 16
X#define 2x2_height 16
Xstatic char 2x2_bits[] = {
X   0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff,
X   0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33,
X   0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33};
END_OF_FILE
if test 272 -ne `wc -c <'Appls/bed/2x2'`; then
    echo shar: \"'Appls/bed/2x2'\" unpacked with wrong size!
fi
# end of 'Appls/bed/2x2'
fi
if test -f 'Appls/tetris/tetris.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Appls/tetris/tetris.c'\"
else
echo shar: Extracting \"'Appls/tetris/tetris.c'\" \(16117 characters\)
sed "s/^X//" >'Appls/tetris/tetris.c' <<'END_OF_FILE'
X/* Tetris.
X
X   A simple but challenging game where pieces of different shapes
X   falling with a constant speed must be manoeuvered to a final
X   docking position.  Piece shapes are randomly chosen from all possible
X   ways to connect four squares.  The manipulations allowed are moving
X   the piece left or right and rotating it; every clock tick it moves
X   down one step, until it cannot move further, or the player decides
X   to "drop" it to earn more points.  The game is made more or less
X   challenging by making the clock tick faster or slower.  A score is
X   kept.  Points earned per piece depend on the height where it is
X   dropped or stopped; extra points are awarded for choosing a higher
X   speed ("level").  To allow the play to continue indefinitely,
X   complete rows (i.e., horizontal rows where all squares
X   are filled) are removed from the board and its contents above that
X   row shifted down.
X   
X   The user interface uses mostly the left, right and up keys, for
X   moving the piece left and right and rotating it.  For Unix adepts,
X   'h'=left, 'k'=up and 'l'=right also work.  Space bar or Return
X   drops the piece.  The Cancel key (Command-Period on the Mac,
X   Control-C on most other systems) restarts the game; close the window
X   or type 'q' to quit the game.
X   
X   The origin of the game appears to be in the East Block; I've heard
X   that the (original?) Macintosh version was by a Hungarian programmer.
X   
X   This code is hereby put in the public domain, but the package
X   STDWIN used as portable window interface is copyrighted.  STDWIN
X   is available from me, too, provided you respect the copyright etc.
X   
X   Guido van Rossum, CWI, Kruislaan 413, 1098 SJ Amsterdam,
X   The Netherlands
X   Internet e-mail address: guido@cwi.nl
X   April 1989
X*/
X
X/* TO DO:
X	- show next piece coming up in a side window
X	- change chance distribution of pieces? (too few bars)
X	- advanced level (what should it do? just faster?)
X	- rethink level <--> delay relation
X	
X	- improve "game over" behavior
X	
X	- display high score
X	- score and status display next to the board
X	- statistics
X	
X	- cute graphics
X*/
X
X/* Standard include files */
X
X#include "stdwin.h"
X#include <stdio.h>
X
X/* Parametrizations */
X
X/* Piece size.  It only makes sense to change this if you also
X   change the initialization of the 'shapes' array.
X   Since we rotate pieces, their max size must always be square. */ 
X#define PSIZE 4
X
X/* Game dimensions.  Traditionally it is played on a 10x20 board. */
X#ifndef BWIDTH
X#define BWIDTH 10
X#endif
X#ifndef BHEIGHT
X#define BHEIGHT 20
X#endif
X
X/* Initial timer delay.  This affects initial difficulty and scoring.
X   The current value is kept in variable 'delay'. */
X#ifndef DELAY
X#define DELAY 10
X#endif
X
X/* Individual 'square' sizes.
X   These can be adjusted according to taste and the size of pixels
X   on your screen.  (You can also fine-tune window and document size
X   in main() below.)
X   For the alfa version of STDWIN, where pixel size == character size,
X   a fixed size of 2x1 is forced later. */
X#ifndef SQWIDTH
X#define SQWIDTH 12
X#endif
X#ifndef SQHEIGHT
X#define SQHEIGHT 12
X#endif
X
X/* Left, top of board image in window */
X#ifndef BLEFT
X#define BLEFT 0
X#endif
X#ifndef BTOP
X#define BTOP 4
X#endif
X
X/* Some useful macros (predefined on some but not all systems) */
X
X#ifndef MIN
X#define MIN(a, b) ((a) < (b) ? (a) : (b))
X#endif
X
X#ifndef MAX
X#define MAX(a, b) ((a) > (b) ? (a) : (b))
X#endif
X
X/* Available shapes.
X   Relnext is the offset to the next piece after rotation.
X   The pieces are aligned with the bottom so their scoring values are
X   comparable, and the delay before their start is minimal; they are
X   centered horizontally so the random placement appears even.
X   Remember C's aggregate initialization rules; the initializer below
X   is completely bracketed, but trailing zeros are sometimes elided. */
X
Xstruct shapedef {
X	int relnext;
X	char piece[PSIZE][PSIZE];
X} shapes[] = {
X	
X	/* "Four in a row" (2 orientations) */
X	
X	{1,	{{0, 0, 0, 0},
X		 {0, 0, 0, 0},
X		 {0, 0, 0, 0},
X		 {1, 1, 1, 1}}},
X	
X	{-1,	{{0, 1, 0, 0},
X		 {0, 1, 0, 0},
X		 {0, 1, 0, 0},
X		 {0, 1, 0, 0}}},
X	
X	/* "L shape" (4 orientations) */
X	
X	{1,	{{0, 0, 0},
X		 {0, 1, 0},
X		 {0, 1, 0},
X		 {0, 1, 1}}},
X	
X	{1,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {0, 0, 1},
X		 {1, 1, 1}}},
X	
X	{1,	{{0, 0, 0},
X		 {0, 1, 1},
X		 {0, 0, 1},
X		 {0, 0, 1}}},
X	
X	{-3,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {1, 1, 1},
X		 {1, 0, 0}}},
X	
X	/* "Inverse L shape" (4 orientations) */
X	
X	{1,	{{0, 0, 0},
X		 {0, 1, 1},
X		 {0, 1, 0},
X		 {0, 1, 0}}},
X	
X	{1,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {1, 0, 0},
X		 {1, 1, 1}}},
X	
X	{1,	{{0, 0, 0},
X		 {0, 0, 1},
X		 {0, 0, 1},
X		 {0, 1, 1}}},
X	
X	{-3,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {1, 1, 1},
X		 {0, 0, 1}}},
X	
X	/* "Z shape" (2 orientations) */
X	
X	{1,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {1, 1, 0},
X		 {0, 1, 1}}},
X	
X	{-1,	{{0, 0, 0},
X		 {0, 0, 1},
X		 {0, 1, 1},
X		 {0, 1, 0}}},
X	
X	/* "S shape" (2 orientations) */
X	
X	{1,	{{0, 0, 0},
X		 {0, 1, 0},
X		 {0, 1, 1},
X		 {0, 0, 1}}},
X	
X	{-1,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {0, 1, 1},
X		 {1, 1, 0}}},
X	
X	/* "T shape" (4 orientations) */
X	
X	{1,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {1, 1, 1},
X		 {0, 1, 0}}},
X	
X	{1,	{{0, 0, 0},
X		 {0, 1, 0},
X		 {0, 1, 1},
X		 {0, 1, 0}}},
X	
X	{1,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {0, 1, 0},
X		 {1, 1, 1}}},
X	
X	{-3,	{{0, 0, 0},
X		 {0, 0, 1},
X		 {0, 1, 1},
X		 {0, 0, 1}}},
X	
X	/* "Block" (1 orientation) */
X	
X	{0,	{{0, 0, 0},
X		 {0, 0, 0},
X		 {0, 1, 1},
X		 {0, 1, 1}}},
X
X};
X
X/* Global variables */
X
Xint alfa;			/* Nonzero if using alfa STDWIN */
Xint sqwidth = SQWIDTH;		/* Width of squares, in pixels */
Xint sqheight = SQHEIGHT;	/* Height */
Xint bleft = BLEFT;
Xint btop = BTOP;
XWINDOW *win;			/* The window where we do our drawing */
Xint delay;			/* Current delay */
X				/* NB: level = MAX(0, DELAY-delay) */
Xchar board[BHEIGHT][BWIDTH];	/* Contents of board, except current piece */
Xchar (*piece)[PSIZE];		/* Piece currently being manoeuvered */
Xint pindex;			/* Index in the shape array of current piece */
Xint pleft, ptop;		/* Position of current piece */
Xlong score;			/* Score of current game */
X
X/* Generate an informative title from level and/or score.
X   (By putting it in the title bar we don't need an info window.) */
X
Xsettitle()
X{
X	char buf[100];
X	int level = MAX(0, DELAY-delay);
X	
X	if (level == 0) {
X		if (score == 0)
X			strcpy(buf, "Tetris");
X		else
X			sprintf(buf, "Score %ld", score);
X	}
X	else {
X		if (score == 0)
X			sprintf(buf, "Level %d", level);
X		else
X			sprintf(buf, "Sc %ld Lv %d", score, level);
X	}
X	wsettitle(win, buf);
X}
X
X/* Erase a portion of the board on the screen.
X   Call only within wbegin/enddrawing. */
X
Xeraseboard(ileft, itop, iright, ibottom)
X	int ileft, itop, iright, ibottom;
X{
X	werase(bleft + ileft*sqwidth,  btop + itop*sqheight,
X	       bleft + iright*sqwidth, btop + ibottom*sqheight);
X}
X
X/* Draw a portion of the board, and a border around it.
X   Call only within wbegin/enddrawing.
X   This may be called with out-of-range parameters.
X   Draw those squares of the game that lie (partly) in the rectangle
X   given by the parameters.  Assume the background is blank.
X   This contains #ifdef'ed code for the alfa version of STDWIN,
X   which only supports text output. */
X
Xdrawboard(ileft, itop, iright, ibottom)
X	int ileft, itop, iright, ibottom;
X{
X	int ih, iv;
X	int h, v;
X	int flag;
X	
X	ileft = MAX(0, ileft);
X	itop = MAX(0, itop);
X	iright = MIN(BWIDTH, iright);
X	ibottom = MIN(BHEIGHT, ibottom);
X	for (iv = itop, v = btop + iv*sqheight; iv < ibottom;
X					++iv, v += sqheight) {
X		for (ih = ileft, h = bleft + ih*sqwidth; ih < iright;
X						++ih, h += sqwidth) {
X			flag = board[iv][ih];
X			if (!flag && pleft <= ih && ih < pleft+PSIZE &&
X					ptop <= iv && iv < ptop+PSIZE)
X				flag = piece[iv-ptop][ih-pleft];
X			if (flag) {
X				if (alfa)
X					wdrawchar(h, v, '#');
X				else
X					wshade(h+1, v+1,
X						h+sqwidth, v+sqheight, 50);
X			}
X		}
X	}
X	if (alfa) {
X		/* Draw markers at the right margin */
X		wdrawchar(bleft + BWIDTH*sqwidth, btop, '|');
X		wdrawchar(bleft + BWIDTH*sqwidth, btop + (BHEIGHT-1)*sqheight,
X									'|');
X	}
X	else {
X		/* Draw a box around the board */
X		wdrawbox(bleft - 1, btop - 1, bleft + BWIDTH*sqwidth + 2,
X						btop + BHEIGHT*sqheight + 2);
X	}
X}
X
X/* Erase and redraw part of the board.
X   Unlike eraseboard and drawboard above, this includes calls to
X   wbegin/enddrawing. */
X
Xredrawboard(ileft, itop, iright, ibottom)
X	int ileft, itop, iright, ibottom;
X{
X	wbegindrawing(win);
X	eraseboard(ileft, itop, iright, ibottom);
X	drawboard(ileft, itop, iright, ibottom);
X	wenddrawing(win);
X}
X
X/* Draw procedure, passed to STDWIN's wopen */
X
Xvoid
Xdrawproc(win, left, top, right, bottom)
X	WINDOW *win;
X	int left, top, right, bottom;
X{
X	drawboard((left-bleft)/sqwidth, (top-btop)/sqheight,
X		(right-bleft+sqwidth-1)/sqwidth,
X		(bottom-btop+sqheight-1)/sqheight);
X}
X
X/* Check if the piece can be at (dh, dv).
X   This is used to check for legal moves.
X   No part of the piece can be on a filled spot in the board or
X   be outside it, but it can stick out above the top. */
X
Xint
Xallowed(dh, dv)
X	int dh, dv;
X{
X	int ih, iv;
X	
X	for (iv = 0; iv < PSIZE; ++iv) {
X		for (ih = 0; ih < PSIZE; ++ih) {
X			if (piece[iv][ih]) {
X				if (ih+dh < 0 || ih+dh >= BWIDTH)
X					return 0;
X				if (iv+dv < 0)
X					continue;
X				if (iv+dv >= BHEIGHT)
X					return 0;
X				if (board[iv+dv][ih+dh])
X					return 0;
X			}
X		}
X	}
X	return 1;
X}
X
X/* Return a random integer in the range [0..n-1] */
X
Xint
Xuniform(n)
X	int n;
X{
X	return rand() % n;
X}
X
X/* Rotate the piece 90 degrees counterclockwise.  No drawing is done.
X   The implementation is trivial: just take the "next" element from the
X   shape array. */
X
Xleftrotate()
X{
X	pindex += shapes[pindex].relnext;
X	piece = shapes[pindex].piece;
X}
X
X/* Move the piece by the given vector (dh, dv), if this is a legal move..
X   Return 1 if moved, 0 if not (then no changes were made). */
X
Xint
Xmoveby(dh, dv)
X	int dh, dv;
X{
X	int ileft, itop, iright, ibottom;
X	
X	if (!allowed(pleft+dh, ptop+dv)) {
X		return 0;
X	}
X	ileft   = pleft + MIN(dh, 0);
X	itop    = ptop  + MIN(dv, 0);
X	iright  = pleft + PSIZE + MAX(dh, 0);
X	ibottom = ptop  + PSIZE + MAX(dv, 0);
X	pleft += dh;
X	ptop += dv;
X	redrawboard(ileft, itop, iright, ibottom);
X	return 1;
X}
X
X/* Rotate the piece n quarter left turns, if this is a legal move.
X   Return 1 if moved, 0 if not (then no changes were made). */
X
Xint
Xrotateby(n)
X	int n;
X{
X	int i;
X	
X	for (i = 0; i < n; ++i)
X		leftrotate();
X	if (!allowed(pleft, ptop)) {
X		for (i = 0; i < 4-n; ++i)
X			leftrotate();
X		return 0;
X	}
X	redrawboard(pleft, ptop, (pleft+PSIZE), (ptop+PSIZE));
X	return 1;
X}
X
X
X/* Trivial routines to implement the commands. */
X
Xleft()
X{
X	(void) moveby(-1, 0);
X}
X
Xright()
X{
X	(void) moveby(1, 0);
X}
X
Xrot()
X{
X	(void) rotateby(1);
X}
X
X/* Generate a new piece.  Its initial position is just above the top of
X   the board, so that a single move down will show its bottom row.
X   (This is one reason why the pieces are aligned with the bottom in the
X   'shapes' array.) */
X
Xgenerate()
X{
X	pindex = uniform(sizeof shapes / sizeof shapes[0]);
X	
X	piece = shapes[pindex].piece;
X	pleft = (BWIDTH-PSIZE) / 2;
X	ptop = -PSIZE;
X}
X
X/* Start a new game.
X   Reset deley/level, score, board and title; generate a new piece.
X   The game is not restarted immediately. */
X
Xreset()
X{
X	int ih, iv;
X	
X	wsettimer(win, 0);
X	delay = DELAY;
X	score = 0;
X	for (iv = 0; iv < BHEIGHT; ++iv) {
X		for (ih = 0; ih < BWIDTH; ++ih)
X			board[iv][ih] = 0;
X	}
X	generate();
X	redrawboard(0, 0, BWIDTH, BHEIGHT);
X	settitle();
X}
X
X/* Remove any full rows found, shifting the board above down */
X
Xremoverows()
X{
X	int ih, iv;
X	int jv;
X	
X	for (iv = 0; iv < BHEIGHT; ++iv) {
X		for (ih = 0; ih < BWIDTH; ++ih) {
X			if (!board[iv][ih])
X				goto next; /* Two-level continue */
X		}
X		for (jv = iv; jv > 0; --jv) {
X			for (ih = 0; ih < BWIDTH; ++ih)
X				board[jv][ih] = board[jv-1][ih];
X		}
X		for (ih = 0; ih < BWIDTH; ++ih)
X			board[jv][ih] = 0;
X		wscroll(win,
X			bleft, btop,
X			bleft + BWIDTH*sqwidth, btop + (iv+1)*sqheight,
X			0, sqheight);
X	next:	;
X	}
X}
X
X/* Add the score for the current piece to the total score.
X   The title is not regenerated; that is done later in finish(). */
X
Xaddscore()
X{
X	int level = MAX(0, DELAY-delay);
X	int height = MAX(0, BHEIGHT-ptop);
X	
X	score += height + 2*level /* *(advanced?2:1) */ ;
X}
X
X/* Finish a piece off by dropping it; score and generate a new one.
X   Called by the user and from timer of the piece can't move down.
X   This also contains a hack to detect the end of the game:
X   if the new piece can't move one step, it is over. */
X
Xfinish()
X{
X	int ih, iv;
X	
X	addscore();
X	while (moveby(0, 1))
X		;
X	for (iv = 0; iv < PSIZE; ++iv) {
X		for (ih = 0; ih < PSIZE; ++ih) {
X			if (piece[iv][ih] && iv+ptop >= 0)
X				board[iv+ptop][ih+pleft] = 1;
X		}
X	}
X	removerows();
X	generate();
X	settitle();
X	if (moveby(0, 1))
X		wsettimer(win, delay);
X	else {
X		if (alfa) {
X			/* Alfa STDWIN's wmessage doesn't wait for an OK */
X			char buffer[10];
X			strcpy(buffer, "Game over");
X			(void) waskstr("", buffer, strlen(buffer));
X		}
X		else
X			wmessage("Game over");
X		reset();
X	}
X}
X
X/* The clock has ticked.
X   Try to move down; if it can't, finish it off and start a new one. */
X
Xtimer()
X{
X	if (moveby(0, 1))
X		wsettimer(win, delay);
X	else
X		finish();
X}
X
X/* Make the clock tick faster (increase level) */
X
Xfaster()
X{
X	if (delay > 1)
X		--delay;
X	settitle();
X}
X
X/* Make the clock tick slower (decrease level) */
X
Xslower()
X{
X	++delay;
X	settitle();
X}
X
X/* Quit the program.
X   Note that wdone() MUST be called before a STDWIN application exits. */
X
Xquit()
X{
X	wdone();
X	exit(0);
X}
X
X/* Main event loop.
X   React on commands, ignoring illegal ones, and react to clock ticks.
X   Call various routines to execute the commands.
X   Never return; calls quit() to exit. */
X
Xmainloop()
X{
X	EVENT e;
X	
X	for (;;) {
X		wgetevent(&e);
X		
X		switch (e.type) {
X		
X		case WE_TIMER:
X			timer();
X			break;
X		
X		case WE_CHAR:
X			switch (e.u.character) {
X			case '+':
X				faster();
X				break;
X			case '-':
X				slower();
X				break;
X			case 'g':
X				timer();
X				break;
X			case ' ':
X				finish();
X				break;
X			case 'h':
X				left();
X				break;
X			case 'k':
X			 	rot();
X			 	break;
X			case 'l':
X				right();
X				break;
X			case 'q':
X				quit();
X				break;
X			case 'r':
X				reset();
X				break;
X			case '0': case '1': case '2': case '3': case '4':
X			case '5': case '6': case '7': case '8': case '9':
X				delay = DELAY-(e.u.character-'0');
X				delay = MAX(1, delay);
X				settitle();
X				break;
X			}
X			break;
X		
X		case WE_CLOSE:
X			quit();
X			break;
X
X		case WE_COMMAND:
X			switch (e.u.command) {
X			case WC_RETURN:
X				timer();
X				break;
X			case WC_CANCEL:
X				reset();
X				break;
X			case WC_CLOSE:
X				quit();
X				break;
X			case WC_LEFT:
X				left();
X				break;
X			case WC_UP:
X				rot();
X				break;
X			case WC_RIGHT:
X				right();
X				break;
X			}
X			break;
X		
X		}
X	}
X	/*NOTREACHED*/
X}
X
X/* Add a menu, only used as a cheap way to display some help */
X
Xaddhelpmenu()
X{
X	MENU *mp;
X	
X	mp = wmenucreate(1, "Help");
X	wmenuadditem(mp, "g or return starts the game", -1);
X	wmenuadditem(mp, "", -1);
X	wmenuadditem(mp, "h or left arrow moves left", -1);
X	wmenuadditem(mp, "l or right arrow moves right", -1);
X	wmenuadditem(mp, "k or up arrow rotates", -1);
X	wmenuadditem(mp, "space drops", -1);
X	wmenuadditem(mp, "", -1);
X	wmenuadditem(mp, "+/- increases/decreases level (speed)", -1);
X	wmenuadditem(mp, "0-9 chooses level directly", -1);
X	wmenuadditem(mp, "", -1);
X	wmenuadditem(mp, "r or cancel restarts", -1);
X	wmenuadditem(mp, "close or q quits the game", -1);
X}
X
X/* Main program.
X   Initialize STDWIN, create the window and the help menu,
X   reset the game and call 'mainloop()' to play it. */
X
Xmain(argc, argv)
X	int argc;
X	char **argv;
X{
X	long t;
X	
X	time(&t);
X	srand((short)t ^ (short)(t>>16));
X	winitargs(&argc, &argv);
X	if (wlineheight() == 1) {
X		alfa = 1;
X		sqwidth = 2;
X		sqheight = 1;
X		bleft = btop = 0;
X	}
X	wsetdefwinsize(bleft + BWIDTH*sqwidth + 1,
X					btop + BHEIGHT*sqheight + 5);
X	win = wopen("Tetris", drawproc);
X	if (win == NULL) {
X		printf("Can't create window\n");
X		wdone();
X		exit(1);
X	}
X	wsetdocsize(win,
X		bleft + BWIDTH*sqwidth + 1, btop + BHEIGHT*sqheight + 1);
X	addhelpmenu();
X	reset();
X	mainloop();
X	/*NOTREACHED*/
X}
END_OF_FILE
if test 16117 -ne `wc -c <'Appls/tetris/tetris.c'`; then
    echo shar: \"'Appls/tetris/tetris.c'\" unpacked with wrong size!
fi
# end of 'Appls/tetris/tetris.c'
fi
if test -f 'Ports/vtrm/vtrm.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Ports/vtrm/vtrm.c'\"
else
echo shar: Extracting \"'Ports/vtrm/vtrm.c'\" \(34702 characters\)
sed "s/^X//" >'Ports/vtrm/vtrm.c' <<'END_OF_FILE'
X/*
X * Virtual TeRMinal package.
X * (For a description see at the end of this file.)
X *
X * TO DO:
X *	- add interrupt handling (trminterrupt)
X *	- adapt to changed window size when suspended or at SIGWINCH
X *	  (unfortunately, the calling module must be changed first --
X *	  it is not prepared for the changed window size...)
X */
X
X#include "tools.h"
X
Xint tgetent();
Xint tgetnum();
Xint tgetflag();
Xchar *tgetstr();
Xchar *tgoto();
X
X#include "vtrm.h"
X
X#ifdef TRACE
X#define Tprintf(args) fprintf args
X#else
X#define Tprintf(args) /*empty*/
X#endif
X
X#ifdef lint
X#define VOID (void)
X#else
X#define VOID
X#endif
X
X#define Forward
X#define Visible
X#define Hidden static
X#define Procedure
X
Xtypedef char *string;
X#ifndef bool
X#define bool int
X#endif
X#define Yes 1
X#define No  0
X
X#define Min(a,b) ((a) <= (b) ? (a) : (b))
X
X/* Variables needed by termcap's tgoto() or tputs(): */
Xchar PC;
Xchar *BC;
Xchar *UP;
Xshort ospeed;
X
XForward int outchar(); 		/* procedure for termcap's tputs */
X#define Putstr(str)	tputs((str), 1, outchar)
X
X/* termcap terminal capabilities */
X
XHidden int lines;
XHidden int cols;
X
X/*
X * String-valued capabilities are saved in one big array.
X * Extend this only at the end (even though it disturbs the sorting)
X * because otherwise you have to change all macros...
X */
X
X#define par_al_str strcaps[0]	/* parametrized al (AL) */
X#define cap_cm_str strcaps[1]	/* screen-relative cursor motion (CM) */
X#define par_dl_str strcaps[2]	/* parametrized dl (DL) */
X#define al_str strcaps[3] 	/* add new blank line */
X#define cd_str strcaps[4] 	/* clear to end of display */
X#define ce_str strcaps[5] 	/* clear to end of line */
X#define cl_str strcaps[6] 	/* cursor home and clear screen */
X#define cm_str strcaps[7] 	/* cursor motion */
X#define cp_str strcaps[8]	/* cursor position sense reply */
X#define cr_str strcaps[9] 	/* carriage return */
X#define cs_str strcaps[10] 	/* change scrolling region */
X#define dc_str strcaps[11] 	/* delete character */
X#define dl_str strcaps[12] 	/* delete line */
X#define dm_str strcaps[13] 	/* enter delete mode */
X#define do_str strcaps[14] 	/* cursor down one line */
X#define ed_str strcaps[15] 	/* end delete mode */
X#define ei_str strcaps[16] 	/* end insert mode */
X#define ho_str strcaps[17]	/* cursor home */
X#define ic_str strcaps[18] 	/* insert character (if necessary; may pad) */
X#define im_str strcaps[19] 	/* enter insert mode */
X#define nd_str strcaps[20] 	/* cursor right (non-destructive space) */
X#define nl_str strcaps[21]	/* newline */
X#define se_str strcaps[22] 	/* end standout mode */
X#define sf_str strcaps[23] 	/* scroll text up (from bottom of region) */
X#define so_str strcaps[24] 	/* begin standout mode */
X#define sp_str strcaps[25]	/* sense cursor position */
X#define sr_str strcaps[26] 	/* scroll text down (from top of region) */
X#define te_str strcaps[27] 	/* end termcap */
X#define ti_str strcaps[28] 	/* start termcap */
X#define vb_str strcaps[29] 	/* visible bell */
X#define ve_str strcaps[30] 	/* make cursor visible again */
X#define vi_str strcaps[31] 	/* make cursor invisible */
X#define le_str strcaps[32] 	/* cursor left */
X#define bc_str strcaps[33]	/* backspace character */
X#define up_str strcaps[34] 	/* cursor up */
X#define pc_str strcaps[35]	/* pad character */
X#define ks_str strcaps[36]	/* keypad mode start */
X#define ke_str strcaps[37]	/* keypad mode end */
X/* Insert new entries here only! Don't forget to change the next line! */
X#define NSTRCAPS 38 /* One more than the last entry's index */
X
XHidden char *strcaps[NSTRCAPS];
XHidden char strcapnames[] =
X"ALCMDLalcdceclcmcpcrcsdcdldmdoedeihoicimndnlsesfsospsrtetivbvevilebcuppckske";
X
X/* Same for Boolean-valued capabilities */
X
X#define has_am flagcaps[0]	/* has automatic margins */
X#define has_da flagcaps[1]	/* display may be retained above screen */
X#define has_db flagcaps[2]	/* display may be retained below screen */
X#define has_in flagcaps[3]	/* not safe to have null chars on the screen */
X#define has_mi flagcaps[4]	/* move safely in insert (and delete?) mode */
X#define has_ms flagcaps[5]	/* move safely in standout mode */
X#define has_xs flagcaps[6]	/* standout not erased by overwriting */
X#define has_bs flagcaps[7]	/* terminal can backspace */
X#define hardcopy flagcaps[8]	/* hardcopy terminal */
X#define has_xn flagcaps[9]	/* Vt100 / Concept glitch */
X#define NFLAGS 10
X
XHidden char flagcaps[NFLAGS];
XHidden char flagnames[]= "amdadbinmimsxsbshcxn";
X
XHidden Procedure
Xgetcaps(parea)
X	register char **parea;
X{
X	register char *capname;
X	register char **capvar;
X	register char *flagvar;
X
X	for (capname= flagnames, flagvar= flagcaps;
X			*capname != '\0'; capname += 2, ++flagvar)
X		*flagvar= tgetflag(capname);
X
X	for (capname= strcapnames, capvar= strcaps;
X			*capname != '\0'; capname += 2, ++capvar)
X		*capvar= tgetstr(capname, parea);
X}
X
X/* terminal status */
X
X/* calling order of Visible Procs */
XHidden bool started = No;
X
X/* to exports the capabilities mentioned in vtrm.h: */
XHidden int flags = 0;
X
X/* cost for impossible operations */
X#define Infinity 9999
X	/* Allow for adding Infinity+Infinity within range */
X	/* (Range is assumed at least 2**15 - 1) */
X
X/* The following for all sorts of undefined things (except for UNKNOWN char) */
X#define Undefined (-1)
X
X/* current mode of putting char's */
X#define Normal	0
X#define Insert	1
X#define	Delete	2
XHidden short mode = Normal;
X
X/* current standout mode */
X#define Off	0
X#define On	0200
XHidden short so_mode = Off;
X
X/* masks for char's and short's */
X#define NULCHAR	'\000'
X#define CHAR	0177
X#define SOBIT	On
X#define SOCHAR	0377
X/* if (has_xs) record cookies placed on screen in extra bit */
X/* type of cookie is determined by the SO bit */
X#define XSBIT	0400
X#define SOCOOK	0600
X#define COOKBITS SOCOOK
X#define UNKNOWN	1
X#define NOCOOK	UNKNOWN
X
X/* current cursor position */
XHidden short cur_y = Undefined, cur_x = Undefined;
X
X/* "line[y][x]" holds the char on the terminal, with the SOBIT and XSBIT.
X * the SOBIT tells whether the character is standing out, the XSBIT whether
X * there is a cookie on the screen at this position.
X * In particular a standend-cookie may be recorded AFTER the line
X * (just in case some trmputdata will write after that position).
X * "lenline[y]" holds the length of the line.
X * Unknown chars will be 1, so the optimising compare in putline will fail.
X * (Partially) empty lines are distinghuished by "lenline[y] < cols".
X */
XHidden short **line = 0, *lenline = 0;
X
X/* Clear the screen initially iff only memory cursor addressing available */
XHidden bool mustclear = No;
X
X/* Make the cursor invisible when trmsync() tries to move outside the screen */
XHidden bool no_cursor = No;
X
X/* Optimise cursor motion */
XHidden int abs_cost; 		/* cost of absolute cursor motion */
XHidden int cr_cost; 		/* cost of carriage return */
XHidden int do_cost; 		/* cost of down */
XHidden int le_cost; 		/* cost of left */
XHidden int nd_cost; 		/* cost of right */
XHidden int up_cost; 		/* cost of up */
X
X/* Optimise trailing match in put_line, iff the terminal can insert and delete
X * characters; the cost per n characters will be:
X * 	n * MultiplyFactor + OverHead
X */
XHidden int ins_mf, ins_oh, del_mf, del_oh;
XHidden int ed_cost, ei_cost; 		/* used in move() */
X
X/* The type of scrolling possible determines which routines get used;
X * these may be:
X * (1) with addline and deleteline (termcap: al_str & dl_str);
X * (2) with a settable scrolling region, like VT100 (cs_str, sr_str, sf_str);
X * (3) no scrolling available. (NOT YET IMPLEMENTED)
X */
XHidden Procedure (*scr_up)();
XHidden Procedure (*scr_down)();
XForward Procedure scr1up();
XForward Procedure scr1down();
XForward Procedure scr2up();
XForward Procedure scr2down();
X/*Forward Procedure scr3up(); */
X/*Forward Procedure scr3down(); */
X
X/*
X * Starting, Ending and (fatal) Error.
X */
X
X/* 
X * Initialization call.
X * Determine terminal capabilities from termcap.
X * Set up tty modes.
X * Start up terminal and internal administration.
X * Return 0 if all well, error code if in trouble.
X */
XVisible int
Xtrmstart(plines, pcols, pflags)
Xint *plines;
Xint *pcols;
Xint *pflags;
X{
X	register int err;
X	
X	Tprintf((stderr, "\ttrmstart(&li, &co, &fl);\n"));
X	if (started)
X		return TE_TWICE;
X	err= gettermcaps();
X	if (err != TE_OK)
X		return err;
X	err= setttymode();
X	if (err != TE_OK)
X		return err;
X	err= start_trm();
X	if (err != TE_OK) {
X		trmend();
X		return err;
X	}
X
X	*plines = lines;
X	*pcols = cols;
X	*pflags = flags;
X
X	started = Yes;
X	return TE_OK;
X}
X
X/*
X * Termination call.
X * Reset tty modes, etc.
X * Beware that it might be called by a caught interrupt even in the middle
X * of trmstart()!
X */
XVisible Procedure
Xtrmend()
X{
X	Tprintf((stderr, "\ttrmend();\n"));
X	set_mode(Normal);
X	if (so_mode != Off)
X		standend();
X	Putstr(ke_str);
X	Putstr(te_str);
X	VOID fflush(stdout);
X	resetttymode();
X
X	started = No;
X}
X
X/*
X * Set all internal statuses to undefined, especially the contents of
X * the screen, so a hard redraw will not be optimised to heaven.
X */
XVisible Procedure
Xtrmundefined()
X{
X	register int y, x;
X	Tprintf((stderr, "\ttrmundefined();\n"));
X
X	cur_y = cur_x = Undefined;
X	mode = so_mode = Undefined;
X	
X	for (y = 0; y < lines; y++) {
X		for (x = 0; x <= cols; x++)
X			line[y][x] = 1; /* impossible char, no so bits */
X		lenline[y] = cols;
X	}
X}
X
X#ifndef NDEBUG
X/*ARGSUSED*/
XHidden Procedure
Xcheck_started(m)
X	char *m;
X{
X	if (!started) {
X		trmend();
X		fprintf(stderr, "bad VTRM call\n");
X		abort();
X	}
X}
X#else
X#define check_started(m) /*empty*/
X#endif
X
XHidden int ccc;
X
X/*ARGSUSED*/
XHidden Procedure
Xcountchar(ch)
Xchar ch;
X{
X	ccc++;
X}
X
XHidden int
Xstrcost(str)
Xchar *str;
X{
X	if (str == NULL)
X		return Infinity;
X	return str0cost(str);
X}
X
XHidden int
Xstr0cost(str)
Xchar *str;
X{
X	ccc = 0;
X	tputs(str, 1, countchar);
X	return ccc;
X}
X
X/*
X * Get terminal capabilities from termcap and compute related static
X * properties.  Return TE_OK if all well, error code otherwise.
X */
X
XHidden int
Xgettermcaps() 
X{
X	string trmname;
X	static char tc_buf[1024]; /* Static so main program can get keydefs */
X	static char strbuf[1024]; /* Buffer where our caps are saved */
X	char *area = strbuf;
X	int sg;
X	static bool tc_initialized = No;
X
X	if (tc_initialized)
X		return TE_OK;
X	
X	trmname=getenv("TERM");
X	if (trmname == NULL || trmname[0] == '\0')
X		return TE_NOTERM;
X	if (tgetent(tc_buf, trmname) != 1)
X		return TE_BADTERM;
X
X	getcaps(&area); /* Read all flag and string type capabilities */
X	if (hardcopy)
X		return TE_DUMB;
X	BC = le_str;
X	if (BC == NULL) {
X		BC = bc_str;
X		if (BC == NULL) {
X			if (has_bs)
X				BC = "\b";
X			else
X				return TE_DUMB;
X		}
X	}
X	UP = up_str;
X	if (UP == NULL)
X		return TE_DUMB;
X	PC = (pc_str != NULL? pc_str[0] : NULCHAR);
X
X	if (cm_str == NULL) {
X		cm_str = cap_cm_str;
X		if (cm_str == NULL) {
X			if (ho_str == NULL || do_str == NULL || nd_str == NULL)
X				return TE_DUMB;
X		}
X		else
X			mustclear = Yes;
X	}
X	if (al_str && dl_str) {
X		scr_up = scr1up;
X		scr_down = scr1down;
X		flags |= CAN_SCROLL;
X	}
X	else {
X		if (sf_str == NULL)
X			sf_str = "\n";
X		if (cs_str && sr_str) {
X			scr_up = scr2up;
X			scr_down = scr2down;
X			flags |= CAN_SCROLL;
X		}
X		else
X			return TE_DUMB;
X	}
X		
X	lines = tgetnum("li");
X	cols = tgetnum("co");
X	if (lines <= 0) lines = 24;
X	if (cols <= 0) cols = 80;
X	
X	if ((sg=tgetnum("sg")) == 0)
X		has_xs = Yes;
X	else if (sg > 0)
X		return TE_DUMB;
X	
X	if (!ce_str)
X		return TE_DUMB;
X	if (cr_str == NULL) cr_str = "\r";
X	if (do_str == NULL) {
X		do_str = nl_str;
X		if (do_str == NULL) do_str = "\n";
X	}
X	le_str = BC;
X	up_str = UP;
X	if (vb_str == NULL) 	/* then we will do with the audible bell */
X		vb_str = "\007";
X	
X	/* cursor sensing (non standard) */
X	if (cp_str != NULL && sp_str != NULL)
X		flags |= CAN_SENSE;
X
X	if (so_str != NULL && se_str != NULL)
X		flags |= HAS_STANDOUT;
X
X	/* calculate costs of local and absolute cursor motions */
X	if (cm_str == NULL)
X		abs_cost = Infinity;
X	else
X		abs_cost = strcost(tgoto(cm_str, 0, 0));
X	cr_cost = strcost(cr_str);
X	do_cost = strcost(do_str);
X	le_cost = strcost(le_str);
X	nd_cost = strcost(nd_str);
X	up_cost = strcost(up_str);
X
X	/* cost of leaving insert or delete mode, used in move() */
X	ei_cost = str0cost(ei_str);
X	ed_cost = str0cost(ed_str);
X	
X	/* calculate insert and delete cost multiply_factor and overhead */
X	if (((im_str && ei_str) || ic_str) && dc_str) {
X		flags |= CAN_OPTIMISE;
X		ins_mf = 1 + str0cost(ic_str);
X		ins_oh = str0cost(im_str) + ei_cost;
X		del_mf = str0cost(dc_str);
X		del_oh = str0cost(dm_str) + ed_cost;
X	}
X		
X	tc_initialized = Yes;
X	return TE_OK;
X}
X
XHidden int
Xstart_trm()
X{
X	register int y;
X	int newlines, newcols;
X	
X	gettruewinsize(&newlines, &newcols);
X	if (newcols != 0 && newcols != cols ||
X		newlines != 0 && newlines != lines) {
X		/* Window size has changed.
X		   Release previously allocated buffers. */
X		if (line != NULL) {
X			for (y= 0; y < lines; ++y)
X				free((char *) line[y]);
X			free((char *) line);
X			line= NULL;
X		}
X		if (lenline != NULL) {
X			free((char *) lenline);
X			lenline= NULL;
X		}
X		if (newcols != 0)
X			cols = newcols;
X		if (newlines != 0)
X			lines = newlines;
X	}
X
X	if (line == NULL) {
X		line = (short **) malloc((unsigned) (lines * sizeof(short*)));
X		if (line == NULL)
X			return TE_NOMEM;
X		for (y = 0; y < lines; y++) {
X			line[y] = (short *)
X				malloc((unsigned) ((cols+1) * sizeof(short)));
X			if (line == NULL)
X				return TE_NOMEM;
X		}
X	}
X	if (lenline == NULL) {
X		lenline = (short *) malloc((unsigned) (lines * sizeof(short)));
X		if (lenline == NULL)
X			return TE_NOMEM;
X	}
X
X	trmundefined();
X
X	Putstr(ti_str);
X	Putstr(ks_str);
X	if (cs_str)
X		Putstr(tgoto(cs_str, lines-1, 0));
X	if (mustclear)
X		clear_lines(0, lines-1);
X	return TE_OK;
X}
X
X
X/*
X * Sensing and moving the cursor.
X */
X
X/*
X * Sense the current (y, x) cursor position, after a possible manual
X * change by the user with local cursor motions.
X * If the terminal cannot be asked for the current cursor position,
X * or if the string returned by the terminal is garbled,
X * the position is made Undefined.
X */
X
XVisible Procedure
Xtrmsense(py, px)
X	int *py;
X	int *px;
X{
X	bool getpos();
X
X	Tprintf((stderr, "\ttrmsense(&yy, &xx);\n"));
X	check_started("trmsense");
X
X	*py = *px = Undefined;
X	set_mode(Normal);
X	if (so_mode != Off)
X		standend();
X	
X	if (flags&CAN_SENSE && getpos(py, px)) {
X		if (*py < 0 || lines <= *py || *px < 0 || cols <= *px)
X			*py = *px = Undefined;
X	}
X	cur_y = Undefined;
X	cur_x = Undefined;
X}
X
XHidden bool
Xgetpos(py, px)
Xint *py, *px;
X{
X	char *format = cp_str;
X	int fc; 		/* current format character */
X	int ic; 		/* current input character */
X	int num;
X	int on_y = 1;
X	bool incr_orig = No;
X	int i, ni;
X
X	Putstr(sp_str);
X	VOID fflush(stdout);
X
X	while (fc = *format++) {
X		if (fc != '%') {
X			if (trminput() != fc)
X				return No;
X		}
X		else {
X			switch (fc = *format++) {
X			case '%':
X				if (trminput() != '%')
X					return No;
X				continue;
X			case 'r':
X				on_y = 1 - on_y;
X				continue;
X			case 'i':
X				incr_orig = Yes;
X				continue;
X			case 'd':
X				ic = trminput();
X				if (!isdigit(ic))
X					return No;
X				num = ic - '0';
X				while (isdigit(ic=trminput()))
X					num = 10*num + ic - '0';
X				trmpushback(ic);
X				break;
X			case '2':
X			case '3':
X				ni = fc - '0';
X		    		num = 0;
X				for (i=0; i<ni; i++) {
X					ic = trminput();
X					if (isdigit(ic))
X						num = 10*num + ic - '0';
X					else
X						return No;
X				}
X				break;
X			case '+':
X				num = trminput() - *format++;
X				break;
X			case '-':
X				num = trminput() + *format++;
X				break;
X			default:
X				return No;
X			}
X			/* assign num to parameter */
X			if (incr_orig)
X				num--;
X			if (on_y)
X				*py = num;
X			else
X				*px = num;
X			on_y = 1 - on_y;
X		}
X	}
X
X	return Yes;
X}
X		
X/* 
X * To move over characters by rewriting them, we have to check:
X * (1) that the screen has been initialised on these positions;
X * (2) we do not screw up characters
X * when rewriting line[y] from x_from upto x_to
X */
XHidden bool
Xrewrite_ok(y, xfrom, xto)
Xint y, xfrom, xto;
X{
X	register short *plnyx, *plnyto;
X	
X	if (xto > lenline[y])
X		return No;
X
X	plnyto = &line[y][xto];
X	for (plnyx = &line[y][xfrom]; plnyx <= plnyto; plnyx++)
X		if (*plnyx == UNKNOWN
X		    ||
X		    (!has_xs && (*plnyx & SOBIT) != so_mode)
X		   )
X			return No;
X	return Yes;
X}
X		
X/*
X * Move to position y,x on the screen
X */
X/* possible move types for y and x respectively: */
X#define None	0
X#define Down	1
X#define Up	2
X#define Right	1
X#define ReWrite	2
X#define Left	3
X#define CrWrite	4
X
XHidden Procedure
Xmove(y, x)
Xint y, x;
X{
X	int dy, dx;
X	int y_cost, x_cost, y_move, x_move;
X	int mode_cost;
X	int xi;
X	
X	if (cur_y == y && cur_x == x)
X		return;
X	
X	if (!has_mi || mode == Undefined)
X		set_mode(Normal);
X	if (!has_xs && ((!has_ms && so_mode != Off) || so_mode == Undefined))
X		standend();
X	
X	if (cur_y == Undefined || cur_x == Undefined)
X		goto absmove;
X	
X	dy = y - cur_y;
X	dx = x - cur_x;
X
X	if (dy > 0) {
X		y_move = Down;
X		y_cost = dy * do_cost;
X	}
X	else if (dy < 0) {
X		y_move = Up;
X		y_cost = -dy * up_cost;
X	}
X	else {
X		y_move = None;
X		y_cost = 0;
X	}
X	if (y_cost < abs_cost) {
X		switch (mode) {
X		case Normal:
X			mode_cost = 0;
X			break;
X		case Insert:
X			mode_cost = ei_cost;
X			break;
X		case Delete:
X			mode_cost = ed_cost;
X			break;
X		}
X		if (dx > 0) {
X			x_cost = dx + mode_cost;
X			if (dx*nd_cost < x_cost || !rewrite_ok(y, cur_x, x)) {
X				x_cost = dx * nd_cost;
X				x_move = Right;
X			}
X			else
X				x_move = ReWrite;
X		}
X		else if (dx < 0) {
X			x_cost = -dx * le_cost;
X			x_move = Left;
X		}
X		else {
X			x_cost = 0;
X			x_move = None;
X		}
X		if (cr_cost + x + mode_cost < x_cost && rewrite_ok(y, 0, x)) {
X			x_move = CrWrite;
X			x_cost = cr_cost + x + mode_cost;
X		}
X	}
X	else
X		x_cost = abs_cost;
X
X	if (y_cost + x_cost < abs_cost) {
X		switch (y_move) {
X		case Down:
X			while (dy-- > 0) Putstr(do_str);
X			break;
X		case Up:
X			while (dy++ < 0) Putstr(up_str);
X			break;
X		}
X		switch (x_move) {
X		case Right:
X			while (dx-- > 0) Putstr(nd_str);
X			break;
X		case Left:
X			while (dx++ < 0) Putstr(le_str);
X			break;
X		case CrWrite:
X			Putstr(cr_str);
X			cur_x = 0;
X			/* FALL THROUGH */
X		case ReWrite:
X			set_mode(Normal);
X			for (xi = cur_x; xi < x; xi++)
X				putchar(line[y][xi]);
X			break;
X		}
X	}
X	else
X	{
X    absmove:
X		if (cm_str == NULL) {
X			Putstr(ho_str);
X			for (cur_y = 0; cur_y < y; ++cur_y)
X				Putstr(do_str);
X			/* Should try to use tabs here: */
X			for (cur_x = 0; cur_x < x; ++cur_x)
X				Putstr(nd_str);
X		}
X		else
X			Putstr(tgoto(cm_str, x, y));
X	}
X	
X	cur_y = y;
X	cur_x = x;
X}
X
X
X/*
X * Putting data on the screen.
X */
X
X/*
X * Fill screen area with given data.
X * Characters with the SO-bit (0200) set are put in standout mode.
X */
XVisible Procedure
Xtrmputdata(yfirst, ylast, indent, data)
Xint yfirst;
Xint ylast;
Xregister int indent;
Xregister string data;
X{
X	register int y;
X	int x, len, lendata, space;
X		
X	Tprintf((stderr, "\ttrmputdata(%d, %d, %d, \"%s\");\n", yfirst, ylast, indent, data));
X	check_started("trmputdata");
X	
X	if (yfirst < 0)
X		yfirst = 0;
X	if (ylast >= lines)
X		ylast = lines-1;
X	space = cols*(ylast-yfirst+1) - indent;
X	if (space <= 0)
X		return;
X	yfirst += indent/cols;
X	indent %= cols;
X	y= yfirst;
X	if (!data)
X		data= ""; /* Safety net */
X	x = indent;
X	lendata = strlen(data);
X	if (ylast == lines-1 && lendata >= space)
X		lendata = space - 1;
X	len = Min(lendata, cols-x);
X	while (y <= ylast) {
X		put_line(y, x, data, len);
X		y++;
X		lendata -= len;
X		if (lendata > 0) {
X			x = 0;
X			data += len;
X			len = Min(lendata, cols);
X		}
X		else
X			break;
X	}
X	if (y <= ylast)
X		clear_lines(y, ylast);
X}
X
X/* 
X * We will first try to get the picture:
X *
X *                  op>>>>>>>>>>>op          oq<<<<<<<<<<<<<<<<<<<<<<<<oq
X *                  ^            ^           ^                         ^
X *           <xskip><-----m1----><----od-----><-----------m2----------->
X *   OLD:   "You're in a maze of twisty little pieces of code, all alike"
X *   NEW:          "in a maze of little twisting pieces of code, all alike"
X *                  <-----m1----><-----nd------><-----------m2----------->
X *                  ^            ^             ^                         ^
X *                  np>>>>>>>>>>>np            nq<<<<<<<<<<<<<<<<<<<<<<<<nq
X * where
X *	op, oq, np, nq are pointers to start and end of Old and New data,
X * and
X *	xskip = length of indent to be skipped,
X *	m1 = length of Matching part at start,
X *	od = length of Differing mid on screen,
X *	nd = length of Differing mid in data to be put,
X *	m2 = length of Matching trail.
X *
X * Then we will try to find a long blank-or-cleared piece in <nd+m2>:
X *
X *    <---m1---><---d1---><---nb---><---d2---><---m2--->
X *              ^         ^         ^        ^         ^
X *              np        bp        bq1      nq        nend
X * where
X *	bp, bq are pointers to start and AFTER end of blank piece,
X * and
X *	d1 = length of differing part before blank piece,
X *	nb = length of blank piece to be skipped,
X *	d2 = length of differing part after blank piece.
X * Remarks:
X *	d1 + nb + d2 == nd,
X * and
X *	d2 maybe less than 0.
X */
XHidden int
Xput_line(y, xskip, data, len)
Xint y, xskip;
Xstring data;
Xint len;
X{
X	register short *op, *oq;
X	register char *np, *nq, *nend;
X	char *bp, *bq1, *p, *q;
X	int m1, m2, od, nd, delta, dd, d1, nb, d2;
X	bool skipping;
X	int cost, o_cost; 	/* normal and optimising cost */
X	
X	/* Bugfix GvR 19-June-87: */
X	while (lenline[y] < xskip)
X		line[y][lenline[y]++] = ' ';
X	
X	/* calculate the magic parameters */
X	op = &line[y][xskip];
X	oq = &line[y][lenline[y]-1];
X	np = data;
X	nq = nend = data + len - 1;
X	m1 = m2 = 0;
X	while ((*op&SOCHAR) == (((short)*np)&SOCHAR) && op <= oq && np <= nq)
X		op++, np++, m1++;
X	if (flags & CAN_OPTIMISE)
X		while ((*oq&SOCHAR) == (((short)*nq)&SOCHAR) && op <= oq && np <= nq)
X			oq--, nq--, m2++;
X	od = oq - op + 1;
X	nd = nq - np + 1;
X	/* now we have the first picture above */
X
X	if (od==0 && nd==0)
X		return;
X	delta = nd - od;
X
X	/* find the blank piece */
X	p = q = bp = bq1 = np;
X	oq += m2; 		/* back to current eol */
X	if (!has_in) {
X		while (p <= nend) {
X			while (q<=nend && *q==' ' && (op>oq || *op==' '))
X				q++, op++;
X			if (q - p > bq1 - bp)
X				bp = p, bq1 = q;
X			p = ++q;
X			op++;
X		}
X	}
X	d1 = bp - np;
X	nb = bq1 - bp;
X	d2 = nq - bq1 + 1;
X	
X	/* what is cheapest:
X	 *	normal: put nd+m2;                         (dd = nd+m2)
X	 *	skipping: put d1, skip nb, put d2+m2;      (dd = d2+m2)
X	 *	optimise: put dd, insert or delete delta.  (dd = min(od,nd))
X	 */
X	cost = nd + m2; 	/* normal cost */
X	if (nb > abs_cost || (d1 == 0 && nb > 0)) {
X		skipping = Yes;
X		cost -= nb - (d1>0 ? abs_cost : 0); /* skipping cost */
X		dd = d2;
X	}
X	else {
X		skipping = No;
X		dd = nd;
X	}
X	
X	if (m2 != 0) {
X		/* try optimising */
X		o_cost = Min(od, nd);
X		if (delta > 0)
X			o_cost += delta * ins_mf + ins_oh;
X		else if (delta < 0)
X			o_cost += -delta * del_mf + del_oh;
X		if (o_cost >= cost) {
X			/* discard m2, no optimise */
X			dd += m2;
X			m2 = 0;
X		}
X		else {
X			dd = Min(od, nd);
X			skipping = No;
X		}
X	}
X
X	/* and now for the real work */
X	if (!skipping || d1 > 0)
X		move(y, xskip + m1);
X
X	if (has_xs)
X		get_so_mode();
X	
X	if (skipping) {
X		if (d1 > 0) {
X			set_mode(Normal);
X			put_str(np, d1, No);
X		}
X		if (has_xs && so_mode != Off)
X			standend();
X		set_blanks(y, xskip+m1+d1, xskip+m1+d1+nb);
X		if (dd != 0 || delta < 0) {
X			move(y, xskip+m1+d1+nb);
X			np = bq1;
X		}
X	}
X	
X	if (dd > 0) {
X		set_mode(Normal);
X		put_str(np, dd, No);
X	}
X	
X	if (m2 > 0) {
X		if (delta > 0) {
X			set_mode(Insert);
X			ins_str(np+dd, delta);
X		}
X		else if (delta < 0) {
X			if (so_mode != Off)
X				standend();
X				/* Some terminals fill with standout spaces! */
X			set_mode(Delete);
X			del_str(-delta);
X		}
X	}
X	else {
X		if (delta < 0) {
X			clr_to_eol();
X			return;
X		}
X	}
X	
X	lenline[y] = xskip + len;
X	if (cur_x == cols) {
X		if (!has_mi)
X			set_mode(Normal);
X		if (!has_ms)
X			so_mode = Undefined;
X		if (has_am) {
X			if (has_xn)
X				cur_y= Undefined;
X			else
X				cur_y++;
X		}
X		else
X			Putstr(cr_str);
X		cur_x = 0;
X	}
X	else if (has_xs) {
X		if (m2 == 0) {
X			if (so_mode == On)
X				standend();
X		}
X		else {
X			if (!(line[cur_y][cur_x] & XSBIT)) {
X				if (so_mode != (line[cur_y][cur_x] & SOBIT))
X					(so_mode ? standend() : standout());
X			}
X		}
X	}
X}
X
XHidden Procedure
Xset_mode(m)
Xint m;
X{
X	if (m == mode)
X		return;
X	switch (mode) {
X	case Insert:
X		Putstr(ei_str);
X		break;
X	case Delete:
X		Putstr(ed_str);
X		break;
X	case Undefined:
X		Putstr(ei_str);
X		Putstr(ed_str);
X		break;
X	}
X	switch (m) {
X	case Insert:
X		Putstr(im_str);
X		break;
X	case Delete:
X		Putstr(dm_str);
X		break;
X	}
X	mode = m;
X}
X
XHidden Procedure
Xget_so_mode()
X{
X	if (cur_x >= lenline[cur_y] || line[cur_y][cur_x] == UNKNOWN)
X		so_mode = Off;
X	else
X		so_mode = line[cur_y][cur_x] & SOBIT;
X}
X
XHidden Procedure
Xstandout()
X{
X	Putstr(so_str);
X	so_mode = On;
X	if (has_xs)
X		line[cur_y][cur_x] |= SOCOOK;
X}
X
XHidden Procedure
Xstandend()
X{
X	Putstr(se_str);
X	so_mode = Off;
X	if (has_xs)
X		line[cur_y][cur_x] = (line[cur_y][cur_x] & ~SOBIT) | XSBIT;
X}
X
XHidden Procedure
Xput_str(data, n, inserting)
Xchar *data;
Xint n;
Xbool inserting;
X{
X	register short c, so;
X	short *ln_y_x, *ln_y_end;
X	
X	so = so_mode;
X	if (has_xs) {
X		ln_y_x = &line[cur_y][cur_x];
X		ln_y_end = &line[cur_y][lenline[cur_y]];
X	}
X	while (n-- > 0) {
X		if (has_xs && ln_y_x <= ln_y_end && ((*ln_y_x)&XSBIT))
X			so = so_mode = (*ln_y_x)&SOBIT;
X			/* this also checks for the standend cookie AFTER */
X			/* the line because off the equals sign in <= */
X		c = ((short)(*data++))&SOCHAR;
X		if ((c&SOBIT) != so) {
X			so = c&SOBIT;
X			so ? standout() : standend();
X 		}
X		if (inserting)
X			Putstr(ic_str);
X		put_c(c);
X		if (has_xs)
X			ln_y_x++;
X	}
X}
X
XHidden Procedure
Xins_str(data, n)
Xchar *data;
Xint n;
X{
X	int x;
X	
X	/* x will start AFTER the line, because there might be a cookie */
X	for (x = lenline[cur_y]; x >= cur_x; x--)
X		line[cur_y][x+n] = line[cur_y][x];
X	put_str(data, n, Yes);
X}
X
XHidden Procedure
Xdel_str(n)
Xint n;
X{
X	int x, xto;
X	
X	xto = lenline[cur_y] - n; /* again one too far because of cookie */
X	if (has_xs) {
X		for (x = cur_x + n; x >= cur_x; x--) {
X			if (line[cur_y][x] & XSBIT)
X				break;
X		}
X		if (x >= cur_x)
X			line[cur_y][cur_x+n] =
X				(line[cur_y][cur_x+n] & CHAR)
X				|
X				(line[cur_y][x] & COOKBITS);
X	}
X	for (x = cur_x; x <= xto; x++)
X		line[cur_y][x] = line[cur_y][x+n];
X	while (n-- > 0)
X		Putstr(dc_str);
X}
X
XHidden Procedure
Xput_c(c)
Xint c;
X{
X	char ch;
X	short xs_flag;
X	
X	ch = c&CHAR;
X	if (!isprint(ch) && ch != ' ') /* V7 isprint doesn't include blank */
X		ch= '?';
X	putchar(ch);
X	if (has_xs)
X		xs_flag = line[cur_y][cur_x]&XSBIT;
X	else
X		xs_flag = 0;
X	line[cur_y][cur_x] = (c&SOCHAR)|xs_flag;
X	cur_x++;
X}
X
XHidden Procedure
Xclear_lines(yfirst, ylast)
Xint yfirst, ylast ;
X{
X	register int y;
X	
X	if (!has_xs && so_mode != Off)
X		standend();
X	if (cl_str && yfirst == 0 && ylast == lines-1) {
X		Putstr(cl_str);
X		cur_y = cur_x = 0;
X		for (y = 0; y < lines; ++y) {
X			lenline[y] = 0;
X			if (has_xs) line[y][0] = NOCOOK;
X		}
X		return;
X	}
X	for (y = yfirst; y <= ylast; y++) {
X		if (lenline[y] > 0) {
X			move(y, 0);
X			if (ylast == lines-1 && cd_str) {
X				Putstr(cd_str);
X				while (y <= ylast) {
X					if (has_xs) line[y][0] = NOCOOK;
X					lenline[y++] = 0;
X				}
X				break;
X			}
X			else {
X				clr_to_eol();
X			}
X		}
X	}
X}
X
XHidden Procedure
Xclr_to_eol()
X{
X	lenline[cur_y] = cur_x;
X	if (!has_xs && so_mode != Off)
X		standend();
X	Putstr(ce_str);
X	if (has_xs) {
X		if (cur_x == 0)
X			line[cur_y][0] = NOCOOK;
X		else if (line[cur_y][cur_x-1]&SOBIT)
X			standend();
X	}
X}
X
XHidden Procedure
Xset_blanks
X(y, xfrom, xto)
Xint y, xfrom, xto;
X{
X	register int x;
X	
X	for (x = xfrom; x < xto; x++) {
X		line[y][x] = (line[y][x]&XSBIT) | ' ';
X	}
X}
X
X/* 
X * outchar() is used by termcap's tputs;
X * we can't use putchar because that's probably a macro
X */
XHidden int
Xoutchar(ch)
Xchar ch;
X{
X	putchar(ch);
X}
X
X/*
X * Scrolling (part of) the screen up (or down, dy<0).
X */
X
XVisible Procedure
Xtrmscrollup(yfirst, ylast, by)
Xregister int yfirst;
Xregister int ylast;
Xregister int by;
X{
X	Tprintf((stderr, "\ttrmscrollup(%d, %d, %d);\n", yfirst, ylast, by));
X	check_started("trmscrollup");
X	
X	if (yfirst < 0)
X		yfirst = 0;
X	if (ylast >= lines)
X		ylast = lines-1;
X
X	if (yfirst > ylast)
X		return;
X
X	if (!has_xs && so_mode != Off)
X		standend();
X	
X	if (by > 0 && yfirst + by > ylast
X	    ||
X	    by < 0 && yfirst - by > ylast)
X	{
X		clear_lines(yfirst, ylast);
X		return;
X	}
X	
X	if (by > 0) {
X		(*scr_up)(yfirst, ylast, by);
X		scr_lines(yfirst, ylast, by, 1);
X	}
X	else if (by < 0) {
X		(*scr_down)(yfirst, ylast, -by);
X		scr_lines(ylast, yfirst, -by, -1);
X	}
X}
X
XHidden Procedure
Xscr_lines(yfrom, yto, n, dy)
Xint yfrom, yto, n, dy;
X{
X	register int y;
X	short *saveln;
X	
X	while (n-- > 0) {
X		saveln = line[yfrom];
X		for (y = yfrom; y != yto; y += dy) {
X			line[y] = line[y+dy];
X			lenline[y] = lenline[y+dy];
X		}
X		line[yto] = saveln;
X		lenline[yto] = 0;
X		if (has_xs) line[yto][0] = NOCOOK;
X	}
X}
X
XHidden Procedure
Xscr1up(yfirst, ylast, n)
X	int yfirst;
X	int ylast;
X	int n;
X{
X	move(yfirst, 0);
X	dellines(n);
X	if (ylast < lines-1) {
X		move(ylast-n+1, 0);
X		addlines(n);
X	}
X}
X
X
XHidden Procedure
Xscr1down(yfirst, ylast, n)
X	int yfirst;
X	int ylast;
X	int n;
X{
X	if (ylast == lines-1) {
X		clear_lines(ylast-n+1, ylast);
X	}
X	else {
X		move(ylast-n+1, 0);
X		dellines(n);
X	}
X	move(yfirst, 0);
X	addlines(n);
X}
X
X
XHidden Procedure
Xaddlines(n)
Xregister int n;
X{
X	if (par_al_str && n > 1)
X			Putstr(tgoto(par_al_str, n, n));
X	else {
X		while (n-- > 0)
X			Putstr(al_str);
X	}
X}
X
X
XHidden Procedure
Xdellines(n)
Xregister int n;
X{
X	if (par_dl_str && n > 1)
X		Putstr(tgoto(par_dl_str, n, n));
X	else {
X		while (n-- > 0)
X			Putstr(dl_str);
X	}
X}
X
X
XHidden Procedure
Xscr2up(yfirst, ylast, n)
Xint yfirst, ylast, n;
X{
X	Putstr(tgoto(cs_str, ylast, yfirst));
X	cur_y = cur_x = Undefined;
X	move(ylast, 0);
X	while (n-- > 0) {
X		Putstr(sf_str);
X		if (has_db && ylast == lines-1)
X			clr_to_eol();
X	}
X	Putstr(tgoto(cs_str, lines-1, 0));
X	cur_y = cur_x = Undefined;
X}
X
X
XHidden Procedure
Xscr2down(yfirst, ylast, n)
Xint yfirst, ylast, n;
X{
X	Putstr(tgoto(cs_str, ylast, yfirst));
X	cur_y = cur_x = Undefined;
X	move(yfirst, 0);
X	while (n-- > 0) {
X		Putstr(sr_str);
X		if (has_da && yfirst == 0)
X			clr_to_eol();
X	}
X	Putstr(tgoto(cs_str, lines-1, 0));
X	cur_y = cur_x = Undefined;
X}
X
X
X/*
X * Synchronization, move cursor to given position (or previous if < 0).
X */
X
XVisible Procedure
Xtrmsync(y, x)
X	int y;
X	int x;
X{
X	Tprintf((stderr, "\ttrmsync(%d, %d);\n", y, x));
X	check_started("trmsync");
X	
X	if (0 <= y && y < lines && 0 <= x && x < cols) {
X		move(y, x);
X		if (no_cursor) {
X			Putstr(ve_str);
X			no_cursor = No;
X		}
X	}
X	else if (no_cursor == No) {
X		Putstr(vi_str);
X		no_cursor = Yes;
X	}
X	VOID fflush(stdout);
X}
X
X
X/*
X * Send a bell, visible if possible.
X */
X
XVisible Procedure
Xtrmbell()
X{
X	Tprintf((stderr, "\ttrmbell();\n"));
X	check_started("trmbell");
X	
X	Putstr(vb_str);
X	VOID fflush(stdout);
X}
X
X
X#ifdef SHOW
X
X/*
X * Show the current internal statuses of the screen on stderr.
X * For debugging only.
X */
X
XVisible Procedure
Xtrmshow(s)
Xchar *s;
X{
X	int y, x;
X	
X	fprintf(stderr, "<<< %s >>>\n", s);
X	for (y = 0; y < lines; y++) {
X		for (x = 0; x <= lenline[y] /*** && x < cols-1 ***/ ; x++) {
X			fputc(line[y][x]&CHAR, stderr);
X		}
X		fputc('\n', stderr);
X		for (x = 0; x <= lenline[y] && x < cols-1; x++) {
X			if (line[y][x]&SOBIT)
X				fputc('-', stderr);
X			else
X				fputc(' ', stderr);
X		}
X		fputc('\n', stderr);
X		for (x = 0; x <= lenline[y] && x < cols-1; x++) {
X			if (line[y][x]&XSBIT)
X				fputc('+', stderr);
X			else
X				fputc(' ', stderr);
X		}
X		fputc('\n', stderr);
X	}
X	fprintf(stderr, "CUR_Y = %d, CUR_X = %d.\n", cur_y, cur_x);
X	VOID fflush(stderr);
X}
X#endif
X
X
X/*
X * Return an error message corresponding to an error code from trmstart.
X */
X
Xchar *
Xtrmwhy(err)
X	int err;
X{
X	static char *errlist[] = {
X		"no error",
X		"trmstart called twice",
X		"$TERM not set or empty",
X		"$TERM not found in termcap database",
X		"terminal too dumb",
X		"stdout not a tty device",
X		"can't get enough memory",
X	};
X	static char buf[50];
X	
X	if (err < TE_OK || err >= TE_OTHER) {
X		sprintf(buf, "trmstart error %d", err);
X		return buf;
X	}
X	else {
X		return errlist[err];
X	}
X}
X
X
X/*
X * DESCRIPTION.
X *
X * This package uses termcap to determine the terminal capabilities.
X *
X * The lines and columns of our virtual terminal are numbered 
X *	y = {0...lines-1} from top to bottom, and
X *	x = {0...cols-1} from left to right,
X * respectively.
X *
X * The Visible Procedures in this package are:
X *
X * trmstart(&lines, &cols, &flags)
X * 	Obligatory initialization call (sets tty modes etc.),
X * 	Returns the height and width of the screen to the integers
X * 	whose addresses are passed as parameters, and a flag that
X *	describes some capabilities.
X *	Function return value: 0 if all went well, an error code if there
X *	is any trouble.  No messages are printed for errors.
X *
X * trmundefined()
X *	Sets internal representation of screen and attributes to undefined.
X *	This is necessary for a hard redraw, which would get optimised to
X *	oblivion,
X *
X * trmsense(&y, &x)
X *	Returns the cursor position through its parameters
X *	after a possible manual change by the user.
X *
X * trmputdata(yfirst, ylast, indent, data)
X * 	Fill lines {yfirst..ylast} with data, after skipping the initial
X *	'indent' positions. It is assumed that these positions do not contain
X *	anything dangerous (like standout cookies or null characters).
X *
X * trmscrollup(yfirst, ylast, by)
X * 	Shift lines {yfirst..ylast} up by lines (down |by| if by < 0).
X *
X * trmsync(y, x)
X * 	Call to output data to the terminal and set cursor position.
X *
X * trmbell()
X *	Send a (possibly visible) bell, immediately (flushing stdout).
X *
X * trmend()
X * 	Obligatory termination call (resets tty modes etc.).
X *
X * You may call these as one or more cycles of:
X * 	+ trmstart
X * 	+    zero or more times any of the other routines
X * 	+ trmend
X * Trmend may be called even in the middle of trmstart; this is necessary
X * to make it possible to write an interrupt handler that resets the tty
X * state before exiting the program.
X *
X * ADDITIONAL SPECIFICATIONS (ROUTINES FOR CHARACTER INPUT)
X *
X * trminput()
X *	Return the next input character (with its parity bit cleared
X *	if any).  This value is a nonnegative int.  Returns -1 if the
X *	input can't be read any more.
X *
X * trmavail()
X *	Return 1 if there is an input character immediately available,
X *	0 if not.  Return -1 if not implementable.
X *
X * trminterrupt()
X *	Return 1 if an interrupt has occurred since the last call to
X *	trminput or trmavail, 0 else.  [Currently not implemented.]
X *
X * trmsuspend()
X *	When called in the proper environment (4BSD with job control
X *	enabled), suspends the editor, temporarily popping back to
X *	the calling shell.  The caller should have called trmend()
X *	first, and must call trmstart again afterwards.
X *	BUG: there is a timing window where keyboard-generated
X *	signals (such as interrupt) can reach the program.
X *
X * char *trmwhy()
X *	Return error string corresponding to trmstart() error code.
X */
END_OF_FILE
if test 34702 -ne `wc -c <'Ports/vtrm/vtrm.c'`; then
    echo shar: \"'Ports/vtrm/vtrm.c'\" unpacked with wrong size!
fi
# end of 'Ports/vtrm/vtrm.c'
fi
echo shar: End of archive 3 \(of 19\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 19 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0