plan9fox/sys/src/cmd/abaco/page.c
2011-03-30 19:35:09 +03:00

840 lines
14 KiB
C

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <plumb.h>
#include <html.h>
#include "dat.h"
#include "fns.h"
static void pageload1(Page *, Url *, int);
static
void
addchild(Page *p, Page *c)
{
Page *t;
c->parent = p;
c->w = p->w;
c->b = p->b;
c->col = p->col;
c->row = p->row;
if(p->child == nil)
p->child = c;
else{
for(t=p->child; t->next!=nil; t=t->next)
;
t->next = c;
}
}
static
void
loadchilds(Page *p, Kidinfo *k)
{
Runestr rs;
Kidinfo *t;
Page *c;
addrefresh(p, "loading frames...");
p->kidinfo = k;
for(t=k->kidinfos; t!=nil; t=t->next){
c = emalloc(sizeof(Page));
addchild(p, c);
if(t->isframeset){
c->url = urldup(p->url);
loadchilds(c, t);
}else{
c->kidinfo = t;
/* this check shouldn't be necessary, but... */
if(t->src){
rs.r = urlcombine(p->url->act.r, t->src);
rs.nr = runestrlen(rs.r);
pageload1(c, urlalloc(&rs, nil, HGet), FALSE);
closerunestr(&rs);
}
}
}
}
static struct {
char *mime;
char *filter;
}filtertab[] = {
"image/gif", "gif -t9",
"image/jpeg", "jpg -t9",
"image/jpg", "jpg -t9",
"image/pjpeg", "jpg -t9",
"image/png", "png -t9",
"image/ppm", "ppm -t9",
nil, nil,
};
char *
getfilter(Rune *r, int x, int y)
{
char buf[128];
int i;
snprint(buf, sizeof(buf), "%S", r);
for(i=0; filtertab[i].mime!=nil; i++)
if(cistrncmp(buf, filtertab[i].mime, strlen(filtertab[i].mime)) == 0)
break;
if(filtertab[i].filter == nil)
return nil;
if(x==0 && y==0)
return smprint("%s", filtertab[i].filter);
if(x!=0 && y!=0)
return smprint("%s | resample -x %d -y %d", filtertab[i].filter, x, y);
if(x != 0)
return smprint("%s | resample -x %d", filtertab[i].filter, x);
/* y != 0 */
return smprint("%s | resample -y %d", filtertab[i].filter, y);
}
static Cimage *cimages = nil;
static QLock cimagelock;
static
void
freecimage(Cimage *ci)
{
Cimage *ci1;
qlock(&cimagelock);
if(decref(ci) == 0){
if(ci->i)
freeimage(ci->i);
else if(ci->mi)
freememimage(ci->mi);
urlfree(ci->url);
ci1 = cimages;
if(ci1 == ci)
cimages = ci->next;
else{
while(ci1){
if(ci1->next == ci){
ci1->next = ci->next;
break;
}
ci1 = ci1->next;
}
}
free(ci);
}
qunlock(&cimagelock);
}
static
void
closeimages(Page *p)
{
int i;
for(i=0; i<p->ncimage; i++)
freecimage(p->cimage[i]);
free(p->cimage);
p->cimage =nil;
p->ncimage = 0;
}
static
Cimage *
loadimg(Rune *src, int x , int y)
{
Channel *sync;
Cimage *ci;
Runestr rs;
Exec *e;
char *filter;
int fd, p[2], q[2];
ci = emalloc(sizeof(Cimage));
rs. r = src;
rs.nr = runestrlen(rs.r);
ci->url = urlalloc(&rs, nil, HGet);
fd = urlopen(ci->url);
if(fd < 0){
Err1:
return ci;
}
filter = getfilter(ci->url->ctype.r, x, y);
if(filter == nil){
werrstr("%S unsupported: %S", ci->url->ctype.r, ci->url->act.r);
Err2:
close(fd);
goto Err1;
}
if(pipe(p)<0 || pipe(q)<0)
error("can't create pipe");
close(p[0]);
p[0] = fd;
sync = chancreate(sizeof(ulong), 0);
if(sync == nil)
error("can't create channel");
e = emalloc(sizeof(Exec));
e->p[0] = p[0];
e->p[1] = p[1];
e->q[0] = q[0];
e->q[1] = q[1];
e->cmd = filter;
e->sync = sync;
proccreate(execproc, e, STACK);
recvul(sync);
chanfree(sync);
close(p[0]);
close(p[1]);
close(q[1]);
ci->mi = readmemimage(q[0]);
close(q[0]);
if(ci->mi == nil){
werrstr("can't read image");
goto Err2;
}
free(filter);
return ci;
}
static
Cimage *
findimg(Rune *s)
{
Cimage *ci;
qlock(&cimagelock);
for(ci=cimages; ci!=nil; ci=ci->next)
if(runestrcmp(ci->url->src.r, s) == 0)
break;
qunlock(&cimagelock);
return ci;
}
void
loadimages(Page *p)
{
Cimage *ci;
Iimage *i;
Rune *src;
addrefresh(p, "loading images...");
reverseimages(&p->doc->images);
for(i=p->doc->images; i!=nil; i=i->nextimage){
if(p->aborting)
break;
src = urlcombine(getbase(p), i->imsrc);
ci = findimg(src);
if(ci == nil){
ci = loadimg(src, i->imwidth, i->imheight);
qlock(&cimagelock);
ci->next = cimages;
cimages = ci;
qunlock(&cimagelock);
}
free(src);
incref(ci);
i->aux = ci;
p->cimage = erealloc(p->cimage, ++p->ncimage*sizeof(Cimage *));
p->cimage[p->ncimage-1] = ci;
p->changed = TRUE;
addrefresh(p, "");
}
}
static char *mimetab[] = {
"text/html",
"application/xhtml",
nil,
};
static
void
pageloadproc(void *v)
{
Page *p;
char buf[BUFSIZE], *s;
long n, l;
int fd, i, ctype;
threadsetname("pageloadproc");
rfork(RFFDG);
p = v;
addrefresh(p, "opening: %S...", p->url->src.r);
fd = urlopen(p->url);
if(fd < 0){
addrefresh(p, "%S: %r", p->url->src.r);
Err:
p->loading = FALSE;
return;
}
if(runestrlen(p->url->ctype.r) == 0) /* assume .html when headers don't say anyting */
goto Html;
snprint(buf, sizeof(buf), "%S", p->url->ctype.r);
for(i=0; mimetab[i]!=nil; i++)
if(cistrncmp(buf, mimetab[i], strlen(mimetab[i])) == 0)
break;
if(mimetab[i]){
Html:
ctype = TextHtml;
}else if(cistrncmp(buf, "text/", 5) == 0)
ctype = TextPlain;
else{
close(fd);
addrefresh(p, "%S: unsupported mime type: '%S'", p->url->act.r, p->url->ctype.r);
goto Err;
}
addrefresh(p, "loading: %S...", p->url->src.r);
s = nil;
l = 0;
while((n=read(fd, buf, sizeof(buf))) > 0){
if(p->aborting){
if(s){
free(s);
s = nil;
}
break;
}
s = erealloc(s, l+n+1);
memmove(s+l, buf, n);
l += n;
s[l] = '\0';
}
close(fd);
n = l;
if(s){
s = convert(p->url->ctype, s, &n);
p->items = parsehtml((uchar *)s, n, p->url->act.r, ctype, UTF_8, &p->doc);
free(s);
fixtext(p);
if(ctype==TextHtml && p->aborting==FALSE){
p->changed = TRUE;
addrefresh(p, "");
if(p->doc->doctitle){
p->title.r = erunestrdup(p->doc->doctitle);
p->title.nr = runestrlen(p->title.r);
}
p->loading = XXX;
if(p->doc->kidinfo)
loadchilds(p, p->doc->kidinfo);
else if(p->doc->images)
loadimages(p);
}
}
p->changed = TRUE;
p->loading = FALSE;
addrefresh(p, "");
}
static
void
pageload1(Page *p, Url *u, int dohist)
{
pageclose(p);
p->loading = TRUE;
p->url = u;
if(dohist)
winaddhist(p->w, p->url);
proccreate(pageloadproc, p, STACK);
}
void
pageload(Page *p, Url *u, int dohist)
{
if(p->parent == nil)
textset(&p->w->url, u->src.r, u->src.nr);
draw(p->b, p->all, display->white, nil, ZP);
pageload1(p, u, dohist);
}
void
pageget(Page *p, Runestr *src, Runestr *post, int m, int dohist)
{
pageload(p, urlalloc(src, post, m), dohist);
}
void
pageclose(Page *p)
{
Page *c, *nc;
if(p == selpage)
selpage = nil;
pageabort(p);
closeimages(p);
urlfree(p->url);
p->url = nil;
if(p->doc){
freedocinfo(p->doc);
p->doc = nil;
}
layfree(p->lay);
p->lay = nil;
freeitems(p->items);
p->items = nil;
for(c=p->child; c!=nil; c=nc){
nc = c->next;
pageclose(c);
free(c);
}
p->child = nil;
closerunestr(&p->title);
closerunestr(&p->refresh.rs);
p->refresh.t = 0;
p->pos = ZP;
p->top = ZP;
p->bot = ZP;
p->loading = p->aborting = FALSE;
}
int
pageabort(Page *p)
{
Page *c;
for(c=p->child; c!=nil; c=c->next)
pageabort(c);
p->aborting = TRUE;
while(p->loading)
sleep(100);
p->aborting = FALSE;
return TRUE;
}
static Image *tmp;
void
tmpresize(void)
{
if(tmp)
freeimage(tmp);
tmp = eallocimage(display, Rect(0,0,Dx(screen->r),Dy(screen->r)), screen->chan, 0, -1);
}
static
void
renderchilds(Page *p)
{
Rectangle r;
Kidinfo *k;
Page *c;
int i, j, x, y, *w, *h;
draw(p->b, p->all, display->white, nil, ZP);
r = p->all;
y = r.min.y;
c = p->child;
k = p->kidinfo;
frdims(k->rows, k->nrows, Dy(r), &h);
frdims(k->cols, k->ncols, Dx(r), &w);
for(i=0; i<k->nrows; i++){
x = r.min.x;
for(j=0; j<k->ncols; j++){
if(c->aborting)
return;
c->b = p->b;
c->all = Rect(x,y,x+w[j],y+h[i]);
c->w = p->w;
pagerender(c);
c = c->next;
x += w[j];
}
y += h[i];
}
free(w);
free(h);
}
static
void
pagerender1(Page *p)
{
Rectangle r;
r = p->all;
p->hscrollr = r;
p->hscrollr.min.y = r.max.y-Scrollsize;
p->vscrollr = r;
p->vscrollr.max.x = r.min.x+Scrollsize;
r.max.y -= Scrollsize;
r.min.x += Scrollsize;
p->r = r;
p->vscrollr.max.y = r.max.y;
p->hscrollr.min.x = r.min.x;
laypage(p);
pageredraw(p);
}
void
pagerender(Page *p)
{
if(p->child && p->loading==FALSE)
renderchilds(p);
else if(p->doc)
pagerender1(p);
}
void
pageredraw(Page *p)
{
Rectangle r;
r = p->lay->r;
if(Dx(r)==0 || Dy(r)==0){
draw(p->b, p->r, display->white, nil, ZP);
return;
}
if(tmp == nil)
tmpresize();
p->selecting = FALSE;
draw(tmp, tmp->r, getbg(p), nil, ZP);
laydraw(p, tmp, p->lay);
draw(p->b, p->r, tmp, nil, tmp->r.min);
r = p->vscrollr;
r.min.y = r.max.y;
r.max.y += Scrollsize;
draw(p->b, r, tagcols[HIGH], nil, ZP);
draw(p->b, insetrect(r, 1), tagcols[BACK], nil, ZP);
pagescrldraw(p);
}
static
void
pageselect1(Page *p) /* when called, button 1 is down */
{
Point mp, npos, opos;
int b, scrled, x, y;
b = mouse->buttons;
mp = mousectl->xy;
opos = getpt(p, mp);
do{
x = y = 0;
if(mp.x < p->r.min.x)
x -= p->r.min.x-mp.x;
else if(mp.x > p->r.max.x)
x += mp.x-p->r.max.x;
if(mp.y < p->r.min.y)
y -= (p->r.min.y-mp.y)*Panspeed;
else if(mp.y > p->r.max.y)
y += (mp.y-p->r.max.y)*Panspeed;
scrled = pagescrollxy(p, x, y);
npos = getpt(p, mp);
if(opos.y < npos.y){
p->top = opos;
p->bot = npos;
}else{
p->top = npos;
p->bot = opos;
}
pageredraw(p);
if(scrled == TRUE)
scrsleep(100);
else
readmouse(mousectl);
mp = mousectl->xy;
}while(mousectl->buttons == b);
}
static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
static Rune left2[] = { L'\'', L'"', L'`', 0 };
static
Rune *left[] = {
left1,
left2,
nil
};
static
Rune *right[] = {
right1,
left2,
nil
};
void
pagedoubleclick(Page *p)
{
Point xy;
Line *l;
Box *b;
xy = getpt(p, mouse->xy);
l = linewhich(p->lay, xy);
if(l==nil || l->hastext==FALSE)
return;
if(xy.x<l->boxes->r.min.x && hasbrk(l->state)){ /* beginning of line? */
p->top = l->boxes->r.min;
if(l->next && !hasbrk(l->next->state)){
for(l=l->next; l->next!=nil; l=l->next)
if(hasbrk(l->next->state))
break;
}
p->bot = l->lastbox->r.max;;
}else if(xy.x>l->lastbox->r.max.x && hasbrk(l->next->state)){ /* end of line? */
p->bot = l->lastbox->r.max;
if(!hasbrk(l->state) && l->prev!=nil){
for(l=l->prev; l->prev!=nil; l=l->prev)
if(hasbrk(l->state))
break;
}
p->top = l->boxes->r.min;
}else{
b = pttobox(l, xy);
if(b!=nil && b->i->tag==Itexttag){
p->top = b->r.min;
p->bot = b->r.max;
}
}
p->top.y += 2;
p->bot.y -= 2;
pageredraw(p);
}
static uint clickmsec;
void
pageselect(Page *p)
{
int b, x, y;
selpage = p;
/*
* To have double-clicking and chording, we double-click
* immediately if it might make sense.
*/
b = mouse->buttons;
if(mouse->msec-clickmsec<500){
pagedoubleclick(p);
x = mouse->xy.x;
y = mouse->xy.y;
/* stay here until something interesting happens */
do
readmouse(mousectl);
while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
mouse->xy.x = x; /* in case we're calling pageselect1 */
mouse->xy.y = y;
}
if(mousectl->buttons == b)
pageselect1(p);
if(eqpt(p->top, p->bot)){
if(mouse->msec-clickmsec<500)
pagedoubleclick(p);
else
clickmsec = mouse->msec;
}
while(mouse->buttons){
mouse->msec = 0;
b = mouse->buttons;
if(b & 2) /* snarf only */
cut(nil, nil, TRUE, FALSE, nil, 0);
while(mouse->buttons == b)
readmouse(mousectl);
}
}
Page *
pagewhich(Page *p, Point xy)
{
Page *c;
if(p->child == nil)
return p;
for(c=p->child; c!=nil; c=c->next)
if(ptinrect(xy, c->all))
return pagewhich(c, xy);
return nil;
}
void
pagemouse(Page *p, Point xy, int but)
{
Box *b;
p = pagewhich(p, xy);
if(p == nil)
return;
if(pagerefresh(p))
return;
if(p->lay == nil)
return;
if(ptinrect(xy, p->vscrollr)){
pagescroll(p, but, FALSE);
return;
}
if(ptinrect(xy, p->hscrollr)){
pagescroll(p, but, TRUE);
return;
}
xy = getpt(p, xy);
b = boxwhich(p->lay, xy);
if(b && b->mouse)
b->mouse(b, p, but);
else if(but == 1)
pageselect(p);
}
void
pagetype(Page *p, Rune r, Point xy)
{
Box *b;
int x, y;
p = pagewhich(p, xy);
if(p == nil)
return;
if(pagerefresh(p))
return;
if(p->lay == nil)
return;
/* text field? */
xy = getpt(p, xy);
b = boxwhich(p->lay, xy);
if(b && b->key){
b->key(b, p, r);
return;
}
/* ^H: same as 'Back' */
if(r == 0x08){
wingohist(p->w, FALSE);
return;
}
x = 0;
y = 0;
switch(r){
case Kleft:
x -= Dx(p->r)/2;
break;
case Kright:
x += Dx(p->r)/2;
break;
case Kdown:
case Kscrollonedown:
y += Dy(p->r)/2;
break;
case Kpgdown:
y += Dy(p->r);
break;
case Kup:
case Kscrolloneup:
y -= Dy(p->r)/2;
break;
case Kpgup:
y -= Dy(p->r);
break;
case Khome:
y -= Dy(p->lay->r); /* force p->pos.y = 0 */
break;
case Kend:
y = Dy(p->lay->r) - Dy(p->r);
break;
default:
return;
}
if(pagescrollxy(p, x, y))
pageredraw(p);
}
void
pagesnarf(Page *p)
{
Runestr rs;
memset(&rs, 0, sizeof(Runestr));
laysnarf(p, p->lay, &rs);
putsnarf(&rs);
closerunestr(&rs);
}
void
pagesetrefresh(Page *p)
{
Runestr rs;
Rune *s, *q, *t;
char *v;
int n;
if(!p->doc || !p->doc->refresh)
return;
s = p->doc->refresh;
q = runestrchr(s, L'=');
if(q == nil)
return;
q++;
if(!q)
return;
n = runestrlen(q);
if(*q == L'''){
q++;
n -= 2;
}
if(n <= 0)
return;
t = runesmprint("%.*S", n, q);
rs.r = urlcombine(getbase(p), t);
rs.nr = runestrlen(rs.r);
copyrunestr(&p->refresh.rs, &rs);
closerunestr(&rs);
free(t);
/* now the time */
q = runestrchr(s, L';');
if(q){
v = smprint("%.*S", (int)(q-s), s);
p->refresh.t = atoi(v);
free(v);
}else
p->refresh.t = 1;
p->refresh.t += time(0);
}
int
pagerefresh(Page *p)
{
int t;
if(!p->refresh.t)
return 0;
t = p->refresh.t - time(0);
if(t > 0)
return 0;
pageget(p, &p->refresh.rs, nil, HGet, FALSE);
return 1;
}