| #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=%lud 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 t; |
| 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->t.nkid >= 0) |
| return; |
| |
| j = 0; |
| if(t->printlabel){ |
| t->t.kid = mallocz(Q*sizeof(t->t.kid[0]), 1); |
| t->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->t.nkid = j; |
| return; |
| case 0: |
| break; |
| case 1: |
| if(j%Q == 0) |
| t->t.kid = realloc(t->t.kid, (j+Q)*sizeof(t->t.kid[0])); |
| t->t.kid[j++] = nn; |
| break; |
| } |
| } |
| } |
| |
| int |
| nilgen(void *v, Block *b, int o, Tnode **tp) |
| { |
| return -1; |
| } |
| |
| Tnode* |
| initxblock(Block *b, char *s, int (*gen)(void *v, Block *b, int o, Tnode **tp), 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->t.str = smprint("Block %V: %s", b->score, s); |
| else |
| t->t.str = smprint("Block %#ux: %s", b->addr, s); |
| t->printlabel = 1; |
| t->t.nkid = -1; |
| t->t.expand = xblockexpand; |
| return (Tnode*)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 t; |
| Entry e; |
| }; |
| |
| void |
| xentryexpand(Tnode *tt) |
| { |
| Xentry *t = (Xentry*)tt; |
| |
| if(t->t.nkid >= 0) |
| return; |
| |
| t->t.nkid = 1; |
| t->t.kid = mallocz(sizeof(t->t.kid[0])*t->t.nkid, 1); |
| t->t.kid[0] = initxsource(t->e, 1); |
| } |
| |
| Tnode* |
| initxentry(Entry e) |
| { |
| Xentry *t; |
| |
| t = mallocz(sizeof *t, 1); |
| t->t.nkid = -1; |
| t->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->t.str = smprint("%s archive=%d snap=%d tag=%#ux", t->t.str, e.archive, e.snap, e.tag); |
| t->t.expand = xentryexpand; |
| t->e = e; |
| return (Tnode*)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 *v, 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 *v, 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 *b, 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->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 = (Tnode*)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) |
| { |
| 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); |
| #ifdef PLAN9PORT |
| initdraw(0, "/lib/font/bit/lucsans/unicode.8.font", "tree"); |
| #else |
| initdraw(0, "/lib/font/bit/lucidasans/unicode.8.font", "tree"); |
| #endif |
| 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; |
| } |
| } |
| } |
| } |