ref: a8558cb1e89245ecb38501b0fe4b64462ba69889
dir: /spread.c/
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include "spread.h"
int debug = 0;
void
usage(void)
{
fprint(2, "usage: %s [-di] [file]\n", argv0);
exits("usage");
}
char Ebadcmd[] = "bad command";
char Ebadaddr[] = "bad address";
typedef struct Colors Colors;
struct Colors {
Image *bg;
Image *err;
Image *head;
Image *lines;
Image *mtext;
};
void
initcolors(Colors *c)
{
c->bg = allocimagemix(display, DPaleyellow, DWhite);
c->err = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DRed);
c->head = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DBlue);
c->lines = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkyellow);
c->mtext = allocimagemix(display, DBlack, DPaleyellow);
}
typedef struct Drawstate Drawstate;
struct Drawstate {
P firstcell;
int dcolwidth;
Rectangle r;
int leftpad;
int toppad;
int mathmode;
int minwidth;
};
void
initdrawstate(Drawstate *d)
{
d->firstcell.x = d->firstcell.y = 1;
d->dcolwidth = 100;
d->r = screen->r;
d->leftpad = 2;
d->toppad = 2;
d->mathmode = 0;
d->minwidth = 20;
}
P
getcelldim(P first, Drawstate *d)
{
P p;
int nx, tx;
int x = Dx(d->r);
int y = Dy(d->r);
int f;
nx = 0;
f = first.x;
do {
tx = getwidth(f);
if (tx < d->minwidth)
tx = d->minwidth;
nx += tx;
f++;
} while (nx <= x);
p.x = f - first.x + 1;
p.y = y / font->height + 1;
return p;
}
Point
getcellpos(P cell, Drawstate *d)
{
Point pt;
int tw, x, y;
int c;
c = d->firstcell.x;
x = 0;
while (c < cell.x) {
tw = getwidth(c);
if (tw < d->minwidth)
tw = d->minwidth;
x += tw + d->leftpad*2;
c++;
}
y = cell.y - d->firstcell.y;
pt.y = y * font->height;
pt.x = x;
return pt;
}
Point
getheadpos(int x, Drawstate *d)
{
P p;
p.x = x;
p.y = 0;
return Pt(getcellpos(p, d).x, - font->height);
}
Drawstate dstate;
Colors colors;
Event ev;
char *file = nil;
int dirty = 0;
char *error = nil;
char errstring[ERRMAX] = "";
void
drawtopline(void)
{
char filebuf[25];
char *f;
Point p;
int w;
f = nil;
if (file) {
f = strrchr(file, '/');
f = f ? f+1 : file;
}
snprint(filebuf, sizeof(filebuf), "%s%s", f, dirty ? "*" : "");
p = addpt(screen->r.min, Pt(4, 4));
string(screen, p, display->black, ZP, font, filebuf);
if (!error)
return;
w = stringwidth(font, error);
p.x = screen->r.max.x - w - 4;
string(screen, p, colors.err, ZP, font, error);
}
static Image*
getcolor(Cell *c)
{
switch (c->type) {
case STRING:
return dstate.mathmode ? colors.mtext : display->black;
case FUNCTION:
default:
return display->black;
}
}
static void
drawcell(char *s, int align, Point p, int cw, Image *img)
{
int w;
switch (align) {
default:
case Aleft:
string(screen, p, img, ZP, font, s);
break;
case Adot:
case Aright:
w = stringwidth(font, s);
p.x += cw - w;
string(screen, p, img, ZP, font, s);
break;
case Acenter:
w = stringwidth(font, s);
p.x += (cw - w)/2;
string(screen, p, img, ZP, font, s);
break;
}
}
void
redraw(void)
{
P dim;
P first;
P cell;
int x, y;
int dx, dy;
Point p;
Point q;
Cell *c;
Image *img;
Response r;
char buf[10];
int cellwidth;
draw(screen, screen->r, colors.bg, nil, ZP);
drawtopline();
dstate.r = insetrect(screen->r, 4);
dstate.r.min.y += font->height * 2;
dstate.r.min.x += stringwidth(font, "88888888");
dx = Dx(dstate.r);
dy = Dy(dstate.r);
first = dstate.firstcell;
dim = getcelldim(first, &dstate);
for (x = first.x; x < first.x + dim.x; x++) {
cell.x = x;
cell.y = first.y - 1;
p = addpt(getcellpos(cell, &dstate), dstate.r.min);
/* for some reason, drawing ntoa(x) directly doesn't work */
snprint(buf, sizeof(buf), "%s", ntoa(x));
q = p;
q.y += dy + font->height;
line(screen, p, q, Endsquare, Endsquare, 0, colors.lines, ZP);
p.x += dstate.leftpad;
p.y += dstate.toppad;
string(screen, p, colors.head, ZP, font, buf);
}
for (y = first.y; y < first.y + dim.y; y++) {
cell.x = first.x;
cell.y = y;
snprint(buf, sizeof(buf), "%d", y);
p = addpt(getcellpos(cell, &dstate), dstate.r.min);
p.x -= stringwidth(font, buf) + 5;
q = p;
q.x += dx;
line(screen, p, q, Endsquare, Endsquare, 0, colors.lines, ZP);
p.x += dstate.leftpad;
p.y += dstate.toppad;
string(screen, p, colors.head, ZP, font, buf);
}
first = dstate.firstcell;
for (x = first.x; x < first.x + dim.x; x++) {
cellwidth = getwidth(x);
for (y = first.y; y < first.y + dim.y; y++) {
cell.x = x;
cell.y = y;
c = getcell(cell);
if (c) {
img = getcolor(c);
r = getvalue(cell);
p = addpt(getcellpos(cell, &dstate), dstate.r.min);
p.x += dstate.leftpad;
p.y += dstate.toppad;
drawcell(r.msg, c->align, p, cellwidth,
r.error ? colors.err : img);
/* string(screen, p,
r.error ? colors.err : img,
ZP, font, r.msg); */
freeresponse(&r);
}
}
}
}
void
eresized(int new)
{
if (new && getwindow(display, Refnone) < 0)
sysfatal("unable to reattach window: %r");
redraw();
}
static void
go(P p)
{
p.x = p.x < 1 ? 1 : p.x;
p.y = p.y < 1 ? 1 : p.y;
dstate.firstcell = p;
redraw();
}
static void
set(P p, char *value)
{
int type;
int align;
char *s;
Cell *c;
align = Aleft;
type = STRING;
s = value;
switch (*s) {
case '<':
align = Aleft;
s++;
break;
case '>':
align = Aright;
s++;
break;
case '|':
align = Acenter;
s++;
break;
case '.':
align = Adot;
s++;
break;
}
if (*s == '=') {
type = FUNCTION;
s++;
}
if (c = getcell(p)) {
if (c->type == type
&& strcmp(c->value, s) == 0
&& c->align == align)
return;
}
if (!s[0])
return;
addcell(p, s, type, align);
if (!updatecells()) {
rerrstr(errstring, ERRMAX);
error = errstring;
} else
error = nil;
dirty = 1;
redraw();
}
static void
edit(P p)
{
Cell *c;
char buf[512];
char *s;
char addr[25];
int n;
c = getcell(p);
if (!c)
buf[0] = 0;
else {
s = buf;
switch (c->align) {
case Aleft:
*s = '<';
s++;
break;
case Aright:
*s = '>';
s++;
break;
case Acenter:
*s = '|';
s++;
break;
case Adot:
*s = '.';
s++;
break;
}
switch (c->type) {
case FUNCTION:
*s = '=';
s++;
strncpy(s, c->value, sizeof(buf)-1);
break;
case STRING:
strncpy(s, c->value, sizeof(buf));
break;
}
}
snprint(addr, sizeof(addr), "%s", ptoa(p));
n = eenter(addr, buf, sizeof(buf), &ev.mouse);
if (n < 0)
return;
set(p, buf);
}
int interactive = 0;
static void
processcmd(char *s)
{
Point p;
char *args[5];
int n;
n = tokenize(s, args, sizeof(args));
switch (*args[0]) {
case 'g': /* go command */
if (args[0][1] == 'g') {
/* special case: gg sets position to A1 */
p.x = 1;
p.y = 1;
go(p);
break;
}
if (n != 2) {
error = Ebadcmd;
break;
}
p = atop(args[1]);
if (p.x < 1) {
error = Ebadaddr;
break;
}
go(p);
break;
case 's': /* set command */
if (n != 2) {
error = Ebadcmd;
break;
}
p = atop(args[1]);
if (p.x < 1) {
error = Ebadaddr;
break;
}
edit(p);
break;
case 'w': /* write command */
if (n == 1) {
writefile(file);
dirty = 0;
goto Savequit;
}
if (n == 2) {
file = strdup(args[1]);
writefile(file);
dirty = 0;
goto Savequit;
}
error = Ebadcmd;
break;
Savequit:
if (*(args[0]+1) == 'q')
exits(nil);
break;
case 'm': /* toggle math mode */
dstate.mathmode = !dstate.mathmode;
break;
}
}
enum {
Kup = 61454,
Kdown = 63488,
Kleft = 61457,
Kright = 61458,
Kdel = 127,
};
static int
processkbd(Event ev)
{
char cmd[128];
int n;
switch (ev.kbdc) {
case 'q':
case Kdel:
return 1;
case Kup:
if (dstate.firstcell.y == 1)
goto Out;
dstate.firstcell.y -= 1;
goto Movement;
case Kdown:
dstate.firstcell.y += 1;
goto Movement;
case Kleft:
if (dstate.firstcell.x == 1)
goto Out;
dstate.firstcell.x -= 1;
goto Movement;
case Kright:
dstate.firstcell.x += 1;
goto Movement;
}
// fprint(2, "kbd: %d\n", k);
// return 0;
if ( (ev.kbdc >= 'A' && ev.kbdc <= 'Z')
|| (ev.kbdc >= 'a' && ev.kbdc <= 'z')
|| (ev.kbdc >= '0' && ev.kbdc <= '9') ) {
cmd[0] = ev.kbdc;
cmd[1] = 0;
n = eenter("cmd:", cmd, sizeof(cmd), &ev.mouse);
if (n > 0)
processcmd(cmd);
}
Movement:
redraw();
Out:
return 0;
}
P clicktop(Point xy)
{
P p;
int x, tx;
p.x = 0;
p.y = 0;
if (!ptinrect(xy, dstate.r)) {
return p;
}
xy = subpt(xy, dstate.r.min);
p.x = dstate.firstcell.x;
x = 0;
do {
tx = getwidth(p.x);
if (tx < dstate.minwidth)
tx = dstate.minwidth;
x += tx + dstate.leftpad*2;
p.x++;
} while (x < xy.x);
p.x -= 1;
p.y = xy.y / font->height;
p.y += dstate.firstcell.y;
return p;
}
static void
processclick(Event ev)
{
P cell;
if (ev.mouse.buttons & 4) {
/* right click to edit */
cell = clicktop(ev.mouse.xy);
edit(cell);
return;
}
}
void
onexit(void)
{
teardown();
}
void
main(int argc, char **argv)
{
int e;
ARGBEGIN{
case 'd':
debug++;
break;
case 'i':
interactive++;
break;
default:
usage();
break;
}ARGEND;
file = (argc == 1) ? *argv : nil;
atexit(onexit);
if (!inithoc())
sysfatal("%r");
if (file)
if (!loadfile(file))
sysfatal("%r");
if (interactive) {
interactivehoc();
exits(nil);
}
if (initdraw(nil, nil, "spread") < 0) {
sysfatal("%r");
}
initcolors(&colors);
initdrawstate(&dstate);
updatecells();
eresized(0);
einit(Emouse|Ekeyboard);
for (;;) {
e = event(&ev);
switch (e) {
case Emouse:
processclick(ev);
break;
case Ekeyboard:
if (processkbd(ev))
exits(nil);
break;
}
}
}