blob: 7dc204435f2bc98247b9c85c4507f3badeae49e6 [file] [log] [blame]
#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 <9pclient.h>
#include <plumb.h>
#include <complete.h>
#include "dat.h"
#include "fns.h"
#define MOVEIT if(0)
enum
{
HiWater = 64000000, /* 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 Image *cols[NCOL];
static Image *grey;
static Image *darkgrey;
static Cursor *lastcursor;
static Image *titlecol;
static Image *lighttitlecol;
static Image *holdcol;
static Image *lightholdcol;
static Image *paleholdcol;
static int
wscale(Window *w, int n)
{
if(w == nil || w->i == nil)
return n;
return scalesize(w->i->display, n);
}
Window*
wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
{
Window *w;
Rectangle r;
if(cols[0] == nil){
/* greys are multiples of 0x11111100+0xFF, 14* being palest */
grey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
darkgrey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x666666FF);
cols[BACK] = display->white;
cols[HIGH] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
cols[BORD] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x999999FF);
cols[TEXT] = display->black;
cols[HTEXT] = display->black;
titlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreygreen);
lighttitlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreygreen);
holdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DMedblue);
lightholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreyblue);
paleholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreyblue);
}
w = emalloc(sizeof(Window));
w->screenr = i->r;
r = insetrect(i->r, wscale(w, Selborder)+wscale(w, 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->mouseread = chancreate(sizeof(Mousereadmesg), 0);
w->wctlread = chancreate(sizeof(Consreadmesg), 0);
w->scrollr = r;
w->scrollr.max.x = r.min.x+wscale(w, Scrollwid);
w->lastsr = ZR;
r.min.x += wscale(w, Scrollwid)+wscale(w, Scrollgap);
frinit(&w->f, r, font, i, cols);
w->f.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, wscale(w, Selborder));
draw(w->i, r, cols[BACK], nil, w->f.entire.min);
wborder(w, wscale(w, Selborder));
wscrdraw(w);
incref(&w->ref); /* 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);
if(w->i != i){
fprint(2, "res %p %p\n", w->i, i);
freeimage(w->i);
w->i = i;
}
/* wsetname(w); */
/*XXX w->mc.image = i; */
r = insetrect(i->r, wscale(w, Selborder)+wscale(w, 1));
w->scrollr = r;
w->scrollr.max.x = r.min.x+wscale(w, Scrollwid);
w->lastsr = ZR;
r.min.x += wscale(w, Scrollwid)+wscale(w, Scrollgap);
if(move)
frsetrects(&w->f, r, w->i);
else{
frclear(&w->f, FALSE);
frinit(&w->f, r, w->f.font, w->i, cols);
wsetcols(w);
w->f.maxtab = maxtab*stringwidth(w->f.font, "0");
r = insetrect(w->i->r, wscale(w, Selborder));
draw(w->i, r, cols[BACK], nil, w->f.entire.min);
wfill(w);
wsetselect(w, w->q0, w->q1);
wscrdraw(w);
}
wborder(w, wscale(w, Selborder));
w->topped = ++topped;
w->resized = TRUE;
w->mouse.counter++;
}
void
wrefresh(Window *w, Rectangle r)
{
/* USED(r); */
/* BUG: rectangle is ignored */
if(w == input)
wborder(w, wscale(w, Selborder));
else
wborder(w, wscale(w, Unselborder));
if(w->mouseopen)
return;
draw(w->i, insetrect(w->i->r, Borderwidth), w->f.cols[BACK], nil, w->i->r.min);
w->f.ticked = 0;
if(w->f.p0 > 0)
frdrawsel(&w->f, frptofchar(&w->f, 0), 0, w->f.p0, 0);
if(w->f.p1 < w->f.nchars)
frdrawsel(&w->f, frptofchar(&w->f, w->f.p1), w->f.p1, w->f.nchars, 0);
frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, w->f.p1, 1);
w->lastsr = ZR;
wscrdraw(w);
}
int
wclose(Window *w)
{
int i;
i = decref(&w->ref);
if(i > 0)
return 0;
if(i < 0)
error("negative ref count");
if(!w->deleted)
wclosewin(w);
wsendctlmesg(w, Exited, ZR, nil);
return 1;
}
void
winctl(void *arg)
{
Rune *rp, *bp, *up, *kbdr;
uint qh;
int nr, nb, c, wid, i, npart, initial, lastb, scrolling;
char *s, *t, part[UTFmax];
Window *w;
Mousestate *mp, m;
enum { WKey, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, NWALT };
Alt alts[NWALT+1];
Mousereadmesg mrm;
Conswritemesg cwm;
Consreadmesg crm;
Consreadmesg cwrm;
Stringpair pair;
Wctlmesg wcm;
char buf[4*12+1];
w = arg;
snprint(buf, sizeof buf, "winctl-id%d", w->id);
threadsetname(buf);
mrm.cm = chancreate(sizeof(Mouse), 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[WKey].c = w->ck;
alts[WKey].v = &kbdr;
alts[WKey].op = CHANRCV;
alts[WMouse].c = w->mc.c;
alts[WMouse].v = &w->mc.m;
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;
npart = 0;
lastb = -1;
for(;;){
if(w->mouseopen && w->mouse.counter != w->mouse.lastcounter)
alts[WMouseread].op = CHANSND;
else
alts[WMouseread].op = CHANNOP;
// if(!w->scrolling && !w->mouseopen && w->qh>w->org+w->f.nchars)
// alts[WCwrite].op = CHANNOP;
// else
alts[WCwrite].op = CHANSND;
if(w->deleted || !w->wctlready)
alts[WWread].op = CHANNOP;
else
alts[WWread].op = 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 WKey:
for(i=0; kbdr[i]!=L'\0'; i++)
wkeyctl(w, kbdr[i]);
/* wkeyctl(w, r); */
/* while(nbrecv(w->ck, &r)) */
/* wkeyctl(w, r); */
break;
case WMouse:
if(w->mouseopen) {
w->mouse.counter++;
/* queue click events */
if(!w->mouse.qfull && lastb != w->mc.m.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->m = w->mc.m;
mp->counter = w->mouse.counter;
lastb = w->mc.m.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.m = w->mc.m;
m.counter = w->mouse.counter;
}
w->mouse.lastcounter = m.counter;
send(mrm.cm, &m.m);
continue;
case WCtl:
if(wctlmesg(w, wcm.type, wcm.r, wcm.image) == Exited){
chanfree(crm.c1);
chanfree(crm.c2);
chanfree(mrm.cm);
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;
up = rp;
initial = 0;
for(i=0; i<nr; i++){
switch(*bp){
case 0:
break;
case '\b':
if(up == rp)
initial++;
else
--up;
break;
case '\r':
while(i<nr-1 && *(bp+1) == '\r'){
bp++;
i++;
}
if(i<nr-1 && *(bp+1) != '\n'){
while(up > rp && *(up-1) != '\n')
up--;
if(up == rp)
initial = wbswidth(w, '\r');
}else if(i == nr-1)
*up++ = '\n';
break;
default:
*up++ = *bp;
break;
}
bp++;
}
if(initial){
if(initial > w->qh)
initial = w->qh;
qh = w->qh - initial;
wdelete(w, qh, qh+initial);
w->qh = qh;
}
nr = up - rp;
scrolling = w->scrolling && w->org <= w->qh && w->qh <= w->org + w->f.nchars;
w->qh = winsert(w, rp, nr, w->qh)+nr;
if(scrolling)
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;
if(r == 0)
return;
if(w->deleted)
return;
w->rawing = rawon();
/* navigation keys work only when mouse is not open */
if(!w->mouseopen)
switch(r){
case Kdown:
n = w->f.maxlines/3;
goto case_Down;
case Kscrollonedown:
n = mousescrollsize(w->f.maxlines);
if(n <= 0)
n = 1;
goto case_Down;
case Kpgdown:
n = 2*w->f.maxlines/3;
case_Down:
q0 = w->org+frcharofpt(&w->f, Pt(w->f.r.min.x, w->f.r.min.y+n*w->f.font->height));
wsetorigin(w, q0, TRUE);
return;
case Kup:
n = w->f.maxlines/3;
goto case_Up;
case Kscrolloneup:
n = mousescrollsize(w->f.maxlines);
if(n <= 0)
n = 1;
goto case_Up;
case Kpgup:
n = 2*w->f.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:
if(w->org > w->iq1) {
q0 = wbacknl(w, w->iq1, 1);
wsetorigin(w, q0, TRUE);
} else
wshow(w, 0);
return;
case Kend:
if(w->iq1 > w->org+w->f.nchars) {
q0 = wbacknl(w, w->iq1, 1);
wsetorigin(w, q0, TRUE);
} else
wshow(w, w->nr);
return;
case 0x01: /* ^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 0x05: /* ^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;
}
/*
* This if used to be below the if(w->rawing ...),
* but let's try putting it here. This will allow ESC-processing
* to toggle hold mode even in remote SSH connections.
* The drawback is that vi-style processing gets harder.
* If you find yourself in some weird readline mode, good
* luck getting out without ESC. Let's see who complains.
*/
if(r==0x1B || (w->holding && r==0x7F)){ /* toggle hold */
if(w->holding)
--w->holding;
else
w->holding++;
wrepaint(w);
if(r == 0x1B)
return;
}
if(!w->holding && w->rawing && (w->q0==w->nr || w->mouseopen)){
waddraw(w, &r, 1);
return;
}
if(r == Kcmd+'x'){
wsnarf(w);
wcut(w);
wscrdraw(w);
return;
}
if(r == Kcmd+'c'){
wsnarf(w);
return;
}
if(r == Kcmd+'v'){
riogetsnarf();
wpaste(w);
wscrdraw(w);
return;
}
if(r != 0x7F){
wsnarf(w);
wcut(w);
}
switch(r){
case 0x03: /* maybe send interrupt */
/* since ^C is so commonly used as interrupt, special case it */
if (intrc() != 0x03)
break;
/* fall through */
case 0x7F: /* send interrupt */
w->qh = w->nr;
wshow(w, w->qh);
winterrupt(w);
w->iq1 = w->q0;
return;
case 0x06: /* ^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);
w->iq1 = w->q0;
free(rp);
return;
case 0x08: /* ^H: erase character */
case 0x15: /* ^U: erase line */
case 0x17: /* ^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);
}
w->iq1 = w->q0;
return;
}
/* otherwise ordinary character; just insert */
q0 = w->q0;
q0 = winsert(w, &r, 1, q0);
wshow(w, q0+1);
w->iq1 = w->q0;
}
void
wsetcols(Window *w)
{
if(w->holding)
if(w == input)
w->f.cols[TEXT] = w->f.cols[HTEXT] = holdcol;
else
w->f.cols[TEXT] = w->f.cols[HTEXT] = lightholdcol;
else
if(w == input)
w->f.cols[TEXT] = w->f.cols[HTEXT] = display->black;
else
w->f.cols[TEXT] = w->f.cols[HTEXT] = darkgrey;
}
void
wrepaint(Window *w)
{
wsetcols(w);
if(!w->mouseopen){
frredraw(&w->f);
}
if(w == input){
wborder(w, wscale(w, Selborder));
wsetcursor(w, 0);
}else
wborder(w, wscale(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 && c != '\r') /* 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);
rioputsnarf();
}
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 && !w->holding && 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 CFid *fd;
char buf[32];
uint p0, p1;
Cursor *c;
if(fd == nil)
fd = plumbopenfid("send", OWRITE);
if(fd == nil)
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(plumbsendtofid(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, wscale(w, Selborder)));
}
void
wmousectl(Window *w)
{
int but;
if(w->mc.m.buttons == 1)
but = 1;
else if(w->mc.m.buttons == 2)
but = 2;
else if(w->mc.m.buttons == 4)
but = 3;
else{
if(w->mc.m.buttons == 8)
wkeyctl(w, Kscrolloneup);
if(w->mc.m.buttons == 16)
wkeyctl(w, Kscrollonedown);
return;
}
incref(&w->ref); /* hold up window while we track */
if(w->deleted)
goto Return;
if(ptinrect(w->mc.m.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->iq1)
w->iq1 -= min(n, w->iq1-q0);
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->f.nchars){
p1 = q1 - w->org;
if(p1 > w->f.nchars)
p1 = w->f.nchars;
if(q0 < w->org){
w->org = q0;
p0 = 0;
}else
p0 = q0 - w->org;
frdelete(&w->f, 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->f)
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->f.p0)
wsetselect(w, w->org+w->f.p0, selectq);
else
wsetselect(w, selectq, w->org+w->f.p0);
}else{
if(w->org+w->f.nchars == w->nr)
return;
q0 = w->org+frcharofpt(&w->f, Pt(w->f.r.min.x, w->f.r.min.y+dl*w->f.font->height));
if(selectq >= w->org+w->f.p1)
wsetselect(w, w->org+w->f.p1, selectq);
else
wsetselect(w, selectq, w->org+w->f.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.m.buttons;
q0 = w->q0;
q1 = w->q1;
selectq = w->org+frcharofpt(&w->f, w->mc.m.xy);
if(clickwin==w && w->mc.m.msec-clickmsec<500)
if(q0==q1 && selectq==w->q0){
wdoubleclick(w, &q0, &q1);
wsetselect(w, q0, q1);
flushimage(display, 1);
x = w->mc.m.xy.x;
y = w->mc.m.xy.y;
/* stay here until something interesting happens */
do
readmouse(&w->mc);
while(w->mc.m.buttons==b && abs(w->mc.m.xy.x-x)<3 && abs(w->mc.m.xy.y-y)<3);
w->mc.m.xy.x = x; /* in case we're calling frselect */
w->mc.m.xy.y = y;
q0 = w->q0; /* may have changed */
q1 = w->q1;
selectq = q0;
}
if(w->mc.m.buttons == b){
w->f.scroll = framescroll;
frselect(&w->f, &w->mc);
/* horrible botch: while asleep, may have lost selection altogether */
if(selectq > w->nr)
selectq = w->org + w->f.p0;
w->f.scroll = nil;
if(selectq < w->org)
q0 = selectq;
else
q0 = w->org + w->f.p0;
if(selectq > w->org+w->f.nchars)
q1 = selectq;
else
q1 = w->org+w->f.p1;
}
if(q0 == q1){
if(q0==w->q0 && clickwin==w && w->mc.m.msec-clickmsec<500){
wdoubleclick(w, &q0, &q1);
clickwin = nil;
}else{
clickwin = w;
clickmsec = w->mc.m.msec;
}
}else
clickwin = nil;
wsetselect(w, q0, q1);
flushimage(display, 1);
while(w->mc.m.buttons){
w->mc.m.msec = 0;
b = w->mc.m.buttons;
if(b & 6){
if(b & 2){
wsnarf(w);
wcut(w);
}else{
if(first){
first = 0;
riogetsnarf();
}
wpaste(w);
}
}
wscrdraw(w);
flushimage(display, 1);
while(w->mc.m.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);
w->wctlready = 1;
if(Dx(r) > 0){
if(w != input)
wcurrent(w);
}else if(w == input)
wcurrent(nil);
flushimage(display, 1);
break;
case Refresh:
if(w->deleted || Dx(w->screenr)<=0 || !rectclip(&r, w->i->r))
break;
if(!w->mouseopen)
wrefresh(w, r);
flushimage(display, 1);
break;
case Movemouse:
if(sweeping || !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:
if(w->deleted)
break;
wrepaint(w);
flushimage(display, 1);
break;
case Deleted:
if(w->deleted)
break;
write(w->notefd, "hangup", 6);
wclosewin(w);
break;
case Exited:
frclear(&w->f, 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);
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)
{
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
wcurrent(Window *w)
{
Window *oi;
if(wkeyboard!=nil && w==wkeyboard)
return;
oi = input;
input = w;
if(oi!=w && oi!=nil)
wrepaint(oi);
if(w !=nil){
wrepaint(w);
wsetcursor(w, 0);
}
if(w != oi){
if(oi){
oi->wctlready = 1;
wsendctlmesg(oi, Wakeup, ZR, nil);
}
if(w){
w->wctlready = 1;
wsendctlmesg(w, Wakeup, ZR, nil);
}
}
}
void
wsetcursor(Window *w, int force)
{
Cursor *p;
if(w==nil || /*w!=input || */ 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;
}
Window*
wtop(Point pt)
{
Window *w;
w = wpointto(pt);
if(w){
if(w->topped == topped)
return nil;
topwindow(w->i);
wcurrent(w);
flushimage(display, 1);
w->topped = ++topped;
}
return w;
}
void
wtopme(Window *w)
{
if(w!=nil && w->i!=nil && !w->deleted && w->topped!=topped){
topwindow(w->i);
flushimage(display, 1);
w->topped = ++ topped;
}
}
void
wbottomme(Window *w)
{
if(w!=nil && w->i!=nil && !w->deleted){
bottomwindow(w->i);
flushimage(display, 1);
w->topped = 0;
}
}
Window*
wlookid(int id)
{
int i;
for(i=0; i<nwindow; i++)
if(window[i]->id == id)
return window[i];
return nil;
}
void
wclosewin(Window *w)
{
Rectangle r;
int i;
w->deleted = TRUE;
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*));
w->deleted = TRUE;
r = w->i->r;
/* move it off-screen to hide it, in case client is slow in letting it go */
MOVEIT originwindow(w->i, r.min, view->r.max);
freeimage(w->i);
w->i = nil;
return;
}
error("unknown window in closewin");
}
void
wsetpid(Window *w, int pid, int dolabel)
{
char buf[128];
w->pid = pid;
if(dolabel){
sprint(buf, "rc %d", pid);
free(w->label);
w->label = estrdup(buf);
drawsetlabel(w->label);
}
}
static Rune left1[] = {
'{', '[', '(', '<', 0xAB,
0x207d, 0x2329, 0x27e6, 0x27e8, 0x27ea,
0xfe59, 0xfe5b, 0xfe5d, 0xff08, 0xff3b, 0xff5b,
0
};
static Rune right1[] = {
'}', ']', ')', '>', 0xBB,
0x207e, 0x232a, 0x27e7, 0x27e9, 0x27eb,
0xfe5a, 0xfe5c, 0xfe5e, 0xff09, 0xff3d, 0xff5d,
0
};
static Rune left2[] = { '\n', 0 };
static Rune left3[] = { '\'', '"', '`', 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->f.nchars;
if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
wscrdraw(w);
else{
nl = 4*w->f.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->f.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->f.nchars){
frdelete(&w->f, 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->f.nchars){
n = w->org - org;
r = runemalloc(n);
runemove(r, w->r+org, n);
frinsert(&w->f, r, r+n, 0);
free(r);
}else
frdelete(&w->f, 0, w->f.nchars);
w->org = org;
wfill(w);
wscrdraw(w);
wsetselect(w, w->q0, w->q1);
if(fixup && w->f.p1 > w->f.p0)
frdrawsel(&w->f, frptofchar(&w->f, w->f.p1-1), w->f.p1-1, w->f.p1, 1);
}
void
wsetselect(Window *w, uint q0, uint q1)
{
int p0, p1;
/* w->f.p0 and w->f.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->f.nchars)
p0 = w->f.nchars;
if(p1 > w->f.nchars)
p1 = w->f.nchars;
if(p0==w->f.p0 && p1==w->f.p1)
return;
/* screen disagrees with desired selection */
if(w->f.p1<=p0 || p1<=w->f.p0 || p0==p1 || w->f.p1==w->f.p0){
/* no overlap or too easy to bother trying */
frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, w->f.p1, 0);
frdrawsel(&w->f, frptofchar(&w->f, p0), p0, p1, 1);
goto Return;
}
/* overlap; avoid unnecessary painting */
if(p0 < w->f.p0){
/* extend selection backwards */
frdrawsel(&w->f, frptofchar(&w->f, p0), p0, w->f.p0, 1);
}else if(p0 > w->f.p0){
/* trim first part of selection */
frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, p0, 0);
}
if(p1 > w->f.p1){
/* extend selection forwards */
frdrawsel(&w->f, frptofchar(&w->f, w->f.p1), w->f.p1, p1, 1);
}else if(p1 < w->f.p1){
/* trim last part of selection */
frdrawsel(&w->f, frptofchar(&w->f, p1), p1, w->f.p1, 0);
}
Return:
w->f.p0 = p0;
w->f.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->iq1)
w->iq1 += n;
if(q0 < w->org)
w->org += n;
else if(q0 <= w->org+w->f.nchars)
frinsert(&w->f, r, r+n, q0-w->org);
return q0;
}
void
wfill(Window *w)
{
Rune *rp;
int i, n, m, nl;
if(w->f.lastlinefull)
return;
rp = malloc(messagesize);
do{
n = w->nr-(w->org+w->f.nchars);
if(n == 0)
break;
if(n > 2000) /* educated guess at reasonable amount */
n = 2000;
runemove(rp, w->r+(w->org+w->f.nchars), n);
/*
* it's expensive to frinsert more than we need, so
* count newlines.
*/
nl = w->f.maxlines-w->f.nlines;
m = 0;
for(i=0; i<n; ){
if(rp[i++] == '\n'){
m++;
if(m >= nl)
break;
}
}
frinsert(&w->f, rp, rp+i, w->f.nchars);
}while(w->f.lastlinefull == FALSE);
free(rp);
}
char*
wcontents(Window *w, int *ip)
{
return runetobyte(w->r, w->nr, ip);
}