shithub: fossil

ref: 333ae58f37c2c8f79f7d7078283a30e42c4d7a27
dir: /view.c/

View raw version
#include "stdinc.h"
#include "dat.h"
#include "fns.h"
#include <draw.h>
#include <event.h>

/* --- tree.h */
typedef struct Tree Tree;
typedef struct Tnode Tnode;

struct Tree
{
	Tnode *root;
	Point offset;
	Image *clipr;
};

struct Tnode
{
	Point offset;

	char *str;
//	char *(*strfn)(Tnode*);
//	uint (*draw)(Tnode*, Image*, Image*, Point);
	void (*expand)(Tnode*);
	void (*collapse)(Tnode*);

	uint expanded;
	Tnode **kid;
	int nkid;
	void *aux;
};

typedef struct Atree Atree;
struct Atree
{
	int resizefd;
	Tnode *root;
};

Atree *atreeinit(char*);

/* --- visfossil.c */
Tnode *initxheader(void);
Tnode *initxcache(char *name);
Tnode *initxsuper(void);
Tnode *initxlocalroot(char *name, u32int addr);
Tnode *initxentry(Entry);
Tnode *initxsource(Entry, int);
Tnode *initxentryblock(Block*, Entry*);
Tnode *initxdatablock(Block*, uint);
Tnode *initxroot(char *name, uchar[VtScoreSize]);

int fd;
int mainstacksize = STACK;
Header h;
Super super;
VtConn *z;
VtRoot vac;
int showinactive;

/*
 * dumbed down versions of fossil routines
 */
char*
bsStr(int state)
{
	static char s[100];

	if(state == BsFree)
		return "Free";
	if(state == BsBad)
		return "Bad";

	sprint(s, "%x", state);
	if(!(state&BsAlloc))
		strcat(s, ",Free");	/* should not happen */
	if(state&BsVenti)
		strcat(s, ",Venti");
	if(state&BsClosed)
		strcat(s, ",Closed");
	return s;
}

char *bttab[] = {
	"BtData",
	"BtData+1",
	"BtData+2",
	"BtData+3",
	"BtData+4",
	"BtData+5",
	"BtData+6",
	"BtData+7",
	"BtDir",
	"BtDir+1",
	"BtDir+2",
	"BtDir+3",
	"BtDir+4",
	"BtDir+5",
	"BtDir+6",
	"BtDir+7",
};

char*
btStr(int type)
{
	if(type < nelem(bttab))
		return bttab[type];
	return "unknown";
}

Block*
allocBlock(void)
{
	Block *b;

	b = mallocz(sizeof(Block)+h.blockSize, 1);
	b->data = (void*)&b[1];
	return b;
}

void
blockPut(Block *b)
{
	free(b);
}

static u32int
partStart(int part)
{
	switch(part){
	default:
		assert(0);
	case PartSuper:
		return h.super;
	case PartLabel:
		return h.label;
	case PartData:
		return h.data;
	}
}


static u32int
partEnd(int part)
{
	switch(part){
	default:
		assert(0);
	case PartSuper:
		return h.super+1;
	case PartLabel:
		return h.data;
	case PartData:
		return h.end;
	}
}

Block*
readBlock(int part, u32int addr)
{
	u32int start, end;
	u64int offset;
	int n, nn;
	Block *b;
	uchar *buf;

	start = partStart(part);
	end = partEnd(part);
	if(addr >= end-start){
		werrstr("bad addr 0x%.8ux; wanted 0x%.8ux - 0x%.8ux", addr, start, end);
		return nil;
	}

	b = allocBlock();
	b->addr = addr;
	buf = b->data;
	offset = ((u64int)(addr+start))*h.blockSize;
	n = h.blockSize;
	while(n > 0){
		nn = pread(fd, buf, n, offset);
		if(nn < 0){
			blockPut(b);
			return nil;
		}
		if(nn == 0){
			werrstr("short read");
			blockPut(b);
			return nil;
		}
		n -= nn;
		offset += nn;
		buf += nn;
	}
	return b;
}

int vtType[BtMax] = {
	VtDataType,		/* BtData | 0  */
	VtDataType+1,		/* BtData | 1  */
	VtDataType+2,		/* BtData | 2  */
	VtDataType+3,		/* BtData | 3  */
	VtDataType+4,		/* BtData | 4  */
	VtDataType+5,		/* BtData | 5  */
	VtDataType+6,		/* BtData | 6  */
	VtDataType+7,		/* BtData | 7  */
	VtDirType,		/* BtDir | 0  */
	VtDirType+1,		/* BtDir | 1  */
	VtDirType+2,		/* BtDir | 2  */
	VtDirType+3,		/* BtDir | 3  */
	VtDirType+4,		/* BtDir | 4  */
	VtDirType+5,		/* BtDir | 5  */
	VtDirType+6,		/* BtDir | 6  */
	VtDirType+7,		/* BtDir | 7  */
};

Block*
ventiBlock(uchar score[VtScoreSize], uint type)
{
	int n;
	Block *b;

	b = allocBlock();
	memmove(b->score, score, VtScoreSize);
	b->addr = NilBlock;

	n = vtread(z, b->score, vtType[type], b->data, h.blockSize);
	if(n < 0){
		fprint(2, "vtread returns %d: %r\n", n);
		blockPut(b);
		return nil;
	}
	vtzeroextend(vtType[type], b->data, n, h.blockSize);
	b->l.type = type;
	b->l.state = 0;
	b->l.tag = 0;
	b->l.epoch = 0;
	return b;
}

Block*
dataBlock(uchar score[VtScoreSize], uint type, uint tag)
{
	Block *b, *bl;
	int lpb;
	Label l;
	u32int addr;

	addr = globalToLocal(score);
	if(addr == NilBlock)
		return ventiBlock(score, type);

	lpb = h.blockSize/LabelSize;
	bl = readBlock(PartLabel, addr/lpb);
	if(bl == nil)
		return nil;
	if(!labelUnpack(&l, bl->data, addr%lpb)){
		werrstr("%r");
		blockPut(bl);
		return nil;
	}
	blockPut(bl);
	if(l.type != type){
		werrstr("type mismatch; got %d (%s) wanted %d (%s)",
			l.type, btStr(l.type), type, btStr(type));
		return nil;
	}
	if(tag && l.tag != tag){
		werrstr("tag mismatch; got 0x%.8ux wanted 0x%.8ux",
			l.tag, tag);
		return nil;
	}
	b = readBlock(PartData, addr);
	if(b == nil)
		return nil;
	b->l = l;
	return b;
}

Entry*
copyEntry(Entry e)
{
	Entry *p;

	p = mallocz(sizeof *p, 1);
	*p = e;
	return p;
}

MetaBlock*
copyMetaBlock(MetaBlock mb)
{
	MetaBlock *p;

	p = mallocz(sizeof mb, 1);
	*p = mb;
	return p;
}

/*
 * visualizer 
 */

#pragma	varargck	argpos	stringnode	1

Tnode*
stringnode(char *fmt, ...)
{
	va_list arg;
	Tnode *t;

	t = mallocz(sizeof(Tnode), 1);
	va_start(arg, fmt);
	t->str = vsmprint(fmt, arg);
	va_end(arg);
	t->nkid = -1;
	return t;
}

void
xcacheexpand(Tnode *t)
{
	if(t->nkid >= 0)
		return;

	t->nkid = 1;
	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
	t->kid[0] = initxheader();
}

Tnode*
initxcache(char *name)
{
	Tnode *t;

	if((fd = open(name, OREAD)) < 0)
		sysfatal("cannot open %s: %r", name);

	t = stringnode("%s", name);
	t->expand = xcacheexpand;
	return t;
}

void
xheaderexpand(Tnode *t)
{
	if(t->nkid >= 0)
		return;

	t->nkid = 1;
	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
	t->kid[0] = initxsuper();
	//t->kid[1] = initxlabel(h.label);
	//t->kid[2] = initxdata(h.data);
}

Tnode*
initxheader(void)
{
	u8int buf[HeaderSize];
	Tnode *t;

	if(pread(fd, buf, HeaderSize, HeaderOffset) < HeaderSize)
		return stringnode("error reading header: %r");
	if(!headerUnpack(&h, buf))
		return stringnode("error unpacking header: %r");

	t = stringnode("header "
		"version=%#ux (%d) "
		"blockSize=%#ux (%d) "
		"super=%#lux (%ld) "
		"label=%#lux (%ld) "
		"data=%#lux (%ld) "
		"end=%#lux (%ld)",
		h.version, h.version, h.blockSize, h.blockSize,
		h.super, h.super,
		h.label, h.label, h.data, h.data, h.end, h.end);
	t->expand = xheaderexpand;
	return t;
}

void
xsuperexpand(Tnode *t)
{
	if(t->nkid >= 0)
		return;

	t->nkid = 1;
	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
	t->kid[0] = initxlocalroot("active", super.active);
//	t->kid[1] = initxlocalroot("next", super.next);
//	t->kid[2] = initxlocalroot("current", super.current);
}

Tnode*
initxsuper(void)
{
	Block *b;
	Tnode *t;

	b = readBlock(PartSuper, 0);
	if(b == nil)
		return stringnode("reading super: %r");
	if(!superUnpack(&super, b->data)){
		blockPut(b);
		return stringnode("unpacking super: %r");
	}
	blockPut(b);
	t = stringnode("super "
		"version=%#ux "
		"epoch=[%#ux,%#ux) "
		"qid=%#llux "
		"active=%#x "
		"next=%#x "
		"current=%#x "
		"last=%V "
		"name=%s",
		super.version, super.epochLow, super.epochHigh,
		super.qid, super.active, super.next, super.current,
		super.last, super.name);
	t->expand = xsuperexpand;
	return t;
}

void
xvacrootexpand(Tnode *t)
{
	if(t->nkid >= 0)
		return;

	t->nkid = 1;
	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
	t->kid[0] = initxroot("root", vac.score);
}

Tnode*
initxvacroot(uchar score[VtScoreSize])
{
	Tnode *t;
	uchar buf[VtRootSize];
	int n;

	if((n = vtread(z, score, VtRootType, buf, VtRootSize)) < 0)
		return stringnode("reading root %V: %r", score);

	if(vtrootunpack(&vac, buf) < 0)
		return stringnode("unpack %d-byte root: %r", n);

	h.blockSize = vac.blocksize;
	t = stringnode("vac version=%#ux name=%s type=%s blocksize=%ud score=%V prev=%V",
		VtRootVersion, vac.name, vac.type, vac.blocksize, vac.score, vac.prev);
	t->expand = xvacrootexpand;
	return t;
}

Tnode*
initxlabel(Label l)
{
	return stringnode("label type=%s state=%s epoch=%#ux tag=%#ux",
		btStr(l.type), bsStr(l.state), l.epoch, l.tag);
}

typedef struct Xblock Xblock;
struct Xblock
{
	Tnode;
	Block *b;
	int (*gen)(void*, Block*, int, Tnode**);
	void *arg;
	int printlabel;
};

void
xblockexpand(Tnode *tt)
{
	int i, j;
	enum { Q = 32 };
	Xblock *t = (Xblock*)tt;
	Tnode *nn;

	if(t->nkid >= 0)
		return;

	j = 0;
	if(t->printlabel){
		t->kid = mallocz(Q*sizeof(t->kid[0]), 1);
		t->kid[0] = initxlabel(t->b->l);
		j = 1;
	}

	for(i=0;; i++){
		switch((*t->gen)(t->arg, t->b, i, &nn)){
		case -1:
			t->nkid = j;
			return;
		case 0:
			break;
		case 1:
			if(j%Q == 0)
				t->kid = realloc(t->kid, (j+Q)*sizeof(t->kid[0]));
			t->kid[j++] = nn;
			break;
		}
	}
}

int
nilgen(void*, Block*, int, Tnode**)
{
	return -1;
}

Tnode*
initxblock(Block *b, char *s, int (*gen)(void*, Block*, int, Tnode**), void *arg)
{
	Xblock *t;

	if(gen == nil)
		gen = nilgen;
	t = mallocz(sizeof(Xblock), 1);
	t->b = b;
	t->gen = gen;
	t->arg = arg;
	if(b->addr == NilBlock)
		t->str = smprint("Block %V: %s", b->score, s);
	else
		t->str = smprint("Block %#ux: %s", b->addr, s);
	t->printlabel = 1;
	t->nkid = -1;
	t->expand = xblockexpand;
	return t;
}

int
xentrygen(void *v, Block *b, int o, Tnode **tp)
{
	Entry e;
	Entry *ed;

	ed = v;
	if(o >= ed->dsize/VtEntrySize)
		return -1;

	entryUnpack(&e, b->data, o);
	if(!showinactive && !(e.flags & VtEntryActive))
		return 0;
	*tp = initxentry(e);
	return 1;
}

Tnode*
initxentryblock(Block *b, Entry *ed)
{
	return initxblock(b, "entry", xentrygen, ed);
}

typedef struct Xentry Xentry;
struct Xentry 
{
	Tnode;
	Entry e;
};

void
xentryexpand(Tnode *tt)
{
	Xentry *t = (Xentry*)tt;

	if(t->nkid >= 0)
		return;

	t->nkid = 1;
	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
	t->kid[0] = initxsource(t->e, 1);
}

Tnode*
initxentry(Entry e)
{
	Xentry *t;

	t = mallocz(sizeof *t, 1);
	t->nkid = -1;
	t->str = smprint("Entry gen=%#ux psize=%d dsize=%d depth=%d flags=%#ux size=%lld score=%V",
		e.gen, e.psize, e.dsize, e.depth, e.flags, e.size, e.score);
	if(e.flags & VtEntryLocal)
		t->str = smprint("%s archive=%d snap=%d tag=%#ux", t->str, e.archive, e.snap, e.tag);
	t->expand = xentryexpand;
	t->e = e;
	return t;	
}

int
ptrgen(void *v, Block *b, int o, Tnode **tp)
{
	Entry *ed;
	Entry e;

	ed = v;
	if(o >= ed->psize/VtScoreSize)
		return -1;

	e = *ed;
	e.depth--;
	memmove(e.score, b->data+o*VtScoreSize, VtScoreSize);
	if(memcmp(e.score, vtzeroscore, VtScoreSize) == 0)
		return 0;
	*tp = initxsource(e, 0);
	return 1;
}

static int
etype(int flags, int depth)
{
	uint t;

	if(flags&_VtEntryDir)
		t = BtDir;
	else
		t = BtData;
	return t+depth;
}

Tnode*
initxsource(Entry e, int dowrap)
{
	Block *b;
	Tnode *t, *tt;

	b = dataBlock(e.score, etype(e.flags, e.depth), e.tag);
	if(b == nil)
		return stringnode("dataBlock: %r");

	if((e.flags & VtEntryActive) == 0)
		return stringnode("inactive Entry");

	if(e.depth == 0){
		if(e.flags & _VtEntryDir)
			tt = initxentryblock(b, copyEntry(e));
		else
			tt = initxdatablock(b, e.dsize);
	}else{
		tt = initxblock(b, smprint("%s+%d pointer", (e.flags & _VtEntryDir) ? "BtDir" : "BtData", e.depth),
			ptrgen, copyEntry(e));
	}

	/*
	 * wrap the contents of the Source in a Source node,
	 * just so it's closer to what you see in the code.
	 */
	if(dowrap){
		t = stringnode("Source");
		t->nkid = 1;
		t->kid = mallocz(sizeof(Tnode*)*1, 1);
		t->kid[0] = tt;
		tt = t;
	}
	return tt;
}

int
xlocalrootgen(void*, Block *b, int o, Tnode **tp)
{
	Entry e;

	if(o >= 1)
		return -1;
	entryUnpack(&e, b->data, o);
	*tp = initxentry(e);
	return 1;
}

Tnode*
initxlocalroot(char *name, u32int addr)
{
	uchar score[VtScoreSize];
	Block *b;

	localToGlobal(addr, score);
	b = dataBlock(score, BtDir, RootTag);
	if(b == nil)
		return stringnode("read data block %#ux: %r", addr);
	return initxblock(b, smprint("'%s' fs root", name), xlocalrootgen, nil);
}

int
xvacrootgen(void*, Block *b, int o, Tnode **tp)
{
	Entry e;

	if(o >= 3)
		return -1;
	entryUnpack(&e, b->data, o);
	*tp = initxentry(e);
	return 1;
}

Tnode*
initxroot(char *name, uchar score[VtScoreSize])
{
	Block *b;

	b = dataBlock(score, BtDir, RootTag);
	if(b == nil)
		return stringnode("read data block %V: %r", score);
	return initxblock(b, smprint("'%s' fs root", name), xvacrootgen, nil);
}
Tnode*
initxdirentry(MetaEntry *me)
{
	DirEntry dir;
	Tnode *t;

	if(!deUnpack(&dir, me))
		return stringnode("deUnpack: %r");

	t = stringnode("dirEntry elem=%s size=%llud data=%#lux/%#lux meta=%#lux/%#lux", dir.elem, dir.size, dir.entry, dir.gen, dir.mentry, dir.mgen);
	t->nkid = 1;
	t->kid = mallocz(sizeof(t->kid[0])*1, 1);
	t->kid[0] = stringnode(
		"qid=%#llux\n"
		"uid=%s gid=%s mid=%s\n"
		"mtime=%lud mcount=%lud ctime=%lud atime=%lud\n"
		"mode=%luo\n"
		"plan9 %d p9path %#llux p9version %lud\n"
		"qidSpace %d offset %#llux max %#llux",
		dir.qid,
		dir.uid, dir.gid, dir.mid,
		dir.mtime, dir.mcount, dir.ctime, dir.atime,
		dir.mode,
		dir.plan9, dir.p9path, dir.p9version,
		dir.qidSpace, dir.qidOffset, dir.qidMax);
	return t;
}

int
metaentrygen(void *v, Block*, int o, Tnode **tp)
{
	Tnode *t;
	MetaBlock *mb;
	MetaEntry me;

	mb = v;
	if(o >= mb->nindex)
		return -1;
	meUnpack(&me, mb, o);

	t = stringnode("MetaEntry %d bytes", mb->size);
	t->kid = mallocz(sizeof(t->kid[0])*1, 1);
	t->kid[0] = initxdirentry(&me);
	t->nkid = 1;
	*tp = t;
	return 1;
}

int
metablockgen(void *v, Block *b, int o, Tnode **tp)
{
	Xblock *t;
	MetaBlock *mb;

	if(o >= 1)
		return -1;

	/* hack: reuse initxblock as a generic iterator */
	mb = v;
	t = (Xblock*)initxblock(b, "", metaentrygen, mb);
	t->str = smprint("MetaBlock %d/%d space used, %d add'l free %d/%d table used%s",
		mb->size, mb->maxsize, mb->free, mb->nindex, mb->maxindex,
		mb->botch ? " [BOTCH]" : "");
	t->printlabel = 0;
	*tp = t;
	return 1;
}

/*
 * attempt to guess at the type of data in the block.
 * it could just be data from a file, but we're hoping it's MetaBlocks.
 */
Tnode*
initxdatablock(Block *b, uint n)
{
	MetaBlock mb;

	if(n > h.blockSize)
		n = h.blockSize;

	if(mbUnpack(&mb, b->data, n))
		return initxblock(b, "metadata", metablockgen, copyMetaBlock(mb));

	return initxblock(b, "data", nil, nil);
}

int
parseScore(uchar *score, char *buf, int n)
{
	int i, c;

	memset(score, 0, VtScoreSize);

	if(n < VtScoreSize*2)
		return 0;
	for(i=0; i<VtScoreSize*2; i++){
		if(buf[i] >= '0' && buf[i] <= '9')
			c = buf[i] - '0';
		else if(buf[i] >= 'a' && buf[i] <= 'f')
			c = buf[i] - 'a' + 10;
		else if(buf[i] >= 'A' && buf[i] <= 'F')
			c = buf[i] - 'A' + 10;
		else{
			return 0;
		}

		if((i & 1) == 0)
			c <<= 4;
	
		score[i>>1] |= c;
	}
	return 1;
}

int
scoreFmt(Fmt *f)
{
	uchar *v;
	int i;
	u32int addr;

	v = va_arg(f->args, uchar*);
	if(v == nil){
		fmtprint(f, "*");
	}else if((addr = globalToLocal(v)) != NilBlock)
		fmtprint(f, "0x%.8ux", addr);
	else{
		for(i = 0; i < VtScoreSize; i++)
			fmtprint(f, "%2.2ux", v[i]);
	}

	return 0;
}

Atree*
atreeinit(char *arg)
{
	Atree *a;
	uchar score[VtScoreSize];

	fmtinstall('V', scoreFmt);

	z = vtdial(nil);
	if(z == nil)
		fprint(2, "warning: cannot dial venti: %r\n");
	else if(vtconnect(z) < 0){
		fprint(2, "warning: cannot connect to venti: %r\n");
		z = nil;
	}
	a = mallocz(sizeof(Atree), 1);
	if(strncmp(arg, "vac:", 4) == 0){
		if(!parseScore(score, arg+4, strlen(arg+4))){
			fprint(2, "cannot parse score\n");
			return nil;
		}
		a->root = initxvacroot(score);
	}else
		a->root = initxcache(arg);
	a->resizefd = -1;
	return a;
}

/* --- tree.c */
enum
{
	Nubwidth = 11,
	Nubheight = 11,
	Linewidth = Nubwidth*2+4,
};

uint
drawtext(char *s, Image *m, Image *clipr, Point o)
{
	char *t, *nt, *e;
	uint dy;

	if(s == nil)
		s = "???";

	dy = 0;
	for(t=s; t&&*t; t=nt){
		if(nt = strchr(t, '\n')){
			e = nt;
			nt++;
		}else
			e = t+strlen(t);

		_string(m, Pt(o.x, o.y+dy), display->black, ZP, display->defaultfont,
			t, nil, e-t, clipr->clipr, nil, ZP, SoverD);
		dy += display->defaultfont->height;
	}
	return dy;
}

void
drawnub(Image *m, Image *clipr, Point o, Tnode *t)
{
	clipr = nil;

	if(t->nkid == 0)
		return;
	if(t->nkid == -1 && t->expand == nil)
		return;

	o.y += (display->defaultfont->height-Nubheight)/2;
	draw(m, rectaddpt(Rect(0,0,1,Nubheight), o), display->black, clipr, ZP);
	draw(m, rectaddpt(Rect(0,0,Nubwidth,1), o), display->black, clipr, o);
	draw(m, rectaddpt(Rect(Nubwidth-1,0,Nubwidth,Nubheight), o), 
		display->black, clipr, addpt(o, Pt(Nubwidth-1, 0)));
	draw(m, rectaddpt(Rect(0, Nubheight-1, Nubwidth, Nubheight), o),
		display->black, clipr, addpt(o, Pt(0, Nubheight-1)));

	draw(m, rectaddpt(Rect(0, Nubheight/2, Nubwidth, Nubheight/2+1), o),
		display->black, clipr, addpt(o, Pt(0, Nubheight/2)));
	if(!t->expanded)
		draw(m, rectaddpt(Rect(Nubwidth/2, 0, Nubwidth/2+1, Nubheight), o),
			display->black, clipr, addpt(o, Pt(Nubwidth/2, 0)));

}

uint
drawnode(Tnode *t, Image *m, Image *clipr, Point o)
{
	int i;
	char *fs, *s;
	uint dy;
	Point oo;

	if(t == nil)
		return 0;

	t->offset = o;

	oo = Pt(o.x+Nubwidth+2, o.y);
//	if(t->draw)
//		dy = (*t->draw)(t, m, clipr, oo);
//	else{
		fs = nil;
		if(t->str)
			s = t->str;
	//	else if(t->strfn)
	//		fs = s = (*t->strfn)(t);
		else
			s = "???";
		dy = drawtext(s, m, clipr, oo);
		free(fs);
//	}

	if(t->expanded){
		if(t->nkid == -1 && t->expand)
			(*t->expand)(t);
		oo = Pt(o.x+Nubwidth+(Linewidth-Nubwidth)/2, o.y+dy);
		for(i=0; i<t->nkid; i++)
			oo.y += drawnode(t->kid[i], m, clipr, oo);
		dy = oo.y - o.y;
	}
	drawnub(m, clipr, o, t);
	return dy;
}

void
drawtree(Tree *t, Image *m, Rectangle r)
{
	Point p;

	draw(m, r, display->white, nil, ZP);

	replclipr(t->clipr, 1, r);
	p = addpt(t->offset, r.min);
	drawnode(t->root, m, t->clipr, p);
}

Tnode*
findnode(Tnode *t, Point p)
{
	int i;
	Tnode *tt;

	if(ptinrect(p, rectaddpt(Rect(0,0,Nubwidth, Nubheight), t->offset)))
		return t;
	if(!t->expanded)
		return nil;
	for(i=0; i<t->nkid; i++)
		if(tt = findnode(t->kid[i], p))
			return tt;
	return nil;
}

void
usage(void)
{
	fprint(2, "usage: fossil/view /dev/sdC0/fossil\n");
	threadexitsall("usage");
}

Tree t;

void
eresized(int new)
{
	Rectangle r;
	r = screen->r;
	if(new && getwindow(display, Refnone) < 0)
		fprint(2,"can't reattach to window");
	drawtree(&t, screen, screen->r);
}

enum
{
	Left = 1<<0,
	Middle = 1<<1,
	Right = 1<<2,

	MMenu = 2,
};

char *items[] = { "exit", 0 };
enum { IExit, };

Menu menu;

void
threadmain(int argc, char **argv)
{
	int n;
	char *dir;
	Event e;
	Point op, p;
	Tnode *tn;
	Mouse m;
	int Eready;
	Atree *fs;

	ARGBEGIN{
	case 'a':
		showinactive = 1;
		break;
	default:
		usage();
	}ARGEND

	switch(argc){
	default:
		usage();
	case 1:
		dir = argv[0];
		break;
	}

	fs = atreeinit(dir);
	initdraw(0, "/lib/font/bit/lucidasans/unicode.8.font", "tree");
	t.root = fs->root;
	t.offset = ZP;
	t.clipr = allocimage(display, Rect(0,0,1,1), GREY1, 1, DOpaque);

	eresized(0);
	flushimage(display, 1);

	einit(Emouse);

	menu.item = items;
	menu.gen = 0;
	menu.lasthit = 0;
	if(fs->resizefd > 0){
		Eready = 1<<3;
		estart(Eready, fs->resizefd, 1);
	}else
		Eready = 0;

	for(;;){
		switch(n=eread(Emouse|Eready, &e)){
		default:
			if(Eready && n==Eready)
				eresized(0);
			break;
		case Emouse:
			m = e.mouse;
			switch(m.buttons){
			case Left:
				op = t.offset;
				p = m.xy;
				do {
					t.offset = addpt(t.offset, subpt(m.xy, p));
					p = m.xy;
					eresized(0);
					m = emouse();
				}while(m.buttons == Left);
				if(m.buttons){
					t.offset = op;
					eresized(0);
				}
				break;
			case Middle:
				n = emenuhit(MMenu, &m, &menu);
				if(n == -1)
					break;
				switch(n){
				case IExit:
					threadexitsall(nil);
				}
				break;
			case Right:
				do
					m = emouse();
				while(m.buttons == Right);
				if(m.buttons)
					break;
				tn = findnode(t.root, m.xy);
				if(tn){
					tn->expanded = !tn->expanded;
					eresized(0);
				}
				break;
			}
		}
	}
}