shithub: purgatorio

ref: 9ca327dd06c30341d984bff84727ea451b428db7
dir: /appl/demo/camera/tkinterface.b/

View raw version
# Sort out timing with taking photo & getting jpg/thumbnail - make sure it gets the right one when 2photos have been taken & sort out 'cannot communicate with camera' error

implement tkinterface;

include "tk.m";
	tk: Tk;
include "tkclient.m";
	tkclient: Tkclient;
include "sys.m";
	sys : Sys;
include "daytime.m";
	daytime: Daytime;
include "readdir.m";
	readdir: Readdir;
include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;
include "selectfile.m";
	selectfile: Selectfile;

include "string.m";
	str : String;
include "draw.m";
	draw: Draw;
	Context, Display, Point, Rect, Image, Screen, Font: import draw;
include "grid/readjpg.m";
	readjpg: Readjpg;

display : ref draw->Display;
context : ref draw->Context;
camerapath := "";
savepath := "";
tmppath := "/tmp/";
usecache := 1;
working := 0;
processing := 0;
coords: draw->Rect;
DONE : con 1;
KILLED : con 2;
font: ref Draw->Font;
tkfont := "";
tkfontb := "";
tkfontf := "";
ssize := 3;
maxsize : Point;
nilrect := Draw->Rect((0,0),(0,0));
runwithoutcam := 0;
toplevels : list of (ref Tk->Toplevel, string, list of int, int) = nil;
procimg : ref Draw->Image;
loadimg: ref Draw->Image;

tkinterface : module {
	init : fn (ctxt : ref Draw->Context, argv : list of string);
};

init(ctxt : ref Draw->Context, argv : list of string)
{
	display = ctxt.display;
	context = ctxt;

	sys = load Sys Sys->PATH;
#	sys->pctl(Sys->NEWPGRP, nil);
#	sys->pctl(Sys->FORKNS, nil);

	str = load String String->PATH;
	readdir = load Readdir Readdir->PATH;
	daytime = load Daytime Daytime->PATH;
	bufio = load Bufio Bufio->PATH;
	
	str = load String String->PATH;
	draw = load Draw Draw->PATH;

	tk = load Tk Tk->PATH;
	tkclient = load Tkclient Tkclient->PATH;
	tkclient->init();
	selectfile = load Selectfile Selectfile->PATH;
	selectfile->init();
	readjpg = load Readjpg Readjpg->PATH;
	readjpg->init(display);
	font = draw->Font.open(display, "/fonts/charon/plain.small.font");
	runfrom := hd argv;
	p := isat2(runfrom,"/");
	savepath = runfrom[:p+1];
	argv = tl argv;
	while (argv != nil) {
		if (camerapath == "" && (hd argv)[0] == '/') camerapath = hd argv;
		if (hd argv == "nocache") usecache = 0;
		argv = tl argv;
	}
	if (camerapath == "")
		camerapath = "./";
	if (camerapath != "" && camerapath[len camerapath - 1] != '/')
		camerapath[len camerapath] = '/';

	r := display.image.r;
#	if (r.dx() < 800 || r.dy() < 600) ssize = 2;
	if (r.dx() < 400 || r.dy() < 300) ssize = 1;
	maxsize = (r.dx(), r.dy());

	if (ssize == 1) {
		tkfont = "/fonts/charon/plain.tiny.font";
		tkfontb = "/fonts/charon/bold.tiny.font";
		tkfontf = "/fonts/pelm/unicode.8.font";
	}
	else if (ssize == 2) {
		tkfont = "/fonts/charon/plain.small.font";
		tkfontb = "/fonts/charon/bold.small.font";
		tkfontf = "/fonts/pelm/unicode.8.font";
	}
	else {
		tkfont = "/fonts/charon/plain.normal.font";
		tkfontb = "/fonts/charon/bold.normal.font";
		tkfontf = "/fonts/pelm/unicode.8.font";
	}
	if ((sys->stat(tkfont)).t0 == -1)
		tkfont = "";
	else tkfont = " -font " + tkfont;
	if ((sys->stat(tkfontb)).t0 == -1)
		tkfontb = "";
	else tkfontb = " -font " + tkfontb;
	if ((sys->stat(tkfontf)).t0 == -1)
		tkfontf = "";
	else tkfontf = " -font " + tkfontf;

	procimg = display.open("camproc.bit");
	loadimg = display.open("camload.bit");

	spawn tkstuff();
}

# Tk stuff

thumbscr := array[] of {
	"frame .f",
	"frame .fthumb -bg white",
	"frame .f.finfo",
	"frame .f.fsnap",
	"menubutton .f.fsnap.fsettings.mb2 -text {Selected\n(0 files)} -menu .m2 @",
	"menu .m2 @",
	".m2 add command -text {Select All} -command {send butchan selectall 1}",
	".m2 add command -text {Select None} -command {send butchan selectall 0}",
	".m2 add command -text {Invert Selection} -command {send butchan invert}",
	".m2 add command -text {Refresh Files} -command {send butchan refresh}",
	"menu .m @",

	"frame .f.fsnap.fsettings -borderwidth 1 -relief raised",
	"menubutton .f.fsnap.fsettings.mb -text {Settings} -menu .m &",
	"button .f.fsnap.fsettings.b -text {Information} -command {send butchan info} &",
	"grid .f.fsnap.fsettings.b -row 0 -column 0 -sticky ew",
	"grid .f.fsnap.fsettings.mb -row 1 -column 0 -sticky ew",
	"grid .f.fsnap.fsettings.mb2 -row 2 -column 0 -sticky ew",

	"frame .f.fsnap.fstore -borderwidth 1 -relief raised",
	"label .f.fsnap.fstore.l1 -text {  Photos taken: } @",
	"label .f.fsnap.fstore.l2 -text {  Remaining: } @",
	"label .f.fsnap.fstore.l3 -text {  } @",
	"label .f.fsnap.fstore.l4 -text {  } @",
	"grid .f.fsnap.fstore.l1 -row 0 -column 0 -sticky w",
	"grid .f.fsnap.fstore.l2 -row 1 -column 0 -sticky w",
	"grid .f.fsnap.fstore.l3 -row 0 -column 1 -sticky w",
	"grid .f.fsnap.fstore.l4 -row 1 -column 1 -sticky w",

	"frame .f.fsnap.ftime -borderwidth 1 -relief raised",
	"label .f.fsnap.ftime.l1 -text {Local: } @",
	"label .f.fsnap.ftime.l2 -text {Camera: } @",
	"label .f.fsnap.ftime.l3",
	"label .f.fsnap.ftime.l4",
	"checkbutton .f.fsnap.ftime.cb -text {Set camera to local time} -variable time &",
	"button .f.fsnap.ftime.b -text {refresh} -command {send butchan gettime} &",
	"grid .f.fsnap.ftime.l1 -row 0 -column 0 -sticky w",
	"grid .f.fsnap.ftime.l2 -row 1 -column 0 -sticky w",
	"grid .f.fsnap.ftime.l3 -row 0 -column 1 -sticky w",
	"grid .f.fsnap.ftime.l4 -row 1 -column 1 -sticky w",
	"grid .f.fsnap.ftime.cb -row 2 -column 0 -columnspan 2",
	"grid .f.fsnap.ftime.b -row 3 -column 0 -columnspan 2",

	"button .f.fsnap.b -text {Take Photo} -command {send butchan snap} &",
	"grid columnconfigure .f.fsnap 2 -minsize 150",
	"frame .f.fcom",
	"frame .f.f1 -background #0d0d0d1a",
	"canvas .f.f1.c1 -yscrollcommand {.f.f1.sb1 set} -height 255 -width 542 -bg white",
	".f.f1.c1 create window 0 0 -window .fthumb -anchor nw",
	"scrollbar .f.f1.sb1 -command {.f.f1.c1 yview}",

#	"frame .f.f2",
#	"canvas .f.f2.c1 -width 556 -height 304",
#	".f.f2.c1 create window 0 0 -window .f.fsnap -anchor nw",

	"grid .f.fsnap -column 0 -row 0",
	"grid .f.f1 -column 0 -row 1",
	"grid .f.f1.c1 -column 0 -row 0",
	"grid .f.f1.sb1 -column 1 -row 0 -sticky ns",
#	"grid .f.f2 -column 0 -row 0",
#	"grid .f.f2.c1 -column 0 -row 0 -sticky ew",
	"bind .Wm_t <ButtonPress-1> +{focus .}",
	"bind .Wm_t.title <ButtonPress-1> +{focus .}",
};

lastpath := "";

Aitem: adt {
	pname,desc: string;
	dtype,factory: int;
	read, location: string;
	data: list of (string, int);
};
LIST: con 0;
MINMAX: con 1;
OTHER: con 2;

noabilities := 0;	
abilities : array of Aitem;

getdesc(l : list of string): list of string
{
	s := "";
	while(hd l != "min" && hd l != "items" && tl l != nil) {
		s += hd l + " ";
		l = tl l;
	}
	while (s[len s - 1] == ' ' || s[len s - 1] == '\n')
		s = s[:len s -1];
	l = s :: l;
	return l;
}

inflist : list of (string, string);
ablmenu : array of string;

getabilities()
{
	inflist = nil;
	abilities = array[200] of Aitem;	
	fd := bufio->open(camerapath+"abilities", bufio->OREAD);
	if (runwithoutcam)
		fd = bufio->open("/usr/danny/camera/abls", bufio->OREAD);
	i := 0;
	for (;;) {
		take := 0;
		s := fd.gets('\n');
		if (s == "") break;
		(n, lst) := sys->tokenize(s," ,:\t\n");
		abilities[i].data = nil;
		abilities[i].read = "";
		if (lst != nil && len hd lst == 4) {
			abilities[i].pname = hd lst;
			lst = getdesc(tl lst);
			abilities[i].desc = hd lst;
			if (hd tl lst == "items") {
				abilities[i].dtype = LIST;
				abilities[i].factory = int hd tl tl tl tl lst;
				noitems := int hd tl tl lst;
				for (k := 0; k < noitems; k++) {
					s = fd.gets('\n');
					(n2, lst2) := sys->tokenize(s,",:\t\n");
					name := hd lst2;
					val := int hd tl lst2;
					if (k == 0) {
						if (abilities[i].pname == "ssiz")
							abilities[i].factory = val;
						else if (abilities[i].pname == "scpn")
							abilities[i].factory = val;
					}	
					if (val == abilities[i].factory && noitems > 1) name += " *";
					abilities[i].data = (name, val) :: abilities[i].data;
				}
				if (noitems < 2) {
					inflist = (abilities[i].desc, (hd abilities[i].data).t0) :: inflist;
					take = 1;
				}
			}
			else if (hd tl lst == "min") {
				abilities[i].dtype = MINMAX;
				abilities[i].factory = int hd tl tl tl tl tl tl lst;
				min := int hd tl tl lst;
				max := int hd tl tl tl tl lst;
				mul := 1;
				while (max > 200000) {
					min /= 10;
					max /= 10;
					mul *= 10;
				}
				abilities[i].data = ("min", min) :: abilities[i].data;
				abilities[i].data = ("max", max) :: abilities[i].data;
				abilities[i].data = ("mul", mul) :: abilities[i].data;
			}
			else {
				inflist = (abilities[i].desc,list2string(tl lst)) :: inflist;
				take = 1;
			}
			if (take || 
				abilities[i].desc == "Time Format" ||
				abilities[i].desc == "Date Format" ||
				abilities[i].desc == "File Type" ||
				contains(abilities[i].desc,"Video") ||
				contains(abilities[i].desc,"Media") ||
				contains(abilities[i].desc,"Sound") ||
				contains(abilities[i].desc,"Volume") ||
				contains(abilities[i].desc,"Slide") ||
				contains(abilities[i].desc,"Timelapse") ||
				contains(abilities[i].desc,"Burst") ||
				contains(abilities[i].desc,"Power") ||
				contains(abilities[i].desc,"Sleep"))
					i--;
			i++;
		}	
	}
	noabilities = i;
}

isat(s: string, test: string): int
{
	num := -1;
	if (len test > len s) return -1;
	for (i := 0; i < (1 + (len s) - (len test)); i++) {
		if (num == -1 && test == s[i:i+len test]) num = i;
	}
	return num;
}

isat2(s: string, test: string): int
{
	num := -1;
	if (len test > len s) return -1;
	for (i := len s - len test; i >= 0; i--) {
		if (num == -1 && test == s[i:i+len test]) num = i;
	}
	return num;
}


nomatches(s: string): int
{
	n := 0;
	for (i := 0; i < noabilities; i++) {
		test := abilities[i].desc;
		if (len s <= len test && test[:len s] == s) n++;
	}
	return n;
}

matches(s1,s2: string): int
{
	if (len s1 < len s2) return 0;
	if (s1[:len s2] == s2) return 1;
	return 0;
}

biggestmatch(nm: int, s: string, l: int): string
{
	bigmatch := s;
	match := s[:l];
	for (;;) {
		if (bigmatch == match) break;
		if (nomatches(bigmatch) == nm) return bigmatch;
		p := isat2(bigmatch," ");
		if (p < len match) break;
		bigmatch = bigmatch[:p];
	}
	return match;
}

getabllist(): array of string
{
	los : list of string;
	los = nil;
	for (i := 0; i < noabilities; i++) {
		p := 0;
		p2 := 0;
		nm : int;
		for (;;) {
			nm = -1;
			tmpl := los;
			while (tmpl != nil) {
				if (matches(abilities[i].desc, hd tmpl)) nm = 0;
				tmpl = tl tmpl;
			}
			if (nm == 0) break;
			p += p2;
			tmp := abilities[i].desc[p:];
			p2 = isat(tmp, " ");
			if (p2 == -1) p2 = len tmp;
			else p2++;
			nm = nomatches(abilities[i].desc[:p+p2]);
			if (nm <= 5) break;
		}
		if (nm > 0) {
			listitem := biggestmatch(nm, abilities[i].desc,p+p2);
			los = listitem :: los;
		}
	}
	ar := array[len los] of string;
	for (i = len ar - 1; i >= 0; i--) {
		ar[i] = hd los;
		los = tl los;
	}
	return ar;
}

buildabilitiesframes(top: ref Tk->Toplevel)
{
	ablmenu = getabllist();
	tkcmd(top, ".m add command -text {Refresh Main Screen} -command {send butchan refreshstate}");
	tkcmd(top, ".m add command -text {Reset Camera} -command {send butchan reset}");
	for (k := 0; k < len ablmenu; k++) {
		if (len ablmenu[k] > 4 && (ablmenu[k][:4] == "Zoom" || ablmenu[k][:5] == "Still")) 
 			buildabilitiesframe(top,k,"butchan");
		else
			tkcmd(top, ".m add command -text {"+ablmenu[k]+
				"} -command {send butchan abls "+string k+"}");
	}
	tkcmd(top, "menu .mthumb "+tkfont);
	tkcmd(top, ".mthumb add command -label {Selection (88 files)}");
	tkcmd(top, ".mthumb add separator");
	for (k = nothumbs; k < len menu; k++)
		 tkcmd(top, ".mthumb add command -text {"+menu[k].text+"} " +
				"-command {send butchan}");

}

buildabilitiesframe(top: ref Tk->Toplevel,k: int, chanout: string)
{
	nm := string nomatches(ablmenu[k]);
	count2 := 0;
	for (i := 0; i < noabilities; i++) {
		if (matches(abilities[i].desc,ablmenu[k])) {

			frame : string;
			case abilities[i].pname {
				"scpn" or "ssiz" or "zpos" =>
					frame = ".f.fsnap.f"+abilities[i].pname;
					tkcmd(top, "frame "+frame+" -borderwidth 1 -relief raised");
				* =>
					frame = ".f";
					if (count2 == 0)  { 
						tkcmd(top, "frame "+frame);
						tkcmd(top, "label "+frame+".l -text {"+ablmenu[k]+"}"+tkfontb);
						tkcmd(top, "grid "+frame+".l -row 0 -column 0 -columnspan "+nm);
					}
					frame = frame + ".f"+string count2;
					tkcmd(top, "frame "+frame+" -borderwidth 1 -relief raised");
					tkcmd(top, "grid "+frame+" -row 1 -column "+string count2+ " -sticky nsew");
					mul := getval(abilities[i].data,"mul");
					s := abilities[i].desc[len ablmenu[k]:];
					if (mul != 1 && abilities[i].dtype == MINMAX)
						s += " (x"+string mul+")";
					tkcmd(top, "label "+frame+".l -text {"+s+"}"+tkfont);
					tkcmd(top, "grid "+frame+".l -row 0 -column 0 -sticky nw");
			}
				
			if (abilities[i].dtype == MINMAX) {
				abilities[i].location = frame+".sc";
				min := getval(abilities[i].data,"min");
				max := getval(abilities[i].data,"max");
				tkcmd(top, sys->sprint("scale %s.sc -to %d -from %d %s", frame,min,max,tkfont));
				tkcmd(top, "bind "+frame+".sc <ButtonPress-3> {send " +
					chanout + " scaleval " + string i + " %X %Y}");
				tkcmd(top, "grid "+frame+".sc -row 1 -column 0");		
			}
			else if (abilities[i].dtype == LIST) {
				tkcmd(top, "frame "+frame+".frb");
				tkcmd(top, "grid "+frame+".frb -row 1 -column 0");
				tmp := abilities[i].data;
				row := 0;
				while (tmp != nil) {
					(name, val) := hd tmp;
					s := sys->sprint("radiobutton %s.frb.rb%d -text {%s} -value %d -variable %s  -height %d %s",frame,row,name,val,abilities[i].pname,24 - (3*(3-ssize)), tkfont);
					tkcmd(top,s);
					tkcmd(top, sys->sprint("grid %s.frb.rb%d -row %d -column 0 -sticky w",
						frame,row,row));
					tmp = tl tmp;
					row++;
				}
			}
			tkcmd(top, "button "+frame+".bs -text {Set} -command "+
				"{send "+chanout+" set "+string i+"}"+butheight+tkfont);
			tkcmd(top, "grid "+frame+".bs -row 2 -column 0 -sticky ew");
			if (abilities[i].dtype == MINMAX) {
				tkcmd(top, "button "+frame+".bf -text {Default} -command "+
					"{send "+chanout+" setdef "+string i+"}"+butheight+tkfont);
				tkcmd(top, "grid "+frame+".bf -row 3 -column 0 -sticky ew");
			}
			count2++;
		}
	}
}

getvaluescr := array[] of {
	"frame .f -height 84 -width 114 -borderwidth 2 -relief raised",
	"label .f.l1 -text {Enter Value:} @",
	"entry .f.e1 -width 100 -bg white @",
	"button .f.b1 -text { ok } -command {send chanin ok} &",
	"button .f.b2 -text cancel -command {send chanin cancel} &",
	"grid .f.l1 -column 1 -row 0 -columnspan 2 -padx 0 -sticky w",
	"grid .f.e1 -column 1 -row 1 -columnspan 2 -padx 0 -pady 5",
	"grid .f.b1 -column 1 -row 2 -padx 0",
	"grid .f.b2 -column 2 -row 2 -padx 0",
	"grid columnconfigure .f 1 -minsize 20",
	"grid columnconfigure .f 2 -minsize 20",
	"grid columnconfigure .f 3 -minsize 5",
	"grid rowconfigure .f 0 -minsize 20",
	"grid rowconfigure .f 1 -minsize 20",
	"grid rowconfigure .f 2 -minsize 20",
	"grid columnconfigure .f 0 -minsize 5",
	"bind .f.e1 <Key> {send chanin key %s}",
	"focus .f.e1",
	"pack .f",
	"update",
};

getvaluescreen(x,y: string): int
{
	x = string ((int x) - 55);
	y = string ((int y) - 30);
	(top, nil) := tkclient->toplevel(context, "-x "+x+" -y "+y, nil, tkclient->Plain);
	chanin := chan of string;
	tk->namechan(top, chanin, "chanin");
	for (tk1 := 0; tk1 < len getvaluescr; tk1++)
		tkcmd(top, getvaluescr[tk1]);
	tkclient->onscreen(top, "exact");
	tkclient->startinput(top, "kbd"::"ptr"::nil);
	for(;;) {
		alt {
		s := <-top.ctxt.kbd =>
			tk->keyboard(top, s);
		s := <-top.ctxt.ptr =>
			tk->pointer(top, *s);
		inp := <- chanin =>
			if (inp == "ok") return int tkcmd(top, ".f.e1 get");
			else if (inp == "cancel") return -1;
			else if (inp[:3] == "key") {
				s := " ";
				s[0] = int inp[4:];
				if (s[0] == '\n') return int tkcmd(top, ".f.e1 get");
				if (s[0] >= '0' && s[0] <= '9') {
					tkcmd(top, ".f.e1 delete sel.first sel.last");
					tkcmd(top, ".f.e1 insert insert {"+s+"}; update");
				}
			}
		}	
	}
}

infoscreen()
{
	(top, titlebar) := tkclient->toplevel(context, "", "Information", Tkclient->Hide);
	tmp := inflist;
	tkcmd(top, "frame .f");
	tkcmd(top, "label .f.l -text {Information}");
	tkcmd(top, "grid .f.l -row 0 -column 0 -columnspan 2");
	tkcmd(top, "frame .f.finfo -borderwidth 1 -relief raised");
	tkcmd(top, "grid .f.finfo");
	infrow := 0;
	while (tmp != nil) {
		infrow++;
		s := string infrow;
		(d1,d2) := hd tmp;
		tkcmd(top, "label .f.finfo.l"+s+"1 -text {"+d1+"}");
		tkcmd(top, "label .f.finfo.l"+s+"2 -text {"+d2+"}");
		tkcmd(top, "grid .f.finfo.l"+s+"1 -row "+s+" -column 0 -sticky w");
		tkcmd(top, "grid .f.finfo.l"+s+"2 -row "+s+" -column 1 -sticky e");
		tmp = tl tmp;
	}
	tkcmd(top, "pack .f; update");
	tkclient->onscreen(top, nil);
	tkclient->startinput(top, "kbd"::"ptr"::nil);
	main: for(;;) {
		alt {
		s := <-top.ctxt.kbd =>
			tk->keyboard(top, s);
		s := <-top.ctxt.ptr =>
			tk->pointer(top, *s);
		title := <-top.ctxt.ctl or
		title = <-top.wreq or
		title = <- titlebar =>
			if (title == "exit") break main;
			tkclient->wmctl(top, title);
		}
	}
}

settingsscreen(k: int, ctlchan: chan of int)
{
	low := toplevels;
	for (;low != nil; low = tl low) {
		(tplvl, name, nil,nil) := hd low;
		if (name == ablmenu[k]) {
			tkcmd(tplvl, "raise .; focus .; update");
			ctlchan <-= DONE;
			return;
		}
	}
	pid := sys->pctl(0, nil);
	(top, titlebar) := tkclient->toplevel(context, "", "Config", Tkclient->Appl);
	chanin := chan of string;
	tk->namechan(top,chanin, "chanin");
	buildabilitiesframe(top,k, "chanin");
	tkcmd(top,"bind .Wm_t <ButtonPress-1> +{focus .}");
	tkcmd(top,"bind .Wm_t.title <ButtonPress-1> +{focus .}");
	tkcmd(top, "pack .f; update");
	err := 0;
	allread := 1;
	l : list of int = nil;
	for (i := 0; i < noabilities; i++) {
		if (matches(abilities[i].desc, ablmenu[k])) {
			l = i :: l;
			if (abilities[i].read != "")
				setmystate(top,i,abilities[i].read);
			else
				allread = 0;
		}
	}
	tkclient->onscreen(top, nil);
	tkclient->startinput(top, "kbd"::"ptr"::nil);
	if (!allread) {
		spawn workingscreen2(getcoords(top),pid, ctlchan,0);
		ltmp := l;
		for (;ltmp != nil; ltmp = tl ltmp) {
			if (abilities[hd ltmp].read == "" && getstate(top, hd ltmp) == -1) {
				err = 1;
				break;
			}
		}
	}
	if (!err)
		spawn settingsloop(top,chanin,titlebar,k,l);
	ctlchan <-= DONE;
}

settingsloop(top: ref Tk->Toplevel, chanin,titlebar: chan of string, k: int, abls: list of int)
{
	tkcmd(top, "focus .Wm_t");
	pid := sys->pctl(0,nil);
	addtoplevel(top,ablmenu[k], abls, pid);
	ctlchan := chan of int;
	main: for(;;) {
		alt {
		s := <-top.ctxt.kbd =>
			tk->keyboard(top, s);
		s := <-top.ctxt.ptr =>
			tk->pointer(top, *s);
		inp := <- chanin =>
			tkcmd(top, "focus .");
			(n, lst) := sys->tokenize(inp, " \t\n");
			case hd lst {
				"scaleval" =>
					i := int hd tl lst;
					val := getvaluescreen(hd tl tl lst, hd tl tl tl lst);
					if (val != -1) tkcmd(top, abilities[i].location+" set "+string val+";update");
				"set" or "setdef" =>
					if (working)
						dialog(" Camera is busy! ", 2,-1,getcoords(top));
					else {
						spawn set(top, int hd tl lst, hd lst, ctlchan);
						<-ctlchan;
						working = 0;
					}
			}
			clearbuffer(chanin);
		title := <-top.ctxt.ctl or
		title = <-top.wreq or
		title = <- titlebar =>
			if (title == "exit") break main;
			tkclient->wmctl(top, title);
		}
	}
	deltoplevel(top);
}

clearbuffer(c: chan of string)
{
	tc := chan of int;
	spawn timer(tc);
	main: for (;;) alt {
		del := <-c => ;
		tick := <-tc =>		
			break main;
	}	
}

timer(tick: chan of int)
{
	sys->sleep(100);
	tick <- = 1;
}

getval(l: list of (string,int), s: string): int
{
	while (l != nil) {
		(name,val) := hd l;
		if (name == s) return val;
		l = tl l;
	}
	return -2;
}

list2string(l : list of string): string
{
	s := "";
	while (l != nil) {
		s += " " + hd l;
		l = tl l;
	}
	if (s != "") return s[1:];
	return s;
}

JPG: con 0;
THUMB: con 1;

Imgloaded: adt {
	name: string;
	imgtype: int;
};

nofiles := 0;
filelist := array[200] of string;
thumbimg := array[200] of ref draw->Image;
selected := array[200] of { * => 0 };
noselected := 0;
fnew : list of int;
imgloaded :  list of Imgloaded;
maxwidth, maxheight: int;
nothumbs := 0;

nocamera(): int
{
	(n,dir) := sys->stat(camerapath+"ctl");
	if (n != -1) return 0;
	return 1;
}

startuptkstuff(top: ref Tk->Toplevel, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(coords,pid, ctlchan,1);
	getabilities();
	(dirs,n) := readdir->init(camerapath+"thumb", readdir->NAME);
	if (n == -1) nothumbs = 1;
	buildabilitiesframes(top);
	refreshfilelist(top,0);
	ctlchan <-= DONE;
}

tibuild := 0;
butheight := "";

tkstuff()
{
	if (!runwithoutcam && nocamera()) {
		dialog("Cannot find camera!",0,-1,nilrect);
		exit;
	}
	(win, titlebar) := tkclient->toplevel(context, "", "Camera", Tkclient->Appl);
	tkcmd(win, "frame .test");
	if (tkcmd(win, ".test cget -bg") == "#ffffffff")
		tibuild = 1;
	tkcmd(win, "destroy .test");
	butheight = " -height "+string (16 + (5*tibuild) - (3*(3-ssize)));
	butchan := chan of string;
	tk->namechan(win, butchan, "butchan");
	for (tk1 := 0; tk1 < len thumbscr; tk1++)
		tkcmd(win, thumbscr[tk1]);
	coords = display.image.r;
	ctlchan := chan of int;
	imgloaded = nil;

	spawn startuptkstuff(win, ctlchan);
	e := <- ctlchan;
	if (e == KILLED) {
		dialog("Cancel during load!",0,-1,coords);
		exit;
	}
	working = 0;
	spawn mainscreen(win, 1, ctlchan);
	<- ctlchan;
	working = 0;

	processing = 0;	
	tkcmd(win, "pack propagate . 0");
	resizemain(win,1);
	tkcmd(win, "pack .f; update; focus .");
	coords = getcoords(win);
	loadimg = nil;
	tkclient->onscreen(win, nil);
	tkclient->startinput(win, "kbd"::"ptr"::nil);
	main: for (;;) {
		alt {
		s := <-win.ctxt.kbd =>
			tk->keyboard(win, s);
		s := <-win.ctxt.ptr =>
			tk->pointer(win, *s);
		inp := <-butchan =>
			tkcmd(win, "focus .");
			(n, lst) := sys->tokenize(inp, "\t\n ");
			case hd lst {

				# Communicates internally

				"scaleval" =>
					i := int hd tl lst;
					val := getvaluescreen(hd tl tl lst, hd tl tl tl lst);
					if (val != -1) tkcmd(win, abilities[i].location+" set "+string val);
				"info" =>
					spawn infoscreen();
				"unload" =>
					i := int hd tl lst;
					for (k := 0; k < nofiles; k++) {
						if (i == k || (i == -1 && selected[k])) {
							delloaded(filelist[k],JPG);
							delloaded(filelist[k],THUMB);
						}
					}
				"invert" =>
					nf := 0;
					for (i := 0; i < nofiles; i++)
						selected[i] = 1 - selected[i];
					doselect(win);
				"selectall" =>
					val := int hd tl lst;
					for (i := 0; i < nofiles; i++)
						selected[i] = val;
					doselect(win);
				"select" =>
					i := int hd tl lst;
					selected[i] = 1 - selected[i];
					doselect(win);
				"selectonly" =>
					i := int hd tl lst;
					val := selected[i];
					for (k := 0; k < nofiles; k++)
						selected[k] = 0;
					if (noselected - val == 0) selected[i] = 1 - val;
					else selected[i] = 1;
					doselect(win);
				"menu" =>
					i := int hd tl lst;
					if (selected[i] && noselected > 1) i = -1;
					title := "Selection ("+string noselected+" files)";
					if (i != -1) title = filelist[i]+".jpg";
					si := string i;
						tkcmd(win, ".mthumb entryconfigure 0 -text {"+title+"}");
					for (k := nothumbs; k < len menu; k++)
						tkcmd(win, ".mthumb entryconfigure "+string (2+k-nothumbs)+
							" -command {send butchan "+	menu[k].com+" "+si+"}"); 
					tkcmd(win, ".mthumb post "+hd tl tl lst+" "+hd tl tl tl lst);
				* =>
					if (!processing) 
						spawn dealwithcamera(win, lst);
			}
			tkcmd(win, "update");
			clearbuffer(butchan);
		title := <-win.ctxt.ctl or
		title = <-win.wreq or
		title = <-titlebar =>
			if (title == "exit")
				break main;
			err := tkclient->wmctl(win, title);
			if (err == nil && title == "!size") {
				(n, lst) := sys->tokenize(title, " ");
				if (hd tl lst == ".")
					resizemain(win,0);
			}
			coords = getcoords(win);
		}	
	}
	for (; toplevels != nil; toplevels = tl toplevels) {
		(nil, nil, nil, pid) := hd toplevels;
		if (pid != -1)
			kill(pid);
	}
	while (imgloaded != nil) {
		(fname, ftype) := hd imgloaded;
		sys->remove(tmppath+fname+"."+string ftype+"~");
		imgloaded = tl imgloaded;
	}
	tkcmd(win, "destroy .");
	exit;
}

dealwithcamera(win: ref Tk->Toplevel, lst: list of string)
{
	ctlchan := chan of int;
	processing = 1;
	case hd lst {
		"gettime" =>
			spawn refreshtime(win, ctlchan);
			<- ctlchan;
		"show" =>
			spawn loadthumb(win,int hd tl lst,ctlchan);
			<- ctlchan;
		"snap" =>
			selected[nofiles+1] = 0;
			spawn takephoto(win, ctlchan);
			<- ctlchan;
			working = 0;
			if (fnew == nil)
				break;
			spawn waittilready(camerapath+"jpg/"+filelist[hd fnew]+".jpg", ctlchan);
			e := <- ctlchan;
			working = 0;
			if (e == DONE) {
				spawn loadnewthumb(win, ctlchan);
			 	<- ctlchan;
			 	working = 0;
			}
		"abls" =>
			spawn settingsscreen(int hd tl lst, ctlchan);
			<- ctlchan;
		"set" or "setdef" =>
			spawn set(win, int hd tl lst, hd lst, ctlchan);
			<- ctlchan;
		"del" =>
			spawn delete(win, int hd tl lst, ctlchan);
			<- ctlchan;
		"view" =>
			i := int hd tl lst;
			unnew(win, i);
			if (i == -1) multiview();
			else vw(i);
		"refresh" =>
			spawn refresh(win, ctlchan);
			<- ctlchan;
		"refreshstate" =>
			spawn mainscreen(win, 0, ctlchan);
			<- ctlchan;
		"dnld" =>
			i := int hd tl lst;
			unnew(win, i);
			if (i == -1) multidownload();
			else dnld(i, "");
		"reset" =>
			if (dialog("reset camera to default settings?",1,-1,coords)) {
				spawn resetcam(win,1, ctlchan);
				<- ctlchan;
			}
	}
	processing = 0;
	working = 0;
}

refresh(top: ref Tk->Toplevel, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(coords,pid, ctlchan,0);
	refreshfilelist(top,1);
	ctlchan <-= DONE;
}

delete(top: ref Tk->Toplevel, i: int, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	ok : int;
	s := "";
	loi : list of int;
	loi = nil;
	if (i == -1) {
		for (k := 0; k < nofiles; k++)
			if (selected[k]) s+= filelist[k]+".jpg\n";
		if (!dialog("Delete Selected files?\n\n"+s,1,-1,coords)) {
			ctlchan <-= DONE;
			return;
		}
	}
	else if (!dialog("Delete "+filelist[i]+".jpg?",1,i,coords)) {
		ctlchan <-= DONE;
		return;
	}
	spawn workingscreen2(coords,pid, ctlchan,0);
	s = "";
	for (k := 0; k < nofiles; k++) {
		if ((i == -1 && selected[k]) || k == i) {
			s += filelist[k]+".jpg ";
			ok = sys->remove(camerapath+
					"jpg/"+filelist[k]+".jpg");
			if (ok == -1) s+="failed\n";
			else {
				s+="ok\n";
				loi = k :: loi;
			}
		}
	}
	if (loi == nil && i != -1) {
		dialog("cannot remove "+filelist[i]+".jpg?",0,i,coords);
		ctlchan <-= DONE;
		return;
	}
	while (loi != nil) {
		delloaded(filelist[hd loi],JPG);
		delloaded(filelist[hd loi],THUMB);
		delselect(hd loi);
		loi = tl loi;
	}
	refreshfilelist(top,0);
	getstore(top);
	if (i == -1) dialog("Files deleted:\n\n"+s,0,-1,coords);
	ctlchan <-= DONE;
}

delselect(n: int)
{
	for (i := n; i < nofiles - 1; i++)
		selected[i] = selected[i+1];
	selected[nofiles - 1] = 0;
}

doselect(top: ref Tk->Toplevel)
{
	n := 0;
	for (i := 0; i < nofiles; i++) {	
		col := "white";
		if (selected[i]) {
			col = "blue";
			n++;
		}
		tkcmd(top,".fthumb.p"+string i+" configure -bg "+col);
	}
	noselected = n;
	s := " files";
	if (n == 1) s = " file";
	tkcmd(top, ".f.fsnap.fsettings.mb2 configure -text {Selected\n("+string n+s+")}");
}

takephoto(top: ref Tk->Toplevel, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(coords,pid, ctlchan,0);
	fd := sys->open(camerapath+"ctl",sys->OWRITE);
	if (fd != nil) {
		e := sys->fprint(fd, "snap");
		if (e < 0) {
			dialog("Could not take photo",0,-1,coords);
			getstore(top);
		}
		else {
			getstore(top);
			n := nofiles;
			for (i := 0; i < 5; i++) {
				refreshfilelist(top,1);
				sys->sleep(1000);
				if (nofiles > n)
					break;
			}
		}
	}
	ctlchan <-= DONE;
}

unnew(top: ref Tk->Toplevel, i: int)
{
	if (fnew == nil)
		return;
	tmp : list of int = nil;
	for (;fnew != nil; fnew = tl fnew) {
		if (i == -1 && selected[hd fnew])
			i = hd fnew;
		if (hd fnew == i)
			tkcmd(top, ".fthumb.mb"+string hd fnew+" configure -fg black; update");
		else
			tmp = hd fnew :: tmp;
	}
	fnew = tmp;
}

refreshtime(top: ref Tk->Toplevel, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(coords,pid, ctlchan,0);
	if (!samedate(top) && tkcmd(top, "variable time") == "1") settime();
	gettime(top);
	ctlchan <-= DONE;
}

addtoplevel(top: ref Tk->Toplevel, name: string, abls: list of int, pid: int)
{
	ltmp := toplevels;
	isin := 0;
	for (;ltmp != nil; ltmp = tl ltmp) {
		(tplvl, nil, nil, nil) := hd ltmp;
		if (tplvl == top) isin = 1;
	}
	if (!isin)
		toplevels = (top, name, abls, pid) :: toplevels;
}

deltoplevel(top: ref Tk->Toplevel)
{
	ltmp : list of (ref Tk->Toplevel, string, list of int, int) = nil;;
	for (;toplevels != nil; toplevels = tl toplevels) {
		(tplvl, nm, loi, p) := hd toplevels;
		if (tplvl != top) 
			ltmp = (tplvl, nm, loi, p) :: ltmp;
	}
	toplevels = ltmp;
}

resetcam(top: ref Tk->Toplevel, show: int, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(coords,pid, ctlchan,0);
	for (i := 0; i < noabilities; i++)
		setstate(i, string abilities[i].factory);
	if (show) {
		ltmp := toplevels;
		for (;ltmp != nil; ltmp = tl ltmp) {
			(tplvl, nm, loi, p) := hd ltmp;
			for (; loi != nil; loi = tl loi)
				setmystate(tplvl, hd loi, string abilities[hd loi].factory);
		}
		if (top != nil)
			getstore(top);
	}
	ctlchan <-= DONE;
}

set(top: ref Tk->Toplevel, i: int, s: string, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(getcoords(top),pid, ctlchan,0);

	val : string;
	if (s == "setdef") {
		val = string abilities[i].factory;
		setmystate(top,i,val);
	}
	else {
		if (abilities[i].dtype == MINMAX) {
			val = tkcmd(top, abilities[i].location+" get");
			mul := getval(abilities[i].data, "mul");
			val = string (int val * mul);
		}
		else {
			val = tkcmd(top, "variable "+abilities[i].pname);	
		}
	}

	e := setstate(i,val);
	if (e == 2) getstore(top);
	else if (e == 0)
		dialog("cannot communicate with camera",0,-1,coords);
	ctlchan <-= DONE;
}

setstate(i: int, val: string): int
{
	fd := sys->open(camerapath+"ctl",sys->OWRITE);
	if (fd != nil) {
		sys->fprint(fd, "%s %s",abilities[i].pname,val);
		abilities[i].read = val;
		if (abilities[i].pname == "ssiz" || abilities[i].pname == "scpn") return 2;
		return 1;
	}
	else return 0;
}

getfirst(s: string): string
{
	(n, lst) := sys->tokenize(s," \n\t");
	if (lst == nil) return "";
	return hd lst;
}

getabl(pname: string): int
{
	for (i := 0; i < noabilities; i++)
		if (abilities[i].pname == pname) return i;
	return -1;
}

getstate(top: ref Tk->Toplevel, i: int): int
{
	fd := sys->open(camerapath+"state", sys->OWRITE);
	if (fd != nil) {
		sys->fprint(fd ,"%s", abilities[i].pname);
		sys->sleep(500);
		fdi := bufio->open(camerapath+"state",sys->OREAD);
		if (fdi != nil) {
			s := fdi.gets('\n');
			if (s != nil) {
				(n,lst) := sys->tokenize(s,":\n");
				val := hd tl lst;
				setmystate(top,i,val);
			}
			return 0;
		}
	}
	dialog("cannot communicate with camera",0,-1,coords);
	return -1;
}

setmystate(top: ref Tk->Toplevel, i: int, val: string)
{
	abilities[i].read = val;
	if (abilities[i].dtype == LIST)
		tkcmd(top, "variable "+abilities[i].pname+" "+val);
	else if (abilities[i].dtype == MINMAX) {
		mul := getval(abilities[i].data, "mul");
		tkcmd(top, abilities[i].location+" set "+string((int val)/mul));
	}
	tkcmd(top, "update");
}

max(a,b: int): int
{
	if (a > b) return a;
	return b;
}

refreshfilelist(win: ref Tk->Toplevel, refresh: int): int
{
	if (refresh) {
		fd := sys->open(camerapath+"ctl",sys->OWRITE);
		if (fd == nil) {
			dialog("cannot communicate with camera",0,-1,coords);
			return -1;
		}
		else
			sys->fprint(fd, "refresh");
	}
	oldlist := filelist[:nofiles];
	for (i := 0; i < nofiles; i++) {
		si := string i;
		tk->cmd(win, "grid forget .fthumb.mb"+si+" .fthumb.p"+si);
		tk->cmd(win, "destroy .fthumb.mb"+si+" .fthumb.p"+si+" .mthumb"+si);
	}
	(dirs,n) := readdir->init(camerapath+"jpg", readdir->NAME);
	if (n == -1)
		return -1;
	nofiles = n;
	row := 0;
	col := 0;
	nocols := -1;
	w1 := int tkcmd(win, ".f.f1.c1 cget -width");
	w := 0;
	fnew = nil;
	for (i = 0; i < nofiles; i++) {
		filelist[i] = dirs[i].name;
		if (len filelist[i] > 3 && filelist[i][len filelist[i] - 4] == '.')
			filelist[i] = filelist[i][:len filelist[i]-4];
		
		isnew := 1;
		for (k := 0; k < len oldlist; k++) {
			if (filelist[i] == oldlist[k]) {
				isnew = 0;
				break;
			}
		}
		si := string i;
		tkcmd(win, "menubutton .fthumb.mb"+si+" -bg white " +
			"-text {"+filelist[i]+".jpg} -menu .mthumb"+si+tkfontf);
		if (isnew && refresh) {
			fnew = i :: fnew;
			tkcmd(win, ".fthumb.mb"+si+" configure -fg red");
		}
		thumbimg[i] = display.newimage(Rect((0,0),(90,90)),draw->RGB24,0,int 16rffcc00ff);
		e := tkcmd(win,"panel .fthumb.p"+si+" -borderwidth 2 -bg white"+
					" -height 90 -width 90 -relief raised");
		tk->putimage(win,".fthumb.p"+si, thumbimg[i],nil);
		tkcmd(win, "bind .fthumb.p"+si+" <Double-Button-1> {send butchan view "+si+"}");
		tkcmd(win, "bind .fthumb.p"+si+" <ButtonPress-1> {send butchan selectonly "+si+"}");
		tkcmd(win, "bind .fthumb.p"+si+" <ButtonPress-2> {send butchan select "+si+"}");
		tkcmd(win, "bind .fthumb.p"+si+" <ButtonPress-3> {send butchan menu "+si+" %X %Y}");
		thisw := int tkcmd(win, ".fthumb.mb"+si+" cget -width");
		w += max(94, thisw);
		if ((nocols == -1 && w >= w1-(col*2)) || col == nocols) {
			nocols = col;
			col = 0;
			row+=2;
			w = thisw;
		}
		if (col == 0)
			tkcmd(win, "grid rowconfigure .fthumb "+string (row+1)+
						" -minsize "+string (105 - 2*(3-ssize)));

		tkcmd(win, "grid .fthumb.mb"+si+" -row "+string row+" -column "+string col);
		tkcmd(win, "grid .fthumb.p"+si+" -row "+string (row+1)+" -column "+string col+" -sticky n");

		tkcmd(win, "menu .mthumb"+si+tkfont);
		for (k = nothumbs; k < len menu; k++)
			tkcmd(win, ".mthumb"+si+" add command -text {"+menu[k].text+"} " +
				"-command {send butchan "+menu[k].com+" "+si+"}");
		
		if (isloaded(filelist[i],THUMB) && usecache)
			loadthumbnail(win,i);
		col++;
	}
	if (row == 0)
		nocols = col;
	doselect(win);
	size := tkcmd(win, "grid size .fthumb");
	csize := int size[:isat(size, " ")];
	rsize := int size[isat(size, " ")+1:];
	if (csize > nocols)
		tkcmd(win, "grid columndelete .fthumb "+string nocols+" "+string csize);
	if (rsize > row+1)
		tkcmd(win, "grid rowdelete .fthumb "+string (row+2)+" "+string rsize);
	height := string (2 + int tkcmd(win, ".fthumb cget -height"));
	width := tkcmd(win, ".f.f1.c1 cget -width");
	colsize : int;
	if (nocols > 0) colsize = int width / nocols;
	else colsize = int width;
	for (i = 0; i < nocols; i++)
			tkcmd(win, "grid columnconfigure .fthumb "+string i+" -minsize "+string colsize);

	tkcmd(win, ".f.f1.c1 configure -scrollregion { 0 0 "+width+" "+height+"}");
	tkcmd(win, "update");
	return 0;
}

Mtype: adt {
	text, com: string;
};

menu := array[] of {
	Mtype ("Show Thumbnail", "show"),
	Mtype ("Download", "dnld"),
	Mtype ("View", "view"),
	Mtype ("Delete", "del"),
	Mtype ("Clear Cache", "unload"),
	Mtype ("Refresh Files", "refresh"),
};

tkcmd(top: ref Tk->Toplevel, cmd: string): string
{
	if (cmd[len cmd - 1] == '$')
		cmd = cmd[:len cmd - 1] + tkfontb;
	else if (cmd[len cmd - 1] == '@')
		cmd = cmd[:len cmd - 1] + tkfont;
	if (cmd[len cmd - 1] == '&')
		cmd = cmd[:len cmd - 1] + butheight+tkfont;
	
	e := tk->cmd(top, cmd);
	if (e != "" && e[0] == '!') sys->print("tk error: '%s': %s\n",cmd,e);
	return e;
}

loadnewthumb(top: ref Tk->Toplevel, ctlchan: chan of int)
{
	pid := sys->pctl(0,nil);
	spawn workingscreen2(coords,pid, ctlchan,0);
	getstore(top);
	for (tmp := fnew; tmp != nil; tmp = tl tmp)
		loadthumbnail(top,hd tmp);
	ctlchan <-= DONE;
}

loadthumb(top: ref Tk->Toplevel, i: int, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(coords,pid, ctlchan,0);
	if (i == -1) {
		for (k := 0; k < nofiles; k++)
			if (selected[k])
				if (loadthumbnail(top, k) != 0) break;
	}
	else loadthumbnail(top, i);
	ctlchan <-= DONE;
}

loadthumbnail(top: ref Tk->Toplevel, i: int): int
{
	fd : ref sys->FD;
	if (usecache && isloaded(filelist[i],THUMB))
		fd  = sys->open(tmppath+filelist[i]+"."+string THUMB+"~",sys->OREAD);
	else fd = sys->open(camerapath+"thumb/"+filelist[i]+".bit",sys->OREAD);
	if (fd == nil) {
		if (usecache && isloaded(filelist[i],THUMB)) {
			delloaded(filelist[i],THUMB);
			return loadthumbnail(top,i);
		}
		else dialog("cannot open "+filelist[i]+".bit",0,-1,coords);
		return -2;
	}
	image := display.readimage(fd);
	if (image == nil) {
		if (usecache && isloaded(filelist[i],THUMB)) {
			delloaded(filelist[i],THUMB);
			return loadthumbnail(top,i);
		}
		else dialog("Could not load thumbnail: "+filelist[i]+".jpg",0,-1,coords);
		return -1;
	}
	else {
		p := Point((90-image.r.max.x)/2,(90-image.r.max.y)/2);
		thumbimg[i].draw(image.r.addpt(p), image,nil,(0,0));
		si := string i;
		tkcmd(top,".fthumb.p"+si+" dirty");
		fd = nil;
		n := -1;
		if (usecache) {
			fd  = sys->create(tmppath+filelist[i]+"."+string THUMB+"~",sys->OWRITE,8r666);
			n = display.writeimage(fd, image);
		}
		x := int tkcmd(top, ".fthumb.mb"+string i+" cget -actx");
		y := int tkcmd(top, ".fthumb.mb"+string i+" cget -acty");
		h := int tkcmd(top, ".fthumb.mb"+string i+" cget -height");
		x1 := int tkcmd(top, ".fthumb cget -actx");
		y1 := int tkcmd(top, ".fthumb cget -acty");
		tkcmd(top, ".f.f1.c1 see "+string (x-x1)+" " +string (y-y1)+
			" "+string (x-x1+90)+" " +string (y-y1+h+102)+"; update");
		if (!usecache || n == 0) imgloaded = (filelist[i],THUMB) :: imgloaded;
	}
	return 0;
}

isloaded(name: string, ftype: int): int
{
	tmp := imgloaded;
	while (tmp != nil) {
		ic := hd tmp;
		if (ic.name == name && ic.imgtype == ftype) return 1;
		tmp = tl tmp;
	}
	return 0;
}

delloaded(name: string, ftype: int)
{
	tmp :  list of Imgloaded;
	tmp = nil;
	while (imgloaded != nil) {
		ic := hd imgloaded;
		if (ic.name != name || ic.imgtype != ftype)
			tmp = ic :: tmp;
		else sys->remove(tmppath+ic.name+"."+string ic.imgtype+"~");
		imgloaded = tl imgloaded;
	}
	imgloaded = tmp;
}

dialog(msg: string, diagtype, img: int, r: Rect): int
{
	if (diagtype == 2)
		diagtype = 0;
	else 
		working = 0;
	tmpimg : ref draw->Image;
	out := 0;
	title := "Dialog";
	if (diagtype == 0) title = "Alert!";
	(win, titlebar) := tkclient->toplevel(context, "" , title, Tkclient->Appl);
	diagchan := chan of string;
	tk->namechan(win, diagchan, "diagchan");
	tkcmd(win, "frame .f");
	tkcmd(win, "label .f.l -text {"+msg+"}"+tkfont);
	tkcmd(win, "button .f.bo -text { ok } -command {send diagchan ok} "+butheight+tkfont);
	tkcmd(win, "button .f.bc -text {cancel} -command {send diagchan cancel}"+butheight+tkfont);
	if (img >= 0 && isloaded(filelist[img], THUMB) && usecache) {
		fd := sys->open(tmppath+filelist[img]+"."+string THUMB+"~", sys->OREAD);
		if (fd != nil) {
			tmpimg = display.readimage(fd);
			tkcmd(win,"panel .f.p -height "+string tmpimg.r.max.y+
				" -width "+string tmpimg.r.max.x+" -borderwidth 2 -relief raised");
			tk->putimage(win,".f.p", tmpimg, nil);
			tkcmd(win, "grid .f.p -row 1 -column 0 -columnspan 2 -padx 5 -pady 5");
		}
	}
	tkcmd(win, "grid .f.l -row 0 -column 0 -columnspan 2 -padx 10 -pady 5");
	if (diagtype == 1) {
		tkcmd(win, "grid .f.bo -row 2 -column 0 -padx 5 -pady 5");
		tkcmd(win, "grid .f.bc -row 2 -column 1 -padx 5 -pady 5");
	}
	else 	tkcmd(win, "grid .f.bo -row 2 -column 0 -columnspan 2 -padx 5 -pady 5");
	if (!r.eq(nilrect))
		centrewin(win, r, 1);
	else
		tkcmd(win, "pack .f; focus .; update");
	tkclient->onscreen(win, "exact");
	tkclient->startinput(win, "kbd"::"ptr"::nil);
	main: for (;;) {
		alt {
		s := <-win.ctxt.kbd =>
			tk->keyboard(win, s);
		s := <-win.ctxt.ptr =>
			tk->pointer(win, *s);
		inp := <-diagchan =>
			if (inp == "ok") {
				out = 1;
				break main;
			}
			if (inp == "cancel")
				break main;

		title = <-win.ctxt.ctl or
		title = <-win.wreq or
		title = <-titlebar =>
			if (title == "exit")
				break main;
			else
				tkclient->wmctl(win, title);
		}
	}
	return out;
}	

snapscr := array[] of {
	"label .f.fsnap.ltime -text {Date and Time} $",
	"label .f.fsnap.lstore -text {Memory Status} $",
	"label .f.fsnap.lzpos -text {Zoom} $",
	"label .f.fsnap.lssiz -text {Resolution} $",
	"label .f.fsnap.lscpn -text {Compression} $",
	"grid .f.fsnap.ltime -row 0 -column 0 -sticky sw",
	"grid .f.fsnap.lstore -row 0 -column 1 -sticky sw",
	"grid .f.fsnap.lscpn -row 2 -column 0 -sticky sw",
	"grid .f.fsnap.lssiz -row 2 -column 1 -sticky sw",
	"grid .f.fsnap.lzpos -row 2 -column 2 -sticky sw",

	"grid .f.fsnap.ftime -row 1 -column 0  -sticky nsew",
	"grid .f.fsnap.fstore -row 1 -column 1  -sticky nsew",
	"grid .f.fsnap.fsettings -row 1 -column 2  -sticky nsew",
	"grid .f.fsnap.fscpn -row 3 -column 0  -sticky nsew",
	"grid .f.fsnap.fssiz -row 3 -column 1 -sticky nsew",
	"grid .f.fsnap.fzpos -row 3 -column 2  -sticky nsew",
	"grid .f.fsnap.b -row 4 -column 0 -columnspan 3",
	"grid rowconfigure .f.fsnap 0 -minsize 30",
	"grid rowconfigure .f.fsnap 2 -minsize 30",
	"grid rowconfigure .f.fsnap 4 -minsize 30",

	"update",
};

mainscreen(win: ref Tk->Toplevel, opt: int, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(coords, pid, ctlchan, opt);
	if (opt == 1) {
		for (tk1 := 0; tk1 < len snapscr; tk1++)
			tkcmd(win, snapscr[tk1]);

		gettime(win);
		if (samedate(win)) tkcmd(win, "variable time 1; update");
	}
	getstore(win);
	lst := getabl("scpn") :: getabl("ssiz") :: getabl("zpos") :: nil;
	if (getstate(win, hd tl tl lst) == 0);
		if (getstate(win, hd tl lst) == 0);
			getstate(win, hd lst);
	if (opt == 1) {
		addtoplevel(win, "", lst, -1);
		height := tkcmd(win, ".f.fsnap cget -height");
		width := tkcmd(win, ".f.fsnap cget -width");
#		tkcmd(win, ".f.f2.c1 configure -scrollregion { 0 0 "+width+" "+height+"}");
#		tkcmd(win, ".f.f2.c1 configure -height "+height+"}");
	}
	ctlchan <-= DONE;
}

kill(pid: int)
{	
	pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE);
	if (pctl != nil)
		sys->write(pctl, array of byte "kill", len "kill");
}

gettime(win: ref Tk->Toplevel)
{
	tkcmd(win,".f.fsnap.ftime.l3 configure -text {}"+tkfont);
	tkcmd(win,".f.fsnap.ftime.l4 configure -text {}"+tkfont);
	fdi := bufio->open(camerapath+"date",sys->OREAD);
	if (fdi != nil) {
		s := fdi.gets('\n');
		if (s != nil) {
			if (s[len s - 1] == '\n') s = s[:len s - 1];
			tm := daytime->local(daytime->now());
			time := sys->sprint("%d/%d/%d %d:%d:%d", tm.mon+1, tm.mday, tm.year-100,
							tm.hour,tm.min,tm.sec);
			ltime = addzeros(time);
			ctime = addzeros(s[len "date is ":]);
			tk->cmd(win,".f.fsnap.ftime.l3 configure -text {"+ltime+"}");
			tk->cmd(win,".f.fsnap.ftime.l4 configure -text {"+ctime+"}");
		}
	}
	if (len ltime < 16)
		ltime = "??/??/?? ??:??:??";
	if (len ctime < 16)
		ctime = "??/??/?? ??:??:??";
	tkcmd(win, "update");
}

addzeros(s: string): string
{
	s[len s] = ' ';
	rs := "";
	start := 0;
	isnum := 0;
	for (i := 0; i < len s; i++) {
		if (s[i] < '0' || s[i] > '9') {
			if (isnum && i - start < 2) rs[len rs] = '0';
			rs += s[start:i+1];
			start = i+1;
			isnum = 0;
		}
		else isnum = 1;
	}
	i = len rs - 1;
	while (i >= 0 && rs[i] == ' ') i--;
	return rs[:i+1];
}	

samedate(win: ref Tk->Toplevel): int
{
	s1 := tkcmd(win, ".f.fsnap.ftime.l3 cget -text");
	s2 := tkcmd(win, ".f.fsnap.ftime.l4 cget -text");
	if (s1 == "" || s1 == "") return 0;
	if (s1[:len s1 - 3] == s2[:len s2 - 3]) return 1;
	return 0;
}

settime()
{
	tm := daytime->local(daytime->now());
	fd := sys->open(camerapath+"date", sys->OWRITE);
	if (fd != nil) {
		sys->fprint(fd, "%s", addzeros(sys->sprint("%d/%d/%d %d:%d:%d"
			,tm.mon+1, tm.mday, tm.year-100, tm.hour,tm.min,tm.sec)));
	}
}

getstore(win: ref Tk->Toplevel)
{
	fdi := bufio->open(camerapath+"storage",sys->OREAD);
	if (fdi != nil) {
		for(i := 0; i < 3; i++) {
			s := fdi.gets('\n');
			if (s == nil) break;
			if (i > 0) {
				(n,lst) := sys->tokenize(s,"\t\n:");
				val := string int hd tl lst;
				if (i == 2 && val == "0") 
					tkcmd(win, ".f.fsnap.b configure -state disabled");
				else tkcmd(win, ".f.fsnap.b configure -state normal");
				tkcmd(win,".f.fsnap.fstore.l"+string (2+i)+" configure -text {"+val+"  }");
			}
		}
		tkcmd(win, "update");	
	}
}

contains(s: string, test: string): int
{
	num :=0;
	if (len test > len s) return 0;
	for (i := 0; i < (1 + (len s) - (len test)); i++) {
		if (test == s[i:i+len test]) num++;
	}
	return num;
}

multidownload()
{
	getpath := selectfile->filename(context,
							display.image,
							"Multiple download to directory...", 
							nil,
							lastpath);
	if (getpath == "" || getpath[0] != '/' || getpath[len getpath - 1] != '/')
		return;
	s := "";
	for (k := 0; k < nofiles; k++) {
		if (selected[k]) {
			e := dnld(k,getpath);
			if (e != 1) 
				s += filelist[k]+".jpg ";
			if (e == 3) {
				s += "cancelled\n";
				break;
			}
			else if (e == 0)
				s += "failed\n";
			working = 0;
		}
	}
	if (s != "") s = ":\n\n"+s;
	dialog("Multiple download complete"+s,0,-1,coords);
}

downloading := "";

dnld(i: int, path: string): int
{
	ctlchan := chan of int;
	ctlchans := chan of string;
	chanout := chan of string;
	spawn downloadscreen(coords, i, ctlchans, chanout);
	spawn download(i,path,ctlchan, ctlchans, chanout);
	pid := <-ctlchan;
	alt {
		s := <-ctlchans =>
			chanout <-= "!done!";
			if (s == "kill") {
				if (downloading != "") {
					(n,lst) := sys->tokenize(downloading, " \t\n");
					for(;lst != nil; lst = tl lst)
						sys->remove(hd lst);
				}
				kill(pid);
				return 3;
			}
			else return dnld(i, "!"+s);
		e := <-ctlchan =>
			chanout <-= "!show!";
			chanout <-= "!done!";
			return e;
	}
	return 0;
}

filelenrefresh(filename: string): int
{
	fd := sys->open(camerapath+"ctl",sys->OWRITE);
	if (fd != nil) {
		sys->fprint(fd, "refresh");
		(n, dir) := sys->stat(filename);
		if (n == -1)
			return -1;
		return int dir.length;
	}
	return -1;
}

testfilesize(filename: string): int
{
	e := filelenrefresh(filename);
	if (e == 0) {
		e2 := dialog("Camera is still processing image\nwait until ready?",1,-1,coords);
		if (e2 == 0)
			return 0;
		ctlchan := chan of int;
		spawn waittilready(filename, ctlchan);
		e3 := <- ctlchan;
		working = 0;
		if (e3 == KILLED)
			return 0;
		return testfilesize(filename);
	}
	else return e;
}

waittilready(filename: string, ctlchan: chan of int)
{
	pid := sys->pctl(0, nil);
	spawn workingscreen2(coords,pid,ctlchan,0);
	for (;;) {
		if (filelenrefresh(filename) != 0)
			break;
		sys->sleep(2000);
	}
	ctlchan <-= DONE;
}

download(i: int, path: string, ctlchan: chan of int, ctlchans, chanout: chan of string)
{
	ctlchan <-= sys->pctl(0, nil);
	downloading = "";
	savename : string;
	if (path == "") {
		savename = selectfile->filename(context,
								display.image,
								"Save "+filelist[i]+".jpg to directory...", 
								"*.jpg" :: "*.jpeg" :: nil,
								lastpath);
		if (savename == "" || savename[0] != '/') {
			ctlchan <-= 0;
			return;
		}
	}
	else savename = path;

	# Used when retrying due to cache copy failing
	if (savename[0] == '!') {
		delloaded(filelist[i],JPG);
		savename = savename[1:];
		path = "";
	}
	confirm := 1;
	# Don't confirm overwrite
	if (savename[0] == '$') {
		confirm = 0;
		savename = savename[1:];
	}

	if (savename[len savename - 1] == '/')
		savename += filelist[i]+".jpg";

	if (!hasext(savename, ".jpg"))
		savename += ".jpg";

	p := isat2(savename,"/");
	lastpath = savename[:p+1];

	filename := camerapath+"jpg/"+filelist[i]+".jpg";
	filesize := testfilesize(filename);
	cached := 0;
	if (filesize > 0 && isloaded(filelist[i],JPG) && usecache) {
		cachefilename := tmppath+filelist[i]+"."+string JPG+"~";
		if (testfilesize(cachefilename) == filesize) {
			cached = 1;
			filename = cachefilename;
		}
		else delloaded(filelist[i],JPG);
	}
	fd := sys->open(filename, sys->OREAD);
	if (filesize < 1 || fd == nil) {
		ctlchan <-= -1;
		return;
	 }

	read := 0;
	cancel : int;
	buf : array of byte;
	fd2, fd3 : ref sys->FD = nil;
	cachename := tmppath+filelist[i]+"."+string JPG+"~";
	if (confirm) (fd2, cancel) = create(savename, coords);
	else fd2 = sys->create(savename,sys->OWRITE, 8r666);
	if (fd2 == nil) {
		ctlchan <-= cancel;
		return;
	}
	if (usecache && !cached)
		fd3 = sys->create(cachename,sys->OWRITE,8r666);
	chanout <-= "!show!";
	chanout <-= "l2 Downloading...";
	chanout <-= "pc 0";
	n : int;
	downloading = savename;
	if (fd3 != nil)
		downloading += " "+cachename;
	loop: for(;;) {
		rlen := 8192;
		if (read + rlen >= filesize) rlen = filesize - read;
		buf = array[rlen] of byte;
		n = sys->read(fd,buf,len buf);
		read += n;
		sout := "pc "+string ( (100*read)/filesize);
		chanout <-= sout;
		if (n < 1) break loop;
		written := 0;
		while (written < n) {
			n2 := sys->write(fd2,buf,n);
			if (n2 < 1) break loop;
			if (fd3 != nil) sys->write(fd3,buf,n);
			written += n2;
		}
	}
	chanout <-= "pc 100";
	downloading = "";
	fd = nil;
	fd2 = nil;
	if (read < filesize || n == -1) {
		if (cached) {
			ctlchans <-= savename;
			return;
		}
		sys->remove(savename);
		sys->remove(cachename);
		if (path == "")
			dialog(sys->sprint("Download Failed: %s.jpg\nread %d of %d bytes\n",
					filelist[i],read,filesize), 0, i,coords);
		ctlchan <-= 0;
		return;
	}
	
	# save it in cache 
	if (usecache)
		imgloaded = (filelist[i],JPG) :: imgloaded;
	if (path == "") dialog(filelist[i]+".jpg downloaded",0,i,coords);
	ctlchan <-= 1;
}

downloadscr := array[] of {
	"frame .f -borderwidth 2 -relief raised",
	"label .f.l1 -text { } @",
	"label .f.l2 -text {Waiting...} @",
	"button .f.b -text {Cancel} -command {send ctlchans kill} &",
	"grid .f.l1 -row 0 -column 0 -columnspan 2 -pady 5",
	"grid .f.l2 -row 2 -column 1 -sticky w -padx 10",
	"grid .f.p -row 3 -column 1 -columnspan 1 -padx 10",
	"grid .f.b -row 4 -column 0 -pady 5 -columnspan 2",
};

downloadscreen(r: Rect, i: int, ctlchans, chanin: chan of string)
{
	working = 1;
	<- chanin;
	(top, nil) := tkclient->toplevel(context,"", nil, tkclient->Plain);
	progr := Rect((0,0),(100,15));
	imgbg := display.newimage(progr,draw->CMAP8,1,draw->Black);
	black := display.newimage(progr,draw->CMAP8,1,draw->Black);
	white := display.newimage(progr,draw->CMAP8,1,draw->White);
	imgfg := display.newimage(progr,draw->CMAP8,1,draw->Blue);
	tkcmd(top, "panel .f.p -width 100 -height 15 -bg white -borderwidth 2 - relief raised");
	tk->putimage(top, ".f.p",imgbg,nil);
	tk->namechan(top, ctlchans, "ctlchans");
	for (tk1 := 0; tk1 < len downloadscr; tk1++)
		tkcmd(top, downloadscr[tk1]);
	tmpimg : ref Image = nil;
	if (i >= 0 && isloaded(filelist[i], THUMB) && usecache)
		tmpimg = display.open(tmppath+filelist[i]+"."+string THUMB+"~");
	if (tmpimg == nil)
		tmpimg = procimg;
	if (tmpimg != nil) {
		w := tmpimg.r.dx();
		h := tmpimg.r.dy();
		tkcmd(top, "panel .f.p2 -width "+string w+" -height "+string h+
					" -borderwidth 2 -relief raised");
		tk->putimage(top, ".f.p2", tmpimg, nil);
		tkcmd(top, "grid .f.p2 -row 2 -column 0 -rowspan 2 -sticky e");
		tkcmd(top, "grid columnconfigure .f 0 -minsize "+string (w + 14));
	}

	tkcmd(top, ".f.l1 configure -text {"+filelist[i]+".jpg}");
	centrewin(top,r,1);
	oldcoords := coords;
	tkclient->onscreen(top, "exact");
	tkclient->startinput(top, "kbd"::"ptr"::nil);

	main: for (;;) {
		alt {
		s := <-top.ctxt.kbd =>
			tk->keyboard(top, s);
		s := <-top.ctxt.ptr =>
			tk->pointer(top, *s);
		text := <-chanin =>
			if (!oldcoords.eq(coords)) {
				centrewin(top,coords,0);
				oldcoords = coords;
			}
			if (text == "!done!") break main;
			if (text[:2] == "pc") {
				val := int text[3:];
				imgbg.draw(((0,0),(val,15)), imgfg,nil,(0,0));
				if (val != 100)
					imgbg.draw(((val+1,0),(100,15)), black,nil,(0,0));
				imgbg.text((42,1),white,(0,0),font, text[3:]+"%");
				tkcmd(top,".f.p dirty; update");
			}
			else if (text[:2] == "l2")
				tkcmd(top, ".f.l2 configure -text {"+text[3:]+"}; update");
		}
	}
	working = 0;
}

centrewin(top: ref Tk->Toplevel, r: Rect, first: int)
{
	s := "";
	if (first)
		s = "pack .f;";
	w := int tkcmd(top, ".f cget -width");
	h := int tkcmd(top, ".f cget -height");
	tmp := tk->cmd(top, ".Wm_t cget -height");
	if (tmp != "" && tmp[0] != '!') {
		h += int tmp;
		s += "focus .;";
	}
	px := r.min.x + ((r.max.x - r.min.x - w) / 2);
	py := r.min.y + ((r.max.y - r.min.y - h) / 2);
	tkcmd(top, ". configure -x "+string px+" -y "+string py);
	tkcmd(top, s+"raise .; update");
}

workingscr2 := array[] of {
	"frame .f -borderwidth 2 -relief raised",
	"label .f.l3 -text { } -width 220 -height 2",
	"label .f.l -text {Please Wait} @",
	"label .f.l2 -text {|} -width 20 @",
	"button .f.b -text {Cancel} -command {send chanin kill} &",
	"grid .f.l -row 1 -column 0 -sticky e",
	"grid .f.l2 -row 1 -column 1 -sticky w",
	"grid .f.b -pady 5 -row 3 -column 0 -columnspan 2",
	"grid .f.l3 -row 4 -column 0 -columnspan 2",
	"grid rowconfigure .f 1 -minsize 80",
};

workingscreen2(r : Rect, pid: int, ctlchan: chan of int, loading: int)
{
	(top, nil) := tkclient->toplevel(context,"",nil, tkclient->Plain);
	chanin := chan of string;
	tk->namechan(top, chanin, "chanin");
	for (tk1 := 0; tk1 < len workingscr2; tk1++)
		tkcmd(top, workingscr2[tk1]);

	if (loading) {
#		loadimg := display.open("camload.bit");
		if (loadimg != nil) {
			w := loadimg.r.dx();
			h := loadimg.r.dy();
			tkcmd(top, "panel .f.p -width "+string w+" -height "+string h+
						" -borderwidth 2 -relief raised");
			tk->putimage(top, ".f.p", loadimg, nil);
			tkcmd(top, "grid .f.p -row 2 -column 0 -columnspan 2 -pady 5 -padx 20");
			tkcmd(top, "grid forget .f.l .f.l2; grid rowconfigure .f 1 -minsize 20");
		}
	}
	else {
		if (procimg != nil) {
			w := procimg.r.dx();
			h := procimg.r.dy();
			tkcmd(top, "panel .f.p -width "+string w+" -height "+string h+
						" -borderwidth 2 -relief raised");
			tk->putimage(top, ".f.p", procimg, nil);
			tkcmd(top, "grid .f.p -row 2 -column 0 -columnspan 2");
			tkcmd(top, "grid rowconfigure .f 1 -minsize 30");
			tkcmd(top, "grid rowconfigure .f 2 -minsize 50");
		}
	}

	centrewin(top,r,1);
	spawn workingupdate(top,chanin);
	tkclient->onscreen(top, "exact");
	tkclient->startinput(top, "kbd"::"ptr"::nil);

	main: for (;;) {
		alt {
		s := <-top.ctxt.kbd =>
			tk->keyboard(top, s);
		s := <-top.ctxt.ptr =>
			tk->pointer(top, *s);
		inp := <-chanin =>
			if (inp == "done") break main;
			if (inp == "kill") {
				working = 0;
				if (pid != -1) kill(pid);
				ctlchan <-= KILLED;
				<-chanin;
				break main;
			}
		}
	}
}

workingupdate(top: ref Tk->Toplevel, chanout: chan of string)
{
	show := array[] of { "/", "-", "\\\\", "|", };
	if (working) {
		chanout <-= "done";
		return;
	}
	working = 1;
	oldcoords := coords;
	hidden := 0;
	loop: for(;;) {
		for (i := 0; i < 4; i++) {
			sys->sleep(100);
			tkcmd(top, ".f.l2 configure -text {"+show[i]+"}; update");
			if (!working) break loop;
			if (!oldcoords.eq(coords)) {
				centrewin(top, coords,0);
				oldcoords = coords;
			}
		}
	}
	chanout <-= "done";
}

scrollx := 0;
scrolly := 0;

resizemain(top: ref Tk->Toplevel, init: int)
{
	h, w: int;
	if (init) {
		growheight(top, 4000);
		h = int tkcmd(top, ".f.fsnap cget -height") +
			int tkcmd(top, ".Wm_t cget -height") +
			2 * (124 - (5*(3-ssize)));
		if (h > display.image.r.dy())
			h = display.image.r.dy();
		w = display.image.r.dx();
	}
	else {
		r := tk->rect(top, ".", 0);
		h = r.dy();
		w = r.dx();	
	}

	ht := int tkcmd(top, ".Wm_t cget -height");

	hf := int tkcmd(top, ".f cget -height");
	wf := int tkcmd(top, ".f cget -width");
	wsb := int tkcmd(top, ".f.f1.sb1 cget -width");

	growwidth(top, w - 4);
	ws := int tkcmd(top, ".f.fsnap cget -width");
	if (w > ws + 4)
		w = ws + 4;
	shrinkwidth(top,w - 4);
	ws = int tkcmd(top, ".f.fsnap cget -width");
	if (w < ws || init)
		w = ws + 4;
		
	hmax := ((3*(h - ht))/5) - 4;
	growheight(top, hmax);
	shrinkheight(top, hmax);
	hs := int tkcmd(top, ".f.fsnap cget -height");

	hmb := int tkcmd(top, ".f.fsnap.fsettings.mb cget -height");
	if (h < ht+hs + 107 + hmb) h = ht+hs+107 + hmb;

#	hc2 = int tkcmd(top, ".f.fsnap cget -height");
	wc2 := int tkcmd(top, ".f.fsnap cget -width");

	hc1 := h - ht - hs - 4;
	wc1 := w-wsb-4;
#	wc1 = wc2 - wsb;
	tkcmd(top, ".f.f1.c1 configure -height "+string hc1+" -width "+string wc1);
#	tkcmd(top, ".f.f2.c1 configure -height "+string hc2+" -width "+string wc2);
	if (w < wc2 + 4)
		w = wc2 + 4;
	ws = int tkcmd(top, ".f.fsnap cget -width");
	hs = int tkcmd(top, ".f.fsnap cget -height");
		
	tkcmd(top, ". configure -height "+string h+" -width "+string w+"; update");
	refreshfilelist(top, 0);
}

growwidth(top: ref Tk->Toplevel, wc2: int)
{
	ws := int tkcmd(top, ".f.fsnap cget -width");
	if (wc2 > ws && reducew[2]) {
		tkcmd(top, ".f.fsnap.ftime.l1 configure -text {Local:}");
		tkcmd(top, ".f.fsnap.ftime.l2 configure -text {Camera:}");
		tkcmd(top, ".f.fsnap.ftime.cb configure -text {Set to local time}");
		reducew[2] = 0;
	}
	ws = int tkcmd(top, ".f.fsnap cget -width");
	if (wc2 > ws && reducew[1]) {
		tkcmd(top, ".f.fsnap.ftime.l3 configure -text {"+ltime+"}");
		tkcmd(top, ".f.fsnap.ftime.l4 configure -text {"+ctime+"}");
		tkcmd(top, ".f.fsnap.ftime.cb configure -text {Set camera to local time}");
		reducew[1] = 0;
	}
	ws = int tkcmd(top, ".f.fsnap cget -width");
	if (wc2 > ws && reducew[0]) {
		tkcmd(top, ".f.fsnap.fstore.l1 configure -text {  Photos taken:}");
		reducew[0] = 0;
	}
	ws = int tkcmd(top, ".f.fsnap cget -width");
	if (wc2 > ws) {
		wfs += wc2 - ws;
		if (wfs > 125-(20*(3-ssize))) wfs = 125-(20*(3-ssize));
		tkcmd(top, "grid columnconfigure .f.fsnap 2 -minsize "+string wfs);
	}
}

growheight(top: ref Tk->Toplevel, hc2: int)
{
	hs := int tkcmd(top, ".f.fsnap cget -height");
	if (hc2 > hs) {
		tk->cmd(top, "grid .f.fsnap.fsettings.mb2 -row 2 -column 0 -sticky ew");
		tk->cmd(top, "grid .f.fsnap.ftime.cb -row 2 -column 0 -columnspan 2");
		tk->cmd(top, "grid .f.fsnap.ftime.b -row 3 -column 0 -columnspan 2");
	}
	hs = int tkcmd(top, ".f.fsnap cget -height");
	if (hc2 > hs) {
		hsc := int tkcmd(top, ".f.fsnap.fzpos.sc cget -height");
		hsc += hc2 - hs;
		if (hsc > 88-(10*(3-ssize))) hsc = 88-(10*(3-ssize));
		tkcmd(top, ".f.fsnap.fzpos.sc configure -height "+string hsc);
	}
	hs = int tkcmd(top, ".f.fsnap cget -height");
	if (hc2 > hs) {
		hfs += hc2 - hs;
		if (hfs > 30 - (5*(3-ssize))) hfs = 30- (5*(3-ssize));
		tkcmd(top, "grid rowconfigure .f.fsnap 0 -minsize "+string hfs);
		tkcmd(top, "grid rowconfigure .f.fsnap 2 -minsize "+string hfs);
		tkcmd(top, "grid rowconfigure .f.fsnap 4 -minsize "+string hfs);
	}
}

shrinkheight(top: ref Tk->Toplevel, hc2: int)
{
	hs := int tkcmd(top, ".f.fsnap cget -height");
	if (hc2 < hs) {
		hfs -= hs - hc2;
		if (hfs < 15) hfs = 15;
		tkcmd(top, "grid rowconfigure .f.fsnap 0 -minsize "+string hfs);
		tkcmd(top, "grid rowconfigure .f.fsnap 2 -minsize "+string hfs);
		tkcmd(top, "grid rowconfigure .f.fsnap 4 -minsize "+string hfs);
	}
	hs = int tkcmd(top, ".f.fsnap cget -height");
	if (hc2 < hs) {
		hsc := int tkcmd(top, ".f.fsnap.fzpos.sc cget -height");
		hsc -= hs - hc2;
		if (hsc < 55-(5*(3-ssize))) hsc = 55-(5*(3-ssize));
		tkcmd(top, ".f.fsnap.fzpos.sc configure -height "+string hsc);
	}
	hs = int tkcmd(top, ".f.fsnap cget -height");
	if (hc2 < hs) {
		tk->cmd(top, "grid forget .f.fsnap.fsettings.mb2");
		tk->cmd(top, "grid forget .f.fsnap.ftime.cb");
		tk->cmd(top, "grid forget .f.fsnap.ftime.b");
	}
}

shrinkwidth(top: ref Tk->Toplevel, wc2: int)
{
	ws := int tkcmd(top, ".f.fsnap cget -width");
	wib := int tkcmd(top, ".f.fsnap.fsettings.b cget -width");
	if (wc2 < ws) {
		diff := ws - wc2;
		wfs -= diff;
		if (wfs < wib) wfs = wib;
		tkcmd(top, "grid columnconfigure .f.fsnap 2 -minsize "+string wfs);
	}
	ws = int tkcmd(top, ".f.fsnap cget -width");
	if (wc2 < ws) {
		tkcmd(top, ".f.fsnap.fstore.l1 configure -text {  Taken:}");
		reducew[0] = 1;
	}
	ws = int tkcmd(top, ".f.fsnap cget -width");
	if (wc2 < ws) {
		tkcmd(top, ".f.fsnap.ftime.l3 configure -text {"+ltime[len ltime - 8:]+"}");
		tkcmd(top, ".f.fsnap.ftime.l4 configure -text {"+ctime[len ctime - 8:]+"}");
		tkcmd(top, ".f.fsnap.ftime.cb configure -text {Set to local time}");
		reducew[1] = 1;
	}
	ws = int tkcmd(top, ".f.fsnap cget -width");
	if (wc2 < ws) {
		tkcmd(top, ".f.fsnap.ftime.l1 configure -text {C:}");
		tkcmd(top, ".f.fsnap.ftime.l2 configure -text {}");
		tkcmd(top, ".f.fsnap.ftime.l3 configure -text {"+ctime[len ctime - 17:len ctime - 8]+"}");
		tkcmd(top, ".f.fsnap.ftime.cb configure -text {Set local}");
		reducew[2] = 1;
	}
	ws = int tkcmd(top, ".f.fsnap cget -width");
	if (wc2 > ws) {
		wfs = 125-(20*(3-ssize));
		tkcmd(top, "grid columnconfigure .f.fsnap 2 -minsize "+string wfs);
	}
}

ltime, ctime: string;
wfs := 150;
hfs := 30;
reducew := array[10] of { * => 0 };

getcoords(top: ref Tk->Toplevel): Rect
{
	h := int tkcmd(top, ". cget -height");
	w := int tkcmd(top, ". cget -width");
	x := int tkcmd(top, ". cget -actx");
	y := int tkcmd(top, ". cget -acty");
	r := Rect((x,y),(x+w,y+h));
	return r;
}

viewscr := array[] of {
	"frame .f -bg",
	"canvas .f.c -yscrollcommand {.f.sy set} -xscrollcommand {.f.sx set} -height 300 -width 500",
	"scrollbar .f.sx -command {.f.c xview} -orient horizontal",
	"scrollbar .f.sy -command {.f.c yview}",
	"grid .f.c -row 0 -column 0",
	"grid .f.sy -row 0 -column 1 -sticky ns",
	"grid .f.sx -row 1 -column 0 -sticky ew",
	"bind .Wm_t <ButtonPress-1> +{focus .}",
	"bind .Wm_t.title <ButtonPress-1> +{focus .}",
	"pack propagate . 0",
	"menu .m @",
	".m add command -text {Save As...}",
	".m add separator",
	".m add command -text {bit} -command {send butchan save bit}",
	".m add command -text {jpeg} -command {send butchan save jpg}",

};

resizeview(top: ref Tk->Toplevel, wp,hp: int)
{
	w := int tkcmd(top, ". cget -width");
	h := int tkcmd(top, ". cget -height");
	hs := int tkcmd(top, ".f.sx cget -height");
	ws := int tkcmd(top, ".f.sy cget -width");
	ht := int tkcmd(top, ".Wm_t cget -height");
	wc := w - ws - 4;
	hc := h - hs - ht - 6;
	wpc := wc - wp;
	hpc := hc - hp;
	if (wpc > 0) {
		wc -= wpc;
		w -= wpc;
	}
	if (hpc > 0) {
		hc -= hpc;
		h -= hpc;
	}
	tkcmd(top, ". configure -height "+string h+" -width "+string w);
	tkcmd(top, ".f.c configure -height "+string hc+" -width "+string wc);
	tkcmd(top, "update");
}

multiview()
{
	s := "";
	for (k := 0; k < nofiles; k++) {
		if (selected[k]) {
			e := vw(k);
			if (e != 0)
				s += filelist[k]+".jpg ";
			if (e == 3) {
				s += "cancelled\n";
				break;
			}
			else if (e == -1)
				s += "failed\n";
		}
	}
	if (s != "")
		dialog("Multiple view complete:\n\n"+s,0,-1,coords);
}

vw(i: int): int
{
	# raise window if it is already open
	low := toplevels;
	for(; low != nil; low = tl low) {
		(tplvl, name, nil, nil) := hd low;
		if (filelist[i]+".jpg" == name) {
			tkcmd(tplvl, "raise .; focus .; update");
			return 0;
		}
	}

	ctlchan := chan of int;
	ctlchans := chan of string;
	chanout := chan of string;
	chanin := chan of string;
	spawn downloadscreen(coords, i, ctlchans, chanout);
	chanout <-= "!show!";
	spawn view(i,ctlchan, chanin, chanout);
	pid := <-ctlchan;
	killed := 0;
	for (;;) alt {
		s := <-ctlchans =>
			if (s == "kill") {
				chanin <-= "kill";
				killed = 1;
			}
		e := <-ctlchan =>
			chanout <-= "!done!";
			if (killed)
				return 3;
			if (e == -1)
				dialog(sys->sprint("Cannot read file: %s.jpg\n%r",filelist[i]),0,i,coords);
			if (e == -2) return vw(i);
			else return e;
	}
	return 0;
}

view(i: int, ctlchan: chan of int, chanin, chanout: chan of string)
{
	ctlchan <-= sys->pctl(0, nil);
	titlename := filelist[i]+".jpg";

	filename := camerapath+"jpg/"+filelist[i]+".jpg";
	filesize := testfilesize(filename);
	cached := 0;
	if (filesize > 0 && isloaded(filelist[i],JPG) && usecache) {
		cachefilename := tmppath+filelist[i]+"."+string JPG+"~";
		if (testfilesize(cachefilename) == filesize) {
			cached = 1;
			filename = cachefilename;
		}
		else delloaded(filelist[i],JPG);
	}
	if (filesize < 1) {
		ctlchan <-= -1;
		return;
	 }

	img: ref Image;
	cachepath := "";
	if (!cached && usecache)
		cachepath = tmppath+filelist[i]+"."+string JPG+"~";
	img = readjpg->jpg2img(filename, cachepath, chanin, chanout);
	if(img == nil) {
		if (cachepath != nil)
			sys->remove(cachepath);
		if (!cached)
			ctlchan <-= -1;
		else {
			delloaded(filelist[i], JPG);
			ctlchan <-= -2;
		}
		return;
	}
	else {
		chanout <-= "l2 Displaying";
		if (cachepath != "")
			imgloaded = (filelist[i], JPG) :: imgloaded;
		(t, titlechan) := tkclient->toplevel(context, "", titlename, Tkclient->Appl);
		butchan := chan of string;
		tk->namechan(t, butchan, "butchan");
		tkcmd(t, "focus .Wm_t; update");
		for (tk1 := 0; tk1 < len viewscr; tk1++)
			tkcmd(t, viewscr[tk1]);
		w := img.r.dx();
		h :=  img.r.dy();
		tkcmd(t, "panel .p -width "+string w+" -height "+string h);
		tk->putimage(t, ".p",img,nil);
		tkcmd(t, "bind .p <ButtonPress-2> {send butchan move %X %Y}");
		tkcmd(t, "bind .p <ButtonRelease-2> {send butchan release}");
		tkcmd(t, "bind .p <ButtonPress-3> {send butchan menu %X %Y}");
		tkcmd(t, ".f.c create window 0 0 -window .p -anchor nw");
		tkcmd(t, ".f.c configure -scrollregion {0 0 "+string w+" "+string h+"}");
		ctlchan <-= 0;
		addtoplevel(t,titlename,nil, sys->pctl(0,nil));

		h1 := 300;
		w1 := 500;
		ht := int tkcmd(t, ".Wm_t cget -height");
		if (h1 > display.image.r.dy() - ht) h1 = display.image.r.dy() - ht;
		if (w1 > display.image.r.dx()) w1 = display.image.r.dx();
		tkcmd(t, ". configure -width "+string w1+" -height "+string h1);
		resizeview(t,w,h);
		tkcmd(t, "pack .f; update");
		scrolling := 0;
		origin := Point (0,0);
		tkclient->onscreen(t, nil);
		tkclient->startinput(t, "kbd"::"ptr"::nil);

		loop: for(;;) alt{
			s := <-t.ctxt.kbd =>
				tk->keyboard(t, s);
			s := <-t.ctxt.ptr =>
				tk->pointer(t, *s);
			inp := <- butchan =>
				(n, lst) := sys->tokenize(inp, " \t\n");
				case hd lst {
					"save" =>
						ftype := "."+hd tl lst;
						savename := selectfile->filename(context,
								display.image,
								"Save "+filelist[i]+ftype+" to directory...", 
								"*"+ftype :: nil,
								lastpath);
						if (savename != "" && savename[0] == '/') {
							lastpath = savename[:isat2(savename,"/")+1];
							if (savename[len savename - 1] == '/')
								savename += filelist[i]+ftype;

							if (!hasext(savename, ftype))
								savename += ftype;
							(fd, cancel) := create(savename, getcoords(t));
							if (fd != nil) {
								n2 := -1;
								if (ftype == ".bit")
									n2 = display.writeimage(fd,img);
								if (ftype == ".jpg")
									n2 = 1 - dnld(i, "$"+savename);
								if (n2 == 0) {
									dialog(filelist[i]+ftype+" saved",0,i,getcoords(t));
									break;
								}
								dialog("Could not save: "+filelist[i]+ftype,0,i,getcoords(t));
							}
							if (!cancel)
								dialog("Could not save: "+filelist[i]+ftype,0,i,getcoords(t));
							break;
						}
						
					"menu" =>
						tkcmd(t, ".m post "+hd tl lst+" "+hd tl tl lst);
					"release" =>
						scrolling = 0;
					"move" =>
						newpoint := Point (int hd tl lst, int hd tl tl lst);

						if (scrolling) {
							diff := (origin.sub(newpoint)).mul(2);
							tkcmd(t, ".f.c xview scroll "+string diff.x+" units");
							tkcmd(t, ".f.c yview scroll "+string diff.y+" units");
							origin = newpoint;
							# clearbuffer(butchan);
						}
						else {
							origin = newpoint;
							scrolling = 1;
						}
				}
	
			s := <-t.ctxt.ctl or
			s = <-t.wreq or
			s = <-titlechan =>
				if (s == "exit")
					break loop;
				e := tkclient->wmctl(t, s);
				if (e == nil && s[0] == '!')
					resizeview(t,w,h);
		}		
		deltoplevel(t);
	}
}

create(filename: string, co: Rect): (ref sys->FD, int)
{
	(n,dir) := sys->stat(filename);
	if (n != -1 && !dialog("overwrite "+filename+"?",1,-1,co))
		return (nil,1);
	return (sys->create(filename,sys->OWRITE,8r666), 0);
}

hasext(name,ext: string): int
{
	if (len name >= len ext && name[len name - len ext:] == ext)
		return 1;
	return 0;
}