wind.c (14905B)
1 #include <u.h> 2 #include <libc.h> 3 #include <draw.h> 4 #include <thread.h> 5 #include <cursor.h> 6 #include <mouse.h> 7 #include <keyboard.h> 8 #include <frame.h> 9 #include <fcall.h> 10 #include <plumb.h> 11 #include <libsec.h> 12 #include "dat.h" 13 #include "fns.h" 14 15 int winid; 16 17 void 18 wininit(Window *w, Window *clone, Rectangle r) 19 { 20 Rectangle r1, br; 21 File *f; 22 Reffont *rf; 23 Rune *rp; 24 int nc; 25 26 w->tag.w = w; 27 w->taglines = 1; 28 w->tagexpand = TRUE; 29 w->body.w = w; 30 w->id = ++winid; 31 incref(&w->ref); 32 if(globalincref) 33 incref(&w->ref); 34 w->ctlfid = ~0; 35 w->utflastqid = -1; 36 r1 = r; 37 38 w->tagtop = r; 39 w->tagtop.max.y = r.min.y + font->height; 40 r1.max.y = r1.min.y + w->taglines*font->height; 41 42 incref(&reffont.ref); 43 f = fileaddtext(nil, &w->tag); 44 textinit(&w->tag, f, r1, &reffont, tagcols); 45 w->tag.what = Tag; 46 /* tag is a copy of the contents, not a tracked image */ 47 if(clone){ 48 textdelete(&w->tag, 0, w->tag.file->b.nc, TRUE); 49 nc = clone->tag.file->b.nc; 50 rp = runemalloc(nc); 51 bufread(&clone->tag.file->b, 0, rp, nc); 52 textinsert(&w->tag, 0, rp, nc, TRUE); 53 free(rp); 54 filereset(w->tag.file); 55 textsetselect(&w->tag, nc, nc); 56 } 57 r1 = r; 58 r1.min.y += w->taglines*font->height + 1; 59 if(r1.max.y < r1.min.y) 60 r1.max.y = r1.min.y; 61 f = nil; 62 if(clone){ 63 f = clone->body.file; 64 w->body.org = clone->body.org; 65 w->isscratch = clone->isscratch; 66 rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name); 67 }else 68 rf = rfget(FALSE, FALSE, FALSE, nil); 69 f = fileaddtext(f, &w->body); 70 w->body.what = Body; 71 textinit(&w->body, f, r1, rf, textcols); 72 r1.min.y -= 1; 73 r1.max.y = r1.min.y+1; 74 draw(screen, r1, tagcols[BORD], nil, ZP); 75 textscrdraw(&w->body); 76 w->r = r; 77 br.min = w->tag.scrollr.min; 78 br.max.x = br.min.x + Dx(button->r); 79 br.max.y = br.min.y + Dy(button->r); 80 draw(screen, br, button, nil, button->r.min); 81 w->filemenu = TRUE; 82 w->maxlines = w->body.fr.maxlines; 83 w->autoindent = globalautoindent; 84 if(clone){ 85 w->dirty = clone->dirty; 86 w->autoindent = clone->autoindent; 87 textsetselect(&w->body, clone->body.q0, clone->body.q1); 88 winsettag(w); 89 } 90 } 91 92 /* 93 * Draw the appropriate button. 94 */ 95 void 96 windrawbutton(Window *w) 97 { 98 Image *b; 99 Rectangle br; 100 101 b = button; 102 if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache)) 103 b = modbutton; 104 br.min = w->tag.scrollr.min; 105 br.max.x = br.min.x + Dx(b->r); 106 br.max.y = br.min.y + Dy(b->r); 107 draw(screen, br, b, nil, b->r.min); 108 } 109 110 int 111 delrunepos(Window *w) 112 { 113 Rune *r; 114 int i; 115 116 r = parsetag(w, 0, &i); 117 free(r); 118 i += 2; 119 if(i >= w->tag.file->b.nc) 120 return -1; 121 return i; 122 } 123 124 void 125 movetodel(Window *w) 126 { 127 int n; 128 129 n = delrunepos(w); 130 if(n < 0) 131 return; 132 moveto(mousectl, addpt(frptofchar(&w->tag.fr, n), Pt(4, w->tag.fr.font->height-4))); 133 } 134 135 /* 136 * Compute number of tag lines required 137 * to display entire tag text. 138 */ 139 int 140 wintaglines(Window *w, Rectangle r) 141 { 142 int n; 143 Rune rune; 144 Point p; 145 146 if(!w->tagexpand && !w->showdel) 147 return 1; 148 w->showdel = FALSE; 149 w->tag.fr.noredraw = 1; 150 textresize(&w->tag, r, TRUE); 151 w->tag.fr.noredraw = 0; 152 w->tagsafe = FALSE; 153 154 if(!w->tagexpand) { 155 /* use just as many lines as needed to show the Del */ 156 n = delrunepos(w); 157 if(n < 0) 158 return 1; 159 p = subpt(frptofchar(&w->tag.fr, n), w->tag.fr.r.min); 160 return 1 + p.y / w->tag.fr.font->height; 161 } 162 163 /* can't use more than we have */ 164 if(w->tag.fr.nlines >= w->tag.fr.maxlines) 165 return w->tag.fr.maxlines; 166 167 /* if tag ends with \n, include empty line at end for typing */ 168 n = w->tag.fr.nlines; 169 if(w->tag.file->b.nc > 0){ 170 bufread(&w->tag.file->b, w->tag.file->b.nc-1, &rune, 1); 171 if(rune == '\n') 172 n++; 173 } 174 if(n == 0) 175 n = 1; 176 return n; 177 } 178 179 int 180 winresize(Window *w, Rectangle r, int safe, int keepextra) 181 { 182 int oy, y, mouseintag, mouseinbody; 183 Point p; 184 Rectangle r1; 185 186 mouseintag = ptinrect(mouse->xy, w->tag.all); 187 mouseinbody = ptinrect(mouse->xy, w->body.all); 188 189 /* tagtop is first line of tag */ 190 w->tagtop = r; 191 w->tagtop.max.y = r.min.y+font->height; 192 193 r1 = r; 194 r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height); 195 196 /* If needed, recompute number of lines in tag. */ 197 if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){ 198 w->taglines = wintaglines(w, r); 199 r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height); 200 } 201 202 /* If needed, resize & redraw tag. */ 203 y = r1.max.y; 204 if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){ 205 textresize(&w->tag, r1, TRUE); 206 y = w->tag.fr.r.max.y; 207 windrawbutton(w); 208 w->tagsafe = TRUE; 209 210 /* If mouse is in tag, pull up as tag closes. */ 211 if(mouseintag && !ptinrect(mouse->xy, w->tag.all)){ 212 p = mouse->xy; 213 p.y = w->tag.all.max.y-3; 214 moveto(mousectl, p); 215 } 216 217 /* If mouse is in body, push down as tag expands. */ 218 if(mouseinbody && ptinrect(mouse->xy, w->tag.all)){ 219 p = mouse->xy; 220 p.y = w->tag.all.max.y+3; 221 moveto(mousectl, p); 222 } 223 } 224 225 /* If needed, resize & redraw body. */ 226 r1 = r; 227 r1.min.y = y; 228 if(!safe || !eqrect(w->body.all, r1)){ 229 oy = y; 230 if(y+1+w->body.fr.font->height <= r.max.y){ /* room for one line */ 231 r1.min.y = y; 232 r1.max.y = y+1; 233 draw(screen, r1, tagcols[BORD], nil, ZP); 234 y++; 235 r1.min.y = min(y, r.max.y); 236 r1.max.y = r.max.y; 237 }else{ 238 draw(screen, r1, textcols[BACK], nil, ZP); 239 r1.min.y = y; 240 r1.max.y = y; 241 } 242 y = textresize(&w->body, r1, keepextra); 243 w->r = r; 244 w->r.max.y = y; 245 textscrdraw(&w->body); 246 w->body.all.min.y = oy; 247 } 248 w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines)); 249 return w->r.max.y; 250 } 251 252 void 253 winlock1(Window *w, int owner) 254 { 255 incref(&w->ref); 256 qlock(&w->lk); 257 w->owner = owner; 258 } 259 260 void 261 winlock(Window *w, int owner) 262 { 263 int i; 264 File *f; 265 266 f = w->body.file; 267 for(i=0; i<f->ntext; i++) 268 winlock1(f->text[i]->w, owner); 269 } 270 271 void 272 winunlock(Window *w) 273 { 274 int i; 275 File *f; 276 277 /* 278 * subtle: loop runs backwards to avoid tripping over 279 * winclose indirectly editing f->text and freeing f 280 * on the last iteration of the loop. 281 */ 282 f = w->body.file; 283 for(i=f->ntext-1; i>=0; i--){ 284 w = f->text[i]->w; 285 w->owner = 0; 286 qunlock(&w->lk); 287 winclose(w); 288 } 289 } 290 291 void 292 winmousebut(Window *w) 293 { 294 moveto(mousectl, addpt(w->tag.scrollr.min, 295 divpt(Pt(Dx(w->tag.scrollr), font->height), 2))); 296 } 297 298 void 299 windirfree(Window *w) 300 { 301 int i; 302 Dirlist *dl; 303 304 if(w->isdir){ 305 for(i=0; i<w->ndl; i++){ 306 dl = w->dlp[i]; 307 free(dl->r); 308 free(dl); 309 } 310 free(w->dlp); 311 } 312 w->dlp = nil; 313 w->ndl = 0; 314 } 315 316 void 317 winclose(Window *w) 318 { 319 int i; 320 321 if(decref(&w->ref) == 0){ 322 xfidlog(w, "del"); 323 windirfree(w); 324 textclose(&w->tag); 325 textclose(&w->body); 326 if(activewin == w) 327 activewin = nil; 328 for(i=0; i<w->nincl; i++) 329 free(w->incl[i]); 330 free(w->incl); 331 free(w->events); 332 free(w); 333 } 334 } 335 336 void 337 windelete(Window *w) 338 { 339 Xfid *x; 340 341 x = w->eventx; 342 if(x){ 343 w->nevents = 0; 344 free(w->events); 345 w->events = nil; 346 w->eventx = nil; 347 sendp(x->c, nil); /* wake him up */ 348 } 349 } 350 351 void 352 winundo(Window *w, int isundo) 353 { 354 Text *body; 355 int i; 356 File *f; 357 Window *v; 358 359 w->utflastqid = -1; 360 body = &w->body; 361 fileundo(body->file, isundo, &body->q0, &body->q1); 362 textshow(body, body->q0, body->q1, 1); 363 f = body->file; 364 for(i=0; i<f->ntext; i++){ 365 v = f->text[i]->w; 366 v->dirty = (f->seq != v->putseq); 367 if(v != w){ 368 v->body.q0 = v->body.fr.p0+v->body.org; 369 v->body.q1 = v->body.fr.p1+v->body.org; 370 } 371 } 372 winsettag(w); 373 } 374 375 void 376 winsetname(Window *w, Rune *name, int n) 377 { 378 Text *t; 379 Window *v; 380 int i; 381 static Rune Lslashguide[] = { '/', 'g', 'u', 'i', 'd', 'e', 0 }; 382 static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 }; 383 384 t = &w->body; 385 if(runeeq(t->file->name, t->file->nname, name, n) == TRUE) 386 return; 387 w->isscratch = FALSE; 388 if(n>=6 && runeeq(Lslashguide, 6, name+(n-6), 6)) 389 w->isscratch = TRUE; 390 else if(n>=7 && runeeq(Lpluserrors, 7, name+(n-7), 7)) 391 w->isscratch = TRUE; 392 filesetname(t->file, name, n); 393 for(i=0; i<t->file->ntext; i++){ 394 v = t->file->text[i]->w; 395 winsettag(v); 396 v->isscratch = w->isscratch; 397 } 398 } 399 400 void 401 wintype(Window *w, Text *t, Rune r) 402 { 403 int i; 404 405 texttype(t, r); 406 if(t->what == Body) 407 for(i=0; i<t->file->ntext; i++) 408 textscrdraw(t->file->text[i]); 409 winsettag(w); 410 } 411 412 void 413 wincleartag(Window *w) 414 { 415 int i, n; 416 Rune *r; 417 418 /* w must be committed */ 419 n = w->tag.file->b.nc; 420 r = parsetag(w, 0, &i); 421 for(; i<n; i++) 422 if(r[i] == '|') 423 break; 424 if(i == n) 425 return; 426 i++; 427 textdelete(&w->tag, i, n, TRUE); 428 free(r); 429 w->tag.file->mod = FALSE; 430 if(w->tag.q0 > i) 431 w->tag.q0 = i; 432 if(w->tag.q1 > i) 433 w->tag.q1 = i; 434 textsetselect(&w->tag, w->tag.q0, w->tag.q1); 435 } 436 437 Rune* 438 parsetag(Window *w, int extra, int *len) 439 { 440 static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ', 'S', 'n', 'a', 'r', 'f', 0 }; 441 static Rune Lspacepipe[] = { ' ', '|', 0 }; 442 static Rune Ltabpipe[] = { '\t', '|', 0 }; 443 int i; 444 Rune *r, *p, *pipe; 445 446 r = runemalloc(w->tag.file->b.nc+extra+1); 447 bufread(&w->tag.file->b, 0, r, w->tag.file->b.nc); 448 r[w->tag.file->b.nc] = '\0'; 449 450 /* 451 * " |" or "\t|" ends left half of tag 452 * If we find " Del Snarf" in the left half of the tag 453 * (before the pipe), that ends the file name. 454 */ 455 pipe = runestrstr(r, Lspacepipe); 456 if((p = runestrstr(r, Ltabpipe)) != nil && (pipe == nil || p < pipe)) 457 pipe = p; 458 if((p = runestrstr(r, Ldelsnarf)) != nil && (pipe == nil || p < pipe)) 459 i = p - r; 460 else { 461 for(i=0; i<w->tag.file->b.nc; i++) 462 if(r[i]==' ' || r[i]=='\t') 463 break; 464 } 465 *len = i; 466 return r; 467 } 468 469 void 470 winsettag1(Window *w) 471 { 472 int i, j, k, n, bar, dirty, resize; 473 Rune *new, *old, *r; 474 uint q0, q1; 475 static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ', 476 'S', 'n', 'a', 'r', 'f', 0 }; 477 static Rune Lundo[] = { ' ', 'U', 'n', 'd', 'o', 0 }; 478 static Rune Lredo[] = { ' ', 'R', 'e', 'd', 'o', 0 }; 479 static Rune Lget[] = { ' ', 'G', 'e', 't', 0 }; 480 static Rune Lput[] = { ' ', 'P', 'u', 't', 0 }; 481 static Rune Llook[] = { ' ', 'L', 'o', 'o', 'k', ' ', 0 }; 482 static Rune Lpipe[] = { ' ', '|', 0 }; 483 484 /* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */ 485 if(w->tag.ncache!=0 || w->tag.file->mod) 486 wincommit(w, &w->tag); /* check file name; also guarantees we can modify tag contents */ 487 old = parsetag(w, 0, &i); 488 if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){ 489 textdelete(&w->tag, 0, i, TRUE); 490 textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE); 491 free(old); 492 old = runemalloc(w->tag.file->b.nc+1); 493 bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc); 494 old[w->tag.file->b.nc] = '\0'; 495 } 496 497 /* compute the text for the whole tag, replacing current only if it differs */ 498 new = runemalloc(w->body.file->nname+100); 499 i = 0; 500 if(w->body.file->nname != 0) 501 runemove(new, w->body.file->name, w->body.file->nname); 502 i += w->body.file->nname; 503 runemove(new+i, Ldelsnarf, 10); 504 i += 10; 505 if(w->filemenu){ 506 if(w->body.needundo || w->body.file->delta.nc>0 || w->body.ncache){ 507 runemove(new+i, Lundo, 5); 508 i += 5; 509 } 510 if(w->body.file->epsilon.nc > 0){ 511 runemove(new+i, Lredo, 5); 512 i += 5; 513 } 514 dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq); 515 if(!w->isdir && dirty){ 516 runemove(new+i, Lput, 4); 517 i += 4; 518 } 519 } 520 if(w->isdir){ 521 runemove(new+i, Lget, 4); 522 i += 4; 523 } 524 runemove(new+i, Lpipe, 2); 525 i += 2; 526 r = runestrchr(old, '|'); 527 if(r) 528 k = r-old+1; 529 else{ 530 k = w->tag.file->b.nc; 531 if(w->body.file->seq == 0){ 532 runemove(new+i, Llook, 6); 533 i += 6; 534 } 535 } 536 new[i] = 0; 537 538 /* replace tag if the new one is different */ 539 resize = 0; 540 if(runeeq(new, i, old, k) == FALSE){ 541 resize = 1; 542 n = k; 543 if(n > i) 544 n = i; 545 for(j=0; j<n; j++) 546 if(old[j] != new[j]) 547 break; 548 q0 = w->tag.q0; 549 q1 = w->tag.q1; 550 textdelete(&w->tag, j, k, TRUE); 551 textinsert(&w->tag, j, new+j, i-j, TRUE); 552 /* try to preserve user selection */ 553 r = runestrchr(old, '|'); 554 if(r){ 555 bar = r-old; 556 if(q0 > bar){ 557 bar = (runestrchr(new, '|')-new)-bar; 558 w->tag.q0 = q0+bar; 559 w->tag.q1 = q1+bar; 560 } 561 } 562 } 563 free(old); 564 free(new); 565 w->tag.file->mod = FALSE; 566 n = w->tag.file->b.nc+w->tag.ncache; 567 if(w->tag.q0 > n) 568 w->tag.q0 = n; 569 if(w->tag.q1 > n) 570 w->tag.q1 = n; 571 textsetselect(&w->tag, w->tag.q0, w->tag.q1); 572 windrawbutton(w); 573 if(resize){ 574 w->tagsafe = 0; 575 winresize(w, w->r, TRUE, TRUE); 576 } 577 } 578 579 void 580 winsettag(Window *w) 581 { 582 int i; 583 File *f; 584 Window *v; 585 586 f = w->body.file; 587 for(i=0; i<f->ntext; i++){ 588 v = f->text[i]->w; 589 if(v->col->safe || v->body.fr.maxlines>0) 590 winsettag1(v); 591 } 592 } 593 594 void 595 wincommit(Window *w, Text *t) 596 { 597 Rune *r; 598 int i; 599 File *f; 600 601 textcommit(t, TRUE); 602 f = t->file; 603 if(f->ntext > 1) 604 for(i=0; i<f->ntext; i++) 605 textcommit(f->text[i], FALSE); /* no-op for t */ 606 if(t->what == Body) 607 return; 608 r = parsetag(w, 0, &i); 609 if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){ 610 seq++; 611 filemark(w->body.file); 612 w->body.file->mod = TRUE; 613 w->dirty = TRUE; 614 winsetname(w, r, i); 615 winsettag(w); 616 } 617 free(r); 618 } 619 620 void 621 winaddincl(Window *w, Rune *r, int n) 622 { 623 char *a; 624 Dir *d; 625 Runestr rs; 626 627 a = runetobyte(r, n); 628 d = dirstat(a); 629 if(d == nil){ 630 if(a[0] == '/') 631 goto Rescue; 632 rs = dirname(&w->body, r, n); 633 r = rs.r; 634 n = rs.nr; 635 free(a); 636 a = runetobyte(r, n); 637 d = dirstat(a); 638 if(d == nil) 639 goto Rescue; 640 r = runerealloc(r, n+1); 641 r[n] = 0; 642 } 643 free(a); 644 if((d->qid.type&QTDIR) == 0){ 645 free(d); 646 warning(nil, "%s: not a directory\n", a); 647 free(r); 648 return; 649 } 650 free(d); 651 w->nincl++; 652 w->incl = realloc(w->incl, w->nincl*sizeof(Rune*)); 653 memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*)); 654 w->incl[0] = runemalloc(n+1); 655 runemove(w->incl[0], r, n); 656 free(r); 657 return; 658 659 Rescue: 660 warning(nil, "%s: %r\n", a); 661 free(r); 662 free(a); 663 return; 664 } 665 666 int 667 winclean(Window *w, int conservative) 668 { 669 if(w->isscratch || w->isdir) /* don't whine if it's a guide file, error window, etc. */ 670 return TRUE; 671 if(!conservative && w->nopen[QWevent]>0) 672 return TRUE; 673 if(w->dirty){ 674 if(w->body.file->nname) 675 warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name); 676 else{ 677 if(w->body.file->b.nc < 100) /* don't whine if it's too small */ 678 return TRUE; 679 warning(nil, "unnamed file modified\n"); 680 } 681 w->dirty = FALSE; 682 return FALSE; 683 } 684 return TRUE; 685 } 686 687 char* 688 winctlprint(Window *w, char *buf, int fonts) 689 { 690 sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->b.nc, 691 w->body.file->b.nc, w->isdir, w->dirty); 692 if(fonts) 693 return smprint("%s%11d %q %11d %11d %11d ", buf, Dx(w->body.fr.r), 694 w->body.reffont->f->name, w->body.fr.maxtab, seqof(w, 1) != 0, seqof(w, 0) != 0); 695 return buf; 696 } 697 698 void 699 winevent(Window *w, char *fmt, ...) 700 { 701 int n; 702 char *b; 703 Xfid *x; 704 va_list arg; 705 706 if(w->nopen[QWevent] == 0) 707 return; 708 if(w->owner == 0) 709 error("no window owner"); 710 va_start(arg, fmt); 711 b = vsmprint(fmt, arg); 712 va_end(arg); 713 if(b == nil) 714 error("vsmprint failed"); 715 n = strlen(b); 716 w->events = erealloc(w->events, w->nevents+1+n); 717 w->events[w->nevents++] = w->owner; 718 memmove(w->events+w->nevents, b, n); 719 free(b); 720 w->nevents += n; 721 x = w->eventx; 722 if(x){ 723 w->eventx = nil; 724 sendp(x->c, nil); 725 } 726 }