ref: 1260c5c1735d5cf5a60890df7471bcd808b6231d
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 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; 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; } } }