diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2021-03-07 20:26:30 +0100 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2021-03-07 20:26:30 +0100 |
commit | b5783b1e39122f466c1a577720496284223e2217 (patch) | |
tree | 1f56ab52e2968a14fba725c3d5ec15b9114e1dd0 | |
parent | ea347ee7f181208be5a2ace0e35d268ccfefa1e4 (diff) | |
download | plan9front-b5783b1e39122f466c1a577720496284223e2217.tar.xz |
rio: make window focus changes deterministic, cleanup wind.c
Switching window focus used to be non deterministic
as the current window in focus (Window *input) was set
concurrently while processing window messages such as
Resized and Topped.
This implements a new approach where wcurrent() and
wuncurrent() are responsible for the synchronization
and switch of the input.
It is implemented by sending a Repaint message to the
old input window first, neccesarily waiting until that
window releases the focus and then input is updated
and then a Topped or Reshaped message is send to the
new input window.
Note, that when the whole screen is resized that no
input changes need to happening anymore.
-rw-r--r-- | sys/src/cmd/rio/dat.h | 22 | ||||
-rw-r--r-- | sys/src/cmd/rio/rio.c | 28 | ||||
-rw-r--r-- | sys/src/cmd/rio/wctl.c | 4 | ||||
-rw-r--r-- | sys/src/cmd/rio/wind.c | 2348 |
4 files changed, 1194 insertions, 1208 deletions
diff --git a/sys/src/cmd/rio/dat.h b/sys/src/cmd/rio/dat.h index edc855595..dac32da5f 100644 --- a/sys/src/cmd/rio/dat.h +++ b/sys/src/cmd/rio/dat.h @@ -183,45 +183,25 @@ Window* wtop(Point); void wtopme(Window*); void wbottomme(Window*); char* wcontents(Window*, int*); -int wbswidth(Window*, Rune); -int wclickmatch(Window*, int, int, int, uint*); int wclose(Window*); -int wctlmesg(Window*, int, Rectangle, void*); uint wbacknl(Window*, uint, uint); -uint winsert(Window*, Rune*, int, uint); -void waddraw(Window*, Rune*, int); -void wborder(Window*, int); -void wclunk(Window*); -void wclosewin(Window*); void wcurrent(Window*); +void wuncurrent(Window*); void wcut(Window*); -void wdelete(Window*, uint, uint); -void wstretchsel(Window*, uint, uint*, uint*, int); -void wfill(Window*); -void wframescroll(Window*, int); -void wkeyctl(Window*, Rune); -void wmousectl(Window*); -void wmovemouse(Window*, Point); void wpaste(Window*); void wplumb(Window*); void wlook(Window*); -void wrefresh(Window*); -void wrepaint(Window*); -void wresize(Window*, Image*); void wscrdraw(Window*); void wscroll(Window*, int); -void wselect(Window*); void wsend(Window*); void wsendctlmesg(Window*, int, Rectangle, void*); void wsetcursor(Window*, int); void wsetname(Window*); void wsetorigin(Window*, uint, int); void wsetpid(Window*, int, int); -void wsetselect(Window*, uint, uint); void wshow(Window*, uint); void wsnarf(Window*); void wscrsleep(Window*, uint); -void wsetcols(Window*, int); struct Dirtab { diff --git a/sys/src/cmd/rio/rio.c b/sys/src/cmd/rio/rio.c index a28fb2ccc..9c18a6955 100644 --- a/sys/src/cmd/rio/rio.c +++ b/sys/src/cmd/rio/rio.c @@ -530,8 +530,10 @@ mousethread(void*) else i = drag(winput); sweeping = FALSE; - if(i != nil) + if(i != nil){ + wcurrent(winput); wsendctlmesg(winput, Reshaped, i->r, i); + } wclose(winput); continue; } @@ -616,9 +618,10 @@ resized(void) if(j < nhidden){ im = allocimage(display, r, screen->chan, 0, DNofill); r = ZR; - } else + } else { im = allocwindow(wscreen, r, Refbackup, DNofill); - if(im) + } + if(im!=nil) wsendctlmesg(w, Reshaped, r, im); wclose(w); } @@ -1001,7 +1004,7 @@ delete(void) Window *w; w = pointto(TRUE); - if(w) + if(w!=nil) wsendctlmesg(w, Deleted, ZR, nil); } @@ -1016,8 +1019,10 @@ resize(void) return; incref(w); i = sweep(); - if(i) + if(i!=nil){ + wcurrent(w); wsendctlmesg(w, Reshaped, i->r, i); + } wclose(w); } @@ -1032,8 +1037,10 @@ move(void) return; incref(w); i = drag(w); - if(i) + if(i!=nil){ + wcurrent(w); wsendctlmesg(w, Reshaped, i->r, i); + } wclose(w); } @@ -1049,8 +1056,9 @@ whide(Window *w) if(nhidden >= nelem(hidden)) return 0; incref(w); + wuncurrent(w); i = allocimage(display, w->screenr, w->i->chan, 0, DNofill); - if(i){ + if(i!=nil){ hidden[nhidden++] = w; wsendctlmesg(w, Reshaped, ZR, i); } @@ -1070,8 +1078,9 @@ wunhide(Window *w) if(j == nhidden) return -1; /* not hidden */ incref(w); + wcurrent(w); i = allocwindow(wscreen, w->i->r, Refbackup, DNofill); - if(i){ + if(i!=nil){ --nhidden; memmove(hidden+j, hidden+j+1, (nhidden-j)*sizeof(Window*)); wsendctlmesg(w, Reshaped, w->i->r, i); @@ -1109,8 +1118,9 @@ unhide(int j) for(j=0; j<nwindow; j++) if(window[j] == w){ incref(w); - wtopme(w); wcurrent(w); + wtopme(w); + wsendctlmesg(w, Topped, ZR, nil); wclose(w); return; } diff --git a/sys/src/cmd/rio/wctl.c b/sys/src/cmd/rio/wctl.c index bef98fda9..b2de73e86 100644 --- a/sys/src/cmd/rio/wctl.c +++ b/sys/src/cmd/rio/wctl.c @@ -380,6 +380,7 @@ wctlcmd(Window *w, Rectangle r, int cmd, char *err) } else { /* hidden */ if(eqrect(r, w->i->r)) return 1; + wuncurrent(w); i = allocimage(display, r, w->i->chan, 0, DNofill); r = ZR; } @@ -409,8 +410,9 @@ wctlcmd(Window *w, Rectangle r, int cmd, char *err) strcpy(err, "window is hidden"); return -1; } - wtopme(w); wcurrent(w); + wtopme(w); + wsendctlmesg(w, Topped, ZR, nil); return 1; case Hide: switch(whide(w)){ diff --git a/sys/src/cmd/rio/wind.c b/sys/src/cmd/rio/wind.c index 1fd1b671c..6869cb8af 100644 --- a/sys/src/cmd/rio/wind.c +++ b/sys/src/cmd/rio/wind.c @@ -12,6 +12,142 @@ #include "dat.h" #include "fns.h" +Window* +wlookid(int id) +{ + int i; + + for(i=0; i<nwindow; i++) + if(window[i]->id == id) + return window[i]; + return nil; +} + +Window* +wpointto(Point pt) +{ + int i; + Window *v, *w; + + w = nil; + for(i=0; i<nwindow; i++){ + v = window[i]; + if(ptinrect(pt, v->screenr)) + if(w==nil || v->topped>w->topped) + w = v; + } + return w; +} + +static int topped; + +void +wtopme(Window *w) +{ + if(w!=nil && w->i!=nil && w->topped!=topped){ + w->topped = ++topped; + topwindow(w->i); + flushimage(display, 1); + } +} + +void +wbottomme(Window *w) +{ + if(w!=nil && w->i!=nil){ + w->topped = - ++topped; + bottomwindow(w->i); + flushimage(display, 1); + } +} + +Window* +wtop(Point pt) +{ + Window *w; + + w = wpointto(pt); + if(w!=nil){ + incref(w); + wcurrent(w); + wtopme(w); + wsendctlmesg(w, Topped, ZR, nil); + wclose(w); + } + return w; +} + +void +wcurrent(Window *w) +{ + Channel *c; + + if(input == nil){ + input = w; + return; + } + if(w == input) + return; + incref(input); + c = chancreate(sizeof(Window*), 0); + wsendctlmesg(input, Repaint, ZR, c); + sendp(c, w); /* send the new input */ + wclose(recvp(c)); /* release old input */ + chanfree(c); +} + +void +wuncurrent(Window *w) +{ + Channel *c; + + if(input == nil || w != input) + return; + c = chancreate(sizeof(Window*), 0); + wsendctlmesg(w, Repaint, ZR, c); + sendp(c, nil); + recvp(c); + chanfree(c); +} + +static Cursor *lastcursor; + +void +riosetcursor(Cursor *p) +{ + if(p==lastcursor) + return; + setcursor(mousectl, p); + lastcursor = p; +} + +void +wsetcursor(Window *w, int force) +{ + Cursor *p; + + if(menuing || sweeping || (w!=input && wpointto(mouse->xy)!=w)) + return; + if(w==nil) + p = nil; + else { + p = w->cursorp; + if(p==nil && w->holding) + p = &whitearrow; + } + if(p && force) /* force cursor reload */ + lastcursor = nil; + riosetcursor(p); +} + +static void +waddraw(Window *w, Rune *r, int nr) +{ + w->raw = runerealloc(w->raw, w->nraw+nr); + runemove(w->raw+w->nraw, r, nr); + w->nraw += nr; +} + enum { HiWater = 640000, /* max size of history */ @@ -19,49 +155,175 @@ enum MinWater = 20000, /* room to leave available when reallocating */ }; -static int topped; -static int id; -static Cursor *lastcursor; +static uint +winsert(Window *w, Rune *r, int n, uint q0) +{ + uint m; -Window* -wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling) + if(n == 0) + return q0; + if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){ + m = min(HiWater-LoWater, min(w->org, w->qh)); + w->org -= m; + w->qh -= m; + if(w->q0 > m) + w->q0 -= m; + else + w->q0 = 0; + if(w->q1 > m) + w->q1 -= m; + else + w->q1 = 0; + w->nr -= m; + runemove(w->r, w->r+m, w->nr); + q0 -= m; + } + if(w->nr+n > w->maxr){ + /* + * Minimize realloc breakage: + * Allocate at least MinWater + * Double allocation size each time + * But don't go much above HiWater + */ + m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater; + if(m > HiWater) + m = max(HiWater+MinWater, w->nr+n); + if(m > w->maxr){ + w->r = runerealloc(w->r, m); + w->maxr = m; + } + } + runemove(w->r+q0+n, w->r+q0, w->nr-q0); + runemove(w->r+q0, r, n); + w->nr += n; + /* if output touches, advance selection, not qh; works best for keyboard and output */ + if(q0 <= w->q1) + w->q1 += n; + if(q0 <= w->q0) + w->q0 += n; + if(q0 < w->qh) + w->qh += n; + if(q0 < w->org) + w->org += n; + else if(q0 <= w->org+w->nchars) + frinsert(w, r, r+n, q0-w->org); + return q0; +} + +static void +wfill(Window *w) { - Window *w; - Rectangle r; + Rune *rp; + int i, n, m, nl; - w = emalloc(sizeof(Window)); - w->screenr = i->r; - r = insetrect(i->r, Selborder+1); - w->i = i; - w->mc = *mc; - w->ck = ck; - w->cctl = cctl; - w->cursorp = nil; - w->conswrite = chancreate(sizeof(Conswritemesg), 0); - w->consread = chancreate(sizeof(Consreadmesg), 0); - w->kbdread = chancreate(sizeof(Consreadmesg), 0); - w->mouseread = chancreate(sizeof(Mousereadmesg), 0); - w->wctlread = chancreate(sizeof(Consreadmesg), 0); - w->complete = chancreate(sizeof(Completion*), 0); - w->gone = chancreate(sizeof(char*), 0); - w->scrollr = r; - w->scrollr.max.x = r.min.x+Scrollwid; - w->lastsr = ZR; - r.min.x += Scrollwid+Scrollgap; - frinit(w, r, font, i, cols); - w->maxtab = maxtab*stringwidth(font, "0"); - w->topped = ++topped; - w->id = ++id; - w->notefd = -1; - w->scrolling = scrolling; - w->dir = estrdup(startdir); - w->label = estrdup("<unnamed>"); - r = insetrect(w->i->r, Selborder); - draw(w->i, r, cols[BACK], nil, w->entire.min); - wborder(w, Selborder); - wscrdraw(w); - incref(w); /* ref will be removed after mounting; avoids delete before ready to be deleted */ - return w; + while(w->lastlinefull == FALSE){ + n = w->nr-(w->org+w->nchars); + if(n == 0) + break; + if(n > 2000) /* educated guess at reasonable amount */ + n = 2000; + rp = w->r+(w->org+w->nchars); + + /* + * it's expensive to frinsert more than we need, so + * count newlines. + */ + nl = w->maxlines-w->nlines; + m = 0; + for(i=0; i<n; ){ + if(rp[i++] == '\n'){ + m++; + if(m >= nl) + break; + } + } + frinsert(w, rp, rp+i, w->nchars); + } +} + +static void +wsetselect(Window *w, uint q0, uint q1) +{ + int p0, p1; + + /* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */ + w->q0 = q0; + w->q1 = q1; + /* compute desired p0,p1 from q0,q1 */ + p0 = q0-w->org; + p1 = q1-w->org; + if(p0 < 0) + p0 = 0; + if(p1 < 0) + p1 = 0; + if(p0 > w->nchars) + p0 = w->nchars; + if(p1 > w->nchars) + p1 = w->nchars; + if(p0==w->p0 && p1==w->p1) + return; + /* screen disagrees with desired selection */ + if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){ + /* no overlap or too easy to bother trying */ + frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0); + frdrawsel(w, frptofchar(w, p0), p0, p1, 1); + goto Return; + } + /* overlap; avoid unnecessary painting */ + if(p0 < w->p0){ + /* extend selection backwards */ + frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1); + }else if(p0 > w->p0){ + /* trim first part of selection */ + frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0); + } + if(p1 > w->p1){ + /* extend selection forwards */ + frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1); + }else if(p1 < w->p1){ + /* trim last part of selection */ + frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0); + } + + Return: + w->p0 = p0; + w->p1 = p1; +} + +static void +wborder(Window *w, int type) +{ + Image *col; + + if(w->i == nil) + return; + if(w->holding){ + if(type == Selborder) + col = holdcol; + else + col = paleholdcol; + }else{ + if(type == Selborder) + col = titlecol; + else + col = lighttitlecol; + } + border(w->i, w->i->r, Selborder, col, ZP); +} + +static void +wsetcols(Window *w, int topped) +{ + if(w->holding) + if(topped) + w->cols[TEXT] = holdcol; + else + w->cols[TEXT] = lightholdcol; + else + if(topped) + w->cols[TEXT] = cols[TEXT]; + else + w->cols[TEXT] = paletextcol; } void @@ -84,12 +346,11 @@ wsetname(Window *w) fprint(2, "rio: setname failed: %s\n", err); } -void +static void wresize(Window *w, Image *i) { Rectangle r; - wclosewin(w); w->i = i; w->mc.image = i; r = insetrect(i->r, Selborder+1); @@ -99,7 +360,7 @@ wresize(Window *w, Image *i) r.min.x += Scrollwid+Scrollgap; frclear(w, FALSE); frinit(w, r, w->font, w->i, cols); - wsetcols(w, 1); + wsetcols(w, w == input); w->maxtab = maxtab*stringwidth(w->font, "0"); if(!w->mouseopen || !w->winnameread){ r = insetrect(w->i->r, Selborder); @@ -108,7 +369,10 @@ wresize(Window *w, Image *i) wsetselect(w, w->q0, w->q1); wscrdraw(w); } - wborder(w, Selborder); + if(w == input) + wborder(w, Selborder); + else + wborder(w, Unselborder); flushimage(display, 1); wsetname(w); w->topped = ++topped; @@ -118,7 +382,19 @@ wresize(Window *w, Image *i) w->wctlready = 1; } -void +static void +wrepaint(Window *w) +{ + wsetcols(w, w == input); + if(!w->mouseopen || !w->winnameread) + frredraw(w); + if(w == input) + wborder(w, Selborder); + else + wborder(w, Unselborder); +} + +static void wrefresh(Window *w) { Rectangle r; @@ -140,316 +416,11 @@ wrefresh(Window *w) wscrdraw(w); } -int -wclose(Window *w) -{ - int i; - - i = decref(w); - if(i > 0) - return 0; - if(i < 0) - error("negative ref count"); - wclunk(w); - wsendctlmesg(w, Exited, ZR, nil); - return 1; -} - -void -showcandidates(Window *, Completion *); - -void -winctl(void *arg) -{ - Rune *rp, *up, r; - uint qh, q0; - int nr, nb, c, wid, i, npart, initial, lastb; - char *s, *t, part[3]; - Window *w; - Mousestate *mp, m; - enum { WKbd, WKbdread, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, WComplete, Wgone, NWALT }; - Alt alts[NWALT+1]; - Consreadmesg crm; - Mousereadmesg mrm; - Conswritemesg cwm; - Stringpair pair; - Wctlmesg wcm; - Completion *cr; - char *kbdq[32], *kbds; - uint kbdqr, kbdqw; - - w = arg; - threadsetname("winctl-id%d", w->id); - - mrm.cm = chancreate(sizeof(Mouse), 0); - crm.c1 = chancreate(sizeof(Stringpair), 0); - crm.c2 = chancreate(sizeof(Stringpair), 0); - cwm.cw = chancreate(sizeof(Stringpair), 0); - - alts[WKbd].c = w->ck; - alts[WKbd].v = &kbds; - alts[WKbd].op = CHANRCV; - alts[WKbdread].c = w->kbdread; - alts[WKbdread].v = &crm; - alts[WKbdread].op = CHANSND; - alts[WMouse].c = w->mc.c; - alts[WMouse].v = &w->mc.Mouse; - alts[WMouse].op = CHANRCV; - alts[WMouseread].c = w->mouseread; - alts[WMouseread].v = &mrm; - alts[WMouseread].op = CHANSND; - alts[WCtl].c = w->cctl; - alts[WCtl].v = &wcm; - alts[WCtl].op = CHANRCV; - alts[WCwrite].c = w->conswrite; - alts[WCwrite].v = &cwm; - alts[WCwrite].op = CHANSND; - alts[WCread].c = w->consread; - alts[WCread].v = &crm; - alts[WCread].op = CHANSND; - alts[WWread].c = w->wctlread; - alts[WWread].v = &crm; - alts[WWread].op = CHANSND; - alts[WComplete].c = w->complete; - alts[WComplete].v = &cr; - alts[WComplete].op = CHANRCV; - alts[Wgone].c = w->gone; - alts[Wgone].v = "window deleted"; - alts[Wgone].op = CHANNOP; - alts[NWALT].op = CHANEND; - - kbdqr = kbdqw = 0; - npart = 0; - lastb = -1; - for(;;){ - if(w->i==nil){ - /* window deleted */ - alts[Wgone].op = CHANSND; - - alts[WKbdread].op = CHANNOP; - alts[WMouseread].op = CHANNOP; - alts[WCwrite].op = CHANNOP; - alts[WWread].op = CHANNOP; - alts[WCread].op = CHANNOP; - } else { - alts[WKbdread].op = (w->kbdopen && kbdqw != kbdqr) ? - CHANSND : CHANNOP; - alts[WMouseread].op = (w->mouseopen && w->mouse.counter != w->mouse.lastcounter) ? - CHANSND : CHANNOP; - alts[WCwrite].op = w->scrolling || w->mouseopen || (w->qh <= w->org+w->nchars) ? - CHANSND : CHANNOP; - alts[WWread].op = w->wctlready ? - CHANSND : CHANNOP; - /* this code depends on NL and EOT fitting in a single byte */ - /* kind of expensive for each loop; worth precomputing? */ - if(w->holding) - alts[WCread].op = CHANNOP; - else if(npart || (w->rawing && w->nraw>0)) - alts[WCread].op = CHANSND; - else{ - alts[WCread].op = CHANNOP; - for(i=w->qh; i<w->nr; i++){ - c = w->r[i]; - if(c=='\n' || c=='\004'){ - alts[WCread].op = CHANSND; - break; - } - } - } - } - switch(alt(alts)){ - case WKbd: - if(kbdqw - kbdqr < nelem(kbdq)) - kbdq[kbdqw++ % nelem(kbdq)] = kbds; - else - free(kbds); - if(w->kbdopen) - continue; - while(kbdqr != kbdqw){ - kbds = kbdq[kbdqr++ % nelem(kbdq)]; - if(*kbds == 'c'){ - chartorune(&r, kbds+1); - if(r) - wkeyctl(w, r); - } - free(kbds); - } - break; - case WKbdread: - recv(crm.c1, &pair); - nb = 0; - while(kbdqr != kbdqw){ - kbds = kbdq[kbdqr % nelem(kbdq)]; - i = strlen(kbds)+1; - if(nb+i > pair.ns) - break; - memmove((char*)pair.s + nb, kbds, i); - free(kbds); - nb += i; - kbdqr++; - } - pair.ns = nb; - send(crm.c2, &pair); - continue; - case WMouse: - if(w->mouseopen) { - w->mouse.counter++; - - /* queue click events */ - if(!w->mouse.qfull && lastb != w->mc.buttons) { /* add to ring */ - mp = &w->mouse.queue[w->mouse.wi]; - if(++w->mouse.wi == nelem(w->mouse.queue)) - w->mouse.wi = 0; - if(w->mouse.wi == w->mouse.ri) - w->mouse.qfull = TRUE; - mp->Mouse = w->mc; - mp->counter = w->mouse.counter; - lastb = w->mc.buttons; - } - } else - wmousectl(w); - break; - case WMouseread: - /* send a queued event or, if the queue is empty, the current state */ - /* if the queue has filled, we discard all the events it contained. */ - /* the intent is to discard frantic clicking by the user during long latencies. */ - w->mouse.qfull = FALSE; - if(w->mouse.wi != w->mouse.ri) { - m = w->mouse.queue[w->mouse.ri]; - if(++w->mouse.ri == nelem(w->mouse.queue)) - w->mouse.ri = 0; - } else - m = (Mousestate){w->mc.Mouse, w->mouse.counter}; - - w->mouse.lastcounter = m.counter; - send(mrm.cm, &m.Mouse); - continue; - case WCtl: - if(wctlmesg(w, wcm.type, wcm.r, wcm.p) == Exited){ - while(kbdqr != kbdqw) - free(kbdq[kbdqr++ % nelem(kbdq)]); - chanfree(crm.c1); - chanfree(crm.c2); - chanfree(mrm.cm); - chanfree(cwm.cw); - threadexits(nil); - } - continue; - case WCwrite: - recv(cwm.cw, &pair); - rp = pair.s; - nr = pair.ns; - for(i=0; i<nr; i++) - if(rp[i] == '\b'){ - up = rp+i; - initial = 0; - for(; i<nr; i++){ - if(rp[i] == '\b'){ - if(up == rp) - initial++; - else - up--; - }else - *up++ = rp[i]; - } - if(initial){ - if(initial > w->qh) - initial = w->qh; - qh = w->qh-initial; - wdelete(w, qh, qh+initial); - w->qh = qh; - } - nr = up - rp; - break; - } - w->qh = winsert(w, rp, nr, w->qh)+nr; - if(w->scrolling || w->mouseopen) - wshow(w, w->qh); - wsetselect(w, w->q0, w->q1); - wscrdraw(w); - free(rp); - break; - case WCread: - recv(crm.c1, &pair); - t = pair.s; - nb = pair.ns; - i = npart; - npart = 0; - if(i) - memmove(t, part, i); - while(i<nb && (w->qh<w->nr || w->nraw>0)){ - if(w->qh == w->nr){ - wid = runetochar(t+i, &w->raw[0]); - w->nraw--; - runemove(w->raw, w->raw+1, w->nraw); - }else - wid = runetochar(t+i, &w->r[w->qh++]); - c = t[i]; /* knows break characters fit in a byte */ - i += wid; - if(!w->rawing && (c == '\n' || c=='\004')){ - if(c == '\004') - i--; - break; - } - } - if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004') - w->qh++; - if(i > nb){ - npart = i-nb; - memmove(part, t+nb, npart); - i = nb; - } - pair.s = t; - pair.ns = i; - send(crm.c2, &pair); - continue; - case WWread: - w->wctlready = 0; - recv(crm.c1, &pair); - s = Dx(w->screenr) > 0 ? "visible" : "hidden"; - t = "notcurrent"; - if(w == input) - t = "current"; - pair.ns = snprint(pair.s, pair.ns+1, "%11d %11d %11d %11d %11s %11s ", - w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s); - send(crm.c2, &pair); - continue; - case WComplete: - if(w->i!=nil){ - if(!cr->advance) - showcandidates(w, cr); - if(cr->advance){ - rp = runesmprint("%s", cr->string); - if(rp){ - nr = runestrlen(rp); - q0 = w->q0; - q0 = winsert(w, rp, nr, q0); - wshow(w, q0+nr); - free(rp); - } - } - } - freecompletion(cr); - break; - } - if(w->i!=nil && Dx(w->screenr) > 0 && display->bufp > display->buf) - flushimage(display, 1); - } -} - -void -waddraw(Window *w, Rune *r, int nr) -{ - w->raw = runerealloc(w->raw, w->nraw+nr); - runemove(w->raw+w->nraw, r, nr); - w->nraw += nr; -} - /* * Need to do this in a separate proc because if process we're interrupting * is dying and trying to print tombstone, kernel is blocked holding p->debug lock. */ -void +static void interruptproc(void *v) { int *notefd; @@ -460,7 +431,35 @@ interruptproc(void *v) free(notefd); } -int +typedef struct Completejob Completejob; +struct Completejob +{ + char *dir; + char *str; + Window *win; +}; + +static void +completeproc(void *arg) +{ + Completejob *job; + Completion *c; + + job = arg; + threadsetname("namecomplete %s", job->dir); + + c = complete(job->dir, job->str); + if(c != nil && sendp(job->win->complete, c) <= 0) + freecompletion(c); + + wclose(job->win); + + free(job->dir); + free(job->str); + free(job); +} + +static int windfilewidth(Window *w, uint q0, int oneelement) { uint q; @@ -478,7 +477,45 @@ windfilewidth(Window *w, uint q0, int oneelement) return q0-q; } -void +static void +namecomplete(Window *w) +{ + int nstr, npath; + Rune *path, *str; + char *dir, *root; + Completejob *job; + + /* control-f: filename completion; works back to white space or / */ + if(w->q0<w->nr && w->r[w->q0]>' ') /* must be at end of word */ + return; + nstr = windfilewidth(w, w->q0, TRUE); + str = w->r+(w->q0-nstr); + npath = windfilewidth(w, w->q0-nstr, FALSE); + path = w->r+(w->q0-nstr-npath); + + /* is path rooted? if not, we need to make it relative to window path */ + if(npath>0 && path[0]=='/') + dir = runetobyte(path, npath, &npath); + else { + if(strcmp(w->dir, "") == 0) + root = "."; + else + root = w->dir; + dir = smprint("%s/%.*S", root, npath, path); + } + if(dir == nil) + return; + + /* run in background, winctl will collect the result on w->complete chan */ + job = emalloc(sizeof *job); + job->str = runetobyte(str, nstr, &nstr); + job->dir = cleanname(dir); + job->win = w; + incref(w); + proccreate(completeproc, job, STACK); +} + +static void showcandidates(Window *w, Completion *c) { int i; @@ -520,73 +557,288 @@ showcandidates(Window *w, Completion *c) free(rp); } -typedef struct Completejob Completejob; -struct Completejob +static int +wbswidth(Window *w, Rune c) { - char *dir; - char *str; - Window *win; -}; + uint q, eq, stop; + Rune r; + int skipping; + + /* there is known to be at least one character to erase */ + if(c == 0x08) /* ^H: erase character */ + return 1; + q = w->q0; + stop = 0; + if(q > w->qh) + stop = w->qh; + skipping = TRUE; + while(q > stop){ + r = w->r[q-1]; + if(r == '\n'){ /* eat at most one more character */ + if(q == w->q0) /* eat the newline */ + --q; + break; + } + if(c == 0x17){ + eq = isalnum(r); + if(eq && skipping) /* found one; stop skipping */ + skipping = FALSE; + else if(!eq && !skipping) + break; + } + --q; + } + return w->q0-q; +} void -completeproc(void *arg) +wsetorigin(Window *w, uint org, int exact) { - Completejob *job; - Completion *c; + int i, a, fixup; + Rune *r; + uint n; - job = arg; - threadsetname("namecomplete %s", job->dir); + if(org>0 && !exact){ + /* org is an estimate of the char posn; find a newline */ + /* don't try harder than 256 chars */ + for(i=0; i<256 && org<w->nr; i++){ + if(w->r[org] == '\n'){ + org++; + break; + } + org++; + } + } + a = org-w->org; + fixup = 0; + if(a>=0 && a<w->nchars){ + frdelete(w, 0, a); + fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */ + }else if(a<0 && -a<w->nchars){ + n = w->org - org; + r = w->r+org; + frinsert(w, r, r+n, 0); + }else + frdelete(w, 0, w->nchars); + w->org = org; + wfill(w); + wscrdraw(w); + wsetselect(w, w->q0, w->q1); + if(fixup && w->p1 > w->p0) + frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1); +} - c = complete(job->dir, job->str); - if(c != nil && sendp(job->win->complete, c) <= 0) - freecompletion(c); +uint +wbacknl(Window *w, uint p, uint n) +{ + int i, j; - wclose(job->win); + /* look for start of this line if n==0 */ + if(n==0 && p>0 && w->r[p-1]!='\n') + n = 1; + i = n; + while(i-->0 && p>0){ + --p; /* it's at a newline now; back over it */ + if(p == 0) + break; + /* at 128 chars, call it a line anyway */ + for(j=128; --j>0 && p>0; p--) + if(w->r[p-1]=='\n') + break; + } + return p; +} - free(job->dir); - free(job->str); - free(job); +char* +wcontents(Window *w, int *ip) +{ + return runetobyte(w->r, w->nr, ip); } void -namecomplete(Window *w) +wshow(Window *w, uint q0) { - int nstr, npath; - Rune *path, *str; - char *dir, *root; - Completejob *job; + int qe; + int nl; + uint q; - /* control-f: filename completion; works back to white space or / */ - if(w->q0<w->nr && w->r[w->q0]>' ') /* must be at end of word */ + qe = w->org+w->nchars; + if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr))) + wscrdraw(w); + else{ + nl = 4*w->maxlines/5; + q = wbacknl(w, q0, nl); + /* avoid going backwards if trying to go forwards - long lines! */ + if(!(q0>w->org && q<w->org)) + wsetorigin(w, q, TRUE); + while(q0 > w->org+w->nchars) + wsetorigin(w, w->org+1, FALSE); + } +} + +void +wsnarf(Window *w) +{ + if(w->q1 == w->q0) return; - nstr = windfilewidth(w, w->q0, TRUE); - str = w->r+(w->q0-nstr); - npath = windfilewidth(w, w->q0-nstr, FALSE); - path = w->r+(w->q0-nstr-npath); + nsnarf = w->q1-w->q0; + snarf = runerealloc(snarf, nsnarf); + snarfversion++; /* maybe modified by parent */ + runemove(snarf, w->r+w->q0, nsnarf); + putsnarf(); +} - /* is path rooted? if not, we need to make it relative to window path */ - if(npath>0 && path[0]=='/') - dir = runetobyte(path, npath, &npath); - else { - if(strcmp(w->dir, "") == 0) - root = "."; - else - root = w->dir; - dir = smprint("%s/%.*S", root, npath, path); +void +wsend(Window *w) +{ + getsnarf(); + wsnarf(w); + if(nsnarf == 0) + return; + if(w->rawing){ + waddraw(w, snarf, nsnarf); + if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') + waddraw(w, L"\n", 1); + }else{ + winsert(w, snarf, nsnarf, w->nr); + if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') + winsert(w, L"\n", 1, w->nr); } - if(dir == nil) + wsetselect(w, w->nr, w->nr); + wshow(w, w->nr); +} + +static void +wdelete(Window *w, uint q0, uint q1) +{ + uint n, p0, p1; + + n = q1-q0; + if(n == 0) return; + runemove(w->r+q0, w->r+q1, w->nr-q1); + w->nr -= n; + if(q0 < w->q0) + w->q0 -= min(n, w->q0-q0); + if(q0 < w->q1) + w->q1 -= min(n, w->q1-q0); + if(q1 < w->qh) + w->qh -= n; + else if(q0 < w->qh) + w->qh = q0; + if(q1 <= w->org) + w->org -= n; + else if(q0 < w->org+w->nchars){ + p1 = q1 - w->org; + if(p1 > w->nchars) + p1 = w->nchars; + if(q0 < w->org){ + w->org = q0; + p0 = 0; + }else + p0 = q0 - w->org; + frdelete(w, p0, p1); + wfill(w); + } +} - /* run in background, winctl will collect the result on w->complete chan */ - job = emalloc(sizeof *job); - job->str = runetobyte(str, nstr, &nstr); - job->dir = cleanname(dir); - job->win = w; - incref(w); - proccreate(completeproc, job, STACK); +void +wcut(Window *w) +{ + if(w->q1 == w->q0) + return; + wdelete(w, w->q0, w->q1); + wsetselect(w, w->q0, w->q0); +} + +void +wpaste(Window *w) +{ + uint q0; + + if(nsnarf == 0) + return; + wcut(w); + q0 = w->q0; + if(w->rawing && q0==w->nr){ + waddraw(w, snarf, nsnarf); + wsetselect(w, q0, q0); + }else{ + q0 = winsert(w, snarf, nsnarf, w->q0); + wsetselect(w, q0, q0+nsnarf); + } +} + +void +wlook(Window *w) +{ + int i, n, e; + + i = w->q1; + n = i - w->q0; + e = w->nr - n; + if(n <= 0 || e < n) + return; + + if(i > e) + i = 0; + + while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){ + if(i < e) + i++; + else + i = 0; + } + + wsetselect(w, i, i+n); + wshow(w, i); } void +wplumb(Window *w) +{ + Plumbmsg *m; + static int fd = -2; + char buf[32]; + uint p0, p1; + Cursor *c; + + if(fd == -2) + fd = plumbopen("send", OWRITE|OCEXEC); + if(fd < 0) + return; + m = emalloc(sizeof(Plumbmsg)); + m->src = estrdup("rio"); + m->dst = nil; + m->wdir = estrdup(w->dir); + m->type = estrdup("text"); + p0 = w->q0; + p1 = w->q1; + if(w->q1 > w->q0) + m->attr = nil; + else{ + while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n') + p0--; + while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n') + p1++; + snprint(buf, sizeof(buf), "click=%d", w->q0-p0); + m->attr = plumbunpackattr(buf); + } + if(p1-p0 > messagesize-1024){ + plumbfree(m); + return; /* too large for 9P */ + } + m->data = runetobyte(w->r+p0, p1-p0, &m->ndata); + if(plumbsend(fd, m) < 0){ + c = lastcursor; + riosetcursor(&query); + sleep(300); + riosetcursor(c); + } + plumbfree(m); +} + +static void wkeyctl(Window *w, Rune r) { uint q0 ,q1; @@ -736,234 +988,6 @@ wkeyctl(Window *w, Rune r) wshow(w, q0+1); } -void -wsetcols(Window *w, int topped) -{ - if(w->holding) - if(topped) - w->cols[TEXT] = holdcol; - else - w->cols[TEXT] = lightholdcol; - else - if(topped) - w->cols[TEXT] = cols[TEXT]; - else - w->cols[TEXT] = paletextcol; -} - -void -wrepaint(Window *w) -{ - wsetcols(w, w == input); - if(!w->mouseopen || !w->winnameread) - frredraw(w); - if(w == input) - wborder(w, Selborder); - else - wborder(w, Unselborder); -} - -int -wbswidth(Window *w, Rune c) -{ - uint q, eq, stop; - Rune r; - int skipping; - - /* there is known to be at least one character to erase */ - if(c == 0x08) /* ^H: erase character */ - return 1; - q = w->q0; - stop = 0; - if(q > w->qh) - stop = w->qh; - skipping = TRUE; - while(q > stop){ - r = w->r[q-1]; - if(r == '\n'){ /* eat at most one more character */ - if(q == w->q0) /* eat the newline */ - --q; - break; - } - if(c == 0x17){ - eq = isalnum(r); - if(eq && skipping) /* found one; stop skipping */ - skipping = FALSE; - else if(!eq && !skipping) - break; - } - --q; - } - return w->q0-q; -} - -void -wsnarf(Window *w) -{ - if(w->q1 == w->q0) - return; - nsnarf = w->q1-w->q0; - snarf = runerealloc(snarf, nsnarf); - snarfversion++; /* maybe modified by parent */ - runemove(snarf, w->r+w->q0, nsnarf); - putsnarf(); -} - -void -wcut(Window *w) -{ - if(w->q1 == w->q0) - return; - wdelete(w, w->q0, w->q1); - wsetselect(w, w->q0, w->q0); -} - -void -wpaste(Window *w) -{ - uint q0; - - if(nsnarf == 0) - return; - wcut(w); - q0 = w->q0; - if(w->rawing && q0==w->nr){ - waddraw(w, snarf, nsnarf); - wsetselect(w, q0, q0); - }else{ - q0 = winsert(w, snarf, nsnarf, w->q0); - wsetselect(w, q0, q0+nsnarf); - } -} - -void -wplumb(Window *w) -{ - Plumbmsg *m; - static int fd = -2; - char buf[32]; - uint p0, p1; - Cursor *c; - - if(fd == -2) - fd = plumbopen("send", OWRITE|OCEXEC); - if(fd < 0) - return; - m = emalloc(sizeof(Plumbmsg)); - m->src = estrdup("rio"); - m->dst = nil; - m->wdir = estrdup(w->dir); - m->type = estrdup("text"); - p0 = w->q0; - p1 = w->q1; - if(w->q1 > w->q0) - m->attr = nil; - else{ - while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n') - p0--; - while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n') - p1++; - snprint(buf, sizeof(buf), "click=%d", w->q0-p0); - m->attr = plumbunpackattr(buf); - } - if(p1-p0 > messagesize-1024){ - plumbfree(m); - return; /* too large for 9P */ - } - m->data = runetobyte(w->r+p0, p1-p0, &m->ndata); - if(plumbsend(fd, m) < 0){ - c = lastcursor; - riosetcursor(&query); - sleep(300); - riosetcursor(c); - } - plumbfree(m); -} - -void -wlook(Window *w) -{ - int i, n, e; - - i = w->q1; - n = i - w->q0; - e = w->nr - n; - if(n <= 0 || e < n) - return; - - if(i > e) - i = 0; - - while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){ - if(i < e) - i++; - else - i = 0; - } - - wsetselect(w, i, i+n); - wshow(w, i); -} - -void -wmousectl(Window *w) -{ - int but; - - for(but=1;; but++){ - if(but > 5) - return; - if(w->mc.buttons == 1<<(but-1)) - break; - } - - incref(w); /* hold up window while we track */ - if(w->i != nil){ - if(shiftdown && but > 3) - wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown); - else if(ptinrect(w->mc.xy, w->scrollr) || (but > 3)) - wscroll(w, but); - else if(but == 1) - wselect(w); - } - wclose(w); -} - -void -wdelete(Window *w, uint q0, uint q1) -{ - uint n, p0, p1; - - n = q1-q0; - if(n == 0) - return; - runemove(w->r+q0, w->r+q1, w->nr-q1); - w->nr -= n; - if(q0 < w->q0) - w->q0 -= min(n, w->q0-q0); - if(q0 < w->q1) - w->q1 -= min(n, w->q1-q0); - if(q1 < w->qh) - w->qh -= n; - else if(q0 < w->qh) - w->qh = q0; - if(q1 <= w->org) - w->org -= n; - else if(q0 < w->org+w->nchars){ - p1 = q1 - w->org; - if(p1 > w->nchars) - p1 = w->nchars; - if(q0 < w->org){ - w->org = q0; - p0 = 0; - }else - p0 = q0 - w->org; - frdelete(w, p0, p1); - wfill(w); - } -} - - static Window *clickwin; static uint clickmsec; static Point clickpt; @@ -971,18 +995,7 @@ static uint clickcount; static Window *selectwin; static uint selectq; -/* - * called from frame library - */ -void -framescroll(Frame *f, int dl) -{ - if(f != &selectwin->Frame) - error("frameselect not right frame"); - wframescroll(selectwin, dl); -} - -void +static void wframescroll(Window *w, int dl) { uint q0; @@ -1009,7 +1022,118 @@ wframescroll(Window *w, int dl) wsetorigin(w, q0, TRUE); } -void +/* + * called from frame library + */ +static void +framescroll(Frame *f, int dl) +{ + if(f != &selectwin->Frame) + error("frameselect not right frame"); + wframescroll(selectwin, dl); +} + +static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 }; +static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 }; +static Rune left2[] = { L'\n', 0 }; +static Rune left3[] = { L'\'', L'"', L'`', 0 }; + +static Rune *left[] = { + left1, + left2, + left3, + nil +}; +static Rune *right[] = { + right1, + left2, + left3, + nil +}; + +static int +wclickmatch(Window *w, int cl, int cr, int dir, uint *q) +{ + Rune c; + int nest; + + nest = 1; + for(;;){ + if(dir > 0){ + if(*q == w->nr) + break; + c = w->r[*q]; + (*q)++; + }else{ + if(*q == 0) + break; + (*q)--; + c = w->r[*q]; + } + if(c == cr){ + if(--nest==0) + return 1; + }else if(c == cl) + nest++; + } + return cl=='\n' && nest==1; +} + +static int +inmode(Rune r, int mode) +{ + return (mode == 1) ? isalnum(r) : r && !isspace(r); +} + +static void +wstretchsel(Window *w, uint pt, uint *q0, uint *q1, int mode) +{ + int c, i; + Rune *r, *l, *p; + uint q; + + *q0 = pt; + *q1 = pt; + for(i=0; left[i]!=nil; i++){ + q = *q0; + l = left[i]; + r = right[i]; + /* try matching character to left, looking right */ + if(q == 0) + c = '\n'; + else + c = w->r[q-1]; + p = strrune(l, c); + if(p != nil){ + if(wclickmatch(w, c, r[p-l], 1, &q)) + *q1 = q-(c!='\n'); + return; + } + /* try matching character to right, looking left */ + if(q == w->nr) + c = '\n'; + else + c = w->r[q]; + p = strrune(r, c); + if(p != nil){ + if(wclickmatch(w, c, l[p-r], -1, &q)){ + *q1 = *q0+(*q0<w->nr && c=='\n'); + *q0 = q; + if(c!='\n' || q!=0 || w->r[0]=='\n') + (*q0)++; + } + return; + } + } + /* try filling out word to right */ + while(*q1<w->nr && inmode(w->r[*q1], mode)) + (*q1)++; + /* try filling out word to left */ + while(*q0>0 && inmode(w->r[*q0-1], mode)) + (*q0)--; +} + +static void wselect(Window *w) { uint q0, q1; @@ -1100,6 +1224,118 @@ wselect(Window *w) } } +/* + * Convert back to physical coordinates + */ +static void +wmovemouse(Window *w, Point p) +{ + if(w != input || menuing || sweeping) + return; + p.x += w->screenr.min.x-w->i->r.min.x; + p.y += w->screenr.min.y-w->i->r.min.y; + moveto(mousectl, p); +} + + +Window* +wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling) +{ + static int id; + + Window *w; + Rectangle r; + + w = emalloc(sizeof(Window)); + w->screenr = i->r; + r = insetrect(i->r, Selborder+1); + w->i = i; + w->mc = *mc; + w->ck = ck; + w->cctl = cctl; + w->cursorp = nil; + w->conswrite = chancreate(sizeof(Conswritemesg), 0); + w->consread = chancreate(sizeof(Consreadmesg), 0); + w->kbdread = chancreate(sizeof(Consreadmesg), 0); + w->mouseread = chancreate(sizeof(Mousereadmesg), 0); + w->wctlread = chancreate(sizeof(Consreadmesg), 0); + w->complete = chancreate(sizeof(Completion*), 0); + w->gone = chancreate(sizeof(char*), 0); + w->scrollr = r; + w->scrollr.max.x = r.min.x+Scrollwid; + w->lastsr = ZR; + r.min.x += Scrollwid+Scrollgap; + frinit(w, r, font, i, cols); + w->maxtab = maxtab*stringwidth(font, "0"); + w->topped = ++topped; + w->id = ++id; + w->notefd = -1; + w->scrolling = scrolling; + w->dir = estrdup(startdir); + w->label = estrdup("<unnamed>"); + r = insetrect(w->i->r, Selborder); + draw(w->i, r, cols[BACK], nil, w->entire.min); + wborder(w, Selborder); + wscrdraw(w); + incref(w); /* ref will be removed after mounting; avoids delete before ready to be deleted */ + return w; +} + +static void +wclosewin(Window *w) +{ + Image *i = w->i; + if(i == nil) + return; + w->i = nil; + /* move it off-screen to hide it, in case client is slow in letting it go */ + originwindow(i, i->r.min, view->r.max); + freeimage(i); +} + +static void +wclunk(Window *w) +{ + int i; + + if(w->deleted) + return; + w->deleted = TRUE; + if(w == input){ + input = nil; + riosetcursor(nil); + } + if(w == wkeyboard) + wkeyboard = nil; + for(i=0; i<nhidden; i++) + if(hidden[i] == w){ + --nhidden; + memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0])); + break; + } + for(i=0; i<nwindow; i++) + if(window[i] == w){ + --nwindow; + memmove(window+i, window+i+1, (nwindow-i)*sizeof(window[0])); + break; + } +} + +int +wclose(Window *w) +{ + int i; + + i = decref(w); + if(i > 0) + return 0; + if(i < 0) + error("negative ref count"); + wclunk(w); + wsendctlmesg(w, Exited, ZR, nil); + return 1; +} + void wsendctlmesg(Window *w, int type, Rectangle r, void *p) { @@ -1111,7 +1347,7 @@ wsendctlmesg(Window *w, int type, Rectangle r, void *p) send(w->cctl, &wcm); } -int +static int wctlmesg(Window *w, int m, Rectangle r, void *p) { Image *i = p; @@ -1121,8 +1357,6 @@ wctlmesg(Window *w, int m, Rectangle r, void *p) error("unknown control message"); break; case Wakeup: - if(p!=nil) - sendp((Channel*)p, w); break; case Reshaped: if(w->deleted){ @@ -1130,54 +1364,23 @@ wctlmesg(Window *w, int m, Rectangle r, void *p) break; } w->screenr = r; + wclosewin(w); wresize(w, i); - if(Dx(r)<=0){ /* window got hidden, if we had the input, drop it */ - if(w==input) - input = nil; - break; - } - /* fall through to get input if needed */ + wsetcursor(w, FALSE); + break; case Topped: - if(w->deleted || w==input) + if(w->deleted) break; - if(input!=nil){ - Window *oi; - Channel *c; - - oi = input; - incref(oi); - - /* - * have to wait until old input responds before - * changing input to us because the window might - * currently be mouse tracking and it is not - * prepared for getting its input revoked. - */ - c = chancreate(sizeof(void*), 0); - wsendctlmesg(oi, Wakeup, ZR, c); - recv(c, nil); - chanfree(c); - - /* - * if we are still top window and nobody else has taken - * input from original window, take the input. - */ - if(!w->deleted && w->topped==topped && oi==input){ - input = w; - - oi->wctlready = 1; - wsendctlmesg(oi, Repaint, ZR, nil); - } - wclose(oi); - } else { - input = w; - wsetcursor(w, FALSE); - } w->wctlready = 1; - if(m!=Topped && w==input) - break; + wsetcursor(w, FALSE); /* fall thrugh for redraw after input change */ case Repaint: + if(p != nil){ + /* sync with input change from wcurrent()/wuncurrent() */ + Channel *c = p; + input = recvp(c); + sendp(c, w); + } if(w->i==nil || Dx(w->screenr)<=0) break; wrepaint(w); @@ -1246,175 +1449,307 @@ wctlmesg(Window *w, int m, Rectangle r, void *p) return m; } -/* - * Convert back to physical coordinates - */ -void -wmovemouse(Window *w, Point p) -{ - if(w != input || menuing || sweeping) - return; - p.x += w->screenr.min.x-w->i->r.min.x; - p.y += w->screenr.min.y-w->i->r.min.y; - moveto(mousectl, p); -} - -void -wborder(Window *w, int type) -{ - Image *col; - - if(w->i == nil) - return; - if(w->holding){ - if(type == Selborder) - col = holdcol; - else - col = paleholdcol; - }else{ - if(type == Selborder) - col = titlecol; - else - col = lighttitlecol; - } - border(w->i, w->i->r, Selborder, col, ZP); -} - -Window* -wpointto(Point pt) -{ - int i; - Window *v, *w; - - w = nil; - for(i=0; i<nwindow; i++){ - v = window[i]; - if(ptinrect(pt, v->screenr)) - if(w==nil || v->topped>w->topped) - w = v; - } - return w; -} - -void -wcurrent(Window *w) -{ - if(w!=nil && w!=input) - wsendctlmesg(w, Topped, ZR, nil); -} - -void -wsetcursor(Window *w, int force) +static void +wmousectl(Window *w) { - Cursor *p; + int but; - if(menuing || sweeping || (w!=input && wpointto(mouse->xy)!=w)) - return; - if(w==nil) - p = nil; - else { - p = w->cursorp; - if(p==nil && w->holding) - p = &whitearrow; + for(but=1;; but++){ + if(but > 5) + return; + if(w->mc.buttons == 1<<(but-1)) + break; } - if(p && force) /* force cursor reload */ - lastcursor = nil; - riosetcursor(p); -} -void -riosetcursor(Cursor *p) -{ - if(p==lastcursor) - return; - setcursor(mousectl, p); - lastcursor = p; -} - -void -wtopme(Window *w) -{ - if(w!=nil && w->i!=nil && w->topped!=topped){ - w->topped = ++topped; - topwindow(w->i); - flushimage(display, 1); + incref(w); /* hold up window while we track */ + if(w->i != nil){ + if(shiftdown && but > 3) + wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown); + else if(ptinrect(w->mc.xy, w->scrollr) || (but > 3)) + wscroll(w, but); + else if(but == 1) + wselect(w); } + wclose(w); } void -wbottomme(Window *w) -{ - if(w!=nil && w->i!=nil){ - w->topped = - ++topped; - bottomwindow(w->i); - flushimage(display, 1); - } -} - -Window* -wtop(Point pt) +winctl(void *arg) { + Rune *rp, *up, r; + uint qh, q0; + int nr, nb, c, wid, i, npart, initial, lastb; + char *s, *t, part[3]; Window *w; + Mousestate *mp, m; + enum { WKbd, WKbdread, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, WComplete, Wgone, NWALT }; + Alt alts[NWALT+1]; + Consreadmesg crm; + Mousereadmesg mrm; + Conswritemesg cwm; + Stringpair pair; + Wctlmesg wcm; + Completion *cr; + char *kbdq[32], *kbds; + uint kbdqr, kbdqw; - w = wpointto(pt); - if(w){ - incref(w); - wtopme(w); - wcurrent(w); - wclose(w); - } - return w; -} + w = arg; + threadsetname("winctl-id%d", w->id); -Window* -wlookid(int id) -{ - int i; + mrm.cm = chancreate(sizeof(Mouse), 0); + crm.c1 = chancreate(sizeof(Stringpair), 0); + crm.c2 = chancreate(sizeof(Stringpair), 0); + cwm.cw = chancreate(sizeof(Stringpair), 0); + + alts[WKbd].c = w->ck; + alts[WKbd].v = &kbds; + alts[WKbd].op = CHANRCV; + alts[WKbdread].c = w->kbdread; + alts[WKbdread].v = &crm; + alts[WKbdread].op = CHANSND; + alts[WMouse].c = w->mc.c; + alts[WMouse].v = &w->mc.Mouse; + alts[WMouse].op = CHANRCV; + alts[WMouseread].c = w->mouseread; + alts[WMouseread].v = &mrm; + alts[WMouseread].op = CHANSND; + alts[WCtl].c = w->cctl; + alts[WCtl].v = &wcm; + alts[WCtl].op = CHANRCV; + alts[WCwrite].c = w->conswrite; + alts[WCwrite].v = &cwm; + alts[WCwrite].op = CHANSND; + alts[WCread].c = w->consread; + alts[WCread].v = &crm; + alts[WCread].op = CHANSND; + alts[WWread].c = w->wctlread; + alts[WWread].v = &crm; + alts[WWread].op = CHANSND; + alts[WComplete].c = w->complete; + alts[WComplete].v = &cr; + alts[WComplete].op = CHANRCV; + alts[Wgone].c = w->gone; + alts[Wgone].v = "window deleted"; + alts[Wgone].op = CHANNOP; + alts[NWALT].op = CHANEND; - for(i=0; i<nwindow; i++) - if(window[i]->id == id) - return window[i]; - return nil; -} + kbdqr = kbdqw = 0; + npart = 0; + lastb = -1; + for(;;){ + if(w->i==nil){ + /* window deleted */ + alts[Wgone].op = CHANSND; -void -wclunk(Window *w) -{ - int i; + alts[WKbdread].op = CHANNOP; + alts[WMouseread].op = CHANNOP; + alts[WCwrite].op = CHANNOP; + alts[WWread].op = CHANNOP; + alts[WCread].op = CHANNOP; + } else { + alts[WKbdread].op = (w->kbdopen && kbdqw != kbdqr) ? + CHANSND : CHANNOP; + alts[WMouseread].op = (w->mouseopen && w->mouse.counter != w->mouse.lastcounter) ? + CHANSND : CHANNOP; + alts[WCwrite].op = w->scrolling || w->mouseopen || (w->qh <= w->org+w->nchars) ? + CHANSND : CHANNOP; + alts[WWread].op = w->wctlready ? + CHANSND : CHANNOP; + /* this code depends on NL and EOT fitting in a single byte */ + /* kind of expensive for each loop; worth precomputing? */ + if(w->holding) + alts[WCread].op = CHANNOP; + else if(npart || (w->rawing && w->nraw>0)) + alts[WCread].op = CHANSND; + else{ + alts[WCread].op = CHANNOP; + for(i=w->qh; i<w->nr; i++){ + c = w->r[i]; + if(c=='\n' || c=='\004'){ + alts[WCread].op = CHANSND; + break; + } + } + } + } + switch(alt(alts)){ + case WKbd: + if(kbdqw - kbdqr < nelem(kbdq)) + kbdq[kbdqw++ % nelem(kbdq)] = kbds; + else + free(kbds); + if(w->kbdopen) + continue; + while(kbdqr != kbdqw){ + kbds = kbdq[kbdqr++ % nelem(kbdq)]; + if(*kbds == 'c'){ + chartorune(&r, kbds+1); + if(r) + wkeyctl(w, r); + } + free(kbds); + } + break; + case WKbdread: + recv(crm.c1, &pair); + nb = 0; + while(kbdqr != kbdqw){ + kbds = kbdq[kbdqr % nelem(kbdq)]; + i = strlen(kbds)+1; + if(nb+i > pair.ns) + break; + memmove((char*)pair.s + nb, kbds, i); + free(kbds); + nb += i; + kbdqr++; + } + pair.ns = nb; + send(crm.c2, &pair); + continue; + case WMouse: + if(w->mouseopen) { + w->mouse.counter++; - if(w->deleted) - return; - w->deleted = TRUE; - if(w == input){ - input = nil; - riosetcursor(nil); - } - if(w == wkeyboard) - wkeyboard = nil; - for(i=0; i<nhidden; i++) - if(hidden[i] == w){ - --nhidden; - memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0])); + /* queue click events */ + if(!w->mouse.qfull && lastb != w->mc.buttons) { /* add to ring */ + mp = &w->mouse.queue[w->mouse.wi]; + if(++w->mouse.wi == nelem(w->mouse.queue)) + w->mouse.wi = 0; + if(w->mouse.wi == w->mouse.ri) + w->mouse.qfull = TRUE; + mp->Mouse = w->mc; + mp->counter = w->mouse.counter; + lastb = w->mc.buttons; + } + } else + wmousectl(w); break; - } - for(i=0; i<nwindow; i++) - if(window[i] == w){ - --nwindow; - memmove(window+i, window+i+1, (nwindow-i)*sizeof(window[0])); + case WMouseread: + /* send a queued event or, if the queue is empty, the current state */ + /* if the queue has filled, we discard all the events it contained. */ + /* the intent is to discard frantic clicking by the user during long latencies. */ + w->mouse.qfull = FALSE; + if(w->mouse.wi != w->mouse.ri) { + m = w->mouse.queue[w->mouse.ri]; + if(++w->mouse.ri == nelem(w->mouse.queue)) + w->mouse.ri = 0; + } else + m = (Mousestate){w->mc.Mouse, w->mouse.counter}; + + w->mouse.lastcounter = m.counter; + send(mrm.cm, &m.Mouse); + continue; + case WCtl: + if(wctlmesg(w, wcm.type, wcm.r, wcm.p) == Exited){ + while(kbdqr != kbdqw) + free(kbdq[kbdqr++ % nelem(kbdq)]); + chanfree(crm.c1); + chanfree(crm.c2); + chanfree(mrm.cm); + chanfree(cwm.cw); + threadexits(nil); + } + continue; + case WCwrite: + recv(cwm.cw, &pair); + rp = pair.s; + nr = pair.ns; + for(i=0; i<nr; i++) + if(rp[i] == '\b'){ + up = rp+i; + initial = 0; + for(; i<nr; i++){ + if(rp[i] == '\b'){ + if(up == rp) + initial++; + else + up--; + }else + *up++ = rp[i]; + } + if(initial){ + if(initial > w->qh) + initial = w->qh; + qh = w->qh-initial; + wdelete(w, qh, qh+initial); + w->qh = qh; + } + nr = up - rp; + break; + } + w->qh = winsert(w, rp, nr, w->qh)+nr; + if(w->scrolling || w->mouseopen) + wshow(w, w->qh); + wsetselect(w, w->q0, w->q1); + wscrdraw(w); + free(rp); + break; + case WCread: + recv(crm.c1, &pair); + t = pair.s; + nb = pair.ns; + i = npart; + npart = 0; + if(i) + memmove(t, part, i); + while(i<nb && (w->qh<w->nr || w->nraw>0)){ + if(w->qh == w->nr){ + wid = runetochar(t+i, &w->raw[0]); + w->nraw--; + runemove(w->raw, w->raw+1, w->nraw); + }else + wid = runetochar(t+i, &w->r[w->qh++]); + c = t[i]; /* knows break characters fit in a byte */ + i += wid; + if(!w->rawing && (c == '\n' || c=='\004')){ + if(c == '\004') + i--; + break; + } + } + if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004') + w->qh++; + if(i > nb){ + npart = i-nb; + memmove(part, t+nb, npart); + i = nb; + } + pair.s = t; + pair.ns = i; + send(crm.c2, &pair); + continue; + case WWread: + w->wctlready = 0; + recv(crm.c1, &pair); + s = Dx(w->screenr) > 0 ? "visible" : "hidden"; + t = "notcurrent"; + if(w == input) + t = "current"; + pair.ns = snprint(pair.s, pair.ns+1, "%11d %11d %11d %11d %11s %11s ", + w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s); + send(crm.c2, &pair); + continue; + case WComplete: + if(w->i!=nil){ + if(!cr->advance) + showcandidates(w, cr); + if(cr->advance){ + rp = runesmprint("%s", cr->string); + if(rp){ + nr = runestrlen(rp); + q0 = w->q0; + q0 = winsert(w, rp, nr, q0); + wshow(w, q0+nr); + free(rp); + } + } + } + freecompletion(cr); break; } -} - -void -wclosewin(Window *w) -{ - Image *i = w->i; - if(i == nil) - return; - w->i = nil; - /* move it off-screen to hide it, in case client is slow in letting it go */ - originwindow(i, i->r.min, view->r.max); - freeimage(i); + if(w->i!=nil && Dx(w->screenr) > 0 && display->bufp > display->buf) + flushimage(display, 1); + } } void @@ -1481,344 +1816,3 @@ winshell(void *args) _exits("exec failed"); } } - -static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 }; -static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 }; -static Rune left2[] = { L'\n', 0 }; -static Rune left3[] = { L'\'', L'"', L'`', 0 }; - -Rune *left[] = { - left1, - left2, - left3, - nil -}; -Rune *right[] = { - right1, - left2, - left3, - nil -}; - -int -inmode(Rune r, int mode) -{ - return (mode == 1) ? isalnum(r) : r && !isspace(r); -} - -void -wstretchsel(Window *w, uint pt, uint *q0, uint *q1, int mode) -{ - int c, i; - Rune *r, *l, *p; - uint q; - - *q0 = pt; - *q1 = pt; - for(i=0; left[i]!=nil; i++){ - q = *q0; - l = left[i]; - r = right[i]; - /* try matching character to left, looking right */ - if(q == 0) - c = '\n'; - else - c = w->r[q-1]; - p = strrune(l, c); - if(p != nil){ - if(wclickmatch(w, c, r[p-l], 1, &q)) - *q1 = q-(c!='\n'); - return; - } - /* try matching character to right, looking left */ - if(q == w->nr) - c = '\n'; - else - c = w->r[q]; - p = strrune(r, c); - if(p != nil){ - if(wclickmatch(w, c, l[p-r], -1, &q)){ - *q1 = *q0+(*q0<w->nr && c=='\n'); - *q0 = q; - if(c!='\n' || q!=0 || w->r[0]=='\n') - (*q0)++; - } - return; - } - } - /* try filling out word to right */ - while(*q1<w->nr && inmode(w->r[*q1], mode)) - (*q1)++; - /* try filling out word to left */ - while(*q0>0 && inmode(w->r[*q0-1], mode)) - (*q0)--; -} - -int -wclickmatch(Window *w, int cl, int cr, int dir, uint *q) -{ - Rune c; - int nest; - - nest = 1; - for(;;){ - if(dir > 0){ - if(*q == w->nr) - break; - c = w->r[*q]; - (*q)++; - }else{ - if(*q == 0) - break; - (*q)--; - c = w->r[*q]; - } - if(c == cr){ - if(--nest==0) - return 1; - }else if(c == cl) - nest++; - } - return cl=='\n' && nest==1; -} - - -uint -wbacknl(Window *w, uint p, uint n) -{ - int i, j; - - /* look for start of this line if n==0 */ - if(n==0 && p>0 && w->r[p-1]!='\n') - n = 1; - i = n; - while(i-->0 && p>0){ - --p; /* it's at a newline now; back over it */ - if(p == 0) - break; - /* at 128 chars, call it a line anyway */ - for(j=128; --j>0 && p>0; p--) - if(w->r[p-1]=='\n') - break; - } - return p; -} - -void -wshow(Window *w, uint q0) -{ - int qe; - int nl; - uint q; - - qe = w->org+w->nchars; - if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr))) - wscrdraw(w); - else{ - nl = 4*w->maxlines/5; - q = wbacknl(w, q0, nl); - /* avoid going backwards if trying to go forwards - long lines! */ - if(!(q0>w->org && q<w->org)) - wsetorigin(w, q, TRUE); - while(q0 > w->org+w->nchars) - wsetorigin(w, w->org+1, FALSE); - } -} - -void -wsetorigin(Window *w, uint org, int exact) -{ - int i, a, fixup; - Rune *r; - uint n; - - if(org>0 && !exact){ - /* org is an estimate of the char posn; find a newline */ - /* don't try harder than 256 chars */ - for(i=0; i<256 && org<w->nr; i++){ - if(w->r[org] == '\n'){ - org++; - break; - } - org++; - } - } - a = org-w->org; - fixup = 0; - if(a>=0 && a<w->nchars){ - frdelete(w, 0, a); - fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */ - }else if(a<0 && -a<w->nchars){ - n = w->org - org; - r = w->r+org; - frinsert(w, r, r+n, 0); - }else - frdelete(w, 0, w->nchars); - w->org = org; - wfill(w); - wscrdraw(w); - wsetselect(w, w->q0, w->q1); - if(fixup && w->p1 > w->p0) - frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1); -} - -void -wsetselect(Window *w, uint q0, uint q1) -{ - int p0, p1; - - /* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */ - w->q0 = q0; - w->q1 = q1; - /* compute desired p0,p1 from q0,q1 */ - p0 = q0-w->org; - p1 = q1-w->org; - if(p0 < 0) - p0 = 0; - if(p1 < 0) - p1 = 0; - if(p0 > w->nchars) - p0 = w->nchars; - if(p1 > w->nchars) - p1 = w->nchars; - if(p0==w->p0 && p1==w->p1) - return; - /* screen disagrees with desired selection */ - if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){ - /* no overlap or too easy to bother trying */ - frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0); - frdrawsel(w, frptofchar(w, p0), p0, p1, 1); - goto Return; - } - /* overlap; avoid unnecessary painting */ - if(p0 < w->p0){ - /* extend selection backwards */ - frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1); - }else if(p0 > w->p0){ - /* trim first part of selection */ - frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0); - } - if(p1 > w->p1){ - /* extend selection forwards */ - frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1); - }else if(p1 < w->p1){ - /* trim last part of selection */ - frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0); - } - - Return: - w->p0 = p0; - w->p1 = p1; -} - -uint -winsert(Window *w, Rune *r, int n, uint q0) -{ - uint m; - - if(n == 0) - return q0; - if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){ - m = min(HiWater-LoWater, min(w->org, w->qh)); - w->org -= m; - w->qh -= m; - if(w->q0 > m) - w->q0 -= m; - else - w->q0 = 0; - if(w->q1 > m) - w->q1 -= m; - else - w->q1 = 0; - w->nr -= m; - runemove(w->r, w->r+m, w->nr); - q0 -= m; - } - if(w->nr+n > w->maxr){ - /* - * Minimize realloc breakage: - * Allocate at least MinWater - * Double allocation size each time - * But don't go much above HiWater - */ - m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater; - if(m > HiWater) - m = max(HiWater+MinWater, w->nr+n); - if(m > w->maxr){ - w->r = runerealloc(w->r, m); - w->maxr = m; - } - } - runemove(w->r+q0+n, w->r+q0, w->nr-q0); - runemove(w->r+q0, r, n); - w->nr += n; - /* if output touches, advance selection, not qh; works best for keyboard and output */ - if(q0 <= w->q1) - w->q1 += n; - if(q0 <= w->q0) - w->q0 += n; - if(q0 < w->qh) - w->qh += n; - if(q0 < w->org) - w->org += n; - else if(q0 <= w->org+w->nchars) - frinsert(w, r, r+n, q0-w->org); - return q0; -} - -void -wfill(Window *w) -{ - Rune *rp; - int i, n, m, nl; - - while(w->lastlinefull == FALSE){ - n = w->nr-(w->org+w->nchars); - if(n == 0) - break; - if(n > 2000) /* educated guess at reasonable amount */ - n = 2000; - rp = w->r+(w->org+w->nchars); - - /* - * it's expensive to frinsert more than we need, so - * count newlines. - */ - nl = w->maxlines-w->nlines; - m = 0; - for(i=0; i<n; ){ - if(rp[i++] == '\n'){ - m++; - if(m >= nl) - break; - } - } - frinsert(w, rp, rp+i, w->nchars); - } -} - -char* -wcontents(Window *w, int *ip) -{ - return runetobyte(w->r, w->nr, ip); -} - -void -wsend(Window *w) -{ - getsnarf(); - wsnarf(w); - if(nsnarf == 0) - return; - if(w->rawing){ - waddraw(w, snarf, nsnarf); - if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') - waddraw(w, L"\n", 1); - }else{ - winsert(w, snarf, nsnarf, w->nr); - if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') - winsert(w, L"\n", 1, w->nr); - } - wsetselect(w, w->nr, w->nr); - wshow(w, w->nr); -} |