/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
 * Copyright  (c) 1985 AT&T
 *	All Rights Reserved
 */
#ident	"@(#)if_form.c	1.6	93/09/30 SMI"       /* SVr4.0 1.79 */

#include	<ctype.h>
#include	<stdio.h>
#include	<string.h>
#include        <curses.h>
#include	<sys/types.h>		/* EFT abs k16 */
#include	"wish.h"
#include	"menudefs.h"
#include	"message.h"
#include	"token.h"
#include	"vtdefs.h"
#include	"ctl.h"
#include	"slk.h"
#include	"actrec.h"
#include	"typetab.h"
#include	"winp.h"
#include	"form.h"
#include	"fm_mn_par.h"
#include	"objform.h"
#include	"eval.h"
#include	"terror.h"
#include	"var_arrays.h"
#include	"moremacros.h"
#include	"interrupt.h"
#include	"sizes.h"


#define FM_KEYS		13
#define FM_INTR		PAR_INTR  
#define FM_ONINTR	PAR_ONINTR
#define FM_DONE 	PAR_DONE
#define FM_TITLE	3
#define FM_LIFE		4 
#define FM_INIT		5 
#define FM_BEGROW	6 
#define FM_BEGCOL	7 
#define FM_HELP		8 
#define FM_REREAD	9	
#define FM_CLOSE	10
#define FM_ALTSLKS	11
#define FM_FRMMSG	12

#define FM_FLD_KEYS	27

/* defined above   abs
#define FM_INTR		PAR_INTR
#define FM_ONINTR	PAR_ONINTR
*/
#define FM_ACTI		PAR_ACTION
#define FM_NAME		PAR_NAME
#define FM_FROW		4 
#define FM_FCOL		5 
#define FM_NROW		6 
#define FM_NCOL		7 
#define FM_ROWS		8 
#define FM_COLS		9 
#define FM_FLEN		10
#define FM_VALUE	11
#define FM_RMENU	12
#define FM_CHOICEMSG	13
#define FM_VALID	14
#define FM_NOECHO	15
#define FM_MENUO	16
#define FM_SHOW		17
#define FM_SCROLL	18
#define FM_WRAP		19
#define FM_PAGE		20
#define FM_BUTT		21
#define FM_VALMSG	22
#define FM_INACTIVE	23
#define FM_FIELDMSG	24
#define FM_LININFO	25
#define FM_AUTOADVANCE  26

static struct attribute Fm_tab[FM_KEYS] = {
	{ "interrupt",	RET_STR|EVAL_ALWAYS,    NULL, NULL, 0 },
	{ "oninterrupt",RET_STR|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "done",	RET_ARGS|EVAL_ALWAYS,	"", NULL, 0 },
	{ "form",	RET_STR|EVAL_ONCE,	"Form", NULL, 0 },
	{ "lifetime",	RET_STR|EVAL_ALWAYS,	"longterm", NULL, 0 },
	{ "init",	RET_BOOL|EVAL_ALWAYS,	"", NULL, 0 },
	{ "begrow",	RET_INT|EVAL_ONCE,	"any", NULL, 0 },
	{ "begcol",	RET_INT|EVAL_ONCE,	"any", NULL, 0 },
	{ "help",	RET_ARGS|EVAL_ALWAYS,	"", NULL, 0 },
	{ "reread",	RET_BOOL|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "close",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "altslks",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "framemsg",	RET_STR|EVAL_ONCE,	"", NULL, 0 }
};

static struct attribute Fm_fld_tab[FM_FLD_KEYS] = {
	{ "interrupt",	RET_STR|EVAL_ALWAYS,    NULL, NULL, 0 },
	{ "oninterrupt",RET_STR|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "action",	RET_ARGS|EVAL_ALWAYS,	"", NULL, 0 },
	{ "name",	RET_STR|EVAL_ONCE,	NULL, NULL, 0 },
	{ "frow",	RET_INT|EVAL_ONCE,	"-1", NULL, 0 },
	{ "fcol",	RET_INT|EVAL_ONCE,	"-1", NULL, 0 },
	{ "nrow",	RET_INT|EVAL_ONCE,	"-1", NULL, 0 },
	{ "ncol",	RET_INT|EVAL_ONCE,	"-1", NULL, 0 },
	{ "rows",	RET_INT|EVAL_ONCE,	"1",  NULL, 0 }, /* abs f15 */
	{ "columns",	RET_INT|EVAL_ONCE,	"-1", NULL, 0 },
	{ "flen",	RET_INT|EVAL_ONCE,	NULL, NULL, 0 },
	{ "value",	MAKE_COPY|RET_STR|EVAL_SOMETIMES,"", NULL, 0 },
	{ "rmenu",	RET_LIST|EVAL_ONCE|EV_SQUIG,"", NULL, 0 },
	{ "choicemsg",	RET_STR|EVAL_ONCE,	NULL, NULL, 0 },
	{ "valid",	RET_BOOL|EVAL_SOMETIMES,"", NULL, 0 },
	{ "noecho",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "menuonly",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "show",	RET_BOOL|EVAL_SOMETIMES,"", NULL, 0 },
	{ "scroll",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "wrap",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "page", 	RET_STR|EVAL_ONCE,	"1", NULL, 0 },
	{ "button",	RET_INT|EVAL_ONCE,	"0", NULL, 0 },
	{ "invalidmsg",	RET_STR|EVAL_SOMETIMES,	NULL, NULL, 0 },
	{ "inactive",	RET_BOOL|EVAL_SOMETIMES,	NULL, NULL, 0 },
	{ "fieldmsg",	RET_STR|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "lininfo",	RET_STR|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "autoadvance",RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 }
};

char *itoa();

#define DEVirt(X) (((forminfo *) Cur_rec->odptr)->visible[X])
#define CURforminfo() ((forminfo *) Cur_rec->odptr)
#define CURform() (&(((forminfo *) Cur_rec->odptr)->fm_mn))
#define CURattr(x, y) ((CURform()->multi + x)->attrs[y])
#define NUMvis() (array_len(((forminfo *) Cur_rec->odptr)->visible))
#define NUMactive() (((forminfo *) Cur_rec->odptr)->numactive)
#define VALS() (((forminfo *) Cur_rec->odptr)->mulvals)
#define PTRS() (((forminfo *) Cur_rec->odptr)->holdptrs)
#define NUMflds() (array_len(((forminfo *) Cur_rec->odptr)->fm_mn.multi))
#define CURfield() (((forminfo *) Cur_rec->odptr)->curfield)
#define CURffield() (objform_disp(DEVirt(((forminfo *) Cur_rec->odptr)->curfield), CURforminfo()))
#define SET_curfield(X) (((forminfo *) Cur_rec->odptr)->curfield) = X
#define CURpage() ((((forminfo *) Cur_rec->odptr)->curpage))
#define LASTpage() ((((forminfo *) Cur_rec->odptr)->lastpage))
#define NUMSTR(X) (strcpy(Field_str + 1, itoa((long)X, 10)), Field_str)	/* abs k16 */

extern int Mouse_row;		/* Row offset of the mouse */
extern int Mouse_col;		/* Column offset of the mouse */
extern int Toggle;		/* when to "toggle" field choices (if_init.c) */

#define NX_ANY		0	/* pseudo flag */
#define NX_NOCUR	1
#define NX_ADVANCE	2

extern char *shrink_str();
extern char *expand();
extern char *getaltenv();
extern struct actrec *ar_create(), *ar_current();

static void chg_curfield();
static int objform_close();
static int objform_reinit();
static int objform_reread();
static int objform_current();
static int objform_noncur();
static int objform_ctl();
static int objform_stream();
struct fm_mn parse_form();

static struct actrec *Cur_rec;		/* current activation record */
static char *Equal = "=";
static char *Field_str = "F000";

#define QUERY -1
#define TOOBIG 1000
#define TOOSMALL -1000

/*
 * Returns the virtual index of the given actual field number ........
 *
 * The ACTUAL number is the order that the field appears in the form's
 * definition file.
 *
 * The VIRTUAL number is the index into the VISIBLE array of fields
 * (i.e., the subset of ACTUAL fields that contains only those fields
 * that appear on the "current page" or whose "show" descriptor = "true").
 */
virt(i)
register int i;
{
    register int j;
    int	lcv;

    lcv = NUMvis();
    for (j = 0; j < lcv; j++)
	if (DEVirt(j) == i)
	    return(j);
    return(0);		/* not exactly right but better than garbage.. */
                        /* ..since no one checks the return value */
}

/*
** Starting with start, recalculate the values until they are
** all set.  This works by faking out each value into being
** a control sequence and then substituting the real value for
** the control sequence
*/
redo_vals(start)
int start;
{
    int changed;
    register char *envbuf;
    register int i;
    char *hold1, *hold2;
    char buf[BUFSIZ];
    int	lcv;

    upseqno(CURform());

    envbuf = buf;
    lcv = NUMflds();
    for (i = start; i < lcv; i++) {
	strcpy(envbuf, NUMSTR(i + 1));
	strcat(envbuf, "=\001");
	strcat(envbuf, NUMSTR(i + 1));
	putAltenv(envbuf);
    }

    changed = FALSE;
    lcv = NUMflds();
    for (i = start; i < lcv; i++) {
	int dofree, maxamt, amt;

	dofree = 0;
	hold1 = multi_eval(CURform(), i, FM_VALUE);
	maxamt = BUFSIZ - strlen(NUMSTR(i + 1)) - 2;
	if ((amt = strlen(hold1)) > maxamt) { 
	    /*
	     * Value is greater than 1K so malloc 
	     * enough space to hold it. 
	     */
	    maxamt = amt + strlen(NUMSTR(i + 1)) + 2;
	    if ((envbuf = (char *) malloc(maxamt)) == NULL)
		fatal(NOMEM, nil); 
	    dofree++;
	}
	else {
	    /*
	     * ... otherwise, use static 1K buffer
	     */
	    envbuf = buf;
	    dofree = 0;
	}
	strcpy(envbuf, NUMSTR(i + 1));
	changed |= strcmp(hold1, (hold2 = getaltenv(VALS(), envbuf)) ?
			  hold2 : nil) == 0;
	strcat(envbuf, Equal);
	strncat(envbuf, hold1, maxamt); 
	putAltenv(envbuf);
	putaltenv(&VALS(), envbuf);
	if (dofree)		/* if buffer was malloc'd, free it */
	    free(envbuf);
    }
    while (changed) {
	changed = FALSE;
	lcv = NUMflds();
	for (i = start; i < lcv; i++) {
	    register char *p;
	    int dofree, amt, maxamt;

	    dofree = 0;
	    envbuf = buf;
	    strcpy(envbuf, NUMSTR(i + 1));
	    hold1 = getaltenv(VALS(), envbuf);
	    for (p = NULL; p = strchr(hold1, '\001'); )
		*p = '$';
	    if (!p)
		continue;
	    hold2 = expand(hold1);
	    maxamt = BUFSIZ - strlen(NUMSTR(i + 1)) - 2;
	    if ((amt = strlen(hold2)) > maxamt) { 
		/*
		 * Value is greater than 1K so malloc 
		 * enough space to hold it. 
		 */
		maxamt = amt + strlen(NUMSTR(i + 1)) + 2;
		if ((envbuf = (char *) malloc(maxamt)) == NULL)
		    fatal(NOMEM, nil); 
		strcpy(envbuf, NUMSTR(i + 1));
		dofree++;
	    }
	    else {
		/*
		 * ... otherwise, use static 1K buffer
		 */
		dofree = 0;
	    }
	    strcat(envbuf, Equal);
	    strncat(envbuf, hold2, maxamt); 
	    changed = TRUE;
	    free(hold2);
	    putaltenv(&VALS(), envbuf);
	    putAltenv(envbuf);
	    if (dofree)
		free(envbuf);
	}
    }
}

/*
** Returns the current value of the field, fieldno.
*/
char *
curval(fieldno)
int fieldno;
{
	return(getaltenv(VALS(), NUMSTR(fieldno + 1)));
}

/*
** Figure out which fields are on the screen as decided by the show
** function values.
*/
int 
fm_vislist(ptr)
forminfo *ptr;
{
    int i, num;
    struct fm_mn *fm_mn;
    char *page;
    int	lcv;

    fm_mn = &(ptr->fm_mn);
    if (!ptr->visible)  {
	ptr->slks = (int *) array_create(sizeof(int),array_len(fm_mn->multi));
	ptr->visible = (int *) array_create(sizeof(int), NUMflds());
    }
    else  {
	array_trunc(ptr->visible);
	array_trunc(ptr->slks);
    }

    lcv = NUMflds();
    NUMactive() = 0;
    for (i = 0; i < lcv; i++) {
	if (atoi(multi_eval(fm_mn, i, FM_BUTT))) {
	    /*
	     * SLK definition
	     */
	    if (multi_eval(fm_mn, i, FM_SHOW))
		ptr->slks = (int *) array_append(ptr->slks, (char *) &i);
	}
	else {
	    /*
	     * FIELD definition
	     */
	    page = multi_eval(fm_mn, i, FM_PAGE);
	    num = atoi(page);
	    if (num > LASTpage())
		LASTpage() = num; /* record last page */
	    /*
	     * Only make visible fields on the CURRENT 
	     * page ...
	     */ 
	    if ((num <= 0) || !page)
		continue;
	    else if ((num == CURpage()) ||
		     (strcmp(page, "all") == 0) || (*page == '*')) {
		/*
		 * add field to visible list ...
		 * keep track of the number of active fields
		 * for this page ...
		 */
		ptr->visible = (int *) array_append(ptr->visible, &i);
		if (multi_eval(fm_mn, i, FM_SHOW) &&
		    (!multi_eval(fm_mn, i, FM_INACTIVE)))
		    NUMactive()++;
	    }
	}
    }
}


static int
objform_reinit(a)
struct actrec *a;
{
    Cur_rec = a;
    if (sing_eval(CURform(), FM_REREAD))
	return(objform_reread(a));
    return(SUCCESS);
}

/*
** A front end to parser() which will set up most of the defaults for
** a form.
*/
static struct fm_mn
parse_form(flags, info_or_file, fp)
int flags;
char *info_or_file;
FILE *fp;
{
    struct fm_mn fm_mn;

    fm_mn.single.attrs = NULL;
    fm_mn.multi = NULL;
    filldef(&fm_mn.single, Fm_tab, FM_KEYS);
    parser(flags, info_or_file, Fm_tab, FM_KEYS, &fm_mn.single,
	   Fm_fld_tab, FM_FLD_KEYS, &fm_mn.multi, fp);
    return(fm_mn);
}

/*
** Read the form object indicated by this actrec, if a->id > 0
** then the object is being reread.
*/
static int
objform_reread(a)
struct actrec *a;
{
    extern struct slk Defslk[MAX_SLK + 1];
    extern struct slk Formslk[];
    forminfo *fi;
    register int i, but;
    char *label, *intr, *onintr, *get_def();
    int	  lcv;
    FILE *fp = NULL;

    Cur_rec = a;
    fi = CURforminfo();

    /* make sure file exists and is readable (if there is a file) 
     * The "flags" say if a->path is  the information
     * itself or the file of where the information sits.  abs k15
     */
    if (!(fi->flags & INLINE))
	if ((fp = fopen(a->path, "r")) == NULL)
	{
	    if (a->id >= 0)	/* if frame is already posted */
		warn(NOT_UPDATED, a->path);
	    else
		warn(FRAME_NOPEN, a->path);
	    return(FAIL);
	}
    if (a->id >= 0)
	freeitup(CURform());	/* if posted then free it old one */
    fi->fm_mn = parse_form(fi->flags, a->path, fp);	/* abs k14.0 */
    if ((CURform())->single.attrs == NULL) {
#ifdef _DEBUG4
	_debug4(stderr, "Couldn't parse it\n");
#endif
	return(FAIL);
    }
    (CURform())->seqno = 1;
    if (PTRS())
	free(PTRS());
    if (NUMflds() && (PTRS() = (char **) calloc(NUMflds(), sizeof(char *))) == NULL)
	fatal(NOMEM, nil);
    lcv = NUMflds();
    for (i = 0; i < lcv; i++)
	PTRS()[i] = (char *) NULL;

    fi->visible = NULL;		/* initialize array of visible fields */
    fi->slks = NULL;		/* initialize array of object's SLKS */
    redo_vals(0);		/* initialize field values */
    fm_vislist(CURforminfo());	/* set up visible field list */
    if (a->id < 0)
	SET_curfield(-1);

    /*
     * If "init=false" or Form is empty then cleanup
     */
    if (!sing_eval(CURform(), FM_INIT) || (NUMactive() <= 0))
    {
	if (a->id >= 0)	       /* form is already posted */
	{
	    if (a->lifetime == AR_INITIAL)
	    {
		mess_temp("can't close this frame");
		mess_lock();
	    }
	    else
	    {
		ar_close(a, FALSE);	
		return(FAIL);
	    }
	}
	else 
	{
	    sing_eval(CURform(), FM_CLOSE);
	    objform_noncur(a, TRUE); /* takes ARGs out of Altenv */
	    freeitup(CURform());
	    return(FAIL);
	}
    }

    /*
     * update the interrupt descriptors in the activation rec
     */
    ar_ctl(a, CTSETINTR, get_sing_def(CURform(), FM_INTR), NULL, NULL, NULL, NULL, NULL);
    ar_ctl(a, CTSETONINTR, get_sing_def(CURform(), FM_ONINTR), NULL, NULL, NULL, NULL, NULL);
    /*
     * Set up object specific SLK array
     */
    set_top_slks(Formslk);
    memcpy((char *)a->slks, (char *)Defslk, sizeof(Defslk));
    lcv = array_len(fi->slks);
    for (i = 0; i < lcv; i++) {
	but = atoi(multi_eval(CURform(), fi->slks[i], FM_BUTT)) - 1;
	if (but <  0 || but >= MAX_SLK)	/* abs */
	    continue;
	label = multi_eval(CURform(), fi->slks[i], FM_NAME);
	intr  = get_def(CURform(),fi->slks[i], FM_INTR);
	onintr  = get_def(CURform(),fi->slks[i], FM_ONINTR);
	set_obj_slk(&(a->slks[but]), label, TOK_SLK1 + but, intr, onintr);
    }

    if (a->id >= 0)		/* go to first field, first page */
    {
	vt_current(form_ctl(a->id, CTGETVT));
	CURpage() = 1;
	fm_vislist(CURforminfo());
	form_ctl(a->id, CTSETPAGE, TRUE, 1, fi->lastpage);
	nextfield(0, 0, 1, NX_ANY);	
	form_ctl(a->id, CTSETDIRTY);

	/*          used to just  go to first field of current page..
		    ar_ctl(a, CTCLEARWIN, 0, NULL, NULL, NULL, NULL, NULL); 
		    nextfield(0, 0, 1, NX_ANY);	
		    form_ctl(a->id, CTSETDIRTY);
		    */
    }
    (void) ar_ctl(Cur_rec, CTSETMSG, FALSE, NULL, NULL, NULL, NULL, NULL); /* was AR_cur.  abs k15 */
    return(SUCCESS);
}

/*
** Frees up the structures and evaluates the "close" function.
*/
static int 
objform_close(a) 
struct actrec *a; 
{ 
    register int i, lcv;
    char *p, *strchr();

    Cur_rec = a;
    form_close(a->id);		/* remove the form FIRST */ 
    copyAltenv(VALS());
    sing_eval(CURform(), FM_CLOSE); /* evaluate close function */
    objform_noncur(a, FALSE);       /* remove ARGs from Altenv */

    /*
     * Free information IN the forminfo structure
     */
    freeitup(CURform());	/* the form parse table */
    if (PTRS())			/* holdptrs array */
	free(PTRS());
    lcv = array_len(VALS());
    for (i = 0; i < lcv; i++) {	/* form specific variables */
	char namebuf[BUFSIZ];	/* (e.g., F1, F2, .... ) */

	if (p = strchr(VALS()[0], '='))
	    *p = '\0';
	strcpy(namebuf, VALS()[0]);
	if (p)
	    *p = '=';
	delaltenv(&VALS(), namebuf);
    }
    array_destroy(VALS());	/* variables array */
    array_destroy(((forminfo *)a->odptr)->slks); /* visible SLKS */
    array_destroy(((forminfo *)a->odptr)->visible); /* visible fields */

    /*
     * Free information in the activation record
     */
    free(a->odptr);		/* the forminfo structure */
    free(a->slks);		/* object specific SLKS */
    free(a->path);		/* form definition file */

    return(SUCCESS);
}

/*
** Takes this objects's information out of the major altenv.
*/
static int 
objform_noncur(a, all) 
struct actrec *a; 
bool all;
{
    register int i;
    register char *p;
    int	lcv;

    Cur_rec = a;
    lcv = array_len(VALS());
    for (i = 0; i < lcv; i++) {
	char namebuf[BUFSIZ];

	if (p = strchr(VALS()[i], '='))
	    *p = '\0';
	strcpy(namebuf, VALS()[i]);
	if (p)
	    *p = '=';
	delAltenv(namebuf);
    }
    if (all)
	return(form_noncurrent());
    else
	return(SUCCESS);
}

/*
** Sets up the major alternate environment based on the values
** for the altenv that pertains to this object.
*/
static int 
objform_current(a) 
struct actrec *a; 
{
    char *choice;
    static char *Form_Choice = "Form_Choice";

    /*
     * Make the form "current" and make the first field
     * current if this is the first time.
     */
    Cur_rec = a;
    form_current(a->id);
    if (CURfield() == -1)
	nextfield(0, 0, 1, NX_ANY);
    /*
     * Initialize the field with the value of variable "Form_Choice"
     * (variable holds the selected item(s) from a choices menu)
     */
    if (choice = getAltenv(Form_Choice)) {
	copyAltenv(VALS());
	if (set_curval(strsave(choice)) == SUCCESS) {
	    fm_vislist(CURforminfo());
	    redo_vals(CURfield() + 1);
	    form_ctl(Cur_rec->id, CTSETDIRTY);
	    if (multi_eval(CURform(), CURfield(), FM_AUTOADVANCE))
		nextfield(atoi(CURattr(CURfield(),FM_FROW)->cur),
			  atoi(CURattr(CURfield(),FM_FCOL)->cur),
			  1, NX_NOCUR);
	}
	delAltenv(Form_Choice);
    }
    else
	copyAltenv(VALS());

    set_form_field(a->id, CURfield());
    return(SUCCESS);
}

/*
** Sets up the major alternate environment based on the values
** for the altenv that pertains to this object. 
** Does min neccessary to make object "temporarily" current,
** invisible to the user.
*/
static int 
objform_temp_cur(a) 
struct actrec *a; 
{
    /*
     * Make the form "current" 
     */
    Cur_rec = a;
    form_current(a->id);
    if (CURfield() == -1)
	nextfield(0, 0, 1, NX_ANY);
    copyAltenv(VALS());

    return(SUCCESS);
}

/*
** Evaluates many of the fields to return a form structure that includes
** name, value, their positions, editing capabilities and a structure
** that is held here and is used by the low-level form code to pertain
** to each field.
*/
static formfield
objform_disp(n, fi)
int n;
forminfo *fi;
{
    register int i;
    struct fm_mn *ptr;
    formfield m;

    ptr = &(fi->fm_mn);
    if (n >= (int)NUMvis() || n < 0) /* abs k17 */
	m.name = NULL;
    else
    {
	i = DEVirt(n);
	m.name = multi_eval(ptr, i, FM_NAME);
	m.value = (char *) curval(i);
	m.frow = atoi(multi_eval(ptr, i, FM_FROW));
	m.fcol = atoi(multi_eval(ptr, i, FM_FCOL));
	m.nrow = atoi(multi_eval(ptr, i, FM_NROW));
	m.ncol = atoi(multi_eval(ptr, i, FM_NCOL));
	m.rows = atoi(multi_eval(ptr, i, FM_ROWS));
	m.cols = atoi(multi_eval(ptr, i, FM_COLS));
	if (multi_eval(ptr, i, FM_INACTIVE))
	    m.flags = I_FANCY;
	else
	    m.flags = I_BLANK|I_FANCY|I_FILL;
	if (!multi_eval(ptr, i, FM_SHOW))
	    m.flags |= I_NOSHOW;
	m.ptr = PTRS() + i;
	if (multi_eval(ptr, i, FM_WRAP))
	    m.flags |= I_WRAP;
	if (multi_eval(ptr, i, FM_SCROLL))
	    m.flags |= I_SCROLL;
	if (LASTpage() > 1)
	    m.flags |= I_NOPAGE;
	if (multi_eval(ptr, i, FM_NOECHO)) {
	    m.flags |= I_INVISIBLE;
	    m.flags &= ~(I_BLANK | I_FILL);
	}
	if (multi_eval(ptr, i, FM_AUTOADVANCE))
	    m.flags |= I_AUTOADV;

	if (m.cols <= 0  || m.rows <= 0 || m.frow < 0 || m.fcol < 0)
	{			/* field not active */
	    m.cols = 1;
	    m.rows = 1;
	    m.frow = -1;	/* title bar line */
	    m.fcol = 0;
	    m.flags = I_NOEDIT;
	    m.value = "";
	}
    }
    return(m);
}

/*
** Evaluates the help field and returns a token for it.
*/
static token
objform_help(rec)
struct actrec *rec;
{
    token make_action();

    Cur_rec = rec;
    return(make_action(sing_eval(CURform(), FM_HELP)));
}

/*
** Forms have no arguments to give, so that must fail.  All else is
** passed on.
*/
static int
objform_ctl(rec, cmd, arg1, arg2, arg3, arg4, arg5, arg6)
struct actrec *rec;
int cmd;
int arg1, arg2, arg3, arg4, arg5, arg6;
{
    Cur_rec = rec;
    switch (cmd)
    {
    case CTGETARG:
	return(FAIL);
    case CTSETMSG:
	if (arg1 == TRUE) {
	    /* 
	     * if arg1 == TRUE then the frame message was
	     * generated "externally" (i.e., via the message
	     * built-it).  Update the "framemsg" descriptor
	     * accordingly.
	     */
	    char *newmsg, *get_mess_frame();

	    newmsg = get_mess_frame();
	    set_single_default(CURform(), FM_FRMMSG, newmsg);
	}
	else 
	    mess_frame(sing_eval(CURform(), FM_FRMMSG));
	return(SUCCESS);
    case CTSETLIFE:
    {
	char *life;

	/* used CURform, Cur_rec before F15.  abs */
	life = sing_eval((&(((forminfo *) rec->odptr)->fm_mn)), FM_LIFE);
	setlifetime(rec, life);
	return(SUCCESS);
    }
    default:
	return(form_ctl(rec->id, cmd, arg1, arg2, arg3, arg4, arg5, arg6));
    }
}

/*
** Calls path_to_ar() and nextpath_to_ar() to decide whether this is a
** reopen or a first open.  If it is the latter, it sets up the actrec
** and calls ar_create().
*/
int
IF_ofopen(args)
register char **args;
{
    register int i;
    struct actrec a, *first_rec, *path_to_ar(), *nextpath_to_ar();
    extern struct slk Defslk[MAX_SLK + 1];
    int startrow, startcol;
    int inline;
    int type;
    char *life;
    char *begcol, *begrow;
    register struct fm_mn *fm_mn;
    forminfo *fi;
    char envbuf[6];

    a.serial = 0;
    a.slks = (struct slk *)NULL;
    a.prevrec = (struct actrec *)NULL;
    a.nextrec = (struct actrec *)NULL;
    a.backup = (struct actrec *)NULL;

    if (strCcmp(args[0], "-i") == 0)
    {
	inline = TRUE;
	Cur_rec = path_to_ar(args[1]);
    }
    else
    {
	inline = FALSE;
	Cur_rec = path_to_ar(args[0]);
    }
    for (first_rec = Cur_rec; Cur_rec; ) {
	char *env;

	strcpy(envbuf, "ARG1");
	for (i = inline ? 2 : 1;
	     (env = getaltenv(VALS(), envbuf)) && args[i];
	     envbuf[3]++, i++)
	    if (strcmp(args[i], env))
		break;
	if (!args[i] && !env) {
	    ar_current(Cur_rec, TRUE); /* abs k15 */
	    return(SUCCESS);
	}
	Cur_rec = nextpath_to_ar(Cur_rec);
	if (Cur_rec == first_rec)
	    break;
    }
    fi = (forminfo *)new(forminfo);
    fi->flags = inline ? INLINE : 0;
    fi->mulvals = NULL;
    fm_mn = &(fi->fm_mn);
    a.odptr = (char *) fi;
    a.id = -1;
    fm_mn->single.attrs = NULL;
    if (inline)
	a.path = strsave(args[1]);
    else
	a.path = strsave(args[0]);
    if ((a.slks = (struct slk *) malloc(sizeof(Defslk))) == NULL)
	fatal(NOMEM, nil);
    a.fcntbl[AR_CLOSE] = objform_close;
    a.fcntbl[AR_HELP] = (int (*)())objform_help; /* added cast abs */
    a.fcntbl[AR_REREAD] = objform_reread;
    a.fcntbl[AR_REINIT] = objform_reinit;
    a.fcntbl[AR_CURRENT] = objform_current;
    a.fcntbl[AR_TEMP_CUR] = objform_temp_cur; /* abs k15 */
    a.fcntbl[AR_NONCUR] = objform_noncur;
    a.fcntbl[AR_CTL] = objform_ctl;
    a.fcntbl[AR_ODSH] = objform_stream;
    Cur_rec = &a;
    CURpage() = 1;
    setupenv(fi->flags, args, &VALS());
    if (objform_reread(&a) == FAIL)
	return(FAIL);
    /*		return(NULL);   abs */
    type = 0;
    life = sing_eval(CURform(), FM_LIFE);
    begrow = sing_eval(fm_mn, FM_BEGROW);
    begcol = sing_eval(fm_mn, FM_BEGCOL);
    life_and_pos(&a, life, begrow, begcol, &startrow, &startcol, &type);
    a.id = form_default(shrink_str(sing_eval(fm_mn, FM_TITLE), MAX_TITLE),
			type, startrow, startcol, objform_disp, (char *)fi);
    if (sing_eval(fm_mn, FM_ALTSLKS))
	a.flags = AR_ALTSLKS | AR_NORESHAPE;
    else
	a.flags = AR_NORESHAPE;
    if (a.id == FAIL)
	return(FAIL);
    form_ctl(a.id, CTSETPAGE, FALSE, CURpage(), LASTpage()); 
    return((int) ar_current(Cur_rec = (struct actrec *) ar_create(&a), FALSE));
}

/*
** Set the value of the current field.
*/
set_curval(str)
char *str;
{
    char *s;
    char *tmp=NULL;
    char buf[BUFSIZ];
    char *envbuf;

    strcpy(buf, NUMSTR(CURfield() + 1));
    form_ctl(Cur_rec->id, CTGETARG, &tmp);
    if (strcmp(str, s = getaltenv(VALS(), buf)) || strcmp(str,tmp)) {
	/*
	 * If the current value and the passed value are
	 * different then add the passed value (str) to
	 * the environment
	 */
	int dofree, maxamt, amt;

	dofree = 0;
	maxamt = BUFSIZ - strlen(NUMSTR(CURfield() + 1)) - 2;
	if ((amt = strlen(str)) > maxamt) { 
	    /*
	     * Value is greater than 1K so malloc 
	     * enough space to hold it. 
	     */
	    maxamt = amt + strlen(NUMSTR(CURfield() + 1)) + 2;
	    if ((envbuf = (char *) malloc(maxamt)) == NULL)
		fatal(NOMEM, nil); 
	    dofree++;
	}
	else {
	    /*
	     * ... otherwise, use static 1K buffer
	     */
	    envbuf = buf;
	    dofree = 0;
	}
	set_cur(CURform(), CURfield(), FM_VALUE, strsave(str));
	strcpy(envbuf, NUMSTR(CURfield() + 1));
	strcat(envbuf, Equal);
	strncat(envbuf, str, maxamt); 
	putaltenv(&VALS(), envbuf);
	putAltenv(envbuf);
	if (dofree)
	    free(envbuf);
	return(SUCCESS);
    }
    return(FAIL);
}

/*
 * CHK_FORM returns the actual number of the FIRST field that
 * is invalid ....
 */ 
static int
chk_form()
{
    register int n, page, fldnum;
    int savefield, savepage, retval;
    static int chk_page();

    /*
     * Save current form page/field
     */
    savepage = CURpage();
    savefield = CURfield();

    /*
     * if the current page has an invalid field then return
     * its field number
     */
    if ((fldnum = chk_page(CURpage())) >= 0) {
	SET_curfield(savefield);
	return(fldnum);
    }

    /*
     * For each page of the form, scan the list of
     * fields and validate those that were not touched.
     * (Start with the page AFTER the current page and wrap around) 
     */
    retval = -1;
    page = CURpage();
    for (n = 1; n < LASTpage(); n++) {
	page = (page % LASTpage()) + 1;
	if ((fldnum = chk_page(page)) >= 0) {
	    retval = fldnum;
	    break;
	}
    }

    /*
     * restore current form page/field
     */
    if (CURpage() != savepage) { 
	CURpage() = savepage; 
	fm_vislist(CURforminfo()); /* create new visible list */
    }
    SET_curfield(savefield);
    return(retval);
}

/*
 * CHK_PAGE will make sure that all visible fields of "page" are valid.
 */
static int
chk_page(page)
int page;
{
    register int i, j, lcv;
    register struct attribute *attr;

    if (page != CURpage()) {	/* compute new visible list? */
	CURpage() = page; 
	fm_vislist(CURforminfo());
    }
    for (i = 0, lcv = NUMvis(); i < lcv; i++) {
	j = DEVirt(i);
	/*
	 * check the flags of the appropriate "attribute"
	 * structure to see if the field has ever been "touched"
	 * (visited) ...
	 */
	if (multi_eval(CURform(), j, FM_MENUO)) 
	    attr = CURattr(j, FM_MENUO);
	else
	    attr = CURattr(j, FM_VALID);
	if (multi_eval(CURform(), j, FM_SHOW) && attr &&
	    !(attr->flags & ATTR_TOUCHED)) {
	    attr->flags |= ATTR_TOUCHED;
	    SET_curfield(j);
	    if (fld_ck(j) != SUCCESS) 
		return(j);
	}
    }
    return(-1);
}

/*
** Given a form_field structure, it will get the value of the current
** field and see if it is A) different and B) valid.  If the value is
** different all sorts of recalculation must go on (the show function,
** all the new values).  If the value is valid, it returns SUCCESS,
** so that the function calling it can navigate or close.
*/
fld_get_ck(form_field)
formfield *form_field;
{
    char *s;
    char buf[BUFSIZ];

    if ( form_field->flags & I_SCROLL )
	s = NULL;
    else
	s = (form_field->rows * form_field->cols >= BUFSIZ) ? NULL : buf;
    form_ctl(Cur_rec->id, CTGETARG, &s);
    if (set_curval(s) == SUCCESS) {
	redo_vals(CURfield() + 1);
	fm_vislist(CURforminfo());
	form_ctl(Cur_rec->id, CTSETDIRTY);
    }
    return(is_valid(s));
}

/*
 * FLD_IS_VALID will check to see if a field is valid by retrieving, and
 * not computing, the value of "$FN" ...
 */ 
static int
fld_ck(i)
int i;
{
    char *s;
    char buf[BUFSIZ];

    strcpy(buf, NUMSTR(i + 1));
    s = getaltenv(VALS(), buf);
    return(is_valid(s));
}

static int 
is_valid(s)
char *s;
{
    int ret;
    char *str;

    ret = SUCCESS;
    if (!multi_eval(CURform(), CURfield(), FM_VALID)) {
	if ((str=multi_eval(CURform(), CURfield(), FM_VALMSG)) && *str) 
	    mess_temp(str);
	else
	    warn(VALID, s);
	mess_lock();
	ret = FAIL;
    }
    else if (multi_eval(CURform(), CURfield(), FM_MENUO)) {
	char **list;

	if ((list = (char **) multi_eval(CURform(), CURfield(), FM_RMENU)) &&
	    *list != '\0')
	{
	    int i, lcv;

	    lcv = array_len(list) - 1;
	    for (i = 1; i < lcv; i++)
		if (strcmp(s, list[i]) == 0)
		    break;
	    if (i == lcv) {
		if ((str = multi_eval(CURform(), CURfield(), FM_VALMSG)) && *str) 
		    mess_temp(str);
		else
		    warn(VALID, s);
		mess_lock();
		ret = FAIL;
	    }
	}
	else {
	    if ((str = multi_eval(CURform(), CURfield(), FM_VALMSG)) && *str) 
		mess_temp(str);
	    else
		warn(VALID, s);
	    mess_lock();
	    ret = FAIL;
	}
    }
    return(ret);
}

/*
** Move to another field.
*/
static void
chg_curfield(virtnum)
int virtnum;
{
    int num;

    num = DEVirt(virtnum);
    if (CURfield() == num)
	return;
    SET_curfield(num);
    set_form_field(Cur_rec->id, num);
}

/*
** Calculates the next field to go to.  Mode is either positive or
** negative 1 for forward and backward navigation.  Canbecur
** decides whether the current field should be eliminated from the
** choices for nextfield().
*/
nextfield(currow, curcol, mode, flags)
int currow;
int curcol;
register int mode;
int flags;
{
    register int i, j, frow, fcol;
    int curi, rows, cols;
    int newcurrow, newcurcol;
    int leastcol, leastrow, leasti;
    struct fm_mn *curf;
    int no_current, page_advance;
    int	lcv;
    int f_error, oldfield, oldpage;			/* abs */
    int try;						/* abs k17 */

    oldfield = CURfield();	/* abs */
    oldpage  = CURpage();	/* abs */
    no_current = page_advance = 0;
    if (flags & NX_NOCUR)
	no_current++;
    if (flags & NX_ADVANCE)
	page_advance++;
    
    curf = CURform();
    leastrow = mode * TOOBIG;
    leastcol = mode * TOOBIG;
    newcurrow = mode * TOOBIG;
    newcurcol = mode * TOOBIG;
    curi = -1;
    leasti = -1;
    lcv = NUMvis();
    for (i = 0; i < lcv; i++)
    {
	/* 
	 * First eliminate fields that can be eliminated 
	 *
	 * IF ...
	 *	1. field is inactive/non showable   OR
	 *	2. field is current and isn't eligible  OR
	 * 	3. either rows or cols <= 0	OR
	 * 	4. frow or fcol < 0
	 *
	 * THEN skip the field 
	 *
	 * NOTE: The reason that fields that do not satisfy
	 *	 (3) and (4) are visible is that one can
	 * 	 have a field name with no field associated
	 *	 with it .... (ETI does not have such a
	 *	 field/field-name association)
	 *
	 */ 
	j = DEVirt(i);
	if (multi_eval(curf, j, FM_INACTIVE) ||
	    (!multi_eval(curf, j, FM_SHOW)) ||
	    ((j == CURfield()) && no_current))
	      continue;
	
	rows = atoi(multi_eval(curf, j, FM_ROWS));
	cols = atoi(multi_eval(curf, j, FM_COLS));
	frow = atoi(multi_eval(curf, j, FM_FROW));
	fcol = atoi(multi_eval(curf, j, FM_FCOL));
	if (rows <= 0 || cols <= 0 || frow < 0 || fcol < 0)
	    continue;
	
	/*
	 * Determine whether the "ith" visible field is next
	 * A few comments here would help !!!
	 */
	if ((mode * frow >= mode * currow) && (mode * frow <= mode * newcurrow))
	{
	    if (((mode * frow > mode * currow) ||
		 (mode * fcol >= mode * curcol)) &&
		((mode * frow < mode * newcurrow) ||
		 (mode * fcol < mode * newcurcol)))
	    {
		newcurcol = fcol;
		newcurrow = frow;
		curi = i;
		continue;
	    }
	}
	if ((mode * frow <= mode * leastrow))
	{
	    if ((mode * frow < mode * leastrow) ||
		(mode * fcol <= mode * leastcol))
	    {
		leastcol = fcol;
		leastrow = frow;
		leasti = i;
	    }
	}
    } /* end for i=0.. */
    if ((newcurrow == mode * TOOBIG) && (newcurcol == mode * TOOBIG))
    {
	/*
	 * User has reached a page boundary (i.e., there is no 
	 * next/previous field on the current page)
	 */ 
	if (LASTpage() != 1 && page_advance)
	{
	    /*
	     * If this is a multi-page form AND the page should be
	     * automatically advanced on page boundaries then ...
	     */
	    if (mode < 0) 		/* prev field */
	    {   /* find the prev page  with visible fields abs k17 */
		f_error = TRUE;				/* abs k17 */
		for (try = LASTpage(); try > 1; try--)	/* abs k17 */
		{					/* abs k17 */
		    if (CURpage() != 1)
			CURpage()--;
		    else
			CURpage() = LASTpage();
		    fm_vislist(CURforminfo());
		    if ((int)NUMvis() > 0) 		/* abs k17 */
		    {					/* abs k17 */
			f_error = FALSE; 		/* abs k17 */
			break;				/* abs k17 */
		    }					/* abs k17 */
		}					/* abs k17 */
		
		if (!f_error)				/* abs k17 */
		    f_error = form_ctl(Cur_rec->id, CTSETPAGE, TRUE, CURpage(),
				       LASTpage());
		if (f_error)
		{
		    CURpage() = oldpage;
		    CURfield() = oldfield;
		    fm_vislist(CURforminfo());
		    form_ctl(Cur_rec->id, CTSETPAGE, TRUE, oldpage, LASTpage());
		    mess_temp("Cannot display the previous page: page may be too large");
		    mess_lock();
		    return;
		}
		CURfield() = -1;	/* abs */
		nextfield(1000, 1000, -1, NX_ANY);
		return;
	    }
	    else  			/* next field */
	    {   /* find the next page  with visible fields abs k17 */
		f_error = TRUE;				/* abs k17 */
		for (try = LASTpage(); try > 1; try--)	/* abs k17 */
		{					/* abs k17 */
		    if (CURpage() != LASTpage())
			CURpage()++;
		    else
			CURpage() = 1;
		    fm_vislist(CURforminfo());
		    if ((int)NUMvis() > 0)	 	/* abs k17 */
		    {					/* abs k17 */
			f_error = FALSE; 		/* abs k17 */
			break;				/* abs k17 */
		    }					/* abs k17 */
		}					/* abs k17 */
		
		if (!f_error)				/* abs k17 */
		    f_error = form_ctl(Cur_rec->id, CTSETPAGE, TRUE, CURpage(),
				       LASTpage());
		if (f_error)
		{
		    CURpage() = oldpage;
		    CURfield() = oldfield;
		    fm_vislist(CURforminfo());
		    form_ctl(Cur_rec->id, CTSETPAGE, TRUE, oldpage, LASTpage());
		    mess_temp("Cannot display the next page: page may be too large");
		    mess_lock();
		    return;
		}
		CURfield() = -1;	/* abs */
		nextfield(0, 0, 1, NX_ANY);
		return;
	    }
	}
	else
	{
	    /*
	     * simply wrap around to the top/bottom of the page 
	     */
	    curi = leasti;
	}
    }
    if (curi < 0)
	curi = virt(CURfield());	/*  zero or one active field */

    chg_curfield(curi);
}

token
seek_field(row, col)
int row, col;
{
    register int i, j, lcv;
    struct fm_mn *curf;
    int frow, fcol, foundfield;

    curf = CURform();
    lcv = NUMvis();
    foundfield = -1;
    /*
     * since row,col is 0,0 use 1,1 scale for offset 
     */ 
    for (i = 0; i < lcv; i++) {
	/*
	 * First eliminate fields that can be eliminated 
	 */ 
	j = DEVirt(i);
	if ((multi_eval(curf, j, FM_SHOW)) &&
	    ((frow = atoi(multi_eval(curf, j, FM_FROW))) <= row) &&
	    (atoi(multi_eval(curf, j, FM_ROWS)) + frow > row) &&
	    ((fcol = atoi(multi_eval(curf, j, FM_FCOL))) <= col) &&
	    (atoi(multi_eval(curf, j, FM_COLS)) + fcol > col)) {
	    foundfield = i;
	    break;
	}
    }
    if (foundfield < 0 || multi_eval(curf, foundfield, FM_INACTIVE))
	return(TOK_BADCHAR);
    else {
	chg_curfield(foundfield);
	return(TOK_NOP);
    }
}

/* return values */
#define TOGGLE		1
#define LONGLIST	2
#define ACTION		3
 
/*
** Checks an "rmenu" to see if it is a small list (toggle choices 
** if less than "threshold" members), a large list or a command. 
*/
testlist(list)
char **list;
{
    if (list[0][0] == '{') {
	if (((int)array_len(list) - 2) <= Toggle) /* account for "{ }" */
	    return(TOGGLE);
	return(LONGLIST);
    }
    return(ACTION);
}

char *Choice_list[3] =
{
	"OPEN",
	"MENU",
	"-i"
};

/*
** Turns an rmenu field into a command.
*/
token
rmenuaction(list)
register char **list;
{
    extern char	*Args[];
    extern int	Arg_count;
    int	lcv;

    if (testlist(list) == LONGLIST) {
	register int i;
	register IOSTRUCT *out;
	/*		char **help;
	 */
	out = io_open(EV_USE_STRING, NULL);

	putastr("menu=Choices\n", out);
	putastr("lifetime=shortterm\n", out);

	/*		Shouldn't evaluate help when choices is pressed!! abs.
	 *		putastr("Help=", out);
	 *		help = (char **) sing_eval(CURform(), FM_HELP);
	 *		lcv = array_len(help);
	 *		for (i = 0; i < lcv; i++) {
	 *			putastr(help[i], out);
	 *			putac(' ', out);
	 *		}
	 *		putac('\n', out);
	 */
	lcv = array_len(list) - 1;
	for (i = 1; i < lcv; i++) {
	    putac('\n', out);
	    putastr("name=\"", out);
	    putastr(list[i], out);
	    putastr("\"\n", out);
	    putastr("lininfo=\"", out);
	    putastr(list[i], out);
	    putastr("\"\n", out);
	    putastr("action=`set -l Form_Choice=\"", out);
	    putastr(list[i], out);
	    putastr("\"`close", out);
	    putac('\n', out);
	    putac('\n', out);
	}
	putastr("name=\nbutton=1\naction=badchar\n", out);
	putastr("name=\nbutton=2\naction=badchar\n", out);
	putastr("name=\nbutton=4\naction=badchar\n", out);
	putastr("name=\nbutton=5\naction=badchar\n", out);
	putastr("name=\nbutton=7\naction=badchar\n", out);
	putastr("name=\nbutton=8\naction=badchar\n", out);

	for (Arg_count = 0; Arg_count < 3; Arg_count++) {
	    if (Args[Arg_count])
		free(Args[Arg_count]); /* les */

	    Args[Arg_count] = strsave(Choice_list[Arg_count]);
	}

	if (Args[Arg_count])
	    free(Args[Arg_count]); /* les */

	Args[Arg_count++] = io_string(out);
	io_close(out);

	if (Args[Arg_count])
	    free(Args[Arg_count]); /* les */

	Args[Arg_count] = NULL;
	return(TOK_OPEN);
    }
    return(setaction(list));
}

/*
** Processes characters after the editor.
*/
token
post_stream(t)
register token t;
{
    formfield form_field;
    char *str;
    char **list;
    int *slks;
    int i, len;
    int num, fnum;
    int nextflags, flag;
    char *s;
    int	lcv;
    int f_error;		/* abs */
    token make_action();

    nextflags = flag = 0;
    form_field = CURffield();
	
    s = NULL;
    if (t >= TOK_SLK1 && t <= TOK_SLK16) {
	slks = CURforminfo()->slks;
	num = t - TOK_SLK1 + 1;
	lcv = array_len(slks);
	for(i = 0; i < lcv; i++)
	    if (atoi(multi_eval(CURform(), slks[i], FM_BUTT)) == num) {
		form_ctl(Cur_rec->id, CTGETARG, &s);
		t = setaction(multi_eval(CURform(), slks[i], FM_ACTI));
		break;
	    }
    }
    switch(t) {
    case TOK_BPRESSED:
	return(TOK_NOP);	/* do nothing on a button press */ 
    case TOK_BRELEASED:
	return((token) seek_field(Mouse_row, Mouse_col));
    case TOK_OPTIONS:
	t = TOK_NOP;
	if (list = (char **) multi_eval(CURform(), CURfield(), FM_RMENU)) {
	    int i;
	    char *str;

	    if ((str = multi_eval(CURform(), CURfield(), FM_CHOICEMSG)) && *str) {
		mess_temp(str);
		mess_lock();	/* don't overwrite it !!! */
	    }
	    len  = array_len(list);
	    if (len == 0 || (len <= 2 && list[0][0] ==  '{')) {
		if (!(str && *str))
		    mess_temp("There are no choices available");
	    }
	    else if (testlist(list) == TOGGLE) {
		char *s;

		s = getaltenv(VALS(), NUMSTR(CURfield() + 1));
		len -= 2;
		list = list + 1;

		for (i = 0; i < len - 1; i++)
		    if (strcmp(s, list[i]) == 0)
			break;
		if (set_curval(strsave(list[(i + 1) % len])) == SUCCESS) {
		    fm_vislist(CURforminfo());
		    redo_vals(CURfield() + 1);
		    form_ctl(Cur_rec->id, CTSETDIRTY);
		}
	    }
	    else
		t = rmenuaction(list);
	}
	break;
    case TOK_RESET:
    {
	char *s = NULL;

	form_ctl(Cur_rec->id, CTGETARG, &s);
	de_const(CURform(), CURfield(), FM_VALUE);
	redo_vals(CURfield());
	fm_vislist(CURforminfo());
	form_ctl(Cur_rec->id, CTSETDIRTY);
	t = TOK_NOP;
	break;
    }
    case TOK_DONE:
	t = TOK_BADCHAR;
	if (fld_get_ck(&form_field) != SUCCESS)
	    t = TOK_NOP;
	else if ((fnum = chk_form()) >= 0) {
	    int page;

	    /*
	     * fnum is the actual (rather than the virtual)
	     * field num
	     */
	    page = atoi(multi_eval(CURform(), fnum, FM_PAGE));
	    if (page != CURpage()) {
		/*
		 * make the new page visible 
		 */
		CURpage() = page;
		fm_vislist(CURforminfo());
		form_ctl(Cur_rec->id, CTSETPAGE, TRUE, CURpage(), LASTpage()); 
	    }
	    chg_curfield(virt(fnum));
	    t = TOK_NOP;
	}
	else if (str = sing_eval(CURform(), FM_DONE))
	    t = make_action(str);
	else {
	    warn(VALID, "");
	    mess_lock();
	}
	break;
    case TOK_UP:
	nextflags |= NX_ADVANCE;
	/* fall through */
    case TOK_PREVIOUS:
    case TOK_BTAB:		/* added backtab mapping.  abs k16 */
	nextflags |= NX_NOCUR;
	if (fld_get_ck(&form_field) == SUCCESS)
	    nextfield(atoi(multi_eval(CURform(), CURfield(), FM_FROW)),
		      atoi(multi_eval(CURform(), CURfield(), FM_FCOL)),
		      -1, nextflags);
	else
	    set_form_field(Cur_rec->id, CURfield());
	t = TOK_NOP;
	break;
    case TOK_DOWN:
	nextflags |= NX_ADVANCE;
	/* fall through */
    case TOK_TIME:
    case TOK_SAVE:
    case TOK_NEXT:
	nextflags |= NX_NOCUR;
	if (fld_get_ck(&form_field) == SUCCESS)
	    nextfield(atoi(multi_eval(CURform(), CURfield(), FM_FROW)),
		      atoi(multi_eval(CURform(), CURfield(), FM_FCOL)),
		      1, nextflags);
	else
	    set_form_field(Cur_rec->id, CURfield());
	t = TOK_NOP;
	break;
    case TOK_PPAGE:
	if (fld_get_ck(&form_field) == SUCCESS) 
	{   /* find the prev page  with visible fields.    abs k17 */
	    int oldpage = CURpage(); 			/* abs k17 */
	    
	    if (CURpage() == 1)				/* abs k17 */
	    {
		set_form_field(Cur_rec->id, CURfield());
		t = TOK_BADCHAR;
		break;
	    }
	    else
	    {
		while (CURpage() != 1)			/* abs k17 */
		{					/* abs k17 */
		    CURpage()--;
		    fm_vislist(CURforminfo());
		    if ((int)NUMvis() > 0) 		/* abs k17 */
		    {					/* abs k17 */
			f_error = FALSE; 		/* abs k17 */
			break;				/* abs k17 */
		    }					/* abs k17 */
		    else				/* abs k17 */
			f_error = TRUE;			/* abs k17 */
		}					/* abs k17 */
		
		if (!f_error)				/* abs k17 */
		    f_error = form_ctl(Cur_rec->id, CTSETPAGE, TRUE,
				       CURpage(), LASTpage());
		if (f_error)	/* bad page .. go back to old one */
		{
		    CURpage() = oldpage; 		/* abs k17 */
		    fm_vislist(CURforminfo());
		    f_error = form_ctl(Cur_rec->id, CTSETPAGE, TRUE,
				       CURpage(), LASTpage());
		    mess_temp("Cannot display the previous page: page may be too large");
		    mess_lock();
		}
		else
		    nextfield(0, 0, 1, NX_ANY);
	    }
	}
	else 
	    set_form_field(Cur_rec->id, CURfield());
	t = TOK_NOP;
	break;
    case TOK_NPAGE:
	if (fld_get_ck(&form_field) == SUCCESS)
	{   /* find the next page  with visible fields.    abs k17 */
	    int oldpage = CURpage(); 			/* abs k17 */
	    
	    if (CURpage() == LASTpage())		/* abs k17 */
	    {
		set_form_field(Cur_rec->id, CURfield());
		t = TOK_BADCHAR;
		break;
	    }
	    else
	    {
		while (CURpage() != LASTpage() )	/* abs k17 */
		{					/* abs k17 */
		    CURpage()++;
		    fm_vislist(CURforminfo());
		    if ((int)NUMvis() > 0) 		/* abs k17 */
		    {					/* abs k17 */
			f_error = FALSE; 		/* abs k17 */
			break;				/* abs k17 */
		    }					/* abs k17 */
		    else				/* abs k17 */
			f_error = TRUE;			/* abs k17 */
		}					/* abs k17 */
		
		if (!f_error)				/* abs k17 */
		    f_error = form_ctl(Cur_rec->id, CTSETPAGE, TRUE,
				       CURpage(), LASTpage()); 
		if (f_error)	/* bad page .. go back to old one */
		{
		    CURpage() = oldpage;		 /* abs k17 */
		    fm_vislist(CURforminfo());
		    f_error = form_ctl(Cur_rec->id, CTSETPAGE, TRUE,
				       CURpage(), LASTpage());
		    mess_temp("Cannot display the next page: page may be too large");
		    mess_lock();
		}
		else
		    nextfield(0, 0, 1, NX_ANY);
	    }
	}
	else 
	    set_form_field(Cur_rec->id, CURfield());
	t = TOK_NOP;
	break;
    }
    return(t);
}

/*
** Processes characters before the editor.
*/
int
pre_stream(t)
register token t;
{
    formfield form_field;

    /* les */
/*    if ( isprint(t))	   ** only looks at 8 bits. abs k17 */
    if ( t > 037 && t < 0177 )
	return t;
    /*******/

    form_field = CURffield();
    switch(t) {
    case TOK_END:
	nextfield(1000, 1000, -1, NX_ANY);
	t = TOK_NOP;
	break;
    case TOK_BEG:
	nextfield(0, 0, 1, NX_ANY);
	t = TOK_NOP;
	break;
    case TOK_BTAB:
	if (fld_get_ck(&form_field) == SUCCESS)
	    nextfield(atoi(multi_eval(CURform(), CURfield(), FM_FROW)),
		      atoi(multi_eval(CURform(), CURfield(), FM_FCOL)),
		      -1, NX_NOCUR | NX_ADVANCE);
	else
	    set_form_field(Cur_rec->id, CURfield());
	t = TOK_NOP;
	break;
    case TOK_TAB:
	t = TOK_SAVE;
    case TOK_WDWMGMT:
	break;
    }
    return(t);
}

/*
** Sets up the stream for forms.
*/
static int
objform_stream(a, t)
struct actrec *a;
token t;
{
    int (*func[5])();
    register int olifetime;
    extern int field_stream();

    Cur_rec = a;
    olifetime = Cur_rec->lifetime;
    Cur_rec->lifetime = AR_PERMANENT;
    func[0] = pre_stream;
    func[1] = field_stream;
    func[2] = (int (*)())post_stream; /* added cast abs */
    func[3] = NULL;
    t = stream(t, func);
    Cur_rec->lifetime = olifetime;
    return(t);
}

set_form_field(id, field_num)
int id, field_num;
{
    char *str;
    char *lininfo;
    char buf[BUFSIZ];
    struct attribute *attr;

    /*
     * mark the attribute as touched (visited) ...
     */
    if (multi_eval(CURform(), field_num, FM_MENUO)) 
	attr = CURattr(field_num, FM_MENUO);
    else
	attr = CURattr(field_num, FM_VALID);
    if (attr)
	attr->flags |= ATTR_TOUCHED;

    /*
     * set "LININFO" variable to the value of the "lininfo"
     * descriptor for field_num
     */ 
    lininfo = multi_eval(CURform(), field_num, FM_LININFO);
    if (strlen(lininfo)) {
	sprintf(buf, "LININFO=%s", lininfo);
	putAltenv(buf);
    }
    else 
	delAltenv("LININFO");
	
    /*
     * display on the message line the "fieldmsg" for field_num
     */
    if ((str = multi_eval(CURform(), field_num, FM_FIELDMSG)) && *str)
	mess_temp(str);
    form_ctl(id, CTSETPOS, virt(field_num), 0, 0);
}
