/* #includes */ /*{{{C}}}*//*{{{*/ #ifdef DMALLOC #include "dmalloc.h" #endif #include #include #include #include #include #include #include #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; xoffchanged = 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; xdimx && zdimz) *(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; ilabelcache[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; ilabelcache[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; xdimx; ++x) for (z=0; zdimz; ++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 (xdimx && zdimz) 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 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; ichanged=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; xchanged=1; cachelabels(sheet); forceupdate(sheet); } /*}}}*/