shithub: spread

ref: a8558cb1e89245ecb38501b0fe4b64462ba69889
dir: /spread.c/

View raw version
#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;
		}
	}
}