ref: 7e3533cf6041b0c950a0161c11f391617f1aa21d
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;
};
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);
}
typedef struct Drawstate Drawstate;
struct Drawstate {
P firstcell;
int dcolwidth;
Rectangle r;
int leftpad;
int toppad;
};
void
initdrawstate(Drawstate *d)
{
d->firstcell.x = d->firstcell.y = 1;
d->dcolwidth = 100;
d->r = screen->r;
d->leftpad = 2;
d->toppad = 2;
}
P
getcelldim(Drawstate *d)
{
P p;
int x = Dx(d->r);
int y = Dy(d->r);
p.x = x / d->dcolwidth + 1;
p.y = y / font->height + 1;
return p;
}
Point
getcellpos(P cell, Drawstate *d)
{
P p;
p.x = cell.x - d->firstcell.x;
p.y = cell.y - d->firstcell.y;
return Pt(p.x * d->dcolwidth, p.y * font->height);
}
Point
getheadpos(int x, Drawstate *d)
{
return Pt(x * d->dcolwidth, - 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 = 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);
}
void
redraw(void)
{
P dim;
P first;
P cell;
int x, y;
int dx, dy;
Point p;
Point q;
Cell *c;
Response r;
char buf[10];
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(&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++)
for (y = first.y; y < first.y + dim.y; y++) {
cell.x = x;
cell.y = y;
c = getcell(cell);
if (c) {
r = getvalue(cell);
p = addpt(getcellpos(cell, &dstate), dstate.r.min);
p.x += dstate.leftpad;
p.y += dstate.toppad;
string(screen, p,
r.error ? colors.err : display->black,
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;
char *s;
Cell *c;
type = STRING;
s = value;
if (*value == '=') {
type = FUNCTION;
s += 1;
}
if (c = getcell(p)) {
if (c->type == type && strcmp(c->value, s) == 0)
return;
}
if (!s[0])
return;
addcell(p, s, type);
if (!updatecells()) {
rerrstr(errstring, ERRMAX);
error = errstring;
} else
error = nil;
dirty = 1;
redraw();
}
static void
edit(P p)
{
Cell *c;
char buf[512];
char addr[25];
int n;
c = getcell(p);
if (!c)
buf[0] = 0;
else {
switch (c->type) {
case FUNCTION:
*buf = '=';
strncpy(buf+1, c->value, sizeof(buf)-1);
break;
case STRING:
strncpy(buf, 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;
break;
}
if (n == 2) {
file = strdup(args[1]);
writefile(file);
dirty = 0;
break;
}
error = Ebadcmd;
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;
p.x = 0;
p.y = 0;
if (!ptinrect(xy, dstate.r)) {
return p;
}
xy = subpt(xy, dstate.r.min);
p.x = xy.x / dstate.dcolwidth;
p.y = xy.y / font->height;
p.x += dstate.firstcell.x;
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
main(int argc, char **argv)
{
int e;
ARGBEGIN{
case 'd':
debug++;
break;
case 'i':
interactive++;
break;
default:
usage();
break;
}ARGEND;
if (argc != 1)
usage();
file = *argv;
if (!inithoc())
sysfatal("%r");
if (!loadfile(file))
sysfatal("%r");
if (interactive) {
interactivehoc();
exits(nil);
}
if (initdraw(nil, nil, "spread") < 0) {
sysfatal("%r");
}
initcolors(&colors);
initdrawstate(&dstate);
eresized(0);
einit(Emouse|Ekeyboard);
for (;;) {
e = event(&ev);
switch (e) {
case Emouse:
processclick(ev);
break;
case Ekeyboard:
if (processkbd(ev))
exits(nil);
break;
}
}
}