stew

a monorepo of some sort
git clone git://git.nsmpr.xyz/stew.git
Log | Files | Refs

tabul.c (12272B)


      1 /*
      2 	Primitive TSV spreadsheet editor
      3 
      4 	TODO:
      5 	- get some kind of refcount system for cell data instead of strdup`ing
      6 	- look at optimizing drawing routines
      7 */
      8 
      9 #include <u.h>
     10 #include <libc.h>
     11 #include <bio.h>
     12 #include <String.h>
     13 #include <thread.h>
     14 #include <draw.h>
     15 #include <keyboard.h>
     16 #include <mouse.h>
     17 
     18 #define CellWidth 8 /* in characters */
     19 
     20 typedef struct Array Array;
     21 struct Array {
     22 	long n;
     23 	void **p;
     24 	void (*freep)(void *);
     25 	void *aux;
     26 };
     27 
     28 Array * createarray(void (*freep)(void *));
     29 void freearray(void *);
     30 void astore(Array *, int, void *);
     31 void * afetch(Array *, int);
     32 void atrim(Array *ar);
     33 
     34 Array *view;
     35 
     36 Array * readtable(int);
     37 int writetable(Array *, int);
     38 char * tfetch(Array *, Point);
     39 void tstore(Array *, Point, char *);
     40 void cleartable(Array *, Rectangle);
     41 Array * duptable(Array *, Rectangle);
     42 Rectangle pastetable(Array *, Array *, Point);
     43 
     44 Image *bord, *text, *bg, *curbg;
     45 Rectangle cell, blank;
     46 Mousectl *mctl;
     47 Keyboardctl *kctl;
     48 Mouse mv;
     49 int rv[2];
     50 Rune kv;
     51 char file[256];
     52 char *snarf = "/tmp/tabsnarf";
     53 
     54 struct {
     55 	Rectangle r;
     56 	struct {
     57 		int s;
     58 		int e;
     59 	} sel;
     60 	String *str;
     61 } edit;
     62 
     63 void flushedit(void);
     64 void setedit(void);
     65 void drawedit(void);
     66 
     67 struct {
     68 	Rectangle r;
     69 	Rectangle sel;
     70 	Point scroll;
     71 	Point cur;
     72 } cells;
     73 
     74 void _cell(Image *, Rectangle, Image *, Image *, Image *, int, Font *, char *);
     75 void snarfsel(void);
     76 void pastesel(void);
     77 
     78 void
     79 loadfile(char *file)
     80 {
     81 	int fd = open(file, OREAD);
     82 	if (fd < 0) {
     83 		fprint(2, "loadfile: %r\n");
     84 		return;
     85 	}
     86 	Array *t = readtable(fd);
     87 	close(fd);
     88 	if (t != nil) {
     89 		freearray(view);
     90 		view = t;
     91 	}
     92 }
     93 
     94 void
     95 savefile(char *file)
     96 {
     97 	int fd = create(file, OWRITE, 0666);
     98 	if (fd < 0) {
     99 		fprint(2, "savefile: %r\n");
    100 		return;
    101 	}
    102 	writetable(view, fd);
    103 	close(fd);
    104 }
    105 
    106 void
    107 snarfsel(void)
    108 {
    109 	int fd = create(snarf, OWRITE, 0666);
    110 	if (fd < 0) {
    111 		fprint(2, "snarfsel: %r");
    112 		return;
    113 	}
    114 	Array *t = duptable(view, cells.sel);
    115 	writetable(t, fd);
    116 	freearray(t);
    117 	close(fd);
    118 }
    119 
    120 void
    121 pastesel(void)
    122 {
    123 	int fd = open(snarf, OREAD);
    124 	if (fd < 0) {
    125 		fprint(2, "pastesel: %r");
    126 		return;
    127 	}
    128 	Array *t = readtable(fd);
    129 	cells.sel = pastetable(view, t, cells.cur);
    130 	cells.cur = cells.sel.min;
    131 	
    132 	close(fd);
    133 }
    134 
    135 void
    136 _cell(Image *screen, Rectangle r, Image *brd, Image *bg, Image *fg, int t, Font *font, char *text)
    137 {
    138 	Rectangle r2 = insetrect(r, t);
    139 	draw(screen, r, brd, nil, ZP);
    140 	draw(screen, r2, bg, nil, ZP);
    141 	if (text != nil) {
    142 		int n = strlen(text);
    143 		while (stringnwidth(font, text, n) > Dx(r2)) n--;
    144 		stringn(screen, addpt(r2.min, Pt(1,(Dy(r2) - font->height)/2)), fg, ZP, font, text, n);
    145 	}
    146 }
    147 
    148 void
    149 drawcell(Point xy)
    150 {
    151 	Image *cellbg = bg;
    152 	char *s = "";
    153 	int t = 1;
    154 	if (eqpt(xy, cells.cur) != 0) t = 2;
    155 	if (ptinrect(xy, cells.sel) != 0) cellbg = curbg;
    156 	Rectangle r = rectaddpt(cell, addpt(Pt(
    157 	    (xy.x - cells.scroll.x) * cell.max.x,
    158 	    (xy.y - cells.scroll.y) * cell.max.y), cells.r.min));
    159 	if (ptinrect(r.min, cells.r) == 1) {
    160 		char *v = tfetch(view, xy);
    161 		if (v != nil) s = v;
    162 		_cell(screen, r, bord, cellbg, text, t, font, s);
    163 	}
    164 }
    165 
    166 void
    167 resize(void)
    168 {
    169 	edit.r = Rpt(
    170 	  screen->r.min,
    171 	  Pt(screen->r.max.x, screen->r.min.y + font->height)
    172 	);
    173 	blank = rectaddpt(cell, addpt(screen->r.min, Pt(0, Dy(edit.r))));
    174 	cells.r = Rpt(
    175 	  blank.max,
    176 	  screen->r.max
    177 	);
    178 }
    179 
    180 void
    181 drawcolumns(void)
    182 {
    183 	char buf[9];
    184 	buf[8] = '\0';
    185 	Rectangle r = rectaddpt(blank, Pt(Dx(cell), 0));
    186 	int x;
    187 	for (
    188 	  x = cells.scroll.x;
    189 	  rectXrect(r, screen->r) != 0;
    190 	  x++, r = rectaddpt(r, Pt(Dx(cell), 0))) {
    191 		snprint(buf, 8, "%d", x);
    192 		_cell(screen, r, bord, curbg, text, 1, font, buf);
    193 	}
    194 }
    195 
    196 void
    197 drawrows(void)
    198 {
    199 	char buf[9];
    200 	int y;
    201 	buf[8] = '\0';
    202 	Rectangle r = rectaddpt(blank, Pt(0, Dy(cell)));
    203 	for (
    204 	  y = cells.scroll.y;
    205 	  rectXrect(r, screen->r) != 0;
    206 	  y++, r = rectaddpt(r, Pt(0, Dy(cell)))) {
    207 		snprint(buf, 8, "%d", y);
    208 		_cell(screen, r, bord, curbg, text, 1, font, buf);
    209 	}
    210 }
    211 
    212 void
    213 drawcells(void)
    214 {
    215 	int x, y, maxx, maxy;
    216 	maxx = Dx(cells.r) / cell.max.x;
    217 	maxy = Dy(cells.r) / cell.max.y;
    218 	for (x = 0; x <= maxx; x++) for (y = 0; y <= maxy; y++)
    219 		drawcell(Pt(x + cells.scroll.x, y + cells.scroll.y));
    220 }
    221 
    222 void
    223 redraw(void)
    224 {
    225 	drawedit();
    226 	draw(screen, blank, curbg, nil, ZP);
    227 	drawcolumns();
    228 	drawrows();
    229 	drawcells();
    230 }
    231 
    232 void
    233 colors(void)
    234 {
    235 	Image *black, *white, *gray;
    236 	black = display->black;
    237 	white = display->white;
    238 	gray = allocimage(display, Rect(0,0,1,1), XRGB32, 1, 0xddddddff);
    239 
    240 	bord = black;
    241 	text = black;
    242 	bg = white;
    243 	curbg = gray;
    244 }
    245 
    246 void
    247 mouse(Mouse m)
    248 {
    249 	enum { MFREE, MSELECT, MSCROLL, };
    250 	static state = MFREE;
    251 	static Point startxy, startsel, scroll;
    252 	if (m.buttons == 0) {
    253 		state = MFREE;
    254 	}
    255 	if ((m.buttons == 1) && (ptinrect(m.xy, cells.r) != 0)) {
    256 		Point mr = subpt(m.xy, cells.r.min);
    257 		Point newcur = addpt(Pt(mr.x / cell.max.x, mr.y / cell.max.y), cells.scroll);
    258 		if (state != MSELECT) {
    259 			state = MSELECT;
    260 			startsel = newcur;
    261 		}
    262 		cells.sel = canonrect(Rpt(startsel, newcur));
    263 		cells.sel.max = addpt(cells.sel.max, Pt(1, 1));
    264 		if ((cells.cur.x != newcur.x) || (cells.cur.y != newcur.y)) {
    265 			//Point oldcur = cells.cur;
    266 			flushedit();
    267 			cells.cur = newcur;
    268 			//drawcell(oldcur);
    269 			//drawcell(newcur);
    270 			drawcells();
    271 			setedit();
    272 			drawedit();
    273 			flushimage(display, 1);
    274 		}
    275 	} else if (m.buttons == 2) {
    276 		if (state != MSCROLL) {
    277 			state = MSCROLL;
    278 			startxy = m.xy;
    279 			scroll = cells.scroll;
    280 		}
    281 		Point diff = subpt(startxy, m.xy);
    282 		diff.x /= Dx(cell);
    283 		diff.y /= Dy(cell);
    284 		Point newscroll = addpt(scroll, diff);
    285 		if (newscroll.x < 0) newscroll.x = 0;
    286 		if (newscroll.y < 0) newscroll.y = 0;
    287 		if (eqpt(cells.scroll, newscroll) != 1) {
    288 			cells.scroll = newscroll;
    289 			drawcolumns();
    290 			drawrows();
    291 			drawcells();
    292 			flushimage(display, 1);	
    293 		}
    294 	} else if (m.buttons == 4) {
    295 		static char *items[] = {"cut", "paste", "snarf", "load", "save", "exit", nil};
    296 		static Menu menu = {
    297 			items,
    298 			nil,
    299 			0,
    300 		};
    301 		switch (menuhit(3, mctl, &menu, nil)) {
    302 		case 0: /* cut */
    303 			snarfsel();
    304 			cleartable(view, cells.sel);
    305 			setedit();
    306 			redraw();
    307 			flushimage(display, 1);
    308 			break;
    309 		case 1: /* paste */
    310 			pastesel();
    311 			setedit();
    312 			redraw();
    313 			flushimage(display, 1);
    314 			break;
    315 		case 2: /* snarf */
    316 			snarfsel();
    317 			break;
    318 		case 3: /* load */
    319 			if (enter("load", file, 1024, mctl, kctl, nil) > 0) {
    320 				loadfile(file);
    321 				redraw();
    322 				flushimage(display, 1);	
    323 			}
    324 			break;
    325 		case 4: /* save */
    326 			if (enter("save", file, 1024, mctl, kctl, nil) > 0) {
    327 				savefile(file);
    328 			}
    329 			break;
    330 		case 5: /* exit */
    331 			threadexitsall(nil);
    332 			break;
    333 		}
    334 	}
    335 }
    336 
    337 void
    338 kbd(Rune r)
    339 {
    340 	switch (r) {
    341 	case 0x7f: /* del */
    342 		// TODO: should reset cursor to original state?
    343 		break;
    344 	case 0x0a: /* newline */
    345 		flushedit();
    346 		cells.cur.y++;
    347 		cells.sel = Rpt(cells.cur, addpt(Pt(1, 1), cells.cur));
    348 		setedit();
    349 		drawedit();
    350 		//drawcell(cells.cur);
    351 		//drawcell(subpt(cells.cur, Pt(0,1)));
    352 		drawcells();
    353 		flushimage(display, 1);
    354 		break;
    355 	case 0x09: /* tab */
    356 		flushedit();
    357 		cells.cur.x++;
    358 		cells.sel = Rpt(cells.cur, addpt(Pt(1, 1), cells.cur));
    359 		setedit();
    360 		drawedit();
    361 		//drawcell(cells.cur);
    362 		//drawcell(subpt(cells.cur, Pt(1,0)));
    363 		drawcells();
    364 		flushimage(display, 1);
    365 		break;
    366 	case 0x08: /* backspace */
    367 		// TODO: make utf8-complaint, make thread safe?
    368 		if (edit.str->ptr > edit.str->base) edit.str->ptr--;
    369 		s_terminate(edit.str);
    370 		flushedit();
    371 		drawcell(cells.cur);
    372 		drawedit();
    373 		flushimage(display, 1);
    374 		break;
    375 	default:
    376 		s_putc(edit.str, r);
    377 		s_terminate(edit.str);
    378 		flushedit();
    379 		drawcell(cells.cur);
    380 		drawedit();
    381 		flushimage(display, 1);
    382 	}
    383 }
    384 
    385 void
    386 usage(void)
    387 {
    388 	fprint(2, "usage: %s [tsv_file]\n", argv0);
    389 	threadexitsall("usage");
    390 }
    391 
    392 void
    393 init(void)
    394 {
    395 	if (initdraw(nil, nil, "tabul") == 0) sysfatal("%r");
    396 	colors();
    397 	mctl = initmouse(nil, screen);
    398 	kctl = initkeyboard(nil);
    399 	cell = Rect(0, 0, stringwidth(font, "0") * CellWidth + 4, font->height + 4);
    400 	edit.str = s_new();
    401 	cells.cur = ZP;
    402 	cells.scroll = ZP;
    403 	cells.sel = Rect(0, 0, 1, 1);
    404 	setedit();
    405 	resize();
    406 	redraw();
    407 	flushimage(display, 1);	
    408 }
    409 
    410 void
    411 threadmain(int argc, char **argv)
    412 {
    413 	ARGBEGIN {
    414 	default: usage();
    415 	} ARGEND;
    416 	file[0] = '\0';
    417 	if (argc == 1) {
    418 		loadfile(argv[0]);
    419 		strncat(file, argv[0], 256);
    420 	} else view = createarray(freearray);
    421 	init();
    422 	Alt alts[] = {
    423 		{mctl->c, &mv, CHANRCV},
    424 		{mctl->resizec, rv, CHANRCV},
    425 		{kctl->c, &kv, CHANRCV},
    426 		{0, 0, CHANEND},
    427 	};
    428 	for(;;){
    429 		switch(alt(alts)) {
    430 		case 0: /* mouse */
    431 			mouse(mv);
    432 			break;
    433 		case 1: /* resize */
    434 			if (getwindow(display, Refnone) < 0) sysfatal("%r");
    435 			resize();
    436 			redraw();
    437 			flushimage(display, 1);	
    438 			break;
    439 		case 2: /* keyboard */
    440 			kbd(kv);
    441 			break;
    442 		}
    443 	}
    444 }
    445 
    446 void
    447 flushedit(void)
    448 {
    449 	if (strlen(s_to_c(edit.str)) == 0) tstore(view, cells.cur, nil);
    450 	else tstore(view, cells.cur, s_to_c(edit.str));
    451 }
    452 
    453 void
    454 setedit(void)
    455 {
    456 	char *v = tfetch(view, cells.cur);
    457 	s_reset(edit.str);
    458 	if (v != nil) s_append(edit.str, v);
    459 }
    460 
    461 void
    462 drawedit(void)
    463 {
    464 	char buf[256];
    465 	snprint(buf, 16, "[%d %d] ", cells.cur.x, cells.cur.y);
    466 	if (edit.str != nil) {
    467 		strncat(buf, s_to_c(edit.str), 256 - strlen(buf));
    468 	}
    469 	_cell(screen, edit.r, bord, curbg, text, 1, font, buf);
    470 }
    471 
    472 Array *
    473 duptable(Array *t, Rectangle r)
    474 {
    475 	assert(t != nil);
    476 	Array *n = createarray(freearray);
    477 	int x, y;
    478 	for (x = r.min.x; x < r.max.x; x++)
    479 	for (y = r.min.y; y < r.max.y; y++) {
    480 		char *s = tfetch(t, Pt(x, y));
    481 		tstore(n, subpt(Pt(x, y), r.min), s);
    482 	}
    483 	return n;
    484 }
    485 
    486 void
    487 cleartable(Array *t, Rectangle r)
    488 {
    489 	assert(t != nil);
    490 	int x, y;
    491 	for (x = r.min.x; x < r.max.x; x++)
    492 	for (y = r.min.y; y < r.max.y; y++) {
    493 		tstore(t, Pt(x, y), nil);
    494 	}
    495 }
    496 
    497 Rectangle 
    498 pastetable(Array *to, Array *fr, Point sp)
    499 {
    500 	assert(to != nil);
    501 	assert(fr != nil);
    502 	Rectangle r;
    503 	int x, y;
    504 	r.min = ZP;
    505 	r.max.x = 0;
    506 	r.max.y = fr->n;
    507 	for (y = 0; y < fr->n; y++) {
    508 		Array *row = afetch(fr, y);
    509 		if ((row != nil) && (row->n > r.max.x)) {
    510 			r.max.x = row->n;
    511 		}
    512 	}
    513 	r = rectaddpt(r, sp);
    514 	for (x = r.min.x; x < r.max.x; x++)
    515 	for (y = r.min.y; y < r.max.y; y++) {
    516 		char *s = tfetch(fr, subpt(Pt(x, y), r.min));
    517 		tstore(to, Pt(x, y), s);
    518 	}
    519 	return r;
    520 }
    521 
    522 char *
    523 tfetch(Array *t, Point xy)
    524 {
    525 	Array *row = afetch(t, xy.y);
    526 	if (row == nil) return nil;
    527 	return afetch(row, xy.x);
    528 }
    529 
    530 void
    531 tstore(Array *t, Point xy, char *s)
    532 {
    533 	Array *row = afetch(t, xy.y);
    534 	if (row == nil) row = createarray(free);
    535 	char *col = afetch(row, xy.x);
    536 	free(col);
    537 	if (s != nil) s = strdup(s);
    538 	astore(row, xy.x, s);
    539 	astore(t, xy.y, row);
    540 }
    541 
    542 Array *
    543 readtable(int fd)
    544 {
    545 	Array *t = mallocz(sizeof(Array), 1);
    546 	char *s, *args[64];
    547 	int n, x, y = 0;
    548 	fd = dup(fd, -1);
    549 	Biobuf *b = Bfdopen(fd, OREAD);
    550 	if (b == nil) {
    551 		fprint(2, "readtable: %r\n");
    552 		return nil;
    553 	}
    554 	while( (s = Brdstr(b, '\n', 1)) != nil) {
    555 		n = getfields(s, args, 64, 0, "\t");
    556 		for (x = 0; x < n; x++) {
    557 			if (strlen(args[x]) != 0) tstore(t, Pt(x, y), args[x]);
    558 		};
    559 		free(s);
    560 		y++;
    561 	}
    562 	Bterm(b);
    563 	return t;
    564 }
    565 
    566 int
    567 writetable(Array *t, int fd)
    568 {
    569 	fd = dup(fd, -1);
    570 	Biobuf *b = Bfdopen(fd, OWRITE);
    571 	int x, y;
    572 	for (y = 0; y < t->n; y++) {
    573 		Array *row = afetch(t, y);
    574 		if (row != nil) {
    575 			atrim(row);
    576 			if (row->n == 0) {
    577 				freearray(row);
    578 				astore(t, y, nil);
    579 			}
    580 		}
    581 	}
    582 	atrim(t);
    583 
    584 	for (y = 0; y < t->n; y++) {
    585 		Array *row = afetch(t, y);
    586 		if (row != nil) for (x = 0; x < row->n; x++) {
    587 			char *s = afetch(row, x);
    588 			if (x != 0) Bprint(b, "\t");
    589 			if (s != nil) Bprint(b, "%s", s);
    590 		}
    591 		Bprint(b, "\n");
    592 	}
    593 	Bterm(b);
    594 	return 0;
    595 }
    596 
    597 Array *
    598 createarray(void (*freep)(void *))
    599 {
    600 	Array *new = mallocz(sizeof(Array), 1);
    601 	new->freep = freep;
    602 	return new;
    603 }
    604 
    605 void
    606 freearray(void *v)
    607 {
    608 	if (v == nil) return;
    609 	Array *ar = v;
    610 	if (ar->freep != nil) {
    611 		int i;
    612 		for (i = 0; i < ar->n; i++) ar->freep(ar->p[i]);
    613 	}
    614 	free(ar);
    615 }
    616 
    617 void
    618 astore(Array *ar, int n, void *p)
    619 {
    620 	assert(ar != nil);
    621 	assert(n >= 0);
    622 	if (n >= ar->n) {
    623 		ar->p = realloc(ar->p, sizeof(void *) * (n + 1));
    624 		int i;
    625 		for (i = ar->n; i < n; i++) ar->p[i] = nil;
    626 		ar->n = n + 1;
    627 	}
    628 	ar->p[n] = p;
    629 }
    630 
    631 void *
    632 afetch(Array *ar, int n)
    633 {
    634 	assert(ar != nil);
    635 	assert(n >= 0);
    636 	if (n < ar->n) {
    637 		return ar->p[n];
    638 	}
    639 	else return nil;
    640 }
    641 
    642 void
    643 atrim(Array *ar)
    644 {
    645 	assert(ar != nil);
    646 	int n;
    647 	for (n = ar->n - 1; n >= 0; n--) {
    648 		if (ar->p[n] != nil) {
    649 			ar->n = n + 1;
    650 			return;
    651 		}
    652 	}
    653 	ar->n = 0;
    654 	free(ar->p);
    655 	ar->p = nil;
    656 }