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 }