/* SC A Spreadsheet Calculator * Main driver * * original by James Gosling, September 1982 * modifications by Mark Weiser and Bruce Israel, * University of Maryland * * More mods Robert Bond, 12/86 * More mods by Alan Silverstein, 3-4/88, see list of changes. * $Revision: 7.16 $ * */ #include #include #include #include #include #ifdef BSD42 #include #else #ifndef SYSIII #include #endif #endif #if defined(BSD42) || defined(BSD43) || defined(VMS) #include #else #include #endif #ifndef MSDOS #include #endif #include #include "sc.h" #ifndef SAVENAME #define SAVENAME "SC.SAVE" /* file name to use for emergency saves */ #endif /* SAVENAME */ /* Globals defined in sc.h */ struct ent ***tbl; int arg = 1; int strow = 0, stcol = 0; int currow = 0, curcol = 0; int savedrow[37], savedcol[37]; int savedstrow[37], savedstcol[37]; int FullUpdate = 0; int maxrow, maxcol; int maxrows, maxcols; int *fwidth; int *precision; int *realfmt; char *col_hidden; char *row_hidden; char line[FBUFLEN]; int changed; struct ent *delbuf[DELBUFSIZE]; char *delbuffmt[DELBUFSIZE]; int dbidx; int qbuf; /* buffer no. specified by " command */ int modflg; int cellassign; int numeric; char *mdir; char *autorun; int skipautorun; char *fkey[FKEYS]; char *scext; char *ascext; char *tbl0ext; char *tblext; char *latexext; char *slatexext; char *texext; int scrc = 0; int showsc, showsr; /* Starting cell for highlighted range */ int usecurses = TRUE; /* Use curses unless piping/redirection or using -q */ int brokenpipe = FALSE; /* Set to true if SIGPIPE is received */ #ifdef RIGHT_CBUG int wasforw = FALSE; #endif void update(); void repaint(); extern void doshell(); extern void gohome(); extern void leftlimit(); extern void rightlimit(); extern void gototop(); extern void gotobottom(); char curfile[PATHLEN]; char revmsg[80]; /* numeric separators, country-dependent if locale support enabled: */ char dpoint = '.'; /* decimal point */ char thsep = ','; /* thousands separator */ int linelim = -1; 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 shownote = 0; /* Causes cells with attached notes to be highlighted */ int braille = 0; /* Be nice to users of braille displays */ int braillealt = 0; /* Alternate mode for braille users */ int autocalc = 1; /* 1 to calculate after each update */ int autolabel = 1; /* If room, causes label to be created after a define */ int autoinsert = 0; /* Causes rows to be inserted if craction is non-zero and the last cell in a row/column of the scrolling portion of a framed range has been filled */ int autowrap = 0; /* Causes cursor to move to next row/column if craction is non-zero and the last cell in a row/column of the scrolling portion of a framed range has been filled */ int calc_order = BYROWS; int optimize = 0; /* Causes numeric expressions to be optimized */ int tbl_style = 0; /* headers for T command output */ int rndtoeven = 0; int color = 0; /* Use color */ int colorneg = 0; /* Increment color number for cells with negative numbers */ int colorerr = 0; /* Color cells with errors with color 3 */ int numeric_field = 0; /* Started the line editing with a number */ int craction = 0; /* 1 for down, 2 for right */ int pagesize = 0; /* If nonzero, use instead of 1/2 screen height */ int rowlimit = -1; int collimit = -1; int rowsinrange = 1; int colsinrange = DEFWIDTH; extern int lastmx, lastmy; /* Screen address of the cursor */ extern int lastcol, lcols; /* Spreadsheet Column the cursor was in last */ extern int lastendrow; /* Last bottom row of screen */ extern int framerows; /* Rows in current frame */ extern int framecols; /* Columns in current frame */ extern char mode_ind; /* Mode indicator */ /* a linked list of free [struct ent]'s, uses .next as the pointer */ struct ent *freeents = NULL; extern int seenerr; extern char *rev; #ifdef VMS int VMS_read_raw = 0; #endif /* return a pointer to a cell's [struct ent *], creating if needed */ struct ent * lookat(int row, int col) { register struct ent **pp; checkbounds(&row, &col); pp = ATBL(tbl, row, col); if (*pp == NULL) { if (freeents != NULL) { *pp = freeents; (*pp)->flags &= ~is_cleared; (*pp)->flags |= may_sync; freeents = freeents->next; } else *pp = (struct ent *) scxmalloc((unsigned)sizeof(struct ent)); if (row > maxrow) maxrow = row; if (col > maxcol) maxcol = col; (*pp)->label = (char *)0; (*pp)->row = row; (*pp)->col = col; (*pp)->nrow = -1; (*pp)->ncol = -1; (*pp)->flags = may_sync; (*pp)->expr = (struct enode *)0; (*pp)->v = (double) 0.0; (*pp)->format = (char *)0; (*pp)->cellerror = CELLOK; (*pp)->next = NULL; } return (*pp); } /* * This structure is used to keep ent structs around before they * are deleted to allow the sync_refs routine a chance to fix the * variable references. * We also use it as a last-deleted buffer for the 'p' command. */ void free_ent(register struct ent *p, int unlock) { p->next = delbuf[dbidx]; delbuf[dbidx] = p; p->flags |= is_deleted; if (unlock) p->flags &= ~is_locked; } /* free deleted cells */ void flush_saved() { register struct ent *p; register struct ent *q; if (dbidx < 0) return; if (p = delbuf[dbidx]) { scxfree(delbuffmt[dbidx]); delbuffmt[dbidx] = NULL; } while (p) { (void) clearent(p); q = p->next; p->next = freeents; /* put this ent on the front of freeents */ freeents = p; p = q; } delbuf[dbidx--] = NULL; } char *progname; int Vopt; int main (int argc, char **argv) { int inloop = 1; register int c; int edistate = -1; int narg; int nedistate; int running; char *revi; char *home; int anychanged = FALSE; int tempx, tempy; /* Temp versions of curx, cury */ /* * Keep command line options around until the file is read so the * command line overrides file options */ int mopt = 0; int oopt = 0; int nopt = 0; int copt = 0; int ropt = 0; int Copt = 0; int Ropt = 0; int eopt = 0; int popt = 0; int qopt = 0; Vopt = 0; #ifdef MSDOS if ((revi = strrchr(argv[0], '\\')) != NULL) #else #ifdef VMS if ((revi = strrchr(argv[0], ']')) != NULL) #else if ((revi = strrchr(argv[0], '/')) != NULL) #endif #endif progname = revi+1; else progname = argv[0]; while ((c = getopt(argc, argv, "axmoncrCReP:W:vq")) != EOF) { switch (c) { case 'a': skipautorun = 1; break; case 'x': #if defined(VMS) || defined(MSDOS) || !defined(CRYPT_PATH) (void) fprintf(stderr, "Crypt not available\n"); exit (1); #else Crypt = 1; #endif break; case 'm': mopt = 1; break; case 'o': oopt = 1; break; case 'n': nopt = 1; break; case 'c': copt = 1; break; case 'r': ropt = 1; break; case 'C': Copt = 1; craction = CRCOLS; break; case 'R': Ropt = 1; craction = CRROWS; break; case 'e': rndtoeven = 1; eopt = 1; break; case 'P': case 'W': popt = 1; case 'v': break; case 'q': qopt = 1; break; default: (void) fprintf(stderr, "Usage: %s [-acemnoqrxCR] [file...]\n", progname); exit (1); } } if (!isatty(STDOUT_FILENO) || popt || qopt) usecurses = FALSE; startdisp(); signals(); read_hist(); /* setup the spreadsheet arrays, initscr() will get the screen size */ if (!growtbl(GROWNEW, 0, 0)) { stopdisp(); exit (1); } /* * Build revision message for later use: */ if (popt) *revmsg = '\0'; else { (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."); } #ifdef MSDOS if (optind < argc) #else if (optind < argc && !strcmp(argv[optind], "--")) optind++; if (optind < argc && argv[optind][0] != '|' && strcmp(argv[optind], "-")) #endif /* MSDOS */ (void) strcpy(curfile, argv[optind]); for (dbidx = DELBUFSIZE - 1; dbidx >= 0; ) { delbuf[dbidx] = NULL; delbuffmt[dbidx--] = NULL; } if (usecurses && has_colors()) initcolor(0); if (optind < argc) { if (!readfile(argv[optind], 1) && (optind == argc - 1)) error("New file: \"%s\"", curfile); EvalAll(); optind++; } else erasedb(); while (optind < argc) { (void) readfile(argv[optind], 0); optind++; } savedrow[0] = currow; savedcol[0] = curcol; savedstrow[0] = strow; savedstcol[0] = stcol; EvalAll(); if (!(popt || isatty(STDIN_FILENO))) (void) readfile("-", 0); if (qopt) { stopdisp(); exit (0); } clearok(stdscr, TRUE); EvalAll(); if (mopt) autocalc = 0; if (oopt) optimize = 1; if (nopt) numeric = 1; if (copt) calc_order = BYCOLS; if (ropt) calc_order = BYROWS; if (Copt) craction = CRCOLS; if (Ropt) craction = CRROWS; if (eopt) rndtoeven = 1; if (popt) { char *redraw = NULL; int o; #ifdef BSD43 optreset = 1; #endif optind = 1; stopdisp(); while ((o = getopt(argc, argv, "axmoncrCReP:W:vq")) != EOF) { switch (o) { case 'v': Vopt = 1; break; case 'P': if (*optarg == '/') { int in, out; in = dup(STDIN_FILENO); out = dup(STDOUT_FILENO); freopen("/dev/tty", "r", stdin); freopen("/dev/tty", "w", stdout); usecurses = TRUE; startdisp(); if (has_colors()) { initcolor(0); bkgd(COLOR_PAIR(1) | ' '); } clearok(stdscr, TRUE); FullUpdate++; linelim = 0; *line = '\0'; if (mode_ind != 'v') write_line(ctl('v')); error("Select range:"); update(1); while (!linelim) { int c; switch (c = nmgetch()) { case '.': case ':': case ctl('i'): if (!showrange) { write_line(c); break; } /* else drop through */ case ctl('m'): strcpy(line, "put "); linelim = 4; write_line('.'); if (showrange) write_line('.'); strcat(line, optarg); break; case ESC: case ctl('g'): case 'q': linelim = -1; break; case ctl('l'): FullUpdate++; clearok(stdscr, 1); break; default: write_line(c); break; } /* goto switches to insert mode when done, so we * have to switch back. */ if (mode_ind == 'i') write_line(ctl('v')); error(""); update(1); } stopdisp(); dup2(in, STDIN_FILENO); dup2(out, STDOUT_FILENO); close(in); close(out); redraw = "recalc\nredraw\n"; } else { strcpy(line, "put "); linelim = 4; strcat(line, optarg); } if (linelim > 0) { linelim = 0; yyparse(); } Vopt = 0; break; case 'W': strcpy(line, "write "); strcat(line, optarg); linelim = 0; yyparse(); break; default: break; } } if (redraw) printf(redraw); exit (0); } if (!isatty(STDOUT_FILENO)) { stopdisp(); write_fd(stdout, 0, 0, maxrow, maxcol); exit (0); } modflg = 0; cellassign = 0; #ifdef VENIX setbuf(stdin, NULL); #endif while (inloop) { running = 1; while (running) { nedistate = -1; narg = 1; if (edistate < 0 && linelim < 0 && autocalc && (changed || FullUpdate)) { EvalAll(); if (changed) /* if EvalAll changed or was before */ anychanged = TRUE; changed = 0; } else /* any cells change? */ if (changed) anychanged = TRUE; update(anychanged); anychanged = FALSE; #ifndef SYSV3 /* HP/Ux 3.1 this may not be wanted */ (void) refresh(); /* 5.3 does a refresh in getch */ #endif c = nmgetch(); getyx(stdscr, tempy, tempx); (void) move(1, 0); (void) clrtoeol(); (void) move(tempy, tempx); seenerr = 0; showneed = 0; /* reset after each update */ showexpr = 0; shownote = 0; /* * there seems to be some question about what to do w/ the iscntrl * some BSD systems are reportedly broken as well */ /* if ((c < ' ') || ( c == DEL )) how about international here ? PB */ #if pyr if(iscntrl(c) || (c >= 011 && c <= 015)) /* iscntrl broken in OSx4.1 */ #else if ((isascii(c) && (iscntrl(c) || (c == 020))) || /* iscntrl broken in OSx4.1 */ c == KEY_END || c == KEY_BACKSPACE) #endif switch(c) { #ifdef SIGTSTP case ctl('z'): (void) deraw(1); (void) kill(0, SIGTSTP); /* Nail process group */ /* the pc stops here */ (void) goraw(); break; #endif case ctl('r'): showneed = 1; case ctl('l'): FullUpdate++; (void) clearok(stdscr,1); break; case ctl('x'): FullUpdate++; showexpr = 1; (void) clearok(stdscr,1); break; default: error ("No such command (^%c)", c + 0100); break; case ctl('b'): { int ps; ps = pagesize ? pagesize : (LINES - RESROW - framerows)/2; backrow(arg * ps); strow = strow - (arg * ps); if (strow < 0) strow = 0; FullUpdate++; } break; case ctl('c'): running = 0; break; case KEY_END: case ctl('e'): if (linelim < 0 || mode_ind == 'v') { switch (c = nmgetch()) { case KEY_UP: case ctl('p'): case 'k': doend(-1, 0); break; case KEY_DOWN: case ctl('n'): case 'j': doend( 1, 0); break; case KEY_LEFT: case KEY_BACKSPACE: case ctl('h'): case 'h': doend( 0,-1); break; case KEY_RIGHT: case ' ': case ctl('i'): case 'l': doend( 0, 1); break; case ctl('e'): case ctl('y'): while (c == ctl('e') || c == ctl('y')) { int x = arg; while (arg) { if (c == ctl('e')) { strow++; while (strow && row_hidden[strow]) strow++; if (currow < strow) currow = strow; } else { strow--; while (row_hidden[strow]) strow--; forwrow(x); if (currow >= lastendrow) backrow(1); backrow(x); } arg--; } FullUpdate++; update(0); arg++; c = nmgetch(); } ungetch(c); break; case ESC: case ctl('g'): break; default: error("Invalid ^E command"); break; } } else write_line(ctl('e')); break; case ctl('y'): while (c == ctl('e') || c == ctl('y')) { int x = arg; while (arg) { if (c == ctl('e')) { strow++; while (strow && row_hidden[strow]) strow++; if (currow < strow) currow = strow; } else { strow--; while (row_hidden[strow]) strow--; forwrow(x); if (currow >= lastendrow) backrow(1); backrow(x); } arg--; } FullUpdate++; update(0); arg++; c = nmgetch(); } ungetch(c); break; case ctl('f'): { int ps; ps = pagesize ? pagesize : (LINES - RESROW - framerows)/2; forwrow(arg * ps); strow = strow + (arg * ps); FullUpdate++; } break; case ctl('g'): showrange = 0; linelim = -1; (void) move(1, 0); (void) clrtoeol(); break; case ESC: /* ctl('[') */ write_line(ESC); break; case ctl('d'): write_line(ctl('d')); break; case KEY_BACKSPACE: case DEL: case ctl('h'): if (linelim < 0) { /* not editing line */ backcol(arg); /* treat like ^B */ break; } write_line(ctl('h')); break; case ctl('i'): /* tab */ if (linelim < 0) { /* not editing line */ forwcol(arg); break; } write_line(ctl('i')); break; case ctl('m'): case ctl('j'): write_line(ctl('m')); break; case ctl('n'): c = craction; if (numeric_field) { craction = 0; write_line(ctl('m')); numeric_field = 0; } craction = c; if (linelim < 0) { forwrow(arg); break; } write_line(ctl('n')); break; case ctl('p'): c = craction; if (numeric_field) { craction = 0; write_line(ctl('m')); numeric_field = 0; } craction = c; if (linelim < 0) { backrow(arg); break; } write_line(ctl('p')); break; case ctl('q'): break; /* ignore flow control */ case ctl('s'): break; /* ignore flow control */ case ctl('t'): #if !defined(VMS) && !defined(MSDOS) && defined(CRYPT_PATH) error( "Toggle: a:auto,c:cell,e:ext funcs,n:numeric,t:top,x:encrypt,$:pre-scale,"); #else /* no encryption available */ error( "Toggle: a:auto,c:cell,e:ext funcs,n:numeric,t:top,$:pre-scale,"); #endif if (braille) move(1, 0); (void) refresh(); switch (nmgetch()) { case 'a': case 'A': case 'm': case 'M': autocalc ^= 1; error("Automatic recalculation %sabled.", autocalc ? "en":"dis"); break; case 'o': case 'O': optimize ^= 1; error("%sptimize expressions upon entry.", optimize ? "O":"Do not o"); break; case 'n': numeric = (!numeric); error("Numeric input %sabled.", numeric ? "en" : "dis"); break; case 't': case 'T': showtop = (!showtop); error("Top line %sabled.", showtop ? "en" : "dis"); break; case 'c': showcell = (!showcell); repaint(lastmx, lastmy, fwidth[lastcol]); error("Cell highlighting %sabled.", showcell ? "en" : "dis"); --modflg; /* negate the modflg++ */ break; case 'b': braille ^= 1; error("Braille enhancement %sabled.", braille ? "en" : "dis"); --modflg; /* negate the modflg++ */ break; case 's': cslop ^= 1; error("Color slop %sabled.", cslop ? "en" : "dis"); break; case 'C': color = !color; if (has_colors()) if (color) { attron(COLOR_PAIR(1)); bkgd(COLOR_PAIR(1) | ' '); } else { attron(COLOR_PAIR(0)); bkgd(COLOR_PAIR(0) | ' '); } error("Color %sabled.", color ? "en" : "dis"); break; case 'N': colorneg = !colorneg; error("Color changing of negative numbers %sabled.", colorneg ? "en" : "dis"); break; case 'E': colorerr = !colorerr; error("Color changing of cells with errors %sabled.", colorerr ? "en" : "dis"); break; case 'x': case 'X': #if defined(VMS) || defined(MSDOS) || !defined(CRYPT_PATH) error("Encryption not available."); #else Crypt = (! Crypt); error("Encryption %sabled.", Crypt? "en" : "dis"); #endif break; case 'l': case 'L': autolabel = (!autolabel); error("Autolabel %sabled.", autolabel? "en" : "dis"); break; case '$': if (prescale == 1.0) { error("Prescale enabled."); prescale = 0.01; } else { prescale = 1.0; error("Prescale disabled."); } break; case 'e': extfunc = (!extfunc); error("External functions %sabled.", extfunc? "en" : "dis"); break; case ESC: case ctl('g'): error(""); --modflg; /* negate the modflg++ */ break; case 'r': case 'R': error("Which direction after return key?"); switch(nmgetch()) { case ctl('m'): craction = 0; error("No action after new line"); break; case 'j': case ctl('n'): case KEY_DOWN: craction = CRROWS; error("Down row after new line"); break; case 'l': case ' ': case KEY_RIGHT: craction = CRCOLS; error("Right column after new line"); break; case ESC: case ctl('g'): error(""); break; default: error("Not a valid direction"); } break; case 'i': case 'I': autoinsert = (!autoinsert); error("Autoinsert %sabled.", autoinsert? "en" : "dis"); break; case 'w': case 'W': autowrap = (!autowrap); error("Autowrap %sabled.", autowrap? "en" : "dis"); break; case 'z': case 'Z': rowlimit = currow; collimit = curcol; error("Row and column limits set"); break; default: error("Invalid toggle command"); --modflg; /* negate the modflg++ */ } FullUpdate++; modflg++; break; case ctl('u'): narg = arg * 4; nedistate = 1; break; case ctl('v'): /* switch to navigate mode, or if already * * in navigate mode, insert variable name */ if (linelim >= 0) write_line(ctl('v')); break; case ctl('w'): /* insert variable expression */ if (linelim >= 0) { static char *temp = NULL, *temp1 = NULL; static unsigned templen = 0; int templim; /* scxrealloc will scxmalloc if needed */ if (strlen(line)+1 > templen) { templen = strlen(line)+40; temp = scxrealloc(temp, templen); temp1= scxrealloc(temp1, templen); } strcpy(temp, line); templim = linelim; linelim = 0; /* reset line to empty */ editexp(currow,curcol); strcpy(temp1, line); strcpy(line, temp); linelim = templim; ins_string(temp1); } break; case ctl('a'): if (linelim >= 0) write_line(c); else { remember(0); currow = 0; curcol = 0; rowsinrange = 1; colsinrange = fwidth[curcol]; remember(1); FullUpdate++; } break; case '\035': /* ^] */ if (linelim >= 0) write_line(c); break; } /* End of the control char switch stmt */ else if (isascii(c) && isdigit(c) && ((!numeric && linelim < 0) || (linelim >= 0 && (mode_ind == 'e' || mode_ind == 'v')) || edistate >= 0)) { /* we got a leading number */ if (edistate != 0) { /* First char of the count */ if (c == '0') { /* just a '0' goes to left col */ if (linelim >= 0) write_line(c); else leftlimit(); } else { nedistate = 0; narg = c - '0'; } } else { /* Succeeding count chars */ nedistate = 0; narg = arg * 10 + (c - '0'); } } else if (c == KEY_F(1) && !fkey[c - KEY_F0 - 1]) { deraw(1); system("man sc"); goraw(); clear(); } else if (linelim >= 0) { /* Editing line */ switch (c) { case ')': case ',': if (showrange) showdr(); break; default: break; } write_line(c); } else if (!numeric && ( c == '+' || c == '-' )) { /* increment/decrement ops */ register struct ent *p = *ATBL(tbl, currow, curcol); if (!p || !(p->flags & is_valid)) { if (c == '+') { editv(currow, curcol); linelim = strlen(line); insert_mode(); write_line(ctl('v')); } continue; } if (p->expr && !(p->flags & is_strexpr)) { error("Can't increment/decrement a formula\n"); continue; } FullUpdate++; modflg++; if (c == '+') p->v += (double) arg; else p->v -= (double) arg; } else if (c > KEY_F0 && c <= KEY_F(FKEYS)) { /* a function key was pressed */ if (fkey[c - KEY_F0 - 1]) { char *tpp; insert_mode(); strcpy(line, fkey[c - KEY_F0 - 1]); linelim = 0; for (tpp = line; *tpp != '\0'; tpp++) if (*tpp == '\\' && *(tpp + 1) == '"') memmove(tpp, tpp + 1, strlen(tpp)); for (tpp = line; *tpp != '\0'; tpp++) { char mycell[9]; strcpy(mycell, coltoa(curcol)); sprintf(mycell + strlen(mycell), "%d", currow); if (*tpp == '$' && *(tpp + 1) == '$') { memmove(tpp + strlen(mycell), tpp + 2, strlen(tpp + 1)); memcpy(tpp, mycell, strlen(mycell)); tpp += strlen(mycell); } } write_line(ctl('m')); } } else /* switch on a normal command character */ switch (c) { case ':': if (linelim >= 0) write_line(':'); break; /* Be nice to vi users */ case '@': EvalAll(); changed = 0; anychanged = TRUE; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': if (locked_cell(currow, curcol)) break; /* set mark 0 */ savedrow[27] = currow; savedcol[27] = curcol; savedstrow[27] = strow; savedstcol[27] = stcol; numeric_field = 1; (void) sprintf(line,"let %s = %c", v_name(currow, curcol), c); linelim = strlen(line); insert_mode(); break; case '+': case '-': if (!locked_cell(currow, curcol)) { struct ent *p = lookat(currow, curcol); /* set mark 0 */ savedrow[27] = currow; savedcol[27] = curcol; savedstrow[27] = strow; savedstcol[27] = stcol; numeric_field = 1; editv(currow, curcol); linelim = strlen(line); insert_mode(); if (c == '-' || p->flags & is_valid) write_line(c); else write_line(ctl('v')); } break; case '=': if (locked_cell(currow, curcol)) break; /* set mark 0 */ savedrow[27] = currow; savedcol[27] = curcol; savedstrow[27] = strow; savedstcol[27] = stcol; (void) sprintf(line,"let %s = ", v_name(currow, curcol)); linelim = strlen(line); insert_mode(); break; case '!': doshell(); break; /* * Range commands: */ case 'r': error( "Range: x:erase v:value c:copy f:fill d:def l:lock U:unlock S:show u:undef F:fmt"); if (braille) move(1, 0); (void) refresh(); c = nmgetch(); error(""); switch (c) { case 'l': (void) sprintf(line, "lock [range] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'U': (void) sprintf(line, "unlock [range] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'c': (void) sprintf(line, "copy [dest_range src_range] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'm': (void) sprintf(line, "move [destination src_range] %s ", v_name(currow, curcol)); linelim = strlen(line); insert_mode(); write_line(ctl('v')); break; case 'x': (void) sprintf(line, "erase [range] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'y': (void) sprintf(line, "yank [range] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'v': (void) sprintf(line, "value [range] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'f': (void) sprintf(line, "fill [range start inc] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'd': (void) sprintf(line, "define [string range] \""); linelim = strlen(line); insert_mode(); break; case 'u': (void) sprintf(line, "undefine [range] "); linelim = strlen(line); insert_mode(); break; case 'r': error("frame (top/bottom/left/right/all/unframe)"); if (braille) move(1, 0); refresh(); linelim = 0; c = nmgetch(); error(""); switch (c) { case 't': sprintf(line, "frametop [ rows] "); break; case 'b': sprintf(line, "framebottom [ rows] "); break; case 'l': sprintf(line, "frameleft [ cols] "); break; case 'r': sprintf(line, "frameright [ cols] "); break; case 'a': sprintf(line, "frame [ inrange] "); break; case 'u': sprintf(line, "unframe [] "); break; case ESC: case ctl('g'): linelim = -1; break; default: error("Invalid frame command"); linelim = -1; break; } if (linelim == 0) { linelim = strlen(line); insert_mode(); } if (c == 'a' || c == 'u') startshow(); break; case 's': (void) sprintf(line, "sort [range \"criteria\"] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'C': (void) sprintf(line, "color [range color#] "); linelim = strlen(line); insert_mode(); startshow(); break; case 'S': /* Show color definitions and various types of * ranges */ if (!are_ranges() && !are_frames() && !are_colors()) error("Nothing to show"); else { FILE *f; int pid; char px[MAXCMD]; char *pager; (void) strcpy(px, "| "); if (!(pager = getenv("PAGER"))) pager = DFLT_PAGER; (void) strcat(px, pager); f = openfile(px, &pid, NULL); if (!f) { error("Can't open pipe to %s", pager); break; } fprintf(f, "Named Ranges:\n=============\n\n"); if (!brokenpipe) list_ranges(f); if (!brokenpipe) fprintf(f, "\n\nFrames:\n=======\n\n"); if (!brokenpipe) list_frames(f); if (!brokenpipe) fprintf(f, "\n\nColors:\n=======\n\n"); if (!brokenpipe) list_colors(f); closefile(f, pid, 0); } break; case 'F': (void) sprintf(line, "fmt [range \"format\"] "); linelim = strlen(line); insert_mode(); startshow(); break; case '{': (void) sprintf(line, "leftjustify [range] "); linelim = strlen(line); insert_mode(); startshow(); break; case '}': (void) sprintf(line, "rightjustify [range] "); linelim = strlen(line); insert_mode(); startshow(); break; case '|': (void) sprintf(line, "center [range] "); linelim = strlen(line); insert_mode(); startshow(); break; case ESC: case ctl('g'): break; default: error("Invalid region command"); break; } break; case '~': (void) sprintf(line, "abbrev \""); linelim = strlen(line); insert_mode(); break; case '"': error("Select buffer (a-z or 0-9):"); if ((c=nmgetch()) == ESC || c == ctl('g')) { error(""); } else if (c >= '0' && c <= '9') { qbuf = c - '0' + (DELBUFSIZE - 10); error(""); } else if (c >= 'a' && c <= 'z') { qbuf = c - 'a' + (DELBUFSIZE - 36); error(""); } else if (c == '"') { qbuf = 0; error(""); } else error("Invalid buffer"); break; /* * Row/column commands: */ case KEY_IC: case 'i': case 'o': case 'a': case 'd': case 'y': case 'p': case 'v': case 's': case 'Z': { int rcqual; if (!(rcqual = get_rcqual(c))) { error("Invalid row/column command"); break; } error(""); /* clear line */ if (rcqual == ESC || rcqual == ctl('g')) break; switch (c) { case 'i': if (rcqual == 'r') insertrow(arg, 0); else insertcol(arg, 0); break; case 'o': if (rcqual == 'r') insertrow(arg, 1); else insertcol(arg, 1); break; case 'a': if (rcqual == 'r') while (arg--) duprow(); else while (arg--) dupcol(); break; case 'd': if (rcqual == 'r') deleterow(arg); else closecol(arg); break; case 'y': if (rcqual == 'r') yankrow(arg); else yankcol(arg); break; case 'p': if (rcqual == '.') { (void) sprintf(line, "pullcopy "); linelim = strlen(line); insert_mode(); startshow(); break; } while (arg--) pullcells(rcqual); break; /* * turn an area starting at currow/curcol into * constants vs expressions - not reversable */ case 'v': if (rcqual == 'r') { struct frange *fr; if ((fr = find_frange(currow, curcol))) valueize_area(currow, fr->or_left->col, currow + arg - 1, fr->or_right->col); else valueize_area(currow, 0, currow + arg - 1, maxcol); } else valueize_area(0, curcol, maxrow, curcol + arg - 1); modflg++; break; case 'Z': switch (rcqual) { case 'r': hiderow(arg); break; case 'c': hidecol(arg); break; case 'Z': if (modflg && curfile[0]) { writefile(curfile, 0, 0, maxrow, maxcol); running = 0; } else if (modflg) error("No file name."); else running = 0; break; } break; case 's': /* special case; no repeat count */ if (rcqual == 'r') rowshow_op(); else colshow_op(); break; } break; } case '$': rightlimit(); break; case '#': gotobottom(); break; case 'w': { register struct ent *p; while (--arg>=0) { do { if (curcol < maxcols - 1) curcol++; else { if (currow < maxrows - 1) { while(++currow < maxrows - 1 && row_hidden[currow]) ; curcol = 0; } else { error("At end of table"); break; } } } while(col_hidden[curcol] || !VALID_CELL(p, currow, curcol)); } rowsinrange = 1; colsinrange = fwidth[curcol]; break; } case 'b': { register struct ent *p; while (--arg>=0) { do { if (curcol) curcol--; else { if (currow) { while(--currow && row_hidden[currow]) ; curcol = maxcols - 1; } else { error("At start of table"); break; } } } while (col_hidden[curcol] || !VALID_CELL(p, currow, curcol)); } rowsinrange = 1; colsinrange = fwidth[curcol]; break; } case '^': gototop(); break; #ifdef KEY_HELP case KEY_HELP: #endif case '?': help(); break; case '\\': if (!locked_cell(currow, curcol)) { /* set mark 0 */ savedrow[27] = currow; savedcol[27] = curcol; savedstrow[27] = strow; savedstcol[27] = stcol; (void) sprintf(line, "label %s = \"", v_name(currow, curcol)); linelim = strlen(line); insert_mode(); } break; case '<': if (!locked_cell(currow, curcol)) { /* set mark 0 */ savedrow[27] = currow; savedcol[27] = curcol; savedstrow[27] = strow; savedstcol[27] = stcol; (void) sprintf(line, "leftstring %s = \"", v_name(currow, curcol)); linelim = strlen(line); insert_mode(); } break; case '>': if (!locked_cell(currow, curcol)) { /* set mark 0 */ savedrow[27] = currow; savedcol[27] = curcol; savedstrow[27] = strow; savedstcol[27] = stcol; (void) sprintf(line, "rightstring %s = \"", v_name(currow, curcol)); linelim = strlen(line); insert_mode(); } break; case '{': { struct ent *p = *ATBL(tbl, currow, curcol); if (p && p->label) ljustify(currow, curcol, currow, curcol); else error("Nothing to justify"); break; } case '}': { struct ent *p = *ATBL(tbl, currow, curcol); if (p && p->label) rjustify(currow, curcol, currow, curcol); else error("Nothing to justify"); break; } case '|': { struct ent *p = *ATBL(tbl, currow, curcol); if (p && p->label) center(currow, curcol, currow, curcol); else error("Nothing to center"); break; } case 'e': if (!locked_cell(currow, curcol)) { struct ent *p = lookat(currow, curcol); /* set mark 0 */ savedrow[27] = currow; savedcol[27] = curcol; savedstrow[27] = strow; savedstcol[27] = stcol; editv(currow, curcol); if (!(p->flags & is_valid)) { linelim = strlen(line); insert_mode(); } else edit_mode(); } break; case 'E': if (!locked_cell(currow, curcol)) { /* set mark 0 */ savedrow[27] = currow; savedcol[27] = curcol; savedstrow[27] = strow; savedstcol[27] = stcol; edits(currow, curcol); edit_mode(); } break; case 'f': formatcol(arg); break; case 'F': { register struct ent *p = *ATBL(tbl, currow, curcol); if (p && p->format) { (void) sprintf(line, "fmt [format] %s \"%s", v_name(currow, curcol), p->format); edit_mode(); linelim = strlen(line) - 1; } else { (void) sprintf(line, "fmt [format] %s \"", v_name(currow, curcol)); insert_mode(); linelim = strlen(line); } break; } case 'C': { if (braille) { braillealt ^= 1; break; } error("Color number to set (1-8)?"); if ((c=nmgetch()) == ESC || c == ctl('g')) { error(""); break; } if ((c -= ('1' - 1)) < 1 || c > 8) { error("Invalid color number."); break; } error(""); sprintf(line, "color %d = ", c); linelim = strlen(line); if (cpairs[c-1] && cpairs[c-1]->expr) { decompile(cpairs[c-1]->expr, 0); line[linelim] = '\0'; edit_mode(); } else { insert_mode(); } break; } #ifdef KEY_FIND case KEY_FIND: #endif case 'g': (void) sprintf(line, "goto [v] "); linelim = strlen(line); insert_mode(); break; case 'n': go_last(); break; case 'P': (void) sprintf(line, "put [\"dest\" range] \""); /* See the comments under "case 'W':" below for an explanation of the * logic here. */ curfile[strlen(curfile) + 1] = '\0'; if (strrchr(curfile, '.') != NULL) { if (!strcmp((strrchr(curfile, '.')), ".sc")) { *strrchr(curfile, '.') = '\0'; strcpy(curfile + strlen(curfile) + 3, ".\0"); } else if (scext != NULL && !strcmp((strrchr(curfile, '.') + 1), scext)) { *strrchr(curfile, '.') = '\0'; strcpy(curfile + strlen(curfile) + strlen(scext) + 1, ".\0"); } } if (*curfile) error("Default path is \"%s.%s\"", curfile, scext == NULL ? "sc" : scext); c = *(curfile + strlen(curfile) + strlen(curfile + strlen(curfile) + 1)); *(curfile + strlen(curfile) + strlen(curfile + strlen(curfile) + 1)) = '\0'; curfile[strlen(curfile)] = c; linelim = strlen(line); insert_mode(); break; case 'M': (void) sprintf(line, "merge [\"source\"] \""); linelim = strlen(line); insert_mode(); break; case 'R': if (mdir) (void) sprintf(line,"merge [\"macro_file\"] \"%s", mdir); else (void) sprintf(line,"merge [\"macro_file\"] \""); linelim = strlen(line); insert_mode(); break; case 'D': (void) sprintf(line, "mdir [\"macro_directory\"] \""); linelim = strlen(line); insert_mode(); break; case 'A': if (autorun) (void) sprintf(line,"autorun [\"macro_file\"] \"%s", autorun); else (void) sprintf(line, "autorun [\"macro_file\"] \""); linelim = strlen(line); insert_mode(); break; case 'G': (void) sprintf(line, "get [\"source\"] \""); if (*curfile) error("Default file is \"%s\"", curfile); linelim = strlen(line); insert_mode(); break; case 'W': (void) sprintf(line, "write [\"dest\" range] \""); /* First, append an extra null byte to curfile. Then, if curfile ends in * ".sc" (or '.' followed by the string in scext), move the '.' to the * end and replace it with a null byte. This results in two consecutive * null-terminated strings, the first being curfile with the ".sc" (or '.' * and scext) removed, if present, and the second being either "sc." (or * scext and '.') or "", depending on whether the ".sc" (or '.' and scext) * was present or not. */ curfile[strlen(curfile) + 1] = '\0'; if (strrchr(curfile, '.') != NULL) { if (!strcmp((strrchr(curfile, '.')), ".sc")) { *strrchr(curfile, '.') = '\0'; strcpy(curfile + strlen(curfile) + 3, ".\0"); } else if (scext != NULL && !strcmp((strrchr(curfile, '.') + 1), scext)) { *strrchr(curfile, '.') = '\0'; strcpy(curfile + strlen(curfile) + strlen(scext) + 1, ".\0"); } } /* Now append ".asc" (or '.' and the value of ascext) to the possibly * truncated curfile. */ if (*curfile) error("Default file is \"%s.%s\"", curfile, ascext == NULL ? "asc" : ascext); /* Now swap the '.' and null bytes again. If there is no '.', swap a * null byte with itself. This may seem convoluted, but it works well, * and obviates the need for a 1024 byte temporary buffer. - CRM */ c = *(curfile + strlen(curfile) + strlen(curfile + strlen(curfile) + 1)); *(curfile + strlen(curfile) + strlen(curfile + strlen(curfile) + 1)) = '\0'; curfile[strlen(curfile)] = c; linelim = strlen(line); insert_mode(); break; case 'S': /* set options */ (void) sprintf(line, "set "); error("Options:byrows,bycols,iterations=n,tblstyle=(0|tbl|latex|slatex|tex|frame),"); linelim = strlen(line); insert_mode(); break; case 'T': /* tbl output */ (void) sprintf(line, "tbl [\"dest\" range] \""); /* See the comments under "case 'W':" above for an explanation of the * logic here. */ curfile[strlen(curfile) + 1] = '\0'; if (strrchr(curfile, '.') != NULL) { if (!strcmp((strrchr(curfile, '.')), ".sc")) { *strrchr(curfile, '.') = '\0'; strcpy(curfile + strlen(curfile) + 3, ".\0"); } else if (scext != NULL && !strcmp((strrchr(curfile, '.') + 1), scext)) { *strrchr(curfile, '.') = '\0'; strcpy(curfile + strlen(curfile) + strlen(scext) + 1, ".\0"); } } if (*curfile && tbl_style == 0) error("Default file is \"%s.%s\"", curfile, tbl0ext == NULL ? "cln" : tbl0ext); else if (*curfile && tbl_style == TBL) error("Default file is \"%s.%s\"", curfile, tblext == NULL ? "tbl" : tblext); else if (*curfile && tbl_style == LATEX) error("Default file is \"%s.%s\"", curfile, latexext == NULL ? "lat" : latexext); else if (*curfile && tbl_style == SLATEX) error("Default file is \"%s.%s\"", curfile, slatexext == NULL ? "stx" : slatexext); else if (*curfile && tbl_style == TEX) error("Default file is \"%s.%s\"", curfile, texext == NULL ? "tex" : texext); c = *(curfile + strlen(curfile) + strlen(curfile + strlen(curfile) + 1)); *(curfile + strlen(curfile) + strlen(curfile + strlen(curfile) + 1)) = '\0'; curfile[strlen(curfile)] = c; linelim = strlen(line); insert_mode(); break; #ifdef KEY_DC case KEY_DC: #endif case 'x': if (calc_order == BYROWS) eraser(lookat(currow, curcol), lookat(currow, curcol + arg - 1)); else eraser(lookat(currow, curcol), lookat(currow + arg - 1, curcol)); break; case 'Q': case 'q': running = 0; break; case KEY_LEFT: case 'h': backcol(arg); break; case KEY_DOWN: case 'j': forwrow(arg); break; case KEY_UP: case 'k': backrow(arg); break; case 'H': backcol(curcol - stcol + 2); break; #ifdef KEY_NPAGE case KEY_NPAGE: /* next page */ #endif case 'J': { int ps; ps = pagesize ? pagesize : (LINES - RESROW - framerows)/2; forwrow(arg * ps); strow = strow + (arg * ps); FullUpdate++; } break; #ifdef KEY_PPAGE case KEY_PPAGE: /* previous page */ #endif case 'K': { int ps; ps = pagesize ? pagesize : (LINES - RESROW - framerows)/2; backrow(arg * ps); strow = strow - (arg * ps); if (strow < 0) strow = 0; FullUpdate++; } break; #ifdef KEY_HOME case KEY_HOME: gohome(); break; #endif case 'L': forwcol(lcols - (curcol - stcol) + 1); break; case KEY_RIGHT: case ' ': case 'l': forwcol(arg); break; case 'm': markcell(); break; case 'c': error("Copy marked cell:"); if ((c = nmgetch()) == ESC || c == ctl('g')) { error(""); break; } if (c == '.') { copy(NULL, NULL, lookat(currow, curcol), NULL); (void) sprintf(line, "copy [dest_range src_range] "); linelim = strlen(line); insert_mode(); startshow(); break; } 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 \')"); break; } if (savedrow[c] == -1) { error("Mark not set"); break; } error(""); { struct ent *p = *ATBL(tbl, savedrow[c], savedcol[c]); int c1; struct ent *n; for (c1 = curcol; arg-- && c1 < maxcols; c1++) { if ((n = *ATBL(tbl, currow, c1))) { if (n->flags & is_locked) continue; if (!p) { (void) clearent(n); continue; } } else { if (!p) break; n = lookat(currow, c1); } copyent(n, p, currow - savedrow[c], c1 - savedcol[c], 0, 0, maxrow, maxcol, 0); n->flags |= is_changed; } FullUpdate++; modflg++; break; } case '`': case '\'': dotick(c); break; case '*': { register struct ent *p; error("Note: Add/Delete/Show/*(go to note)?"); if ((c = nmgetch()) == ESC || c == ctl('g')) { error(""); break; } if (c == 'a' || c == 'A') { sprintf(line, "addnote [target range] %s ", v_name(currow, curcol)); linelim = strlen(line); insert_mode(); write_line(ctl('v')); error(""); FullUpdate++; break; } if (c == 'd' || c == 'D') { p = lookat(currow, curcol); p->nrow = p->ncol = -1; p->flags |= is_changed; error(""); modflg++; FullUpdate++; break; } if (c == 's' || c == 'S') { FullUpdate++; shownote = 1; clearok(stdscr,1); error("Highlighted cells have attached notes."); break; } if (c == '*') { gotonote(); error(""); break; } error("Invalid command"); break; } case 'z': switch (c = nmgetch()) { case ctl('m'): strow = currow; FullUpdate++; (void) clearok(stdscr,1); break; case '.': strow = -1; FullUpdate++; (void) clearok(stdscr,1); break; case '|': stcol = -1; FullUpdate++; (void) clearok(stdscr,1); break; case 'c': /* Force centering of current cell (or range, if * we've just jumped to a new range with the goto * command). */ strow = -1; stcol = -1; FullUpdate++; (void) clearok(stdscr,1); break; default: break; } break; #ifdef KEY_RESIZE case KEY_RESIZE: #ifndef SIGWINCH winchg(); #endif break; #endif default: if ((toascii(c)) != c) error ("Weird character, decimal %d\n", (int) c); else error ("No such command (%c)", c); break; } edistate = nedistate; arg = narg; } /* while (running) */ inloop = modcheck(" before exiting"); } /* while (inloop) */ stopdisp(); write_hist(); #ifdef VMS /* Until VMS "fixes" exit we should say 1 here */ exit (1); #else exit (0); #endif /*NOTREACHED*/ } /* set the calculation order */ void setorder(int i) { if ((i == BYROWS) || (i == BYCOLS)) calc_order = i; } void setauto(int i) { autocalc = i; } void signals() { #ifdef SIGVOID void doquit(); void time_out(); void dump_me(); void nopipe(); #ifdef SIGWINCH void winchg(); #endif #else int doquit(); int time_out(); int dump_me(); int nopipe(); #ifdef SIGWINCH int winchg(); #endif #endif (void) signal(SIGINT, doquit); #if !defined(MSDOS) (void) signal(SIGQUIT, dump_me); (void) signal(SIGPIPE, nopipe); (void) signal(SIGALRM, time_out); #ifndef __DJGPP__ (void) signal(SIGBUS, doquit); #endif #endif (void) signal(SIGTERM, doquit); (void) signal(SIGFPE, doquit); #ifdef SIGWINCH (void) signal(SIGWINCH, winchg); #endif } #ifdef SIGVOID void #else int #endif nopipe() { brokenpipe = TRUE; } #ifdef SIGVOID void #else int #endif winchg() { stopdisp(); startdisp(); /* * I'm not sure why a refresh() needs to be done both before and after * the clearok() and update(), but without doing it this way, a screen * (or window) that grows bigger will leave the added space blank. - CRM */ refresh(); FullUpdate++; (void) clearok(stdscr, TRUE); update(1); refresh(); #ifdef SIGWINCH (void) signal(SIGWINCH, winchg); #endif } #ifdef SIGVOID void #else int #endif doquit() { if (usecurses) { diesave(); stopdisp(); } write_hist(); exit (1); } #ifdef SIGVOID void #else int #endif dump_me() { if (usecurses) diesave(); deraw(1); abort(); } /* try to save the current spreadsheet if we can */ void diesave() { char path[PATHLEN]; if (modcheck(" before Spreadsheet dies") == 1) { (void) sprintf(path, "~/%s", SAVENAME); if (writefile(path, 0, 0, maxrow, maxcol) < 0) { (void) sprintf(path, "/tmp/%s", SAVENAME); if (writefile(path, 0, 0, maxrow, maxcol) < 0) error("Couldn't save current spreadsheet, Sorry"); } } } /* check if tbl was modified and ask to save */ int modcheck(char *endstr) { if (modflg && curfile[0]) { int yn_ans; char lin[100]; (void) sprintf(lin,"File \"%s\" is modified, save%s? ",curfile,endstr); if ((yn_ans = yn_ask(lin)) < 0) return(1); else if (yn_ans == 1) { if (writefile(curfile, 0, 0, maxrow, maxcol) < 0) return (1); } } else if (modflg) { int yn_ans; if ((yn_ans = yn_ask("Do you want a chance to save the data? ")) < 0) return(1); else return(yn_ans); } return(0); } /* Returns 1 if cell is locked, 0 otherwise */ int locked_cell(int r, int c) { struct ent *p = *ATBL(tbl, r, c); if (p && (p->flags & is_locked)) { error("Cell %s%d is locked", coltoa(c), r) ; return(1); } return(0); } /* Check if area contains locked cells */ int any_locked_cells(int r1, int c1, int r2, int c2) { int r, c; struct ent *p ; for (r=r1; r<=r2; r++) for (c=c1; c<=c2; c++) { p = *ATBL(tbl, r, c); if (p && (p->flags&is_locked)) return(1); } return(0); }