Blob


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 <9pclient.h>
11 #include <plumb.h>
12 #include <complete.h>
13 #include "dat.h"
14 #include "fns.h"
16 #define MOVEIT if(0)
18 enum
19 {
20 HiWater = 640000, /* max size of history */
21 LoWater = 400000, /* min size of history after max'ed */
22 MinWater = 20000, /* room to leave available when reallocating */
23 };
25 static int topped;
26 static int id;
28 static Image *cols[NCOL];
29 static Image *grey;
30 static Image *darkgrey;
31 static Cursor *lastcursor;
32 static Image *titlecol;
33 static Image *lighttitlecol;
34 static Image *holdcol;
35 static Image *lightholdcol;
36 static Image *paleholdcol;
38 Window*
39 wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
40 {
41 Window *w;
42 Rectangle r;
44 if(cols[0] == nil){
45 /* greys are multiples of 0x11111100+0xFF, 14* being palest */
46 grey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
47 darkgrey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x666666FF);
48 cols[BACK] = display->white;
49 cols[HIGH] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
50 cols[BORD] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x999999FF);
51 cols[TEXT] = display->black;
52 cols[HTEXT] = display->black;
53 titlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreygreen);
54 lighttitlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreygreen);
55 holdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DMedblue);
56 lightholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreyblue);
57 paleholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreyblue);
58 }
59 w = emalloc(sizeof(Window));
60 w->screenr = i->r;
61 r = insetrect(i->r, Selborder+1);
62 w->i = i;
63 w->mc = *mc;
64 w->ck = ck;
65 w->cctl = cctl;
66 w->cursorp = nil;
67 w->conswrite = chancreate(sizeof(Conswritemesg), 0);
68 w->consread = chancreate(sizeof(Consreadmesg), 0);
69 w->mouseread = chancreate(sizeof(Mousereadmesg), 0);
70 w->wctlread = chancreate(sizeof(Consreadmesg), 0);
71 w->scrollr = r;
72 w->scrollr.max.x = r.min.x+Scrollwid;
73 w->lastsr = ZR;
74 r.min.x += Scrollwid+Scrollgap;
75 frinit(&w->f, r, font, i, cols);
76 w->f.maxtab = maxtab*stringwidth(font, "0");
77 w->topped = ++topped;
78 w->id = ++id;
79 w->notefd = -1;
80 w->scrolling = scrolling;
81 w->dir = estrdup(startdir);
82 w->label = estrdup("<unnamed>");
83 r = insetrect(w->i->r, Selborder);
84 draw(w->i, r, cols[BACK], nil, w->f.entire.min);
85 wborder(w, Selborder);
86 wscrdraw(w);
87 incref(&w->ref); /* ref will be removed after mounting; avoids delete before ready to be deleted */
88 return w;
89 }
91 void
92 wsetname(Window *w)
93 {
94 int i, n;
95 char err[ERRMAX];
97 n = sprint(w->name, "window.%d.%d", w->id, w->namecount++);
98 for(i='A'; i<='Z'; i++){
99 if(nameimage(w->i, w->name, 1) > 0)
100 return;
101 errstr(err, sizeof err);
102 if(strcmp(err, "image name in use") != 0)
103 break;
104 w->name[n] = i;
105 w->name[n+1] = 0;
107 w->name[0] = 0;
108 fprint(2, "rio: setname failed: %s\n", err);
111 void
112 wresize(Window *w, Image *i, int move)
114 Rectangle r, or;
116 or = w->i->r;
117 if(move || (Dx(or)==Dx(i->r) && Dy(or)==Dy(i->r)))
118 draw(i, i->r, w->i, nil, w->i->r.min);
119 if(w->i != i){
120 fprint(2, "res %p %p\n", w->i, i);
121 freeimage(w->i);
122 w->i = i;
124 // wsetname(w);
125 //XXX w->mc.image = i;
126 r = insetrect(i->r, Selborder+1);
127 w->scrollr = r;
128 w->scrollr.max.x = r.min.x+Scrollwid;
129 w->lastsr = ZR;
130 r.min.x += Scrollwid+Scrollgap;
131 if(move)
132 frsetrects(&w->f, r, w->i);
133 else{
134 frclear(&w->f, FALSE);
135 frinit(&w->f, r, w->f.font, w->i, cols);
136 wsetcols(w);
137 w->f.maxtab = maxtab*stringwidth(w->f.font, "0");
138 r = insetrect(w->i->r, Selborder);
139 draw(w->i, r, cols[BACK], nil, w->f.entire.min);
140 wfill(w);
141 wsetselect(w, w->q0, w->q1);
142 wscrdraw(w);
144 wborder(w, Selborder);
145 w->topped = ++topped;
146 w->resized = TRUE;
147 w->mouse.counter++;
150 void
151 wrefresh(Window *w, Rectangle r)
153 /* USED(r); */
155 /* BUG: rectangle is ignored */
156 if(w == input)
157 wborder(w, Selborder);
158 else
159 wborder(w, Unselborder);
160 if(w->mouseopen)
161 return;
162 draw(w->i, insetrect(w->i->r, Borderwidth), w->f.cols[BACK], nil, w->i->r.min);
163 w->f.ticked = 0;
164 if(w->f.p0 > 0)
165 frdrawsel(&w->f, frptofchar(&w->f, 0), 0, w->f.p0, 0);
166 if(w->f.p1 < w->f.nchars)
167 frdrawsel(&w->f, frptofchar(&w->f, w->f.p1), w->f.p1, w->f.nchars, 0);
168 frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, w->f.p1, 1);
169 w->lastsr = ZR;
170 wscrdraw(w);
173 int
174 wclose(Window *w)
176 int i;
178 i = decref(&w->ref);
179 if(i > 0)
180 return 0;
181 if(i < 0)
182 error("negative ref count");
183 if(!w->deleted)
184 wclosewin(w);
185 wsendctlmesg(w, Exited, ZR, nil);
186 return 1;
190 void
191 winctl(void *arg)
193 Rune *rp, *bp, *up, *kbdr;
194 uint qh;
195 int nr, nb, c, wid, i, npart, initial, lastb;
196 char *s, *t, part[3];
197 Window *w;
198 Mousestate *mp, m;
199 enum { WKey, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, NWALT };
200 Alt alts[NWALT+1];
201 Mousereadmesg mrm;
202 Conswritemesg cwm;
203 Consreadmesg crm;
204 Consreadmesg cwrm;
205 Stringpair pair;
206 Wctlmesg wcm;
207 char buf[4*12+1];
209 w = arg;
210 snprint(buf, sizeof buf, "winctl-id%d", w->id);
211 threadsetname(buf);
213 mrm.cm = chancreate(sizeof(Mouse), 0);
214 cwm.cw = chancreate(sizeof(Stringpair), 0);
215 crm.c1 = chancreate(sizeof(Stringpair), 0);
216 crm.c2 = chancreate(sizeof(Stringpair), 0);
217 cwrm.c1 = chancreate(sizeof(Stringpair), 0);
218 cwrm.c2 = chancreate(sizeof(Stringpair), 0);
221 alts[WKey].c = w->ck;
222 alts[WKey].v = &kbdr;
223 alts[WKey].op = CHANRCV;
224 alts[WMouse].c = w->mc.c;
225 alts[WMouse].v = &w->mc.m;
226 alts[WMouse].op = CHANRCV;
227 alts[WMouseread].c = w->mouseread;
228 alts[WMouseread].v = &mrm;
229 alts[WMouseread].op = CHANSND;
230 alts[WCtl].c = w->cctl;
231 alts[WCtl].v = &wcm;
232 alts[WCtl].op = CHANRCV;
233 alts[WCwrite].c = w->conswrite;
234 alts[WCwrite].v = &cwm;
235 alts[WCwrite].op = CHANSND;
236 alts[WCread].c = w->consread;
237 alts[WCread].v = &crm;
238 alts[WCread].op = CHANSND;
239 alts[WWread].c = w->wctlread;
240 alts[WWread].v = &cwrm;
241 alts[WWread].op = CHANSND;
242 alts[NWALT].op = CHANEND;
244 npart = 0;
245 lastb = -1;
246 for(;;){
247 if(w->mouseopen && w->mouse.counter != w->mouse.lastcounter)
248 alts[WMouseread].op = CHANSND;
249 else
250 alts[WMouseread].op = CHANNOP;
251 if(!w->scrolling && !w->mouseopen && w->qh>w->org+w->f.nchars)
252 alts[WCwrite].op = CHANNOP;
253 else
254 alts[WCwrite].op = CHANSND;
255 if(w->deleted || !w->wctlready)
256 alts[WWread].op = CHANNOP;
257 else
258 alts[WWread].op = CHANSND;
259 /* this code depends on NL and EOT fitting in a single byte */
260 /* kind of expensive for each loop; worth precomputing? */
261 if(w->holding)
262 alts[WCread].op = CHANNOP;
263 else if(npart || (w->rawing && w->nraw>0))
264 alts[WCread].op = CHANSND;
265 else{
266 alts[WCread].op = CHANNOP;
267 for(i=w->qh; i<w->nr; i++){
268 c = w->r[i];
269 if(c=='\n' || c=='\004'){
270 alts[WCread].op = CHANSND;
271 break;
275 switch(alt(alts)){
276 case WKey:
277 for(i=0; kbdr[i]!=L'\0'; i++)
278 wkeyctl(w, kbdr[i]);
279 // wkeyctl(w, r);
280 // while(nbrecv(w->ck, &r))
281 // wkeyctl(w, r);
282 break;
283 case WMouse:
284 if(w->mouseopen) {
285 w->mouse.counter++;
287 /* queue click events */
288 if(!w->mouse.qfull && lastb != w->mc.m.buttons) { /* add to ring */
289 mp = &w->mouse.queue[w->mouse.wi];
290 if(++w->mouse.wi == nelem(w->mouse.queue))
291 w->mouse.wi = 0;
292 if(w->mouse.wi == w->mouse.ri)
293 w->mouse.qfull = TRUE;
294 mp->m = w->mc.m;
295 mp->counter = w->mouse.counter;
296 lastb = w->mc.m.buttons;
298 } else
299 wmousectl(w);
300 break;
301 case WMouseread:
302 /* send a queued event or, if the queue is empty, the current state */
303 /* if the queue has filled, we discard all the events it contained. */
304 /* the intent is to discard frantic clicking by the user during long latencies. */
305 w->mouse.qfull = FALSE;
306 if(w->mouse.wi != w->mouse.ri) {
307 m = w->mouse.queue[w->mouse.ri];
308 if(++w->mouse.ri == nelem(w->mouse.queue))
309 w->mouse.ri = 0;
310 } else
311 m = (Mousestate){w->mc.m, w->mouse.counter};
313 w->mouse.lastcounter = m.counter;
314 send(mrm.cm, &m.m);
315 continue;
316 case WCtl:
317 if(wctlmesg(w, wcm.type, wcm.r, wcm.image) == Exited){
318 chanfree(crm.c1);
319 chanfree(crm.c2);
320 chanfree(mrm.cm);
321 chanfree(cwm.cw);
322 chanfree(cwrm.c1);
323 chanfree(cwrm.c2);
324 threadexits(nil);
326 continue;
327 case WCwrite:
328 recv(cwm.cw, &pair);
329 rp = pair.s;
330 nr = pair.ns;
331 bp = rp;
332 up = rp;
333 initial = 0;
334 for(i=0; i<nr; i++){
335 switch(*bp){
336 case 0:
337 break;
338 case '\b':
339 if(up == rp)
340 initial++;
341 else
342 --up;
343 break;
344 case '\r':
345 while(i<nr-1 && *(bp+1) == '\r'){
346 bp++;
347 i++;
349 if(i<nr-1 && *(bp+1) != '\n'){
350 while(up > rp && *(up-1) != '\n')
351 up--;
352 if(up == rp)
353 initial = wbswidth(w, '\r');
354 }else if(i == nr-1)
355 *up = '\n';
356 break;
357 default:
358 *up++ = *bp;
359 break;
361 bp++;
363 if(initial){
364 if(initial > w->qh)
365 initial = w->qh;
366 qh = w->qh - initial;
367 wdelete(w, qh, qh+initial);
368 w->qh = qh;
370 nr = up - rp;
371 w->qh = winsert(w, rp, nr, w->qh)+nr;
372 if(w->scrolling || w->mouseopen)
373 wshow(w, w->qh);
374 wsetselect(w, w->q0, w->q1);
375 wscrdraw(w);
376 free(rp);
377 break;
378 case WCread:
379 recv(crm.c1, &pair);
380 t = pair.s;
381 nb = pair.ns;
382 i = npart;
383 npart = 0;
384 if(i)
385 memmove(t, part, i);
386 while(i<nb && (w->qh<w->nr || w->nraw>0)){
387 if(w->qh == w->nr){
388 wid = runetochar(t+i, &w->raw[0]);
389 w->nraw--;
390 runemove(w->raw, w->raw+1, w->nraw);
391 }else
392 wid = runetochar(t+i, &w->r[w->qh++]);
393 c = t[i]; /* knows break characters fit in a byte */
394 i += wid;
395 if(!w->rawing && (c == '\n' || c=='\004')){
396 // if(c == '\004')
397 // i--;
398 break;
401 // if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004')
402 // w->qh++;
403 if(i > nb){
404 npart = i-nb;
405 memmove(part, t+nb, npart);
406 i = nb;
408 pair.s = t;
409 pair.ns = i;
410 send(crm.c2, &pair);
411 continue;
412 case WWread:
413 w->wctlready = 0;
414 recv(cwrm.c1, &pair);
415 if(w->deleted || w->i==nil)
416 pair.ns = sprint(pair.s, "");
417 else{
418 s = "visible";
419 for(i=0; i<nhidden; i++)
420 if(hidden[i] == w){
421 s = "hidden";
422 break;
424 t = "notcurrent";
425 if(w == input)
426 t = "current";
427 pair.ns = snprint(pair.s, pair.ns, "%11d %11d %11d %11d %s %s ",
428 w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
430 send(cwrm.c2, &pair);
431 continue;
433 if(!w->deleted)
434 flushimage(display, 1);
438 void
439 waddraw(Window *w, Rune *r, int nr)
441 w->raw = runerealloc(w->raw, w->nraw+nr);
442 runemove(w->raw+w->nraw, r, nr);
443 w->nraw += nr;
446 /*
447 * Need to do this in a separate proc because if process we're interrupting
448 * is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
449 */
450 void
451 interruptproc(void *v)
453 int *notefd;
455 notefd = v;
456 write(*notefd, "interrupt", 9);
457 free(notefd);
460 int
461 windfilewidth(Window *w, uint q0, int oneelement)
463 uint q;
464 Rune r;
466 q = q0;
467 while(q > 0){
468 r = w->r[q-1];
469 if(r<=' ')
470 break;
471 if(oneelement && r=='/')
472 break;
473 --q;
475 return q0-q;
478 void
479 showcandidates(Window *w, Completion *c)
481 int i;
482 Fmt f;
483 Rune *rp;
484 uint nr, qline, q0;
485 char *s;
487 runefmtstrinit(&f);
488 if (c->nmatch == 0)
489 s = "[no matches in ";
490 else
491 s = "[";
492 if(c->nfile > 32)
493 fmtprint(&f, "%s%d files]\n", s, c->nfile);
494 else{
495 fmtprint(&f, "%s", s);
496 for(i=0; i<c->nfile; i++){
497 if(i > 0)
498 fmtprint(&f, " ");
499 fmtprint(&f, "%s", c->filename[i]);
501 fmtprint(&f, "]\n");
503 /* place text at beginning of line before host point */
504 qline = w->qh;
505 while(qline>0 && w->r[qline-1] != '\n')
506 qline--;
508 rp = runefmtstrflush(&f);
509 nr = runestrlen(rp);
511 q0 = w->q0;
512 q0 += winsert(w, rp, runestrlen(rp), qline) - qline;
513 free(rp);
514 wsetselect(w, q0+nr, q0+nr);
517 Rune*
518 namecomplete(Window *w)
520 int nstr, npath;
521 Rune *rp, *path, *str;
522 Completion *c;
523 char *s, *dir, *root;
525 /* control-f: filename completion; works back to white space or / */
526 if(w->q0<w->nr && w->r[w->q0]>' ') /* must be at end of word */
527 return nil;
528 nstr = windfilewidth(w, w->q0, TRUE);
529 str = runemalloc(nstr);
530 runemove(str, w->r+(w->q0-nstr), nstr);
531 npath = windfilewidth(w, w->q0-nstr, FALSE);
532 path = runemalloc(npath);
533 runemove(path, w->r+(w->q0-nstr-npath), npath);
534 rp = nil;
536 /* is path rooted? if not, we need to make it relative to window path */
537 if(npath>0 && path[0]=='/'){
538 dir = malloc(UTFmax*npath+1);
539 sprint(dir, "%.*S", npath, path);
540 }else{
541 if(strcmp(w->dir, "") == 0)
542 root = ".";
543 else
544 root = w->dir;
545 dir = malloc(strlen(root)+1+UTFmax*npath+1);
546 sprint(dir, "%s/%.*S", root, npath, path);
548 dir = cleanname(dir);
550 s = smprint("%.*S", nstr, str);
551 c = complete(dir, s);
552 free(s);
553 if(c == nil)
554 goto Return;
556 if(!c->advance)
557 showcandidates(w, c);
559 if(c->advance)
560 rp = runesmprint("%s", c->string);
562 Return:
563 freecompletion(c);
564 free(dir);
565 free(path);
566 free(str);
567 return rp;
570 void
571 wkeyctl(Window *w, Rune r)
573 uint q0 ,q1;
574 int n, nb, nr;
575 Rune *rp;
577 if(r == 0)
578 return;
579 if(w->deleted)
580 return;
581 w->rawing = rawon();
582 /* navigation keys work only when mouse is not open */
583 if(!w->mouseopen)
584 switch(r){
585 case Kdown:
586 n = w->f.maxlines/3;
587 goto case_Down;
588 case Kscrollonedown:
589 n = mousescrollsize(w->f.maxlines);
590 if(n <= 0)
591 n = 1;
592 goto case_Down;
593 case Kpgdown:
594 n = 2*w->f.maxlines/3;
595 case_Down:
596 q0 = w->org+frcharofpt(&w->f, Pt(w->f.r.min.x, w->f.r.min.y+n*w->f.font->height));
597 wsetorigin(w, q0, TRUE);
598 return;
599 case Kup:
600 n = w->f.maxlines/3;
601 goto case_Up;
602 case Kscrolloneup:
603 n = mousescrollsize(w->f.maxlines);
604 if(n <= 0)
605 n = 1;
606 goto case_Up;
607 case Kpgup:
608 n = 2*w->f.maxlines/3;
609 case_Up:
610 q0 = wbacknl(w, w->org, n);
611 wsetorigin(w, q0, TRUE);
612 return;
613 case Kleft:
614 if(w->q0 > 0){
615 q0 = w->q0-1;
616 wsetselect(w, q0, q0);
617 wshow(w, q0);
619 return;
620 case Kright:
621 if(w->q1 < w->nr){
622 q1 = w->q1+1;
623 wsetselect(w, q1, q1);
624 wshow(w, q1);
626 return;
627 case Khome:
628 wshow(w, 0);
629 return;
630 case Kend:
631 wshow(w, w->nr);
632 return;
633 case 0x01: /* ^A: beginning of line */
634 if(w->q0==0 || w->q0==w->qh || w->r[w->q0-1]=='\n')
635 return;
636 nb = wbswidth(w, 0x15 /* ^U */);
637 wsetselect(w, w->q0-nb, w->q0-nb);
638 wshow(w, w->q0);
639 return;
640 case 0x05: /* ^E: end of line */
641 q0 = w->q0;
642 while(q0 < w->nr && w->r[q0]!='\n')
643 q0++;
644 wsetselect(w, q0, q0);
645 wshow(w, w->q0);
646 return;
648 /*
649 * This if used to be below the if(w->rawing ...),
650 * but let's try putting it here. This will allow ESC-processing
651 * to toggle hold mode even in remote SSH connections.
652 * The drawback is that vi-style processing gets harder.
653 * If you find yourself in some weird readline mode, good
654 * luck getting out without ESC. Let's see who complains.
655 */
656 if(r==0x1B || (w->holding && r==0x7F)){ /* toggle hold */
657 if(w->holding)
658 --w->holding;
659 else
660 w->holding++;
661 wrepaint(w);
662 if(r == 0x1B)
663 return;
665 if(!w->holding && w->rawing && (w->q0==w->nr || w->mouseopen)){
666 waddraw(w, &r, 1);
667 return;
669 if(r != 0x7F){
670 wsnarf(w);
671 wcut(w);
673 switch(r){
674 case 0x7F: /* send interrupt */
675 w->qh = w->nr;
676 wshow(w, w->qh);
677 winterrupt(w);
678 return;
679 case 0x06: /* ^F: file name completion */
680 case Kins: /* Insert: file name completion */
681 rp = namecomplete(w);
682 if(rp == nil)
683 return;
684 nr = runestrlen(rp);
685 q0 = w->q0;
686 q0 = winsert(w, rp, nr, q0);
687 wshow(w, q0+nr);
688 free(rp);
689 return;
690 case 0x08: /* ^H: erase character */
691 case 0x15: /* ^U: erase line */
692 case 0x17: /* ^W: erase word */
693 if(w->q0==0 || w->q0==w->qh)
694 return;
695 nb = wbswidth(w, r);
696 q1 = w->q0;
697 q0 = q1-nb;
698 if(q0 < w->org){
699 q0 = w->org;
700 nb = q1-q0;
702 if(nb > 0){
703 wdelete(w, q0, q0+nb);
704 wsetselect(w, q0, q0);
706 return;
708 /* otherwise ordinary character; just insert */
709 q0 = w->q0;
710 q0 = winsert(w, &r, 1, q0);
711 wshow(w, q0+1);
714 void
715 wsetcols(Window *w)
717 if(w->holding)
718 if(w == input)
719 w->f.cols[TEXT] = w->f.cols[HTEXT] = holdcol;
720 else
721 w->f.cols[TEXT] = w->f.cols[HTEXT] = lightholdcol;
722 else
723 if(w == input)
724 w->f.cols[TEXT] = w->f.cols[HTEXT] = display->black;
725 else
726 w->f.cols[TEXT] = w->f.cols[HTEXT] = darkgrey;
729 void
730 wrepaint(Window *w)
732 wsetcols(w);
733 if(!w->mouseopen)
734 _frredraw(&w->f, w->f.r.min);
735 if(w == input){
736 wborder(w, Selborder);
737 wsetcursor(w, 0);
738 }else
739 wborder(w, Unselborder);
742 int
743 wbswidth(Window *w, Rune c)
745 uint q, eq, stop;
746 Rune r;
747 int skipping;
749 /* there is known to be at least one character to erase */
750 if(c == 0x08) /* ^H: erase character */
751 return 1;
752 q = w->q0;
753 stop = 0;
754 if(q > w->qh)
755 stop = w->qh;
756 skipping = TRUE;
757 while(q > stop){
758 r = w->r[q-1];
759 if(r == '\n'){ /* eat at most one more character */
760 if(q == w->q0 && c != '\r') /* eat the newline */
761 --q;
762 break;
764 if(c == 0x17){
765 eq = isalnum(r);
766 if(eq && skipping) /* found one; stop skipping */
767 skipping = FALSE;
768 else if(!eq && !skipping)
769 break;
771 --q;
773 return w->q0-q;
776 void
777 wsnarf(Window *w)
779 if(w->q1 == w->q0)
780 return;
781 nsnarf = w->q1-w->q0;
782 snarf = runerealloc(snarf, nsnarf);
783 snarfversion++; /* maybe modified by parent */
784 runemove(snarf, w->r+w->q0, nsnarf);
785 rioputsnarf();
788 void
789 wcut(Window *w)
791 if(w->q1 == w->q0)
792 return;
793 wdelete(w, w->q0, w->q1);
794 wsetselect(w, w->q0, w->q0);
797 void
798 wpaste(Window *w)
800 uint q0;
802 if(nsnarf == 0)
803 return;
804 wcut(w);
805 q0 = w->q0;
806 if(w->rawing && q0==w->nr){
807 waddraw(w, snarf, nsnarf);
808 wsetselect(w, q0, q0);
809 }else{
810 q0 = winsert(w, snarf, nsnarf, w->q0);
811 wsetselect(w, q0, q0+nsnarf);
815 void
816 wplumb(Window *w)
818 Plumbmsg *m;
819 static CFid *fd;
820 char buf[32];
821 uint p0, p1;
822 Cursor *c;
824 if(fd == nil)
825 fd = plumbopenfid("send", OWRITE);
826 if(fd == nil)
827 return;
828 m = emalloc(sizeof(Plumbmsg));
829 m->src = estrdup("rio");
830 m->dst = nil;
831 m->wdir = estrdup(w->dir);
832 m->type = estrdup("text");
833 p0 = w->q0;
834 p1 = w->q1;
835 if(w->q1 > w->q0)
836 m->attr = nil;
837 else{
838 while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
839 p0--;
840 while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
841 p1++;
842 sprint(buf, "click=%d", w->q0-p0);
843 m->attr = plumbunpackattr(buf);
845 if(p1-p0 > messagesize-1024){
846 plumbfree(m);
847 return; /* too large for 9P */
849 m->data = runetobyte(w->r+p0, p1-p0, &m->ndata);
850 if(plumbsendtofid(fd, m) < 0){
851 c = lastcursor;
852 riosetcursor(&query, 1);
853 sleep(300);
854 riosetcursor(c, 1);
856 plumbfree(m);
859 int
860 winborder(Window *w, Point xy)
862 return ptinrect(xy, w->screenr) && !ptinrect(xy, insetrect(w->screenr, Selborder));
865 void
866 wmousectl(Window *w)
868 int but;
870 if(w->mc.m.buttons == 1)
871 but = 1;
872 else if(w->mc.m.buttons == 2)
873 but = 2;
874 else if(w->mc.m.buttons == 4)
875 but = 3;
876 else{
877 if(w->mc.m.buttons == 8)
878 wkeyctl(w, Kscrolloneup);
879 if(w->mc.m.buttons == 16)
880 wkeyctl(w, Kscrollonedown);
881 return;
884 incref(&w->ref); /* hold up window while we track */
885 if(w->deleted)
886 goto Return;
887 if(ptinrect(w->mc.m.xy, w->scrollr)){
888 if(but)
889 wscroll(w, but);
890 goto Return;
892 if(but == 1)
893 wselect(w);
894 /* else all is handled by main process */
895 Return:
896 wclose(w);
899 void
900 wdelete(Window *w, uint q0, uint q1)
902 uint n, p0, p1;
904 n = q1-q0;
905 if(n == 0)
906 return;
907 runemove(w->r+q0, w->r+q1, w->nr-q1);
908 w->nr -= n;
909 if(q0 < w->q0)
910 w->q0 -= min(n, w->q0-q0);
911 if(q0 < w->q1)
912 w->q1 -= min(n, w->q1-q0);
913 if(q1 < w->qh)
914 w->qh -= n;
915 else if(q0 < w->qh)
916 w->qh = q0;
917 if(q1 <= w->org)
918 w->org -= n;
919 else if(q0 < w->org+w->f.nchars){
920 p1 = q1 - w->org;
921 if(p1 > w->f.nchars)
922 p1 = w->f.nchars;
923 if(q0 < w->org){
924 w->org = q0;
925 p0 = 0;
926 }else
927 p0 = q0 - w->org;
928 frdelete(&w->f, p0, p1);
929 wfill(w);
934 static Window *clickwin;
935 static uint clickmsec;
936 static Window *selectwin;
937 static uint selectq;
939 /*
940 * called from frame library
941 */
942 void
943 framescroll(Frame *f, int dl)
945 if(f != &selectwin->f)
946 error("frameselect not right frame");
947 wframescroll(selectwin, dl);
950 void
951 wframescroll(Window *w, int dl)
953 uint q0;
955 if(dl == 0){
956 wscrsleep(w, 100);
957 return;
959 if(dl < 0){
960 q0 = wbacknl(w, w->org, -dl);
961 if(selectq > w->org+w->f.p0)
962 wsetselect(w, w->org+w->f.p0, selectq);
963 else
964 wsetselect(w, selectq, w->org+w->f.p0);
965 }else{
966 if(w->org+w->f.nchars == w->nr)
967 return;
968 q0 = w->org+frcharofpt(&w->f, Pt(w->f.r.min.x, w->f.r.min.y+dl*w->f.font->height));
969 if(selectq >= w->org+w->f.p1)
970 wsetselect(w, w->org+w->f.p1, selectq);
971 else
972 wsetselect(w, selectq, w->org+w->f.p1);
974 wsetorigin(w, q0, TRUE);
977 void
978 wselect(Window *w)
980 uint q0, q1;
981 int b, x, y, first;
983 first = 1;
984 selectwin = w;
985 /*
986 * Double-click immediately if it might make sense.
987 */
988 b = w->mc.m.buttons;
989 q0 = w->q0;
990 q1 = w->q1;
991 selectq = w->org+frcharofpt(&w->f, w->mc.m.xy);
992 if(clickwin==w && w->mc.m.msec-clickmsec<500)
993 if(q0==q1 && selectq==w->q0){
994 wdoubleclick(w, &q0, &q1);
995 wsetselect(w, q0, q1);
996 flushimage(display, 1);
997 x = w->mc.m.xy.x;
998 y = w->mc.m.xy.y;
999 /* stay here until something interesting happens */
1001 readmouse(&w->mc);
1002 while(w->mc.m.buttons==b && abs(w->mc.m.xy.x-x)<3 && abs(w->mc.m.xy.y-y)<3);
1003 w->mc.m.xy.x = x; /* in case we're calling frselect */
1004 w->mc.m.xy.y = y;
1005 q0 = w->q0; /* may have changed */
1006 q1 = w->q1;
1007 selectq = q0;
1009 if(w->mc.m.buttons == b){
1010 w->f.scroll = framescroll;
1011 frselect(&w->f, &w->mc);
1012 /* horrible botch: while asleep, may have lost selection altogether */
1013 if(selectq > w->nr)
1014 selectq = w->org + w->f.p0;
1015 w->f.scroll = nil;
1016 if(selectq < w->org)
1017 q0 = selectq;
1018 else
1019 q0 = w->org + w->f.p0;
1020 if(selectq > w->org+w->f.nchars)
1021 q1 = selectq;
1022 else
1023 q1 = w->org+w->f.p1;
1025 if(q0 == q1){
1026 if(q0==w->q0 && clickwin==w && w->mc.m.msec-clickmsec<500){
1027 wdoubleclick(w, &q0, &q1);
1028 clickwin = nil;
1029 }else{
1030 clickwin = w;
1031 clickmsec = w->mc.m.msec;
1033 }else
1034 clickwin = nil;
1035 wsetselect(w, q0, q1);
1036 flushimage(display, 1);
1037 while(w->mc.m.buttons){
1038 w->mc.m.msec = 0;
1039 b = w->mc.m.buttons;
1040 if(b & 6){
1041 if(b & 2){
1042 wsnarf(w);
1043 wcut(w);
1044 }else{
1045 if(first){
1046 first = 0;
1047 riogetsnarf();
1049 wpaste(w);
1052 wscrdraw(w);
1053 flushimage(display, 1);
1054 while(w->mc.m.buttons == b)
1055 readmouse(&w->mc);
1056 clickwin = nil;
1060 void
1061 wsendctlmesg(Window *w, int type, Rectangle r, Image *image)
1063 Wctlmesg wcm;
1065 wcm.type = type;
1066 wcm.r = r;
1067 wcm.image = image;
1068 send(w->cctl, &wcm);
1071 int
1072 wctlmesg(Window *w, int m, Rectangle r, Image *i)
1074 char buf[64];
1076 switch(m){
1077 default:
1078 error("unknown control message");
1079 break;
1080 case Wakeup:
1081 break;
1082 case Moved:
1083 case Reshaped:
1084 if(w->deleted){
1085 freeimage(i);
1086 break;
1088 w->screenr = r;
1089 strcpy(buf, w->name);
1090 wresize(w, i, m==Moved);
1091 w->wctlready = 1;
1092 if(Dx(r) > 0){
1093 if(w != input)
1094 wcurrent(w);
1095 }else if(w == input)
1096 wcurrent(nil);
1097 flushimage(display, 1);
1098 break;
1099 case Refresh:
1100 if(w->deleted || Dx(w->screenr)<=0 || !rectclip(&r, w->i->r))
1101 break;
1102 if(!w->mouseopen)
1103 wrefresh(w, r);
1104 flushimage(display, 1);
1105 break;
1106 case Movemouse:
1107 if(sweeping || !ptinrect(r.min, w->i->r))
1108 break;
1109 wmovemouse(w, r.min);
1110 case Rawon:
1111 break;
1112 case Rawoff:
1113 if(w->deleted)
1114 break;
1115 while(w->nraw > 0){
1116 wkeyctl(w, w->raw[0]);
1117 --w->nraw;
1118 runemove(w->raw, w->raw+1, w->nraw);
1120 break;
1121 case Holdon:
1122 case Holdoff:
1123 if(w->deleted)
1124 break;
1125 wrepaint(w);
1126 flushimage(display, 1);
1127 break;
1128 case Deleted:
1129 if(w->deleted)
1130 break;
1131 write(w->notefd, "hangup", 6);
1132 wclosewin(w);
1133 break;
1134 case Exited:
1135 frclear(&w->f, TRUE);
1136 close(w->notefd);
1137 chanfree(w->mc.c);
1138 chanfree(w->ck);
1139 chanfree(w->cctl);
1140 chanfree(w->conswrite);
1141 chanfree(w->consread);
1142 chanfree(w->mouseread);
1143 chanfree(w->wctlread);
1144 free(w->raw);
1145 free(w->r);
1146 free(w->dir);
1147 free(w->label);
1148 free(w);
1149 break;
1151 return m;
1155 * Convert back to physical coordinates
1157 void
1158 wmovemouse(Window *w, Point p)
1160 p.x += w->screenr.min.x-w->i->r.min.x;
1161 p.y += w->screenr.min.y-w->i->r.min.y;
1162 moveto(mousectl, p);
1165 void
1166 wcurrent(Window *w)
1168 Window *oi;
1170 if(wkeyboard!=nil && w==wkeyboard)
1171 return;
1172 oi = input;
1173 input = w;
1174 if(oi!=w && oi!=nil)
1175 wrepaint(oi);
1176 if(w !=nil){
1177 wrepaint(w);
1178 wsetcursor(w, 0);
1180 if(w != oi){
1181 if(oi){
1182 oi->wctlready = 1;
1183 wsendctlmesg(oi, Wakeup, ZR, nil);
1185 if(w){
1186 w->wctlready = 1;
1187 wsendctlmesg(w, Wakeup, ZR, nil);
1192 void
1193 wsetcursor(Window *w, int force)
1195 Cursor *p;
1197 if(w==nil || /*w!=input || */ w->i==nil || Dx(w->screenr)<=0)
1198 p = nil;
1199 else if(wpointto(mouse->xy) == w){
1200 p = w->cursorp;
1201 if(p==nil && w->holding)
1202 p = &whitearrow;
1203 }else
1204 p = nil;
1205 if(!menuing)
1206 riosetcursor(p, force && !menuing);
1209 void
1210 riosetcursor(Cursor *p, int force)
1212 if(!force && p==lastcursor)
1213 return;
1214 setcursor(mousectl, p);
1215 lastcursor = p;
1218 Window*
1219 wtop(Point pt)
1221 Window *w;
1223 w = wpointto(pt);
1224 if(w){
1225 if(w->topped == topped)
1226 return nil;
1227 topwindow(w->i);
1228 wcurrent(w);
1229 flushimage(display, 1);
1230 w->topped = ++topped;
1232 return w;
1235 void
1236 wtopme(Window *w)
1238 if(w!=nil && w->i!=nil && !w->deleted && w->topped!=topped){
1239 topwindow(w->i);
1240 flushimage(display, 1);
1241 w->topped = ++ topped;
1245 void
1246 wbottomme(Window *w)
1248 if(w!=nil && w->i!=nil && !w->deleted){
1249 bottomwindow(w->i);
1250 flushimage(display, 1);
1251 w->topped = 0;
1255 Window*
1256 wlookid(int id)
1258 int i;
1260 for(i=0; i<nwindow; i++)
1261 if(window[i]->id == id)
1262 return window[i];
1263 return nil;
1266 void
1267 wclosewin(Window *w)
1269 Rectangle r;
1270 int i;
1272 w->deleted = TRUE;
1273 if(w == input){
1274 input = nil;
1275 wsetcursor(w, 0);
1277 if(w == wkeyboard)
1278 wkeyboard = nil;
1279 for(i=0; i<nhidden; i++)
1280 if(hidden[i] == w){
1281 --nhidden;
1282 memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0]));
1283 break;
1285 for(i=0; i<nwindow; i++)
1286 if(window[i] == w){
1287 --nwindow;
1288 memmove(window+i, window+i+1, (nwindow-i)*sizeof(Window*));
1289 w->deleted = TRUE;
1290 r = w->i->r;
1291 /* move it off-screen to hide it, in case client is slow in letting it go */
1292 MOVEIT originwindow(w->i, r.min, view->r.max);
1293 freeimage(w->i);
1294 w->i = nil;
1295 return;
1297 error("unknown window in closewin");
1300 void
1301 wsetpid(Window *w, int pid, int dolabel)
1303 char buf[128];
1305 w->pid = pid;
1306 if(dolabel){
1307 sprint(buf, "rc %d", pid);
1308 free(w->label);
1309 w->label = estrdup(buf);
1310 drawsetlabel(w->label);
1314 static Rune left1[] = { '{', '[', '(', '<', 0xAB, 0 };
1315 static Rune right1[] = { '}', ']', ')', '>', 0xBB, 0 };
1316 static Rune left2[] = { '\n', 0 };
1317 static Rune left3[] = { '\'', '"', '`', 0 };
1319 Rune *left[] = {
1320 left1,
1321 left2,
1322 left3,
1323 nil
1325 Rune *right[] = {
1326 right1,
1327 left2,
1328 left3,
1329 nil
1332 void
1333 wdoubleclick(Window *w, uint *q0, uint *q1)
1335 int c, i;
1336 Rune *r, *l, *p;
1337 uint q;
1339 for(i=0; left[i]!=nil; i++){
1340 q = *q0;
1341 l = left[i];
1342 r = right[i];
1343 /* try matching character to left, looking right */
1344 if(q == 0)
1345 c = '\n';
1346 else
1347 c = w->r[q-1];
1348 p = strrune(l, c);
1349 if(p != nil){
1350 if(wclickmatch(w, c, r[p-l], 1, &q))
1351 *q1 = q-(c!='\n');
1352 return;
1354 /* try matching character to right, looking left */
1355 if(q == w->nr)
1356 c = '\n';
1357 else
1358 c = w->r[q];
1359 p = strrune(r, c);
1360 if(p != nil){
1361 if(wclickmatch(w, c, l[p-r], -1, &q)){
1362 *q1 = *q0+(*q0<w->nr && c=='\n');
1363 *q0 = q;
1364 if(c!='\n' || q!=0 || w->r[0]=='\n')
1365 (*q0)++;
1367 return;
1370 /* try filling out word to right */
1371 while(*q1<w->nr && isalnum(w->r[*q1]))
1372 (*q1)++;
1373 /* try filling out word to left */
1374 while(*q0>0 && isalnum(w->r[*q0-1]))
1375 (*q0)--;
1378 int
1379 wclickmatch(Window *w, int cl, int cr, int dir, uint *q)
1381 Rune c;
1382 int nest;
1384 nest = 1;
1385 for(;;){
1386 if(dir > 0){
1387 if(*q == w->nr)
1388 break;
1389 c = w->r[*q];
1390 (*q)++;
1391 }else{
1392 if(*q == 0)
1393 break;
1394 (*q)--;
1395 c = w->r[*q];
1397 if(c == cr){
1398 if(--nest==0)
1399 return 1;
1400 }else if(c == cl)
1401 nest++;
1403 return cl=='\n' && nest==1;
1407 uint
1408 wbacknl(Window *w, uint p, uint n)
1410 int i, j;
1412 /* look for start of this line if n==0 */
1413 if(n==0 && p>0 && w->r[p-1]!='\n')
1414 n = 1;
1415 i = n;
1416 while(i-->0 && p>0){
1417 --p; /* it's at a newline now; back over it */
1418 if(p == 0)
1419 break;
1420 /* at 128 chars, call it a line anyway */
1421 for(j=128; --j>0 && p>0; p--)
1422 if(w->r[p-1]=='\n')
1423 break;
1425 return p;
1428 void
1429 wshow(Window *w, uint q0)
1431 int qe;
1432 int nl;
1433 uint q;
1435 qe = w->org+w->f.nchars;
1436 if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
1437 wscrdraw(w);
1438 else{
1439 nl = 4*w->f.maxlines/5;
1440 q = wbacknl(w, q0, nl);
1441 /* avoid going backwards if trying to go forwards - long lines! */
1442 if(!(q0>w->org && q<w->org))
1443 wsetorigin(w, q, TRUE);
1444 while(q0 > w->org+w->f.nchars)
1445 wsetorigin(w, w->org+1, FALSE);
1449 void
1450 wsetorigin(Window *w, uint org, int exact)
1452 int i, a, fixup;
1453 Rune *r;
1454 uint n;
1456 if(org>0 && !exact){
1457 /* org is an estimate of the char posn; find a newline */
1458 /* don't try harder than 256 chars */
1459 for(i=0; i<256 && org<w->nr; i++){
1460 if(w->r[org] == '\n'){
1461 org++;
1462 break;
1464 org++;
1467 a = org-w->org;
1468 fixup = 0;
1469 if(a>=0 && a<w->f.nchars){
1470 frdelete(&w->f, 0, a);
1471 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1472 }else if(a<0 && -a<w->f.nchars){
1473 n = w->org - org;
1474 r = runemalloc(n);
1475 runemove(r, w->r+org, n);
1476 frinsert(&w->f, r, r+n, 0);
1477 free(r);
1478 }else
1479 frdelete(&w->f, 0, w->f.nchars);
1480 w->org = org;
1481 wfill(w);
1482 wscrdraw(w);
1483 wsetselect(w, w->q0, w->q1);
1484 if(fixup && w->f.p1 > w->f.p0)
1485 frdrawsel(&w->f, frptofchar(&w->f, w->f.p1-1), w->f.p1-1, w->f.p1, 1);
1488 void
1489 wsetselect(Window *w, uint q0, uint q1)
1491 int p0, p1;
1493 /* w->f.p0 and w->f.p1 are always right; w->q0 and w->q1 may be off */
1494 w->q0 = q0;
1495 w->q1 = q1;
1496 /* compute desired p0,p1 from q0,q1 */
1497 p0 = q0-w->org;
1498 p1 = q1-w->org;
1499 if(p0 < 0)
1500 p0 = 0;
1501 if(p1 < 0)
1502 p1 = 0;
1503 if(p0 > w->f.nchars)
1504 p0 = w->f.nchars;
1505 if(p1 > w->f.nchars)
1506 p1 = w->f.nchars;
1507 if(p0==w->f.p0 && p1==w->f.p1)
1508 return;
1509 /* screen disagrees with desired selection */
1510 if(w->f.p1<=p0 || p1<=w->f.p0 || p0==p1 || w->f.p1==w->f.p0){
1511 /* no overlap or too easy to bother trying */
1512 frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, w->f.p1, 0);
1513 frdrawsel(&w->f, frptofchar(&w->f, p0), p0, p1, 1);
1514 goto Return;
1516 /* overlap; avoid unnecessary painting */
1517 if(p0 < w->f.p0){
1518 /* extend selection backwards */
1519 frdrawsel(&w->f, frptofchar(&w->f, p0), p0, w->f.p0, 1);
1520 }else if(p0 > w->f.p0){
1521 /* trim first part of selection */
1522 frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, p0, 0);
1524 if(p1 > w->f.p1){
1525 /* extend selection forwards */
1526 frdrawsel(&w->f, frptofchar(&w->f, w->f.p1), w->f.p1, p1, 1);
1527 }else if(p1 < w->f.p1){
1528 /* trim last part of selection */
1529 frdrawsel(&w->f, frptofchar(&w->f, p1), p1, w->f.p1, 0);
1532 Return:
1533 w->f.p0 = p0;
1534 w->f.p1 = p1;
1537 uint
1538 winsert(Window *w, Rune *r, int n, uint q0)
1540 uint m;
1542 if(n == 0)
1543 return q0;
1544 if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
1545 m = min(HiWater-LoWater, min(w->org, w->qh));
1546 w->org -= m;
1547 w->qh -= m;
1548 if(w->q0 > m)
1549 w->q0 -= m;
1550 else
1551 w->q0 = 0;
1552 if(w->q1 > m)
1553 w->q1 -= m;
1554 else
1555 w->q1 = 0;
1556 w->nr -= m;
1557 runemove(w->r, w->r+m, w->nr);
1558 q0 -= m;
1560 if(w->nr+n > w->maxr){
1562 * Minimize realloc breakage:
1563 * Allocate at least MinWater
1564 * Double allocation size each time
1565 * But don't go much above HiWater
1567 m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
1568 if(m > HiWater)
1569 m = max(HiWater+MinWater, w->nr+n);
1570 if(m > w->maxr){
1571 w->r = runerealloc(w->r, m);
1572 w->maxr = m;
1575 runemove(w->r+q0+n, w->r+q0, w->nr-q0);
1576 runemove(w->r+q0, r, n);
1577 w->nr += n;
1578 /* if output touches, advance selection, not qh; works best for keyboard and output */
1579 if(q0 <= w->q1)
1580 w->q1 += n;
1581 if(q0 <= w->q0)
1582 w->q0 += n;
1583 if(q0 < w->qh)
1584 w->qh += n;
1585 if(q0 < w->org)
1586 w->org += n;
1587 else if(q0 <= w->org+w->f.nchars)
1588 frinsert(&w->f, r, r+n, q0-w->org);
1589 return q0;
1592 void
1593 wfill(Window *w)
1595 Rune *rp;
1596 int i, n, m, nl;
1598 if(w->f.lastlinefull)
1599 return;
1600 rp = malloc(messagesize);
1601 do{
1602 n = w->nr-(w->org+w->f.nchars);
1603 if(n == 0)
1604 break;
1605 if(n > 2000) /* educated guess at reasonable amount */
1606 n = 2000;
1607 runemove(rp, w->r+(w->org+w->f.nchars), n);
1609 * it's expensive to frinsert more than we need, so
1610 * count newlines.
1612 nl = w->f.maxlines-w->f.nlines;
1613 m = 0;
1614 for(i=0; i<n; ){
1615 if(rp[i++] == '\n'){
1616 m++;
1617 if(m >= nl)
1618 break;
1621 frinsert(&w->f, rp, rp+i, w->f.nchars);
1622 }while(w->f.lastlinefull == FALSE);
1623 free(rp);
1626 char*
1627 wcontents(Window *w, int *ip)
1629 return runetobyte(w->r, w->nr, ip);