shithub: spread

Download patch

ref: f929a0437dcb977136c0318ca851bfb49a254a51
parent: 39a4f1b284bab0a4587a845c53cdb53bcae625c8
author: sirjofri <sirjofri@sirjofri.de>
date: Thu Jul 4 10:17:22 EDT 2024

adds basic UI

--- a/README
+++ b/README
@@ -38,14 +38,31 @@
 
 ## GUI mode
 
-**TODO**
+### Keyboard control
 
-A few notes:
+- Use the arrow keys (←↑↓→) to scroll
+- Use `q` or `DEL` to quit
+- Every other key input [A-Za-z0-9] opens the command line
 
-- Cell syntax will be the same as in the files
-- Function/math cells will start with `=` for editing (output
-  is function result)
-- String cells will have no prefix; input and output are the same
+### Command line
+
+- `w [file]` - save to file (without file: save to original file)
+- `s addr` - open the edit dialog for cell addr (addr be like: B5)
+- `gg` - go to A1
+- `g addr` - go to addr. This scrolls so that addr is in the top left corner.
+
+### Edit dialog
+
+This is a simple text entry box for the specified cell.
+
+- To enter a function, start the line with an `=` sign.
+- To enter a string/text, don't start the line with an `=` sign.
+
+Simple as that.
+
+### TODO
+
+- click to edit. Click on a cell to open the edit dialog.
 
 ## CLI mode
 
--- a/engine.c
+++ b/engine.c
@@ -77,6 +77,15 @@
 	return r;
 }
 
+void
+freeresponse(Response *r)
+{
+	if (r->msg)
+		free(r->msg);
+	r->msg = nil;
+	r->error = 0;
+}
+
 Strchan *outchan;
 
 enum {
@@ -274,7 +283,7 @@
 		h = Hfunc;
 		break;
 	case STRING:
-		h = Hstring;
+		return;
 		break;
 	default:
 		sysfatal("code error");
@@ -371,6 +380,13 @@
 	return 1;
 }
 
+void
+updatecells()
+{
+	sortcells();
+	foreachcell(sendctohoc, nil);
+}
+
 static void
 writecell(Cell *c, void *aux)
 {
@@ -417,15 +433,21 @@
 }
 
 Response
-getvalue(char *cell)
+getvalue(P cell)
 {
+	Cell *c;
 	char *s;
 	Response o;
 	
-	toupperil(cell);
+	c = getcell(cell);
+	if (c && c->type == STRING) {
+		o.msg = strdup(c->value);
+		o.error = 0;
+		return o;
+	}
 	
 	o.msg = nil;
-	s = smprint("%s()\n", cell);
+	s = smprint("%s()\n", ptoa(cell));
 	hocwrite(s, &o);
 	free(s);
 	return o;
--- a/spread.c
+++ b/spread.c
@@ -1,5 +1,7 @@
 #include <u.h>
 #include <libc.h>
+#include <draw.h>
+#include <event.h>
 #include "spread.h"
 
 int debug = 0;
@@ -11,12 +13,313 @@
 	exits("usage");
 }
 
+typedef struct Colors Colors;
+struct Colors {
+	Image *bg;
+	Image *err;
+	Image *head;
+};
+
+void
+initcolors(Colors *c)
+{
+	c->bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 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);
+}
+
+typedef struct Drawstate Drawstate;
+struct Drawstate {
+	P firstcell;
+	int dcolwidth;
+	Rectangle r;
+};
+
+void
+initdrawstate(Drawstate *d)
+{
+	d->firstcell.x = d->firstcell.y = 1;
+	d->dcolwidth = 100;
+	d->r = screen->r;
+}
+
+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;
+
+void
+redraw(void)
+{
+	P dim;
+	P first;
+	P cell;
+	int x, y;
+	Point p;
+	Cell *c;
+	Response r;
+	char buf[10];
+	
+	dstate.r = insetrect(screen->r, 4);
+	dstate.r.min.y += font->height;
+	dstate.r.min.x += stringwidth(font, "88888888");
+	
+	first = dstate.firstcell;
+	
+	draw(screen, screen->r, colors.bg, nil, ZP);
+	dim = getcelldim(&dstate);
+	
+	for (x = first.x; x < first.x + dim.x; x++) {
+		cell.x = x;
+		cell.y = first.y - 1;
+		p = getcellpos(cell, &dstate);
+		/* for some reason, drawing ntoa(x) directly doesn't work */
+		snprint(buf, sizeof(buf), "%s", ntoa(x));
+		string(screen, addpt(p, dstate.r.min), 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 = getcellpos(cell, &dstate);
+		p.x -= stringwidth(font, buf);
+		string(screen, addpt(p, dstate.r.min), 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);
+				string(screen, addpt(getcellpos(cell, &dstate), dstate.r.min),
+					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;
+	
+	type = STRING;
+	s = value;
+	if (*value == '=') {
+		type = FUNCTION;
+		s += 1;
+	}
+	
+	addcell(p, s, type);
+	updatecells();
+	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);
+}
+
 char *file = nil;
 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) {
+			/* TODO: error, bad command */
+			fprint(2, "error: bad command\n");
+			break;
+		}
+		p = atop(args[1]);
+		if (p.x < 1) {
+			/* TODO: error, bad address */
+			fprint(2, "error: bad address\n");
+			break;
+		}
+		go(p);
+		break;
+	case 's': /* set command */
+		if (n != 2) {
+			/* TODO: error: bad command */
+			fprint(2, "error: bad command\n");
+			break;
+		}
+		p = atop(args[1]);
+		if (p.x < 1) {
+			/* TODO: error, bad address */
+			fprint(2, "error: bad address\n");
+			break;
+		}
+		edit(p);
+		break;
+	case 'w': /* write command */
+		if (n == 1) {
+			writefile(file);
+			break;
+		}
+		if (n == 2) {
+			writefile(args[1]);
+			break;
+		}
+		/* TODO: error, bad command */
+		fprint(2, "error: bad command\n");
+		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);
+	}
+	
+	return 0;
+
+Movement:
+	redraw();
+
+Out:
+	return 0;
+}
+
 void
 main(int argc, char **argv)
 {
+	int e;
+	
 	ARGBEGIN{
 	case 'd':
 		debug++;
@@ -47,9 +350,27 @@
 		exits(nil);
 	}
 	
-	Response r = getvalue("a4");
+	if (initdraw(nil, nil, "spread") < 0) {
+		sysfatal("%r");
+	}
 	
-	fprint(2, "value: A4='%s' (error=%d)\n", r.msg, r.error);
+	initcolors(&colors);
+	initdrawstate(&dstate);
 	
-	writefile("/tmp/testout");
+	eresized(0);
+	
+	einit(Emouse|Ekeyboard);
+	
+	for (;;) {
+		e = event(&ev);
+		
+		switch (e) {
+		case Emouse:
+			break;
+		case Ekeyboard:
+			if (processkbd(ev))
+				exits(nil);
+			break;
+		}
+	}
 }
--- a/spread.h
+++ b/spread.h
@@ -12,7 +12,8 @@
 void interactivehoc(void);
 int loadfile(char *file);
 int writefile(char *file);
-Response getvalue(char *cell);
+Response getvalue(P);
+void freeresponse(Response*);
 
 void addcell(P cell, char *value, int type);
 void rmcell(P cell);
@@ -21,10 +22,12 @@
 void dumpcells(void);
 void foreachcell(void (*f)(Cell*,void*), void*);
 void sortcells(void);
+void updatecells(void);
 
 void toupperil(char*);
 P atop(char*);
-char* ptoa(P); // resulting char* is mallocd
+char* ptoa(P); // resulting char* is not mallocd
+char* ntoa(int); // resulting char* is not mallocd
 
 #define PEQ(a, b) ((a).x == (b).x && (a).y == (b).y)
 
--- a/test/test.rc
+++ b/test/test.rc
@@ -14,6 +14,6 @@
 A3=5
 EOF
 
-../6.out -d /tmp/test.sp
+../6.out /tmp/test.sp
 prompt=('; ' '	')
 rc -i
--- a/util.c
+++ b/util.c
@@ -40,10 +40,10 @@
 }
 
 char*
-ptoa(P p)
+ntoa(int n)
 {
 	char ab[10];
-	char buf[25];
+	char buf[10];
 	int r;
 	int m = 'Z' - 'A' + 1;
 	char *a = ab;
@@ -50,11 +50,11 @@
 	char *b;
 	
 	do {
-		r = p.x%m;
+		r = n%m;
 		*a = r + 'A' - 1;
 		a++;
-		p.x /= m;
-	} while (p.x > 0 && a < (ab + sizeof(ab)));
+		n /= m;
+	} while (n > 0 && a < (ab + sizeof(ab)));
 	*a = 0;
 	
 	b = buf;
@@ -65,7 +65,14 @@
 		b++;
 	}
 	*b = 0;
+	return buf;
+}
+
+char*
+ptoa(P p)
+{
+	char buf[25];
 	
-	snprint(b, sizeof(buf) - (buf-b), "%d", p.y);
-	return strdup(buf);
+	snprint(buf, sizeof(buf), "%s%d", ntoa(p.x), p.y);
+	return buf;
 }