1896 lines
53 KiB
C
1896 lines
53 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 bool 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 %zu,%zu,%zu.\n",
|
|
x, y, z, sheet->dim[X], sheet->dim[Y], sheet->dim[Z]);
|
|
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: (%s)", TokVariety_Name[tv], Type_Name[c->tok[tv].type]);
|
|
printtok(buf, sizeof(buf), 0, QUOTE_STRING, FLT_COMPACT, -1,
|
|
VERBOSE_ERROR, c->tok + tv);
|
|
printf("%s.\n", buf);
|
|
}
|
|
if (c->label == NULL) printf("\n No label.\n");
|
|
else printf("\n Label: %s.\n", c->label);
|
|
if (c->updated) printf(" updated ");
|
|
if (c->locked) printf(" locked ");
|
|
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 ");
|
|
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, Location l1, Sheet *sheet2, Location l2,
|
|
Location dist)
|
|
{
|
|
assert(sheet1 != (Sheet*)0);
|
|
assert(sheet2 != (Sheet*)0);
|
|
resize(sheet1, l1[X]+dist[X]-1, l1[Y]+dist[Y]-1, l1[Z]+dist[Z]-1, NULL);
|
|
resize(sheet2, l2[X]+dist[X]-1, l2[Y]+dist[Y]-1, l2[Z]+dist[Z]-1, NULL);
|
|
Location off;
|
|
for (off[Z] = 0; off[Z] < dist[Z]; off[Z]++)
|
|
for (off[Y] = 0; off[Y] < dist[Y]; off[Y]++)
|
|
for (off[X] = 0; off[X] < dist[X]; off[X]++)
|
|
{
|
|
Location a; LOCATION_GETS(a,l1); LOCATION_ADD(a,off);
|
|
Cell *t = CELL_AT(sheet1, a);
|
|
Location b; LOCATION_GETS(b,l2); LOCATION_ADD(b,off);
|
|
CELL_AT(sheet1,a) = CELL_AT(sheet2,b);
|
|
CELL_AT(sheet2,b) = 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, const Location l1,
|
|
Sheet *sheet2, const Location l2, int sortkey)
|
|
{
|
|
int ascending = (sortkey & ASCENDING) ? 1 : -1;
|
|
Token leftval = gettok(safe_cell_at(sheet1, l1), CURR_VAL);
|
|
Token rightval = gettok(safe_cell_at(sheet2, l2), 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->dim[X] = sheet->dim[Y] = sheet->dim[Z] = 0;
|
|
sheet->sheet = (Cell**)0;
|
|
sheet->column = (ColWidT*)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, CoordT x, CoordT y, CoordT z, bool *qextended)
|
|
{
|
|
assert(x >= 0);
|
|
assert(y >= 0);
|
|
assert(z >= 0);
|
|
assert(sheet != (Sheet*)0);
|
|
|
|
if (!LOC_WITHINC(sheet,x,y,z))
|
|
{
|
|
Sheet dummy;
|
|
|
|
sheet->changed = true;
|
|
if (qextended) *qextended = true;
|
|
dummy.dim[X] = ((size_t)x >= sheet->dim[X] ? (size_t)(x+1) : sheet->dim[X]);
|
|
dummy.dim[Y] = ((size_t)y >= sheet->dim[Y] ? (size_t)(y+1) : sheet->dim[Y]);
|
|
dummy.dim[Z] = ((size_t)z >= sheet->dim[Z] ? (size_t)(z+1) : sheet->dim[Z]);
|
|
/* allocate new sheet */ /*{{{*/
|
|
dummy.sheet = malloc(dummy.dim[X]*dummy.dim[Y]*dummy.dim[Z]*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 ((size_t)x > sheet->dim[X] || (size_t)z >= sheet->dim[Z])
|
|
{
|
|
dummy.column = malloc(dummy.dim[X] * dummy.dim[Z] * sizeof(ColWidT));
|
|
for (x=0; (size_t)x < dummy.dim[X]; ++x)
|
|
for (z=0; (size_t)z < dummy.dim[Z]; ++z)
|
|
{
|
|
if ((size_t)x < sheet->dim[X] && (size_t)z < sheet->dim[Z])
|
|
*(dummy.column + ((size_t)x)*dummy.dim[Z] + (size_t)z)
|
|
= *(sheet->column + ((size_t)x)*sheet->dim[Z] + z);
|
|
else
|
|
*(dummy.column + ((size_t)x)*dummy.dim[Z] + (size_t)z)
|
|
= DEF_COLUMNWIDTH;
|
|
}
|
|
if (sheet->column != (ColWidT*)0) free(sheet->column);
|
|
sheet->column = dummy.column;
|
|
}
|
|
/*}}}*/
|
|
for (Dimensions dd = X; dd < HYPER; ++dd) sheet->dim[dd] = dummy.dim[dd];
|
|
}
|
|
}
|
|
/*}}}*/
|
|
|
|
/* 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 = 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)
|
|
{
|
|
assert(sheet != (Sheet*)0);
|
|
sheet->changed = FALSE;
|
|
Location w;
|
|
for (ALL_LOCS_IN_SHEET(sheet,w)) freecellofsheet(sheet,w);
|
|
if (all)
|
|
{
|
|
for (int i=0; i < LABEL_CACHE; ++i) /* free all buckets */ /*{{{*/
|
|
for (struct Label *run = sheet->labelcache[i]; run != (struct Label*)0;) {
|
|
struct Label *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 {
|
|
for (size_t x = 0; x < sheet->dim[X]; ++x)
|
|
for (size_t z = 0; z < sheet->dim[Z]; ++z)
|
|
*(sheet->column + x*sheet->dim[Z] + z) = DEF_COLUMNWIDTH;
|
|
cachelabels(sheet);
|
|
forceupdate(sheet);
|
|
}
|
|
}
|
|
/*}}}*/
|
|
|
|
/* forceupdate -- clear all clock and update flags */ /*{{{*/
|
|
void forceupdate(Sheet *sheet)
|
|
{
|
|
assert(sheet != (Sheet*)0);
|
|
if (sheet->sheet == (Cell **)0) return;
|
|
size_t i; Cell *cell;
|
|
for (ALL_CELLS_IN_SHEET(sheet,i,cell))
|
|
if (cell != NULLCELL) {
|
|
cell->updated = cell->clock_t0 = cell->clock_t1 = cell->clock_t2 = false;
|
|
tfree(&(cell->tok[STYLE_VAL]));
|
|
}
|
|
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 */ /*{{{*/
|
|
ColWidT columnwidth(Sheet *sheet, CoordT x, CoordT z)
|
|
{
|
|
assert(sheet!=(Sheet*)0);
|
|
if ((size_t)x < sheet->dim[X] && (size_t)z < sheet->dim[Z])
|
|
return (*(sheet->column + ((size_t)x)*sheet->dim[Z] + (size_t)z));
|
|
else return DEF_COLUMNWIDTH;
|
|
}
|
|
/*}}}*/
|
|
|
|
/* setwidth -- set width of column */ /*{{{*/
|
|
bool setwidth(Sheet *sheet, CoordT x, CoordT z, ColWidT width)
|
|
{
|
|
assert(sheet != (Sheet*)0);
|
|
bool isbigger = false;
|
|
resize(sheet, x, 1, z, &isbigger);
|
|
if (isbigger) sheet->changed = true;
|
|
ColWidT *storage = sheet->column + (size_t)x*sheet->dim[Z] + (size_t)z;
|
|
if (*storage != width) {
|
|
*storage = width;
|
|
sheet->changed = true;
|
|
return true;
|
|
}
|
|
return isbigger;
|
|
}
|
|
/*}}}*/
|
|
|
|
/* cellwidth -- get width of a cell */ /*{{{*/
|
|
ColWidT cellwidth(Sheet *sheet, const Location at)
|
|
{
|
|
if (shadowed(sheet,at)) return 0;
|
|
Location near; LOCATION_GETS(near,at);
|
|
ColWidT width = columnwidth(sheet,at[X],at[Z]);
|
|
for (near[X]++; shadowed(sheet,near);
|
|
width += columnwidth(sheet,near[X]++,near[Z]));
|
|
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;
|
|
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 */ /*{{{*/
|
|
bool orig_upd_clock = upd_clock;
|
|
if (cell->ignored)
|
|
{
|
|
tfree(cell->tok + CURR_VAL);
|
|
cell->updated = true;
|
|
}
|
|
else if (cell->updated == false)
|
|
{
|
|
/* 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))
|
|
{
|
|
cell->updated = true;
|
|
oldvalue = gettok(cell, CURR_VAL);
|
|
upd_clock = false;
|
|
cell->tok[CURR_VAL] = evaltoken(gettok(cell, CONTINGENT), FULL);
|
|
tfree(&oldvalue);
|
|
}
|
|
else if (upd_clock)
|
|
{
|
|
cell->updated = true;
|
|
upd_clock = false;
|
|
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;
|
|
}
|
|
/*}}}*/
|
|
|
|
/* evaluate_at -- evaluate a token in a sheet at a location */ /*{{{*/
|
|
Token evaluate_at(Token t, Sheet *sheet, const Location at)
|
|
{
|
|
assert(sheet != (Sheet *)0);
|
|
Sheet *oldsheet = upd_sheet;
|
|
upd_sheet = sheet;
|
|
Location old_l;
|
|
LOCATION_GETS(old_l, upd_l);
|
|
LOCATION_GETS(upd_l, at);
|
|
Token result = evaltoken(t, FULL);
|
|
LOCATION_GETS(upd_l, old_l);
|
|
upd_sheet = oldsheet;
|
|
return result;
|
|
}
|
|
/*}}}*/
|
|
|
|
/* update -- update all cells that need it */ /*{{{*/
|
|
void update(Sheet *sheet)
|
|
{
|
|
assert(sheet != (Sheet*)0);
|
|
int iterating = 0;
|
|
bool kp = false;
|
|
for (sheet->clk = true; sheet->sheet && sheet->clk && !kp; kp = keypressed())
|
|
{
|
|
sheet->clk = 0;
|
|
if (iterating == 1)
|
|
{
|
|
line_msg(NULL, _("Realculation running, press Escape to abort it"));
|
|
++iterating;
|
|
}
|
|
else if (iterating == 0) ++iterating;
|
|
Cell *cell;
|
|
size_t i;
|
|
for (ALL_CELLS_IN_SHEET(sheet,i,cell))
|
|
{
|
|
if (cell && cell->clock_t2)
|
|
{
|
|
cell->updated = false;
|
|
cell->clock_t0 = true;
|
|
cell->clock_t1 = true;
|
|
cell->clock_t2 = false;
|
|
tfree(&(cell->tok[STYLE_VAL]));
|
|
}
|
|
}
|
|
Location w;
|
|
for (ALL_LOCS_IN_SHEET(sheet,w))
|
|
{
|
|
upd_clock = true;
|
|
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 = false;
|
|
}
|
|
}
|
|
upd_clock = false;
|
|
}
|
|
if (iterating == 2)
|
|
line_msg(NULL, kp ? _("Calculation aborted") : _("Calculation finished"));
|
|
sheet->clk = false;
|
|
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 */ /*{{{*/
|
|
size_t printvalue(char *s, size_t size, size_t chars, StringFormat sf,
|
|
FloatFormat ff, PrecisionLevel precision,
|
|
Sheet *sheet, const Location at)
|
|
{
|
|
assert(sheet != (Sheet*)0);
|
|
Token t = recompvalue(sheet, at);
|
|
size_t retval =
|
|
printtok(s, size, chars, sf, ff, precision, TRUNCATED_ERROR, &t);
|
|
tfree(&t);
|
|
return retval;
|
|
}
|
|
/*}}}*/
|
|
|
|
/* getstyle -- get the current style token */
|
|
Style getstyle(Sheet *sheet, const Location at)
|
|
{
|
|
Token res;
|
|
res.type = STYLE;
|
|
clearstyle(&(res.u.style));
|
|
res.u.style.fform = DEF_FLOATFORM;
|
|
res.u.style.precision = def_precision;
|
|
if (!IN_OCTANT(at) || !CELL_IS_GOOD(sheet, at)) {
|
|
res.u.style.adjust = LEFT; return res.u.style;
|
|
}
|
|
Cell *cell = CELL_AT(sheet, at);
|
|
if (!(cell->updated)) recompvalue(sheet, at);
|
|
assert(cell->updated);
|
|
if (cell->tok[STYLE_VAL].type == STYLE) return cell->tok[STYLE_VAL].u.style;
|
|
Sheet *old_up_sh = upd_sheet;
|
|
Location old_upd_l; LOCATION_GETS(old_upd_l, upd_l);
|
|
upd_sheet = sheet;
|
|
LOCATION_GETS(upd_l, at);
|
|
Token tsty = evaltoken(cell->tok[STYLE_CONT], FULL);
|
|
LOCATION_GETS(upd_l, old_upd_l);
|
|
upd_sheet = old_up_sh;
|
|
Style sty;
|
|
switch (tsty.type) {
|
|
case STYLE: sty = tsty.u.style; break;
|
|
case EMPTY: sty = res.u.style; break;
|
|
default: {
|
|
char buf[4096];
|
|
const char *msg = _("Produced non-style value at &(%d,%d,%d) '");
|
|
sprintf(buf, msg, at[X], at[Y], at[Z]);
|
|
printtok(buf+strlen(buf), sizeof(buf)-strlen(buf)-1, 0, QUOTE_STRING,
|
|
FLT_COMPACT, -1, VERBOSE_ERROR, &tsty);
|
|
strcat(buf, "'");
|
|
line_msg(_("Cell style expr:"), msg);
|
|
sty = res.u.style;
|
|
break;
|
|
}
|
|
}
|
|
if (sty.adjust == AUTOADJUST)
|
|
sty.adjust = TOKISNUM(cell->tok[CURR_VAL]) ? RIGHT : LEFT;
|
|
if (sty.fform == FLT_NO_FORMAT) sty.fform = DEF_FLOATFORM;
|
|
if (sty.precision == NO_PRECISION) sty.precision = def_precision;
|
|
cell->tok[STYLE_VAL].type = STYLE;
|
|
cell->tok[STYLE_VAL].u.style = sty;
|
|
return sty;
|
|
}
|
|
|
|
/* getadjust -- get cell adjustment */
|
|
Adjust getadjust(Sheet *sheet, const Location at)
|
|
{
|
|
return getstyle(sheet, at).adjust;
|
|
}
|
|
|
|
/* shadowed -- is cell shadowed? */
|
|
bool shadowed(Sheet *sheet, const Location at)
|
|
{
|
|
return getstyle(sheet, at).shadowed;
|
|
}
|
|
|
|
/* isbold -- is cell bold? */
|
|
bool isbold(Sheet *sheet, const Location at)
|
|
{
|
|
return getstyle(sheet, at).bold;
|
|
}
|
|
|
|
/* getcolor -- a color associated to cell */
|
|
ColorNum getcolor(Sheet *sheet, const Location at, ColorAspect aspect)
|
|
{
|
|
return getstyle(sheet, at).aspect[aspect];
|
|
}
|
|
|
|
/* isunderline -- is cell underlined? */
|
|
bool underlined(Sheet *sheet, const Location at)
|
|
{
|
|
return getstyle(sheet, at).underline;
|
|
}
|
|
|
|
/* transparent -- is cell transparent? */
|
|
bool transparent(Sheet *sheet, const Location at)
|
|
{
|
|
return getstyle(sheet, at).transparent;
|
|
}
|
|
|
|
/* getfltformat -- float format for numbers */
|
|
FloatFormat getfltformat(Sheet *sheet, const Location at)
|
|
{
|
|
return getstyle(sheet, at).fform;
|
|
}
|
|
|
|
/* getprecision -- get cell precision */
|
|
PrecisionLevel getprecision(Sheet *sheet, const Location at)
|
|
{
|
|
return getstyle(sheet, at).precision;
|
|
}
|
|
|
|
/* overridestyle -- coerce the style of a cell to match sty */
|
|
bool overridestyle(Sheet *sheet, const Location at, Style sty) {
|
|
Style esty;
|
|
clearstyle(&esty);
|
|
if (style_equal(sty, esty)) return false;
|
|
assert(sheet != (Sheet*)0);
|
|
bool isnew = false;
|
|
Cell* thecell = initcellofsheet(sheet, at, &isnew);
|
|
if (isnew) sheet->changed = true;
|
|
Token stok = gettok(thecell, STYLE_CONT);
|
|
switch (stok.type) {
|
|
case EMPTY:
|
|
thecell->tok[STYLE_CONT].type = STYLE;
|
|
thecell->tok[STYLE_CONT].u.style = sty;
|
|
tfree(&(thecell->tok[STYLE_VAL]));
|
|
return true;
|
|
case STYLE: {
|
|
Token tsty; tsty.type = STYLE; tsty.u.style = sty;
|
|
thecell->tok[STYLE_CONT] = tadd(tsty, stok);
|
|
if (style_equal(stok.u.style, thecell->tok[STYLE_CONT].u.style))
|
|
return false;
|
|
tfree(&(thecell->tok[STYLE_VAL]));
|
|
return true;
|
|
}
|
|
default: break;
|
|
}
|
|
/* Create an expression that adds the prior expression to this style */
|
|
Token tsty; tsty.type = STYLE; tsty.u.style = sty;
|
|
Token fsty; fsty.type = FUNCALL;
|
|
fsty.u.funcall.fident = FUNC_PLUS_SYMBOL;
|
|
fsty.u.funcall.argc = 2;
|
|
fsty.u.funcall.argv = malloc(2*sizeof(Token));
|
|
fsty.u.funcall.argv[0] = tsty;
|
|
fsty.u.funcall.argv[1] = stok;
|
|
thecell->tok[STYLE_CONT] = fsty;
|
|
tfree(&(thecell->tok[STYLE_VAL]));
|
|
return true;
|
|
}
|
|
|
|
/* The cell field setters used to 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(lockcell, bool, locked);
|
|
DEFSETTER(igncell, bool, ignored);
|
|
|
|
#define DEFSTYVAL(funcname,argtype,field) bool\
|
|
funcname(Sheet *sheet, const Location at, argtype arg)\
|
|
{\
|
|
Style nsty; clearstyle(&nsty);\
|
|
nsty.field = arg;\
|
|
return overridestyle(sheet, at, nsty);\
|
|
}
|
|
|
|
DEFSTYVAL(setadjust, Adjust, adjust);
|
|
DEFSTYVAL(setfltformat, FloatFormat, fform);
|
|
DEFSTYVAL(setprecision, PrecisionLevel, precision);
|
|
|
|
#define DEFSTYFLAG(funcname,field) bool\
|
|
funcname(Sheet *sheet, const Location at, bool arg)\
|
|
{\
|
|
Style nsty; clearstyle(&nsty);\
|
|
nsty.field = arg;\
|
|
nsty.field##_set = true;\
|
|
return overridestyle(sheet, at, nsty);\
|
|
}
|
|
|
|
DEFSTYFLAG(shadow, shadowed);
|
|
DEFSTYFLAG(bold, bold);
|
|
DEFSTYFLAG(underline, underline);
|
|
DEFSTYFLAG(maketrans, transparent);
|
|
|
|
/* setcolor -- force the color of a cell to be as specified */
|
|
bool setcolor(Sheet *sheet, const Location at, ColorAspect asp, ColorNum col)
|
|
{
|
|
Style nsty; clearstyle(&nsty);
|
|
nsty.aspect[asp] = col;
|
|
return overridestyle(sheet, at, nsty);
|
|
}
|
|
|
|
/* 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 = true;
|
|
sheet->clk = true;
|
|
}
|
|
}
|
|
/*}}}*/
|
|
|
|
/* 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];
|
|
/*}}}*/
|
|
|
|
/* 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(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);
|
|
Style sw = getstyle(sheet,w);
|
|
if (sw.shadowed && fputc_close('s',fp)==EOF) return strerror(errno);
|
|
if (sw.bold && fputc_close('b',fp)==EOF) return strerror(errno);
|
|
if (sw.underline && fputc_close('u',fp)==EOF) return strerror(errno);
|
|
switch (sw.adjust)
|
|
{
|
|
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]))
|
|
{
|
|
Cell *cw = CELL_AT(sheet,w);
|
|
Style sw = getstyle(sheet,w);
|
|
if (!sw.shadowed)
|
|
{
|
|
if (w[X] > beg[X] && fputc_close('\t',fp)==EOF) return strerror(errno);
|
|
if (cw != NULLCELL)
|
|
{
|
|
printvalue(buf, sizeof(buf), 0, DIRECT_STRING, sw.fform,
|
|
sw.precision, sheet, w);
|
|
if (sw.transparent)
|
|
{
|
|
if (fputs_close(buf,fp)==EOF) return strerror(errno);
|
|
}
|
|
else for (char *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)
|
|
{
|
|
/* asserts */ /*{{{*/
|
|
assert(sheet != (Sheet*)0);
|
|
assert(name != (const char*)0);
|
|
/*}}}*/
|
|
|
|
*count=0;
|
|
Location w;
|
|
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(sheet,w)) return _("Shadowed cells in first column");
|
|
FILE *fp;
|
|
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);
|
|
Cell *cw = CELL_AT(sheet,w);
|
|
if (cw != NULLCELL)
|
|
{
|
|
char *buf = malloc(size*UTF8SZ+1);
|
|
Style sw = getstyle(sheet,w);
|
|
printvalue(buf, size*UTF8SZ + 1, size, DIRECT_STRING,
|
|
sw.fform, sw.precision, sheet, w);
|
|
adjust(sw.adjust, 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)
|
|
{
|
|
/* asserts */ /*{{{*/
|
|
assert(sheet != (Sheet*)0);
|
|
assert(name != (const char*)0);
|
|
/*}}}*/
|
|
|
|
*count = 0;
|
|
Location w;
|
|
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(sheet,w)) return _("Shadowed cells in first column");
|
|
FILE *fp;
|
|
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 size_t bufchar = 511;
|
|
static const size_t bufsz = bufchar*UTF8SZ + 1;
|
|
char *buf = malloc(bufsz);
|
|
Style sw = getstyle(sheet, w);
|
|
printvalue(buf, bufsz, bufchar, DIRECT_STRING, sw.fform,
|
|
sw.precision, sheet, w);
|
|
bool needquote = !(sw.transparent)
|
|
&& (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", [STYLE_CONT] = "\\\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 (CoordT z = (CoordT)(sheet->dim[Z] - 1); z >= 0; --z)
|
|
{
|
|
for (CoordT y = (CoordT)(sheet->dim[Y] - 1); y >= 0; --y)
|
|
{
|
|
for (CoordT x = (CoordT)(sheet->dim[X] - 1); x >= 0; --x)
|
|
{
|
|
if (y == 0 && columnwidth(sheet,x,z) != DEF_COLUMNWIDTH)
|
|
fprintf(fp,"W%d %d %zu\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->label) fprintf(fp, "L%s ", cell->label);
|
|
if (cell->locked) fprintf(fp, "K ");
|
|
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 */ /*{{{*/
|
|
size_t 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] = (CoordT)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] = (CoordT)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] = (CoordT)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 */ /*{{{*/
|
|
cleartoken(&(loaded.tok[STYLE_CONT]));
|
|
do
|
|
{
|
|
while (*ns==' ') ++ns;
|
|
switch (*ns)
|
|
{
|
|
/* A -- adjustment */ /*{{{*/
|
|
case 'A':
|
|
{
|
|
++ns;
|
|
const char *found = strchr(Adjust_Char, *ns);
|
|
if (found != NULL)
|
|
{
|
|
loaded.tok[STYLE_CONT].type = STYLE;
|
|
loaded.tok[STYLE_CONT].u.style.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.tok[STYLE_CONT].type = STYLE;
|
|
loaded.tok[STYLE_CONT].u.style.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 lbuf[1024];
|
|
char *p = lbuf;
|
|
++ns;
|
|
while (*ns && *ns!=' ') { *p = *ns; ++p; ++ns; }
|
|
*p = '\0';
|
|
loaded.label = strdup(lbuf);
|
|
break;
|
|
}
|
|
/*}}}*/
|
|
/* P -- precision */ /*{{{*/
|
|
case 'P':
|
|
{
|
|
os=++ns;
|
|
loaded.tok[STYLE_CONT].type = STYLE;
|
|
loaded.tok[STYLE_CONT].u.style.precision =
|
|
(PrecisionLevel)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 = (size_t)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;
|
|
}
|
|
loaded.tok[STYLE_CONT].type = STYLE;
|
|
for (unsigned int a = 0; a < na; ++a)
|
|
{
|
|
while (*ns && (*ns == ' ')) { ++ns; }
|
|
os = ns;
|
|
loaded.tok[STYLE_CONT].u.style.aspect[a] =
|
|
(ColorNum)strtol(os, &ns, 0);
|
|
if (os == ns)
|
|
{
|
|
sprintf(errbuf, _("Parse error in hue %ud 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.tok[STYLE_CONT].type = STYLE;
|
|
loaded.tok[STYLE_CONT].u.style.shadowed = true;
|
|
loaded.tok[STYLE_CONT].u.style.shadowed_set = true;
|
|
break;
|
|
}
|
|
/*}}}*/
|
|
/* U -- underline */ /*{{{*/
|
|
case 'U':
|
|
{
|
|
++ns;
|
|
loaded.tok[STYLE_CONT].type = STYLE;
|
|
loaded.tok[STYLE_CONT].u.style.underline = true;
|
|
loaded.tok[STYLE_CONT].u.style.underline_set = true;
|
|
break;
|
|
}
|
|
/*}}}*/
|
|
/* B -- bold */ /*{{{*/
|
|
case 'B':
|
|
{
|
|
++ns;
|
|
loaded.tok[STYLE_CONT].type = STYLE;
|
|
loaded.tok[STYLE_CONT].u.style.bold = true;
|
|
loaded.tok[STYLE_CONT].u.style.bold_set = true;
|
|
break;
|
|
}
|
|
/*}}}*/
|
|
/* E -- scientific, for backward compatibility */ /*{{{*/
|
|
case 'E':
|
|
{
|
|
++ns;
|
|
loaded.tok[STYLE_CONT].type = STYLE;
|
|
loaded.tok[STYLE_CONT].u.style.fform = FLT_SCIENTIFIC;
|
|
break;
|
|
}
|
|
/*}}}*/
|
|
/* K (formerly C) -- locked */ /*{{{*/
|
|
case 'C':
|
|
case 'K':
|
|
{
|
|
++ns;
|
|
loaded.locked=1;
|
|
break;
|
|
}
|
|
/*}}}*/
|
|
/* T -- transparent */ /*{{{*/
|
|
case 'T':
|
|
{
|
|
++ns;
|
|
loaded.tok[STYLE_CONT].type = STYLE;
|
|
loaded.tok[STYLE_CONT].u.style.transparent = true;
|
|
loaded.tok[STYLE_CONT].u.style.transparent_set = true;
|
|
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] = '\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;
|
|
CoordT cx = (CoordT)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;
|
|
CoordT cz = (CoordT)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;
|
|
ColWidT cwidth = (ColWidT)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, cwidth);
|
|
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);
|
|
return err;
|
|
}
|
|
/*}}}*/
|
|
|
|
/* loadcsv -- load/merge CSVs */ /*{{{*/
|
|
const char *loadcsv(Sheet *sheet, const char *name)
|
|
{
|
|
bool separator = false;
|
|
|
|
FILE *fp;
|
|
if ((fp = fopen(name,"r")) == (FILE*)0) return strerror(errno);
|
|
const char *err = NULL;
|
|
Location where; LOCATION_GETS(where, sheet->cur);
|
|
char ln[4096];
|
|
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 = true;
|
|
csv_setopt(scnt > ccnt);
|
|
}
|
|
|
|
s=cend=ln;
|
|
where[X] = sheet->cur[X];
|
|
do
|
|
{
|
|
Token t;
|
|
t.type = EMPTY;
|
|
|
|
IntT 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
|
|
{
|
|
FltT 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
|
|
{
|
|
const char *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 *sh, const Location beg, const Location end, Dimensions dm)
|
|
{
|
|
Location start; LOCATION_GETS(start,end);
|
|
Location fini; LOCATION_GETS(fini,beg);
|
|
start[dm] = (CoordT)(sh->dim[dm]) + end[dm] - beg[dm];
|
|
fini[dm] = end[dm]+1;
|
|
Location w;
|
|
for (w[Z] = start[Z]; w[Z] >= fini[Z]; w[Z]--)
|
|
for (w[Y] = start[Y]; w[Y] >= fini[Y]; w[Y]--)
|
|
for (w[X] = start[X]; w[X] >= fini[X]; w[X]--)
|
|
{
|
|
resize(sh, w[X], w[Y], w[Z], NULL);
|
|
Location from; LOCATION_GETS(from,w);
|
|
from[dm] -= end[dm]-beg[dm]+1;
|
|
CELL_AT(sh,w) = CELL_AT(sh,from);
|
|
CELL_AT(sh,from) = NULLCELL;
|
|
}
|
|
sh->changed = true;
|
|
cachelabels(sh);
|
|
forceupdate(sh);
|
|
}
|
|
/*}}}*/
|
|
|
|
/* deletecube -- delete a block */ /*{{{*/
|
|
void deletecube(Sheet *sh, const Location beg, const Location end, Dimensions dm)
|
|
{
|
|
Location w;
|
|
/* free cells in marked block */ /*{{{*/
|
|
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]++)
|
|
freecellofsheet(sh, w);
|
|
/*}}}*/
|
|
Location fini; LOCATION_GETS(fini, end);
|
|
fini[dm] = (CoordT)(sh->dim[dm]) - (end[dm]-beg[dm]+1);
|
|
for (w[Z] = beg[Z]; w[Z] <= fini[Z]; w[Z]++)
|
|
for (w[Y] = beg[Y]; w[Y] <= fini[Y]; w[Y]++)
|
|
for (w[X] = beg[X]; w[X] <= fini[X]; w[X]++)
|
|
{
|
|
Location fm; LOCATION_GETS(fm, w);
|
|
fm[dm] += end[dm]-beg[dm]+1;
|
|
if (LOC_WITHIN(sh,fm)) {
|
|
CELL_AT(sh,w) = CELL_AT(sh,fm); CELL_AT(sh,fm) = NULLCELL;
|
|
}
|
|
}
|
|
sh->changed = true;
|
|
cachelabels(sh);
|
|
forceupdate(sh);
|
|
}
|
|
/*}}}*/
|
|
|
|
/* 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,
|
|
Dimensions dm, Sortkey *sk, size_t sklen)
|
|
{
|
|
assert(sklen > 0);
|
|
|
|
Location wbeg, wend;
|
|
LOCATION_GETS(wbeg, beg);
|
|
LOCATION_GETS(wend, end);
|
|
|
|
poscorners(wbeg, wend);
|
|
|
|
Location dist; LOCATION_GETS(dist, wend);
|
|
LOCATION_SUB(dist, wbeg);
|
|
Location one = {1, 1, 1};
|
|
LOCATION_ADD(dist, one);
|
|
|
|
Location inc; OLOCATION(inc);
|
|
inc[dm] = 1; wend[dm]--; dist[dm] = 1;
|
|
|
|
bool norel = false;
|
|
bool work;
|
|
do
|
|
{
|
|
work = false;
|
|
Location w;
|
|
for (LOCATION_GETS(w,wbeg); LOCATION_LEQ(w,wend); LOCATION_ADD(w,inc)) {
|
|
int r = -3;
|
|
for (size_t i = 0; i < sklen; ++i)
|
|
{
|
|
Location here, there;
|
|
LOCATION_GETS(here, w);
|
|
LOCATION_ADD(here, sk[i].soff);
|
|
LOCATION_GETS(there, here);
|
|
LOCATION_ADD(there, inc);
|
|
r = cmpcell(sheet, here, sheet, there, sk[i].sortkey);
|
|
if (r == NOT_COMPARABLE) norel = true;
|
|
else if (r == -1 || r == 1) break;
|
|
else assert(r == 0);
|
|
}
|
|
if (r == 1)
|
|
{
|
|
Location la; LOCATION_GETS(la,wbeg); la[dm] = w[dm];
|
|
Location lb; LOCATION_GETS(lb,wbeg); lb[dm] = w[dm]+inc[dm];
|
|
swapblock(sheet, la, sheet, lb, dist);
|
|
work = true;
|
|
}
|
|
}
|
|
LOCATION_SUB(wend, inc);
|
|
} while (work);
|
|
cachelabels(sheet);
|
|
forceupdate(sheet);
|
|
sheet->changed = true;
|
|
if (norel) return _("uncomparable elements");
|
|
else return NULL;
|
|
}
|
|
/*}}}*/
|
|
|
|
/* mirrorblock -- mirror a block */ /*{{{*/
|
|
void mirrorblock(Sheet *sh, const Location beg, const Location end, Dimensions dm)
|
|
{
|
|
Location dist; LOCATION_GETS(dist, end); LOCATION_SUB(dist, beg);
|
|
Location one = {1, 1, 1}; LOCATION_ADD(dist, one);
|
|
for (CoordT c = 0; c < (end[dm]-beg[dm]+1)/2; ++c) {
|
|
Location la; LOCATION_GETS(la, beg); la[dm] += c;
|
|
Location lb; LOCATION_GETS(lb, end); lb[dm] -= c;
|
|
swapblock(sh, la, sh, lb, dist);
|
|
}
|
|
sh->changed = true;
|
|
cachelabels(sh);
|
|
forceupdate(sh);
|
|
}
|
|
/*}}}*/
|