#include "i.h"
enum {
ALERTLINELEN = 80, // max number of characters in an alert line
SP = 8, // a spacer for between controls
SP2 = 4 // half of SP
};
Channel* gochan;
AuthInfo* auths = nil;
Channel* popupans;
int popupactive = 0;
History history;
CtlLayout ctllay;
ProgLayout proglay;
PopupLayout popuplay;
Loc* keyfocus;
int pgrp = 0;
int dbgres = 0;
Frame* top;
Frame* curframe;
Frame* ctlframe;
Frame* progframe;
Frame* popupframe;
Image* popupwin;
static void start(void);
static void redrawctl(int resized);
static void redrawmain(int resized);
static void redrawprog(int resized);
static void dopopup(int kind, Rune* s);
static void redrawpopup(void);
static void finishpopup(int code);
static void freectls(Control** a, int n);
static Rune* ctlentrytext(Control* c);
static Loc* frameloc(Control* c, Frame* f);
static void resetkeyfocus(Frame* oldf);
static GoSpec* handlemouse(EV e);
static GoSpec* handlekey(EV e);
static void go(void* arg);
static void goproc(void* arg);
static int get(GoSpec* g, Frame* f, int origkind, HistNode* hn);
static void go_local(Frame* f, Rune* loc);
static void checkrefresh(Frame* f);
static Frame* findnamedframe(Frame* f, Rune* name);
static Frame* findframe(Frame* f, int id);
static GoSpec* anchorgospec(Item* it, Anchor* a, Point p);
static GoSpec* pushaction(Control* c, Loc* loc);
static GoSpec* form_submit(Frame* fr, Form* frm, Point p, Control* submitctl);
static Rune* ucvt(Rune* s);
static int hexdigit(int v);
static void form_reset(Frame* fr, Form* frm);
static GoSpec* formaction(int frameid, int formid, int ftype);
static ParsedUrl* findhit(Map* map, Point p, int w, int h, int* ptarget);
static int d2pix(Dimen d, int tot);
static GoSpec* newget(ParsedUrl* url, int target);
static GoSpec* newpost(ParsedUrl* url, Rune* body, int target);
static GoSpec* newspecial(int kind, HistNode* hn);
static GoSpec* copygospec(GoSpec* g);
static int gospecequal(GoSpec* a, GoSpec* b);
static int docconfigequal(DocConfig* a, DocConfig* b);
static int docconfigequalarray(DocConfig** a1, int n1, DocConfig** a2, int n2);
static DocConfig* newdocconfig(Rune* fname, Rune* title, int initconfig, GoSpec* g);
static DocConfig* copydocconfig(DocConfig* d);
static HistNode_list* newhistnodelist(HistNode* hn, HistNode_list* l);
static void histaddedge(HistNode* a, HistNode* b, int atob);
static HistNode* histnodecopy(HistNode* a);
static HistNode* newhistnode(DocConfig* top, DocConfig** kids, int nkids,
HistNode_list* preds, HistNode_list* succs);
static void histadd(Frame* f, GoSpec* g, int navkind);
static void histupdate(Frame* f);
static HistNode* histfind(int gokind);
static void histprint();
static HistNode_list* remhnode(HistNode_list* l, HistNode* hn);
static void printhnodeindices(char* label, HistNode_list* l);
static void dumphistory(void);
static AuthInfo* getauth(Rune* chal);
static Rune* tobase64(Rune* a);
static int c64(int c);
static void dosaveas(ByteSource* bsmain);
static void handleprogress(EV e);
static void showstatus(Rune* msg);
static void alert(void *arg);
static void showurl(Rune* u);
void
threadmain(int argc, char* argv[])
{
int showprog;
ResourceState curres;
ResourceState newres;
int v;
GoSpec* g;
void* alertargs[2];
int dfile;
EV ev;
uchar* fname;
meminit(GRmain);
if(initdraw(nil, nil, "i") < 0)
fatalerror("initdraw failed");
iutilsinit(argc, argv);
dbgres = config.dbg['r'];
showprog = config.showprogress;
if(dbg && config.dbgfile != nil) {
fname = fromStr(config.dbgfile, Strlen(config.dbgfile), UTF_8);
dfile = create((char*)fname, OWRITE, 0644);
if(dfile >= 0) {
dup(dfile, 1);
trace("debug output\n");
}
}
curres = curresstate();
if(dbgres) {
resstateprint(startres, "starting resources");
curres = curresstate();
}
guiinit();
if(dbgres) {
newres = curresstate();
resstateprint(resstatesince(newres, curres), "difference after made screen windows");
curres = newres;
}
start();
gochan = chancreate(sizeof(GoSpec*), 1);
if(gochan == nil)
fatalerror("Can't make go channel");
g = newget(config.starturl, FTtop);
if(dbgres) {
newres = curresstate();
resstateprint(resstatesince(newres, curres), "difference after initial configure");
curres = newres;
}
if(threadcreate(netget, nil, STACKSIZE) < 0)
fatalerror("Can't make netget thread!");
if(threadcreate(go, g, STACKSIZE) < 0)
fatalerror("Can't make go thread!");
while(1) {
if(recv(evchan, &ev) == -1)
goto maindone;
if(dbg > 1) {
if(ev.tag == EVmousetag) {
if(dbg > 2 || ev.u.mouse.mtype != Mmove)
trace("Ev: %M\n", &ev);
}
else
trace("Ev: %M\n", &ev);
}
switch(ev.tag) {
case EVkeytag:
switch(ev.u.keychar) {
case Kdown:
yscroll(curframe, CAscrollpage, 1);
break;
case Kup:
yscroll(curframe, CAscrollpage, -1);
break;
case Khome:
yscroll(curframe, CAscrollpage, -10000);
break;
default:
g = handlekey(ev);
break;
}
break;
case EVmousetag:
g = handlemouse(ev);
break;
case EVresizetag:
redrawctl(1);
redrawmain(1);
redrawprog(1);
curframe = top;
g = newspecial(GoHistnode, histfind(0));
break;
case EVexposetag:
g = nil;
break;
case EVhidetag:
g = nil;
break;
case EVquittag:
goto maindone;
break;
case EValerttag:
alertargs[0] = ev.u.alert.msg;
alertargs[1] = ev.u.alert.sync;
if(proccreate(alert, (void*)alertargs, STACKSIZESMALL) < 0) {
trace("can't create alert proc\n");
if(sendul((Channel*)ev.u.alert.sync, 1) < 0)
finish();
}
g = nil;
break;
case EVformtag:
g = formaction(ev.u.form.frameid, ev.u.form.formid, ev.u.form.ftype);
break;
case EVgotag:
if(ev.u.go.url == nil)
g = newspecial(GoHistnode, histfind(0));
else
g = newget(ev.u.go.url, ev.u.go.target);
break;
case EVprogresstag:
if(showprog)
handleprogress(ev);
g = nil;
break;
default:
trace("unknown event tag %d\n", ev.tag);
g = nil;
}
if(g != nil) {
abortgo();
v = nbsend(gochan, &g);
if(v < 0)
goto maindone;
if(v == 0) {
// discard the gospec: last one hasn't been
// acted on yet.
// (We do this to avoid having events pile up
// while waiting for abort to have effect)
trace("go pileup; discard\n");
g = nil;
}
}
}
maindone:
finish();
}
static void
start(void)
{
Image* i;
Image* m;
top = newframe();
ctlframe = newframe(); // not really a frame, but need cim pointer
progframe = newframe(); // not really a frame, but need cim pointer
popupframe = newframe(); // not really a frame, but need cim pointer
curframe = top;
ctllay.logoicon = geticon(IClogo, nil);
i = geticon(ICback, &m);
ctllay.controls[CLCbackbut] = newbutton(ctlframe, i, m, L"Go back", nil, 1, 1);
i = geticon(ICfwd, &m);
ctllay.controls[CLCfwdbut] = newbutton(ctlframe, i, m, L"Go forward", nil, 1, 1);
i = geticon(ICreload, &m);
ctllay.controls[CLCreloadbut] = newbutton(ctlframe, i, m, L"Reload current page", nil, 1, 1);
i = geticon(ICstop, &m);
ctllay.controls[CLCstopbut] = newbutton(ctlframe, i, m, L"Stop", nil, 1, 1);
i = geticon(IChist, &m);
ctllay.controls[CLChistbut] = newbutton(ctlframe, i, m, L"Show history", nil, 1, 1);
i = geticon(ICbmark, &m);
ctllay.controls[CLCbmarkbut] = newbutton(ctlframe, i, m, L"Show bookmarks", nil, 1, 1);
i = geticon(ICexit, &m);
ctllay.controls[CLCexitbut] = newbutton(ctlframe, i, m, L"Exit", nil, 1, 1);
ctllay.controls[CLCentry] = newentry(ctlframe, 30, 1, 0);
disable(ctllay.controls[CLCbackbut]);
disable(ctllay.controls[CLCfwdbut]);
disable(ctllay.controls[CLCstopbut]);
ctllay.status = nil;
keyfocus = frameloc(ctllay.controls[CLCentry], ctlframe);
gainfocus(ctllay.controls[CLCentry]);
popuplay.kind = PopupNone;
popupans = chancreate(sizeof(PopupAns), 0);
redrawctl(1);
redrawmain(1);
redrawprog(1);
}
static void
redrawctl(int resized)
{
Rectangle r;
Point p;
Image* li;
int lw;
int lh;
int i;
Control* b;
int x;
int y;
pushclipr(rctl);
r = insetrect(rctl, ReliefBd);
drawfill(screen, r, Grey);
drawrelief(screen, r, ReliefRaised);
replaceclipr(r);
p = ctllay.logopos;
li = ctllay.logoicon;
lw = Dx(li->r);
lh = Dy(li->r);
if(resized) {
ctlframe->r = insetrect(rctl, 2*ReliefBd);
ctlframe->cr = r;
ctlframe->cim = screen;
p = addpt(r.min, Pt(7, 7));
ctllay.logopos = p;
x = p.x + lw;
y = p.y;
x += SP;
for(i = 0; i < NUMCLCS; i++) {
b = ctllay.controls[i];
b->r = rectsubpt(b->r, b->r.min);
if(i == CLCentry || i == CLCexitbut)
continue;
b->r = rectaddpt(b->r, Pt(x, y));
x += Dx(b->r) + SP2;
}
x += SP2;
ctllay.entrypos = Pt(x, y);
ctllay.controls[CLCentry]->r = Rpt(ctllay.entrypos, Pt(r.max.x - 7, y + 22));
ctllay.controls[CLCentry]->r.max.x -= Dx(ctllay.controls[CLCexitbut]->r) + SP2;
ctllay.controls[CLCexitbut]->r = rectaddpt(ctllay.controls[CLCexitbut]->r,
Pt(ctllay.controls[CLCentry]->r.max.x + SP2, y));
}
ctllay.statuspos = Pt(p.x + lw + SP, p.y + Dy((ctllay.controls[CLCbackbut])->r) + SP);
draw(screen, Rpt(p, addpt(p, Pt(lw, lh))), li, nil, ZP);
for(i = 0; i < NUMCLCS; i++)
drawctl(ctllay.controls[i], 0);
showstatus(ctllay.status);
popclipr();
flushimage(display, 1);
}
static void
redrawmain(int resized)
{
if(resized) {
top->r = insetrect(rmain, 2*ReliefBd);
top->cr = top->r;
top->cim = screen;
resetframe(top);
imcacheresetlimits();
}
pushclipr(rmain);
drawrelief(screen, insetrect(top->r, -ReliefBd), ReliefRaised);
drawrelief(screen, top->r, ReliefSunk);
replaceclipr(top->r);
drawfill(screen, top->r, White);
popclipr();
flushimage(display, 1);
}
static void
redrawprog(int resized)
{
Rectangle r;
int i;
Control** newbox;
Point p;
int nbox;
int nboxold;
Control* c;
int vo;
if(!config.showprogress)
return;
pushclipr(rprog);
r = insetrect(rprog, ReliefBd);
drawfill(screen, r, Grey);
drawrelief(screen, r, ReliefRaised);
replaceclipr(r);
if(resized) {
p = proglay.first;
nbox = proglay.boxlen;
progframe->r = insetrect(rprog, 2*ReliefBd);
progframe->cr = progframe->r;
progframe->cim = screen;
nboxold = nbox;
c = newprogbox(progframe);
vo = (Dy(r) - Dy(c->r))/2;
p = addpt(r.min, Pt(7, vo));
proglay.first = p;
proglay.dx = Dx(c->r) + SP2;
nbox = (Dx(r) - 2*7 - SP2)/proglay.dx;
if(nboxold != nbox) {
newbox = (Control**)erealloc(proglay.box, nbox * sizeof(Control*));
proglay.box = newbox;
proglay.boxlen = nbox;
for(i = nboxold; i < nbox; i++)
proglay.box[i] = newprogbox(progframe);
}
for(i = 0; i < nbox; i++) {
c = proglay.box[i];
c->r = rectsubpt(c->r, c->r.min);
c->r = rectaddpt(c->r, Pt(p.x + i*proglay.dx, p.y));
}
}
for(i = 0; i < proglay.nused; i++)
drawctl(proglay.box[i], 0);
popclipr();
flushimage(display, 1);
}
// Display popup of given kind (s is more info for drawing, depending on kind),
// and return user's answer on popupans as (code, string), where code will
// be -1 for error, 0 when the user hit cancel, and 1 when the user hit OK.
static void
dopopup(int kind, Rune* s)
{
int w;
int h;
int n;
int nw;
int i;
int k;
int nline;
int curlen;
int curlinestart;
Control* cok;
Control* ccancel;
Point p;
Control* oldc;
Control* newc;
Control* chead;
Control* crealm;
Control* cunlab;
Control* cpwlab;
Control* cuser;
Control* cpass;
Control* clab;
Control* cfile;
Rune* l;
Control* c;
PopupAns pans;
Rune* words[BIGBUFSIZE];
int wordlens[BIGBUFSIZE];
Rune* lines[SMALLBUFSIZE];
cok = newbutton(popupframe, nil, nil, L"Ok", nil, 1, 1);
ccancel = newbutton(popupframe, nil, nil, L"Cancel", nil, 1, 1);
p = Pt(0, 0);
switch(kind) {
case PopupAuth:
popuplay.ncontrols = 8;
popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*));
popuplay.controls[0] = cuser = newentry(popupframe, 30, 1, 0);
popuplay.controls[1] = cpass = newentry(popupframe, 30, 1, 0);
popuplay.controls[2] = chead = newlabel(popupframe, L"Type your user name and password");
popuplay.controls[3] = crealm = newlabel(popupframe, Strdup2(L"Resource: ", s));
popuplay.controls[4] = cunlab = newlabel(popupframe, Strdup(L"User Name: "));
popuplay.controls[5] = cpwlab = newlabel(popupframe, Strdup(L"Password: "));
popuplay.controls[6] = cok;
popuplay.controls[7] = ccancel;
w = SP +
max(chead->r.max.x, max(crealm->r.max.x, cunlab->r.max.x + cuser->r.max.x)) +
SP;
w = min(w, Dx(rmain) - 2*SP);
h = SP + chead->r.max.y + SP + crealm->r.max.y + SP + cuser->r.max.y + SP +
cpass->r.max.y + 2*SP + cok->r.max.y + SP;
popupwin = makepopup(w, h);
if(popupwin == nil) {
pans.code = -1;
pans.s = nil;
if(send(popupans, &pans) < 0)
finish();
freectls(popuplay.controls, popuplay.ncontrols);
return;
}
p = addpt(popupwin->r.min, Pt(SP, SP));
chead->r = rectaddpt(chead->r, p);
p.y += Dy(chead->r) + SP;
crealm->r = rectaddpt(crealm->r, p);
p.y += Dy(crealm->r) + SP;
cunlab->r = rectaddpt(cunlab->r, p);
cuser->r = rectaddpt(cuser->r, addpt(p, Pt(Dx(cunlab->r), 0)));
p.y += Dy(cuser->r) + SP;
cpwlab->r = rectaddpt(cpwlab->r, p);
cpass->r = rectaddpt(cuser->r, Pt(0, Dy(cuser->r) + SP));
p.y += Dy(cpass->r) + 2*SP;
break;
case PopupSaveAs:
popuplay.ncontrols = 4;
popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*));
popuplay.controls[0] = cfile = newentry(popupframe, 40, 1, 0);
popuplay.controls[1] = clab = newlabel(popupframe, L"Save As: ");
popuplay.controls[2] = cok;
popuplay.controls[3] = ccancel;
((Centry*)cfile)->s = s;
w = SP + clab->r.max.x + cfile->r.max.x + SP;
h = SP + cfile->r.max.y + 2*SP + cok->r.max.y + SP;
popupwin = makepopup(w, h);
if(popupwin == nil) {
pans.code = -1;
pans.s = nil;
if(send(popupans, &pans) < 0)
finish();
freectls(popuplay.controls, popuplay.ncontrols);
return;
}
p = addpt(popupwin->r.min, Pt(SP, SP));
clab->r = rectaddpt(clab->r, p);
cfile->r = rectaddpt(cfile->r, addpt(p, Pt(Dx(clab->r) + SP, 0)));
p.y += Dy(clab->r) + 2*SP;
break;
case PopupAlert:
ccancel = nil;
// split s up into lines of at most ALERTLINELEN characters each
n = splitall(s, Strlen(s), L" \t\n\r", words, wordlens, BIGBUFSIZE);
curlen = 0;
curlinestart = 0;
nline = 0;
for(i = 0; i < n; i++) {
nw = wordlens[i];
if(curlen + nw >= ALERTLINELEN) {
l = newstr(curlen);
if(nline < SMALLBUFSIZE)
lines[nline++] = l;
for(k = curlinestart; k < i; k++) {
l = Stradd(l, words[k], wordlens[k]);
if(k < i-1)
*l++ = ' ';
}
*l = 0;
curlinestart = i;
curlen = nw;
}
else
curlen += nw + (curlen > 0);
}
if(curlen != 0) {
l = newstr(curlen);
if(nline < SMALLBUFSIZE)
lines[nline++] = l;
for(k = curlinestart; k < i; k++) {
l = Stradd(l, words[k], wordlens[k]);
if(k < i-1)
*l++ = ' ';
}
*l = 0;
}
popuplay.ncontrols = nline+1;
popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*));
popuplay.controls[0] = cok;
w = SP + cok->r.max.x + SP;
h = SP + 2*SP + cok->r.max.y + SP;
for(k = 0; k < nline; k++) {
l = lines[k];
c = newlabel(popupframe, l);
popuplay.controls[k+1] = c;
w = max(w, SP + Dx(c->r) + SP);
h += Dy(c->r);
}
popupwin = makepopup(w, h);
if(popupwin == nil) {
pans.code = -1;
pans.s = nil;
if(send(popupans, &pans) < 0)
finish();
freectls(popuplay.controls, popuplay.ncontrols);
return;
}
p = addpt(popupwin->r.min, Pt(SP, SP));
for(k = 1; k <= nline; k++) {
c = popuplay.controls[k];
c->r = rectaddpt(c->r, p);
p.y += Dy(c->r);
}
p.y += 2*SP;
break;
default:
assert(0);
break;
}
if(ccancel == nil) {
p.x += (Dx(popupwin->r) - cok->r.max.x)/2 - SP;
cok->r = rectaddpt(cok->r, p);
}
else {
p.x += (Dx(popupwin->r) - (cok->r.max.x + SP + ccancel->r.max.x))/2 - SP;
cok->r = rectaddpt(cok->r, p);
p.x += Dx(cok->r) + SP;
ccancel->r = rectaddpt(ccancel->r, p);
}
popupframe->cim = popupwin;
popuplay.kind = kind;
popuplay.okbut = cok;
popuplay.cancelbut = ccancel;
popupactive = 1;
oldc = keyfocus->le[keyfocus->n - 1].control;
if(oldc != nil)
losefocus(oldc);
newc = popuplay.controls[0];
keyfocus = frameloc(newc, popupframe);
gainfocus(newc);
redrawpopup();
}
static void
redrawpopup(void)
{
Rectangle r;
int i;
if(popuplay.kind == PopupNone)
return ;
assert(popupwin != nil);
popupwin->clipr = popupwin->r;
r = insetrect(popupwin->r, ReliefBd);
drawfill(popupwin, r, Grey);
drawrelief(popupwin, r, ReliefRaised);
popupwin->clipr = r;
for(i = 0; i < popuplay.ncontrols; i++)
drawctl(popuplay.controls[i], 0);
flushimage(display, 1);
}
static void
finishpopup(int code)
{
PopupAns pans;
pans.code = code;
pans.s = nil;
switch(popuplay.kind) {
case PopupAuth:
pans.s = Strdup3(ctlentrytext(popuplay.controls[0]), L":",
ctlentrytext(popuplay.controls[1]));
break;
case PopupSaveAs:
pans.s = ctlentrytext(popuplay.controls[0]);
break;
}
popuplay.kind = PopupNone;
popuplay.controls = nil;
popuplay.okbut = nil;
popuplay.cancelbut = nil;
popupframe->cim = nil;
popupwin = nil;
popupactive = 0;
freectls(popuplay.controls, popuplay.ncontrols);
if(send(popupans, &pans) < 0)
finish();
}
// assuming c is an entry control, return its contents
// (caller must dup, if it wants to save the result)
static Rune*
ctlentrytext(Control* c)
{
assert(c->tag == Centrytag);
return ((Centry*)c)->s;
}
// Return a Loc representing a control in the frame f
static Loc*
frameloc(Control* c, Frame* f)
{
Loc* loc;
loc = newloc();
addloc(loc, LEframe, f->r.min);
loc->le[loc->n - 1].frame = f;
addloc(loc, LEcontrol, c->r.min);
loc->le[loc->n - 1].control = c;
return loc;
}
// Frame oldf is being reset, so change keyfocus back to ctllay.entry
static void
resetkeyfocus(Frame* oldf)
{
USED(oldf);
keyfocus = frameloc(ctllay.controls[CLCentry], ctlframe);
}
// If mouse event results in command to navigate somewhere else,
// return a GoSpec ref, else nil.
// TODO: deactivate activated controls if mouse leaves the area;
// perhaps do grabs?
static GoSpec*
handlemouse(EV e)
{
Point p;
GoSpec* g;
Control* oldc;
Control* c;
Anchor* a;
Item* it;
// ScriptEvent* se;
// int hasscripts;
Frame* f;
int n1;
Loc* loc;
Rune* msg;
int i;
Cprogbox* pc;
int ns;
char buf[SMALLBUFSIZE];
p = e.u.mouse.p;
g = nil;
if(popupactive) {
for(i = 0; i < popuplay.ncontrols; i++) {
c = popuplay.controls[i];
if(ptinrect(p, c->r)) {
if(dbg > 1)
trace("mouse in popup control\n");
switch(domouse(c, p, e.u.mouse.mtype)) {
case CAbuttonpush:
if(c == popuplay.okbut)
finishpopup(1);
else if(c == popuplay.cancelbut)
finishpopup(0);
break;
case CAkeyfocus:
if(dbg > 1)
printloc(keyfocus, "old focus");
oldc = keyfocus->le[keyfocus->n - 1].control;
if(oldc != nil)
losefocus(oldc);
keyfocus = frameloc(c, popupframe);
gainfocus(c);
if(dbg > 1)
printloc(keyfocus, "new focus");
break;
}
}
}
}
else if(ptinrect(p, rctl)) {
for(i = 0; i < NUMCLCS; i++) {
c = ctllay.controls[i];
if(ptinrect(p, c->r)) {
if(dbg > 1)
trace("mouse in controlwin control\n");
switch(domouse(c, p, e.u.mouse.mtype)) {
case CAbuttonpush:
switch(i) {
case CLCbackbut:
g = newspecial(GoHistnode, histfind(-1));
break;
case CLCfwdbut:
g = newspecial(GoHistnode, histfind(1));
break;
case CLCreloadbut:
g = newspecial(GoHistnode, histfind(0));
break;
case CLChistbut:
g = newspecial(GoHistory, nil);
break;
case CLCbmarkbut:
g = newspecial(GoBookmarks, nil);
break;
case CLCstopbut:
g = newspecial(GoStop, nil);
break;
case CLCexitbut:
finish();
}
break;
case CAflyover:
if(c->tag == Cbuttontag)
showstatus(((Cbutton*)c)->label);
break;
case CAkeyfocus:
if(dbg > 1)
printloc(keyfocus, "old focus");
oldc = keyfocus->le[keyfocus->n - 1].control;
if(oldc != nil)
losefocus(oldc);
keyfocus = frameloc(c, ctlframe);
gainfocus(c);
if(dbg > 1)
printloc(keyfocus, "new focus");
break;
}
break;
}
}
}
else if(ptinrect(p, rmain)) {
loc = findloc(top, p, nil);
if(loc != nil) {
if(dbg > 1)
printloc(loc, "mouse loc");
f = lastframe(loc);
// hasscripts = f->doc->hasscripts;
if(e.u.mouse.mtype != Mmove)
curframe = f;
n1 = loc->n - 1;
switch(loc->le[n1].kind) {
case LEitem:
it = loc->le[n1].item;
if(it->anchorid >= 0) {
for(a = f->doc->anchors; a != nil; a = a->next) {
if(a->index == it->anchorid) {
if(dbg > 1)
trace("in anchor %d, href=%U\n", a->index, a->href);
if(e.u.mouse.mtype == Mlbuttonup)
g = anchorgospec(it, a, loc->pos);
else if(e.u.mouse.mtype == Mmbuttonup) {
showstatus(a->href->url);
}
}
}
}
break;
case LEcontrol:
c = loc->le[n1].control;
switch(domouse(c, p, e.u.mouse.mtype)) {
case CAbuttonpush:
// if(hasscripts && c->ff != nil && c->ff->events != nil) {
// se = copyScriptEvent((ScriptEvent(Aonclick, f->id,
// c->ff->form->formid, c->ff->fieldid, -1, e->p.x, e->p.y, 1));
// /* TODO spawn */do_on(se);
// return nil;
// }
g = pushaction(c, loc);
break;
case CAkeyfocus:
if(dbg > 1)
printloc(keyfocus, "old focus");
oldc = keyfocus->le[keyfocus->n - 1].control;
if(oldc != nil)
losefocus(oldc);
keyfocus = frameloc(c, ctlframe);
gainfocus(c);
if(dbg > 1)
printloc(keyfocus, "new focus");
break;
}
break;
}
}
}
else if(ptinrect(p, rprog)) {
for(i = 0; i < proglay.boxlen; i++) {
c = proglay.box[i];
if(ptinrect(p, c->r)) {
if(dbg > 1)
trace("mouse in progbox control %d\n", i);
switch(domouse(c, p, e.u.mouse.mtype)) {
case CAbuttonpush:
switch(c->tag) {
case Cprogboxtag:
assert(c->tag == Cprogboxtag);
pc = (Cprogbox*)c;
ns = snprint(buf, sizeof(buf), "%S, %d%% done", pc->src, pc->pcnt);
if(pc->err != nil)
ns += snprint(buf+ns, sizeof(buf)-ns, ", %S", pc->err);
if(dbg)
ns += snprint(buf+ns, sizeof(buf)-ns, ", bsid=%d", pc->bsid);
msg = toStr((uchar*)buf, ns, UTF_8);
showstatus(msg);
break;
}
break;
}
}
}
}
return g;
}
// If key event results in command to navigate somewhere else,
// return a GoSpec ref, else nil.
static GoSpec*
handlekey(EV e)
{
Loc* loc;
int n1;
ParsedUrl* u;
Rune* s;
Centry* ce;
Control* c;
loc = keyfocus;
if(dbg > 1)
printloc(loc, "key focus loc");
n1 = loc->n - 1;
switch(loc->le[n1].kind) {
case LEcontrol:
c = loc->le[n1].control;
switch(c->tag) {
case Centrytag:
ce = (Centry*)c;
switch(dokey(c, e.u.keychar)) {
case CAreturnkey:
if(c == ctllay.controls[CLCentry]) {
s = ce->s;
if(s != nil) {
u = makeurl(s, 1);
return newget(u, FTtop);
}
}
else if(popupactive) {
finishpopup(1);
return nil;
}
else if(c->ff != nil)
return form_submit(c->f, c->ff->form, ZP, c);
break;
case CAtabkey:
break;
}
break;
}
break;
}
return nil;
}
// Run as separate thread
// Arg has first GoSpec* as arg
static void
go(void* arg)
{
GoSpec* g;
int origkind;
int rv;
HistNode* hn;
Frame* f;
ParsedUrl* url;
Rune* s;
EV prog;
g = (GoSpec*)arg;
while(1) {
origkind = g->kind;
hn = nil;
switch(g->kind) {
case GoNormal:
break;
case GoHistnode:
hn = g->histnode;
if(hn == nil)
return;
g = hn->topconfig->gospec;
break;
case GoBookmarks:
s = Strdup3(L"file:", config.userdir, L"/bookmarks.html");
url = makeurl(s, 0);
g = newget(url, FTtop);
break;
case GoHistory:
s = Strdup3(L"file:", config.userdir, L"/history.html");
url = makeurl(s, 0);
dumphistory();
g = newget(url, FTtop);
break;
}
switch(g->target) {
case FTtop:
curframe = top;
break;
case FTself:
break; // curframe is already OK
case FTparent:
if(curframe->parent != nil)
curframe = curframe->parent;
break;
case FTblank:
curframe = top; // we don't create new browsers...
break;
default:
// this is recommended "current practice"
curframe = findnamedframe(curframe, targetname(g->target));
if(curframe == nil) {
curframe = findnamedframe(top, targetname(g->target));
if(curframe == nil)
curframe = top;
}
}
if(g->kind == GoStop) {
if(dbg)
trace("\n\nSTOP\n");
showstatus(L"Stopped");
}
else {
f = curframe;
if(dbg) {
trace("\n\nGO TO %U\n", g->url);
if(g->target != FTtop)
trace("target frame name=%S\n", f->name);
}
if(g->url->nfrag != 0 && origkind == GoNormal
&& f->doc != nil && f->doc->src != nil && urlequal(g->url, f->doc->src)) {
go_local(f, g->url->frag);
return ;
}
if(config.showprogress) {
prog.tag = EVprogresstag;
prog.u.progress.bsid = -1;
prog.u.progress.state = 0;
prog.u.progress.pcnt = 0;
prog.u.progress.s = nil;
if(send(evchan, &prog) < 0)
finish();
}
enable(ctllay.controls[CLCstopbut]);
rv = get(g, f, origkind, hn);
disable(ctllay.controls[CLCstopbut]);
if(rv) {
showstatus(L"Done");
checkrefresh(f);
}
}
if(recv(gochan, &g) < 0)
finish();
}
}
// The important invariant in get/layout is that we keep
// track of every ByteSource returned by startreq, and
// ensure that we freebs each of them.
// And we can't start a new get() until all the ones started
// for the current get() have been freed.
static int
get(GoSpec* g, Frame* f, int origkind, HistNode* hn)
{
ResourceState curres;
ResourceState newres;
Rune* sdest;
ByteSource* bsmain;
Header* hdr;
ReqInfo* ri;
int authtried;
AuthInfo* auth;
int nredirs;
ByteSource* bs;
int use;
int error;
Rune* challenge;
ParsedUrl* newurl;
GoSpec* gs;
Framelist* kl;
Frame* k;
int i;
Rune* msg;
uchar* body;
int bodylen;
if(dbgres) {
imcacheclear();
curres = curresstate();
}
sdest = g->url->url;
msg = Strdup2(L"Fetching ", sdest);
showstatus(msg);
if(g->body != nil) {
body = fromStr(g->body, Strlen(g->body), UTF_8);
bodylen = strlen((char*)body);
}
else {
body = nil;
bodylen = 0;
}
ri = newreqinfo(g->url, g->meth, body, bodylen, g->auth, g->target);
authtried = 0;
auth = nil;
for(nredirs = 0; ; nredirs++) {
bsmain = startreq(ri);
if(bsmain->err) {
showstatus(errphrase(bsmain->err));
freebs(bsmain);
goto errret;
}
bs = waitreq();
if(bs == nil)
goto errret;
assert(bs == bsmain);
if(bsmain->err != 0) {
showstatus(errphrase(bsmain->err));
freebs(bsmain);
goto errret;
}
hdr = bsmain->hdr;
use = hdraction(bsmain, 1, nredirs, &error, &challenge, &newurl);
if(challenge != nil) {
if(authtried) {
error = ERRauthfailed;
use = 1;
}
else {
auth = getauth(challenge);
if(auth != nil) {
ri->auth = auth->credentials;
authtried = 1;
freebs(bsmain);
continue;
}
else {
error = ERRauthfailed;
use = 1;
}
}
}
if(error)
showstatus(errphrase(error));
else {
showstatus(hcphrase(hdr->code));
if(authtried) {
auth->next = auths;
auths = auth;
}
}
if(newurl != nil) {
ri->url = newurl;
ri->method = HGet;
freebs(bsmain);
continue;
}
if(use == 0) {
freebs(bsmain);
goto errret;
}
break;
}
if(dbgres > 1) {
newres = curresstate();
resstateprint(resstatesince(newres, curres), "resources to get header");
curres = newres;
}
if(hdr->length > 0 && (hdr->mtype == TextHtml || hdr->mtype == TextPlain || supported(hdr->mtype))) {
showurl(sdest);
histadd(f, g, origkind);
resetkeyfocus(f);
layout(f, bsmain, nil);
histupdate(f);
if(dbgres > 1) {
newres = curresstate();
resstateprint(resstatesince(newres, curres), "resources to get page and do layout");
curres = newres;
}
if(f->kids != nil) {
i = 0;
for(kl = f->kids; kl != nil; kl = kl->next) {
k = kl->val;
if(k->src != nil) {
if(hn != nil)
gs = hn->kidconfigs[i]->gospec;
else
gs = newget(copyurl(k->src), FTself);
if(dbg)
trace("get child frame %U\n", gs->url);
if(!get(gs, k, GoNormal, nil))
goto errret;
}
i++;
}
}
if(g->url->nfrag != 0)
go_local(f, g->url->frag);
}
else {
if(hdr->length == 0) {
showstatus(L"Empty page");
freebs(bsmain);
}
else {
msg = Strdup2(L"Unsupported media type: ", mnames[hdr->mtype]);
showstatus(msg);
dosaveas(bsmain); // frees bsmain when done
}
}
if(dbgres == 1) {
newres = curresstate();
resstateprint(resstatesince(newres, curres), "resources to do page");
curres = newres;
}
return 1;
errret:
return 0;
}
static void
go_local(Frame* f, Rune* loc)
{
Loc* dloc;
Point p;
DestAnchor* d;
if(dbg)
trace("go to local destination %S\n", loc);
for(d = f->doc->dests; d != nil; d = d->next) {
if(!Strcmp(d->name, loc)) {
dloc = findloc(f, ZP, d->item);
if(dloc == nil) {
if(warn)
trace("couldn't find item for destination anchor %S\n", loc);
return ;
}
p = sptolp(f, dloc->le[dloc->n - 1].pos);
yscroll(f, CAscrollabs, p.y);
return;
}
}
if(warn)
trace("couldn't find destination anchor %S\n", loc);
}
// If refresh has been set in f (i.e., client pull),
// pause the appropriate amount of time and then go to new place
static void
checkrefresh(Frame* f)
{
Rune* s;
int seconds;
ParsedUrl* url;
int n;
EV e;
EV* goe;
Rune* a[2];
int na[2];
if(f->doc != nil && f->doc->refresh != nil) {
seconds = 0;
url = nil;
n = splitall(f->doc->refresh, Strlen(f->doc->refresh), L"; ", a, na, 2);
if(n > 0) {
seconds = Strtol(a[0], nil, 10);
if(n > 1) {
s = a[1];
if(na[1] > 4 && !Strncmpci(s, 4, L"url=")) {
s = Strndup(s+4, na[1]-4);
url = makeurl(s, 0);
url = makeabsoluteurl(url, f->doc->base);
}
}
}
goe = (EV*)emalloc(sizeof(EV));
if(url == nil)
*goe = evgo(nil, FTtop, EGreload, f->id);
else
*goe = evgo(copyurl(url), targetid(f->name), EGnormal, f->id);
e.tag = EVdelaytag;
e.genframeid = f->id;
e.u.delay.millisecs = seconds*1000;
e.u.delay.ev = goe;
if(send(evchan, &e) < 0)
finish();
}
}
// Do depth first search from f, looking for frame with given name.
static Frame*
findnamedframe(Frame* f, Rune* name)
{
Framelist* kl;
Frame* a;
if(!Strcmp(f->name, name))
return f;
for(kl = f->kids; kl != nil; kl = kl->next) {
a = findnamedframe(kl->val, name);
if(a != nil)
return a;
}
return nil;
}
// Similar, but look for frame id, starting from f
static Frame*
findframe(Frame* f, int id)
{
Framelist* kl;
Frame* a;
if(f->id == id)
return f;
for(kl = f->kids; kl != nil; kl = kl->next) {
a = findframe(kl->val, id);
if(a != nil)
return a;
}
return nil;
}
// Return Gospec resulting from button up in anchor a, at offset pos inside item it.
static GoSpec*
anchorgospec(Item* it, Anchor* a, Point p)
{
GoSpec* g;
ParsedUrl* u;
int target;
int x;
int y;
Rune* sx;
Rune* sy;
Rune* q;
CImage* ci;
Iimage* i;
Ifloat* f;
g = nil;
target = a->target;
u = nil;
switch(it->tag) {
case Iimagetag:
i = (Iimage*)it;
ci = i->ci;
if(ci->mims != nil) {
if(i->map != nil) {
u = findhit(i->map, p, ci->width, ci->height, &target);
}
else if(a->href != nil && (it->state&IFsmap)) {
x = min(max(p.x - (i->hspace + i->border), 0), ci->width - 1);
y = min(max(p.y - (i->vspace + i->border), 0), ci->height - 1);
sx = ltoStr(x);
sy = ltoStr(y);
q = Strdup3(sx, L",", sy);
u = makequeryurl(a->href, q);
}
}
break;
case Ifloattag:
f = (Ifloat*)it;
return anchorgospec(f->item, a, p);
default:
u = copyurl(a->href);
}
if(u != nil)
g = newget(u, target);
return g;
}
// Control c has been pushed.
// Find the form it is in and perform required action (reset, or submit).
// If a submit, the return value is the place to go to.
static GoSpec*
pushaction(Control* c, Loc* loc)
{
Formfield* ff;
Frame* f;
Cbutton* b;
if(c->tag == Cbuttontag) {
b = (Cbutton*)c;
ff = b->ff;
f = b->f;
if(ff != nil) {
switch(ff->ftype) {
case Fsubmit:
case Fimage:
return form_submit(c->f, ff->form, loc->pos, c);
break;
case Freset:
form_reset(f, ff->form);
break;
}
}
}
return nil;
}
static GoSpec*
form_submit(Frame* fr, Form* frm, Point p, Control* submitctl)
{
Rune* v;
Rune* sep;
Rune* t;
Rune* z;
Strlist* radiodone;
Formfield* f;
int nnonhidden;
Strlist* rl;
int checked;
int i;
int n;
Cselect* cs;
Rune* val;
Control* c;
ParsedUrl* action;
if(submitctl != nil && submitctl->tag == Centrytag) {
nnonhidden = 0;
for(f = frm->fields; f != nil; f = f->next) {
if(f->ftype != Fhidden)
nnonhidden++;
}
if(nnonhidden > 1)
return nil;
}
v = nil;
sep = nil;
radiodone = nil;
for(f = frm->fields; f != nil; f = f->next) {
if(f->name == nil)
continue;
val = nil;
if(f->ctlid >= 0)
c = fr->controls[f->ctlid];
else
c = nil;
switch(f->ftype) {
case Ftext:
case Fpassword:
case Ftextarea:
if(c != nil)
val = ((Centry*)c)->s;
if(val != nil && !Strcmp(f->name, L"_ISINDEX_")) {
if(sep != nil)
v = Strdup2(v, sep);
t = ucvt(val);
v = Strdup2(v, t);
goto floop_done;
}
break;
case Fcheckbox:
case Fradio:
if(f->ftype == Fradio) {
// Need the following to catch case where there
// is more than one radiobutton with the same name
// and value.
for(rl = radiodone; rl != nil; rl = rl->next)
if(!Strcmp(rl->val, f->name))
goto floop_continue;
}
checked = 0;
if(c != nil)
if(c->tag == Ccheckboxtag)
checked = ((Ccheckbox*)c)->flags&CFactive;
if(checked) {
val = f->value;
if(f->ftype == Fradio)
radiodone = newstrlist(f->name, radiodone);
}
else
goto floop_continue;
break;
case Fhidden:
val = f->value;
break;
case Fsubmit:
if(submitctl != nil && f == submitctl->ff && Strcmp(f->name, L"_no_name_submit_"))
val = f->value;
else
goto floop_continue;
break;
case Fselect:
if(c != nil) {
assert(c->tag == Cselecttag);
cs = (Cselect*)c;
n = listlen((List*)cs->options);
for(i = 0; i < n; i++) {
if(cs->options[i].selected) {
if(sep != nil)
v = Strdup2(v, sep);
sep = L"&";
t = ucvt(f->name);
v = Strdup3(v, t, L"=");
t = ucvt(cs->options[i].value);
v = Strdup2(v, t);
}
}
goto floop_continue;
}
break;
case Fimage:
if(submitctl != nil && f == submitctl->ff) {
if(sep != nil)
v = Strdup2(v, sep);
sep = L"&";
z = Strdup2(f->name, L".x");
t = ucvt(z);
v = Strdup3(v, t, L"=");
z = ltoStr(max(p.x, 0));
t = ucvt(z);
v = Strdup3(v, t, sep);
z = Strdup2(f->name, L".y");
t = ucvt(z);
v = Strdup3(v, t, L"=");
z = ltoStr(max(p.y, 0));
t = ucvt(z);
v = Strdup2(v, t);
goto floop_continue;
}
break;
}
if(val != nil) {
if(sep != nil)
v = Strdup2(v, sep);
sep = L"&";
t = ucvt(f->name);
v = Strdup3(v, t, L"=");
if(val != nil) {
t = ucvt(val);
v = Strdup2(v, t);
}
}
floop_continue:
;
}
floop_done:
if(frm->method == HPost)
return newpost(copyurl(frm->action), v, frm->target);
else {
action = makequeryurl(frm->action, v);
return newget(action, frm->target);
}
}
static Rune*
ucvt(Rune* s)
{
Rune* u;
int i;
int c;
int n;
int j;
int len;
n = Strlen(s);
len = 0;
for(i = 0; i < n; i++) {
c = s[i];
if(inclass(c, L"- /$_@.!*'(),a-zA-Z0-9"))
len++;
else
len += 3;
}
u = newstr(len);
j = 0;
for(i = 0; i < n; i++) {
c = s[i];
if(inclass(c, L"-/$_@.!*'(),a-zA-Z0-9"))
u[j++] = c;
else if(c == ' ')
u[j++] = '+';
else {
u[j++] = '%';
u[j++] = hexdigit((c >> 4)&15);
u[j++] = hexdigit(c&15);
}
}
u[j] = 0;
return u;
}
static int
hexdigit(int v)
{
if(0 <= v && v <= 9)
return '0' + v;
else
return 'A' + v - 10;
}
static void
form_reset(Frame* fr, Form* frm)
{
Formfield* a;
for(a = frm->fields; a != nil; a = a->next) {
if(a->ctlid >= 0)
resetctl(fr->controls[a->ctlid]);
}
flushimage(display, 1);
}
static GoSpec*
formaction(int frameid, int formid, int ftype)
{
Frame* f;
Form* frm;
Docinfo* d;
trace("formaction %d %d %d\n", frameid, formid, ftype);
f = findframe(top, frameid);
if(f != nil) {
d = f->doc;
if(d != nil) {
for(frm = d->forms; frm != nil; frm = frm->next) {
if(frm->formid == formid) {
if(ftype == EFsubmit) {
return form_submit(f, frm, Pt(0, 0), nil);
}
else {
form_reset(f, frm);
return nil;
}
}
}
}
}
return nil;
}
// Find hit in a local map.
// If found, return action url, and target frame in *ptarget.
static ParsedUrl*
findhit(Map* map, Point p, int w, int h, int* ptarget)
{
int x;
int y;
ParsedUrl* dflt;
int dflttarg;
int xd;
int yd;
double xi;
double yi;
double xj;
double yj;
int np;
double xr;
double yr;
int j;
int i;
Area *a;
Dimen* c;
int nc;
int x1;
int y1;
int x2;
int y2;
int hit;
x = p.x;
y = p.y;
dflt = nil;
dflttarg = FTself;
for(a = map->areas; a != nil; a = a->next) {
c = a->coords;
nc = a->ncoords;
x1 = 0;
y1 = 0;
x2 = 0;
y2 = 0;
if(nc >= 2) {
x1 = d2pix(c[0], w);
y1 = d2pix(c[1], h);
if(nc > 2) {
x2 = d2pix(c[2], w);
if(nc > 3)
y2 = d2pix(c[3], h);
}
}
hit = 0;
switch(a->shape) {
case SHrect:
if(nc == 4)
hit = x1 <= x && x <= x2 && y1 <= y && y <= y2;
break;
case SHcircle:
if(nc == 3) {
xd = x - x1;
yd = y - y1;
hit = xd*xd + yd*yd <= x2*x2;
}
break;
case SHpoly:
np = nc/2;
hit = 0;
xr = (double)x;
yr = (double)y;
j = np - 1;
for(i = 0; i < np; j = i++) {
xi = (double)d2pix(c[2*i], w);
yi = (double)d2pix(c[2*i + 1], h);
xj = (double)d2pix(c[2*j], w);
yj = (double)d2pix(c[2*j + 1], h);
if((((yi <= yr) && (yr < yj)) ||
((yj <= yr) && (yr < yi))) &&
(xr < (xj - xi)*(yr - yi)/(yj - yi) + xi))
hit = !hit;
}
break;
default:
dflt = a->href;
dflttarg = a->target;
}
if(hit) {
*ptarget = a->target;
return copyurl(a->href);
}
}
*ptarget = dflttarg;
return copyurl(dflt);
}
static int
d2pix(Dimen d, int tot)
{
int ans;
ans = dimenspec(d);
if(dimenkind(d) == Dpercent)
ans = (ans*tot)/100;
return ans;
}
static GoSpec*
newget(ParsedUrl* url, int target)
{
GoSpec* g;
g = (GoSpec*)emallocz(sizeof(GoSpec));
g->kind = GoNormal;
g->url = url;
g->meth = HGet;
g->target = target;
return g;
}
static GoSpec*
newpost(ParsedUrl* url, Rune* body, int target)
{
GoSpec* g;
g = (GoSpec*)emallocz(sizeof(GoSpec));
g->kind = GoNormal;
g->url = url;
g->meth = HPost;
g->body = body;
g->target = target;
return g;
}
static GoSpec*
newspecial(int kind, HistNode* hn)
{
GoSpec* g;
g = (GoSpec*)emallocz(sizeof(GoSpec));
g->kind = kind;
g->histnode = hn;
return g;
}
static GoSpec*
copygospec(GoSpec* g)
{
GoSpec* ans;
ans = (GoSpec*)emalloc(sizeof(GoSpec));
ans->kind = g->kind;
ans->url = copyurl(g->url);
ans->meth = g->meth;
ans->body = Strdup(g->body);
ans->auth = g->auth;
ans->histnode = g->histnode;
return ans;
}
static int
gospecequal(GoSpec* a, GoSpec* b)
{
if(a->url == nil || b->url == nil)
return 0;
return urlequal(a->url, b->url) && a->meth == b->meth && !Strcmp(a->body, b->body);
}
static DocConfig*
newdocconfig(Rune* fname, Rune* title, int initconfig, GoSpec* g)
{
DocConfig* d;
d = (DocConfig*)emalloc(sizeof(DocConfig));
d->framename = fname;
d->title = title;
d->initconfig = initconfig;
d->gospec = g;
return d;
}
static DocConfig*
copydocconfig(DocConfig* d)
{
DocConfig* ans;
ans = (DocConfig*)emalloc(sizeof(DocConfig));
ans->framename = d->framename;
ans->title = d->title;
ans->initconfig = d->initconfig;
ans->gospec = d->gospec;
return ans;
}
static int
docconfigequal(DocConfig* a, DocConfig* b)
{
return !Strcmp(a->framename, b->framename) && gospecequal(a->gospec, b->gospec);
}
static int
docconfigequalarray(DocConfig** a1, int n1, DocConfig** a2, int n2)
{
int i;
if(n1 != n2)
return 0;
for(i = 0; i < n1; i++) {
if(a1[i] == nil || a2[i] == nil)
continue;
if(!docconfigequal((a1[i]), a2[i]))
return 0;
}
return 1;
}
static HistNode_list*
newhistnodelist(HistNode* hn, HistNode_list* l)
{
HistNode_list* ans;
ans = (HistNode_list*)emalloc(sizeof(HistNode_list));
ans->histnode = hn;
ans->next = l;
return ans;
}
// Put b in a->succs (if atob is true) or a->preds (if atob is false)
// at front of list.
// If it is already in the list, move it to the front.
static void
histaddedge(HistNode* a, HistNode* b, int atob)
{
HistNode_list* oldl;
int there;
HistNode_list* l;
HistNode_list* newl;
if(atob)
oldl = a->succs;
else
oldl = a->preds;
there = 0;
for(l = oldl; l != nil; l = l->next)
if(l->histnode == b) {
there = 1;
break;
}
if(there)
newl = newhistnodelist(b, remhnode(oldl, b));
else
newl = newhistnodelist(b, oldl);
if(atob)
a->succs = newl;
else
a->preds = newl;
}
// Return copy of l with hn removed (known that hn
// occurs at most once).
static HistNode_list*
remhnode(HistNode_list* l, HistNode* hn)
{
HistNode* hdl;
HistNode_list* ans;
if(l == nil)
return nil;
hdl = l->histnode;
if(hdl == hn) {
ans = l->next;
return ans;
}
return newhistnodelist(hdl, remhnode(l->next, hn));
}
static HistNode*
newhistnode(DocConfig* top, DocConfig** kids, int nkids,
HistNode_list* preds, HistNode_list* succs)
{
HistNode* h;
h = (HistNode*)emalloc(sizeof(HistNode));
h->topconfig = top;
h->kidconfigs = kids;
h->nkids = nkids;
h->preds = preds;
h->succs = succs;
return h;
}
// Copy of a, with new kidconfigs array (so that it can be changed independent
// of a), and clear the preds and succs.
static HistNode*
histnodecopy(HistNode* a)
{
int n;
DocConfig** kc;
int i;
n = a->nkids;
kc = nil;
if(n > 0) {
kc = (DocConfig**)emalloc(n * sizeof(DocConfig*));
for(i = 0; i < n; i++)
kc[i] = copydocconfig(a->kidconfigs[i]);
}
return newhistnode(a->topconfig, kc, n, nil, nil);
}
// This is called just before layout of f with result of getting g.
// (we don't yet know doctitle and whether this is a frameset).
// If navkind is not GoHistnode, update the history graph.
// In any case reorder the history array to put latest last in array.
static void
histadd(Frame* f, GoSpec* g, int navkind)
{
HistNode* oldcur;
DocConfig* dc;
HistNode* hnode;
int hnodepos;
int i;
DocConfig* kc;
int kidpos;
int k;
if(history.hlen <= history.n) {
history.hlen += 20;
history.h = (HistNode**)erealloc(history.h, history.hlen * sizeof(HistNode*));
}
if(history.n > 0)
oldcur = history.h[history.n - 1];
else
oldcur = nil;
dc = newdocconfig(Strdup(f->name), Strdup(g->url->url), navkind != GoHistnode, copygospec(g));
hnode = newhistnode(dc, nil, 0, nil, nil);
if(f == top)
g->target = FTtop;
else if(oldcur != nil) {
kidpos = -1;
for(i = 0; i < oldcur->nkids; i++) {
kc = oldcur->kidconfigs[i];
if(kc != nil && !Strcmp(kc->framename, f->name)) {
kidpos = i;
break;
}
}
if(kidpos == -1) {
if(dbg)
trace("history botch\n");
}
else {
hnode = histnodecopy(oldcur);
hnode->kidconfigs[kidpos] = dc;
}
}
// see if equivalent node to hnode is already in history
hnodepos = -1;
for(i = 0; i < history.n; i++) {
if(docconfigequal(hnode->topconfig, history.h[i]->topconfig)) {
if((hnode->kidconfigs == nil && history.h[i]->topconfig->initconfig)
|| docconfigequalarray(hnode->kidconfigs, hnode->nkids,
history.h[i]->kidconfigs, history.h[i]->nkids)) {
hnodepos = i;
hnode = history.h[i];
break;
}
}
}
if(hnodepos == -1) {
hnodepos = history.n;
history.h[history.n++] = hnode;
}
if(oldcur != nil && hnode != oldcur && navkind != GoHistnode) {
histaddedge(oldcur, hnode, 1);
histaddedge(hnode, oldcur, 0);
}
if(hnodepos != history.n - 1) {
for(k = hnodepos; k < history.n - 1; k++)
history.h[k] = history.h[k + 1];
history.h[history.n - 1] = hnode;
}
if(hnode->preds != nil)
enable(ctllay.controls[CLCbackbut]);
else
disable(ctllay.controls[CLCbackbut]);
if(hnode->succs != nil)
enable(ctllay.controls[CLCfwdbut]);
else
disable(ctllay.controls[CLCfwdbut]);
}
// This is called just after layout of f.
// Now we can put in correct doctitle, and make kids array if necessary.
static void
histupdate(Frame* f)
{
HistNode* hnode;
Framelist* kl;
Frame* kf;
DocConfig** kc;
DocConfig* dc;
int i;
hnode = history.h[history.n - 1];
if(f == top) {
hnode->topconfig->title = Strdup(f->doc->doctitle);
if(f->kids != nil && hnode->kidconfigs == nil) {
kc = (DocConfig**)emalloc(listlen((List*)f->kids) * sizeof(DocConfig*));
i = 0;
for(kl = f->kids; kl != nil; kl = kl->next) {
kf = kl->val;
if(kf->src != nil)
kc[i] = newdocconfig(Strdup(kf->name), Strdup(kf->src->url), 1,
newget(copyurl(kf->src), FTself));
i++;
}
hnode->kidconfigs = kc;
}
}
else {
for(i = 0; i < hnode->nkids; i++) {
dc = hnode->kidconfigs[i];
if(dc != nil && !Strcmp(dc->framename, f->name)) {
hnode->kidconfigs[i]->title = Strdup(f->doc->doctitle);
return;
}
}
if(dbg)
trace("history update botch\n");
}
}
// Find the gokind node (-1==Back, 0==Same, +1==Forward)
static HistNode*
histfind(int gokind)
{
HistNode* cur;
if(history.n > 0) {
cur = history.h[history.n - 1];
switch(gokind) {
case 1:
if(cur->succs != nil)
return cur->succs->histnode;
break;
case -1:
if(cur->preds != nil)
return cur->preds->histnode;
break;
case 0:
return cur;
break;
}
}
return nil;
}
// for debugging
static void
histprint(void)
{
int i;
int j;
HistNode* hn;
DocConfig* dc;
trace("History\n");
for(i = 0; i < history.n; i++) {
hn = history.h[i];
trace("Node %d:\n", i);
dc = hn->topconfig;
trace("\tframe=%S, target=%S, url=%U\n",
dc->framename, targetname(dc->gospec->target), dc->gospec->url);
if(hn->kidconfigs != nil) {
for(j = 0; j < hn->nkids; j++) {
dc = hn->kidconfigs[j];
if(dc != nil)
trace("\t\t%d: frame=%S, target=%S, url=%U\n",
j, dc->framename, targetname(dc->gospec->target), dc->gospec->url);
}
}
if(hn->preds != nil)
printhnodeindices("Preds", hn->preds);
if(hn->succs != nil)
printhnodeindices("Succs", hn->succs);
}
trace("\n");
}
static void
printhnodeindices(char* label, HistNode_list* l)
{
HistNode* hn;
int i;
trace("\t%s:", label);
for(; l != nil; l = l->next) {
hn = l->histnode;
for(i = 0; i < history.n; i++) {
if(hn == history.h[i]) {
trace(" %d", i);
break;
}
}
if(i == history.n)
trace(" ?");
}
trace("\n");
}
// Create HTML representation of history in "history.html" in user's config directory.
// This is called from go thread group.
static void
dumphistory(void)
{
// TODO
/*
Rune* fname;
FD* fd;
Rune* line;
uchar* buf;
uchar* aline;
int bufpos;
int i;
int j;
int n;
int nl;
HistNode* hn;
DocConfig* dc;
char buf[ATOMICIO];
snprint(buf, sizeof(buf),
fname = Strdup2(config.userdir, L"/history.html", 0);
fd = create(fname, OWRITE, 384);
if(fd == nil) {
if(warn)
trace("can't create history file\n");
return ;
}
line = L"<HEAD> <TITLE>History</TITLE></HEAD>\n<BODY>\n";
nl = Strlen(line);
buf = (uchar*)emalloc(ATOMICIO * sizeof(uchar), 0);
n = 0;
aline = fromStr(line, nl, UTF_8, 0);
memmove(buf, aline, nl);
bufpos = nl;
for(i = history->n - 1; i >= 0; i--) {
hn = history->h[i];
dc = hn->topconfig;
line = Strdup2(Strdup2(Strdup2(Strdup2(L"<A HREF=", tostring(dc->gospec->url)), L" TARGET=\"_top\">"), dc->title), L"</A><BR>\n");
if(hn->kidconfigs != nil) {
Stradd(line, L"<UL>", FIXME);
for(j = 0; j < arraylen(hn->kidconfigs); j++) {
dc = hn->kidconfigs[j];
if(dc != nil) {
Stradd(line, Strdup2(Strdup2(Strdup2(Strdup2(Strdup2(Strdup2(L"<LI><A HREF=", tostring(dc->gospec->url)), L" TARGET=\""), dc->framename), L"\">"), dc->title), L"</A>\n"), FIXME);
}
}
Stradd(line, L"</UL>", FIXME);
}
aline = fromStr(line, arraylen(line), UTF_8);
if(bufpos + arraylen(aline) > ATOMICIO) {
write(fd, buf, bufpos);
bufpos = 0;
}
buf[bufpos:...] = aline;
bufpos += arraylen(aline);
}
if(bufpos > 0)
write(fd, buf, bufpos);
*/
}
static void
freectls(Control** a, int n)
{
// TODO: free the memory
USED(a);
USED(n);
}
static AuthInfo*
getauth(Rune* chal)
{
// TODO
USED(chal);
return nil;
/*
Rune* realm;
AuthInfop_list* al;
int code;
Rune* ans;
AuthInfo* a;
if(Strcmp(tolower(chal[0:12]), L"basic realm=")) {
if(dbg || warn)
trace("unrecognized authorization challenge: %s\n", chal);
return makeRunepRunep_pair(L"", L"");
}
realm = chal[12:];
if(realm[0] == 34)
realm = realm[1:arraylen(realm) - 1];
for(al = auths; al != nil; al = al->tl) {
a = al->hd;
if(!Strcmp(realm, a->realm))
return makeRunepRunep_pair(realm, a->credentials);
}
dopopup(PopupAuth, realm);
tmp_24 = (recv(popupans, &tmp_25);
code = tmp_24.t0;
ans = tmp_24.t1;
if(code == -1)
trace("couldn't create popup window\n");
else if(code == 1)
ans = tobase64(ans);
return makeRunepRunep_pair(realm, ans);
*/
}
static Rune*
tobase64(Rune* a)
{
int n;
int nout;
Rune* out;
int j;
int i;
int nmod3;
int x;
n = Strlen(a);
if(n == 0)
return nil;
j = 0;
i = 0;
nout = n*4;
out = newstr(nout);
while(i < n) {
x = a[i++] << 16;
if(i < n)
x |= (a[i++]&255) << 8;
if(i < n)
x |= (a[i++]&255);
out[j++] = c64(x >> 18);
out[j++] = c64(x >> 12);
out[j++] = c64(x >> 6);
out[j++] = c64(x);
}
out[j] = 0;
nmod3 = n%3;
if(nmod3 != 0) {
out[j - 1] = 61;
if(nmod3 == 1)
out[j - 2] = 61;
}
return out;
}
static int
c64(int c)
{
static Rune* v = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
return v[c&63];
}
static void
dosaveas(ByteSource* bsmain)
{
// TODO
/*
int code;
Rune* ans;
ByteSource* bs;
int n;
int i;
Rune* err;
int flen;
FD* fd;
dopopup(PopupSaveAs, L"");
tmp_26 = (recv(popupans, &tmp_27);
code = tmp_26.t0;
ans = tmp_26.t1;
if(code == -1)
trace("couldn't create popup window\n");
else if(code == 1 && ans != nil) {
if(ans[0] != 47)
ans = Strdup2(Strdup2(config.userdir, L"/"), ans);
fd = create(ans, OWRITE, 420);
if(fd == nil) {
dopopup(PopupAlert, Strdup2(L"Couldn't create ", ans));
tmp_28 =(recv(popupans, &tmp_29);
}
else {
showstatus(Strdup2(L"Saving ", tostring(bsmain->hdr->actual)), 1);
err = L"";
flen = bsmain->hdr->length;
while(bsmain->edata < flen) {
bs = waitreq();
if(bs->refgo == 0)
continue;
assert(bs == bsmain);
if(bs->err != nil) {
err = bs->err;
break;
}
}
if(err == nil) {
i = 0;
while(i < flen) {
n = write(fd, bsmain->data[slice i, flen], flen - i);
if(n <= 0)
break;
i += n;
}
if(i != flen)
err = L"whole file not written";
}
if(err != nil)
dopopup(PopupAlert, err);
else
dopopup(PopupAlert, Strdup2(L"Created ", ans));
tmp_30 = (recv(popupans, &tmp_31);
}
}
*/
freebs(bsmain);
}
static void
handleprogress(EV ev)
{
int i;
Cprogbox* pb;
int bsid;
int state;
int pcnt;
Rune* s;
bsid = ev.u.progress.bsid;
state = ev.u.progress.state;
pcnt = ev.u.progress.pcnt;
s = ev.u.progress.s;
if(bsid == -1) {
for(i = 0; i < proglay.nused; i++) {
pb = (Cprogbox*)proglay.box[i];
pb->state = Punused;
pb->pcnt = 0;
pb->bsid = -1;
pb->src = nil;
pb->err = nil;
}
proglay.nused = 0;
redrawprog(0);
}
else {
if(state == Pstart) {
if(proglay.nused < proglay.boxlen) {
i = proglay.nused;
proglay.nused++;
}
else
i = 0;
pb = (Cprogbox*)proglay.box[i];
pb->state = state;
pb->bsid = bsid;
pb->src = s;
}
else {
for(i = 0; i < proglay.nused; i++) {
pb = (Cprogbox*)proglay.box[i];
if(pb->bsid == bsid)
break;
}
if(i == proglay.nused)
return;
pb = (Cprogbox*)proglay.box[i];
if(pb->state != Perr) {
pb->state = state;
pb->pcnt = pcnt;
pb->err = s;
}
}
drawctl(proglay.box[i], 1);
}
}
static void
showstatus(Rune* msg)
{
Rune* ostatus;
Point p;
Point sp;
ostatus = ctllay.status;
p = ctllay.statuspos;
pushclipr(ctlframe->cr);
if(ostatus != nil && Strcmp(ostatus, msg)) {
sp = measurestring(ostatus);
drawfill(screen, Rpt(p, addpt(p, sp)), Grey);
}
ctllay.status = Strdup(msg);
drawstring(screen, p, msg);
popclipr();
flushimage(display, 1);
}
// Run as separate proc to put up alert popup
static void
alert(void* arg)
{
Rune* msg;
Channel* sync;
PopupAns ans;
void** args;
meminit(GRalert);
args = (void**)arg;
msg = (Rune*)args[0];
sync = (Channel*)args[1];
dopopup(PopupAlert, msg);
if(recv(popupans, &ans) < 0)
finish();
if(sendul(sync, 1) < 0)
finish();
}
static void
showurl(Rune* u)
{
entryset(ctllay.controls[CLCentry], Strdup(u));
}
void
fatalerror(char* msg)
{
trace("Fatal error: %s\n", msg);
finish();
}
void
trace(char* fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vfprint(1, fmt, arg);
va_end(arg);
}
void
finish(void)
{
if(dbg)
trace("finish called\n");
threadexitsall("");
}
|