/* SC A Spreadsheet Calculator
* original by James Gosling, September 1982
* modifications by Mark Weiser and Bruce Israel,
* More mods Robert Bond, 12/86
* More mods by Alan Silverstein, 3-4/88, see list of changes.
* Currently supported by pur-phy!sawmill!buhrt (Jeff Buhrt)
#define DFLT_PAGER "more" /* more is probably more widespread than less */
#define MAXCMD 160 /* for ! command below */
/* Globals defined in sc.h */
int showsc
, showsr
; /* Starting cell for highlighted range */
int showtop
= 1; /* Causes current cell value display in top line */
int showcell
= 1; /* Causes current cell to be highlighted */
int showrange
= 0; /* Causes ranges to be highlighted */
int showneed
= 0; /* Causes cells needing values to be highlighted */
int showexpr
= 0; /* Causes cell exprs to be displayed, highlighted */
int autocalc
= 1 ; /* 1 to calculate after each update */
int tbl_style
= 0; /* headers for T command output */
int lastmx
, lastmy
; /* Screen address of the cursor */
int lastcol
; /* Spreadsheet Column the cursor was in last */
char under_cursor
[] = " "; /* Data under the < cursor */
(void) printw("%s: %.*s<=%s",err
,linelim
,line
,line
+linelim
);
register struct ent
**pp
;
pp
= ATBL(tbl
, row
, col
);
if (*pp
== (struct ent
*)0) {
*pp
= (struct ent
*) xmalloc((unsigned)sizeof(struct ent
));
if (row
>maxrow
) maxrow
= row
;
if (col
>maxcol
) maxcol
= col
;
(*pp
)->label
= (char *)0;
(*pp
)->expr
= (struct enode
*)0;
(*pp
)->evnext
= (struct ent
*)0;
* This structure is used to keep ent structs around before they
* are deleted to allow the sync_refs routine a chance to fix the
* We also use it as a last-deleted buffer for the 'p' command.
* standout last time in update()?
* At this point we will let curses do work
int anychanged
; /* did any cell really change in value? */
register struct ent
**pp
;
int minsr
, minsc
, maxsr
, maxsc
;
while (row_hidden
[currow
]) /* You can't hide the last row or col */
while (col_hidden
[curcol
])
/* First see if the last display still covers curcol */
for (i
= stcol
, cols
= 0, col
= RESCOL
;
(col
+ fwidth
[i
]) < COLS
-1 && i
< maxcols
; i
++) {
while (stcol
+ cols
- 1 < curcol
|| curcol
< stcol
) {
if (stcol
- 1 == curcol
) { /* How about back one? */
} else if (stcol
+ cols
== curcol
) { /* Forward one? */
/* Try to put the cursor in the center of the screen */
col
= (COLS
- RESCOL
- fwidth
[curcol
]) / 2 + RESCOL
;
for (i
=curcol
-1; i
>= 0 && col
-fwidth
[i
] > RESCOL
; i
--) {
/* Now pick up the counts again */
for (i
= stcol
, cols
= 0, col
= RESCOL
;
(col
+ fwidth
[i
]) < COLS
-1 && i
< maxcols
; i
++) {
/* Now - same process on the rows */
for (i
= strow
, rows
= 0, row
=RESROW
; row
<LINES
&& i
<maxrows
; i
++) {
while (strow
+ rows
- 1 < currow
|| currow
< strow
) {
if (strow
- 1 == currow
) { /* How about up one? */
} else if (strow
+ rows
== currow
) { /* Down one? */
/* Try to put the cursor in the center of the screen */
row
= (LINES
- RESROW
) / 2 + RESROW
;
for (i
=currow
-1; i
>= 0 && row
-1 > RESROW
; i
--) {
/* Now pick up the counts again */
for (i
= strow
, rows
= 0, row
=RESROW
; row
<LINES
&& i
<maxrows
; i
++) {
mxcol
= stcol
+ cols
- 1;
mxrow
= strow
+ rows
- 1;
if (FullUpdate
|| standlast
) {
for (row
=RESROW
, i
=strow
; i
<= mxrow
; i
++) {
(void) printw("%-*d", RESCOL
-1, i
);
(void) printw("%-*d", RESCOL
, i
);
(void) printw("%*s", RESCOL
, " ");
for (col
=RESCOL
, i
= stcol
; i
<= mxcol
; i
++) {
(void) printw("%1s", coltoa(i
));
(void) printw("%*s%-*s", k
, " ", fwidth
[i
]-k
, coltoa(i
));
/* Get rid of cursor standout on the cell at previous cursor position */
{ (void) move(lastmx
, lastmy
);
repaint(lastmx
, lastmy
, fwidth
[lastcol
]);
minsr
= showsr
< currow
? showsr
: currow
;
minsc
= showsc
< curcol
? showsc
: curcol
;
maxsr
= showsr
> currow
? showsr
: currow
;
maxsc
= showsc
> curcol
? showsc
: curcol
;
(void) printw("Default range: %s",
r_name(minsr
, minsc
, maxsr
, maxsc
));
/* Repaint the visible screen */
if (showrange
|| anychanged
|| FullUpdate
|| standlast
)
/* may be reset in loop, if not next time we will do a FullUpdate */
for (row
= strow
, r
= RESROW
; row
<= mxrow
; row
++) {
for (pp
= ATBL(tbl
, row
, col
= stcol
); col
<= mxcol
;
pp
+= nextcol
- col
, col
= nextcol
, c
+= fieldlen
) {
* - showing ranges, and not showing cells which need to be filled
* in, and not showing cell expressions, and in a range, OR
* - if showing cells which need to be filled in and this one is
* of that type (has a value and doesn't have an expression,
* or it is a string expression), OR
* - if showing cells which have expressions and this one does.
if ((showrange
&& (! showneed
) && (! showexpr
)
&& (row
>= minsr
) && (row
<= maxsr
)
&& (col
>= minsc
) && (col
<= maxsc
))
|| (showneed
&& (*pp
) && ((*pp
) -> flags
& is_valid
)
&& (((*pp
) -> flags
& is_strexpr
) || !((*pp
) -> expr
)))
|| (showexpr
&& (*pp
) && ((*pp
) -> expr
)))
if (!*pp
) /* no cell, but standing out */
{ (void) printw("%*s", fwidth
[col
], " ");
if ((*pp
) && ((*pp
) -> flags
& is_changed
|| FullUpdate
) || do_stand
) {
(*pp
) -> flags
|= is_changed
;
(*pp
) -> flags
&= ~is_changed
;
* Show expression; takes priority over other displays:
if (showexpr
&& ((*pp
) -> expr
)) {
editexp(row
, col
); /* set line to expr */
showstring(line
, /* leftflush = */ 1, /* hasvalue = */ 0,
row
, col
, & nextcol
, mxcol
, & fieldlen
, r
, c
);
* Show cell's numeric value:
if ((*pp
) -> flags
& is_valid
) {
(void)sprintf(field
,"%*.*f", fwidth
[col
], precision
[col
], (*pp
)->v
);
if(strlen(field
) > fwidth
[col
]) {
for(i
= 0; i
<fwidth
[col
]; i
++)
* Show cell's label string:
showstring((*pp
) -> label
,
(*pp
) -> flags
& is_leftflush
,
(*pp
) -> flags
& is_valid
,
row
, col
, & nextcol
, mxcol
,
else /* repaint a blank cell: */
if ((do_stand
|| !FullUpdate
) &&
((*pp
)->flags
& is_changed
) &&
!((*pp
)->flags
& is_valid
) && !(*pp
)->label
) {
(void) printw("%*s", fwidth
[col
], " ");
(void) move(lastmy
, lastmx
+fwidth
[lastcol
]);
if((inch() & A_CHARTEXT
) == '<')
(void) addstr(under_cursor
);
for (row
= strow
; row
< currow
; row
++)
for (col
= stcol
; col
< curcol
; col
++)
if (showcell
&& (! showneed
) && (! showexpr
)) {
(void) move(lastmy
, lastmx
);
repaint(lastmx
, lastmy
, fwidth
[lastcol
]);
(void) move(lastmy
, lastmx
+fwidth
[lastcol
]);
*under_cursor
= (inch() & A_CHARTEXT
);
(void) move(0, linelim
+3);
if (showtop
) { /* show top line */
int printed
= 0; /* printed something? */
(void) printw("%s%d ", coltoa(curcol
), currow
);
if (p1
= *ATBL(tbl
, currow
, curcol
)) {
/* has expr of some type */
editexp(currow
, curcol
); /* set line to expr */
* Display string part of cell:
if ((p1
-> expr
) && (p1
-> flags
& is_strexpr
)) {
(void) addstr((p1
-> flags
& is_leftflush
) ? "<{" : ">{");
(void) addstr("} "); /* and this '}' is for vi % */
} else if (p1
-> label
) {
/* has constant label only */
(void) addstr ((p1
-> flags
& is_leftflush
) ? "<\"" : ">\"");
(void) addstr (p1
-> label
);
* Display value part of cell:
if (p1
-> flags
& is_valid
) {
/* has value or num expr */
if ((! (p1
-> expr
)) || (p1
-> flags
& is_strexpr
))
(void) sprintf (line
, "%.15g", p1
-> v
);
(void) move (lastmy
, lastmx
+ fwidth
[lastcol
]);
(void) clrtoeol (); /* get rid of topline display */
revmsg
[0] = '\0'; /* don't show it again */
(void) move (lastmy
, lastmx
+ fwidth
[lastcol
]);
* Keep command line options around until the file is read so the
* command line overrides file options
int tempx
, tempy
; /* Temp versions of curx, cury */
if ((revi
= strrchr(argv
[0], '/')) != NULL
)
while (argc
> 1 && argv
[1][0] == '-') {
(void) fprintf(stderr
, "Crypt not available for VMS\n");
(void) fprintf(stderr
,"%s: unrecognized option: \"%c\"\n",
/* setup the spreadsheet arrays, initscr() will get the screen size */
if (!growtbl(GROWNEW
, 0, 0))
* Build revision message for later use:
(void) strcpy (revmsg
, progname
);
for (revi
= rev
; (*revi
++) != ':'; ); /* copy after colon */
(void) strcat (revmsg
, revi
);
revmsg
[strlen (revmsg
) - 2] = 0; /* erase last character */
(void) strcat (revmsg
, ": Type '?' for help.");
(void) strcpy(curfile
,argv
[1]);
while (inloop
) { running
= 1;
if (edistate
< 0 && linelim
< 0 && autocalc
&& (changed
|| FullUpdate
))
if (changed
) /* if EvalAll changed or was before */
else /* any cells change? */
(void) refresh(); /* 5.3 does a refresh in getch */
getyx(stdscr
, tempy
, tempx
);
(void) move(tempy
, tempx
);
showneed
= 0; /* reset after each update */
/* if ((c < ' ') || ( c == DEL )) how about international here ? PB */
(void) kill(0, SIGTSTP
); /* Nail process group */
(void) clearok(stdscr
,1);
(void) clearok(stdscr
,1);
error ("No such command (^%c)", c
+ 0100);
case ctl('p'): case 'k': doend (-1, 0); break;
case ctl('n'): case 'j': doend ( 1, 0); break;
case ctl('h'): doend ( 0,-1); break;
case ctl('i'): case ' ': doend ( 0, 1); break;
error("Invalid ^E command");
if (linelim
< 0) { /* not editing line */
backcol(arg
); /* treat like ^B */
if (linelim
< 0) { /* not editing line */
break; /* ignore flow control */
break; /* ignore flow control */
"Toggle: a:auto c:cell e:ext funcs n:numeric t:top x:encrypt $:pre-scale");
error("Automatic recalculation %sabled.",
error ("Numeric input %sabled.",
repaint(lastmx
, lastmy
, fwidth
[lastcol
]);
error ("Top line %sabled.", showtop
? "en" : "dis");
repaint(lastmx
, lastmy
, fwidth
[lastcol
]);
error ("Cell highlighting %sabled.",
showcell
? "en" : "dis");
error ("Encryption %sabled.", Crypt
? "en" : "dis");
error ("Prescale enabled.");
error ("Prescale disabled.");
error ("External functions %sabled.",
--modflg
; /* negate the modflg++ */
error ("Invalid toggle command");
--modflg
; /* negate the modflg++ */
case ctl('v'): /* insert variable name */
ins_string(v_name(currow
, curcol
));
case ctl('w'): /* insert variable expression */
temp
= strcpy(xmalloc((unsigned)(strlen(line
)+1)),line
);
temp1
= strcpy(xmalloc((unsigned)(strlen(line
)+1)),line
);
case ctl('a'): /* insert variable value */
struct ent
*p
= *ATBL(tbl
, currow
, curcol
);
if (p
&& p
-> flags
& is_valid
) {
(void) sprintf (temp
, "%.*f",
precision
[curcol
],p
-> v
);
} /* End of the control char switch stmt */
else if (isdigit(c
) && ((numeric
&& edistate
>= 0) ||
(!numeric
&& (linelim
< 0 || edistate
>= 0)))) {
/* we got a leading number */
/* First char of the count */
if (c
== '0') /* just a '0' goes to left col */
/* Succeeding count chars */
narg
= arg
* 10 + (c
- '0');
} else if (linelim
>= 0) {
} else if (!numeric
&& ( c
== '+' || c
== '-' ) ) {
/* increment/decrement ops */
register struct ent
*p
= *ATBL(tbl
, currow
, curcol
);
if (p
->expr
&& !(p
->flags
& is_strexpr
)) {
error("Can't increment/decrement a formula\n");
/* switch on a normal command character */
break; /* Be nice to vi users */
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '-': case '.': case '+':
(void) sprintf(line
,"let %s = %c",
v_name(currow
, curcol
), c
);
(void) sprintf(line
,"let %s = ",
* "! command" executes command
* "!!" repeats last command
error("Not implemented on VMS");
static char lastcmd
[MAXCMD
];
if (!(shl
= getenv("SHELL")))
(void) fputs("! ", stdout
);
(void) fgets(cmd
, MAXCMD
, stdin
);
cmd
[strlen(cmd
) - 1] = '\0'; /* clobber \n */
if(strcmp(cmd
,"!") == 0) /* repeat? */
(void) strcpy(cmd
, lastcmd
);
(void) strcpy(lastcmd
, cmd
);
(void) puts ("[No write since last change]");
(void) signal (SIGINT
, SIG_DFL
); /* reset */
(void)execl(shl
,shl
,"-c",cmd
,(char *)0);
(void) execl(shl
, shl
, (char *)0);
while (pid
!= wait(&temp
));
(void) printf("Press RETURN to continue ");
"Range: x:erase v:value c:copy f:fill d:define s:show u:undefine");
(void) sprintf(line
,"copy [dest_range src_range] ");
(void) sprintf(line
,"erase [range] ");
(void) sprintf(line
, "value [range] ");
(void) sprintf(line
,"fill [range start inc] ");
(void) sprintf(line
,"define [string range] \"");
(void) sprintf(line
,"undefine [range] ");
(void) strcpy(px
, "| sort | ");
if(!(pager
= getenv("PAGER")))
error("Can't open pipe to sort");
else error("No ranges defined");
error("Invalid region command");
if (! (rcqual
= get_rcqual (c
))) {
error ("Invalid row/column command");
error (""); /* clear line */
if ( rcqual
== ESC
|| rcqual
== ctl('g'))
if (rcqual
== 'r') insertrow(arg
);
else opencol(curcol
, arg
);
if (rcqual
== 'r') while (arg
--) duprow();
else while (arg
--) dupcol();
if (rcqual
== 'r') deleterow(arg
);
else closecol(curcol
, arg
);
while (arg
--) pullcells(rcqual
);
if (rcqual
== 'r') rowvalueize(arg
);
if (rcqual
== 'r') hiderow(arg
);
/* special case; no repeat count */
if (rcqual
== 'r') rowshow_op();
while (!VALID_CELL(p
, currow
, curcol
) && curcol
> 0)
while (!VALID_CELL(p
, currow
, curcol
) && currow
> 0)
if (curcol
< maxcols
- 1)
if (currow
< maxrows
- 1) {
while(++currow
< maxrows
- 1 &&
row_hidden
[currow
]) /* */;
error("At end of table");
} while(col_hidden
[curcol
] ||
!VALID_CELL(p
, currow
, curcol
));
row_hidden
[currow
]) /* */;
error ("At start of table");
} while(col_hidden
[curcol
] ||
!VALID_CELL(p
, currow
, curcol
));
(void) sprintf (line
, "label %s = \"",
(void) sprintf (line
, "leftstring %s = \"",
(void) sprintf (line
, "rightstring %s = \"",
(void) sprintf (line
, "format [for column] %s ",
(void) sprintf(line
, "format [for columns] %s:",
(void) sprintf(line
+strlen(line
), "%s ",
error("Current format is %d %d",
fwidth
[curcol
],precision
[curcol
]);
(void) sprintf (line
, "goto [v] ");
(void) sprintf (line
, "put [\"dest\" range] \"");
error ("Default path is \"%s\"",curfile
);
(void) sprintf (line
, "merge [\"source\"] \"");
(void) sprintf (line
,"merge [\"macro_file\"] \"%s/", mdir
);
(void) sprintf (line
,"merge [\"macro_file\"] \"");
(void) sprintf (line
, "mdir [\"macro_directory\"] \"");
(void) sprintf (line
, "get [\"source\"] \"");
error ("Default file is \"%s\"",curfile
);
(void) sprintf (line
, "write [\"dest\" range] \"");
case 'S': /* set options */
(void) sprintf (line
, "set ");
error("Options: byrows, bycols, iterations=n, tblstyle=(0|tbl|latex|tex)");
case 'T': /* tbl output */
(void) sprintf (line
, "tbl [\"dest\" range] \"");
register struct ent
**pp
;
if(calc_order
== BYROWS
) {
for (c1
= curcol
; arg
-- && c1
< maxcols
; c1
++) {
pp
= ATBL(tbl
, currow
, c1
);
for (c1
= currow
; arg
-- && c1
< maxrows
; c1
++) {
pp
= ATBL(tbl
, c1
, curcol
);
register struct ent
*p
= *ATBL(tbl
, savedrow
, savedcol
);
for (c1
= curcol
; arg
-- && c1
< maxcols
; c1
++) {
copyent( n
, p
, currow
- savedrow
, c1
- savedcol
);
error ("Weird character, decimal %d\n",
error ("No such command (%c)", c
);
inloop
= modcheck(" before exiting");
#ifdef VMS /* Unit VMS "fixes" exit we should say 1 here */
int minsr
, minsc
, maxsr
, maxsc
;
minsr
= showsr
< currow
? showsr
: currow
;
minsc
= showsc
< curcol
? showsc
: curcol
;
maxsr
= showsr
> currow
? showsr
: currow
;
maxsc
= showsc
> curcol
? showsc
: curcol
;
(void) sprintf (line
+linelim
,"%s", r_name(minsr
, minsc
, maxsr
, maxsc
));
if((i
== BYROWS
)||(i
== BYCOLS
))
error("Not yet implemented");
(void) move (LINES
- 1, 0);
#else /* SYSV2 || SYSV3 */
#endif /* SYSV2 || SYSV3 */
(void) move (LINES
- 1, 0);
(void) signal(SIGINT
, SIG_IGN
);
(void) signal(SIGQUIT
, dump_me
);
(void) signal(SIGPIPE
, quit
);
(void) signal(SIGTERM
, quit
);
(void) signal(SIGALRM
, time_out
);
(void) signal(SIGFPE
, quit
);
(void) signal(SIGBUS
, quit
);
/* try to save the current spreadsheet if we can */
if (modcheck(" before Spreadsheet dies") == 1)
{ sprintf(path
, "~/SC.SAVE");
if (writefile(path
, 0, 0, maxrow
, maxcol
) < 0)
if (writefile("/tmp/SC.SAVE", 0, 0, maxrow
, maxcol
) < 0)
error("Couldn't save current spreadsheet, Sorry");
if (modflg
&& curfile
[0]) {
(void) sprintf (lin
,"File \"%s\" is modified, save%s? ",curfile
,endstr
);
if ((yn_ans
= yn_ask(lin
)) < 0)
{ if (writefile(curfile
, 0, 0, maxrow
, maxcol
) < 0)
if ((yn_ans
= yn_ask("Do you want a chance to save the data? ")) < 0)