/*	SC	A Spreadsheet Calculator
 *		Command routines
 *
 *		original by James Gosling, September 1982
 *		modifications by Mark Weiser and Bruce Israel,
 *			University of Maryland
 *
 *              More mods Robert Bond, 12/86
 *
 *		$Revision: 1.1 $
 *	
 *		More mods by Dan Coppersmith, 5/94
 */
#include <config.h>

#include <sys/types.h>
#include <curses.h>

#ifdef HAVE_X11_X_H
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif /* HAVE_X11_X_H */

#ifdef SYSV
#include <fcntl.h>
#else
#if defined(BSD42) || defined(BSD43) || defined(VMS)
#include <sys/file.h>
#else
#include <fcntl.h>
#endif
#endif

#include "sc.h"

#ifdef HAVE_X11_X_H
#include "scXstuff.h"
#endif /* HAVE_X11_X_H */
#include <signal.h>
#include <errno.h>

static	void	openrow PROTO((int));
static	void	print_options PROTO((FILE *));
static	void	syncref PROTO((struct enode *));
static	void	unspecial PROTO((FILE *, char *, int));

static char	*file_mode = "w";	/* write/append */
extern	int	errno;

/* To store location of previous cursor postion */
extern  int     prvmx, prvmy, prvcol;     /* line added by Bob Parbs 12-92 */
extern  int     lastmx, lastmy, lastcol;  /* line added by Bob Parbs 12-92 */

#define DEFCOLDELIM ':'

/* copy the current row (currow) and place the cursor in the new row */
void
duprow()
{
    if (currow >= maxrows - 1 || maxrow >= maxrows - 1) {
	if (!growtbl(GROWROW, 0, 0))
		return;
    }
    modflg++;
    currow++;
    openrow (currow);
    for (curcol = 0; curcol <= maxcol; curcol++) {
	register struct ent *p = *ATBL(tbl, currow - 1, curcol);
	if (p) {
	    register struct ent *n;
	    n = lookat (currow, curcol);
	    (void)copyent ( n, p, 1, 0);
	}
    }
    for (curcol = 0; curcol <= maxcol; curcol++) {
	register struct ent *p = *ATBL(tbl, currow, curcol);
	if (p && (p -> flags & is_valid) && !p -> expr)
	    break;
    }
    if (curcol > maxcol)
	curcol = 0;
}

/* copy the current column (curcol) and place the cursor in the new column */
void
dupcol() 
{
    if (curcol >= maxcols - 1 || maxcol >= maxcols - 1) {
	if (!growtbl(GROWCOL, 0, 0))
		return;
    }
    modflg++;
    curcol++;
    opencol (curcol, 1);
    for (currow = 0; currow <= maxrow; currow++) {
	register struct ent *p = *ATBL(tbl, currow, curcol - 1);
	if (p) {
	    register struct ent *n;
	    n = lookat (currow, curcol);
	    copyent ( n, p, 0, 1);
	}
    }
    for (currow = 0; currow <= maxrow; currow++) {
	register struct ent *p = *ATBL(tbl, currow, curcol);
	if (p && (p -> flags & is_valid) && !p -> expr)
	    break;
    }
    if (currow > maxrow)
	currow = 0;
}

/* insert 'arg' rows before currow */
void
insertrow(arg)
register int arg;
{
    while (--arg>=0) openrow (currow);
}

/* delete 'arg' rows starting at currow (deletes from currow downward) */
void
deleterow(arg)
register int arg;
{
    if (any_locked_cells(currow, 0, currow + arg - 1, maxcol))
	scerror("Locked cells encountered. Nothing changed");
    else {
    	flush_saved();
    	erase_area(currow, 0, currow + arg - 1, maxcol);
    	currow += arg;
    	while (--arg>=0) closerow (--currow);
    	sync_refs();
    }
}

#ifdef NEW
void
erase_area(sr, sc, er, ec)
int sr, sc, er, ec;
{	erase_and_pull(sr, sc, er, ec, 1, 1);
}

void
erase_and_pull(sr, sc, er, ec, eraseflg, pull)
int sr, sc, er, ec;
int	eraseflg;
int	pull;
{
    register int r, c;
    register struct ent **pp;

    if (sr > er) {
	r = sr; sr = er; er= r;	
    }

    if (sc > ec) {
	c = sc; sc = ec; ec= c;	
    }

    if (sr < 0)
	sr = 0; 
    if (sc < 0)
	sc = 0;
    checkbounds(&er, &ec);

    for (r = sr; r <= er; r++) {
	for (c = sc; c <= ec; c++) {
	    pp = ATBL(tbl, r, c);
/*	    if (*pp && !((*pp)->flags&is_locked)) {
FIXME		free_ent(*pp);
		*pp = (struct ent *)0;
	    }
*/
	    if (*pp)
	    {
		struct ent	*p = *pp;

		if (eraseflg && !((*pp)->flags&is_locked)) {
			*pp = (struct ent *)0;
			if (!pull)
				del_ent(p);	/* untested */
		}
		if (pull)
		{
			/* if am JUST pulling, need to make a copy */
			if (!eraseflg) {
				*pp = new_ent();
				copyent(*pp, p, 0, 0);
				(*pp)->row = p->row;
				(*pp)->col = p->col;
			}

			free_ent(p);
		}
	    }
	}
    }
}
#endif
void
erase_area(sr, sc, er, ec)
int sr, sc, er, ec;
{
    register int r, c;
    register struct ent **pp;

    if (sr > er) {
	r = sr; sr = er; er= r;	
    }

    if (sc > ec) {
	c = sc; sc = ec; ec= c;	
    }

    if (sr < 0)
	sr = 0; 
    if (sc < 0)
	sc = 0;
    checkbounds(&er, &ec);

    for (r = sr; r <= er; r++) {
	for (c = sc; c <= ec; c++) {
	    pp = ATBL(tbl, r, c);
	    if (*pp && !((*pp)->flags&is_locked)) {
		free_ent(*pp);
		*pp = (struct ent *)0;
	    }
	}
    }
}

/*
 * deletes the expression associated w/ a cell and turns it into a constant
 * containing whatever was on the screen
 */
void
valueize_area(sr, sc, er, ec)
int sr, sc, er, ec;
{
    register int r, c;
    register struct ent *p;

    if (sr > er) {
	r = sr; sr = er; er= r;	
    }

    if (sc > ec) {
	c = sc; sc = ec; ec= c;	
    }

    if (sr < 0)
	sr = 0; 
    if (sc < 0)
	sc = 0;
    checkbounds(&er, &ec);

    for (r = sr; r <= er; r++) {
	for (c = sc; c <= ec; c++) {
	    p = *ATBL(tbl, r, c);
	    if (p && p->flags&is_locked) {
		sprintf(stringbuf, " Cell %s%d is locked", coltoa(c), r);
		scerror(stringbuf);
		continue;
	    }
	    if (p && p->expr) {
		efree(p->expr);
		p->expr = (struct enode *)0;
		p->flags &= ~is_strexpr;
	    }
	}
    }

}

void
pullcells(to_insert)
int to_insert;
{
    register struct ent *p, *n;
    register int deltar, deltac;
    int minrow, mincol;
    int mxrow, mxcol;
    int numrows, numcols;

    if (! to_fix)
    {
	scerror ("No data to pull");
	return;
    }

    minrow = maxrows; 
    mincol = maxcols;
    mxrow = 0;
    mxcol = 0;

    for (p = to_fix; p; p = p->next) {
	if (p->row < minrow)
	    minrow = p->row;
	if (p->row > mxrow)
	    mxrow = p->row;
	if (p->col < mincol)
	    mincol = p->col;
	if (p->col > mxcol)
	    mxcol = p->col;
    }

    numrows = mxrow - minrow + 1;
    numcols = mxcol - mincol + 1;
    deltar = currow - minrow;
    deltac = curcol - mincol;

    if (to_insert == 'r') {
	insertrow(numrows);
	deltac = 0;
    } else if (to_insert == 'c') {
	opencol(curcol, numcols);
	deltar = 0;
    }

    FullUpdate++;
    modflg++;

    for (p = to_fix; p; p = p->next) {
	n = lookat (p->row + deltar, p->col + deltac);
	(void) clearent(n);
	copyent( n, p, deltar, deltac);
	n -> flags = p -> flags & ~is_deleted;
    }
}

void
colshow_op()
{
    register int i,j;
    for (i=0; i<maxcols; i++)
	if (col_hidden[i]) 
	    break;
    for(j=i; j<maxcols; j++)
	if (!col_hidden[j])
	    break;
    j--;
    if (i>=maxcols)
	scerror ("No hidden columns to show");
    else {
	(void) sprintf(line,"show %s:", coltoa(i));
	(void) sprintf(line + strlen(line),"%s",coltoa(j));
	linelim = strlen (line);
    }
}

void
rowshow_op()
{
    register int i,j;
    for (i=0; i<maxrows; i++)
	if (row_hidden[i]) 
	    break;
    for(j=i; j<maxrows; j++)
	if (!row_hidden[j]) {
	    break;
	}
    j--;

    if (i>=maxrows)
	scerror ("No hidden rows to show");
    else {
	(void) sprintf(line,"show %d:%d", i, j);
        linelim = strlen (line);
    }
}

#ifdef notdef
/*
 * Given a row/column command letter, emit a small menu, then read a qualifier
 * character for a row/column command and convert it to 'r' (row), 'c'
 * (column), or 0 (unknown).  If ch is 'p', an extra qualifier 'm' is allowed.
 */
int
get_rcqual (ch)
    int ch;
{
	sprintf(stringbuf, "%sow/column:  r: row  c: column%s",

	    (ch == 'i') ? "Insert r" :
	    (ch == 'a') ? "Append r" :
	    (ch == 'd') ? "Delete r" :
	    (ch == 'p') ? "Pull r" :
	    (ch == 'v') ? "Values r" :
	    (ch == 'z') ? "Zap r" :
	    (ch == 's') ? "Show r" : "R",

	    (ch == 'p') ? "  m: merge" : "");
	scerror(stringbuf);

	if (!using_X)
	    (void) refresh();

	switch (nmgetch())
	{
		case 'r':
		case 'l':
		case 'h':
		case ctl('f'):
		case ctl('b'):	return ('r');

		case 'c':
		case 'j':
		case 'k':
		case ctl('p'):
		case ctl('n'):	return ('c');

		case 'm':	return ((ch == 'p') ? 'm' : 0);

		case ESC:
		case ctl('g'):	return (ESC);

		default:	return (0);
	}
	/*NOTREACHED*/
}
#endif

static	void
openrow (rs)
int	rs;
{
    register	r, c;
    struct ent	**tmprow, **pp;

    if (rs > maxrow) maxrow = rs;
    if (maxrow >= maxrows - 1 || rs > maxrows - 1) {
	if (!growtbl(GROWROW, rs, 0))
		return;
    }
	/*
	 * save the last active row+1, shift the rows downward, put the last
	 * row in place of the first
	 */
    tmprow = tbl[++maxrow];
    for (r = maxrow; r > rs; r--) {
	row_hidden[r] = row_hidden[r-1];
	tbl[r] = tbl[r-1];
	pp = ATBL(tbl, r, 0);
	for (c = 0; c < maxcols; c++, pp++)
		if (*pp)
			(*pp)->row = r;
    }
    tbl[r] = tmprow;	/* the last row was never used.... */
    FullUpdate++;
    modflg++;
}

/* delete row r */
void
closerow (r)
register int	r;
{
    register struct ent **pp;
    register c;
    struct ent	**tmprow;

    if (r > maxrow) return;

    /* save the row and empty it out */
    tmprow = tbl[r];
    pp = ATBL(tbl, r, 0);
    for (c=maxcol+1; --c>=0; pp++) {
	if (*pp)
	{	free_ent(*pp);
		*pp = (struct ent *)0;
	}
    }

    /* move the rows, put the deleted, but now empty, row at the end */
    for (; r < maxrows - 1; r++) {
	row_hidden[r] = row_hidden[r+1];
	tbl[r] = tbl[r+1];
	pp = ATBL(tbl, r, 0);
	for (c = 0; c < maxcols; c++, pp++)
		if (*pp)
			(*pp)->row = r;
    }
    tbl[r] = tmprow;

    maxrow--;
    FullUpdate++;
    modflg++;
}

void
opencol (cs, numcol)
int	cs;
int	numcol;
{
    register r;
    register struct ent **pp;
    register c;
    register lim = maxcol-cs+1;
    int i;

    if (cs > maxcol)
	maxcol = cs;
    maxcol += numcol;

    if ((maxcol >= maxcols - 1) && !growtbl(GROWCOL, 0, maxcol))
		return;

    for (i = maxcol; i > cs; i--) {
	fwidth[i] = fwidth[i-numcol];
	precision[i] = precision[i-numcol];
	realfmt[i] = realfmt[i-numcol];
	col_hidden[i] = col_hidden[i-numcol];
    }
    for (c = cs; c - cs < numcol; c++)
    {	fwidth[c] = DEFWIDTH;
	precision[c] =  DEFPREC;
	realfmt[c] = DEFREFMT;
    }
	
    for (r=0; r<=maxrow; r++) {
	pp = ATBL(tbl, r, maxcol);
	for (c=lim; --c>=0; pp--)
	    if ((pp[0] = pp[-numcol]) != NULL)
		pp[0]->col += numcol;

	pp = ATBL(tbl, r, cs);
	for (c = cs; c - cs < numcol; c++, pp++)
		*pp = (struct ent *)0;
    }
    FullUpdate++;
    modflg++;
}

/* delete group of columns (1 or more) */
void
closecol (cs, numcol)
int	cs;
int	numcol;
{
    register r;
    register struct ent **pp;
    register struct ent *q;
    register c;
    register lim = maxcol-cs;
    int i;
    char buf[50];

    if (lim - numcol < -1)
    {	(void) sprintf(buf, "Can't delete %d column%s %d columns left", numcol,
			(numcol > 1 ? "s," : ","), lim+1);
	scerror(buf);
	return;
    }
    if (any_locked_cells(0, curcol, maxrow, curcol + numcol - 1)) {
	scerror("Locked cells encountered. Nothing changed");
	return;
    }
    flush_saved();
    erase_area(0, curcol, maxrow, curcol + numcol - 1);
    sync_refs();

    /* clear then copy the block left */
    lim = maxcols - numcol - 1;
    for (r=0; r<=maxrow; r++) {
	for (c = cs; c - cs < numcol; c++)
		if ((q = *ATBL(tbl, r, c)) != NULL)
			free_ent(q);

	pp = ATBL(tbl, r, cs);
	for (c=cs; c <= lim; c++, pp++)
	{   if (c > lim)
		*pp = (struct ent *)0;
	    else
	    if ((pp[0] = pp[numcol]) != NULL)
		pp[0]->col -= numcol;
	}

	c = numcol;
	for (; --c >= 0; pp++)		
		*pp = (struct ent *)0;
    }

    for (i = cs; i < maxcols - numcol - 1; i++) {
	fwidth[i] = fwidth[i+numcol];
	precision[i] = precision[i+numcol];
	realfmt[i] = realfmt[i+numcol];
	col_hidden[i] = col_hidden[i+numcol];
    }
    for (; i < maxcols - 1; i++) {
	fwidth[i] = DEFWIDTH;
	precision[i] = DEFPREC;
	realfmt[i] = DEFREFMT;
	col_hidden[i] = FALSE;
    }

    maxcol -= numcol;
    FullUpdate++;
    modflg++;
}

void
doend(rowinc, colinc)
int rowinc, colinc;
{
    register struct ent *p;
    int r, c;

    if (VALID_CELL(p, currow, curcol)) {
	r = currow + rowinc;
	c = curcol + colinc;
	if (r >= 0 && r < maxrows && 
	    c >= 0 && c < maxcols &&
	    !VALID_CELL(p, r, c)) {
		currow = r;
		curcol = c;
	}
    }

    if (!VALID_CELL(p, currow, curcol)) {
        switch (rowinc) {
        case -1:
	    while (!VALID_CELL(p, currow, curcol) && currow > 0)
		currow--;
	    break;
        case  1:
	    while (!VALID_CELL(p, currow, curcol) && currow < maxrows-1)
		currow++;
	    break;
        case  0:
            switch (colinc) {
 	    case -1:
	        while (!VALID_CELL(p, currow, curcol) && curcol > 0)
		    curcol--;
	        break;
 	    case  1:
	        while (!VALID_CELL(p, currow, curcol) && curcol < maxcols-1)
		    curcol++;
	        break;
	    }
            break;
        }

	scerror ("");	/* clear line */
	return;
    }

    switch (rowinc) {
    case -1:
	while (VALID_CELL(p, currow, curcol) && currow > 0)
	    currow--;
	break;
    case  1:
	while (VALID_CELL(p, currow, curcol) && currow < maxrows-1)
	    currow++;
	break;
    case  0:
	switch (colinc) {
	case -1:
	    while (VALID_CELL(p, currow, curcol) && curcol > 0)
		curcol--;
	    break;
	case  1:
	    while (VALID_CELL(p, currow, curcol) && curcol < maxcols-1)
		curcol++;
	    break;
	}
	break;
    }
    if (!VALID_CELL(p, currow, curcol)) {
        currow -= rowinc;
        curcol -= colinc;
    }
}

/* Modified 9/17/90 THA to handle more formats */
void
doformat(c1,c2,w,p,r)
int c1,c2,w,p,r;
{
    register int i;
    int crows = 0;
    int ccols = c2;

    if (c1 >= maxcols && !growtbl(GROWCOL, 0, c1)) c1 = maxcols-1 ;
    if (c2 >= maxcols && !growtbl(GROWCOL, 0, c2)) c2 = maxcols-1 ;

    if (w > maintextcols - RESCOL - 2) {
	sprintf(stringbuf,"Format too large - Maximum = %d", maintextcols - RESCOL - 2);
	scerror(stringbuf);
	w = maintextcols - RESCOL - 2;
    }

    if (p > w) {
	scerror("Precision too large");
	p = w;
    }

    checkbounds(&crows, &ccols);
    if (ccols < c2) {
	sprintf(stringbuf,"Format statement failed to create implied column %d", c2);
	scerror(stringbuf);
	return;
    }

    for(i = c1; i<=c2; i++)
		fwidth[i] = w, precision[i] = p, realfmt[i] = r;

    FullUpdate++;
    modflg++;
}

static	void
print_options(f)
FILE *f;
{
    if(
       autocalc &&
       propagation == 10 &&
       calc_order == BYROWS &&
       !numeric &&
       prescale == 1.0 &&
       !extfunc &&
       showcell &&
       showtop &&
       tbl_style == 0 &&
       craction == 0 &&
       rowlimit == -1 &&
       collimit == -1
      )
		return;		/* No reason to do this */

    (void) fprintf(f, "set");
    if(!autocalc) 
	(void) fprintf(f," !autocalc");
    if(propagation != 10)
	(void) fprintf(f, " iterations = %d", propagation);
    if(calc_order != BYROWS )
	(void) fprintf(f, " bycols");
    if (numeric)
	(void) fprintf(f, " numeric");
    if (prescale != 1.0)
	(void) fprintf(f, " prescale");
    if (extfunc)
	(void) fprintf(f, " extfun");
    if (!showcell)
	(void) fprintf(f, " !cellcur");
    if (!showtop)
	(void) fprintf(f, " !toprow");
    if (tbl_style)
	(void) fprintf(f, " tblstyle = %s", tbl_style == TBL ? "tbl" :
					tbl_style == LATEX ? "latex" :
					tbl_style == SLATEX ? "slatex" :
					tbl_style == TEX ? "tex" :
					tbl_style == FRAME ? "frame" : "0" );
    if (craction)
	(void) fprintf(f, " craction = %d", craction);
    if (rowlimit >= 0)
	(void) fprintf(f, " rowlimit = %d", rowlimit);
    if (collimit >= 0)
	(void) fprintf(f, " collimit = %d", collimit);
    (void) fprintf(f, "\n");
}

/* output the spreadsheet in a printer-ready form, vs sc commands */
void
printfile (fname, r0, c0, rn, cn)
char *fname;
int r0, c0, rn, cn;
{
    FILE *f;
    static char *pline = NULL;		/* only malloc once, malloc is slow */
    static unsigned fbufs_allocated = 0;
    int plinelim;
    int pid;
    int fieldlen = 0;
    int nextcol = 0;
    register row, col;
    register struct ent **pp;

	/*
	 * don't add a .asc if the user gave us a specific name
	 * what_file() scmalloc's space
	 */
    if (*fname != '\0')
	fname = what_file(fname, NULL);
    else
	fname = what_file(curfile, ".asc");

    if ((strcmp(fname, curfile) == 0) &&
	!yn_ask("Confirm that you want to destroy the output file: (y,n)"))
    {
	scxfree(fname);
	return;
    }

    if (!pline && (pline = scxmalloc((unsigned)(FBUFLEN *
					++fbufs_allocated))) == (char *)NULL)
    {   scerror("Malloc failed in printfile()");
	scxfree(fname);
        return;
    }

    if ((f = openout(fname, &pid)) == (FILE *)0)
    {	sprintf(stringbuf, "Can't create file \"%s\"", fname);
	scerror(stringbuf);
	scxfree(fname);
	return;
    }
    scxfree(fname);

    for (row=r0;row<=rn; row++) {
	register c = 0;

	if (row_hidden[row])
	    continue;

	pline[plinelim=0] = '\0';
	for (pp = ATBL(tbl, row, col=c0); col<=cn;
	        pp += nextcol-col, col = nextcol, c += fieldlen) {

	    nextcol = col+1;
	    if (col_hidden[col]) {
		fieldlen = 0;
		continue;
	    }

	    fieldlen = fwidth[col];
	    if (*pp) {
		char *s;

		/* 
		 * dynamically allocate pline, making sure we are not 
		 * attempting to write 'out of bounds'.
		 */
		while(c > (fbufs_allocated * FBUFLEN)) {
		  if((pline = scxrealloc
			       ((char *)pline, 
				(unsigned)(FBUFLEN * ++fbufs_allocated)))
		     == NULL)
		  {
		    scerror ("Realloc failed in printfile()");
		    return;
		  }
		}		  
		while (plinelim<c) pline[plinelim++] = ' ';
		plinelim = c;
		if ((*pp)->flags&is_valid) {
		    while(plinelim + fwidth[col] > 
			  (fbufs_allocated * FBUFLEN)) {
		      if((pline = ((char *)scxrealloc
				   ((char *)pline, 
				    (unsigned)(FBUFLEN * ++fbufs_allocated))))
			 == NULL) {
			scerror ("Realloc failed in printfile()");
			return;
		      }
		    }
		    if ((*pp)->cellerror)
			(void) sprintf (pline+plinelim, "%*s",
				fwidth[col],
			((*pp)->cellerror == CELLERROR ? "ERROR" : "INVALID"));
		    else
		    {
		      if ((*pp)->format) {
	   	        char field[FBUFLEN];
			format ((*pp)->format, (*pp)->v, field,
				       sizeof(field));
			(void) sprintf (pline+plinelim, "%*s", fwidth[col],
					field);
		      } else {
	   	        char field[FBUFLEN];
			(void) engformat(realfmt[col], fwidth[col],
                                             precision[col], (*pp) -> v,
                                             field, sizeof(field));
			(void) sprintf (pline+plinelim, "%*s", fwidth[col],
				       field);
		      }
		    }
		    plinelim += strlen (pline+plinelim);
		}
		if ((s = (*pp)->label) != NULL) {
		    int slen;
		    char *start, *last;
		    register char *fp;
		    struct ent *nc;

		    /*
		     * Figure out if the label slops over to a blank field. A
		     * string started with backslash is defining a repetition
		     * char
		     */
		    slen = strlen(s);
		    if ((*s == '\\') && (*(s+1) != '\0'))
			slen = fwidth[col];

		    /* a label in the last writeable column gets all the
		     * space it needs
		     */
		    if (col == cn)
			fieldlen = slen;

		    while (slen > fieldlen && nextcol <= cn &&
			    !((nc = lookat(row,nextcol))->flags & is_valid) &&
			    !(nc->label)) {
			
	                if (!col_hidden[nextcol])
		 	    fieldlen += fwidth[nextcol];

			nextcol++;
		    }
		    if (slen > fieldlen)
			slen = fieldlen;
		    
		    while(c + fieldlen + 2 > (fbufs_allocated * FBUFLEN)) {
		      if((pline = ((char *)scxrealloc
				   ((char *)pline, 
				    (unsigned)(FBUFLEN * ++fbufs_allocated))))
			 == NULL) {
			scerror ("scxrealloc failed in printfile()");
			return;
		      }
		    }		  

		    /* Now justify and print */
		    start = (*pp)->flags & is_leftflush ? pline + c
					: pline + c + fieldlen - slen;
		    if( (*pp)->flags & is_label )
			start = pline + (c + ((fwidth[col]>slen)?(fwidth[col]-slen)/2:0));
		    last = pline + c + fieldlen;
		    fp = plinelim < c ? pline + plinelim : pline + c;
		    while (fp < start)
			*fp++ = ' ';
		    if( *s == '\\' && *(s+1)!= '\0' ) {
			char *strt;
			strt = ++s;

			while(slen--) {
				*fp++ = *s++; if( *s == '\0' ) s = strt;
			}
		    }
		    else
		    while (slen--)
			*fp++ = *s++;

		    if (!((*pp)->flags & is_valid) || fieldlen != fwidth[col])
			while(fp < last)
			    *fp++ = ' ';
		    if (plinelim < fp - pline)
			plinelim = fp - pline;
		}
	    }
	}
	pline[plinelim++] = '\n';
	pline[plinelim] = '\0';
	(void) fputs (pline, f);
    }

    closeout(f, pid);
}

char *
printfile_suffix()
{
	char	*suffix = "";

	switch (tbl_style)
	{
	  case 0:	suffix = ".cln";
			break;
	  case TBL:	suffix = ".tbl";
			break;
	  case LATEX:	suffix = ".lat";
			break;
	  case TEX:	suffix = ".tex";
			break;
	  case FRAME:	suffix = ".fram";
			break;
	}
	return suffix;
}

void
tblprintfile (fname, r0, c0, rn, cn)
char *fname;
int r0, c0, rn, cn;
{
    FILE *f;
    int	pid;
    register int row, col;
    register struct ent **pp;
    char coldelim = DEFCOLDELIM;

	/* strip off the .sc ending and add a suffix */
    fname = what_file(fname, printfile_suffix()); /* scmalloc's space */

    if ((strcmp(fname, curfile) == 0) &&
	!yn_ask("Confirm that you want to destroy the table output: (y,n)"))
    {	scxfree(fname);
	return;
    }

    if ((f = openout(fname, &pid)) == (FILE *)0)
    {	sprintf(stringbuf, "Can't create file \"%s\"", fname);
	scerror(stringbuf);
	scxfree(fname);
	return;
    }

    if ( tbl_style == TBL ) {
	fprintf(f,".\\\" ** %s spreadsheet output \n.TS\n",progname);
	fprintf(f,"tab(%c);\n",coldelim);
	for (col=c0;col<=cn; col++) fprintf(f," n");
	fprintf(f, ".\n");
    }
    else if ( tbl_style == LATEX ) {
	fprintf(f,"%% ** %s spreadsheet output\n\\begin{tabular}{",progname);
	for (col=c0;col<=cn; col++) fprintf(f,"c");
	fprintf(f, "}\n");
	coldelim = '&';
    }
    else if ( tbl_style == SLATEX ) {
	fprintf(f,"%% ** %s spreadsheet output\n!begin<tabular><",progname);
	for (col=c0;col<=cn; col++) fprintf(f,"c");
	fprintf(f, ">\n");
	coldelim = '&';
    }
    else if ( tbl_style == TEX ) {
	fprintf(f,"{\t%% ** %s spreadsheet output\n\\settabs %d \\columns\n",
		progname, cn-c0+1);
	coldelim = '&';
    }
    else if ( tbl_style == FRAME ) {
	fprintf(f,"<MIFFile 3.00> # generated by the sc spreadsheet calculator\n");
	fprintf(f,"<Tbls\n");
	fprintf(f," <Tbl \n");
	fprintf(f,"  <TblID 1> # This table's ID is 1\n");
	fprintf(f,"  <TblFormat \n");
	fprintf(f,"   <TblTag `Format A'> # Table Format Catalog\n");
	fprintf(f,"  > # end of TblFormat\n");
	fprintf(f,"  <TblNumColumns %d> # Has %d columns\n",cn-c0+1,cn-c0+1);
	fprintf(f,"  <TblTitleContent\n");
	fprintf(f,"   <Para\n");
	fprintf(f,"    <PgfTag `TableTitle'> # Forces lookup in Paragraph Format Catalog\n");
	fprintf(f,"    <ParaLine\n");
	fprintf(f,"     <String `%s'>\n",fname);
	fprintf(f,"    > # end of ParaLine\n");
	fprintf(f,"   > # end of Para\n");
	fprintf(f,"  > # end of TblTitleContent\n");
	fprintf(f,"  <TblH # The heading\n");
	fprintf(f,"   <Row # The heading row\n");
	for (col=c0; col <= cn; col++) {
	    fprintf(f,"    <Cell <CellContent <Para # Cell in column \n");
	    fprintf(f,"       <PgfTag `CellHeading'> # in Paragraph Format Catalog\n");
	    fprintf(f,"       <ParaLine <String `%c'>>\n",'A'+col);
	    fprintf(f,"    >>> # end of Cell\n");
	}
	fprintf(f,"   > # end of Row\n");
	fprintf(f,"  > # end of TblH\n");
	fprintf(f,"  <TblBody # The body\n");
    }

    for (row=r0; row<=rn; row++) {
	if ( tbl_style == TEX )
	    (void) fprintf (f, "\\+");
	else if ( tbl_style == FRAME ) {
	    fprintf(f,"   <Row # The next body row\n");
	}
	
	for (pp = ATBL(tbl, row, col=c0); col<=cn; col++, pp++) {
	    if ( tbl_style == FRAME ) {
		fprintf(f,"    <Cell <CellContent <Para\n");
		fprintf(f,"       <PgfTag `CellBody'> # in Paragraph Format Catalog\n");
		fprintf(f,"       <ParaLine <String `");
	    } 
	    if (*pp) {
		char *s;
		if ((*pp)->flags&is_valid) {
		    if ((*pp)->cellerror) {
			(void) fprintf (f, "%*s",
					fwidth[col],
			((*pp)->cellerror == CELLERROR ? "ERROR" : "INVALID"));
		    }
		    else
		    if ((*pp)->format) {
		        char field[FBUFLEN];
			
			(void) format ((*pp)->format, (*pp)->v, field,
				       sizeof(field));
			unspecial (f, field, coldelim);
		    } else {
		        char field[FBUFLEN];
                        (void) engformat(realfmt[col], fwidth[col],
                                             precision[col], (*pp) -> v,
                                             field, sizeof(field));
			unspecial (f, field, coldelim);
		    }
		}
		if ((s = (*pp)->label) != NULL) {
	            unspecial (f, s, coldelim);
		}
	    }
	    if (tbl_style == FRAME) {
		fprintf(f, "'>>\n");
		fprintf(f,"    >>> # end of Cell\n");
	    }
	    if ( col < cn )
		if (tbl_style != FRAME)
		    (void) fprintf(f,"%c", coldelim);
	}
	if ( tbl_style == LATEX ) {
		if ( row < rn ) (void) fprintf (f, "\\\\");
	}
	else if ( tbl_style == SLATEX ) {
		if ( row < rn ) (void) fprintf (f, "!!");
	}
	else if ( tbl_style == TEX ) {
		(void) fprintf (f, "\\cr");
	}
	else if ( tbl_style == FRAME ) {
	    fprintf(f,"   > # end of Row\n");
	}
	(void) fprintf (f,"\n");
    }

    if ( tbl_style == TBL )
    (void) fprintf (f,".TE\n.\\\" ** end of %s spreadsheet output\n", progname);
    else if ( tbl_style == LATEX )
    (void) fprintf (f,"\\end{tabular}\n%% ** end of %s spreadsheet output\n", progname);
    else if ( tbl_style == SLATEX )
    (void) fprintf (f,"!end<tabular>\n%% ** end of %s spreadsheet output\n", progname);
    else if ( tbl_style == TEX )
    (void) fprintf (f,"}\n%% ** end of %s spreadsheet output\n", progname);
    else if ( tbl_style == FRAME ) {
	fprintf(f,"  > # end of TblBody\n");
	fprintf(f," ># end of Tbl\n");
	fprintf(f,"> # end of Tbls\n");
	fprintf(f,"<TextFlow <Para \n");
	fprintf(f,"  <PgfTag Body> \n");
	fprintf(f,"  <ParaLine <ATbl 1>> # Reference to table ID 1\n");
	fprintf(f,">>\n");
    }

    scxfree(fname);
    closeout(f, pid);
}

/* unspecial (backquote) things that are special chars in a table */
static	void
unspecial(f, str, delim)
FILE	*f;
char	*str;
int	delim;
{
	if( *str == '\\' ) str++; /* delete wheeling string operator, OK? */
	while (*str)
	{	if (((tbl_style == LATEX) || (tbl_style == SLATEX) ||
		    (tbl_style == TEX)) &&
		    ((*str == delim) || (*str == '$') || (*str == '#') ||
		    (*str == '%') || (*str == '{') || (*str == '}') ||
		    (*str == '[') || (*str == ']') || (*str == '&')))
			putc('\\', f);
		putc(*str, f);
		str++;
	}
}

struct enode *
copye (e, Rdelta, Cdelta)
register struct enode *e;
int Rdelta, Cdelta;
{
    register struct enode *ret;

    if (e == (struct enode *)0) {
        ret = (struct enode *)0;
    } else if (e->op & REDUCE) {
	int newrow, newcol;
	if (freeenodes)
	{	ret = freeenodes;
		freeenodes = ret->e.o.left;
	}
	else
		ret = (struct enode *) scxmalloc ((unsigned) sizeof (struct enode));
	ret->op = e->op;
	newrow=e->e.r.left.vf & FIX_ROW ? e->e.r.left.vp->row :
					  e->e.r.left.vp->row+Rdelta;
	newcol=e->e.r.left.vf & FIX_COL ? e->e.r.left.vp->col :
					  e->e.r.left.vp->col+Cdelta;
	ret->e.r.left.vp = lookat (newrow, newcol);
	ret->e.r.left.vf = e->e.r.left.vf;
	newrow=e->e.r.right.vf & FIX_ROW ? e->e.r.right.vp->row :
					   e->e.r.right.vp->row+Rdelta;
	newcol=e->e.r.right.vf & FIX_COL ? e->e.r.right.vp->col :
					   e->e.r.right.vp->col+Cdelta;
	ret->e.r.right.vp = lookat (newrow, newcol);
	ret->e.r.right.vf = e->e.r.right.vf;
    } else {
	if (freeenodes)
	{	ret = freeenodes;
		freeenodes = ret->e.o.left;
	}
	else
		ret = (struct enode *) scxmalloc ((unsigned) sizeof (struct enode));
	ret->op = e->op;
	switch (ret->op) {
	case 'v':
		{
		    int newrow, newcol;
		    newrow=e->e.v.vf & FIX_ROW ? e->e.v.vp->row :
						 e->e.v.vp->row+Rdelta;
		    newcol=e->e.v.vf & FIX_COL ? e->e.v.vp->col :
						 e->e.v.vp->col+Cdelta;
		    ret->e.v.vp = lookat (newrow, newcol);
		    ret->e.v.vf = e->e.v.vf;
		    break;
		}
	case 'k':
		ret->e.k = e->e.k;
		break;
	case 'f':
		ret->e.o.right = copye (e->e.o.right,0,0);
		ret->e.o.left = (struct enode *)0;
 		break;
	case '$':
		ret->e.s = scxmalloc((unsigned) strlen(e->e.s)+1);
		(void) strcpy(ret->e.s, e->e.s);
		break;
	default:
		ret->e.o.right = copye (e->e.o.right,Rdelta,Cdelta);
		ret->e.o.left = copye (e->e.o.left,Rdelta,Cdelta);
		break;
	}
    }
    return ret;
}

/*
 * sync_refs and syncref are used to remove references to
 * deleted struct ents.  Note that the deleted structure must still
 * be hanging around before the call, but not referenced by an entry
 * in tbl.  Thus the free_ent calls in sc.c
 */
void
sync_refs ()
{
    register i,j;
    register struct ent *p;
    sync_ranges();
    for (i=0; i<=maxrow; i++)
	for (j=0; j<=maxcol; j++)
            {
	    /* if ((p = *ATBL(tbl, i, j)) && p->expr) */
	    p = *ATBL(tbl, i, j);
            if (p && p->expr)
		syncref(p->expr);
            }
}

static	void
syncref(e)
register struct enode *e;
{
    if (e == (struct enode *)0)
	return;
    else if (e->op & REDUCE) {
 	e->e.r.right.vp = lookat(e->e.r.right.vp->row, e->e.r.right.vp->col);
 	e->e.r.left.vp = lookat(e->e.r.left.vp->row, e->e.r.left.vp->col);
    } else {
	switch (e->op) {
	case 'v':
		e->e.v.vp = lookat(e->e.v.vp->row, e->e.v.vp->col);
		break;
	case 'k':
		break;
	case '$':
		break;
	default:
		syncref(e->e.o.right);
		syncref(e->e.o.left);
		break;
	}
    }
}

/* mark a row as hidden */
void
hiderow(arg)
int arg;
{
    register int r1;
    register int r2;

    r1 = currow;
    r2 = r1 + arg - 1;
    if (r1 < 0 || r1 > r2) {
	scerror ("Invalid range");
	return;
    }
    if (r2 >= maxrows-1)
    {	if (!growtbl(GROWROW, arg+1, 0))
	{	scerror("You can't hide the last row");
		return;
	}
    }
    FullUpdate++;
    modflg++;
    while (r1 <= r2)
	row_hidden[r1++] = 1;
}

/* mark a column as hidden */
void
hidecol(arg)
int arg;
{
    register int c1;
    register int c2;

    c1 = curcol;
    c2 = c1 + arg - 1;
    if (c1 < 0 || c1 > c2) {
	scerror ("Invalid range");
	return;
    }
    if (c2 >= maxcols-1)
    {	if ((arg >= ABSMAXCOLS-1) || !growtbl(GROWCOL, 0, arg+1))
	{	scerror("You can't hide the last col");
		return;
	}
    }
    FullUpdate++;
    modflg++;
    while (c1 <= c2)
	col_hidden[c1++] = TRUE;
}

/* mark a row as not-hidden */
void
showrow(r1, r2)
int r1, r2;
{
    if (r1 < 0 || r1 > r2) {
	scerror ("Invalid range");
	return;
    }
    if (r2 > maxrows-1) {
	r2 = maxrows-1;
    }
    FullUpdate++;
    modflg++;
    while (r1 <= r2)
	row_hidden[r1++] = 0;
}

/* mark a column as not-hidden */
void
showcol(c1, c2)
int c1, c2;
{
    if (c1 < 0 || c1 > c2) {
	scerror ("Invalid range");
	return;
    }
    if (c2 > maxcols-1) {
	c2 = maxcols-1;
    }
    FullUpdate++;
    modflg++;
    while (c1 <= c2)
	col_hidden[c1++] = FALSE;
}

/* Open the output file, setting up a pipe if needed */
FILE *
openout(fname, rpid)
char *fname;
int *rpid;
{
    int pipefd[2];
    int pid;
    FILE *f = NULL;
    char *efname;

    while (*fname && (*fname == ' '))  /* Skip leading blanks */
	fname++;

    if (*fname != '|') {		/* Open file if not pipe */
	*rpid = 0;
	
	efname = findhome(fname);
#ifdef DOBACKUPS
	if (!backup_file(efname) &&
	    (yn_ask("Could not create backup copy, Save anyhow?: (y,n)") != 1))
		return(0);
#endif
	return(fopen(efname, file_mode));
    }

#if defined(MSDOS)
    scerror("Piping not available under MS-DOS\n");
    return(0);
#else
    fname++;				/* Skip | */
    if ( pipe (pipefd) < 0) {
	scerror("Can't make pipe to child");
	*rpid = 0;
	return(0);
    }

    if (!using_X)
	deraw();

#ifdef VMS
    fprintf(stderr, "No son tasks available yet under VMS--sorry\n");
#else /* VMS */

    if ((pid=fork()) == 0)			  /* if child  */
    {
	(void) close (0);			  /* close stdin */
	(void) close (pipefd[1]);
	(void) dup (pipefd[0]);		  /* connect to pipe input */
	(void) signal (SIGINT, SIG_DFL);	  /* reset */
	(void) execl ("/bin/sh", "sh", "-c", fname, 0);
	exit (-127);
    }
    else				  /* else parent */
    {
	*rpid = pid;
	if ((f = fdopen (pipefd[1], "w")) == (FILE *)0)
	{
	    (void) kill (pid, -9);
	    scerror ("Can't fdopen output");
	    (void) close (pipefd[1]);
	    *rpid = 0;
	    return(0);
	}
    }
#endif /* VMS */
    return(f);
#endif /* MSDOS */
}

/* close a file opened by openout(), if process wait for return */
void
closeout(f, pid)
FILE *f;
int pid;
{
    int temp;

    (void) fclose (f);
#if !defined(MSDOS)
    if (pid) {
         while (pid != wait(&temp)) /**/;
	 (void) printf("Press RETURN to continue ");
	 (void) fflush(stdout);
	 (void) nmgetch();
	 if (!using_X)
		goraw();
    }
#endif /* MSDOS */
}

void
copyent(n,p,dr,dc)
	    register struct ent *n, *p;
	    int dr, dc;
{
    if(!n||!p)
    {	scerror("internal error");
	return;
    }
    n -> v = p -> v;
    n -> flags = p -> flags;
    n -> expr = copye (p -> expr, dr, dc);
    n -> label = (char *)0;
    if (p -> label) {
	n -> label = scxmalloc ((unsigned) (strlen (p -> label) + 1));
	(void) strcpy (n -> label, p -> label);
    }
    n -> format = 0;
    if (p -> format) {
        n -> format = scxmalloc ((unsigned) (strlen (p -> format) + 1));
	(void) strcpy (n -> format, p -> format);
    }
}

/* output ascii sc commands */
void
write_fd (f, r0, c0, rn, cn)
register FILE *f;
int r0, c0, rn, cn;
{
    register struct ent **pp;
    register r, c;

    (void) fprintf (f, "# This data file was generated by the Spreadsheet ");
    (void) fprintf (f, "Calculator.\n");
    (void) fprintf (f, "# You almost certainly shouldn't edit it.\n\n");
    graphic_write_defn(f);	/* write graph definitions, if any exist */
    print_options(f);
    for (c=0; c<maxcols; c++)
	if (fwidth[c] != DEFWIDTH || precision[c] != DEFPREC || realfmt[c] != DEFREFMT )
	    (void) fprintf (f, "format %s %d %d %d\n",coltoa(c),fwidth[c],precision[c],realfmt[c]);
    for (c=c0; c<cn; c++) {
        if (col_hidden[c]) {
            (void) fprintf(f, "hide %s\n", coltoa(c));
        }
    }
    for (r=r0; r<=rn; r++) {
	if (row_hidden[r]) {
	    (void) fprintf(f, "hide %d\n", r);
	}
    }

    write_range(f);

    if (mdir) 
	    (void) fprintf(f, "mdir \"%s\"\n", mdir);
    for (r=r0; r<=rn; r++) {
	pp = ATBL(tbl, r, c0);
	for (c=c0; c<=cn; c++, pp++)
	    if (*pp) {
		if ((*pp)->label || (*pp)->flags&is_strexpr) {
		    edits(r,c);
		    (void) fprintf(f, "%s\n",line);
		}
		if ((*pp)->flags&is_valid) {
		    editv (r, c);
		    (void) fprintf (f, "%s\n",line);
		}
		if ((*pp)->format) {
		    editfmt (r, c);
		    (void) fprintf (f, "%s\n",line);
		}
		if ((*pp)->flags&is_locked)
		    (void) fprintf(f, "lock %s%d\n", coltoa((*pp)->col),
						     (*pp)->row) ;
	    }
    }
    if (rndinfinity)
	fprintf(f, "set rndinfinity\n");
    fprintf(f, "goto %s\n", v_name( currow, curcol ) );
}

int
writefile (fname, r0, c0, rn, cn)
char *fname;
int r0, c0, rn, cn;
{
    register FILE *f;
    int pid, cret;

    if (*fname == '\0')
	fname = curfile;
    fname = what_file(fname, NULL);

#if !defined(VMS) && !defined(MSDOS) && defined(CRYPT_PATH)
    if (Crypt) {
	cret = cwritefile(fname, r0, c0, rn, cn);
	scxfree(fname);
	return(cret);
    }
#endif /* VMS */

    if ((f= openout(fname, &pid)) == (FILE *)0)
    {	sprintf(stringbuf, "Can't create file \"%s\"", fname);
	scerror(stringbuf);
	scxfree(fname);
	return (-1);
    }

    write_fd(f, r0, c0, rn, cn);
    
    closeout(f, pid);

    if (!pid) {
        (void) strcpy(curfile, fname);
        modflg = 0;
	sprintf(stringbuf,"File \"%s\" written.",curfile);
	scerror(stringbuf);
    }

    scxfree(fname);
    return (0);
}

void
readfile (fname,eraseflg)
char *fname;
int eraseflg;
{
    register FILE *f;
    int tempautolabel;

    tempautolabel = autolabel;		/* turn off auto label */
    autolabel = 0;			/* when reading a file  */

    if (*fname == '\0')
	fname = curfile;
    fname = what_file(fname, NULL);

#if !defined(VMS) && !defined(MSDOS) && defined(CRYPT_PATH)
    if (Crypt)  {
	creadfile(fname, eraseflg);
	scxfree(fname);
	return;
    }
#endif /* VMS */

    if (eraseflg && strcmp(fname,curfile) && modcheck(" first"))
    {	scxfree(fname);
	return;
    }

    if ((f = fopen(findhome(fname), "r")) == (FILE *)0)
    {	sprintf(stringbuf, "Can't read file \"%s\"", fname);
	scerror(stringbuf);
	scxfree(fname);
	return;
    }

    if (eraseflg)
	erasedb();

    loading++;
    while (fgets(line, sizeof(line), f)) {
	linelim = 0;
	if (line[0] == 'G') 	/* indicates graph definitions */
	    graphic_read_defn(f);
	else if (line[0] != '#') (void) yyparse ();
    }
    --loading;
    (void) fclose (f);
    linelim = -1;
    modflg++;
    if (eraseflg) {
	(void) strcpy(curfile,fname);
	modflg = 0;
    }
    autolabel = tempautolabel;
    EvalAll();
    scxfree(fname);
}

/* read in a file as strings into the startrow at startcol,
 * and clipping at endcol.  
 */
void 
readstrfile(fname, startrow, startcol, endrow, endcol)
char	*fname;
int	startrow, startcol;
int	endrow, endcol;
{
    register FILE *f;
    int tempautolabel;
    struct ent	*e;
    int		sr;
    char	*cp;

    tempautolabel = autolabel;		/* turn off auto label when */
    autolabel = 0;			/* when reading a file  */

    if (*fname == '\0')
	fname = curfile;
    fname = what_file(fname, NULL);

    if ((f = fopen(findhome(fname), "r")) == (FILE *)0)
    {	sprintf(stringbuf, "Can't read file \"%s\"", fname);
	scerror(stringbuf);
	scxfree(fname);
	return;
    }

    loading++;

    for (sr = startrow; fgets(line, sizeof(line), f); sr++) {
	if (endrow != -1 && sr > endrow)
		break;
	linelim = 0;

	/* clip the newline if there is one. */
	cp = &line[strlen(line)-1];
	if (*cp == '\n' || *cp == '\r')
		*cp = 0;
	e = lookat(sr, startcol);
	(void)label(e, line, -1);
    }
    sprintf(stringbuf, "Read %d lines.", sr-startrow);
    scerror(stringbuf);
    --loading;
    /* go back to where we started */
    moveto(startrow, startcol);
    (void) fclose (f);
    linelim = -1;
    modflg++;
    autolabel = tempautolabel;
    EvalAll();
}

/* erase the database (tbl, etc.) */
void
erasedb ()
{
    register r, c;
    for (c = 0; c<=maxcol; c++) {
	fwidth[c] = DEFWIDTH;
	precision[c] = DEFPREC;
	realfmt[c] = DEFREFMT;
    }

    for (r = 0; r<=maxrow; r++) {
	register struct ent **pp = ATBL(tbl, r, 0);
	for (c=0; c++<=maxcol; pp++)
	    if (*pp) {
		if ((*pp)->expr)  efree ((*pp) -> expr);
		if ((*pp)->label) scxfree ((char *)((*pp) -> label));
		(*pp)->next = freeents;	/* save [struct ent] for reuse */
		freeents = *pp;
		*pp = (struct ent *)0;
	    }
    }
    maxrow = 0;
    maxcol = 0;
    clean_range();
    FullUpdate++;
}

/* moves curcol back one displayed column */
void
backcol(arg)
	int arg;
{    
    prvmx=lastmx; prvmy=lastmy, prvcol=lastcol; /* line added by Bob Parbs 12-92 */ 
    while (--arg>=0) {
	if (curcol)
	    curcol--;
	else
	{	scerror ("At column A");
		break;
	}
	while(col_hidden[curcol] && curcol)
	    curcol--;
    }
}

/* moves curcol forward one displayed column */
void
forwcol(arg)
	int arg;
{
    prvmx=lastmx; prvmy=lastmy; prvcol=lastcol; /* line added by Bob Parbs 12-92 */
    while (--arg>=0) {
	if (curcol < maxcols - 1)
	    curcol++;
	else
	if (!growtbl(GROWCOL, 0, arg))	/* get as much as needed */
		break;
	else
		curcol++;
	while(col_hidden[curcol]&&(curcol<maxcols-1))
	    curcol++;
    }
}

/* moves currow forward one displayed row */
void
forwrow(arg)
	int arg;
{
    prvmx=lastmx; prvmy=lastmy; prvcol=lastcol; /* line added by Bob Parbs 12-92 */
    while (--arg>=0) {
	if (currow < maxrows - 1)
	    currow++;
	else
	if (!growtbl(GROWROW, arg, 0))	/* get as much as needed */
		break;
	else
		currow++;
	while (row_hidden[currow]&&(currow<maxrows-1))
	    currow++;
    }
}

/* moves currow backward one displayed row */
void
backrow(arg)
	int arg;
{
    prvmx=lastmx; prvmy=lastmy; prvcol=lastcol; /* line added by Bob Parbs 12-92 */
    while (--arg>=0) {
	if (currow)
	    currow--;
	else
	{	scerror ("At row zero");
		break;
	}
	while (row_hidden[currow] && currow)
	    currow--;
    }
}


/*
 * Show a cell's label string or expression value.  May overwrite value if
 * there is one already displayed in the cell.  Created from old code in
 * update(), copied with minimal changes.
 */

void
showstring (string, dirflush, hasvalue, row, col, nextcolp, mxcol, fieldlenp,
	    r, c, do_stand)
    char *string;	/* to display */
    int dirflush;	/* or rightflush or centered */
    int hasvalue;	/* is there a numeric value? */
    int row, col;	/* spreadsheet location */
    int *nextcolp;	/* value returned through it */
    int mxcol;		/* last column displayed? */
    int *fieldlenp;	/* value returned through it */
    int r, c;		/* screen row and column */
    int do_stand;	/* if standout needed */
{
    register int nextcol  = *nextcolp;
    register int fieldlen = *fieldlenp;

    char field[FBUFLEN];
    int  slen;
    char *start, *last;
    register char *fp;
    struct ent *nc;

    /* This figures out if the label is allowed to
       slop over into the next blank field */

    slen = strlen (string);
    if( *string == '\\' && *(string+1)!= '\0' )
	slen = fwidth[col];
    while ((slen > fieldlen) && (nextcol <= mxcol) &&
	   !((nc = lookat (row, nextcol)) -> flags & is_valid) &&
	   !(nc->label)) {

	if (! col_hidden [nextcol])
	    fieldlen += fwidth [nextcol];

	nextcol++;
    }
    if (slen > fieldlen)
	slen = fieldlen;

    /* Now justify and print */
    start = (dirflush&is_leftflush) ? field : field + fieldlen - slen;
    if( dirflush & is_label )
	start = field + ((slen<fwidth[col])?(fieldlen-slen)/2:0);
    last = field+fieldlen;
    fp = field;
    while (fp < start)
	*fp++ = ' ';
    if( *string == '\\'  && *(string+1)!= '\0') {
	char *strt;
	strt = ++string;

	while(slen--) {
		*fp++ = *string++;
		if( *string == '\0' )
			string = strt;
	}
    }
    else
    while (slen--)
	*fp++ = *string++;

    if ((! hasvalue) || fieldlen != fwidth[col]) 
	while (fp < last)
	    *fp++ = ' ';
    *fp = '\0';
#ifdef HAVE_X11_X_H
    if (using_X)
    {
	XDrawImageString(dpy, mainwin, do_stand ? maingcreversed : maingc,
		     textcol(c), textrow(r),
		     field, strlen(field));
    } else
#endif /* HAVE_X11_X_H */
    {
# ifdef VMS
	mvaddstr(r, c, field);	/* this is a macro */
# else
	(void) mvaddstr(r, c, field);
# endif
    } /* HAVE_X11_X_H, end curses */

    *nextcolp  = nextcol;
    *fieldlenp = fieldlen;
}

int
etype(e)
register struct enode *e;
{
    if (e == (struct enode *)0)
	return NUM;
    switch (e->op) {
    case UPPER: case LOWER: case CAPITAL:
    case O_SCONST: case '#': case DATE: case FMT: case STINDEX:
    case EXT: case SVAL: case SUBSTR:
        return (STR);

    case '?':
    case IF:
        return(etype(e->e.o.right->e.o.left));

    case 'f':
        return(etype(e->e.o.right));

    case O_VAR: {
	register struct ent *p;
	p = e->e.v.vp;
	if (p->expr) 
	    return(p->flags & is_strexpr ? STR : NUM);
	else if (p->label)
	    return(STR);
	else
	    return(NUM);
	}

    default:
	return(NUM);
    }
}

/* return 1 if yes given, 0 otherwise */
int
yn_ask(msg)
char	*msg;
{	char ch;

#ifdef HAVE_X11_X_H
	XEvent event;
	char buffer[8];

/* I'd rather use line 1 than line 0, because that is where the user will 
 * be looking most often.  (B.Backman) 
 */
     if (using_X)
     {
	clearlines(1,1);
	XDrawImageString(dpy, mainwin, maingc,
			 textcol(0), textrow(1),
			 msg, strlen(msg));
	while (1)
	{
		XPeekEvent(dpy, &event);

		while (event.type == MotionNotify)
		   XNextEvent(dpy,&event);
		if (event.type == KeyPress)
		{  
		   XNextEvent(dpy,&event);
		   if(XLookupString(&(event.xkey),buffer,8,0,0))
		   {
			ch = buffer[0];
			if (ch != 'y' && ch != 'Y' && ch != 'n' && ch != 'N')
			{	if (ch == ctl('g') || ch == ESC)
					return(-1);
				scerror("y or n response required");
				return (-1);
			}
			if (ch == 'y' || ch == 'Y')
				return(1);
			else
				return(0);
                   }
		}
		else 
		{
		   scerror("Y or N keypress is required");
		   return(-1);
		}
	}
	return(-1);	/* never reached */
     } else
#endif /* HAVE_X11_X_H */
     {
	(void) move (0, 0);
	(void) clrtoeol ();
	(void) addstr (msg);
	(void) refresh();
	ch = nmgetch();
	if ( ch != 'y' && ch != 'Y' && ch != 'n' && ch != 'N' ) {
		if (ch == ctl('g') || ch == ESC)
			return(-1);
		scerror("y or n response required");
		return (-1);
	}
	if (ch == 'y' || ch == 'Y')
		return(1);
	else
		return(0);
     } /* HAVE_X11_X_H, end curses */
}

/* expand a ~ in a path to your home directory */
#if !defined(MSDOS) && !defined(VMS)
#include <pwd.h>
#endif
char	*
findhome(path)
char	*path;
{
	static	char	*HomeDir = NULL;

	if (*path == '~')
	{	char	*pathptr;
		char	tmppath[PATHLEN];

		if (HomeDir == NULL)
		{	HomeDir = getenv("HOME");
			if (HomeDir == NULL)
				HomeDir = "/";
		}
		pathptr = path + 1;
		if ((*pathptr == '/') || (*pathptr == '\0'))
		{	strcpy(tmppath, HomeDir);
		}
#if !defined(MSDOS) && !defined(VMS)
		else
		{	struct	passwd *pwent;
#ifndef __STDC__
			extern	struct	passwd *getpwnam();
#endif
			char	*namep;
			char	name[50];

			namep = name;
			while ((*pathptr != '\0') && (*pathptr != '/'))
				*(namep++) = *(pathptr++);
			*namep = '\0';
			if ((pwent = getpwnam(name)) == NULL)
			{	(void) sprintf(path, "Can't find user %s", name);
				return(NULL);
			}
			strcpy(tmppath, pwent->pw_dir);
		}
#endif
		strcat(tmppath, pathptr);
		strcpy(path, tmppath);
	}
	return(path);
}

#ifdef DOBACKUPS
#include <sys/stat.h>

#ifdef NOTUSED
/******************************************
 * Determine default dir to be printed out, 
 * based on mdir.  Check for //  **********/ 
void
get_default_dir(char *tmp)
{
    int length; 

    if (mdir)
        strcpy(tmp, mdir);       
#ifdef JEFFB /* see my notes */
    else {          /* make home dir the default dir */
        strcpy(tmp, getenv("HOME"));
        if (tmp == NULL)
            *tmp = '/';
    }
#endif
    /* append exactly one / to end of tmp */
    length = strlen(tmp);
    if ((tmp[length-1] == '/')&&(tmp[length-2] == '/'))
	tmp[length-1] = '\0';
    else if (tmp[length-1] != '/')
    {	tmp[length] = '/';
	tmp[length+1] = '\0';
    }
}


/***************************************************************
 * give tmp the string to be printed out as the default path, as
 * determined by mdir and curfile, which may also be adjusted ***/
void
get_default_path(char *tmp)
{
    int i, len_mdir, len_curfile, len_same, length;
    char tmp2[PATHLEN];

#ifdef JEFFB /* see my notes */
    if (!(mdir)&&!(*curfile)) 	  /* give tmp the home dir, with 1 / at end */
    {	strcpy(tmp, getenv("HOME"));
        if (tmp == NULL)
            *tmp = '/';
        else        /* this is to add a / to end of default dir */
        {   length = strlen(tmp);
            tmp[length] = '/';
            tmp[length +1] = '\0';
    	}
    }
    else
#endif
    if((mdir)&&!(*curfile))     /* copy mdir to tmp, with 1 / at end */ 
    {	length = strlen(mdir);
        strcpy(tmp, mdir); 
        if ((tmp[length -1] == '/')&&(tmp[length -2] == '/'))
            tmp[length -1] = '\0';
	if (tmp[length -1] != '/')
	{   tmp[length] = '/';
	    tmp[length +1] = '\0';	
 	}
    }                                                    
    else if(!(mdir)&&(*curfile))  	/* print out most recent curfile */
     	strcpy(tmp, curfile);

    else  	/* ((mdir)&&(*curfile)), find best solution */
    {	len_mdir = strlen(mdir);
        len_curfile = strlen(curfile);      
 
	if (strcmp(mdir, "/") == 0)
	{   if (*curfile == '/')
		strcpy(tmp, curfile);
	    else		/* tmp = /curfile */
	    {	tmp[0] = '/';
		strcpy(tmp2, curfile);
		for (i = 1; i <= len_curfile; i++) 
		    tmp[i] = tmp2[i -1];
	    }
	    return;
	}
	/* find out how many initial chars are the same */
	strcpy(tmp, curfile);
	strcpy(tmp2, mdir);
	for (i = 0; (i < len_mdir)&&(i < len_curfile); i++)
	    if (tmp[i] != tmp2[i])  break;
	len_same = i;
	if (len_same < 3)	/* leave it alone */
	    return;
	else
	{   /* find length of last word in curfile */
	    for (i = len_curfile -1; i > 0; i--)
		if (tmp[i] == '/')	/* strange, no / present */
		    return;
	    /* now copy entire path minus filename to mdir */
	    strcpy(tmp2, tmp); /* note: length of mdir can be shortened, */
	    tmp2[i +1] = '\0'; /* 	but not increased.  This is      */
	    strcpy(mdir, tmp2);/*	still better than nothing.	 */
	}
    }
}   /* get_default_path */
#endif /* NOTUSED */


/*
 * make a backup copy of a file, use the same mode and name in the format
 * [path/]#file~
 * return 1 if we were successful, 0 otherwise
 */
int
backup_file(path)
char	*path;
{
	struct	stat	statbuf;
	char	fname[PATHLEN];
	char	tpath[PATHLEN];
#if HAVE_ST_BLKSIZE
	static	char	*buf = NULL;
	static	unsigned buflen = 0;
#else
	char	buf[BUFSIZ];
#endif
	char	*tpp;
	int	infd, outfd;
	int	count;

	/* tpath will be the [path/]file ---> [path/]#file~ */
	strcpy(tpath, path);
	if ((tpp = strrchr(tpath, '/')) == NULL)
		tpp = tpath;
	else
		tpp++;
	strcpy(fname, tpp);
	(void) sprintf(tpp, "#%s~", fname);

	if (stat(path, &statbuf) == 0)
	{
		/* if we know the optimum block size, use it */
#ifdef HAVE_ST_BLKSIZE
		if ((statbuf.st_blksize > buflen) || (buf == NULL))
		{	buflen = statbuf.st_blksize;
			if ((buf = scxrealloc(buf, buflen)) == (char *)0)
			{	buflen = 0;
				return(0);
			}
		}
#endif

		if ((infd = open(path, O_RDONLY, 0)) < 0)
			return(0);

		if ((outfd = open(tpath, O_TRUNC|O_WRONLY|O_CREAT,
					statbuf.st_mode)) < 0)
			return(0);

#ifdef HAVE_ST_BLKSIZE
		while((count = read(infd, buf, statbuf.st_blksize)) > 0)
#else
		while((count = read(infd, buf, sizeof(buf))) > 0)
#endif
		{	if (write(outfd, buf, count) != count)
			{	count = -1;
				break;
			}
		}
		close(infd);
		close(outfd);

		return((count < 0) ? 0 : 1);
	}
	else
	if (errno == ENOENT)
		return(1);
	return(0);
}
#endif /* DOBACKUP */

#ifdef JEFFB	/* old version */
/* replace a possible '.sc', '.s', or '.' suffix with "ending" */
char *
fsuffix(fname, ending)
char	*fname;
char	*ending;
{	char	*newname, *chp, *slp;

	if ((newname = scxmalloc(strlen(fname)+1+strlen(ending))) != NULL)
	{
		strcpy(newname, fname);
		/*
		 * chp will point to the start of the filename, path seperator
		 */
		if ((slp = strrchr(newname, '/')) != NULL)
		{	*slp = '\0';
			chp = slp+1;
		}
		else
			chp = newname;
	
		/* start of .sc? */
		if ((chp = strrchr(chp, '.')) != NULL)
		{	if (strncmp(chp, ".sc", strlen(chp)) == 0)
				*chp = '\0';
		}

		/* put the path '/' back */
		if (slp != NULL)
			*slp = '/';
		strcat(newname, ending);
	}
	return(newname);
}
#endif /* JEFFB */

/*
 * if ending !NULL: replace a possible '.sc', '.s', or '.' suffix with "ending"
 * if path !NULL: add to front (remove starting '*' from fname)
 */
char *
fsuffix(path, fname, ending)
char	*path, *fname;
char	*ending;
{	char	*newname, *chp, *slp;
	int	len;

	len = strlen(fname)+1;
	if (path != NULL)
		len += strlen(path);
	if (ending != NULL)
		len += strlen(ending);
	if ((newname = scxmalloc(len)) != NULL)
	{
		if (path != NULL)	/* remove leading '*', using mdir */
			sprintf(newname, "%s/%s", path, fname+1);
		else
			strcpy(newname, fname);

		if (ending == NULL)		/* no suffix to replace */
			return(newname);

		/*
		 * chp will point to the start of the filename, path seperator
		 */
		if ((slp = strrchr(newname, '/')) != NULL)
		{	*slp = '\0';
			chp = slp+1;
		}
		else
			chp = newname;
	
		/* start of .sc? */
		if ((chp = strrchr(chp, '.')) != NULL)
		{	if (strncmp(chp, ".sc", strlen(chp)) == 0)
				*chp = '\0';
		}

		/* put the path '/' back */
		if (slp != NULL)
			*slp = '/';
		strcat(newname, ending);
	}
	return(newname);
}


/* used by routines which will accept a >> designation on a filename
 * used to specify append to a file
 */
static char *
extract_filename(fname)
char	*fname;
{
	/* default mode */
	file_mode = "w";

	while (isspace(*fname))
		fname++;
	/* "> file" will be interpreted as write, ">> file" as append */
	if ((*fname == '>') && (*++fname == '>'))
	{	file_mode = "a";
		fname++;
	}
	
	while (isspace(*fname))
		fname++;
	return fname;
}

char *
what_file(fname, suffix)
char *fname, *suffix;
{
    fname = extract_filename(fname);
    if (*fname == '*' && mdir)
    	fname = fsuffix(mdir, fname, suffix);
    else
	fname = fsuffix(NULL, fname, suffix);
    return(fname);
}


syntax highlighted by Code2HTML, v. 0.9.1