shithub: vdir

ref: 2bab9ca96b1aef62b5a83b2cda600517d40b5758
dir: /vdir.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <keyboard.h>
#include <plumb.h>
#include <bio.h>
#include "icons.h"

extern void alert(const char *title, const char *message);

enum
{
	Toolpadding = 4,
	Padding = 1,
	Scrollwidth = 14,
	Slowscroll = 10,
};

char *home;
char path[256];
Dir* dirs;
long ndirs;
Rectangle toolr;
Rectangle homer;
Rectangle upr;
Rectangle cdr;
Rectangle newdirr;
Rectangle newfiler;
Rectangle viewr;
Rectangle scrollr;
Rectangle scrposr;
Image *folder;
Image *file;
Image *ihome;
Image *icd;
Image *iup;
Image *inewfile;
Image *inewfolder;
Image *toolbg;
Image *toolfg;
Image *viewbg;
Image *viewfg;
Image *scrollbg;
Image *scrollfg;
int lineh;
int nlines;
int offset;

void
showerrstr(void)
{
	char errbuf[ERRMAX];

	errstr(errbuf, ERRMAX-1);
	alert("Error", errbuf);
}

void
readhome(void)
{
	Biobuf *bp;
	
	bp = Bopen("/env/home", OREAD);
	home = Brdstr(bp, 0, 0);
	Bterm(bp);
}

int
dircmp(Dir *a, Dir *b)
{
	if(a->qid.type==b->qid.type)
		return strcmp(a->name, b->name);
	if(a->qid.type&QTDIR)
		return -1;
	return 1;
}

void
loaddirs(void)
{
	int fd;

	if(dirs!=nil)
		free(dirs);
	fd = open(path, OREAD);
	if(fd<0)
		sysfatal("open: %r");
	ndirs = dirreadall(fd, &dirs);
	qsort(dirs, ndirs, sizeof *dirs, (int(*)(void*,void*))dircmp);
	offset = 0;
	close(fd);
}

void
up(void)
{
	int i, n;

	n = strlen(path);
	if(n==1)
		return;
	for(i = n-1; path[i]; i--){
		if(path[i]=='/'){
			path[i]=0;
			break;
		}
	}
	if(strlen(path)==0)
		sprint(path, "/");
	loaddirs();
}

void
cd(char *dir)
{
	char *sep;

	if(dir == nil)
		sprint(path, home);
	else if(dir[0] == '/')
		snprint(path, sizeof path, dir);
	else{
		sep = strlen(path)==1 ? "" : "/";
		snprint(path, sizeof path, "%s%s%s", path, sep, dir);
	}
	loaddirs();
}

void
mkdir(char *name)
{
	char *p;
	int fd;

	p = smprint("%s/%s", path, name);
	if(access(p, 0)>=0){
		alert("Error", "Directory already exists");
		goto cleanup;
	}
	fd = create(p, OREAD, DMDIR|0755);
	if(fd<0){
		showerrstr();
		goto cleanup;
	}
	close(fd);
	loaddirs();
cleanup:
	free(p);
}

void
touch(char *name)
{
	char *p;
	int fd;

	p = smprint("%s/%s", path, name);
	if(access(p, 0)>=0){
		alert("Error", "File already exists");
		goto cleanup;
	}
	fd = create(p, OREAD, 0644);
	if(fd<0){
		showerrstr();
		goto cleanup;
	}
	close(fd);
	loaddirs();
cleanup:
	free(p);
}

void
plumbfile(char *path, char *name)
{
	int fd;
	char *f;

	f = smprint("%s/%s", path, name);
	fd = plumbopen("send", OWRITE|OCEXEC);
	if(fd<0)
		return;
	plumbsendtext(fd, "vdir", nil, nil, f);
	close(fd);
	free(f);
}	

void
initcolors(void)
{
	toolbg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xEFEFEFFF);
	toolfg = display->black;
	viewbg = display->white;
	viewfg = display->black;
	scrollbg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x999999FF);
	scrollfg = display->white;
}

Image*
loadicon(Rectangle r, uchar *data, int ndata)
{
	Image *i;
	int n;

	i = allocimage(display, r, RGBA32, 0, DNofill);
	if(i==nil)
		sysfatal("allocimage: %r");
	n = loadimage(i, r, data, ndata);
	if(n<0)
		sysfatal("loadimage: %r");
	return i;
}

void
initimages(void)
{
	Rectangle small = Rect(0, 0, 12, 12);
	Rectangle big   = Rect(0, 0, 16, 16);

	folder = loadicon(small, folderdata, sizeof folderdata);
	file   = loadicon(small, filedata, sizeof filedata);
	ihome  = loadicon(big, homedata, sizeof homedata);
	icd    = loadicon(big, cddata, sizeof cddata);
	iup    = loadicon(big, updata, sizeof updata);
	inewfile = loadicon(big, newfiledata, sizeof newfiledata);
	inewfolder = loadicon(big, newfolderdata, sizeof newfolderdata);
}

char*
mdate(Dir d)
{
	char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
			   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
	Tm *tm;

	tm = localtime(d.mtime);
	return smprint("%s %02d %02d:%02d", months[tm->mon], tm->mday, tm->hour, tm->min);
}

Rectangle
drawbutton(Point *p, Image *i)
{
	Rectangle r;

	p->x += Toolpadding;
	r = Rect(p->x, p->y, p->x+16, p->y+16);
	draw(screen, r, i, nil, ZP);
	p->x += 16+Toolpadding;
	return r;
}

void
drawdir(Point p, Dir d)
{
	char buf[255], *t;
	Image *img;

	t = mdate(d);
	snprint(buf, sizeof buf, "%12lld  %s", d.length, t);
	free(t);
	img = (d.qid.type&QTDIR) ? folder : file;
	p.y -= Padding;
	draw(screen, Rect(p.x, p.y+Padding, p.x+12, p.y+Padding+12), img, nil, ZP);
	p.x += 12+4+Padding;
	p.y += Padding;
	string(screen, p, viewfg, ZP, font, d.name);
	p.x = viewr.max.x - stringwidth(font, buf) - 3*Padding - Toolpadding;
	string(screen, p, viewfg, ZP, font, buf);
}

void
redraw(void)
{
	Point p;
	int i, h, y;

	draw(screen, screen->r, display->white, nil, ZP);
	p = addpt(screen->r.min, Pt(0, Toolpadding));
	draw(screen, toolr, toolbg, nil, ZP);
	line(screen, Pt(toolr.min.x, toolr.max.y), toolr.max, 0, 0, 0, toolfg, ZP);
	homer = drawbutton(&p, ihome);
	cdr = drawbutton(&p, icd);
	upr = drawbutton(&p, iup);
	p.x += Toolpadding;
	p.y = toolr.min.y + (Toolpadding+16+Toolpadding-font->height)/2;
	string(screen, p, toolfg, ZP, font, path);
	p.x = screen->r.max.x - 2*(Toolpadding+16+Toolpadding);
	p.y = screen->r.min.y + Toolpadding;
	newdirr = drawbutton(&p, inewfolder);
	newfiler = drawbutton(&p, inewfile);
	draw(screen, scrollr, scrollbg, nil, ZP);
	if(ndirs>0){
		h = ((double)nlines/ndirs)*Dy(scrollr);
		y = ((double)offset/ndirs)*Dy(scrollr);
		scrposr = Rect(scrollr.min.x, scrollr.min.y+y, scrollr.max.x-1, scrollr.min.y+y+h);
	}else
		scrposr = Rect(scrollr.min.x, scrollr.min.y, scrollr.max.x-1, scrollr.max.y);
	draw(screen, scrposr, display->white, nil, ZP);
	p = addpt(viewr.min, Pt(Toolpadding, Toolpadding));
	for(i = 0; i<nlines && offset+i<ndirs; i++){
		drawdir(p, dirs[offset+i]);
		p.x = viewr.min.x+Toolpadding;
		p.y += lineh;
	}
}

void
scrollup(int off)
{
	if(offset == 0)
		return;
	offset -= off;
	if(offset < 0)
		offset = 0;
	redraw();
}

void
scrolldown(int off)
{
	if(offset+nlines > ndirs)
		return;
	offset += off;
	redraw();
}

void
eresized(int new)
{
	if(new && getwindow(display, Refnone)<0)
		sysfatal("cannot reattach: %r");
	lineh = Padding+font->height+Padding;
	toolr = screen->r;
	toolr.max.y = toolr.min.y+16+2*Toolpadding;
	scrollr = screen->r;
	scrollr.min.y = toolr.max.y+1;
	scrollr.max.x = scrollr.min.x + Scrollwidth;
	scrollr = insetrect(scrollr, 1);
	viewr = screen->r;
	viewr.min.x += Scrollwidth;
	viewr.min.y = toolr.max.y+1;
	nlines = Dy(viewr)/lineh;
	redraw();
}

void
evtkey(Rune k)
{
	switch(k){
	case 'q':
	case Kdel:
		exits(nil);
		break;
	case Kpgup:
		scrollup(nlines);
		break;
	case Kpgdown:
		scrolldown(nlines);
		break;
	case Khome:
		cd(nil);
		redraw();
		break;
	case Kup:
		up();
		redraw();
		break;
	}
}

void
evtmouse(Mouse m)
{
	int n;
	Dir d;
	char buf[256] = {0};

	if(m.buttons&4){
		if(ptinrect(m.xy, homer)){
			cd(nil);
			redraw();
		}else if(ptinrect(m.xy, upr)){
			up();
			redraw();
		}else if(ptinrect(m.xy, cdr)){
			if(eenter("Directory", buf, sizeof buf, &m)>0){
				cd(buf);
				redraw();
			}
		}else if(ptinrect(m.xy, newdirr)){
			if(eenter("Create directory", buf, sizeof buf, &m)>0){
				mkdir(buf);
				redraw();
			}
		}else if(ptinrect(m.xy, newfiler)){
			if(eenter("Create file", buf, sizeof buf, &m)>0){
				touch(buf);
				redraw();
			}
		}else if(ptinrect(m.xy, viewr)){
			n = (m.xy.y-viewr.min.y)/lineh;
			if(offset+n>=ndirs)
				return;
			d = dirs[offset+n];
			if(d.qid.type && QTDIR){
				cd(d.name);
				redraw();
			}else
				plumbfile(path, d.name);
		}
	}else if(m.buttons&8)
		scrollup(Slowscroll);
	else if(m.buttons&16)
		scrolldown(Slowscroll);
}


void
main(int argc, char *argv[])
{
	Event e;

	offset = 0;
	if(argc==2)
		snprint(path, sizeof path, argv[1]);
	else
		getwd(path, sizeof path);
	readhome();
	loaddirs();
	if(initdraw(nil, nil, "vdir")<0)
		sysfatal("initdraw: %r");
	initcolors();
	initimages();
	einit(Emouse|Ekeyboard);
	eresized(0);
	for(;;){
		switch(event(&e)){
		case Ekeyboard:
			evtkey(e.kbdc);
			break;
		case Emouse:
			evtmouse(e.mouse);
			break;
		}
	}
}