Logo Search packages:      
Sourcecode: sc version File versions  Download package

cmds.c

/*    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: 7.16 $
 */

#include <sys/types.h>
#include <sys/wait.h>
#if defined(BSD42) || defined(BSD43)
#include <strings.h>
#else
#ifndef SYSIII
#include <string.h>
#endif
#endif

#include <curses.h>
#include <time.h>
#include <utime.h>
#if defined(BSD42) || defined(BSD43) || defined(VMS)
#include <sys/file.h>
#else
#include <fcntl.h>
#endif
#include "sc.h"
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#ifndef MSDOS
#include <unistd.h>
#endif

void  syncref(register struct enode *e);
void  unspecial(FILE *f, char *str, int delim);

/* a linked list of free [struct ent]'s, uses .next as the pointer */
extern      struct ent *freeents;

/* a linked list of free [struct enodes]'s, uses .e.o.left as the pointer */
extern      struct enode *freeenodes;

#define DEFCOLDELIM ':'

extern      char *scext;
extern      char *ascext;
extern      char *tbl0ext;
extern      char *tblext;
extern      char *latexext;
extern      char *slatexext;
extern      char *texext;
extern      int  Vopt;
extern      struct go_save gs;
int macrofd;
int cslop;
struct impexfilt *filt = NULL; /* root of list of impex filters */

/* copy the current row (currow) and place the cursor in the new row */
void
duprow()
{
    int c1, c2, coltmp = curcol;
    struct frange *fr;

    fr = find_frange(currow, curcol);
    c1 = fr ? fr->or_left->col : 0;
    c2 = fr ? fr->or_right->col : maxcol;

    if (currow >= maxrows - 1 || (!fr && maxrow >= maxrows - 1) ||
          (fr && fr->or_right->row >= maxrows - 1)) {
      if (!growtbl(GROWROW, 0, 0))
            return;
    }
    modflg++;
    insertrow(1, 1);
    for (curcol = c1; curcol <= c2; 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, 0, 0, maxrow, maxcol, 0);
      }
    }
    curcol = coltmp;
}

/* copy the current column (curcol) and place the cursor in the new column */
void
dupcol() 
{
    int rowtmp = currow;

    if (curcol >= maxcols - 1 || maxcol >= maxcols - 1) {
      if (!growtbl(GROWCOL, 0, 0))
            return;
    }
    modflg++;
    insertcol(1, 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, 0, 0, maxrow, maxcol, 0);
      }
    }
    currow = rowtmp;
}

/* Insert 'arg' rows.  The row(s) will be inserted before currow if delta
 * is 0; after if it is 1.
 */
void
insertrow(int arg, int delta)
{
    int     r, c, i;
    struct ent    **tmprow, **pp;
    int lim = maxrow - currow + 1;
    struct frange *fr;

    if (currow > maxrow)
      maxrow = currow;
    maxrow += arg;
    lim = maxrow - lim + delta;

    if ((maxrow >= maxrows) && !growtbl(GROWROW, maxrow, 0))
      return;

    if ((fr = find_frange(currow + delta, curcol))) {
      move_area(currow + arg + delta, fr->or_left->col, currow + delta,
            fr->or_left->col, fr->or_right->row, fr->or_right->col);
      if (!delta && fr->ir_left->row == currow + arg)
          fr->ir_left = lookat(fr->ir_left->row - arg, fr->ir_left->col);
      if (delta && fr->ir_right->row == currow)
          fr->ir_right = lookat(fr->ir_right->row + arg, fr->ir_right->col);

      for (i = 0; i < 37; i++) {    /* update all marked cells */
          if (savedrow[i] >= currow + delta &&
                savedcol[i] >= fr->or_left->col &&
                savedcol[i] <= fr->or_right->col)
            savedrow[i] += arg;
          if (savedstrow[i] >= currow + delta &&
                savedstcol[i] >= fr->or_left->col &&
                savedstcol[i] <= fr->or_right->col)
            savedstrow[i] += arg;
      }
      if (gs.g_row >= currow + delta &&
            gs.g_col >= fr->or_left->col &&
            gs.g_col <= fr->or_right->col)
          gs.g_row += arg;
      if (gs.g_lastrow >= currow + delta &&
            gs.g_lastcol >= fr->or_left->col &&
            gs.g_lastcol <= fr->or_right->col)
          gs.g_lastrow += arg;
      if (gs.strow >= currow + delta &&
            gs.stcol >= fr->or_left->col &&
            gs.stcol <= fr->or_right->col)
          gs.strow += arg;
      for (r = 0; r <= maxrow; r++) {
          for (c = 0; c <= maxcol; c++) {
            pp = ATBL(tbl, r, c);
            if (*pp) {
                if ((*pp)->nrow >= currow + delta &&
                      (*pp)->ncol >= fr->or_left->col &&
                      (*pp)->ncol <= fr->or_right->col)
                  ((*pp)->nrow) += arg;
                if ((*pp)->nlastrow >= currow + delta &&
                      (*pp)->nlastcol >= fr->or_left->col &&
                      (*pp)->nlastcol <= fr->or_right->col)
                  ((*pp)->nlastrow) += arg;
            }
          }
      }
    } else {
      
      /*
       * 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 > lim; r--) {
          row_hidden[r] = row_hidden[r-arg];
          row_hidden[r-arg] = row_hidden[r-1];
          tbl[r] = tbl[r-arg];
          tbl[r-arg] = 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.... */

      for (i = 0; i < 37; i++) {    /* update all marked cells */
          if (savedrow[i] >= currow + delta)
            savedrow[i] += arg;
          if (savedstrow[i] >= currow + delta)
            savedstrow[i] += arg;
      }
      if (gs.g_row >= currow + delta)
          gs.g_row += arg;
      if (gs.g_lastrow >= currow + delta)
          gs.g_lastrow += arg;
      if (gs.strow >= currow + delta)
          gs.strow += arg;
      for (r = 0; r <= maxrow; r++) {
          for (c = 0; c <= maxcol; c++) {
            pp = ATBL(tbl, r, c);
            if (*pp) {
                if ((*pp)->nrow >= currow + delta)
                  ((*pp)->nrow) += arg;
                if ((*pp)->nlastrow >= currow + delta)
                  ((*pp)->nlastrow) += arg;
            }
          }
      }
    }
    fix_ranges(currow + arg * (1 - delta), -1, currow + arg * (1 - delta), -1,
          delta?0:arg, delta?arg:0);
    currow += delta;
    FullUpdate++;
    modflg++;
}

/* Insert 'arg' cols.  The col(s) will be inserted before curcol if delta
 * is 0; after if it is 1.
 */
void
insertcol(int arg, int delta)
{
    int r, c;
    register struct ent **pp;
    int lim = maxcol - curcol - delta + 1;
    struct frange *fr;

    if (curcol + delta > maxcol)
      maxcol = curcol + delta;
    maxcol += arg;

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

    for (c = maxcol; c >= curcol + delta + arg; c--) {
      fwidth[c] = fwidth[c-arg];
      precision[c] = precision[c-arg];
      realfmt[c] = realfmt[c-arg];
      col_hidden[c] = col_hidden[c-arg];
    }
    for (c = curcol + delta; c - curcol - delta < arg; c++) {
      fwidth[c] = DEFWIDTH;
      precision[c] =  DEFPREC;
      realfmt[c] = DEFREFMT;
      col_hidden[c] = FALSE;
    }
      
    for (r=0; r <= maxrow; r++) {
      pp = ATBL(tbl, r, maxcol);
      for (c = lim; --c >= 0; pp--)
          if ((pp[0] = pp[-arg]))
            pp[0]->col += arg;

      pp = ATBL(tbl, r, curcol + delta);
      for (c = curcol + delta; c - curcol - delta < arg; c++, pp++)
          *pp = (struct ent *)0;
    }

    /* Update all marked cells. */
    for (c=0; c < 37; c++) {
      if (savedcol[c] >= curcol + delta)
          savedcol[c] += arg;
      if (savedstcol[c] >= curcol + delta)
          savedstcol[c] += arg;
    }
    if (gs.g_col >= curcol + delta)
      gs.g_col += arg;
    if (gs.g_lastcol >= curcol + delta)
      gs.g_lastcol += arg;
    if (gs.stcol >= curcol + delta)
      gs.stcol += arg;

    /* Update note links. */
    for (r = 0; r <= maxrow; r++) {
      for (c = 0; c <= maxcol; c++) {
          pp = ATBL(tbl, r, c);
          if (*pp) {
            if ((*pp)->ncol >= curcol + delta)
                ((*pp)->ncol) += arg;
            if ((*pp)->nlastcol >= curcol + delta)
                ((*pp)->nlastcol) += arg;
          }
      }
    }
    if (!delta && (fr = find_frange(currow, curcol)) &&
          fr->ir_left->col == curcol + arg)
      fr->ir_left = lookat(fr->ir_left->row, fr->ir_left->col - arg);
    if (delta && (fr = find_frange(currow, curcol)) &&
          fr->ir_right->col == curcol)
      fr->ir_right = lookat(fr->ir_right->row, fr->ir_right->col + arg);
    fix_ranges(-1, curcol + arg * (1 - delta), -1, curcol + arg * (1 - delta),
          delta?0:arg, delta?arg:0);
    curcol += delta;
    FullUpdate++;
    modflg++;
}

/* delete 'arg' rows starting at currow (deletes from currow downward) */
void
deleterow(register int arg)
{
    int i;
    int rs = maxrow - currow + 1;
    struct frange *fr;
    struct ent *p;
    struct ent *obuf = NULL;
    char buf[50];

    if ((fr = find_frange(currow, curcol)))
      rs = fr->or_right->row - currow + 1;
    if (rs - arg < 0) {
      rs = rs > 0 ? rs : 0;
      (void) sprintf(buf, "Can't delete %d row%s %d row%s left", arg,
            (arg != 1 ? "s," : ","), rs, (rs != 1 ? "s" : ""));
      error(buf);
      return;
    }
    if (fr) {
      if (any_locked_cells(currow, fr->or_left->col,
            currow + arg - 1, fr->or_right->col))
          error("Locked cells encountered. Nothing changed");
      else {
          FullUpdate++;
          modflg++;
          if (dbidx < 0) dbidx++;
          delbuf[dbidx] = delbuf[DELBUFSIZE - 1];
          delbuffmt[dbidx] = delbuffmt[DELBUFSIZE - 1];
          delbuf[DELBUFSIZE - 1] = NULL;
          delbuffmt[DELBUFSIZE - 1] = NULL;
          for (i = dbidx + 1; i < DELBUFSIZE; i++) {
            if (delbuf[i] == delbuf[dbidx]) {
                delbuf[dbidx] = NULL;
                delbuffmt[dbidx] = NULL;
                break;
            }
          }
          flush_saved();
          if (qbuf) {
            if (dbidx < 0) dbidx++;
            delbuf[dbidx] = delbuf[qbuf];
            delbuffmt[dbidx] = delbuffmt[qbuf];
            flush_saved();
            obuf = delbuf[qbuf];    /* orig. contents of the del. buffer */
          }
          sync_refs();
          erase_area(currow, fr->or_left->col, currow + arg - 1,
                fr->or_right->col, 0);
          fix_ranges(currow, -1, currow + arg - 1, -1, -1, -1);
          for (i = 0; i < DELBUFSIZE; i++)
            if ((obuf && delbuf[i] == obuf) || (qbuf && i == qbuf)) {
                delbuf[i] = delbuf[dbidx];
                delbuffmt[i] = delbuffmt[dbidx];
            }
          qbuf = 0;
          for (i = DELBUFSIZE - 1; i > DELBUFSIZE - 9; i--) {
            delbuf[i] = delbuf[i-1];
            delbuffmt[i] = delbuffmt[i-1];
          }
          delbuf[DELBUFSIZE - 9] = delbuf[dbidx];
          delbuffmt[DELBUFSIZE - 9] = delbuffmt[dbidx];
          for (p = delbuf[dbidx]; p; p = p->next)
            p->flags &= ~may_sync;
          if (currow + arg > fr->ir_right->row &&
                fr->ir_right->row >= currow)
            fr->ir_right = lookat(currow - 1, fr->ir_right->col);
          if (currow + arg > fr->or_right->row)
            fr->or_right = lookat(currow - 1, fr->or_right->col);
          else
            move_area(currow, fr->or_left->col, currow + arg,
                  fr->or_left->col, fr->or_right->row, fr->or_right->col);
          if (fr->ir_left->row > fr->ir_right->row)
            add_frange(fr->or_left, fr->or_right, NULL, NULL, 0, 0, 0, 0);

          /* Update all marked cells. */
          for (i = 0; i < 37; i++) {
            if (savedcol[i] >= fr->or_left->col &&
                  savedcol[i] <= fr->or_right->col) {
                if (savedrow[i] >= currow && savedrow[i] < currow + arg)
                  savedrow[i] = savedcol[i] = -1;
                if (savedrow[i] >= currow + arg)
                  savedrow[i] -= arg;
            }
            if (savedstcol[i] >= fr->or_left->col &&
                  savedstcol[i] <= fr->or_right->col) {
                if (savedstrow[i] >= currow && savedstrow[i] < currow + arg)
                  savedstrow[i] = currow;
                if (savedstrow[i] >= currow + arg)
                  savedstrow[i] -= arg;
            }
          }
          if (gs.g_col >= fr->or_left->col &&
                gs.g_col <= fr->or_right->col) {
            if (gs.g_row >= currow && gs.g_row < currow + arg)
                gs.g_row = currow;
            if (gs.g_row >= currow + arg)
                gs.g_row -= arg;
          }
          if (gs.g_lastcol >= fr->or_left->col &&
                gs.g_lastcol <= fr->or_right->col) {
            if (gs.g_lastrow >= currow && gs.g_lastrow < currow + arg)
                gs.g_lastrow = currow - 1;
            if (gs.g_lastrow >= currow + arg)
                gs.g_lastrow -= arg;
          }
          if (gs.g_row > gs.g_lastrow)
            gs.g_row = gs.g_col = -1;
          if (gs.stcol >= fr->or_left->col &&
                gs.stcol <= fr->or_right->col) {
            if (gs.strow >= currow && gs.strow < currow + arg)
                gs.strow = currow;
            if (gs.strow >= currow + arg)
                gs.strow -= arg;
          }
      }
    } else {
      
      if (any_locked_cells(currow, 0, currow + arg - 1, maxcol))
          error("Locked cells encountered. Nothing changed");
      else {
          if (dbidx < 0) dbidx++;
          delbuf[dbidx] = delbuf[DELBUFSIZE - 1];
          delbuffmt[dbidx] = delbuffmt[DELBUFSIZE - 1];
          delbuf[DELBUFSIZE - 1] = NULL;
          delbuffmt[DELBUFSIZE - 1] = NULL;
          for (i = dbidx + 1; i < DELBUFSIZE; i++) {
            if (delbuf[i] == delbuf[dbidx]) {
                delbuf[dbidx] = NULL;
                delbuffmt[dbidx] = NULL;
                break;
            }
          }
          flush_saved();
          if (qbuf) {
            if (dbidx < 0) dbidx++;
            delbuf[dbidx] = delbuf[qbuf];
            delbuffmt[dbidx] = delbuffmt[qbuf];
            flush_saved();
            obuf = delbuf[qbuf];    /* orig. contents of the del. buffer */
          }
          sync_refs();
          erase_area(currow, 0, currow + arg - 1, maxcol, 0);
          fix_ranges(currow, -1, currow + arg - 1, -1, -1, -1);
          closerow(currow, arg);
          for (i = 0; i < DELBUFSIZE; i++)
            if ((obuf && delbuf[i] == obuf) || (qbuf && i == qbuf)) {
                delbuf[i] = delbuf[dbidx];
                delbuffmt[i] = delbuffmt[dbidx];
            }
          qbuf = 0;
          for (i = DELBUFSIZE - 1; i > DELBUFSIZE - 9; i--) {
            delbuf[i] = delbuf[i-1];
            delbuffmt[i] = delbuffmt[i-1];
          }
          delbuf[DELBUFSIZE - 9] = delbuf[dbidx];
          delbuffmt[DELBUFSIZE - 9] = delbuffmt[dbidx];
          for (p = delbuf[dbidx]; p; p = p->next)
            p->flags &= ~may_sync;
      }
    }
}

void
yankrow(int arg)
{
    int rs = maxrow - currow + 1;
    int i, qtmp;
    char buf[50];
    struct frange *fr;
    struct ent *obuf;

    if ((fr = find_frange(currow, curcol)))
      rs = fr->or_right->row - currow + 1;
    if (rs - arg < 0) {
      rs = rs > 0 ? rs : 0;
      (void) sprintf(buf, "Can't yank %d row%s %d row%s left", arg,
            (arg != 1 ? "s," : ","), rs, (rs != 1 ? "s" : ""));
      error(buf);
      return;
    }
    sync_refs();
    if (dbidx < 0) dbidx++;
    delbuf[dbidx] = delbuf[DELBUFSIZE - 10];
    delbuffmt[dbidx] = delbuffmt[DELBUFSIZE - 10];
    delbuf[DELBUFSIZE - 10] = NULL;
    delbuffmt[DELBUFSIZE - 10] = NULL;
    for (i = dbidx + 1; i < DELBUFSIZE; i++) {
      if (delbuf[i] == delbuf[dbidx]) {
          delbuf[dbidx] = NULL;
          delbuffmt[dbidx] = NULL;
          break;
      }
    }
    flush_saved();
    if (qbuf) {
      if (dbidx < 0) dbidx++;
      delbuf[dbidx] = delbuf[qbuf];
      delbuffmt[dbidx] = delbuffmt[qbuf];
      flush_saved();
      obuf = delbuf[qbuf];    /* orig. contents of the del. buffer */
    }
    qtmp = qbuf;
    qbuf = 0;
    if (fr) {
      yank_area(currow, fr->or_left->col, currow + arg - 1,
            fr->or_right->col);
    } else {
      yank_area(currow, 0, currow + arg - 1, maxcol);
    }
    qbuf = qtmp;
    for (i = 0; i < DELBUFSIZE; i++)
      if ((obuf && delbuf[i] == obuf) || (qbuf && i == qbuf)) {
          delbuf[i] = delbuf[dbidx];
          delbuffmt[i] = delbuffmt[dbidx];
      }
    qbuf = 0;
    delbuf[DELBUFSIZE - 10] = delbuf[dbidx];
    delbuffmt[DELBUFSIZE - 10] = delbuffmt[dbidx];
}

void
yankcol(int arg)
{
    int cs = maxcol - curcol + 1;
    int i, qtmp;
    char buf[50];
    struct ent *obuf;

    if (cs - arg < 0) {
      cs = cs > 0 ? cs : 0;
      (void) sprintf(buf, "Can't yank %d column%s %d column%s left", arg,
            (arg != 1 ? "s," : ","), cs, (cs != 1 ? "s" : ""));
      error(buf);
      return;
    }
    sync_refs();
    if (dbidx < 0) dbidx++;
    delbuf[dbidx] = delbuf[DELBUFSIZE - 10];
    delbuffmt[dbidx] = delbuffmt[DELBUFSIZE - 10];
    delbuf[DELBUFSIZE - 10] = NULL;
    delbuffmt[DELBUFSIZE - 10] = NULL;
    for (i = dbidx + 1; i < DELBUFSIZE; i++) {
      if (delbuf[i] == delbuf[dbidx]) {
          delbuf[dbidx] = NULL;
          delbuffmt[dbidx] = NULL;
          break;
      }
    }
    flush_saved();
    if (qbuf) {
      if (dbidx < 0) dbidx++;
      delbuf[dbidx] = delbuf[qbuf];
      delbuffmt[dbidx] = delbuffmt[qbuf];
      flush_saved();
      obuf = delbuf[qbuf];    /* orig. contents of the del. buffer */
    }
    qtmp = qbuf;
    qbuf = 0;
    yank_area(0, curcol, maxrow, curcol + arg - 1);
    qbuf = qtmp;
    for (i = 0; i < DELBUFSIZE; i++)
      if ((obuf && delbuf[i] == obuf) || (qbuf && i == qbuf)) {
          delbuf[i] = delbuf[dbidx];
          delbuffmt[i] = delbuffmt[dbidx];
      }
    qbuf = 0;
    delbuf[DELBUFSIZE - 10] = delbuf[dbidx];
    delbuffmt[DELBUFSIZE - 10] = delbuffmt[dbidx];
}

/* ignorelock is used when sorting so that locked cells can still be sorted */

void
erase_area(int sr, int sc, int er, int ec, int ignorelock)
{
    int r, c;
    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);

    /* Do a lookat() for the upper left and lower right cells of the range
     * being erased to make sure they are included in the delete buffer so
     * that pulling cells always works correctly even if the cells at one
     * or more edges of the range are all empty.
     */
    (void) lookat(sr, sc);
    (void) lookat(er, ec);

    delbuffmt[++dbidx] = (char*)scxmalloc((4*(ec-sc+1)+(er-sr+1))*sizeof(char));
    for (c = sc; c <= ec; c++) {
      delbuffmt[dbidx][4*(c-sc)] = (char)fwidth[c];
      delbuffmt[dbidx][4*(c-sc)+1] = (char)precision[c];
      delbuffmt[dbidx][4*(c-sc)+2] = (char)realfmt[c];
      delbuffmt[dbidx][4*(c-sc)+3] = (char)col_hidden[c];
    }
    for (r = sr; r <= er; r++) {
      for (c = sc; c <= ec; c++) {
          pp = ATBL(tbl, r, c);
          if (*pp && (!((*pp)->flags&is_locked) || ignorelock)) {
            free_ent(*pp, 0);
            *pp = NULL;
          }
      }
      delbuffmt[dbidx][4*(ec-sc+1)+(r-sr)] = (char)row_hidden[r];
    }
}

void
yank_area(int sr, int sc, int er, int ec)
{
    int r, c;

    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);

    r = currow;
    currow = sr;
    c = curcol;
    curcol = sc;

    erase_area(sr, sc, er, ec, 0);
    pullcells('p');

    currow = r;
    curcol = c;
}

void
move_area(int dr, int dc, int sr, int sc, int er, int ec)
{
    struct ent *p;
    struct ent **pp;
    int deltar, deltac;
    int r, c;

    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);

    r = currow;
    currow = sr;
    c = curcol;
    curcol = sc;

    /* First we erase the source range, which puts the cells on the delete
     * buffer stack.
     */
    erase_area(sr, sc, er, ec, 0);

    currow = r;
    curcol = c;
    deltar = dr - sr;
    deltac = dc - sc;

    /* Now we erase the destination range, which adds it to the delete buffer
     * stack, but then we flush it off.  We then move the original source
     * range from the stack to the destination range, adjusting the addresses
     * as we go, leaving the stack in its original state.
     */
    erase_area(dr, dc, er + deltar, ec + deltac, 0);
    flush_saved();
    for (p = delbuf[dbidx]; p; p = p->next) {
      pp = ATBL(tbl, p->row + deltar, p->col + deltac);
      *pp = p;
      p->row += deltar;
      p->col += deltac;
      p->flags &= ~is_deleted;
    }
    delbuf[dbidx] = NULL;
    delbuffmt[dbidx--] = NULL;
}

/*
 * deletes the expression associated w/ a cell and turns it into a constant
 * containing whatever was on the screen
 */
void
valueize_area(int sr, int sc, int er, int ec)
{
    int r, c;
    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) {
            error(" Cell %s%d is locked", coltoa(c), r);
            continue;
          }
          if (p && p->expr) {
            efree(p->expr);
            p->expr = (struct enode *)0;
            p->flags &= ~is_strexpr;
          }
      }
    }
}

void
pullcells(int to_insert)
{
    struct ent *obuf;
    struct ent *p, *n;
    struct ent **pp;
    int deltar, deltac;
    int minrow, mincol;
    int mxrow, mxcol;
    int numrows, numcols;
    int i;
    struct frange *fr;
    
    if (qbuf && delbuf[qbuf]) {
      delbuf[++dbidx] = delbuf[qbuf];
      delbuffmt[dbidx] = delbuffmt[qbuf];
    }
    obuf = delbuf[dbidx];     /* orig. contents of the del. buffer */

    if ((qbuf && !delbuf[qbuf]) || dbidx < 0) {
      error("No data to pull");
      qbuf = 0;
      return;
    }

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

    for (p = delbuf[dbidx]; 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;
      p->flags |= may_sync;
    }

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

    if (to_insert == 'C') {
      minrow = 0;
      mincol = 0;
      mxrow = maxrows;
      mxcol = maxcols;
    }

    if (to_insert == 'r') {
      insertrow(numrows, 0);
      if (fr = find_frange(currow, curcol))
          deltac = fr->or_left->col - mincol;
      else {
          for (i = 0; i < numrows; i++)
            row_hidden[currow+i] = delbuffmt[dbidx][4*numcols+i];
          deltac = 0;
      }
    } else if (to_insert == 'c') {
      insertcol(numcols, 0);
      for (i = 0; i < numcols; i++) {
          fwidth[curcol+i] = delbuffmt[dbidx][4*i];
          precision[curcol+i] = delbuffmt[dbidx][4*i+1];
          realfmt[curcol+i] = delbuffmt[dbidx][4*i+2];
          col_hidden[curcol+i] = delbuffmt[dbidx][4*i+3];
      }
      deltar = 0;
    } else if (to_insert == 'x') {  /* Do an exchange. */
      struct ent *tmpbuf;
      char *tmpfmt;

      /* Save the original contents of the destination range on the
       * delete buffer stack in preparation for the exchange, then swap
       * the top two pointers on the stack, so that the original cells
       * to be pulled are still on top.
       */
      erase_area(minrow + deltar, mincol + deltac, mxrow + deltar,
            mxcol + deltac, 0);
      tmpbuf = delbuf[dbidx];
      delbuf[dbidx] = delbuf[dbidx - 1];
      delbuf[dbidx - 1] = tmpbuf;
      tmpfmt = delbuffmt[dbidx];
      delbuffmt[dbidx] = delbuffmt[dbidx - 1];
      delbuffmt[dbidx - 1] = tmpfmt;
    } else if (to_insert == 'p') {
      erase_area(minrow + deltar, mincol + deltac, mxrow + deltar,
            mxcol + deltac, 0);
      sync_refs();
      flush_saved();
    } else if (to_insert == 't') {
      erase_area(minrow + deltar, mincol + deltac,
            minrow + deltar + mxcol - mincol,
            mincol + deltac + mxrow - minrow, 0);
      sync_refs();
      flush_saved();
    }

    FullUpdate++;
    modflg++;

    /* At this point, we copy the cells from the delete buffer into the
     * destination range.
     */
    for (p = delbuf[dbidx]; p; p = p->next) {
      if (to_insert == 't')   /* Transpose rows and columns while pulling. */
          n = lookat(minrow + deltar + p->col - mincol,
                mincol + deltac + p->row - minrow);
      else
          n = lookat(p->row + deltar, p->col + deltac);
      copyent(n, p, deltar, deltac, minrow, mincol, mxrow, mxcol, to_insert);
    }

    /* Now exchange them so that the original cells from the delete buffer
     * are in the destination range instead of the copies.  When doing a
     * "pull exchange" ("px" or "pullxchg"), exchange the original contents
     * of the destination range with the contents of the delete buffer
     * instead.  Don't do this if transposing or merging (including merging
     * cell formats), or if the expressions in the destination cells have
     * been adjusted during a copy.
     */
    if (to_insert != 't' && to_insert != 'm' && to_insert != 'f' &&
          to_insert != 'C') {
      if (to_insert == 'x') {
          struct ent *tmpbuf = delbuf[dbidx];
          char *tmpfmt = delbuffmt[dbidx];

          delbuf[dbidx] = delbuf[dbidx - 1];
          delbuf[dbidx - 1] = tmpbuf;
          delbuffmt[dbidx] = delbuffmt[dbidx - 1];
          delbuffmt[dbidx - 1] = tmpfmt;
      } else
          for (p = delbuf[dbidx++]; p; p = p->next) {
            pp = ATBL(tbl, p->row + deltar, p->col + deltac);
            if (*pp && !((*pp)->flags & is_locked)) {
                free_ent(*pp, 1);
                *pp = NULL;
            }
          }
      for (p = delbuf[dbidx - 1]; p; p = p->next) {
          pp = ATBL(tbl, p->row + deltar, p->col + deltac);
          *pp = p;
          p->row += deltar;
          p->col += deltac;
          p->flags &= ~is_deleted;
      }
      delbuf[dbidx - 1] = delbuf[dbidx];
      delbuf[dbidx--] = NULL;

      sync_refs();
      /*
       * Now change the cell addresses in the delete buffer to match
       * where the original cells came from.
       */
      for (p = delbuf[dbidx]; p; p = p->next) {
          p->row -= deltar;
          p->col -= deltac;
      }
    } else
      sync_refs();
    
    /* Now make sure all references to the pulled cells in all named buffers
     * point to the new set of cells in the delete buffer.
     */
    for (i = 0; i < DELBUFSIZE; i++)
      if (delbuf[i] == obuf) {
          delbuf[i] = delbuf[dbidx];
          delbuffmt[i] = delbuffmt[dbidx];
      }
    if (qbuf && delbuf[qbuf]) {
      delbuf[dbidx] = NULL;
      delbuffmt[dbidx--] = NULL;
    }
    qbuf = 0;
}

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)
      error("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)
      error("No hidden rows to show");
    else {
      (void)sprintf(line,"show %d:%d", i, j);
        linelim = strlen(line);
    }
}

/*
 * 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', three extra qualifiers, 'm', 'x',
 * and 't', are allowed.  If ch is 'Z', an extra qualifier 'Z' is allowed.
 */

int
get_rcqual(int ch)
{
    int c;

    error("%sow/column:  r: row  c: column%s",

#ifdef KEY_IC
      (ch == KEY_IC)          ? "Insert r" :
#endif
      (ch == 'i')       ? "Insert r" :
      (ch == 'o')       ? "Open r" :
      (ch == 'a')       ? "Append r" :
      (ch == 'd')       ? "Delete r" :
      (ch == 'y')       ? "Yank r" :
      (ch == 'p')       ? "Pull r" :
      (ch == 'v')       ? "Values r" :
      (ch == 'Z')       ? "Zap r" :
      (ch == 's')       ? "Show r" : "R",
      
      (ch == 'p')       ? "  p: paste  m: merge  x: xchg  <MORE>" :
      (ch == 'Z')       ? "  Z: save/exit" : "");

    (void) refresh();

    switch (c = nmgetch())
    {
      case 'r':   return ('r');

      case 'c':   return ('c');

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

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

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

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

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

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

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

      case 'Z':   return ((ch == 'Z') ? 'Z' : 0);

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

      case 'd':   if (ch == 'd') {
                      ungetch('x');
                      return (ESC);
                  } else
                      return (0);

      case 'y':   if (ch == 'y') {
                      yankr(lookat(currow, curcol),
                            lookat(currow, curcol));
                      return (ESC);
                  } else
                      return (0);

      case 'v':   if (ch == 'v') {
                      valueize_area(currow, curcol, currow, curcol);
                      modflg++;
                      return (ESC);
                  } else
                      return (0);

      case KEY_UP:
      case KEY_DOWN:
      case KEY_PPAGE:
      case KEY_NPAGE:
      case 'j':
      case 'k':
      case 'J':
      case 'K':
      case ctl('f'):
      case ctl('b'):
      case ctl('n'):
      case ctl('p'):    if (ch == 'd')
                      (void) sprintf(line,"deleterow [range] ");
                  else if (ch == 'y')
                      (void) sprintf(line,"yankrow [range] ");
                  else if (ch == 'Z')
                      (void) sprintf(line,"hide [range] ");
                  else
                      return (0);
                  edit_mode();
                  write_line('A');
                  startshow();
                  showrange = SHOWROWS;
                  showsr = currow;
                  ungetch(c);
                  return (ESC);

      case KEY_BACKSPACE:
      case KEY_LEFT:
      case KEY_RIGHT:
      case ' ':
      case 'h':
      case 'l':
      case 'H':
      case 'L':   if (ch == 'd')
                      (void) sprintf(line,"deletecol [range] ");
                  else if (ch == 'y')
                      (void) sprintf(line,"yankcol [range] ");
                  else if (ch == 'Z')
                      (void) sprintf(line,"hide [range] ");
                  else
                      return (0);
                  edit_mode();
                  write_line('A');
                  startshow();
                  showrange = SHOWCOLS;
                  showsc = curcol;
                  ungetch(c);
                  return (ESC);

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

/* delete numrow rows, starting with rs */
void
closerow(int rs, int numrow)
{
    register struct ent **pp;
    int                 r, c, i;
    struct ent          **tmprow;

    if (rs + numrow - 1 > maxrow) return;
    r = rs;

/*
 * Rows are dealt with in numrow groups, each group of rows spaced numrow
 * rows apart.
 */
    for (i = 0; i < numrow; i++) {
      r = rs + i;

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

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

    /* Update all marked cells. */
    for (i = 0; i < 37; i++) {
      if (savedrow[i] >= rs && savedrow[i] < rs + numrow)
          savedrow[i] = savedcol[i] = -1;
      if (savedrow[i] >= rs + numrow)
          savedrow[i] -= numrow;
      if (savedstrow[i] >= rs && savedstrow[i] < rs + numrow)
          savedstrow[i] = rs;
      if (savedstrow[i] >= rs + numrow)
          savedstrow[i] -= numrow;
    }
    if (gs.g_row >= rs && gs.g_row < rs + numrow)
      gs.g_row = rs;
    if (gs.g_row >= rs + numrow)
      gs.g_row -= numrow;
    if (gs.g_lastrow >= rs && gs.g_lastrow < rs + numrow)
      gs.g_lastrow = rs - 1;
    if (gs.g_lastrow >= rs + numrow)
      gs.g_lastrow -= numrow;
    if (gs.g_row > gs.g_lastrow)
      gs.g_row = gs.g_col = -1;
    if (gs.strow >= rs && gs.strow < rs + numrow)
      gs.strow = rs;
    if (gs.strow >= rs + numrow)
      gs.strow -= numrow;

    maxrow -= numrow;

    /* Update note links. */
    for (r = 0; r <= maxrow; r++) {
      for (c = 0; c <= maxcol; c++) {
          pp = ATBL(tbl, r, c);
          if (*pp) {
            if ((*pp)->nrow >= rs && (*pp)->nrow < rs + numrow)
                (*pp)->nrow = rs;
            if ((*pp)->nrow >= rs + numrow)
                ((*pp)->nrow) -= numrow;
            if ((*pp)->nlastrow >= rs && (*pp)->nlastrow < rs + numrow)
                (*pp)->nlastrow = rs - 1;
            if ((*pp)->nlastrow >= rs + numrow)
                ((*pp)->nlastrow) -= numrow;
            if ((*pp)->nlastrow < (*pp)->nrow)
                (*pp)->nrow = (*pp)->ncol = -1;
          }
      }
    }
    FullUpdate++;
    modflg++;
}

/* delete group of columns (1 or more) */
void
closecol(int arg)
{
    int r, c, i;
    int cs = maxcol - curcol + 1;
    struct ent **pp;
    struct ent *p;
    struct ent *obuf = NULL;
    char buf[50];

    if (cs - arg < 0) {
      cs = cs > 0 ? cs : 0;
      (void) sprintf(buf, "Can't delete %d column%s %d column%s left", arg,
            (arg != 1 ? "s," : ","), cs, (cs != 1 ? "s" : ""));
      error(buf);
      return;
    }
    if (any_locked_cells(0, curcol, maxrow, curcol + arg - 1)) {
      error("Locked cells encountered. Nothing changed");
      return;
    }
    if (dbidx < 0) dbidx++;
    delbuf[dbidx] = delbuf[DELBUFSIZE - 1];
    delbuf[DELBUFSIZE - 1] = NULL;
    delbuffmt[dbidx] = delbuffmt[DELBUFSIZE - 1];
    delbuffmt[DELBUFSIZE - 1] = NULL;
    for (i = dbidx + 1; i < DELBUFSIZE; i++) {
      if (delbuf[i] == delbuf[dbidx]) {
          delbuf[dbidx] = NULL;
          delbuffmt[dbidx] = NULL;
          break;
      }
    }
    flush_saved();
    if (qbuf) {
      if (dbidx < 0) dbidx++;
      delbuf[dbidx] = delbuf[qbuf];
      delbuffmt[dbidx] = delbuffmt[qbuf];
      flush_saved();
      obuf = delbuf[qbuf];    /* orig. contents of the del. buffer */
    }
    sync_refs();
    erase_area(0, curcol, maxrow, curcol + arg - 1, 0);
    fix_ranges(-1, curcol, -1, curcol + arg - 1, -1, -1);
    for (i = 0; i < DELBUFSIZE; i++)
      if ((obuf && delbuf[i] == obuf) || (qbuf && i == qbuf)) {
          delbuf[i] = delbuf[dbidx];
          delbuffmt[i] = delbuffmt[dbidx];
      }
    qbuf = 0;
    for (i = DELBUFSIZE - 1; i > DELBUFSIZE - 9; i--) {
      delbuf[i] = delbuf[i-1];
      delbuffmt[i] = delbuffmt[i-1];
    }
    delbuf[DELBUFSIZE - 9] = delbuf[dbidx];
    delbuffmt[DELBUFSIZE - 9] = delbuffmt[dbidx];
    for (p = delbuf[dbidx]; p; p = p->next)
      p->flags &= ~may_sync;

    /* clear then copy the block left */
    cs = maxcols - arg - 1;
    for (r=0; r<=maxrow; r++) {
      for (c = curcol; c - curcol < arg; c++)
          if ((p = *ATBL(tbl, r, c)))
            free_ent(p, 1);

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

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

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

    /* Update all marked cells. */
    for (i = 0; i < 37; i++) {
      if (savedcol[i] >= curcol && savedcol[i] < curcol + arg)
          savedrow[i] = savedcol[i] = -1;
      if (savedcol[i] >= curcol + arg)
          savedcol[i] -= arg;
      if (savedstcol[i] >= curcol && savedstcol[i] < curcol + arg)
          savedstcol[i] = curcol;
      if (savedstcol[i] >= curcol + arg)
          savedstcol[i] -= arg;
    }
    if (gs.g_col >= curcol && gs.g_col < curcol + arg)
      gs.g_col = curcol;
    if (gs.g_col >= curcol + arg)
      gs.g_col -= arg;
    if (gs.g_lastcol >= curcol && gs.g_lastcol < curcol + arg)
      gs.g_lastcol = curcol - 1;
    if (gs.g_lastcol >= curcol + arg)
      gs.g_lastcol -= arg;
    if (gs.g_col > gs.g_lastcol)
      gs.g_row = gs.g_col = -1;
    if (gs.stcol >= curcol && gs.stcol < curcol + arg)
      gs.stcol = curcol;
    if (gs.stcol >= curcol + arg)
      gs.stcol -= arg;

    maxcol -= arg;

    /* Update note links. */
    for (r = 0; r <= maxrow; r++) {
      for (c = 0; c <= maxcol; c++) {
          pp = ATBL(tbl, r, c);
          if (*pp) {
            if ((*pp)->ncol >= curcol && (*pp)->ncol < curcol + arg)
                (*pp)->ncol = curcol;
            if ((*pp)->ncol >= curcol + arg)
                ((*pp)->ncol) -= arg;
            if ((*pp)->nlastcol >= curcol && (*pp)->nlastcol < curcol + arg)
                (*pp)->nlastcol = curcol - 1;
            if ((*pp)->nlastcol >= curcol + arg)
                ((*pp)->nlastcol) -= arg;
            if ((*pp)->nlastcol < (*pp)->ncol)
                (*pp)->nrow = (*pp)->ncol = -1;
          }
      }
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];

    FullUpdate++;
    modflg++;
}

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

    if (!loading)
      remember(0);

    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;
        }
      rowsinrange = 1;
      colsinrange = fwidth[curcol];
      if (!loading)
          remember(1);

      error (""); /* 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;
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
}

/* Modified 9/17/90 THA to handle more formats */
void
doformat(int c1, int c2, int w, int p, int 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 == 0) {
      error("Width too small - setting to 1");
      w = 1;
    }

    if (usecurses && w > COLS - rescol - 2) {
      error("Width too large - Maximum = %d", COLS - rescol - 2);
      w = COLS - rescol - 2;
    }

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

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

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

    rowsinrange = 1;
    colsinrange = fwidth[curcol];

    FullUpdate++;
    modflg++;
}

void
formatcol(arg)
{
    int c, i;
    int mf = modflg;
    int *oldformat;

    error("Current format is %d %d %d",
          fwidth[curcol], precision[curcol],
          realfmt[curcol]);
    refresh();
    oldformat = (int *)scxmalloc(arg*3*sizeof(int));
    for (i = 0; i < arg; i++) {
      oldformat[i * 3 + 0] = fwidth[i + curcol];
      oldformat[i * 3 + 1] = precision[i + curcol];
      oldformat[i * 3 + 2] = realfmt[i + curcol];
    }
    c = nmgetch();
    while (c >= 0 && c != ctl('m') && c != 'q' && c != ESC &&
          c != ctl('g') && linelim < 0) {
      if (c >= '0' && c <= '9')
          for (i = curcol; i < curcol + arg; i++)
            realfmt[i] = c - '0';
      else
          switch (c) {
            case KEY_LEFT:
            case '<':
            case 'h':
                for (i = curcol; i < curcol + arg; i++) {
                  fwidth[i]--;
                  if (fwidth[i] < 1)
                      fwidth[i] = 1;
                }
                rowsinrange = 1;
                colsinrange = fwidth[curcol];
                modflg++;
                break;
            case KEY_RIGHT:
            case '>':
            case 'l':
                for (i = curcol; i < curcol + arg; i++) {
                  fwidth[i]++;
                  if (fwidth[i] > COLS - rescol - 2)
                      fwidth[i] = COLS - rescol - 2;
                }
                rowsinrange = 1;
                colsinrange = fwidth[curcol];
                modflg++;
                break;
            case KEY_DOWN:
            case '-':
            case 'j':
                for (i = curcol; i < curcol + arg; i++) {
                  precision[i]--;
                  if (precision[i] < 0)
                      precision[i] = 0;
                }
                modflg++;
                break;
            case KEY_UP:
            case '+':
            case 'k':
                for (i = curcol; i < curcol + arg; i++)
                  precision[i]++;
                modflg++;
                break;
            case ' ':
                if (arg == 1)
                  (void) sprintf(line,
                        "format [for column] %s ",
                        coltoa(curcol));
                else {
                  (void) sprintf(line,
                        "format [for columns] %s:",
                        coltoa(curcol));
                  (void) sprintf(line+strlen(line), "%s ",
                        coltoa(curcol+arg-1));
                }
                linelim = strlen(line);
                insert_mode();
                error("Current format is %d %d %d",
                      fwidth[curcol], precision[curcol],
                      realfmt[curcol]);
                continue;
            case '=':
                error("Define format type (0-9):");
                refresh();
                if ((c = nmgetch()) >= '0' && c <= '9') {
                  if (colformat[c-'0']) {
                      (void) sprintf(line,
                            "format %c = \"%s\"", c, colformat[c-'0']);
                      edit_mode();
                      linelim = strlen(line) - 1;
                  } else {
                      (void) sprintf(line,
                            "format %c = \"", c);
                      insert_mode();
                      linelim = strlen(line);
                  }
                  error("");
                } else {
                  error("Invalid format type");
                  c = -1;
                }
                continue;
            case ctl('l'):
                FullUpdate++;
                clearok(stdscr, 1);
                break;
            default:
                break;
          }
      error("Current format is %d %d %d",
            fwidth[curcol], precision[curcol],
            realfmt[curcol]);
      FullUpdate++;
      update(1);
      refresh();
      if (linelim < 0)
          if ((c = nmgetch()) == ESC || c == ctl('g') || c == 'q') {
            for (i = 0; i < arg; i++) {
                fwidth[i + curcol] = oldformat[i * 3 + 0];
                precision[i + curcol] = oldformat[i * 3 + 1];
                realfmt[i + curcol] = oldformat[i * 3 + 2];
            }
            modflg = mf;
            FullUpdate++;
            update(1);
          }
    }
    scxfree((char *)oldformat);
    if (c >= 0)
      error("");
}

void
ljustify(sr, sc, er, ec)
{
    struct ent *p;
    int i, j;

    if (sr > er) {
      i = sr;
      sr = er;
      er = i;
    }
    if (sc > ec) {
      i = sc;
      sc = ec;
      ec = i;
    }
    for (i = sr; i <= er; i++) {
      for (j = sc; j <= ec; j++) {
          p = *ATBL(tbl, i, j);
          if (p && p->label) {
            p->flags &= ~is_label;
            p->flags |= is_leftflush | is_changed;
            changed++;
            modflg++;
          }
      }
    }
}

void
rjustify(sr, sc, er, ec)
{
    struct ent *p;
    int i, j;

    if (sr > er) {
      i = sr;
      sr = er;
      er = i;
    }
    if (sc > ec) {
      i = sc;
      sc = ec;
      ec = i;
    }
    for (i = sr; i <= er; i++) {
      for (j = sc; j <= ec; j++) {
          p = *ATBL(tbl, i, j);
          if (p && p->label) {
            p->flags &= ~(is_label | is_leftflush);
            p->flags |= is_changed;
            changed++;
            modflg++;
          }
      }
    }
}

void
center(sr, sc, er, ec)
{
    struct ent *p;
    int i, j;

    if (sr > er) {
      i = sr;
      sr = er;
      er = i;
    }
    if (sc > ec) {
      i = sc;
      sc = ec;
      ec = i;
    }
    for (i = sr; i <= er; i++) {
      for (j = sc; j <= ec; j++) {
          p = *ATBL(tbl, i, j);
          if (p && p->label) {
            p->flags &= ~is_leftflush;
            p->flags |= is_label | is_changed;
            changed++;
            modflg++;
          }
      }
    }
}

void
print_options(FILE *f)
{
    if (
      autocalc &&
      !autoinsert &&
      !autowrap &&
      !cslop &&
      !optimize &&
      !rndtoeven &&
      propagation == 10 &&
      calc_order == BYROWS &&
      !numeric &&
      prescale == 1.0 &&
      !extfunc &&
      showtop &&
      tbl_style == 0 &&
      craction == 0 &&
      pagesize == 0 &&
      rowlimit == -1 &&
      collimit == -1 &&
      !color &&
      !colorneg &&
      !colorerr
       )
      return;           /* No reason to do this */

    (void) fprintf(f, "set");
    if (!autocalc) 
      (void) fprintf(f," !autocalc");
    if (autoinsert) 
      (void) fprintf(f," autoinsert");
    if (autowrap) 
      (void) fprintf(f," autowrap");
    if (cslop)
      (void) fprintf(f," cslop");
    if (optimize) 
      (void) fprintf(f," optimize");
    if (rndtoeven)
      (void) fprintf(f, " rndtoeven");
    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 (!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 (pagesize)
      (void) fprintf(f, " pagesize = %d", pagesize);
    if (rowlimit >= 0)
      (void) fprintf(f, " rowlimit = %d", rowlimit);
    if (collimit >= 0)
      (void) fprintf(f, " collimit = %d", collimit);
    if (color) 
      (void) fprintf(f," color");
    if (colorneg) 
      (void) fprintf(f," colorneg");
    if (colorerr) 
      (void) fprintf(f," colorerr");
    (void) fprintf(f, "\n");
}

void
printfile(char *fname, int r0, int c0, int rn, int cn)
{
    FILE *f;
    static char *pline = NULL;            /* only malloc once, malloc is slow */
    static unsigned fbufs_allocated = 0;
    int plinelim;
    int pid;
    int fieldlen, nextcol;
    long namelen;
    int row, col;
    register struct ent **pp;
    char file[256];
    char path[1024];
    char *tpp;

    if (fname) {
      /* printfile will be the [path/]file ---> [path/]file.out */
      if (*fname == '\0') {
          strcpy(path, curfile);

#ifdef MSDOS
          namelen = 12;
          if ((tpp = strrchr(path, '\\')) == NULL) {
#else
          if ((tpp = strrchr(path, '/')) == NULL) {
            namelen = pathconf(".", _PC_NAME_MAX);
#endif
            tpp = path;
          } else {
#ifndef MSDOS
            *tpp = '\0';
            namelen = pathconf(path, _PC_NAME_MAX);
            *tpp = '/';
#endif
            tpp++;
          }
          strcpy(file, tpp);

          if (!strcmp(file + strlen(file) - 3, ".sc"))
            file[strlen(file) - 3] = '\0';
          else if (scext != NULL && file[strlen(file) - strlen(scext) - 1] == '.'
                && !strcmp(file + strlen(file) - strlen(scext), scext))
            file[strlen(file) - strlen(scext)] = '\0';

          if (ascext == NULL)
            file[namelen - 4] = '\0';
          else
            file[namelen - strlen(ascext) - 1] = '\0';
          sprintf(tpp, "%s.%s", file, ascext == NULL ? "asc" : ascext);
          fname = path;
      }

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

      if ((f = openfile(fname, &pid, NULL)) == (FILE *)0) {
          error("Can't create file \"%s\"", fname);
          return;
      }
    } else
      f = stdout;

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

    for (row = r0; row <= rn; row++) {
      int 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) {
                  error ("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) {
                  error("Realloc failed in printfile()");
                  return;
                  }
                }
                if ((*pp)->cellerror)
                  (void) sprintf(pline+plinelim, "%*s",
                        fwidth[col], ((*pp)->cellerror == CELLERROR ?
                        "ERROR " : "INVALID "));
                else {
                  char *cfmt;

                  cfmt = (*pp)->format ? (*pp)->format :
                      (realfmt[col] >= 0 && realfmt[col] < COLFORMATS &&
                      colformat[realfmt[col]]) ?
                      colformat[realfmt[col]] : 0;
                  if (cfmt) {
                    char field[FBUFLEN];

                  if (*cfmt == ctl('d')) {
                      time_t v = (time_t) ((*pp)->v);
                      strftime(field, sizeof(field), cfmt + 1,
                            localtime(&v));
                      sprintf(pline+plinelim, "%-*s", fwidth[col],
                            field);
                  } else {
                      format(cfmt, precision[col], (*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)) {
                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 repetition
                 * char
                 */
                slen = strlen(s);
                if (*s == '\\' && *(s+1) != '\0')
                  slen = fwidth[col];
                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) {
                  error ("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);
    }

    if (fname) closefile(f, pid, 0);
}

void
tblprintfile(char *fname, int r0, int c0, int rn, int cn)
{
    FILE *f;
    int pid;
    long namelen;
    int row, col;
    register struct ent **pp;
    char coldelim = DEFCOLDELIM;
    char file[256];
    char path[1024];
    char *tpp;

    /* tblprintfile will be the [path/]file ---> [path/]file.out */
    if (*fname == '\0') {
      strcpy(path, curfile);

#ifdef MSDOS
      namelen = 12;
      if ((tpp = strrchr(path, '\\')) == NULL) {
#else
      if ((tpp = strrchr(path, '/')) == NULL) {
          namelen = pathconf(".", _PC_NAME_MAX);
#endif
          tpp = path;
      } else {
#ifndef MSDOS
          *tpp = '\0';
          namelen = pathconf(path, _PC_NAME_MAX);
          *tpp = '/';
#endif
          tpp++;
      }
      strcpy(file, tpp);

      if (!strcmp(file + strlen(file) - 3, ".sc"))
          file[strlen(file) - 3] = '\0';
      else if (scext != NULL && file[strlen(file) - strlen(scext) - 1] == '.'
            && !strcmp(file + strlen(file) - strlen(scext), scext))
          file[strlen(file) - strlen(scext)] = '\0';

      if (tbl_style == 0) {
          if (tbl0ext == NULL)
            file[namelen - 4] = '\0';
          else
            file[namelen - strlen(tbl0ext) - 1] = '\0';
          sprintf(tpp, "%s.%s", file, tbl0ext == NULL ? "cln" : tbl0ext);
      }
      else if (tbl_style == TBL) {
          if (tblext == NULL)
            file[namelen - 4] = '\0';
          else
            file[namelen - strlen(tblext) - 1] = '\0';
          sprintf(tpp, "%s.%s", file, tblext == NULL ? "tbl" : tblext);
      }
      else if (tbl_style == LATEX) {
          if (latexext == NULL)
            file[namelen - 4] = '\0';
          else
            file[namelen - strlen(latexext) - 1] = '\0';
          sprintf(tpp, "%s.%s", file, latexext == NULL ? "lat" : latexext);
      }
      else if (tbl_style == SLATEX) {
          if (slatexext == NULL)
            file[namelen - 4] = '\0';
          else
            file[namelen - strlen(slatexext) - 1] = '\0';
          sprintf(tpp, "%s.%s", file, slatexext == NULL ? "stx" : slatexext);
      }
      else if (tbl_style == TEX) {
          if (texext == NULL)
            file[namelen - 4] = '\0';
          else
            file[namelen - strlen(texext) - 1] = '\0';
          sprintf(tpp, "%s.%s", file, texext == NULL ? "tex" : texext);
      }
      fname = path;
    }

    if ((strcmp(fname, curfile) == 0) &&
      !yn_ask("Confirm that you want to destroy the data base: (y,n)"))
          return;

    if ((f = openfile(fname, &pid, NULL)) == (FILE *)0) {
      error ("Can't create file \"%s\"", 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];
                  if (*((*pp)->format) == ctl('d')) {
                      time_t v = (time_t) ((*pp)->v);
                      strftime(field, sizeof(field), ((*pp)->format)+1,
                            localtime(&v));
                  } else
                      format((*pp)->format, precision[col], (*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)) {
                  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");
    }

    closefile(f, pid, 0);
}

/* unspecial (backquote) things that are special chars in a table */
void
unspecial(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 == '&')))
          putc('\\', f);
      putc(*str, f);
      str++;
    }
}

struct enode *
copye(register struct enode *e, int Rdelta, int Cdelta, int r1, int c1,
      int r2, int c2, int transpose)
{
    register struct enode *ret;
    static struct enode *range = NULL;

    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 < r1 || e->e.r.left.vp->row > r2 ||
             e->e.r.left.vp->col < c1 || e->e.r.left.vp->col > c2 ?
             e->e.r.left.vp->row :
             transpose ? r1 + Rdelta + e->e.r.left.vp->col - c1 :
             e->e.r.left.vp->row + Rdelta;
      newcol = e->e.r.left.vf & FIX_COL ||
             e->e.r.left.vp->row < r1 || e->e.r.left.vp->row > r2 ||
             e->e.r.left.vp->col < c1 || e->e.r.left.vp->col > c2 ?
             e->e.r.left.vp->col :
             transpose ? c1 + Cdelta + e->e.r.left.vp->row - r1 :
             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 < r1 || e->e.r.right.vp->row > r2 ||
             e->e.r.right.vp->col < c1 || e->e.r.right.vp->col > c2 ?
             e->e.r.right.vp->row :
             transpose ? r1 + Rdelta + e->e.r.right.vp->col - c1 :
             e->e.r.right.vp->row + Rdelta;
      newcol = e->e.r.right.vf & FIX_COL ||
             e->e.r.right.vp->row < r1 || e->e.r.right.vp->row > r2 ||
             e->e.r.right.vp->col < c1 || e->e.r.right.vp->col > c2 ?
             e->e.r.right.vp->col :
             transpose ? c1 + Cdelta + e->e.r.right.vp->row - r1 :
             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 {
      struct enode *temprange;

      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 SUM:
          case PROD:
          case AVG:
          case COUNT:
          case STDDEV:
          case MAX:
          case MIN:
            temprange = range;
            range = e->e.o.left;
            r1 = 0;
            c1 = 0;
            r2 = maxrow;
            c2 = maxcol;
      }
      switch (ret->op) {
          case 'v':
            {
                int newrow, newcol;
                if (range && e->e.v.vp->row >= range->e.r.left.vp->row &&
                         e->e.v.vp->row <= range->e.r.right.vp->row &&
                         e->e.v.vp->col >= range->e.r.left.vp->col &&
                         e->e.v.vp->col <= range->e.r.right.vp->col) {
                  newrow = range->e.r.left.vf & FIX_ROW ?
                        e->e.v.vp->row : e->e.v.vp->row + Rdelta;
                  newcol = range->e.r.left.vf & FIX_COL ?
                        e->e.v.vp->col : e->e.v.vp->col + Cdelta;
                } else {
                  newrow = e->e.v.vf & FIX_ROW ||
                         e->e.v.vp->row < r1 || e->e.v.vp->row > r2 ||
                         e->e.v.vp->col < c1 || e->e.v.vp->col > c2 ?
                         e->e.v.vp->row :
                         transpose ? r1 + Rdelta + e->e.v.vp->col - c1 :
                         e->e.v.vp->row + Rdelta;
                  newcol = e->e.v.vf & FIX_COL ||
                         e->e.v.vp->row < r1 || e->e.v.vp->row > r2 ||
                         e->e.v.vp->col < c1 || e->e.v.vp->col > c2 ?
                         e->e.v.vp->col :
                         transpose ? c1 + Cdelta + e->e.v.vp->row - r1 :
                         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':
          case 'F':
            if (range && ret->op == 'F' ||
                  !range && ret->op == 'f')
                Rdelta = Cdelta = 0;
            ret->e.o.left = copye(e->e.o.left, Rdelta, Cdelta,
                  r1, c1, r2, c2, transpose);
            ret->e.o.right = (struct enode *)0;
            break;
          case '$':
          case EXT:
            ret->e.s = scxmalloc((unsigned) strlen(e->e.s)+1);
            (void) strcpy(ret->e.s, e->e.s);
            if (e->op == '$') /* Drop through if ret->op is EXT */
                break;
          default:
            ret->e.o.left = copye(e->e.o.left, Rdelta, Cdelta,
                  r1, c1, r2, c2, transpose);
            ret->e.o.right = copye(e->e.o.right, Rdelta, Cdelta,
                  r1, c1, r2, c2, transpose);
            break;
      }
      switch (ret->op) {
          case SUM:
          case PROD:
          case AVG:
          case COUNT:
          case STDDEV:
          case MAX:
          case MIN:
            range = temprange;
      }
    }
    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()
{
    int 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)
            syncref(p->expr);
    for (i = 0; i < DELBUFSIZE; i++) {
      p = delbuf[i];
      while (p && p->expr) {
          syncref(p->expr);
          p = p->next;
      }
    }
}

void
syncref(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':
            if (e->e.v.vp->flags & is_cleared) {
                e->op = ERR_;
                e->e.o.left = NULL;
                e->e.o.right = NULL;
            } else if (e->e.v.vp->flags & may_sync)
                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(int arg)
{
    register int r1;
    register int r2;

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

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

    c1 = curcol;
    c2 = c1 + arg - 1;
    if (c1 < 0 || c1 > c2) {
      error ("Invalid range");
      return;
    }
    if (c2 >= maxcols-1) {
      if ((arg >= ABSMAXCOLS-1) || !growtbl(GROWCOL, 0, arg+1)) {
          error("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(int r1, int r2)
{
    if (r1 < 0 || r1 > r2) {
      error ("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(int c1, int c2)
{
    if (c1 < 0 || c1 > c2) {
      error ("Invalid range");
      return;
    }
    if (c2 > maxcols-1) {
      c2 = maxcols-1;
    }
    FullUpdate++;
    modflg++;
    while (c1 <= c2)
      col_hidden[c1++] = FALSE;
}

/* Open the input or output file, setting up a pipe if needed */
FILE *
openfile(char *fname, int *rpid, int *rfd)
{
    int pipefd[4];
    int pid;
    FILE *f;
    char *efname;

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

    if (*fname != '|') {            /* Open file if not pipe */
      *rpid = 0;
      if (rfd != NULL)
          *rfd = 1;                 /* Set to stdout just in case */
      
      efname = findhome(fname);
#ifdef DOBACKUPS
      if (rfd == NULL && !backup_file(efname) &&
          (yn_ask("Could not create backup copy.  Save anyway?: (y,n)") != 1))
            return (0);
#endif
      return (fopen(efname, rfd == NULL ? "w" : "r"));
    }

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

    deraw(rfd==NULL);
#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 first pipe */
      if (rfd != NULL) {            /* if opening for read */
          (void) close(1);          /* close stdout */
          (void) close(pipefd[2]);
          (void) dup(pipefd[3]);    /* connect to second pipe */
      }
      (void) signal(SIGINT, SIG_DFL);     /* reset */
      (void) execl("/bin/sh", "sh", "-c", efname, 0);
      exit (-127);
    } else {                        /* else parent */
      *rpid = pid;
      if ((f = fdopen(pipefd[(rfd==NULL?1:2)], rfd==NULL?"w":"r")) == NULL) {
          (void) kill(pid, 9);
          error("Can't fdopen %sput", rfd==NULL?"out":"in");
          (void) close(pipefd[1]);
          if (rfd != NULL)
            (void) close(pipefd[3]);
          *rpid = 0;
          return (0);
      }
    }
    (void) close(pipefd[0]);
    if (rfd != NULL) {
      (void) close(pipefd[3]);
      *rfd = pipefd[1];
    }
#endif /* VMS */
    return (f);
#endif /* MSDOS */
}

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

    (void) fclose(f);
#ifndef MSDOS
    if (pid) {
      while (pid != wait(&temp)) /**/;
      if (rfd==0) {
          (void) printf("Press any key to continue ");
          (void) fflush(stdout);
          cbreak();
          (void) nmgetch();
          goraw();
          clear();
      } else {
          close(rfd);
          if (usecurses) {
#ifdef VMS
            VMS_read_raw = 1;
#else /* VMS */
#if SYSV2 || SYSV3
            fixterm();
#else /* SYSV2 || SYSV3 */
            cbreak();
            nonl();
            noecho ();
#endif /* SYSV2 || SYSV3 */
            kbd_again();
#endif /* VMS */
            if (color && has_colors())
                bkgdset(COLOR_PAIR(1) | ' ');
          }
      }
    }
#endif /* MSDOS */
    if (brokenpipe) {
      error("Broken pipe");
      brokenpipe = FALSE;
    }
}

/* Copy a cell (struct ent).  "special" indicates special treatment when
 * merging two cells for the "pm" command, merging formats only for the
 * "pf" command, or for adjusting cell references when transposing with
 * the "pt" command.  r1, c1, r2, and c2 define the range in which the dr
 * and dc values should be used.
 */
void
copyent(register struct ent *n, register struct ent *p, int dr, int dc,
      int r1, int c1, int r2, int c2, int special)
{
    if (!n || !p) {
      error("internal error");
      return;
    }
    if (special != 'f') {
      if (special != 'm' || p->flags & is_valid) {
          n->v = p->v;
          n->flags |= p->flags & is_valid;
      }
      if (special != 'm' || p->expr) {
          n->expr = copye(p->expr, dr, dc, r1, c1, r2, c2, special == 't');
          if (p->flags & is_strexpr)
            n->flags |= is_strexpr;
          else
            n->flags &= ~is_strexpr;
      }
      if (p->label) {
          if (n->label)
            scxfree(n->label);
          n->label = scxmalloc((unsigned) (strlen(p->label) + 1));
          (void) strcpy(n->label, p->label);
          n->flags &= ~is_leftflush;
          n->flags |= ((p->flags & is_label) | (p->flags & is_leftflush));
      } else if (special != 'm') {
          n->label = NULL;
          n->flags &= ~(is_label | is_leftflush);
      }
      n->flags |= p->flags & is_locked;
    }
    if (p->format) {
      if (n->format)
          scxfree(n->format);
        n->format = scxmalloc((unsigned) (strlen(p->format) + 1));
      (void) strcpy(n->format, p->format);
    } else if (special != 'm' && special != 'f')
      n->format = NULL;
    n->flags |= is_changed;
}

#ifndef MSDOS
/* add a plugin/mapping pair to the end of the filter list. type is
 * r(ead) or w(rite)
 */

void
addplugin(char *ext, char *plugin, char type)
{
    struct impexfilt *fp;
    char mesg[PATHLEN];

    if (!plugin_exists(plugin, strlen(plugin), mesg)) {
      error("Cannot find plugin %s", plugin);
      return;
    }
    if (filt == NULL) {
      filt = (struct impexfilt *) scxmalloc((unsigned)sizeof(struct impexfilt));
      fp = filt;
    } else {
      fp = filt;
      while (fp->next != NULL)
          fp = fp->next;
      fp->next = (struct impexfilt *)scxmalloc((unsigned)sizeof(struct impexfilt));
      fp = fp->next;
    }
    strcpy(fp->plugin, plugin);
    strcpy(fp->ext, ext);
    fp->type = type;
    fp->next = NULL;
}

char *
findplugin(char *ext, char type)
{
    struct impexfilt *fp;

    fp = filt;
    if (fp == NULL)
      return (NULL);
    if ((!strcmp(fp->ext, ext)) && (fp->type == type))
      return (fp->plugin);
    while (fp->next != NULL) {
      fp = fp->next;
      if ((!strcmp(fp->ext, ext)) && (fp->type == type))
          return (fp->plugin);
    }

    return (NULL);
}
#endif

void
write_fd(register FILE *f, int r0, int c0, int rn, int cn)
{
    register struct ent **pp;
    int 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");
    print_options(f);
    for (c = 0; c < COLFORMATS; c++)
      if (colformat[c])
          (void) fprintf (f, "format %d = \"%s\"\n", c, colformat[c]);
    for (c = c0; c <= cn; 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_ranges(f);
    write_franges(f);
    write_colors(f, 0);
    write_cranges(f);

    if (mdir) 
      (void) fprintf(f, "mdir \"%s\"\n", mdir);
    if (autorun) 
      (void) fprintf(f, "autorun \"%s\"\n", autorun);
    for (c = 0; c < FKEYS; c++)
      if (fkey[c])
          (void) fprintf(f, "fkey %d = \"%s\"\n", c + 1, fkey[c]);

    write_cells(f, r0, c0, rn, cn, r0, c0);
    for (r = r0; r <= rn; r++) {
      pp = ATBL(tbl, r, c0);
      for (c = c0; c <= cn; c++, pp++)
          if (*pp) {
            if ((*pp)->flags&is_locked)
                (void) fprintf(f, "lock %s%d\n", coltoa((*pp)->col),
                                         (*pp)->row);
            if ((*pp)->nrow >= 0) {
                (void) fprintf(f, "addnote %s ",
                      v_name((*pp)->row, (*pp)->col));
                (void) fprintf(f, "%s\n", r_name((*pp)->nrow, (*pp)->ncol,
                      (*pp)->nlastrow, (*pp)->nlastcol));
            }
          }
    }
    /*
     * Don't try to combine these into a single fprintf().  v_name() has
     * a single buffer that is overwritten on each call, so the first part
     * needs to be written to the file before making the second call.
     */
    fprintf(f, "goto %s", v_name(currow, curcol));
    fprintf(f, " %s\n", v_name(strow, stcol));
}

void
write_cells(register FILE *f, int r0, int c0, int rn, int cn, int dr, int dc)
{
    register struct ent **pp;
    int r, c, rs, cs, mf;
    char *dpointptr;

    mf = modflg;
    if (dr != r0 || dc != c0) {
      yank_area(r0, c0, rn, cn);
      rn += dr - r0;
      cn += dc - c0;
      rs = currow;
      cs = curcol;
      currow = dr;
      curcol = dc;
      pullcells('x');
    }
    if (Vopt) valueize_area(dr, dc, rn, cn);
    for (r = dr; r <= rn; r++) {
      pp = ATBL(tbl, r, dc);
      for (c = dc; 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);
                dpointptr = strchr(line, dpoint);
                if (dpointptr != NULL)
                  *dpointptr = '.';
                (void) fprintf(f, "%s\n", line);
            }
            if ((*pp)->format) {
                editfmt(r, c);
                (void) fprintf(f, "%s\n",line);
            }
          }
    }
    if (dr != r0 || dc != c0) {
      pullcells('x');
      currow = rs;
      curcol = cs;
      flush_saved();
    }
    modflg = mf;
}

int
writefile(char *fname, int r0, int c0, int rn, int cn)
{
    register FILE *f;
    char save[PATHLEN];
    char tfname[PATHLEN];
    long namelen;
    char *tpp;
    char *p;
    char *plugin;
    int pid;

#ifndef MSDOS
    /* find the extension and mapped plugin if exists */
    if ((p = strrchr(fname, '.'))) {
      if ((plugin = findplugin(p+1, 'w')) != NULL) {
          if (!plugin_exists(plugin, strlen(plugin), save + 1)) {
            error("plugin not found");
            return;
          }
          *save = '|';
          if ((strlen(save) + strlen(fname) + 20) > PATHLEN) {
            error("Path too long");
            return;
          }
          sprintf(save + strlen(save), " %s%d:", coltoa(c0), r0);
          sprintf(save + strlen(save), "%s%d \"%s\"", coltoa(cn), rn, fname);
          /* pass it to readfile as an advanced macro */
          readfile(save, 0);
          return (0);
      }
    }
#endif

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

    if (*fname == '\0')
      if (isatty(STDOUT_FILENO) || *curfile != '\0')
          fname = curfile;
      else {
          write_fd(stdout, r0, c0, rn, cn);
          return (0);
      }

#ifdef MSDOS
    namelen = 12;
#else
    if ((tpp = strrchr(fname, '/')) == NULL)
      namelen = pathconf(".", _PC_NAME_MAX);
    else {
      *tpp = '\0';
      namelen = pathconf(fname, _PC_NAME_MAX);
      *tpp = '/';
    }
#endif /* MSDOS */

    (void) strcpy(tfname, fname);
    for (tpp = tfname; *tpp != '\0'; tpp++)
      if (*tpp == '\\' && *(tpp + 1) == '"')
          (void) memmove(tpp, tpp + 1, strlen(tpp));

    if (scext != NULL) {
      if (strlen(tfname) > 3 && !strcmp(tfname + strlen(tfname) - 3, ".sc"))
          tfname[strlen(tfname) - 3] = '\0';
      else if (strlen(tfname) > strlen(scext) + 1 &&
            tfname[strlen(tfname) - strlen(scext) - 1] == '.' &&
            !strcmp(tfname + strlen(tfname) - strlen(scext), scext))
          tfname[strlen(tfname) - strlen(scext) - 1] = '\0';
      tfname[namelen - strlen(scext) - 1] = '\0';
      strcat(tfname, ".");
      strcat(tfname, scext);
    }

    (void) strcpy(save, tfname);
    for (tpp = save; *tpp != '\0'; tpp++)
      if (*tpp == '"') {
          (void) memmove(tpp + 1, tpp, strlen(tpp) + 1);
          *tpp++ = '\\';
      }

    if ((f = openfile(tfname, &pid, NULL)) == NULL) {
      error("Can't create file \"%s\"", save);
      return (-1);
    }

    if (usecurses) {
      error("Writing file \"%s\"...", save);
      refresh();
    }
    write_fd(f, r0, c0, rn, cn);
    
    closefile(f, pid, 0);

    if (!pid) {
        (void) strcpy(curfile, save);
        modflg = 0;
      FullUpdate++;
      if (usecurses)
          error("File \"%s\" written", curfile);
      else
          fprintf(stderr, "\nFile \"%s\" written", curfile);
    }

    return (0);
}

int
readfile(char *fname, int eraseflg)
{
    register FILE *f;
    char save[PATHLEN];
    int tempautolabel;
    char *p;
    char *plugin;
    int pid = 0;
    int rfd = STDOUT_FILENO, savefd;

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

    if (*fname == '*' && mdir) { 
       (void) strcpy(save, mdir);
       (void) strcat(save, fname);
    } else {
        if (*fname == '\0')
          fname = curfile;
      (void) strcpy(save, fname);
    }

#ifndef MSDOS
    if ((p = strrchr(fname, '.')) && (fname[0] != '|')) {  /* exclude macros */
      if ((plugin = findplugin(p+1, 'r')) != NULL) {
          if (!(plugin_exists(plugin, strlen(plugin), save + 1))) {
            error("plugin not found");
            return;
          }
          *save = '|';
          if ((strlen(save) + strlen(fname) + 2) > PATHLEN) {
            error("Path too long");
            return;
          }
          sprintf(save + strlen(save), " \"%s\"", fname);
          eraseflg = 0;
          /* get filename: could be preceded by params if this is
          * a save
          */
          while (p > fname) {
            if (*p == ' ') {
                p++;
                break;
            }
            p--;
          }
          (void) strcpy(curfile, p);
      }
    }
#endif

#if !defined(VMS) && !defined(MSDOS) && defined(CRYPT_PATH)
    if (Crypt) {
      int ret = 0;
      if (*save == '-' && strlen(fname) == 1)
          error("Can't use encryption in a pipeline.");
      else
          if (*save == '|')
            error("Can't use encryption with advanced macros.");
      else
          ret = creadfile(save, eraseflg);
      autolabel = tempautolabel;
      return (ret);
    }
#endif /* VMS */

    if (eraseflg && strcmp(fname, curfile) && modcheck(" first")) return 0;

#ifndef MSDOS
    if (fname[0] == '-' && fname[1] == '\0') {
      f = stdin;
      *save = '\0';
    } else {
#endif /* MSDOS */
      if ((f = openfile(save, &pid, &rfd)) == NULL) {
          error("Can't read file \"%s\"", save);
          autolabel = tempautolabel;
          return 0;
      } else if (eraseflg) {
          if (usecurses) {
            error("Reading file \"%s\"", save);
            refresh();
          } else
            fprintf(stderr, "\nReading file \"%s\"", save);
      }
#ifndef MSDOS
    }
    if (*fname == '|')
      *save = '\0';
#endif /* MSDOS */

    if (eraseflg) erasedb();

    remember(0);
    loading++;
    savefd = macrofd;
    macrofd = rfd;
    while (!brokenpipe && fgets(line, sizeof(line), f)) {
#ifndef MSDOS
      if (line[0] == '|' && pid != 0) {
          line[0] = ' ';
      }
#endif /* MSDOS */
      linelim = 0;
      if (line[0] != '#') (void) yyparse();
    }
    macrofd = savefd;
    --loading;
    remember(1);
    closefile(f, pid, rfd);
#ifndef MSDOS
    if (f == stdin) {
      freopen("/dev/tty", "r", stdin);
      goraw();
    }
#endif /* MSDOS */
    linelim = -1;
    if (eraseflg) {
      (void) strcpy(curfile, save);
      modflg = 0;
      cellassign = 0;
      if (autorun && !skipautorun) (void) readfile(autorun, 0);
      skipautorun = 0;
      EvalAll();
    }
    autolabel = tempautolabel;
    FullUpdate++;
    return 1;
}

/* erase the database (tbl, etc.) */
void
erasedb()
{
    int  r, c;
    char *home;

    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;
          }
    }

    for (c = 0; c < COLFORMATS; c++)
      if (colformat[c]) {
          scxfree(colformat[c]);
          colformat[c] = NULL;
      }

    maxrow = 0;
    maxcol = 0;
    clean_range();
    clean_frange();
    clean_crange();

    propagation = 10;
    calc_order = BYROWS;
    prescale = 1.0;
    tbl_style = 0;
    craction = 0;
    rowlimit = collimit = -1;
    qbuf = 0;

    autocalc=showcell=showtop=1;
    autoinsert=autowrap=optimize=numeric=extfunc=color=colorneg=colorerr=cslop=0;
    currow=curcol=strow=stcol=0;
    if (usecurses && has_colors())
      color_set(0, NULL);
    /* unset all marks */
    for (c = 1; c < 37; c++)
      savedrow[c] = savedcol[c] = savedstrow[c] = savedstcol[c] = -1;

    if (mdir) {
      scxfree(mdir);
      mdir = NULL;
    }
    if (autorun) {
      scxfree(autorun);
      autorun = NULL;
    }
    for (c = 0; c < FKEYS; c++)
      if (fkey[c]) {
          scxfree(fkey[c]);
          fkey[c] = NULL;
      }

#ifndef MSDOS
    /*
     * Load $HOME/.scrc if present.
     */
    if ((home = getenv("HOME"))) {
      strcpy(curfile, home);
      strcat(curfile, "/.scrc");
      if ((c = open(curfile, O_RDONLY)) > -1) {
          close(c);
          (void) readfile(curfile, 0);
      }
    }

    /*
     * Load ./.scrc if present and $HOME/.scrc contained `set scrc'.
     */
    if (scrc && strcmp(home, getcwd(curfile, PATHLEN)) &&
          (c = open(".scrc", O_RDONLY)) > -1) {
      close(c);
      (void) readfile(".scrc", 0);
    }
#endif /* MSDOS */

    *curfile = '\0';
    FullUpdate++;
}

/* moves curcol back one displayed column */
void
backcol(int arg)
{
    while (--arg >= 0) {
      if (curcol)
          curcol--;
      else
          {error ("At column A"); break;}
      while(col_hidden[curcol] && curcol)
          curcol--;
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
}

/* moves curcol forward one displayed column */
void
forwcol(int arg)
{
    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++;
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
}

/* moves currow forward one displayed row */
void
forwrow(int arg)
{
    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++;
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
}

/* moves currow backward one displayed row */
void
backrow(int arg)
{
    while (--arg>=0) {
      if (currow)
          currow--;
      else
          {error("At row zero"); break;}
      while (row_hidden[currow] && currow)
          currow--;
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
}

void
markcell()
{
    int c;

    error("Mark cell:");
    if ((c=nmgetch()) == ESC || c == ctl('g')) {
      error("");
      return;
    }
    if ((c -= ('a' - 1)) < 1 || c > 26) {
      error("Invalid mark (must be a-z)");
      return;
    }
    error("");
    savedrow[c] = currow;
    savedcol[c] = curcol;
    savedstrow[c] = strow;
    savedstcol[c] = stcol;
}

void
dotick(int tick)
{
    int c;

    remember(0);

    error("Go to marked cell:");
    if ((c = nmgetch()) == ESC || c == ctl('g')) {
      error("");
      return;
    }
    if (c == '`' || c == '\'')
      c = 0;
    else if (!(((c -= ('a' - 1)) > 0 && c < 27) ||
          ((c += ('a' - '0' + 26)) > 26 && c < 37))) {
      error("Invalid mark (must be a-z, 0-9, ` or \')");
      return;
    }
    if (savedrow[c] == -1) {
      error("Mark not set");
      return;
    }
    error("");
    currow = savedrow[c];
    curcol = savedcol[c];
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
    if (tick == '\'') {
      strow = savedstrow[c];
      stcol = savedstcol[c];
      gs.stflag = 1;
    } else
      gs.stflag = 0;
    remember(1);

    FullUpdate++;
}

void
gotonote()
{
    register struct ent *p;

    p = lookat(currow, curcol);
    if (p->nrow == -1)
      error("No note attached");
    else
      moveto(p->nrow, p->ncol, p->nlastrow, p->nlastcol, -1, -1);
}

/*
 * 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(char *string,      /* to display */
    int dirflush,       /* or rightflush or centered */
    int hasvalue,       /* is there a numeric value? */
    int row, int col,         /* spreadsheet location */
    int *nextcolp,            /* value returned through it */
    int mxcol,                /* last column displayed? */
    int *fieldlenp,           /* value returned through it */
    int r, int c,       /* screen row and column */
    struct frange *fr,        /* frame range we're currently in, if any */
    int frightcols,           /* number of frame columns to the right */
    int flcols, int frcols)   /* width of left and right sides of frame */
{
    int nextcol  = *nextcolp;
    int fieldlen = *fieldlenp;
    char field[FBUFLEN];
    int  slen;
    char *start, *last;
    char *fp, *sp;
    struct ent *nc;
    struct crange *cr;

    cr = find_crange(row, col);

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

    slen = strlen(string);
    for (sp = string; *sp != '\0'; sp++)
      if (*sp == '\\' && *(sp + 1) == '"')
          slen--;
    if (*string == '\\' && *(string+1) != '\0' && *(string+1) != '"')
      slen = fwidth[col];
    if (c + fieldlen == rescol + flcols && nextcol < stcol)
      nextcol = stcol;
    if (frightcols &&
          c + fieldlen + fwidth[nextcol] >= COLS - 1 - frcols &&
          nextcol < fr->or_right->col - frightcols + 1)
      nextcol = fr->or_right->col - frightcols + 1;
    while ((slen > fieldlen) && (nextcol <= mxcol) &&
          !((nc = lookat(row, nextcol))->flags & is_valid) && !(nc->label) &&
          (cslop || find_crange(row, nextcol) == cr)) {

      if (!col_hidden[nextcol])
          fieldlen += fwidth[nextcol];
      nextcol++;
      if (c + fieldlen == rescol + flcols && nextcol < stcol)
          nextcol = stcol;
      if (frightcols &&
            c + fieldlen + fwidth[nextcol] >= COLS - 1 - frcols &&
            nextcol < fr->or_right->col - frightcols + 1)
          nextcol = fr->or_right->col - frightcols + 1;
    }
    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;
    if (slen)
      while (fp < start)
          *fp++ = ' ';
    if (*string == '\\' && *(string+1) != '\0' && *(string+1) != '"') {
      char *strt;
      strt = ++string;

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

    if ((!hasvalue) || fieldlen != fwidth[col]) 
      while (fp < last)
          *fp++ = ' ';
    *fp = '\0';
    for (fp = field; *fp != '\0'; fp++)
      if (*fp == '\\' && *(fp + 1) == '"')
          memmove(fp, fp + 1, strlen(fp));
#ifdef VMS
    mvaddstr(r, c, field);    /* this is a macro */
#else
    (void) mvaddstr(r, c, field);
#endif

    *nextcolp  = nextcol;
    *fieldlenp = fieldlen;
}

int
etype(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(char *msg)
{     char ch;

      (void) move(0, 0);
      (void) clrtoeol();
      (void) addstr(msg);
      (void) refresh();
      while ((ch = nmgetch()) != 'y' && ch != 'Y' && ch != 'n' && ch != 'N') {
          if (ch == ctl('g') || ch == ESC)
            return (-1);
      }
      if (ch == 'y' || ch == 'Y')
          return (1);
      else
          return (0);
}

/* expand a ~ in a path to your home directory */
#if !defined(MSDOS) && !defined(VMS)
#include <pwd.h>
#endif
char *
findhome(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;
          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>

/*
 * 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(char *path)
{
    struct  stat  statbuf;
    struct  utimbuf     timebuf;
    char    fname[PATHLEN];
    char    tpath[PATHLEN];
#ifdef sequent
    static  char  *buf = NULL;
    static  unsigned buflen = 0;
#else
    char    buf[BUFSIZ];
#endif
    char    *tpp;
    int           infd, outfd;
    int           count;
    mode_t  oldumask;

    /* 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) {
#ifdef sequent
      /* if we know the optimum block size, use it */
      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);

      oldumask = umask(0);
      outfd = open(tpath, O_TRUNC|O_WRONLY|O_CREAT, statbuf.st_mode);
      umask(oldumask);
      if (outfd < 0)
          return (0);
      chown(tpath, statbuf.st_uid, statbuf.st_gid);

#ifdef sequent
      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);

      /* copy access and modification times from original file */
      timebuf.actime = statbuf.st_atime;
      timebuf.modtime = statbuf.st_mtime;
      utime(tpath, &timebuf);

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

Generated by  Doxygen 1.6.0   Back to index