Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#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;
Header h;
Super super;
VtSession *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";
}
#pragma varargck argpos stringnode 1

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  */
        VtPointerType0,         /* BtData | 1  */
        VtPointerType1,         /* BtData | 2  */
        VtPointerType2,         /* BtData | 3  */
        VtPointerType3,         /* BtData | 4  */
        VtPointerType4,         /* BtData | 5  */
        VtPointerType5,         /* BtData | 6  */
        VtPointerType6,         /* BtData | 7  */
        VtDirType,              /* BtDir | 0  */
        VtPointerType0,         /* BtDir | 1  */
        VtPointerType1,         /* BtDir | 2  */
        VtPointerType2,         /* BtDir | 3  */
        VtPointerType3,         /* BtDir | 4  */
        VtPointerType4,         /* BtDir | 5  */
        VtPointerType5,         /* BtDir | 6  */
        VtPointerType6,         /* 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))
                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",
                vac.version, 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];

        vtAttach();

        fmtinstall('V', scoreFmt);
        fmtinstall('R', vtErrFmt);

        z = vtDial(nil, 1);
        if(z == nil)
                fprint(2, "warning: cannot dial venti: %R\n");
        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: vtree /dev/sdC0/fossil\n");
        exits("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
main(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:
                                        exits(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;
                        }
                }
        }
}