teapot-spreadsheet/src/common/sheet.c

1913 lines
50 KiB
C

/* #includes */ /*{{{C}}}*//*{{{*/
#ifdef DMALLOC
#include "dmalloc.h"
#endif
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "csv.h"
#include "default.h"
#include "display.h"
#include "eval.h"
#include "main.h"
#include "misc.h"
#include "parser.h"
#include "scanner.h"
#include "sheet.h"
#include "utf8.h"
#include "xdr.h"
/*}}}*/
/* #defines */ /*{{{*/
#define HASH(x,s) \
{ \
const unsigned char *S=(const unsigned char*)s; \
\
x=0; \
while (*S) { x=(x<<5)+((x>>27)^*S); ++S; } \
x%=LABEL_CACHE; \
}
/*}}}*/
/* variables */ /*{{{*/
static int upd_clock; /* evaluate clocked expressions */
/* Used during evaluation of a cell to specify the currently updated cell */
Sheet *upd_sheet;
Location upd_l;
int max_eval;
/*}}}*/
/* copycelltosheet -- copy a cell into a sheet*/ /*{{{*/
void copycelltosheet(const Cell *fromcell, Sheet *sheet2,
const Location to, LabelHandling lh)
{
assert(sheet2 != (Sheet*)0);
Cell *tocell = safe_cell_at(sheet2, to);
if (fromcell == tocell) return;
freecellofsheet(sheet2, to);
if (fromcell != NULLCELL)
/* copy first cell to second */ /*{{{*/
{
sheet2->changed = true;
Cell *alsotocell = initcellofsheet(sheet2, to, NULL);
assert(tocell == NULLCELL || tocell == alsotocell);
copycell(alsotocell, fromcell, lh);
}
/*}}}*/
}
/*}}}*/
/* dump_cell -- write internal contents of cell to standard out */ /*{{{*/
static void dump_cell(Sheet *sheet, int x, int y, int z)
{
Cell* c;
char buf[2048];
assert(sheet != (Sheet*)0);
if (x < 0 || y < 0 || z < 0)
{
printf("TEADUMP: Requested cell &(%d,%d,%d) has negative coordinates.\n",
x, y, z);
return;
}
if (!LOC_WITHINC(sheet, x, y, z))
{
printf("TEADUMP: Requested cell &(%d,%d,%d) outside sheet dims %d,%d,%d.\n",
x, y, z, sheet->dimx, sheet->dimy, sheet->dimz);
return;
}
c = CELL_ATC(sheet, x, y, z);
if (c == NULLCELL)
{
printf("TEADUMP: Cell at &(%d,%d,%d) is empty.\n", x, y, z);
return;
}
printf("TEADUMP of &(%d,%d,%d):\n", x, y, z);
for (TokVariety tv = BASE_CONT; tv < CONTINGENT; ++tv)
{
printf(" %s: ", TokVariety_Name[tv]);
printtok(buf, sizeof(buf), 0, 1, 0, -1, 1, c->tok + tv);
printf("%s.\n", buf);
}
if (c->label == NULL) printf("\n No label.\n");
else printf("\n Label: %s.\n", c->label);
printf(" Adjustment: %s.\n", Adjust_Name[c->adjust]);
printf(" Float format: %s.\n", FloatFormat_Name[c->fform]);
printf(" Precision: %d\n Attributes: ", c->precision);
for (ColorAspect ca = FOREGROUND; ca < NUM_COLOR_ASPECTS; ++ca)
printf(" %s color: %d\n", ColorAspect_Name[ca], c->aspect[ca]);
if (c->updated) printf("updated ");
if (c->shadowed) printf("shadowed ");
if (c->locked) printf("locked ");
if (c->transparent) printf("transparent ");
if (c->ignored) printf("ignored ");
if (c->clock_t0) printf("clock_t0 ");
if (c->clock_t1) printf("clock_t1 ");
if (c->clock_t2) printf("clock_t2 ");
if (c->bold) printf("bold ");
if (c->underline) printf("underline ");
printf("\n\n");
}
/*}}}*/
/* dump_current_cell -- dump_cell of the current_cell */ /*{{{*/
void dump_current_cell(Sheet *sheet)
{
assert(sheet != (Sheet *)0);
dump_cell(sheet, sheet->cur[X], sheet->cur[Y], sheet->cur[Z]);
}
/*}}}*/
/* swapblock -- swap two non-overlapping blocks of cells */ /*{{{*/
static void swapblock(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2, int xdist, int ydist, int zdist)
{
int xoff, yoff, zoff;
assert(sheet1!=(Sheet*)0);
assert(sheet2!=(Sheet*)0);
resize(sheet1, x1+xdist-1, y1+ydist-1, z1+zdist-1, NULL);
resize(sheet2, x2+xdist-1, y2+ydist-1, z2+zdist-1, NULL);
for (xoff=0; xoff<xdist; ++xoff)
for (yoff=0; yoff<ydist; ++yoff)
for (zoff=0; zoff<zdist; ++zoff)
{
Cell *t;
t = CELL_ATC(sheet1,x1+xoff,y1+yoff,z1+zoff);
CELL_ATC(sheet1,x1+xoff,y1+yoff,z1+zoff) = CELL_ATC(sheet2,x2+xoff,y2+yoff,z2+zoff);
CELL_ATC(sheet2,x2+xoff,y2+yoff,z2+zoff) = t;
}
sheet1->changed = true;
sheet2->changed = true;
}
/*}}}*/
/* signum -- -1 if < 0, 0 if 0, 1 if > 0 */
static int signum(int x) {
return (x > 0) - (x < 0);
}
/* cmpflt -- -1 if <, 0 if =, 1 if > */
static int cmpflt(FltT x, FltT y) {
return (x > y) - (x < y);
}
/* cmpflint -- -1 if <, 0 if =, 1 if > */
static int cmpflint(FltT x, IntT y) {
return (x > y) - (x < y);
}
/* cmpint -- -1 if <, 0 if =, 1 if > */
static int cmpint(IntT x, IntT y) {
return (x > y) - (x < y);
}
/* cmpintfl -- -1 if <, 0 if =, 1 if > */
static int cmpintfl(IntT x, FltT y) {
return (x > y) - (x < y);
}
#define NOT_COMPARABLE 2
/* cmpcell -- compare to cells with given order flags */ /*{{{*/
/* Notes */ /*{{{*/
/*
Compare the _values_ of two cells. The result is -1 if first is smaller
than second, 0 if they are equal and 1 if the first is bigger than the
second. A result of 2 means they are not comparable.
*/
/*}}}*/
static int cmpcell(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2, int sortkey)
{
int ascending = (sortkey & ASCENDING) ? 1 : -1;
Token leftval = gettok(safe_cell_atc(sheet1, x1, y1, z1), CURR_VAL);
Token rightval = gettok(safe_cell_atc(sheet2, x2, y2, z2), CURR_VAL);
/* empty cells are smaller than any non-empty cell */ /*{{{*/
if (leftval.type == EMPTY)
if (rightval.type == EMPTY) return 0;
else return -ascending;
if (rightval.type == EMPTY) return ascending;
/*}}}*/
switch (leftval.type)
{
/* STRING */ /*{{{*/
case STRING:
if (rightval.type == STRING)
return signum(strcmp(leftval.u.string, rightval.u.string)) * ascending;
return NOT_COMPARABLE;
/*}}}*/
/* FLOAT */ /*{{{*/
case FLOAT:
if (rightval.type == FLOAT)
return cmpflt(leftval.u.flt, rightval.u.flt)*ascending;
if (rightval.type == INT)
return cmpflint(leftval.u.flt, rightval.u.integer)*ascending;
return NOT_COMPARABLE;
/*}}}*/
/* INT */ /*{{{*/
case INT:
{
if (rightval.type == INT)
return cmpint(leftval.u.integer, rightval.u.integer)*ascending;
if (rightval.type == FLOAT)
return cmpintfl(leftval.u.integer, rightval.u.flt)*ascending;
return NOT_COMPARABLE;
}
/*}}}*/
default: return NOT_COMPARABLE;
}
}
/*}}}*/
/* initialize_sheet -- fill in fields of a newly allocated Sheet */ /*{{{*/
void initialize_sheet(Sheet *sheet) {
assert(sheet != (Sheet*)0);
OLOCATION(sheet->cur);
sheet->offx = sheet->offy = 0;
sheet->dimx = sheet->dimy = sheet->dimz = 0;
sheet->sheet = (Cell**)0;
sheet->column = (int*)0;
sheet->orix = sheet->oriy = 0;
sheet->maxx = sheet->maxy = 0;
sheet->name = (char*)0;
OLOCATION(sheet->mark1);
OLOCATION(sheet->mark2);
sheet->marking = UNMARKED;
sheet->changed = 0;
sheet->moveonly = 0;
sheet->clk = 0;
(void)memset(sheet->labelcache, 0, sizeof(sheet->labelcache));
sheet->max_colors = MAX_MAX_COLORS;
/* start display of this sheet */
if (!batch) {
display_init(sheet, always_redraw);
line_msg((const char*)0,"");
}
}
/*}}}*/
/* resize -- check if sheet needs to be resized in any dimension */ /*{{{*/
void resize(Sheet *sheet, int x, int y, int z, bool *qextended)
{
assert(x>=0);
assert(y>=0);
assert(z>=0);
assert(sheet!=(Sheet*)0);
if (!LOC_WITHINC(sheet,x,y,z))
{
/* variables */ /*{{{*/
Sheet dummy;
/*}}}*/
sheet->changed = true;
if (qextended) *qextended = true;
dummy.dimx = (x >= sheet->dimx ? x+1 : sheet->dimx);
dummy.dimy = (y >= sheet->dimy ? y+1 : sheet->dimy);
dummy.dimz = (z >= sheet->dimz ? z+1 : sheet->dimz);
/* allocate new sheet */ /*{{{*/
dummy.sheet = malloc(dummy.dimx*dummy.dimy*dummy.dimz*sizeof(Cell*));
for (ALL_COORDS_IN_SHEETC(&dummy,x,y,z))
{
if (LOC_WITHINC(sheet,x,y,z)) CELL_ATC(&dummy,x,y,z) = CELL_ATC(sheet,x,y,z);
else CELL_ATC(&dummy,x,y,z) = NULLCELL;
}
if (sheet->sheet!=(Cell**)0) free(sheet->sheet);
sheet->sheet = dummy.sheet;
/*}}}*/
/* allocate new columns */ /*{{{*/
if (x>sheet->dimx || z>=sheet->dimz)
{
dummy.column=malloc(dummy.dimx*dummy.dimz*sizeof(int));
for (x=0; x<dummy.dimx; ++x) for (z=0; z<dummy.dimz; ++z)
{
if (x<sheet->dimx && z<sheet->dimz)
*(dummy.column+x*dummy.dimz+z)=*(sheet->column+x*sheet->dimz+z);
else *(dummy.column+x*dummy.dimz+z)=DEF_COLUMNWIDTH;
}
if (sheet->column!=(int*)0) free(sheet->column);
sheet->column = dummy.column;
}
/*}}}*/
sheet->dimx = dummy.dimx;
sheet->dimy = dummy.dimy;
sheet->dimz = dummy.dimz;
}
}
/*}}}*/
/* initcellofsheet -- initialise new cell, if it does not exist yet */ /*{{{*/
Cell *initcellofsheet(Sheet *sheet, const Location at, bool *qnew)
{
Cell *nc;
assert(IN_OCTANT(at));
resize(sheet, at[X], at[Y], at[Z], qnew);
nc = CELL_AT(sheet,at);
if (nc == NULLCELL)
{
sheet->changed = true;
if (qnew) *qnew = true;
nc = malloc(sizeof(Cell));
CELL_AT(sheet,at) = nc;
initcellcontents(nc);
}
return nc;
}
/*}}}*/
/* safe_cell_at -- returns pointer to the cell at the given location,
even in the face of various edge conditions.
*/
Cell *safe_cell_atc(Sheet *sheet, int x, int y, int z)
{
if (sheet == (Sheet*)0) return NULLCELL;
if (sheet->sheet == (Cell **)0) return NULLCELL;
if (!LOC_WITHINC(sheet,x,y,z)) return NULLCELL;
return CELL_ATC(sheet,x,y,z);
}
/* safe_cell_at -- returns pointer to the cell at the given location,
even in the face of various edge conditions.
*/
Cell *safe_cell_at(Sheet *sheet, const Location at)
{
if (sheet == (Sheet*)0) return NULLCELL;
if (sheet->sheet == (Cell **)0) return NULLCELL;
if (!LOC_WITHIN(sheet, at)) return NULLCELL;
return CELL_AT(sheet, at);
}
/* curcell -- return pointer to the current cell */ /*{{{*/
Cell *curcell(Sheet *sheet)
{
return safe_cell_at(sheet, sheet->cur);
}
/* cachelabels -- create new label cache */ /*{{{*/
void cachelabels(Sheet *sheet)
{
int i;
Location w;
if (sheet==(Sheet*)0) return;
for (i=0; i<LABEL_CACHE; ++i) /* free bucket */ /*{{{*/
{
struct Label *run;
for (run=sheet->labelcache[i]; run!=(struct Label*)0;)
{
struct Label *runnext;
runnext=run->next;
free(run);
run=runnext;
}
sheet->labelcache[i]=(struct Label*)0;
}
/*}}}*/
for (ALL_LOCS_IN_SHEET(sheet,w))
/* cache all labels */ /*{{{*/
{
const char *l;
l = getlabel(CELL_AT(sheet,w));
if (*l)
{
unsigned long hx;
struct Label **run;
HASH(hx,l);
for (run=&sheet->labelcache[(unsigned int)hx]; *run!=(struct Label*)0 && strcmp(l,(*run)->label); run=&((*run)->next));
if (*run==(struct Label*)0)
{
*run=malloc(sizeof(struct Label));
(*run)->next=(struct Label*)0;
(*run)->label=l;
LOCATION_GETS((*run)->location,w);
}
/* else we have a duplicate label, which _can_ happen under */
/* unfortunate conditions. Don't tell anybody. */
}
}
/*}}}*/
}
/*}}}*/
/* freesheet -- free all cells of an entire spread sheet */ /*{{{*/
void freesheet(Sheet *sheet, int all)
{
/* variables */ /*{{{*/
Location w;
/*}}}*/
assert(sheet!=(Sheet*)0);
sheet->changed=0;
for (ALL_LOCS_IN_SHEET(sheet,w))
{
freecellofsheet(sheet,w);
}
if (all)
{
int i;
for (i=0; i<LABEL_CACHE; ++i) /* free all buckets */ /*{{{*/
{
struct Label *run;
for (run=sheet->labelcache[i]; run!=(struct Label*)0;)
{
struct Label *runnext;
runnext=run->next;
free(run);
run=runnext;
}
}
/*}}}*/
if (sheet->sheet) free(sheet->sheet);
if (sheet->column) free(sheet->column);
if (sheet->name) free(sheet->name);
if (!batch)
{
display_end(sheet);
}
}
else
{
int x,z;
for (x=0; x<sheet->dimx; ++x) for (z=0; z<sheet->dimz; ++z)
{
*(sheet->column+x*sheet->dimz+z)=DEF_COLUMNWIDTH;
}
cachelabels(sheet);
forceupdate(sheet);
}
}
/*}}}*/
/* forceupdate -- clear all clock and update flags */ /*{{{*/
void forceupdate(Sheet *sheet)
{
int i;
Cell *cell;
assert(sheet!=(Sheet*)0);
if (sheet->sheet == (Cell **)0) return;
for (ALL_CELLS_IN_SHEET(sheet,i,cell))
if (cell != NULLCELL)
cell->updated = cell->clock_t0 = cell->clock_t1 = cell->clock_t2 = 0;
update(sheet);
}
/*}}}*/
/* freecellofsheet -- free one cell */ /*{{{*/
void freecellofsheet(Sheet *sheet, const Location at)
{
assert(sheet != (Sheet*)0);
if (sheet->sheet != (Cell**)0 && CELL_IS_GOOD(sheet,at))
{
Cell *c = CELL_AT(sheet,at);
freecellcontents(c);
free(c);
CELL_AT(sheet,at) = NULLCELL;
sheet->changed = 1;
}
}
/*}}}*/
/* columnwidth -- get width of column */ /*{{{*/
int columnwidth(Sheet *sheet, int x, int z)
{
assert(sheet!=(Sheet*)0);
if (x<sheet->dimx && z<sheet->dimz) return (*(sheet->column+x*sheet->dimz+z));
else return DEF_COLUMNWIDTH;
}
/*}}}*/
/* setwidth -- set width of column */ /*{{{*/
bool setwidth(Sheet *sheet, int x, int z, int width)
{
assert(sheet!=(Sheet*)0);
bool isbigger = false;
resize(sheet, x, 1, z, &isbigger);
if (isbigger) sheet->changed = true;
int *storage = sheet->column + x*sheet->dimz + z;
if (*storage != width) {
*storage = width;
sheet->changed = true;
return true;
}
return isbigger;
}
/*}}}*/
/* cellwidth -- get width of a cell */ /*{{{*/
int cellwidth(Sheet *sheet, const Location at)
{
int width,x;
if (SHADOWED(sheet,at)) return 0;
x = at[X];
width = columnwidth(sheet,x,at[Z]);
for (++x; SHADOWEDC(sheet,x,at[Y],at[Z]);
width += columnwidth(sheet,x,at[Z]), ++x);
return width;
}
/*}}}*/
/* putcont -- assign new contents */ /*{{{*/
void puttok(Sheet *sheet, const Location at, Token t, TokVariety v)
{
Cell *cell;
assert(sheet != (Sheet*)0);
sheet->changed = 1;
cell = initcellofsheet(sheet, at, NULL);
tfree(&(cell->tok[v]));
cell->tok[v] = t;
redraw_cell(sheet, at);
}
/*}}}*/
/* recompvalue -- get tcopy()ed value */ /*{{{*/
Token recompvalue(Sheet *sheet, const Location at)
{
/* variables */ /*{{{*/
Token result;
int orig_upd_clock;
Cell* cell;
/*}}}*/
assert(sheet!=(Sheet*)0);
if (!IN_OCTANT(at))
/* return error */ /*{{{*/
{
result.type=EEK;
char *templ = _("Attempt to access cell at (%d,%d,%d) with negative index");
result.u.err = malloc(strlen(templ)+32);
sprintf(result.u.err, templ, at[X], at[Y], at[Z]);
return result;
}
/*}}}*/
result.type = EMPTY;
/* Can always short-circuit an out-of-bounds or empty cell */
if (!CELL_IS_GOOD(sheet,at)) return result;
/* only short-circuit on empty contents of a good cell if we are NOT
depending on this call to update the current value
*/
cell = CELL_AT(sheet, at);
if (!upd_clock && gettok(cell, CONTINGENT).type == EMPTY) return result;
/* update value of this cell if needed and return it */ /*{{{*/
orig_upd_clock = upd_clock;
if (cell->ignored)
{
tfree(cell->tok + CURR_VAL);
cell->updated = 1;
}
else if (cell->updated == 0)
{
/* variables */ /*{{{*/
Sheet *old_sheet;
Location old_l;
int old_max_eval;
Token oldvalue;
/*}}}*/
old_sheet = upd_sheet;
LOCATION_GETS(old_l, upd_l);
old_max_eval = max_eval;
upd_sheet = sheet;
LOCATION_GETS(upd_l, at);
max_eval = MAX_EVALNEST;
if (cell->clock_t1 == 0)
{
cell->updated = 1;
oldvalue = gettok(cell, CURR_VAL);
upd_clock = 0;
cell->tok[CURR_VAL] = evaltoken(gettok(cell, CONTINGENT), FULL);
tfree(&oldvalue);
}
else if (upd_clock)
{
cell->updated = 1;
upd_clock = 0;
oldvalue = gettok(cell, RES_VAL);
cell->tok[RES_VAL] = evaltoken(gettok(cell, CONTINGENT), FULL);
tfree(&oldvalue);
}
upd_sheet = old_sheet;
LOCATION_GETS(upd_l, old_l);
max_eval = old_max_eval;
}
if (!orig_upd_clock) result = tcopy(cell->tok[CURR_VAL]);
/*}}}*/
return result;
}
/*}}}*/
/* update -- update all cells that need it */ /*{{{*/
void update(Sheet *sheet)
{
Location w;
Cell *cell;
int i,kp,iterating;
assert(sheet != (Sheet*)0);
kp = 0;
iterating = 0;
do
{
sheet->clk = 0;
if (iterating==1)
{
line_msg((const char*)0,_("Calculating running, press Escape to abort it"));
++iterating;
}
else if (iterating==0) ++iterating;
for (ALL_CELLS_IN_SHEET(sheet,i,cell))
{
if (cell && cell->clock_t2)
{
cell->updated = 0;
cell->clock_t0 = 1;
cell->clock_t1 = 1;
cell->clock_t2 = 0;
}
}
for (ALL_LOCS_IN_SHEET(sheet,w))
{
upd_clock = 1;
recompvalue(sheet, w);
}
for (ALL_CELLS_IN_SHEET(sheet,i,cell))
{
if (cell && cell->clock_t1)
{
tfree(&(cell->tok[CURR_VAL]));
cell->tok[CURR_VAL] = cell->tok[RES_VAL];;
cell->tok[RES_VAL].type = EMPTY;
cell->clock_t1 = 0;
}
}
upd_clock = 0;
} while (sheet->clk && !(kp=keypressed()));
if (iterating == 2) line_msg((const char*)0,kp ? _("Calculation aborted") : _("Calculation finished"));
sheet->clk = 0;
redraw_sheet(sheet);
}
/*}}}*/
/* geterror -- get malloc()ed error string */ /*{{{*/
char *geterror(Sheet *sheet, const Location at)
{
Token v;
assert(sheet!=(Sheet*)0);
if ((v = recompvalue(sheet,at)).type != EEK)
{
tfree(&v);
return NULL;
}
else return (v.u.err);
}
/*}}}*/
/* printvalue -- get ASCII representation of value */ /*{{{*/
void printvalue(char *s, size_t size, size_t chars, StringFormat sf,
FloatFormat ff, int precision, Sheet *sheet, const Location at)
{
assert(sheet != (Sheet*)0);
Token t = recompvalue(sheet, at);
printtok(s, size, chars, sf, ff, precision, TRUNCATED_ERROR, &t);
tfree(&t);
}
/*}}}*/
/* The cell field setters all have nearly identical code, so: */
#define DEFSETTER(funcname,argtype,field) bool\
funcname(Sheet *sheet, const Location at, argtype arg)\
{\
assert(sheet != (Sheet*)0);\
bool isnew = false;\
Cell* thecell = initcellofsheet(sheet, at, &isnew);\
if (isnew) sheet->changed = true;\
if (thecell->field != arg) {\
thecell->field = arg;\
sheet->changed = true;\
return true;\
}\
return isnew;\
}
DEFSETTER(setadjust, Adjust, adjust);
DEFSETTER(shadow, bool, shadowed);
DEFSETTER(bold, bool, bold);
DEFSETTER(underline, bool, underline);
DEFSETTER(lockcell, bool, locked);
DEFSETTER(maketrans, bool, transparent);
DEFSETTER(igncell, bool, ignored);
DEFSETTER(setfltformat, FloatFormat, fform);
DEFSETTER(setprecision, int, precision);
/* clk -- clock cell */ /*{{{*/
void clk(Sheet *sheet, const Location at)
{
assert(sheet != (Sheet*)0);
assert(IN_OCTANT(at));
assert(LOC_WITHIN(sheet,at));
if (CELL_AT(sheet,at))
{
CELL_AT(sheet,at)->clock_t2 = 1;
sheet->clk = 1;
}
}
/*}}}*/
/* getmarkstate -- find out where in the marking cycle the sheet is */ /*{{{*/
MarkState getmarkstate(Sheet *sheet) {
if (sheet == (Sheet*)0) return MARK_CYCLE;
return sheet->marking;
}
/* setlabel -- set cell label */ /*{{{*/
void setlabel(Sheet *sheet, const Location at, const char *buf, int update)
{
Cell *cell;
assert(sheet != (Sheet*)0);
sheet->changed = 1;
cell = initcellofsheet(sheet, at, NULL);
if (cell->label != (char*)0) free(cell->label);
if (*buf == '\0') cell->label = (char*)0;
else cell->label = strcpy(malloc(strlen(buf)+1), buf);
if (update)
{
cachelabels(sheet);
forceupdate(sheet);
}
}
/*}}}*/
/* findlabel -- return cell location for a given label */ /*{{{*/
Token findlabel(Sheet *sheet, const char *label)
{
/* variables */ /*{{{*/
Token result;
unsigned long hx;
struct Label *run;
/*}}}*/
assert(sheet!=(Sheet*)0);
/*
if (sheet==(Sheet*)0) run=(struct Label*)0;
else
*/
{
HASH(hx,label);
for (run=sheet->labelcache[(unsigned int)hx]; run!=(struct Label*)0 && strcmp(label,run->label); run=run->next);
}
if (run)
{
result.type = LOCATION;
LOCATION_GETS(result.u.location, run->location);
}
else
{
result.type = EEK;
char *pref = _("No such label: ");
result.u.err = malloc(strlen(pref) + strlen(label) + 1);
strcpy(result.u.err, pref);
strcat(result.u.err, label);
}
return result;
}
/*}}}*/
/* search and replace for labels in a token */
static void relabel_token(Token *tok, const char *oldl, const char *newl)
{
switch (tok->type)
{
case LIDENT:
if (strcmp(tok->u.lident, oldl) == 0)
{
tfree(tok);
tok->type = LIDENT;
tok->u.lident = strdup(newl);
}
break;
case FUNCALL:
for (int ai = 0; ai < tok->u.funcall.argc; ++ai)
relabel_token((tok->u.funcall.argv) + ai, oldl, newl);
break;
default:
break;
}
}
/* relabel -- search and replace for labels */ /*{{{*/
void relabel(Sheet *sheet, const Location at,
const char *oldlabel, const char *newlabel)
{
/* asserts */ /*{{{*/
assert(oldlabel!=(const char*)0);
assert(newlabel!=(const char*)0);
/*}}}*/
if (!LOC_WITHIN(sheet, at)) return;
Cell *cell = CELL_AT(sheet, at);
if (cell != NULLCELL)
for (TokVariety v = BASE_CONT; v <= MAX_PERSIST_TV; ++v)
relabel_token(cell->tok + v, oldlabel, newlabel);
}
/*}}}*/
/* savetbl -- save as tbl tyble */ /*{{{*/
const char *savetbl(Sheet *sheet, const char *name, int body,
const Location beg, const Location end,
unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp=(FILE*)0; /* cause run time error */
Location w;
char buf[1024];
char num[20];
char fullname[PATH_MAX];
Cell *cw;
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet!=(Sheet*)0);
assert(name!=(const char*)0);
/*}}}*/
*count=0;
w[X] = beg[X];
for (w[Z]=beg[Z]; w[Z]<=end[Z]; ++(w[Z]))
for (w[Y]=beg[Y]; w[Y]<=end[Y]; ++(w[Y]))
if (shadowed(CELL_AT(sheet,w))) return _("Shadowed cells in first column");
if (!body && (fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
for (w[Z]=beg[Z]; w[Z]<=end[Z]; ++(w[Z]))
{
if (body)
/* open new file */ /*{{{*/
{
sprintf(num, ".%d", w[Z]);
fullname[sizeof(fullname)-strlen(num)-1]='\0';
(void)strncpy(fullname,name,sizeof(fullname)-strlen(num)-1);
fullname[sizeof(fullname)-1]='\0';
(void)strncat(fullname,num,sizeof(fullname)-strlen(num)-1);
fullname[sizeof(fullname)-1]='\0';
if ((fp=fopen(fullname,"w"))==(FILE*)0) return strerror(errno);
}
/*}}}*/
else if (fputs_close(".TS\n",fp)==EOF) return strerror(errno);
for (w[Y]=beg[Y]; w[Y]<=end[Y]; ++(w[Y]))
{
/* print format */ /*{{{*/
if (w[Y] > beg[Y] && fputs_close(".T&\n",fp)==EOF) return strerror(errno);
for (w[X]=beg[X]; w[X]<=end[X]; ++(w[X]))
{
if (w[X] > beg [X] && fputc_close(' ',fp)==EOF) return strerror(errno);
cw = CELL_AT(sheet,w);
if (shadowed(cw) && fputc_close('s',fp)==EOF) return strerror(errno);
if (isbold(cw) && fputc_close('b',fp)==EOF) return strerror(errno);
if (underlined(cw) && fputc_close('u',fp)==EOF) return strerror(errno);
switch (getadjust(cw))
{
case LEFT: if (fputc_close('l',fp)==EOF) return strerror(errno); break;
case RIGHT: if (fputc_close('r',fp)==EOF) return strerror(errno); break;
case CENTER: if (fputc_close('c',fp)==EOF) return strerror(errno); break;
default: assert(0);
}
}
if (fputs_close(".\n",fp)==EOF) return strerror(errno);
/*}}}*/
/* print contents */ /*{{{*/
for (w[X]=beg[X]; w[X]<=end[X]; ++(w[X]))
{
cw = CELL_AT(sheet,w);
if (!shadowed(cw))
{
if (w[X] > beg[X] && fputc_close('\t',fp)==EOF) return strerror(errno);
if (cw != NULLCELL)
{
char *bufp;
printvalue(buf, sizeof(buf), 0, DIRECT_STRING, getfltformat(cw),
getprecision(cw), sheet, w);
if (transparent(cw))
{
if (fputs_close(buf,fp)==EOF) return strerror(errno);
}
else for (bufp=buf; *bufp; ++bufp) switch (*bufp)
{
case '\\':
{
if (fputc_close('\\',fp)==EOF || fputc_close('e',fp)==EOF) return strerror(errno);
break;
}
case '_':
{
if (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF || fputc_close('_',fp)==EOF) return strerror(errno);
break;
}
case '.':
{
if (w[X] == beg[X] && bufp==buf && (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF)) return strerror(errno);
if (fputc_close('.',fp)==EOF) return strerror(errno);
break;
}
case '\'':
{
if (w[X] == beg[X] && bufp==buf && (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF)) return strerror(errno);
if (fputc_close('\'',fp)==EOF) return strerror(errno);
break;
}
case '-':
{
if (*(bufp+1)=='-')
{
if (fputc_close('-',fp)==EOF) return strerror(errno);
else ++bufp;
}
else if (fputs_close("\\-",fp)==EOF) return strerror(errno);
break;
}
default: if (fputc_close(*bufp,fp)==EOF) return strerror(errno);
}
}
}
}
if (fputc_close('\n',fp)==EOF) return strerror(errno);
/*}}}*/
++*count;
}
if (!body)
{
if (fputs_close(".TE\n",fp)==EOF) return strerror(errno);
if (w[Z] < end[Z] && fputs_close(".bp\n",fp)==EOF) return strerror(errno);
}
if (body && fclose(fp)==EOF) return strerror(errno);
}
if (!body && fclose(fp)==EOF) return strerror(errno);
return (const char*)0;
}
/*}}}*/
/* savetext -- save as text */ /*{{{*/
const char *savetext(Sheet *sheet, const char *name,
const Location beg, const Location end,
unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp;
Location w;
Cell *cw;
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet != (Sheet*)0);
assert(name != (const char*)0);
/*}}}*/
*count=0;
w[X] = beg[X];
for (w[Z]=beg[Z]; w[Z]<=end[Z]; ++(w[Z]))
for (w[Y]=beg[Y]; w[Y]<=end[Y]; ++(w[Y]))
if (shadowed(CELL_AT(sheet,w))) return _("Shadowed cells in first column");
if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
for (w[Z]=beg[Z]; w[Z]<=end[Z]; ++(w[Z]))
{
for (w[Y]=beg[Y]; w[Y]<=end[Y]; ++(w[Y]))
{
size_t size,fill;
for (w[X]=beg[X]; w[X]<=end[X]; ++(w[X]))
{
size = cellwidth(sheet,w);
cw = CELL_AT(sheet,w);
if (cw != NULLCELL)
{
char *buf;
buf = malloc(size*UTF8SZ+1);
printvalue(buf, size*UTF8SZ + 1, size, DIRECT_STRING,
getfltformat(cw), getprecision(cw), sheet, w);
adjust(getadjust(cw), buf, size);
if (fputs_close(buf,fp)==EOF)
{
free(buf);
return strerror(errno);
}
for (fill=strlen(buf); fill<size; ++fill) if (fputc_close(' ',fp)==EOF)
{
free(buf);
return strerror(errno);
}
free(buf);
}
else
{
for (fill=0; fill<size; ++fill) if (fputc_close(' ',fp)==EOF) return strerror(errno);
}
++*count;
}
if (fputc_close('\n',fp)==EOF) return strerror(errno);
}
if (w[Z] < end[Z] && fputs_close("\f",fp)==EOF) return strerror(errno);
}
if (fclose(fp)==EOF) return strerror(errno);
return (const char*)0;
}
/*}}}*/
/* savecsv -- save as CSV */ /*{{{*/
const char *savecsv(Sheet *sheet, const char *name, char sep,
const Location beg, const Location end,
unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp;
Location w;
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet != (Sheet*)0);
assert(name != (const char*)0);
/*}}}*/
*count = 0;
w[X] = beg[X];
for (w[Z] = beg[Z]; w[Z] <= end[Z]; ++(w[Z]))
for (w[Y] = beg[Y]; w[Y] <= end[Y]; ++(w[Y]))
if (shadowed(CELL_AT(sheet,w))) return _("Shadowed cells in first column");
if ((fp = fopen(name,"w")) == (FILE*)0) return strerror(errno);
for (w[Z]=beg[Z]; w[Z]<=end[Z]; ++(w[Z]))
{
for (w[Y]=beg[Y]; w[Y]<=end[Y]; ++(w[Y]))
{
for (w[X]=beg[X]; w[X]<=end[X]; ++(w[X]))
{
if (w[X] > beg[X])
if (fputc_close(sep,fp) == EOF) return strerror(errno);
Cell *cw = CELL_AT(sheet,w);
if (cw != NULLCELL)
{
static const int bufchar = 511;
static const int bufsz = bufchar*UTF8SZ + 1;
char *buf = malloc(bufsz);
printvalue(buf, bufsz, bufchar, DIRECT_STRING, getfltformat(cw),
getprecision(cw), sheet, w);
bool needquote = !transparent(cw)
&& (strlen(buf) != strcspn(buf, "\",\n\r"));
if (needquote && fputc_close('"',fp) == EOF)
{
free(buf);
return strerror(errno);
}
for (const char *s = buf; *s; ++s)
{
if (fputc_close(*s,fp) == EOF
|| (*s=='"' && needquote && fputc_close(*s,fp) == EOF))
{
free(buf);
return strerror(errno);
}
}
free(buf);
if (needquote && fputc_close('"', fp) == EOF) return strerror(errno);
}
++*count;
}
if (fputc_close('\n', fp) == EOF) return strerror(errno);
}
if (w[Z] < end[Z] && fputc_close('\f', fp) == EOF) return strerror(errno);
}
if (fclose(fp) == EOF) return strerror(errno);
return (const char*)0;
}
/*}}}*/
static const char *saveport_contentleader[] =
{ [BASE_CONT] = ":", [ITER_CONT] = "\\\n", [ATTR_REF] = "\\\n"
};
/* saveport -- save as portable text */ /*{{{*/
const char *saveport(Sheet *sheet, const char *name, unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp;
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet != (Sheet*)0);
assert(name != (const char*)0);
/*}}}*/
*count = 0;
if ((fp = fopen(name,"w")) == (FILE*)0) return strerror(errno);
fprintf(fp,"# This is a work sheet generated with teapot %s.\n",VERSION);
for (int z = sheet->dimz - 1; z >= 0; --z)
{
for (int y = sheet->dimy - 1; y >= 0; --y)
{
for (int x = sheet->dimx - 1; x >= 0; --x)
{
if (y == 0 && columnwidth(sheet,x,z) != DEF_COLUMNWIDTH)
fprintf(fp,"W%d %d %d\n",x,z,columnwidth(sheet,x,z));
Cell *cell = CELL_ATC(sheet,x,y,z);
if (cell != NULLCELL)
{
fprintf(fp,"C%d %d %d ",x,y,z);
if (cell->adjust != AUTOADJUST)
fprintf(fp, "A%c ", Adjust_Char[cell->adjust]);
if (cell->label) fprintf(fp, "L%s ", cell->label);
if (cell->precision != -1)
fprintf(fp,"P%d ", cell->precision);
if (cell->fform != DEF_FLOATFORM)
fprintf(fp,"F%c ", FloatFormat_Char[cell->fform]);
bool needscolor = false;
for (ColorAspect a = FOREGROUND; a < NUM_COLOR_ASPECTS; ++a)
if (cell->aspect[a] != DefaultCN[a])
{ needscolor = true; break; }
if (needscolor)
{
fprintf(fp, "H%d ", NUM_COLOR_ASPECTS);
for (ColorAspect a = FOREGROUND; a < NUM_COLOR_ASPECTS; ++a)
fprintf(fp, "%d ", (int)cell->aspect[a]);
}
if (cell->shadowed) fprintf(fp,"S ");
if (cell->bold) fprintf(fp,"B ");
if (cell->underline) fprintf(fp,"U ");
if (cell->locked) fprintf(fp, "K ");
if (cell->transparent) fprintf(fp,"T ");
if (cell->ignored) fprintf(fp, "I ");
TokVariety lastv = MAX_PERSIST_TV;
while (lastv > BASE_CONT && cell->tok[lastv].type == EMPTY) --lastv;
for (TokVariety v = BASE_CONT; v <= lastv; ++v)
{
if (v == BASE_CONT && cell->tok[v].type == EMPTY) continue;
static const size_t bl = 4096;
char buf[bl];
if (fputs_close(saveport_contentleader[v], fp) == EOF)
return strerror(errno);
size_t used = printtok(buf, bl, 0, QUOTE_STRING, FLT_HEXACT,
-1, RESTORE_ERROR, cell->tok + v);
if (used > bl - 3) return _("Internal buffer overrun in saveport");
if (fputs_close(buf, fp) == EOF) return strerror(errno);
}
if (fputc_close('\n', fp) == EOF) return strerror(errno);
++(*count);
}
}
}
}
if (fclose(fp) == EOF) return strerror(errno);
sheet->changed = false;
return (const char*)0;
}
/*}}}*/
/* loadport -- load from portable text */ /*{{{*/
const char *loadport(Sheet *sheet, const char *name)
{
Cell loaded;
FILE *fp;
if ((fp=fopen(name,"r")) == (FILE*)0) return strerror(errno);
freesheet(sheet, 0);
static char errbuf[80];
const char *err = NULL;
int line = 1;
char buf[4096];
while (fgets(buf, sizeof(buf), fp) != NULL)
{
/* remove nl */ /*{{{*/
int width = strlen(buf);
if (width > 0 && buf[width-1] == '\n') buf[--width] = '\0';
/*}}}*/
switch (buf[0])
{
/* C -- parse cell */ /*{{{*/
case 'C':
{
TokVariety rv = BASE_CONT, nextrv = BASE_CONT;
Location loc;
if (width > 0 && buf[width-1]=='\\') { buf[--width]='\0'; ++nextrv; }
initcellcontents(&loaded);
/* parse x y and z */ /*{{{*/
const char *os = buf + 1;
char *ns = buf + 1;
loc[X] = strtol(os, &ns, 0);
if (os==ns)
{
sprintf(errbuf,_("Parse error for x position in line %d"),line);
err=errbuf;
goto eek;
}
while (*ns==' ') ++ns;
os=ns;
loc[Y] = strtol(os, &ns, 0);
if (os==ns)
{
sprintf(errbuf,_("Parse error for y position in line %d"),line);
err=errbuf;
goto eek;
}
while (*ns==' ') ++ns;
os=ns;
loc[Z] = strtol(os, &ns, 0);
if (os == ns)
{
sprintf(errbuf,_("Parse error for z position in line %d"),line);
err=errbuf;
goto eek;
}
/*}}}*/
/* parse optional attributes */ /*{{{*/
do
{
while (*ns==' ') ++ns;
switch (*ns)
{
/* A -- adjustment */ /*{{{*/
case 'A':
{
++ns;
const char *found = strchr(Adjust_Char, *ns);
if (found != NULL)
{
loaded.adjust = (Adjust)(found - Adjust_Char);
++ns;
break;
}
sprintf(errbuf, _("Parse error for adjustment in line %d"), line);
err = errbuf;
goto eek;
}
/*}}}*/
/* F -- float format */ /*{{{*/
case 'F':
{
++ns;
const char* found = strchr(FloatFormat_Char, *ns);
if (found != NULL)
{
loaded.fform = (FloatFormat)(found - FloatFormat_Char);
++ns;
break;
}
sprintf(errbuf,
_("Parse error for float format in line %d"), line);
err = errbuf;
goto eek;
}
/*}}}*/
/* L -- label */ /*{{{*/
case 'L':
{
char buf[1024],*p;
p=buf;
++ns;
while (*ns && *ns!=' ') { *p=*ns; ++p; ++ns; }
*p='\0';
loaded.label=strdup(buf);
break;
}
/*}}}*/
/* P -- precision */ /*{{{*/
case 'P':
{
os=++ns;
loaded.precision = strtol(os, &ns, 0);
if (os==ns)
{
sprintf(errbuf,_("Parse error for precision in line %d"),line);
err=errbuf;
goto eek;
}
break;
}
/*}}}*/
/* H -- hue (colors) */ /*{{{*/
case 'H':
{
os = ++ns;
size_t na = strtol(os, &ns, 0);
if (os == ns || na > NUM_COLOR_ASPECTS)
{
sprintf(errbuf, _("Parse error for number of hues in line %d"),
line);
err = errbuf;
goto eek;
}
for (size_t a = 0; a < na; ++a)
{
while (*ns && (*ns == ' ')) { ++ns; }
os = ns;
loaded.aspect[a] = strtol(os, &ns, 0);
if (os == ns)
{
sprintf(errbuf, _("Parse error in hue %d on line %d"),
a, line);
err = errbuf;
goto eek;
}
}
break;
}
/*}}}*/
/* S -- shadowed */ /*{{{*/
case 'S':
{
if (loc[X]==0)
{
sprintf(errbuf,_("Trying to shadow cell (%d,%d,%d) in line %d"),
loc[X],loc[Y],loc[Z],line);
err=errbuf;
goto eek;
}
++ns;
loaded.shadowed=1;
break;
}
/*}}}*/
/* U -- underline */ /*{{{*/
case 'U':
{
if (loc[X]==0)
{
sprintf(errbuf,_("Trying to underline cell (%d,%d,%d) in line %d"),
loc[X],loc[Y],loc[Z],line);
err=errbuf;
goto eek;
}
++ns;
loaded.underline=1;
break;
}
/*}}}*/
/* B -- bold */ /*{{{*/
case 'B':
{
if (loc[X]==0)
{
sprintf(errbuf,_("Trying to bold cell (%d,%d,%d) in line %d"),
loc[X], loc[Y], loc[Z], line);
err=errbuf;
goto eek;
}
++ns;
loaded.bold=1;
break;
}
/*}}}*/
/* E -- scientific, for backward compatibility */ /*{{{*/
case 'E':
{
++ns;
loaded.fform = FLT_SCIENTIFIC;
break;
}
/*}}}*/
/* K (formerly C) -- locked */ /*{{{*/
case 'C':
case 'K':
{
++ns;
loaded.locked=1;
break;
}
/*}}}*/
/* T -- transparent */ /*{{{*/
case 'T':
{
++ns;
loaded.transparent=1;
break;
}
/*}}}*/
/* I -- ignored */ /*{{{*/
case 'I':
{
++ns;
loaded.ignored=1;
break;
}
/*}}}*/
/* : \0 -- do nothing */ /*{{{*/
case ':':
case '\0': break;
/*}}}*/
/* default -- error */ /*{{{*/
default: sprintf(errbuf,_("Invalid option %c in line %d"),*ns,line); err=errbuf; goto eek;
/*}}}*/
}
} while (*ns!=':' && *ns!='\0');
/*}}}*/
if (*ns) ++ns;
/* convert remaining string(s) into token sequence */ /*{{{*/
while (true)
{
Token **t = scan(&ns);
if (t == EMPTY_TVEC)
{
sprintf(errbuf,_("Expression syntax error in line %d"),line);
err=errbuf;
goto eek;
}
Token pt = eval_safe(t, LITERAL);
tvecfree(t);
if (pt.type == EEK)
{
sprintf(errbuf, _("Parse error in line %d: %s"),
line, pt.u.err);
err = errbuf;
tfree(&pt);
goto eek;
}
loaded.tok[rv] = pt;
if (nextrv == rv) break;
rv = nextrv;
if (fgets(buf, sizeof(buf), fp) == (char*)0) break;
++line;
width = strlen(buf);
if (width>0 && buf[width-1]=='\n') buf[width-1]='\0';
/* More content? */
if (width > 0 && buf[width-1]=='\\') { buf[--width]='\0'; ++nextrv; }
ns = buf;
}
/*}}}*/
copycelltosheet(&loaded, sheet, loc, PRESERVE_LABEL);
break;
}
/*}}}*/
/* W -- column width */ /*{{{*/
case 'W':
{
/* parse x and z */ /*{{{*/
const char *os = buf+1;
char *ns = buf+1;
int cx = strtol(os, &ns, 0);
if (os==ns)
{
sprintf(errbuf,_("Parse error for x position in line %d"),line);
err=errbuf;
goto eek;
}
while (*ns==' ') ++ns;
os=ns;
int cz = strtol(os, &ns, 0);
if (os==ns)
{
sprintf(errbuf,_("Parse error for z position in line %d"),line);
err=errbuf;
goto eek;
}
/*}}}*/
/* parse width */ /*{{{*/
while (*ns==' ') ++ns;
os=ns;
width = strtol(os, &ns, 0);
if (os==ns)
{
sprintf(errbuf,_("Parse error for width in line %d"),line);
err=errbuf;
goto eek;
}
/*}}}*/
setwidth(sheet,cx,cz,width);
break;
}
/*}}}*/
/* # -- comment */ /*{{{*/
case '#': break;
/*}}}*/
/* default -- error */ /*{{{*/
default:
{
sprintf(errbuf,_("Unknown tag %c in line %d"),buf[0],line);
err=errbuf;
goto eek;
}
/*}}}*/
}
++line;
}
eek:
if (fclose(fp) == EOF) {
if (err == (const char*)0) err=strerror(errno);
} else {
sheet->changed = false;
}
cachelabels(sheet);
forceupdate(sheet);
redraw_sheet(sheet);
return err;
}
/*}}}*/
/* loadcsv -- load/merge CSVs */ /*{{{*/
const char *loadcsv(Sheet *sheet, const char *name)
{
/* variables */ /*{{{*/
FILE *fp;
Token **t;
const char *err;
Location where;
char ln[4096];
const char *str;
FltT value;
IntT lvalue;
int separator = 0;
/*}}}*/
if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno);
err=(const char*)0;
LOCATION_GETS(where, sheet->cur);
for (; fgets(ln,sizeof(ln),fp); ++(where[Y]))
{
const char *s;
const char *cend;
if (!separator) { /* FIXME: find a better way to autodetect */
int ccnt = 0, scnt = 0;
char *pos = ln;
while ((pos = strchr(pos, ','))) pos++, ccnt++;
pos = ln;
while ((pos = strchr(pos, ';'))) pos++, scnt++;
if (ccnt || scnt) separator = 1;
csv_setopt(scnt > ccnt);
}
s=cend=ln;
where[X] = sheet->cur[X];
do
{
Token t;
t.type = EMPTY;
lvalue = csv_long(s, &cend);
if (s != cend) /* ok, it is a integer */ /*{{{*/
{
t.type = INT;
t.u.integer = lvalue;
puttok(sheet, where, t, BASE_CONT);
}
/*}}}*/
else
{
value = csv_double(s, &cend);
if (s != cend) /* ok, it is a double */ /*{{{*/
{
t.type = FLOAT;
t.u.flt = value;
puttok(sheet, where, t, BASE_CONT);
}
/*}}}*/
else
{
str = csv_string(s, &cend);
if (s != cend) /* ok, it is a string */ /*{{{*/
{
t.type = STRING;
t.u.string = strdup(str);
puttok(sheet, where, t, BASE_CONT);
}
/*}}}*/
else
{
csv_separator(s,&cend);
while (s == cend && *s && *s!='\n')
{
err=_("unknown values ignored");
csv_separator(++s, &cend);
}
/* else it is nothing, which does not need to be stored :) */
}
}
}
} while (s != cend ? s = cend, ++(where[X]), 1 : 0);
}
fclose(fp);
return err;
}
/*}}}*/
/* insertcube -- insert a block */ /*{{{*/
void insertcube(Sheet *sheet, const Location beg, const Location end,
Direction ins)
{
/* variables */ /*{{{*/
int x,y,z;
/*}}}*/
switch (ins)
{
/* IN_X */ /*{{{*/
case IN_X:
{
int right;
right=sheet->dimx+end[X]-beg[X];
for (z=beg[Z]; z<=end[Z]; ++z) for (y=beg[Y]; y<=end[Y]; ++y) for (x=right; x>end[X]; --x)
{
resize(sheet, x, y, z, NULL);
CELL_ATC(sheet,x,y,z) = CELL_ATC(sheet,x-(end[X]-beg[X]+1),y,z);
CELL_ATC(sheet,x-(end[X]-beg[X]+1),y,z) = NULLCELL;
}
break;
}
/*}}}*/
/* IN_Y */ /*{{{*/
case IN_Y:
{
int down;
down=sheet->dimy+end[Y]-beg[Y];
for (z=beg[Z]; z<=end[Z]; ++z) for (x=beg[X]; x<=end[X]; ++x) for (y=down; y>end[Y]; --y)
{
resize(sheet, x, y, z, NULL);
CELL_ATC(sheet,x,y,z) = CELL_ATC(sheet,x,y-(end[Y]-beg[Y]+1),z);
CELL_ATC(sheet,x,y-(end[Y]-beg[Y]+1),z) = NULLCELL;
}
break;
}
/*}}}*/
/* IN_Z */ /*{{{*/
case IN_Z:
{
int bottom;
bottom=sheet->dimz+end[Z]-beg[Z];
for (y=beg[Y]; y<=end[Y]; ++y) for (x=beg[X]; x<=end[X]; ++x) for (z=bottom; z>end[Z]; --z)
{
resize(sheet, x, y, z, NULL);
CELL_ATC(sheet,x,y,z) = CELL_ATC(sheet,x,y,z-(end[Z]-beg[Z]+1));
CELL_ATC(sheet,x,y,z-(end[Z]-beg[Z]+1)) = NULLCELL;
}
break;
}
/*}}}*/
/* default */ /*{{{*/
default: assert(0);
/*}}}*/
}
sheet->changed=1;
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/
/* deletecube -- delete a block */ /*{{{*/
void deletecube(Sheet *sheet, const Location beg, const Location end,
Direction del)
{
/* variables */ /*{{{*/
Location w;
Location fm;
/*}}}*/
/* free cells in marked block */ /*{{{*/
for (w[X]=beg[X]; w[X]<=end[X]; ++w[X])
for (w[Y]=beg[Y]; w[Y]<=end[Y]; ++w[Y])
for (w[Z]=beg[Z]; w[Z]<=end[Z]; ++w[Z])
freecellofsheet(sheet, w);
/*}}}*/
switch (del)
{
/* IN_X */ /*{{{*/
case IN_X:
{
for (w[Z]=beg[Z]; w[Z]<=end[Z]; ++w[Z])
for (w[Y]=beg[Y]; w[Y]<=end[Y]; ++w[Y])
for (w[X]=beg[X]; w[X]<=sheet->dimx-(end[X]-beg[X]+1); ++w[X])
{
LOCATION_GETS(fm, w);
fm[X] += end[X]-beg[X]+1;
if (LOC_WITHIN(sheet,fm))
{
CELL_AT(sheet,w) = CELL_AT(sheet,fm);
CELL_AT(sheet,fm) = NULLCELL;
}
}
break;
}
/*}}}*/
/* IN_Y */ /*{{{*/
case IN_Y:
{
for (w[Z]=beg[Z]; w[Z]<=end[Z]; ++w[Z])
for (w[X]=beg[X]; w[X]<=end[X]; ++w[X])
for (w[Y]=beg[Y]; w[Y]<=sheet->dimy-(end[Y]-beg[Y]+1); ++w[Y])
{
LOCATION_GETS(fm, w);
fm[Y] += end[Y]-beg[Y]+1;
if (LOC_WITHIN(sheet,fm))
{
CELL_AT(sheet,w) = CELL_AT(sheet,fm);
CELL_AT(sheet,fm) = NULLCELL;
}
}
break;
}
/*}}}*/
/* IN_Z */ /*{{{*/
case IN_Z:
{
for (w[Y]=beg[Y]; w[Y]<=end[Y]; ++w[Y])
for (w[X]=beg[X]; w[X]<=end[X]; ++w[X])
for (w[Z]=beg[Z]; w[Z]<=sheet->dimz-(end[Z]-beg[Z]+1); ++w[Z])
{
LOCATION_GETS(fm, w);
fm[Z] += end[Z]-beg[Z]+1;
if (LOC_WITHIN(sheet,fm))
{
CELL_AT(sheet,w) = CELL_AT(sheet,fm);
CELL_AT(sheet,fm) = NULLCELL;
}
}
break;
}
/*}}}*/
/* default */ /*{{{*/
default: assert(0);
/*}}}*/
}
sheet->changed=1;
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/
/* moveblock -- move a block */ /*{{{*/
void moveblock(Sheet *sheet, const Location beg, const Location end,
const Location dest, int copy)
{
/* variables */ /*{{{*/
Location wid, dir, fm, to, get, go;
Dimensions dim;
/*}}}*/
if (SAME_LOC(beg, dest)) return;
LOCATION_GETS(wid, end);
LOCATION_SUB(wid, beg);
for (dim = X; dim < HYPER; ++dim) {
if (dest[dim] > beg[dim])
{ dir[dim] = -1; fm[dim] = wid[dim]; to[dim] = beg[dim]-1; }
else
{ dir[dim] = 1; fm[dim] = 0; to[dim] = beg[dim]+wid[dim]+1; }
}
for (get[Z] = beg[Z]+fm[Z], go[Z] = dest[Z]+fm[Z];
get[Z] != to[Z]; get[Z] += dir[Z], go[Z] += dir[Z])
for (get[Y] = beg[Y]+fm[Y], go[Y] = dest[Y]+fm[Y];
get[Y] != to[Y]; get[Y] += dir[Y], go[Y] += dir[Y])
for (get[X] = beg[X]+fm[X], go[X] = dest[X]+fm[X];
get[X] != to[X]; get[X] += dir[X], go[X] += dir[X])
{
if (!LOC_WITHIN(sheet, get)) freecellofsheet(sheet, go);
else if (copy)
copycelltosheet(CELL_AT(sheet, get), sheet, go, ALTER_LABEL);
else
{
resize(sheet, go[X], go[Y], go[Z], NULL);
CELL_AT(sheet, go) = CELL_AT(sheet, get);
CELL_AT(sheet, get) = NULLCELL;
}
}
sheet->changed = TRUE;
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/
/* sortblock -- sort a block */ /*{{{*/
/* Notes */ /*{{{*/
/*
The idea is to sort a block of cells in one direction by swapping the
planes which are canonical to the sort key vectors. An example is to
sort a two dimensional block line-wise with one column as sort key.
You can have multiple sort keys which all have the same direction and
you can sort a cube plane-wise.
*/
/*}}}*/
const char *sortblock(Sheet *sheet, const Location beg, const Location end,
Direction dir, Sortkey *sk, size_t sklen)
{
/* variables */ /*{{{*/
int x1, y1, z1, x2, y2, z2;
int x,y,z;
int incx=0,incy=0,incz=0;
int distx,disty,distz;
int i,r=-3,work;
bool norel = false;
/*}}}*/
/* unpack corners */
x1 = beg[X]; y1 = beg[Y]; z1 = beg[Z];
x2 = end[X]; y2 = end[Y]; z2 = end[Z];
/* asserts */ /*{{{*/
assert(sklen>0);
assert(x1>=0);
assert(x2>=0);
assert(y1>=0);
assert(y2>=0);
assert(z1>=0);
assert(z2>=0);
/*}}}*/
norel=0;
posorder(&x1,&x2);
posorder(&y1,&y2);
posorder(&z1,&z2);
distx=(x2-x1+1);
disty=(y2-y1+1);
distz=(z2-z1+1);
switch (dir)
{
case IN_X: incx=1; --x2; incy=0; incz=0; distx=1; break;
case IN_Y: incx=0; incy=1; --y2; incz=0; disty=1; break;
case IN_Z: incx=0; incy=0; incz=1; --z2; distz=1; break;
default: assert(0);
}
assert(incx || incy || incz);
do
{
work=0;
for (x=x1,y=y1,z=z1; x<=x2&&y<=y2&&z<=z2; x+=incx,y+=incy,z+=incz)
{
for (i=0; i<sklen; ++i)
{
r = cmpcell(sheet,x+sk[i].x,y+sk[i].y,z+sk[i].z,sheet,x+incx+sk[i].x,y+incy+sk[i].y,z+incz+sk[i].z,sk[i].sortkey);
if (r == NOT_COMPARABLE) norel = true;
else if (r==-1 || r==1) break;
else assert(r==0);
}
if (r==1)
{
swapblock(sheet,dir==IN_X ? x : x1,dir==IN_Y ? y : y1,dir==IN_Z ? z : z1,sheet,dir==IN_X ? x+incx : x1,dir==IN_Y ? y+incy : y1,dir==IN_Z ? z+incz : z1,distx,disty,distz);
work=1;
}
}
x2-=incx;
y2-=incy;
z2-=incz;
} while (work);
cachelabels(sheet);
forceupdate(sheet);
sheet->changed=1;
if (norel) return _("uncomparable elements");
else return (const char*)0;
}
/*}}}*/
/* mirrorblock -- mirror a block */ /*{{{*/
void mirrorblock(Sheet *sheet, const Location beg, const Location end,
Direction dir)
{
switch (dir)
{
case IN_X: /* left-right */ /*{{{*/
{
int x,middle=(end[X]-beg[X]+1)/2;
for (x=0; x<middle; ++x)
{
swapblock(sheet,beg[X]+x,beg[Y],beg[Z],sheet,end[X]-x,beg[Y],beg[Z], 1,end[Y]-beg[Y]+1,end[Z]-beg[Z]+1);
}
break;
}
/*}}}*/
case IN_Y: /* upside-down */ /*{{{*/
{
int y,middle=(end[Y]-beg[Y]+1)/2;
for (y=0; y<middle; ++y)
{
swapblock(sheet,beg[X],beg[Y]+y,beg[Z],sheet,beg[X],end[Y]-y,beg[Z], end[X]-beg[X]+1,1,end[Z]-beg[Z]+1);
}
break;
}
/*}}}*/
case IN_Z: /* front-back */ /*{{{*/
{
int z,middle=(end[Z]-beg[Z]+1)/2;
for (z=0; z<middle; ++z)
{
swapblock(sheet,beg[X],beg[Y],beg[Z]+z,sheet,beg[X],beg[Y],end[Z]-z, end[X]-beg[X]+1,end[Y]-beg[Y]+1,1);
}
break;
}
/*}}}*/
default: assert(0);
}
sheet->changed=1;
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/