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;
}