/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:inbound.c 12.0$ */
/* $ACIS:inbound.c 12.0$ */
/* $Source: /ibm/acis/usr/src/ucb/tn3270/RCS/inbound.c,v $ */

#ifndef lint
static char *rcsid = "$Header:inbound.c 12.0$";
#endif

/*
 *	Copyright (c) 1984, 1985, 1986 by the Regents of the
 *	University of California and by Gregory Glenn Minshall.
 *
 *	Permission to use, copy, modify, and distribute these
 *	programs and their documentation for any purpose and
 *	without fee is hereby granted, provided that this
 *	copyright and permission appear on all copies and
 *	supporting documentation, the name of the Regents of
 *	the University of California not be used in advertising
 *	or publicity pertaining to distribution of the programs
 *	without specific prior permission, and notice be given in
 *	supporting documentation that copying and distribution is
 *	by permission of the Regents of the University of California
 *	and by Gregory Glenn Minshall.  Neither the Regents of the
 *	University of California nor Gregory Glenn Minshall make
 *	representations about the suitability of this software
 *	for any purpose.  It is provided "as is" without
 *	express or implied warranty.
 */


#ifndef	lint
static	char	sccsid[] = "@(#)inbound.c	3.3  1/9/87";
#endif	/* ndef lint */


#include <stdio.h>

#define DEFINEAIDS
#include "m4.out"		/* output of termcodes.m4 */
#include "ascebc.h"
#include "3270.h"
#include "screen.h"
#include "options.h"
#include "ectype.h"

#include "globals.h"
#include "inbound.ext"
#include "screen.ext"
#include "outbound.ext"
#include "telnet.ext"

#define EmptyChar()	(ourPTail == ourPHead)
#define FullChar()	(ourPHead == ourBuffer+sizeof ourBuffer)


/*
 * We define something to allow us to to IsProtected() quickly
 * on unformatted screens (with the current algorithm for fields,
 * unprotected takes exponential time...).
 *
 *	The idea is to call SetXIsProtected() BEFORE the
 * loop, then use XIsProtected().
 */

#define	SetXIsProtected()	(XWasSF = 1)
#define	XIsProtected(p)	(IsStartField(p)? \
			    XWasSF = 1 : \
			    (XWasSF? \
				(XWasSF = 0, XProtected = IsProtected(p))  : \
				XProtected))

extern char	ascebc[NASCEBC][NASCII];
extern char	ebcasc[NEBCASC][NEBC];

static char	ourBuffer[400];

static char	*ourPHead = ourBuffer,
		*ourPTail = ourBuffer;

static int	trTbl = AE_IN;		/* which ascii->ebcdic tr table */

static int	HadAid = 0;		/* Had an AID haven't sent */

static int	XWasSF, XProtected;	/* For optimizations */

/* the following are global variables */

extern int UnLocked;		/* keyboard is UnLocked? */

/* Tab() - sets cursor to the start of the next unprotected field */
static void
Tab()
{
    register int i, j;

    i = CursorAddress;
    j = WhereAttrByte(CursorAddress);
    do {
	if (IsStartField(i) && IsUnProtected(ScreenInc(i))) {
	    break;
	}
	i = FieldInc(i);
    } while (i != j);
    if (IsStartField(i) && IsUnProtected(ScreenInc(i))) {
	CursorAddress = ScreenInc(i);
    } else {
	CursorAddress = SetBufferAddress(0,0);
    }
}


/* BackTab() - sets cursor to the start of the most recent field */

static void
BackTab()
{
    register int i;

    i = ScreenDec(CursorAddress);
    for (;;) {
	if (IsStartField(ScreenDec(i)) && IsUnProtected(i)) {
	    CursorAddress = i;
	    break;
	}
	if (i == CursorAddress) {
	    CursorAddress = SetBufferAddress(0,0);
	    break;
	}
	i = ScreenDec(i);
    }
}


/* EraseEndOfField - erase all characters to the end of a field */

static void
EraseEndOfField()
{
    register int i;

    if (IsProtected(CursorAddress)) {
	RingBell();
    } else {
	TurnOnMdt(CursorAddress);
	if (FormattedScreen()) {
	    i = CursorAddress;
	    do {
		AddHost(i, 0);
		i = ScreenInc(i);
	    } while ((i != CursorAddress) && !IsStartField(i));
	} else {                            /* Screen is Unformatted */
	    i = CursorAddress;
	    do {
		AddHost(i, 0);
		i = ScreenInc(i);
	    } while (i != HighestScreen());
       } 
    }
}

/* Delete() - deletes a character from the screen
 *
 *	What we want to do is delete the section
 *	[where, from-1] from the screen,
 *	filling in with what comes at from.
 *
 *	The deleting continues to the end of the field (or
 *	until the cursor wraps).
 *
 *	From can be a start of a field.  We
 *	check for that.  However, there can't be any
 *	fields that start between where and from.
 *	We don't check for that.
 *
 *	Also, we assume that the protection status of
 *	everything has been checked by the caller.
 *
 */

static void
Delete(where, from)
register int	where,		/* Where to start deleting from */
		from;		/* Where to pull back from */
{
    register int i;

    TurnOnMdt(where);			/* Only do this once in this field */
    i = where;
    do {
	if (IsStartField(from)) {
	    AddHost(i, 0);		/* Stick the edge at the start field */
	} else {
	    AddHost(i, GetHost(from));
	    from = ScreenInc(from);		/* Move the edge */
	}
	i = ScreenInc(i);
    } while ((!IsStartField(i)) && (i != where));
}

static void
ColBak()
{
    register int i;

    i = ScreenLineOffset(CursorAddress);
    for (i = i-1; i >= 0; i--) {
	if (OptColTabs[i]) {
	    break;
	}
    }
    if (i < 0) {
	i = 0;
    }
    CursorAddress = SetBufferAddress(ScreenLine(CursorAddress), i);
}

static void
ColTab()
{
    register int i;

    i = ScreenLineOffset(CursorAddress);
    for (i = i+1; i < NumberColumns; i++) {
	if (OptColTabs[i]) {
	    break;
	}
    }
    if (i >= NumberColumns) {
	i = NumberColumns-1;
    }
    CursorAddress = SetBufferAddress(ScreenLine(CursorAddress), i);
}

static void
Home()
{
    register int i;
    register int j;

    i = SetBufferAddress(OptHome, 0);
    j = WhereLowByte(i);
    do {
	if (IsUnProtected(i)) {
	    CursorAddress = i;
	    return;
	}
	    /* the following could be a problem if we got here with an
	     * unformatted screen.  However, this is "impossible", since
	     * with an unformatted screen, the IsUnProtected(i) above
	     * should be true.
	     */
	i = ScreenInc(FieldInc(i));
    } while (i != j);
    CursorAddress = LowestScreen();
}

static
LastOfField(i)
register int	i;	/* position to start from */
{
    register int j;
    register int k;

    k = j = i;
    SetXIsProtected();
    while (XIsProtected(i) || Eisspace(GetHost(i))) {
	i = ScreenInc(i);
	if (i == j) {
	    break;
	}
    }
	    /* We are now IN a word IN an unprotected field (or wrapped) */
    while (!XIsProtected(i)) {
	if (!Eisspace(GetHost(i))) {
	    k = i;
	}
	i = ScreenInc(i);
	if (i == j) {
	    break;
	}
    }
    return(k);
}


static void
FlushChar()
{
    ourPTail = ourPHead = ourBuffer;
}


static void
AddChar(character)
char	character;
{
    if (FullChar()) {
	ourPTail += DataToNetwork(ourPTail, ourPHead-ourPTail, 0);
	if (EmptyChar()) {
	    FlushChar();
	} else {
	    fprintf(stderr, "File %s, line %d:  No room in network buffer!\n",
				__FILE__, __LINE__);
	    return;
	}
    }
    *ourPHead++ = character;
}


static void
SendUnformatted()
{
    register int i, j;
    register int Nulls;
    register int c;

			/* look for start of field */
    Nulls = 0;
    i = j = LowestScreen();
    do {
	c = GetHost(i);
	if (c == 0) {
	    Nulls++;
	} else {
	    while (Nulls) {
		Nulls--;
		AddChar(0x40);		/* put in blanks */
	    }
	    AddChar(c);
	}
	i = ScreenInc(i);
    } while (i != j);
}

static
SendField(i, command)
register int i;			/* where we saw MDT bit */
int	command;		/* The command code (type of read) */
{
    register int j;
    register int k;
    register int Nulls;
    register int c;

			/* look for start of field */
    i = j = WhereLowByte(i);

		/* On a test_request_read, don't send sba and address */
    if ((AidByte != TCtoAid(TC_TREQ))
			|| (command == CMD_SNA_READ_MODIFIED_ALL)) {
	AddChar(ORDER_SBA);		/* set start field */
	AddChar(BufferTo3270_0(j));	/* set address of this field */
	AddChar(BufferTo3270_1(j));
    }
		/*
		 * Only on read_modified_all do we return the contents
		 * of the field when the attention was caused by a
		 * selector pen.
		 */
    if ((AidByte != TCtoAid(TC_SELPEN))
			|| (command == CMD_SNA_READ_MODIFIED_ALL)) {
	if (!IsStartField(j)) {
	    Nulls = 0;
	    k = ScreenInc(WhereHighByte(j));
	    do {
		c = GetHost(j);
		if (c == 0) {
		    Nulls++;
		} else {
		    while (Nulls) {
			Nulls--;
			AddChar(0x40);		/* put in blanks */
		    }
		    AddChar(c);
		}
		j = ScreenInc(j);
	    } while ((j != k) && (j != i));
	}
    } else {
	j = FieldInc(j);
    }
    return(j);
}

/* Various types of reads... */
void
DoReadModified(command)
int	command;			/* The command sent */
{
    register int i, j;

    if (AidByte) {
	if (AidByte != TCtoAid(TC_TREQ)) {
	    AddChar(AidByte);
	} else {
		/* Test Request Read header */
	    AddChar(EBCDIC_SOH);
	    AddChar(ascebc[trTbl]['%']);
	    AddChar(ascebc[trTbl]['/']);
	    AddChar(EBCDIC_STX);
	}
    } else {
	AddChar(0x60);
    }
    if (((AidByte != TCtoAid(TC_PA1)) && (AidByte != TCtoAid(TC_PA2))
	    && (AidByte != TCtoAid(TC_PA3)) && (AidByte != TCtoAid(TC_CLEAR)))
	    || (command == CMD_SNA_READ_MODIFIED_ALL)) {
	if ((AidByte != TCtoAid(TC_TREQ))
	    || (command == CMD_SNA_READ_MODIFIED_ALL)) {
		/* Test request read_modified doesn't give cursor address */
	    AddChar(BufferTo3270_0(CursorAddress));
	    AddChar(BufferTo3270_1(CursorAddress));
	}
	i = j = WhereAttrByte(LowestScreen());
	/* Is this an unformatted screen? */
	if (!IsStartField(i)) {		/* yes, handle separate */
	    SendUnformatted();
	} else {
	    do {
		if (HasMdt(i)) {
		    i = SendField(i, command);
		} else {
		    i = FieldInc(i);
		}
	    } while (i != j);
	}
    }
    ourPTail += DataToNetwork(ourPTail, ourPHead-ourPTail, 1);
    if (EmptyChar()) {
	FlushChar();
	HadAid = 0;			/* killed that buffer */
    }
}

/* A read buffer operation... */

void
DoReadBuffer()
{
    register int i, j;

    if (AidByte) {
	AddChar(AidByte);
    } else {
	AddChar(0x60);
    }
    AddChar(BufferTo3270_0(CursorAddress));
    AddChar(BufferTo3270_1(CursorAddress));
    i = j = LowestScreen();
    do {
	if (IsStartField(i)) {
	    AddChar(ORDER_SF);
	    AddChar(BufferTo3270_1(FieldAttributes(i)));
	} else {
	    AddChar(GetHost(i));
	}
	i = ScreenInc(i);
    } while (i != j);
    ourPTail += DataToNetwork(ourPTail, ourPHead-ourPTail, 1);
    if (EmptyChar()) {
	FlushChar();
	HadAid = 0;			/* killed that buffer */
    }
}
/* Try to send some data to host */

void
SendToIBM()
{
    extern int TransparentClock, OutputClock;

    if (TransparentClock == OutputClock) {
	if (HadAid) {
	    AddChar(AidByte);
	    HadAid = 0;
	} else {
	    AddChar(0xe8);
	}
	do {
	    ourPTail += DataToNetwork(ourPTail, ourPHead-ourPTail, 1);
	} while (!EmptyChar());
	FlushChar();
    } else if (HadAid) {
	DoReadModified(CMD_READ_MODIFIED);
    }
    netflush();
}

/* This takes in one character from the keyboard and places it on the
 * screen.
 */

static void
OneCharacter(c, insert)
int c;			/* character (Ebcdic) to be shoved in */
int insert;		/* are we in insert mode? */
{
    register int i, j;

    if (IsProtected(CursorAddress)) {
	RingBell();
	return;
    }
    if (insert) {
	/* is the last character in the field a blank or null? */
	i = ScreenDec(FieldInc(CursorAddress));
	j = GetHost(i);
	if (!Eisspace(j)) {
	    RingBell();
	    return;
	} else {
	    for (j = ScreenDec(i); i != CursorAddress;
			    j = ScreenDec(j), i = ScreenDec(i)) {
		AddHost(i, GetHost(j));
	    }
	}
    }
    AddHost(CursorAddress, c);
    TurnOnMdt(CursorAddress);
    CursorAddress = ScreenInc(CursorAddress);
    if (IsStartField(CursorAddress) &&
		((FieldAttributes(CursorAddress)&ATTR_AUTO_SKIP_MASK) ==
							ATTR_AUTO_SKIP_VALUE)) {
	Tab();
    }
}

/* go through data until an AID character is hit, then generate an interrupt */

int
DataFrom3270(buffer, count)
char	*buffer;		/* where the data is */
int	count;			/* how much data there is */
{
    int origCount;
    register int c;
    register int i;
    register int j;
    static int InsertMode = 0;	/* is the terminal in insert mode? */

    extern int OutputClock, TransparentClock;

    if (!UnLocked || HadAid) {
	if (HadAid) {
	    SendToIBM();
	    if (!EmptyChar()) {
		return(0);			/* nothing to do */
	    }
	}
	if (!HadAid && (((*buffer&0xff) == TC_RESET) ||
			((*buffer&0xff) == TC_MASTER_RESET)) && EmptyChar()) {
	    UnLocked = 1;
	}
	if (!UnLocked) {
	    return(0);
	}
    }
    /* now, either empty, or haven't seen aid yet */

    origCount = count;

    if (TransparentClock == OutputClock) {
	while (count) {
	    c = (*buffer++)&0xff;
	    count--;
	    if (IsAid(c)) {
		UnLocked = 0;
		InsertMode = 0;
		AidByte = TCtoAid(c);
		HadAid = 1;
	    } else {
		switch (c) {
		case TC_ESCAPE:
		    Stop3270(1);
		    command(0);
		    ConnectScreen();
		    break;

		case TC_RESET:
		case TC_MASTER_RESET:
		    UnLocked = 1;
		    break;

		default:
		    return(origCount-(count+1));
		}
	    }
	}
    }

    while (count) {
	c = (*buffer++)&0xff;
	count--;

	if (!IsTc(c)) {
			/* Add the character to the buffer */
	    OneCharacter(ascebc[trTbl][c], InsertMode);
	} else if (IsAid(c)) {		/* got Aid */
	    if (c == TC_CLEAR) {
		LocalClear3270();
	    }
	    UnLocked = 0;
	    InsertMode = 0;		/* just like a 3278 */
	    AidByte = TCtoAid(c);
	    HadAid = 1;
	    SendToIBM();
	    return(origCount-count);
	} else {

			/* non-AID TC character */
	    switch (c) {

	    case TC_CURSEL:
		c = FieldAttributes(CursorAddress)&ATTR_DSPD_MASK;
		if (!FormattedScreen()
			|| ((c != ATTR_DSPD_DSPD) && (c != ATTR_DSPD_HIGH))) {
		    RingBell("Cursor not in selectable field");
		} else {
		    i = ScreenInc(WhereAttrByte(CursorAddress));
		    c = ebcasc[0][GetHost(i)];
		    if (c == '?') {
			AddHost(i, ascebc[trTbl]['>']);
			TurnOnMdt(i);
		    } else if (c == '>') {
			AddHost(i, ascebc[trTbl]['?']);
			TurnOffMdt(i);
		    } else if (c == ' ' || c == '\0' || c == '&') {
			UnLocked = 0;
			InsertMode = 0;
			if (c == '&') {
			    TurnOnMdt(i);	/* Only for & type */
			    AidByte = TCtoAid(TC_ENTER);
			} else {
			    AidByte = TCtoAid(TC_SELPEN);
			}
			HadAid = 1;
			SendToIBM();
		    } else {
			RingBell(
			    "Cursor not in a selectable field (designator)");
		    }
		}
		break;

	    case TC_ERASE:
		if (IsProtected(ScreenDec(CursorAddress))) {
		    RingBell();
		} else {
		    CursorAddress = ScreenDec(CursorAddress);
		    Delete(CursorAddress, ScreenInc(CursorAddress));
		}
		break;

	    case TC_WERASE:
		j = CursorAddress;
		i = ScreenDec(j);
		if (IsProtected(i)) {
		    RingBell();
		} else {
		    SetXIsProtected();
		    while ((!XIsProtected(i) && Eisspace(GetHost(i)))
							&& (i != j)) {
			i = ScreenDec(i);
		    }
		    /* we are pointing at a character in a word, or
		     * at a protected position
		     */
		    while ((!XIsProtected(i) && !Eisspace(GetHost(i)))
							&& (i != j)) {
			i = ScreenDec(i);
		    }
		    /* we are pointing at a space, or at a protected
		     * position
		     */
		    CursorAddress = ScreenInc(i);
		    Delete(CursorAddress, j);
		}
		break;

	    case TC_FERASE:
		if (IsProtected(CursorAddress)) {
		    RingBell();
		} else {
		    CursorAddress = ScreenInc(CursorAddress);	/* for btab */
		    BackTab();
		    EraseEndOfField();
		}
		break;

	    case TC_RESET:
		InsertMode = 0;
		break;

	    case TC_MASTER_RESET:
		InsertMode = 0;
		RefreshScreen();
		break;

	    case TC_UP:
		CursorAddress = ScreenUp(CursorAddress);
		break;

	    case TC_LEFT:
		CursorAddress = ScreenDec(CursorAddress);
		break;

	    case TC_RIGHT:
		CursorAddress = ScreenInc(CursorAddress);
		break;

	    case TC_DOWN:
		CursorAddress = ScreenDown(CursorAddress);
		break;

	    case TC_DELETE:
		if (IsProtected(CursorAddress)) {
		    RingBell();
		} else {
		    Delete(CursorAddress, ScreenInc(CursorAddress));
		}
		break;

	    case TC_INSRT:
		InsertMode = !InsertMode;
		break;

	    case TC_HOME:
		Home();
		break;

	    case TC_NL:
		/* The algorithm is to look for the first unprotected
		 * column after column 0 of the following line.  Having
		 * found that unprotected column, we check whether the
		 * cursor-address-at-entry is at or to the right of the
		 * LeftMargin AND the LeftMargin column of the found line
		 * is unprotected.  If this conjunction is true, then
		 * we set the found pointer to the address of the LeftMargin
		 * column in the found line.
		 * Then, we set the cursor address to the found address.
		 */
		i = SetBufferAddress(ScreenLine(ScreenDown(CursorAddress)), 0);
		j = ScreenInc(WhereAttrByte(CursorAddress));
		do {
		    if (IsUnProtected(i)) {
			break;
		    }
		    /* Again (see comment in Home()), this COULD be a problem
		     * with an unformatted screen.
		     */
		    /* If there was a field with only an attribute byte,
		     * we may be pointing to the attribute byte of the NEXT
		     * field, so just look at the next byte.
		     */
		    if (IsStartField(i)) {
			i = ScreenInc(i);
		    } else {
			i = ScreenInc(FieldInc(i));
		    }
		} while (i != j);
		if (!IsUnProtected(i)) {	/* couldn't find unprotected */
		    i = SetBufferAddress(0,0);
		}
		if (OptLeftMargin <= ScreenLineOffset(CursorAddress)) {
		    if (IsUnProtected(SetBufferAddress(ScreenLine(i),
							    OptLeftMargin))) {
			i = SetBufferAddress(ScreenLine(i), OptLeftMargin);
		    }
		}
		CursorAddress = i;
		break;

	    case TC_EINP:
		if (!FormattedScreen()) {
		    i = CursorAddress;
		    TurnOnMdt(i);
		    do {
			AddHost(i, 0);
			i = ScreenInc(i);
		    } while (i != CursorAddress);
		} else {
			/*
			 * The algorithm is:  go through each unprotected
			 * field on the screen, clearing it out.  When
			 * we are at the start of a field, skip that field
			 * if its contents are protected.
			 */
		    i = j = FieldInc(CursorAddress);
		    do {
			if (IsUnProtected(ScreenInc(i))) {
			    i = ScreenInc(i);
			    TurnOnMdt(i);
			    do {
			       AddHost(i, 0);
			       i = ScreenInc(i);
			    } while (!IsStartField(i));
			} else {
			    i = FieldInc(i);
			}
		    } while (i != j);
		}
		Home();
		break;

	    case TC_EEOF:
		EraseEndOfField();
		break;

	    case TC_CENTSIGN:
		if (IsProtected(CursorAddress)) {
		    RingBell();
		} else {
		    OneCharacter(EBCDIC_CENTSIGN, InsertMode);  /* Add cent */
		}
		break;

	    case TC_FM:
		if (IsProtected(CursorAddress)) {
		    RingBell();
		} else {
		    OneCharacter(EBCDIC_FM, InsertMode);  /* Add field mark */
		}
		break;

	    case TC_DP:
		if (IsProtected(CursorAddress)) {
		    RingBell();
		} else {
		    OneCharacter(EBCDIC_DUP, InsertMode);/* Add dup character */
		    Tab();
		}
		break;

	    case TC_TAB:
		Tab();
		break;

	    case TC_BTAB:
		BackTab();
		break;

#ifdef	NOTUSED			/* Actually, this is superseded by unix flow
				 * control.
				 */
	    case TC_XOFF:
		Flow = 0;			/* stop output */
		break;

	    case TC_XON:
		if (!Flow) {
		    Flow = 1;			/* turn it back on */
		    DoTerminalOutput();
		}
		break;
#endif	/* NOTUSED */

	    case TC_ESCAPE:
		/* FlushChar(); do we want to flush characters from before? */
		Stop3270(1);
		command(0);
		ConnectScreen();
		break;

	    case TC_DISC:
		Stop3270(1);
		suspend();
		setconnmode();		/* Back in connection mode */
		ConnectScreen();
		break;

	    case TC_RESHOW:
		RefreshScreen();
		break;

	    case TC_SETTAB:
		OptColTabs[ScreenLineOffset(CursorAddress)] = 1;
		break;

	    case TC_DELTAB:
		OptColTabs[ScreenLineOffset(CursorAddress)] = 0;
		break;

		/*
		 * Clear all tabs, home line, and left margin.
		 */
	    case TC_CLRTAB:
		for (i = 0; i < sizeof OptColTabs; i++) {
		    OptColTabs[i] = 0;
		}
		OptHome = 0;
		OptLeftMargin = 0;
		break;

	    case TC_COLTAB:
		ColTab();
		break;

	    case TC_COLBAK:
		ColBak();
		break;

	    case TC_INDENT:
		ColTab();
		OptLeftMargin = ScreenLineOffset(CursorAddress);
		break;

	    case TC_UNDENT:
		ColBak();
		OptLeftMargin = ScreenLineOffset(CursorAddress);
		break;

	    case TC_SETMRG:
		OptLeftMargin = ScreenLineOffset(CursorAddress);
		break;

	    case TC_SETHOM:
		OptHome = ScreenLine(CursorAddress);
		break;

		/*
		 * Point to first character of next unprotected word on
		 * screen.
		 */
	    case TC_WORDTAB:
		i = CursorAddress;
		SetXIsProtected();
		while (!XIsProtected(i) && !Eisspace(GetHost(i))) {
		    i = ScreenInc(i);
		    if (i == CursorAddress) {
			break;
		    }
		}
		/* i is either protected, a space (blank or null),
		 * or wrapped
		 */
		while (XIsProtected(i) || Eisspace(GetHost(i))) {
		    i =  ScreenInc(i);
		    if (i == CursorAddress) {
			break;
		    }
		}
		CursorAddress = i;
		break;

	    case TC_WORDBACKTAB:
		i = ScreenDec(CursorAddress);
		SetXIsProtected();
		while (XIsProtected(i) || Eisspace(GetHost(i))) {
		    i = ScreenDec(i);
		    if (i == CursorAddress) {
			break;
		    }
		}
		    /* i is pointing to a character IN an unprotected word
		     * (or i wrapped)
		     */
		while (!Eisspace(GetHost(i))) {
		    i = ScreenDec(i);
		    if (i == CursorAddress) {
			break;
		    }
		}
		CursorAddress = ScreenInc(i);
		break;

			/* Point to last non-blank character of this/next
			 * unprotected word.
			 */
	    case TC_WORDEND:
		i = ScreenInc(CursorAddress);
		SetXIsProtected();
		while (XIsProtected(i) || Eisspace(GetHost(i))) {
		    i = ScreenInc(i);
		    if (i == CursorAddress) {
			break;
		    }
		}
			/* we are pointing at a character IN an
			 * unprotected word (or we wrapped)
			 */
		while (!Eisspace(GetHost(i))) {
		    i = ScreenInc(i);
		    if (i == CursorAddress) {
			break;
		    }
		}
		CursorAddress = ScreenDec(i);
		break;

			/* Get to last non-blank of this/next unprotected
			 * field.
			 */
	    case TC_FIELDEND:
		i = LastOfField(CursorAddress);
		if (i != CursorAddress) {
		    CursorAddress = i;		/* We moved; take this */
		} else {
		    j = FieldInc(CursorAddress);	/* Move to next field */
		    i = LastOfField(j);
		    if (i != j) {
			CursorAddress = i;	/* We moved; take this */
		    }
			/* else - nowhere else on screen to be; stay here */
		}
		break;

	    default:
		RingBell();		/* We don't handle this yet */
	    }
	}
    }
    return(origCount-count);
}
