plan9fox/sys/src/cmd/rio/wind.c
cinap_lenrek 8057e48ae1 rio: colors, flicker reduction, refresh after mouse close
allocate all the colors in iconinit(), remove unused ones
like grey. rename darkgrey to paletextcol because thats
what it is used for. new approach to window image allocation.
we allocate the window with DNofill and let the window fill
itself. this reduces flickering especially with (-b) option
and makes rio resize feel a lot faster.

wrefresh() didnt work. now fixed.
2012-10-20 15:51:32 +02:00

1714 lines
32 KiB
C

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <complete.h>
#include "dat.h"
#include "fns.h"
#define MOVEIT if(0)
enum
{
HiWater = 640000, /* max size of history */
LoWater = 400000, /* min size of history after max'ed */
MinWater = 20000, /* room to leave available when reallocating */
};
static int topped;
static int id;
static Cursor *lastcursor;
Window*
wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
{
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(Kbdreadmesg), 0);
w->mouseread = chancreate(sizeof(Mousereadmesg), 0);
w->wctlread = chancreate(sizeof(Consreadmesg), 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;
}
void
wsetname(Window *w)
{
int i, n;
char err[ERRMAX];
n = sprint(w->name, "window.%d.%d", w->id, w->namecount++);
for(i='A'; i<='Z'; i++){
if(nameimage(w->i, w->name, 1) > 0)
return;
errstr(err, sizeof err);
if(strcmp(err, "image name in use") != 0)
break;
w->name[n] = i;
w->name[n+1] = 0;
}
w->name[0] = 0;
fprint(2, "rio: setname failed: %s\n", err);
}
void
wresize(Window *w, Image *i, int move)
{
Rectangle r, or;
or = w->i->r;
if(move || (Dx(or)==Dx(i->r) && Dy(or)==Dy(i->r)))
draw(i, i->r, w->i, nil, w->i->r.min);
freeimage(w->i);
w->i = i;
w->mc.image = i;
r = insetrect(i->r, Selborder+1);
w->scrollr = r;
w->scrollr.max.x = r.min.x+Scrollwid;
w->lastsr = ZR;
r.min.x += Scrollwid+Scrollgap;
if(move)
frsetrects(w, r, w->i);
else{
frclear(w, FALSE);
frinit(w, r, w->font, w->i, cols);
wsetcols(w);
w->maxtab = maxtab*stringwidth(w->font, "0");
r = insetrect(w->i->r, Selborder);
draw(w->i, r, cols[BACK], nil, w->entire.min);
wfill(w);
wsetselect(w, w->q0, w->q1);
wscrdraw(w);
}
if(w == input)
wborder(w, Selborder);
else
wborder(w, Unselborder);
wsetname(w);
w->topped = ++topped;
w->resized = TRUE;
w->mouse.counter++;
w->wctlready = 1;
}
void
wrefresh(Window *w, Rectangle r)
{
/* BUG: rectangle is ignored */
if(w == input)
wborder(w, Selborder);
else
wborder(w, Unselborder);
r = insetrect(w->i->r, Selborder);
draw(w->i, r, w->cols[BACK], nil, w->entire.min);
w->ticked = 0;
if(w->p0 > 0)
frdrawsel(w, frptofchar(w, 0), 0, w->p0, 0);
if(w->p1 < w->nchars)
frdrawsel(w, frptofchar(w, w->p1), w->p1, w->nchars, 0);
frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 1);
w->lastsr = ZR;
wscrdraw(w);
}
int
wclose(Window *w)
{
int i;
i = decref(w);
if(i > 0)
return 0;
if(i < 0)
error("negative ref count");
if(!w->deleted)
wclunk(w);
wsendctlmesg(w, Exited, ZR, nil);
return 1;
}
void
winctl(void *arg)
{
Rune *rp, *bp, *tp, *up;
uint qh;
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, NWALT };
Alt alts[NWALT+1];
Mousereadmesg mrm;
Kbdreadmesg krm;
Conswritemesg cwm;
Consreadmesg crm;
Consreadmesg cwrm;
Stringpair pair;
Wctlmesg wcm;
char buf[4*12+1], *kbdq[8], *kbds;
int kbdqr, kbdqw;
w = arg;
snprint(buf, sizeof buf, "winctl-id%d", w->id);
threadsetname(buf);
mrm.cm = chancreate(sizeof(Mouse), 0);
krm.ck = chancreate(sizeof(char*), 0);
cwm.cw = chancreate(sizeof(Stringpair), 0);
crm.c1 = chancreate(sizeof(Stringpair), 0);
crm.c2 = chancreate(sizeof(Stringpair), 0);
cwrm.c1 = chancreate(sizeof(Stringpair), 0);
cwrm.c2 = 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 = &krm;
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 = &cwrm;
alts[WWread].op = CHANSND;
alts[NWALT].op = CHANEND;
memset(kbdq, 0, sizeof(kbdq));
kbdqr = kbdqw = 0;
npart = 0;
lastb = -1;
for(;;){
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) ?
CHANNOP : CHANSND;
alts[WWread].op = (w->deleted || !w->wctlready) ?
CHANNOP : CHANSND;
/* 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(w->kbdopen){
i = (kbdqw+1) % nelem(kbdq);
if(i != kbdqr)
kbdqw = i;
} else if(*kbds == 'c'){
Rune r;
chartorune(&r, kbds+1);
if(r)
wkeyctl(w, r);
}
free(kbdq[kbdqw]);
kbdq[kbdqw] = kbds;
break;
case WKbdread:
i = (kbdqr+1) % nelem(kbdq);
if(kbdqr != kbdqw)
kbdqr = i;
if(kbdq[i]){
sendp(krm.ck, kbdq[i]);
kbdq[i] = nil;
}else
sendp(krm.ck, strdup("K"));
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.image) == Exited){
for(i=0; i<nelem(kbdq); i++)
free(kbdq[i]);
chanfree(crm.c1);
chanfree(crm.c2);
chanfree(mrm.cm);
chanfree(krm.ck);
chanfree(cwm.cw);
chanfree(cwrm.c1);
chanfree(cwrm.c2);
threadexits(nil);
}
continue;
case WCwrite:
recv(cwm.cw, &pair);
rp = pair.s;
nr = pair.ns;
bp = rp;
for(i=0; i<nr; i++)
if(*bp++ == '\b'){
--bp;
initial = 0;
tp = runemalloc(nr);
runemove(tp, rp, i);
up = tp+i;
for(; i<nr; i++){
*up = *bp++;
if(*up == '\b')
if(up == tp)
initial++;
else
--up;
else
up++;
}
if(initial){
if(initial > w->qh)
initial = w->qh;
qh = w->qh-initial;
wdelete(w, qh, qh+initial);
w->qh = qh;
}
free(rp);
rp = tp;
nr = up-tp;
rp[nr] = 0;
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(cwrm.c1, &pair);
if(w->deleted || w->i==nil)
pair.ns = sprint(pair.s, "");
else{
s = "visible";
for(i=0; i<nhidden; i++)
if(hidden[i] == w){
s = "hidden";
break;
}
t = "notcurrent";
if(w == input)
t = "current";
pair.ns = snprint(pair.s, pair.ns, "%11d %11d %11d %11d %s %s ",
w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
}
send(cwrm.c2, &pair);
continue;
}
if(!w->deleted)
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
interruptproc(void *v)
{
int *notefd;
notefd = v;
write(*notefd, "interrupt", 9);
free(notefd);
}
int
windfilewidth(Window *w, uint q0, int oneelement)
{
uint q;
Rune r;
q = q0;
while(q > 0){
r = w->r[q-1];
if(r<=' ')
break;
if(oneelement && r=='/')
break;
--q;
}
return q0-q;
}
void
showcandidates(Window *w, Completion *c)
{
int i;
Fmt f;
Rune *rp;
uint nr, qline, q0;
char *s;
runefmtstrinit(&f);
if (c->nmatch == 0)
s = "[no matches in ";
else
s = "[";
if(c->nfile > 32)
fmtprint(&f, "%s%d files]\n", s, c->nfile);
else{
fmtprint(&f, "%s", s);
for(i=0; i<c->nfile; i++){
if(i > 0)
fmtprint(&f, " ");
fmtprint(&f, "%s", c->filename[i]);
}
fmtprint(&f, "]\n");
}
/* place text at beginning of line before host point */
qline = w->qh;
while(qline>0 && w->r[qline-1] != '\n')
qline--;
rp = runefmtstrflush(&f);
nr = runestrlen(rp);
q0 = w->q0;
q0 += winsert(w, rp, runestrlen(rp), qline) - qline;
free(rp);
wsetselect(w, q0+nr, q0+nr);
}
Rune*
namecomplete(Window *w)
{
int nstr, npath;
Rune *rp, *path, *str;
Completion *c;
char *s, *dir, *root;
/* 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 nil;
nstr = windfilewidth(w, w->q0, TRUE);
str = runemalloc(nstr);
runemove(str, w->r+(w->q0-nstr), nstr);
npath = windfilewidth(w, w->q0-nstr, FALSE);
path = runemalloc(npath);
runemove(path, w->r+(w->q0-nstr-npath), npath);
rp = nil;
/* is path rooted? if not, we need to make it relative to window path */
if(npath>0 && path[0]=='/'){
dir = malloc(UTFmax*npath+1);
sprint(dir, "%.*S", npath, path);
}else{
if(strcmp(w->dir, "") == 0)
root = ".";
else
root = w->dir;
dir = malloc(strlen(root)+1+UTFmax*npath+1);
sprint(dir, "%s/%.*S", root, npath, path);
}
dir = cleanname(dir);
s = smprint("%.*S", nstr, str);
c = complete(dir, s);
free(s);
if(c == nil)
goto Return;
if(!c->advance)
showcandidates(w, c);
if(c->advance)
rp = runesmprint("%s", c->string);
Return:
freecompletion(c);
free(dir);
free(path);
free(str);
return rp;
}
void
wkeyctl(Window *w, Rune r)
{
uint q0 ,q1;
int n, nb, nr;
Rune *rp;
int *notefd;
switch(r){
case 0:
case Kcaps:
case Knum:
case Kshift:
case Kalt:
case Kctl:
case Kaltgr:
return;
}
if(w->deleted)
return;
/* navigation keys work only when mouse and kbd is not open */
if(!w->mouseopen)
switch(r){
case Kdown:
n = shiftdown ? 1 : w->maxlines/3;
goto case_Down;
case Kscrollonedown:
n = mousescrollsize(w->maxlines);
if(n <= 0)
n = 1;
goto case_Down;
case Kpgdown:
n = 2*w->maxlines/3;
case_Down:
q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+n*w->font->height));
wsetorigin(w, q0, TRUE);
return;
case Kup:
n = shiftdown ? 1 : w->maxlines/3;
goto case_Up;
case Kscrolloneup:
n = mousescrollsize(w->maxlines);
if(n <= 0)
n = 1;
goto case_Up;
case Kpgup:
n = 2*w->maxlines/3;
case_Up:
q0 = wbacknl(w, w->org, n);
wsetorigin(w, q0, TRUE);
return;
case Kleft:
if(w->q0 > 0){
q0 = w->q0-1;
wsetselect(w, q0, q0);
wshow(w, q0);
}
return;
case Kright:
if(w->q1 < w->nr){
q1 = w->q1+1;
wsetselect(w, q1, q1);
wshow(w, q1);
}
return;
case Khome:
wshow(w, 0);
return;
case Kend:
wshow(w, w->nr);
return;
case Kscroll:
w->scrolling ^= 1;
wshow(w, w->nr);
return;
case Ksoh: /* ^A: beginning of line */
if(w->q0==0 || w->q0==w->qh || w->r[w->q0-1]=='\n')
return;
nb = wbswidth(w, 0x15 /* ^U */);
wsetselect(w, w->q0-nb, w->q0-nb);
wshow(w, w->q0);
return;
case Kenq: /* ^E: end of line */
q0 = w->q0;
while(q0 < w->nr && w->r[q0]!='\n')
q0++;
wsetselect(w, q0, q0);
wshow(w, w->q0);
return;
}
if(w->rawing && (w->q0==w->nr || w->mouseopen)){
waddraw(w, &r, 1);
return;
}
if(r==Kesc || (w->holding && r==Kdel)){ /* toggle hold */
if(w->holding)
--w->holding;
else
w->holding++;
wrepaint(w);
if(r == Kesc)
return;
}
if(r != Kdel){
wsnarf(w);
wcut(w);
}
switch(r){
case Kdel: /* send interrupt */
w->qh = w->nr;
wshow(w, w->qh);
notefd = emalloc(sizeof(int));
*notefd = w->notefd;
proccreate(interruptproc, notefd, 4096);
return;
case Kack: /* ^F: file name completion */
case Kins: /* Insert: file name completion */
rp = namecomplete(w);
if(rp == nil)
return;
nr = runestrlen(rp);
q0 = w->q0;
q0 = winsert(w, rp, nr, q0);
wshow(w, q0+nr);
free(rp);
return;
case Kbs: /* ^H: erase character */
case Knack: /* ^U: erase line */
case Ketb: /* ^W: erase word */
if(w->q0==0 || w->q0==w->qh)
return;
nb = wbswidth(w, r);
q1 = w->q0;
q0 = q1-nb;
if(q0 < w->org){
q0 = w->org;
nb = q1-q0;
}
if(nb > 0){
wdelete(w, q0, q0+nb);
wsetselect(w, q0, q0);
}
return;
}
/* otherwise ordinary character; just insert */
q0 = w->q0;
q0 = winsert(w, &r, 1, q0);
wshow(w, q0+1);
}
void
wsetcols(Window *w)
{
if(w->holding)
if(w == input)
w->cols[TEXT] = w->cols[HTEXT] = holdcol;
else
w->cols[TEXT] = w->cols[HTEXT] = lightholdcol;
else
if(w == input)
w->cols[TEXT] = w->cols[HTEXT] = cols[TEXT];
else
w->cols[TEXT] = w->cols[HTEXT] = paletextcol;
}
void
wrepaint(Window *w)
{
wsetcols(w);
if(!w->mouseopen)
frredraw(w);
if(w == input){
wborder(w, Selborder);
wsetcursor(w, 0);
}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++;
sprint(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, 1);
sleep(300);
riosetcursor(c, 1);
}
plumbfree(m);
}
int
winborder(Window *w, Point xy)
{
return ptinrect(xy, w->screenr) && !ptinrect(xy, insetrect(w->screenr, Selborder));
}
void
wmousectl(Window *w)
{
int but;
if(w->mc.buttons == 1)
but = 1;
else if(w->mc.buttons == 2)
but = 2;
else if(w->mc.buttons == 4)
but = 3;
else{
if(w->mc.buttons == 8)
wkeyctl(w, Kscrolloneup);
if(w->mc.buttons == 16)
wkeyctl(w, Kscrollonedown);
return;
}
incref(w); /* hold up window while we track */
if(w->deleted)
goto Return;
if(ptinrect(w->mc.xy, w->scrollr)){
if(but)
wscroll(w, but);
goto Return;
}
if(but == 1)
wselect(w);
/* else all is handled by main process */
Return:
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 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
wframescroll(Window *w, int dl)
{
uint q0;
if(dl == 0){
wscrsleep(w, 100);
return;
}
if(dl < 0){
q0 = wbacknl(w, w->org, -dl);
if(selectq > w->org+w->p0)
wsetselect(w, w->org+w->p0, selectq);
else
wsetselect(w, selectq, w->org+w->p0);
}else{
if(w->org+w->nchars == w->nr)
return;
q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+dl*w->font->height));
if(selectq >= w->org+w->p1)
wsetselect(w, w->org+w->p1, selectq);
else
wsetselect(w, selectq, w->org+w->p1);
}
wsetorigin(w, q0, TRUE);
}
void
wselect(Window *w)
{
uint q0, q1;
int b, x, y, first;
first = 1;
selectwin = w;
/*
* Double-click immediately if it might make sense.
*/
b = w->mc.buttons;
q0 = w->q0;
q1 = w->q1;
selectq = w->org+frcharofpt(w, w->mc.xy);
if(clickwin==w && w->mc.msec-clickmsec<500)
if(q0==q1 && selectq==w->q0){
wdoubleclick(w, &q0, &q1);
wsetselect(w, q0, q1);
flushimage(display, 1);
x = w->mc.xy.x;
y = w->mc.xy.y;
/* stay here until something interesting happens */
do
readmouse(&w->mc);
while(w->mc.buttons==b && abs(w->mc.xy.x-x)<3 && abs(w->mc.xy.y-y)<3);
w->mc.xy.x = x; /* in case we're calling frselect */
w->mc.xy.y = y;
q0 = w->q0; /* may have changed */
q1 = w->q1;
selectq = q0;
}
if(w->mc.buttons == b){
w->scroll = framescroll;
frselect(w, &w->mc);
/* horrible botch: while asleep, may have lost selection altogether */
if(selectq > w->nr)
selectq = w->org + w->p0;
w->Frame.scroll = nil;
if(selectq < w->org)
q0 = selectq;
else
q0 = w->org + w->p0;
if(selectq > w->org+w->nchars)
q1 = selectq;
else
q1 = w->org+w->p1;
}
if(q0 == q1){
if(q0==w->q0 && clickwin==w && w->mc.msec-clickmsec<500){
wdoubleclick(w, &q0, &q1);
clickwin = nil;
}else{
clickwin = w;
clickmsec = w->mc.msec;
}
}else
clickwin = nil;
wsetselect(w, q0, q1);
flushimage(display, 1);
while(w->mc.buttons){
w->mc.msec = 0;
b = w->mc.buttons;
if(b & 6){
if(b & 2){
wsnarf(w);
wcut(w);
}else{
if(first){
first = 0;
getsnarf();
}
wpaste(w);
}
}
wscrdraw(w);
flushimage(display, 1);
while(w->mc.buttons == b)
readmouse(&w->mc);
clickwin = nil;
}
}
void
wsendctlmesg(Window *w, int type, Rectangle r, Image *image)
{
Wctlmesg wcm;
wcm.type = type;
wcm.r = r;
wcm.image = image;
send(w->cctl, &wcm);
}
int
wctlmesg(Window *w, int m, Rectangle r, Image *i)
{
char buf[64];
switch(m){
default:
error("unknown control message");
break;
case Wakeup:
break;
case Moved:
case Reshaped:
if(w->deleted){
freeimage(i);
break;
}
w->screenr = r;
strcpy(buf, w->name);
wresize(w, i, m==Moved);
proccreate(deletetimeoutproc, estrdup(buf), 4096);
break;
case Refresh:
if(w->deleted || Dx(w->screenr)<=0 || !rectclip(&r, w->i->r) || w->mouseopen)
break;
wrefresh(w, r);
flushimage(display, 1);
break;
case Movemouse:
if(w->deleted || Dx(w->screenr)<=0 || !ptinrect(r.min, w->i->r))
break;
wmovemouse(w, r.min);
case Rawon:
break;
case Rawoff:
if(w->deleted)
break;
while(w->nraw > 0){
wkeyctl(w, w->raw[0]);
--w->nraw;
runemove(w->raw, w->raw+1, w->nraw);
}
break;
case Holdon:
case Holdoff:
case Repaint:
if(w->deleted)
break;
wrepaint(w);
flushimage(display, 1);
break;
case Deleted:
if(w->deleted)
break;
wclunk(w);
write(w->notefd, "hangup", 6);
proccreate(deletetimeoutproc, estrdup(w->name), 4096);
wclosewin(w);
break;
case Exited:
wclosewin(w);
frclear(w, TRUE);
close(w->notefd);
chanfree(w->mc.c);
chanfree(w->ck);
chanfree(w->cctl);
chanfree(w->conswrite);
chanfree(w->consread);
chanfree(w->mouseread);
chanfree(w->wctlread);
chanfree(w->kbdread);
free(w->raw);
free(w->r);
free(w->dir);
free(w->label);
free(w);
break;
}
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)
{
Window *oi;
if(wkeyboard!=nil && w==wkeyboard)
return;
oi = input;
input = w;
if(w != oi){
if(oi){
oi->wctlready = 1;
wsendctlmesg(oi, Repaint, ZR, nil);
}
if(w){
w->wctlready = 1;
wsendctlmesg(w, Repaint, ZR, nil);
}
}
}
void
wsetcursor(Window *w, int force)
{
Cursor *p;
if(w==nil || w->deleted || w->i==nil || Dx(w->screenr)<=0)
p = nil;
else if(wpointto(mouse->xy) == w){
p = w->cursorp;
if(p==nil && w->holding)
p = &whitearrow;
}else
p = nil;
if(!menuing)
riosetcursor(p, force && !menuing);
}
void
riosetcursor(Cursor *p, int force)
{
if(!force && p==lastcursor)
return;
setcursor(mousectl, p);
lastcursor = p;
}
void
wtopme(Window *w)
{
if(w!=nil && w->i!=nil && !w->deleted && w->topped!=topped){
w->topped = ++topped;
topwindow(w->i);
flushimage(display, 1);
}
}
void
wbottomme(Window *w)
{
if(w!=nil && w->i!=nil && !w->deleted){
w->topped = - ++topped;
bottomwindow(w->i);
flushimage(display, 1);
}
}
Window*
wtop(Point pt)
{
Window *w;
w = wpointto(pt);
if(w){
incref(w);
wtopme(w);
wcurrent(w);
wclose(w);
}
return w;
}
Window*
wlookid(int id)
{
int i;
for(i=0; i<nwindow; i++)
if(window[i]->id == id)
return window[i];
return nil;
}
void
wclunk(Window *w)
{
int i;
if(w == input){
input = nil;
wsetcursor(w, 0);
}
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;
}
w->deleted = TRUE;
}
void
wclosewin(Window *w)
{
Image *i;
i = w->i;
if(i){
w->i = nil;
/* move it off-screen to hide it, in case client is slow in letting it go */
MOVEIT originwindow(i, i->r.min, view->r.max);
freeimage(i);
}
}
void
wsetpid(Window *w, int pid, int dolabel)
{
char buf[128];
int fd;
w->pid = pid;
if(dolabel){
sprint(buf, "rc %d", pid);
free(w->label);
w->label = estrdup(buf);
}
sprint(buf, "/proc/%d/notepg", pid);
fd = open(buf, OWRITE|OCEXEC);
if(w->notefd > 0)
close(w->notefd);
w->notefd = fd;
}
void
winshell(void *args)
{
Window *w;
Channel *pidc;
void **arg;
char *cmd, *dir;
char **argv;
arg = args;
w = arg[0];
pidc = arg[1];
cmd = arg[2];
argv = arg[3];
dir = arg[4];
rfork(RFNAMEG|RFFDG|RFENVG);
if(filsysmount(filsys, w->id) < 0){
fprint(2, "mount failed: %r\n");
sendul(pidc, 0);
threadexits("mount failed");
}
close(0);
if(open("/dev/cons", OREAD) < 0){
fprint(2, "can't open /dev/cons: %r\n");
sendul(pidc, 0);
threadexits("/dev/cons");
}
close(1);
if(open("/dev/cons", OWRITE) < 0){
fprint(2, "can't open /dev/cons: %r\n");
sendul(pidc, 0);
threadexits("open"); /* BUG? was terminate() */
}
if(wclose(w) == 0){ /* remove extra ref hanging from creation */
notify(nil);
dup(1, 2);
if(dir)
chdir(dir);
procexec(pidc, cmd, argv);
_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
};
void
wdoubleclick(Window *w, uint *q0, uint *q1)
{
int c, i;
Rune *r, *l, *p;
uint q;
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 && isalnum(w->r[*q1]))
(*q1)++;
/* try filling out word to left */
while(*q0>0 && isalnum(w->r[*q0-1]))
(*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 = runemalloc(n);
runemove(r, w->r+org, n);
frinsert(w, r, r+n, 0);
free(r);
}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;
if(w->lastlinefull)
return;
rp = malloc(messagesize);
do{
n = w->nr-(w->org+w->nchars);
if(n == 0)
break;
if(n > 2000) /* educated guess at reasonable amount */
n = 2000;
runemove(rp, w->r+(w->org+w->nchars), n);
/*
* 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);
}while(w->lastlinefull == FALSE);
free(rp);
}
char*
wcontents(Window *w, int *ip)
{
return runetobyte(w->r, w->nr, ip);
}