xref: /inferno-os/appl/grid/lib/browser.b (revision d3641b487cf5cdc46e9b537d30eb37736e5c7b1a)
1implement Browser;
2
3#
4# Copyright © 2003 Vita Nuova Holdings Limited.  All rights reserved.
5#
6
7include "sys.m";
8	sys : Sys;
9include "draw.m";
10	draw: Draw;
11	Rect: import draw;
12include "tk.m";
13	tk: Tk;
14include "tkclient.m";
15	tkclient: Tkclient;
16include "./pathreader.m";
17include "./browser.m";
18
19entryheight := "";
20
21init()
22{
23	sys = load Sys Sys->PATH;
24	if (sys == nil)
25		badmod(Sys->PATH);
26	draw = load Draw Draw->PATH;
27	if (draw == nil)
28		badmod(Draw->PATH);
29	tk = load Tk Tk->PATH;
30	if (tk == nil)
31		badmod(Tk->PATH);
32	tkclient = load Tkclient Tkclient->PATH;
33	if (tkclient == nil)
34		badmod(Tkclient->PATH);
35	tkclient->init();
36}
37
38Browse.new(top: ref Tk->Toplevel, tkchanname, root, rlabel: string, nopanes: int, reader: PathReader): ref Browse
39{
40	b : Browse;
41	b.top = top;
42	b.tkchan = tkchanname;
43	if (nopanes < 1 || nopanes > 2)
44		return nil;
45	b.nopanes = 2;
46	b.bgnorm = bgnorm;
47	b.bgselect = bgselect;
48	b.selected = array[2] of { * => Selected (File(nil, nil), nil) };
49	b.opened = (root, nil) :: nil;
50	if (root == nil)
51		return nil;
52	if (root[len root - 1] != '/')
53		root[len root] = '/';
54	b.pane0width = "2 3";
55	b.root = root;
56	b.rlabel = rlabel;
57	b.reader = reader;
58	b.pane1 = File (nil, "-123");
59	b.released = 1;
60	tkcmds(top, pane0scr);
61
62	tkcmds(top, pane1scr);
63	tkcmd(top, "bind .fbrowse.lmov <Button-1> {send "+b.tkchan+" movdiv %X}");
64
65	tkcmd(top, "label .fbrowse.l -text { }  -anchor w -width 0" +
66		" -font /fonts/charon/plain.normal.font");
67	tkcmd(top, ".fbrowse.l configure -height "+tkcmd(top, ".fbrowse.l cget -height"));
68	tkcmd(top, "grid .fbrowse.l -row 0 -column 0 -sticky ew -pady 2 -columnspan 4");
69	rb := ref b;
70	rb.newroot(b.root, b.rlabel);
71	rb.changeview(nopanes);
72	setbrowsescrollr(rb);
73	return rb;
74}
75
76Browse.refresh(b: self ref Browse)
77{
78	scrval := tkcmd(b.top, ".fbrowse.sy1 get");
79	p := isat(scrval, " ");
80	p1 := b.pane1;
81	b.newroot(b.root, b.rlabel);
82	setbrowsescrollr(b);
83	if (b.nopanes == 2)
84		popdirpane1(b, p1);
85	b.selectfile(1,DESELECT, File (nil, nil), nil);
86	b.selectfile(0,DESELECT, File (nil, nil), nil);
87	tkcmd(b.top, ".fbrowse.c1 yview moveto "+scrval[:p]+"; update");
88}
89
90bgnorm := "white";
91bgselect := "#5555FF";
92
93ft := " -font /fonts/charon/plain.normal.font";
94fts := " -font /fonts/charon/plain.tiny.font";
95ftb := " -font /fonts/charon/bold.normal.font";
96
97Browse.gotoselectfile(b: self ref Browse, file: File): string
98{
99	(dir, tkpath) := b.gotopath(file, 0);
100	if (tkpath == nil)
101		return nil;
102	# Select dir
103	tkpath += ".l";
104	if (dir.qid != nil)
105		tkpath += "Q" + dir.qid;
106	b.selectfile(0, SELECT, dir, tkpath);
107
108	# If it is a file, select the file too
109	if (!File.eq(file, dir)) {
110		slaves := tkcmd(b.top, "grid slaves .fbrowse.fl2");
111		(nil, lst) := sys->tokenize(slaves, " ");
112		for (; lst != nil; lst = tl lst) {
113			if (File.eq(file, *b.getpath(hd lst))) {
114				b.selectfile(1, SELECT, file, hd lst);
115				tkpath = hd lst;
116				break;
117			}
118		}
119		pane1see(b);
120	}
121	return tkpath;
122}
123
124pane1see(b: ref Browse)
125{
126	f := b.selected[1].tkpath;
127	if (f == "")
128		return;
129	x1 := int tkcmd(b.top, f+" cget -actx") - int tkcmd(b.top, ".fbrowse.fl2 cget -actx");
130	y1 := int tkcmd(b.top, f+" cget -acty") - int tkcmd(b.top, ".fbrowse.fl2 cget -acty");
131	x2 := x1 + int tkcmd(b.top, f+" cget -actwidth");
132	y2 := y1 + int tkcmd(b.top, f+" cget -actheight");
133	tkcmd(b.top, sys->sprint(".fbrowse.c2 see %d %d %d %d", x1,y1,x2,y2));
134}
135
136Browse.opendir(b: self ref Browse, file: File, tkpath: string, action: int): int
137{
138	curr := tkcmd(b.top, tkpath+".lp cget -text");
139	if ((action == OPEN || action == TOGGLE) && curr == "+") {
140		tkcmd(b.top, tkpath+".lp configure -text {-} -relief sunken");
141		popdirpane0(b, file, tkpath);
142		seeframe(b.top, tkpath);
143		b.addopened(file, 1);
144		setbrowsescrollr(b);
145		return 1;
146	}
147	else if ((action == CLOSE || action == TOGGLE) && curr == "-") {
148		tkcmd(b.top, tkpath+".lp configure -text {+} -relief raised");
149		slaves := tkcmd(b.top, "grid slaves "+tkpath+" -column 1");
150		p := isat(slaves, " ");
151		if (p != -1)
152			tkcmd(b.top, "destroy "+slaves[p:]);
153		slaves = tkcmd(b.top, "grid slaves "+tkpath+" -column 2");
154		if (slaves != "")
155			tkcmd(b.top, "destroy "+slaves);
156		b.addopened(file, 0);
157		setbrowsescrollr(b);
158		return 1;
159	}
160	return 0;
161}
162
163Browse.addopened(b: self ref Browse, file: File, add: int)
164{
165	tmp : list of File = nil;
166	for (; b.opened != nil; b.opened = tl b.opened) {
167		dir := hd b.opened;
168		if (!File.eq(file, dir))
169			tmp = dir :: tmp;
170	}
171	if (add)
172		tmp = file :: tmp;
173	b.opened = tmp;
174}
175
176Browse.changeview(b: self ref Browse, nopanes: int)
177{
178	if (b.nopanes == nopanes)
179		return;
180#	w := int tkcmd(b.top, ".fbrowse cget -actwidth");
181#	ws := int tkcmd(b.top, ".fbrowse.sy1 cget -width");
182	if (nopanes == 1) {
183		b.pane0width = tkcmd(b.top, ".fbrowse.c1 cget -actwidth") + " " +
184						tkcmd(b.top, ".fbrowse.c2 cget -actwidth");
185		tkcmd(b.top, "grid forget .fbrowse.sx2 .fbrowse.c2 .fbrowse.lmov");
186		tkcmd(b.top, "grid columnconfigure .fbrowse 3 -weight 0");
187	}
188	else {
189		(nil, wlist) := sys->tokenize(b.pane0width, " ");
190		tkcmd(b.top, "grid columnconfigure .fbrowse 1 -weight "+hd wlist);
191		tkcmd(b.top, "grid columnconfigure .fbrowse 3 -weight "+hd tl wlist);
192
193		tkcmd(b.top, "grid .fbrowse.sx2 -row 3 -column 3 -sticky ew");
194		tkcmd(b.top, "grid .fbrowse.c2 -row 2 -column 3 -sticky nsew");
195		tkcmd(b.top, "grid .fbrowse.lmov -row 2 -column 2 -rowspan 2 -sticky ns");
196	}
197	b.nopanes = nopanes;
198}
199
200Browse.selectfile(b: self ref Browse, pane, action: int, file: File, tkpath: string)
201{
202	if (action == SELECT && b.selected[pane].tkpath == tkpath)
203		return;
204	if (b.selected[pane].tkpath != nil)
205		tk->cmd(b.top, b.selected[pane].tkpath+" configure -bg "+bgnorm);
206	if ((action == TOGGLE && b.selected[pane].tkpath == tkpath) || action == DESELECT) {
207		if (pane == 0)
208			popdirpane1(b, File (nil,nil));
209		b.selected[pane] = (File(nil, nil), nil);
210		return;
211	}
212	b.selected[pane] = (file, tkpath);
213	tkcmd(b.top, tkpath+" configure -bg "+bgselect);
214	if (pane == 0)
215		popdirpane1(b, file);
216}
217
218Browse.resize(b: self ref Browse)
219{
220 	p1 := b.pane1;
221 	b.pane1 = File (nil, nil);
222
223 	if (p1.path != "")
224 		popdirpane1(b, p1);
225
226	if (b.selected[1].tkpath != nil) {
227		s := b.selected[1];
228		b.selectfile(1, DESELECT, s.file, s.tkpath);
229		b.selectfile(1, SELECT, s.file, s.tkpath);
230	}
231}
232
233setbrowsescrollr(b: ref Browse)
234{
235	h := tkcmd(b.top, ".fbrowse.fl cget -height");
236	w := tkcmd(b.top, ".fbrowse.fl cget -width");
237	tkcmd(b.top, ".fbrowse.c1 configure -scrollregion {0 0 "+w+" "+h+"}");
238	if (b.nopanes == 2) {
239		h = tkcmd(b.top, ".fbrowse.fl2 cget -height");
240		w = tkcmd(b.top, ".fbrowse.fl2 cget -width");
241		tkcmd(b.top, ".fbrowse.c2 configure -scrollregion {0 0 "+w+" "+h+"}");
242	}
243}
244
245seeframe(top: ref Tk->Toplevel, frame: string)
246{
247	x := int tkcmd(top, frame+" cget -actx") - int tkcmd(top, ".fbrowse.fl cget -actx");
248	y := int tkcmd(top, frame+" cget -acty")  - int tkcmd(top, ".fbrowse.fl cget -acty");
249	w := int tkcmd(top, frame+" cget -width");
250	h := int tkcmd(top, frame+" cget -height");
251	wc := int tkcmd(top, ".fbrowse.c1 cget -width");
252	hc := int tkcmd(top, ".fbrowse.c1 cget -height");
253	if (w > wc)
254		w = wc;
255	if (h > hc)
256		h = hc;
257	tkcmd(top, sys->sprint(".fbrowse.c1 see %d %d %d %d",x,y,x+w,y+h));
258}
259
260# Goes to selected dir OR dir containing selected file
261Browse.gotopath(b: self ref Browse, file: File, openfinal: int): (File, string)
262{
263	tkpath := ".fbrowse.fl.f0";
264	path := b.root;
265	testqid := "";
266	testpath := "";
267	close : list of string;
268	trackbacklist : list of (string, list of string, list of string) = nil;
269	trackback := 0;
270	enddir := "";
271	if (file.path[len file.path - 1] != '/') {
272		# i.e. is not a directory
273		p := isatback(file.path, "/");
274		enddir = file.path[:p + 1];
275	}
276	if (enddir == path) {
277		if (!dircontainsfile(b, File (path, nil), file))
278			return (File (nil, nil), nil);
279	}
280	else {
281		for(;;) {
282			lst : list of string;
283			if (trackback) {
284				(path, lst, close) = hd trackbacklist;
285				trackbacklist = tl trackbacklist;
286				if (close != nil)
287					b.opendir(File (hd close, hd tl close), hd tl tl close, CLOSE);
288				trackback = 0;
289			}
290			else {
291				frames := tkcmd(b.top, "grid slaves "+tkpath+" -column 1");
292				(nil, lst) = sys->tokenize(frames, " ");
293				if (lst != nil)
294					lst = tl lst; # ignore first frame (name of parent dir);
295			}
296			found := 0;
297			hasdups := 1;
298			for (; lst != nil; lst = tl lst) {
299				testpath = path;
300				if (hasdups) {
301					labels := tkcmd(b.top, "grid slaves "+hd lst+" -row 0");
302					(nil, lst2) := sys->tokenize(labels, " ");
303					testpath += tkcmd(b.top, hd tl lst2+" cget -text") + "/";
304					testqid = getqidfromlabel(hd tl lst2);
305					if (testqid == nil)
306						hasdups = 0;
307				}
308				else
309					testpath += tkcmd(b.top, hd lst+".l cget -text") + "/";
310				if (len testpath <= len file.path && file.path[:len testpath] == testpath) {
311					opened := 0;
312					close = nil;
313					if (openfinal || testpath != file.path)
314						opened = b.opendir(File(testpath, testqid), hd lst, OPEN);
315					if (opened)
316						close = testpath :: testqid :: hd lst :: nil;
317					if (tl lst != nil && hasdups)
318						trackbacklist = (path, tl lst, close) :: trackbacklist;
319					tkpath = hd lst;
320					path = testpath;
321					found = 1;
322					break;
323				}
324			}
325			if (enddir != nil && path == enddir)
326				if (dircontainsfile(b, File(testpath, testqid), file))
327					break;
328			if (!found) {
329				if (trackbacklist == nil)
330					return (File (nil, nil), nil);
331				trackback = 1;
332			}
333			else if (testpath == file.path && testqid == file.qid)
334				break;
335		}
336	}
337	seeframe(b.top, tkpath);
338	dir := File (path, testqid);
339	popdirpane1(b, dir);
340	return (dir, tkpath);
341}
342
343dircontainsfile(b: ref Browse, dir, file: File): int
344{
345	(files, hasdups) := b.reader->readpath(dir);
346	for (j := 0; j < len files; j++) {
347		if (files[j].name == file.path[len dir.path:] &&
348				(!hasdups || files[j].qid.path == big file.qid))
349			return 1;
350	}
351	return 0;
352}
353
354Browse.getpath(b: self ref Browse, f: string): ref File
355{
356	if (len f < 11 || f[:11] != ".fbrowse.fl")
357		return nil;
358	(nil, lst) := sys->tokenize(f, ".");
359	lst = tl lst;
360	if (hd lst == "fl2") {
361		# i.e. is in pane 1
362		qid := getqidfromlabel(f);
363		return ref File (b.pane1.path + tk->cmd(b.top, f+" cget -text"), qid);
364	}
365	tkpath := ".fbrowse.fl.f0";
366	path := b.root;
367	lst = tl tl lst;
368#	sys->print("getpath: %s %s\n",tkpath, path);
369	qid := "";
370	for (; lst != nil; lst = tl lst) {
371		tkpath += "."+hd lst;
372		if ((hd lst)[0] == 'l') {
373			qid = getqidfromlabel(tkpath);
374			if (qid != nil)
375				qid = "Q" + qid;
376			if (len hd lst - len qid > 1)
377				path += tk->cmd(b.top, tkpath+" cget -text");
378		}
379		else if ((hd lst)[0] == 'f') {
380			qid = getqidfromframe(b,tkpath);
381			if (qid != nil)
382				qid = "Q"+qid;
383			path += tk->cmd(b.top, tkpath+".l"+qid+" cget -text") + "/";
384		}
385#		sys->print("getpath: %s %s\n",tkpath, path);
386	}
387	# Temporary hack!
388	if (qid != nil)
389		qid = qid[1:];
390	return ref File (path, qid);
391}
392
393setroot(b: ref Browse, rlabel, root: string)
394{
395	b.root = root;
396	b.rlabel = rlabel;
397	makedir(b, File (root, nil), ".fbrowse.fl.f0", rlabel, "0");
398	tkcmd(b.top, "grid forget .fbrowse.fl.f0.lp");
399}
400
401getqidfromframe(b: ref Browse, frame: string): string
402{
403	tmp := tkcmd(b.top, "grid slaves "+frame+" -row 0");
404	(nil, lst) := sys->tokenize(tmp, " \t\n");
405	if (lst == nil)
406		return nil;
407	return getqidfromlabel(hd tl lst);
408}
409
410getqidfromlabel(label: string): string
411{
412	p := isatback(label, "Q");
413	if (p != -1)
414		return label[p+1:];
415	return nil;
416}
417
418popdirpane0(b: ref Browse, dir : File, frame: string)
419{
420	(dirs, hasdups) := b.reader->readpath(dir);
421	for (i := 0; i < len dirs; i++) {
422		si := string i;
423		f : string;
424		dirqid := string dirs[i].qid.path;
425		if (!hasdups)
426			dirqid = nil;
427		if (dirs[i].mode & sys->DMDIR) {
428			f = frame + ".f"+si;
429			makedir(b, File (dir.path+dirs[i].name, dirqid), f, dirs[i].name, string (i+1));
430		}
431		else {
432			if (b.nopanes == 1) {
433				f = frame + ".l"+si;
434				makefile(b, f, dirs[i].name, string (i+1), dirqid);
435			}
436		}
437	}
438	dirs = nil;
439}
440
441isopened(b: ref Browse, dir: File): int
442{
443	for (tmp := b.opened; tmp != nil; tmp = tl tmp) {
444		if (File.eq(hd tmp, dir))
445			return 1;
446	}
447	return 0;
448}
449
450makefile(b: ref Browse, f, name, row, qid: string)
451{
452	if (qid != nil)
453		f += "Q" + qid;
454	bgcol := bgnorm;
455#	if (f == selected[0].t1)
456#		bgcol = bgselect;
457	p := isat(name, "\0");
458	if (p != -1) {
459		tkcmd(b.top, "label "+f+" -text {"+name[:p]+"} -bg "+bgcol+ft);
460		tkcmd(b.top, "label "+f+"b -text {"+name[p+1:]+"} -bg "+bgcol+ft);
461		tkcmd(b.top, "grid "+f+" -row "+row+" -column 1 -sticky w -padx 5 -pady 2");
462		tkcmd(b.top, "grid "+f+"b -row "+row+" -column 2 -sticky w -pady 2");
463		tkcmd(b.top, "bind "+f+" <Button-2> {send "+b.tkchan+" but2pane1 "+f+"}");
464		tkcmd(b.top, "bind "+f+" <ButtonRelease-2> {send "+b.tkchan+" release}");
465	}
466	else {
467		tkcmd(b.top, "label "+f+" -text {"+name+"} -bg "+bgcol+ft);
468		tkcmd(b.top, "grid "+f+" -row "+row+" -column 1 -sticky w -padx 5 -pady 2");
469	}
470	tkcmd(b.top, "bind "+f+" <Button-1> {send "+b.tkchan+" but1pane0 "+f+"}");
471	tkcmd(b.top, "bind "+f+" <ButtonRelease-1> {send "+b.tkchan+" release}");
472	tkcmd(b.top, "bind "+f+" <Button-2> {send "+b.tkchan+" but2pane0 "+f+"}");
473	tkcmd(b.top, "bind "+f+" <ButtonRelease-2> {send "+b.tkchan+" release}");
474	tkcmd(b.top, "bind "+f+" <Button-3> {send "+b.tkchan+" but3pane0 "+f+"}");
475	tkcmd(b.top, "bind "+f+" <ButtonRelease-3> {send "+b.tkchan+" release}");
476}
477
478Browse.defaultaction(b: self ref Browse, lst: list of string, rfile: ref File)
479{
480	tkpath: string;
481	file: File;
482	if (len lst > 1) {
483		tkpath = hd tl lst;
484		if (len tkpath > 11 && tkpath[:11] == ".fbrowse.fl") {
485			if (rfile == nil)
486				file = *b.getpath(tkpath);
487			else
488				file = *rfile;
489		}
490	}
491	case hd lst {
492		"release" =>
493			b.released = 1;
494		"open" or "double1pane0" =>
495			if (file.path == b.root)
496				break;
497			if (b.released) {
498				b.selectfile(0, DESELECT, File(nil, nil), nil);
499				b.selectfile(1, DESELECT, File(nil, nil), nil);
500				b.opendir(file, prevframe(tkpath), TOGGLE);
501				b.selectfile(0, SELECT, file, tkpath);
502				b.released = 0;
503			}
504		"double1pane1" =>
505			b.gotoselectfile(file);
506		"but1pane0" =>
507			if (b.released) {
508				b.selectfile(1, DESELECT, File(nil, nil), nil);
509				b.selectfile(0, TOGGLE, file, tkpath);
510				b.released = 0;
511			}
512 		"but1pane1" =>
513			if (b.released) {
514				b.selectfile(1, TOGGLE, file, tkpath);
515				b.released = 0;
516			}
517 		"movdiv" =>
518			movdiv(b, int hd tl lst);
519	}
520}
521
522prevframe(tkpath: string): string
523{
524	end := len tkpath;
525	for (;;) {
526		p := isatback(tkpath[:end], ".");
527		if (tkpath[p+1] == 'f')
528			return tkpath[:end];
529		end = p;
530	}
531	return nil;
532}
533
534makedir(b: ref Browse, dir: File, f, name, row: string)
535{
536	bgcol := bgnorm;
537	if (f == ".fbrowse.fl.f0")
538		dir = File (b.root, nil);
539#	if (name == "")
540#		name = path;
541	if (dir.path[len dir.path - 1] != '/')
542		dir.path[len dir.path] = '/';
543	if (File.eq(dir, b.selected[0].file))
544		bgcol = bgselect;
545	tkcmd(b.top, "frame "+f+" -bg white");
546	label := f+".l";
547	if (dir.qid != nil)
548		label += "Q" + dir.qid;
549	tkcmd(b.top, "label "+label+" -text {"+name+"} -bg "+bgcol+ftb);
550	if (isopened(b, dir)) {
551		popdirpane0(b, dir, f);
552		tkcmd(b.top, "label "+f+".lp -text {-} -borderwidth 1 -relief sunken -height 8 -width 8"+fts);
553	}
554	else tkcmd(b.top, "label "+f+".lp -text {+} -borderwidth 1 -relief raised -height 8 -width 8"+fts);
555	tkcmd(b.top, "bind "+label+" <Button-1> {send "+b.tkchan+" but1pane0 "+label+"}");
556	tkcmd(b.top, "bind "+label+" <Double-Button-1> {send "+b.tkchan+" double1pane0 "+label+"}");
557	tkcmd(b.top, "bind "+label+" <ButtonRelease-1> {send "+b.tkchan+" release}");
558	tkcmd(b.top, "bind "+label+" <Button-3> {send "+b.tkchan+" but3pane0 "+label+"}");
559	tkcmd(b.top, "bind "+label+" <ButtonRelease-3> {send "+b.tkchan+" release}");
560	tkcmd(b.top, "bind "+label+" <Button-2> {send "+b.tkchan+" but2pane0 "+label+"}");
561	tkcmd(b.top, "bind "+label+" <ButtonRelease-2> {send "+b.tkchan+" release}");
562
563	tkcmd(b.top, "bind "+f+".lp <Button-1> {send "+b.tkchan+" open "+label+"}");
564	tkcmd(b.top, "bind "+f+".lp <ButtonRelease-1> {send "+b.tkchan+" release}");
565	tkcmd(b.top, "grid "+f+".lp -row 0 -column 0");
566	tkcmd(b.top, "grid "+label+" -row 0 -column 1 -sticky w -padx 5 -pady 2 -columnspan 2");
567	tkcmd(b.top, "grid "+f+" -row "+row+" -column 1 -sticky w -padx 5 -columnspan 2");
568}
569
570popdirpane1(b: ref Browse, dir: File)
571{
572#	if (path == b.pane1.path && qid == b.pane1.qid)
573#		return;
574	b.pane1 = dir;
575	labelset(b, ".fbrowse.l", prevpath(dir.path+"/"));
576	if (b.nopanes == 1)
577		return;
578	tkcmd(b.top, "destroy .fbrowse.fl2; frame .fbrowse.fl2 -bg white");
579	tkcmd(b.top, ".fbrowse.c2 create window 0 0 -window .fbrowse.fl2 -anchor nw");
580	if (dir.path == nil) {
581		setbrowsescrollr(b);
582		return;
583	}
584	(dirs, hasdups) := b.reader->readpath(dir);
585#	if (path[len path - 1] == '/')
586#		path = path[:len path - 1];
587#	tkcmd(b.top, "label .fbrowse.fl2.l -text {"+path+"}");
588	row := 0;
589	col := 0;
590	tkcmd(b.top, ".fbrowse.c2 see 0 0");
591	ni := 0;
592	n := (int tkcmd(b.top, ".fbrowse.c2 cget -actheight")) / 21;
593	for (i := 0; i < len dirs; i++) {
594
595		f := ".fbrowse.fl2.l"+string ni;
596		if (hasdups)
597			f += "Q" + string dirs[i].qid.path;
598		name := dirs[i].name;
599		isdir := dirs[i].mode & sys->DMDIR;
600		if (isdir)
601			name[len name]= '/';
602		bgcol := bgnorm;
603		# Sort this out later
604		# if (path+"/"+name == selected[1].t0) {
605		#	bgcol = bgselect;
606		#	selected[1].t1 = f;
607		#}
608		tkcmd(b.top, "label "+f+" -text {"+name+"} -bg "+bgcol+ft);
609		tkcmd(b.top, "bind "+f+" <Double-Button-1> {send "+b.tkchan+" double1pane1 "+f+"}");
610		tkcmd(b.top, "bind "+f+" <Button-1> {send "+b.tkchan+" but1pane1 "+f+"}");
611		tkcmd(b.top, "bind "+f+" <ButtonRelease-1> {send "+b.tkchan+" release}");
612		tkcmd(b.top, "bind "+f+" <Button-3> {send "+b.tkchan+" but3pane1 "+f+" %X %Y}");
613		tkcmd(b.top, "bind "+f+" <ButtonRelease-3> {send "+b.tkchan+" release}");
614		tkcmd(b.top, "grid "+f+" -row "+string row+" -column "+string col+
615					" -sticky w -padx 10 -pady 2");
616		row++;
617		if (row >= n) {
618			row = 0;
619			col++;
620		}
621		ni++;
622	}
623
624	dirs = nil;
625	setbrowsescrollr(b);
626}
627
628pane0scr := array[] of {
629	"frame .fbrowse",
630
631	"scrollbar .fbrowse.sy1 -command {.fbrowse.c1 yview}",
632	"scrollbar .fbrowse.sx1 -command {.fbrowse.c1 xview} -orient horizontal",
633	"canvas .fbrowse.c1 -yscrollcommand {.fbrowse.sy1 set} -xscrollcommand {.fbrowse.sx1 set} -bg white -width 50 -height 20 -borderwidth 2 -relief sunken -xscrollincrement 10 -yscrollincrement 21",
634	"grid .fbrowse.sy1 -row 2 -column 0 -sticky ns -rowspan 2",
635	"grid .fbrowse.sx1 -row 3 -column 1 -sticky ew",
636	"grid .fbrowse.c1 -row 2 -column 1 -sticky nsew",
637	"grid rowconfigure .fbrowse 2 -weight 1",
638	"grid columnconfigure .fbrowse 1 -weight 2",
639
640};
641
642pane1scr := array[] of {
643#	".fbrowse.c1 configure -width 146",
644	"frame .fbrowse.fl2 -bg white",
645	"label .fbrowse.fl2.l -text {}",
646	"scrollbar .fbrowse.sx2 -command {.fbrowse.c2 xview} -orient horizontal",
647	"label .fbrowse.lmov -text { } -relief sunken -borderwidth 2 -width 5",
648
649	"canvas .fbrowse.c2 -xscrollcommand {.fbrowse.sx2 set} -bg white -width 50 -height 20 -borderwidth 2 -relief sunken -xscrollincrement 10 -yscrollincrement 21",
650	".fbrowse.c2 create window 0 0 -window .fbrowse.fl2 -anchor nw",
651	"grid .fbrowse.sx2 -row 3 -column 3 -sticky ew",
652	"grid .fbrowse.c2 -row 2 -column 3 -sticky nsew",
653	"grid .fbrowse.lmov -row 2 -column 2 -rowspan 2 -sticky ns",
654	"grid columnconfigure .fbrowse 3 -weight 3",
655};
656
657Browse.newroot(b: self ref Browse, root, rlabel: string)
658{
659	tk->cmd(b.top, "destroy .fbrowse.fl");
660	tkcmd(b.top, "frame .fbrowse.fl -bg white");
661	tkcmd(b.top, ".fbrowse.c1 create window 0 0 -window .fbrowse.fl -anchor nw");
662	b.pane1 = File (root, nil);
663	setroot(b, rlabel, root);
664	setbrowsescrollr(b);
665}
666
667Browse.showpath(b: self ref Browse, on: int)
668{
669	if (on == b.showpathlabel)
670		return;
671	if (on) {
672		b.showpathlabel = 1;
673		if (b.pane1.path != nil)
674			labelset(b, ".fbrowse.l", prevpath(b.pane1.path+"/"));
675	}
676	else {
677		b.showpathlabel = 0;
678		tkcmd(b.top, ".fbrowse.l configure -text {}");
679	}
680}
681
682Browse.getselected(b: self ref Browse, pane: int): File
683{
684	return b.selected[pane].file;
685}
686
687labelset(b: ref Browse, label, text: string)
688{
689	if (!b.showpathlabel)
690		return;
691	if (text != nil) {
692		tmp := b.rlabel;
693		if (tmp[len tmp - 1] != '/')
694			tmp[len tmp] = '/';
695		text = tmp + text[len b.root:];
696	}
697	tkcmd(b.top, label + " configure -text {"+text+"}");
698}
699
700movdiv(b: ref Browse, x: int)
701{
702	x1 := int tkcmd(b.top, ".fbrowse.lmov cget -actx");
703	x2 := x1 + int tkcmd(b.top, ".fbrowse.lmov cget -width");
704	diff := 0;
705	if (x < x1)
706		diff = x - x1;
707	if (x > x2)
708		diff = x - x2;
709	if (abs(diff) > 5) {
710		w1 := int tkcmd(b.top, ".fbrowse.c1 cget -actwidth");
711		w2 := int tkcmd(b.top, ".fbrowse.c2 cget -actwidth");
712		if (w1 + diff < 36)
713			diff = 36 - w1;
714		if (w2 - diff < 36)
715			diff = w2 - 36;
716		w1 += diff;
717		w2 -= diff;
718		# sys->print("w1: %d w2: %d\n",w1,w2);
719		tkcmd(b.top, "grid columnconfigure .fbrowse 1 -weight "+string w1);
720		tkcmd(b.top, "grid columnconfigure .fbrowse 3 -weight "+string w2);
721	}
722}
723
724
725dialog(ctxt: ref draw->Context, oldtop: ref Tk->Toplevel, butlist: list of string, title, msg: string): int
726{
727	(top, titlebar) := tkclient->toplevel(ctxt, "", title, tkclient->Popup);
728	butchan := chan of string;
729	tk->namechan(top, butchan, "butchan");
730	tkcmd(top, "frame .f");
731	tkcmd(top, "label .f.l -text {"+msg+"} -font /fonts/charon/plain.normal.font");
732	tkcmd(top, "bind .Wm_t <Button-1> +{focus .}");
733	tkcmd(top, "bind .Wm_t.title <Button-1> +{focus .}");
734
735	l := len butlist;
736	tkcmd(top, "grid .f.l -row 0 -column 0 -columnspan "+string l+" -sticky w -padx 10 -pady 5");
737	i := 0;
738	for(; butlist != nil; butlist = tl butlist) {
739		si := string i;
740		tkcmd(top, "button .f.b"+si+" -text {"+hd butlist+"} "+
741			"-font /fonts/charon/plain.normal.font -command {send butchan "+si+"}");
742		tkcmd(top, "grid .f.b"+si+" -row 1 -column "+si+" -padx 5 -pady 5");
743		i++;
744	}
745	placement := "";
746	if (oldtop != nil) {
747		setcentre(oldtop, top);
748		placement = "exact";
749	}
750	tkcmd(top, "pack .f; update; focus .");
751	tkclient->onscreen(top, placement);
752	tkclient->startinput(top, "kbd"::"ptr"::nil);
753	for (;;) {
754		alt {
755		s := <-top.ctxt.kbd =>
756			tk->keyboard(top, s);
757		s := <-top.ctxt.ptr =>
758			tk->pointer(top, *s);
759		inp := <- butchan =>
760			tkcmd(oldtop, "focus .");
761			return int inp;
762		title = <-top.ctxt.ctl or
763		title = <-top.wreq or
764		title = <-titlebar =>
765			if (title == "exit") {
766				tkcmd(oldtop, "focus .");
767				return -1;
768			}
769			tkclient->wmctl(top, title);
770		}
771	}
772}
773######################## Select Functions #########################
774
775
776setselectscrollr(s: ref Select, f: string)
777{
778	h := tkcmd(s.top, f+" cget -height");
779	w := tkcmd(s.top, f+" cget -width");
780	tkcmd(s.top, ".fselect.c configure -scrollregion {0 0 "+w+" "+h+"}");
781}
782
783Select.setscrollr(s: self ref Select, fname: string)
784{
785	frame := getframe(s, fname);
786	if (frame != nil)
787		setselectscrollr(s,frame.path);
788}
789
790Select.new(top: ref Tk->Toplevel, tkchanname: string): ref Select
791{
792	s: Select;
793	s.top = top;
794	s.tkchan = tkchanname;
795	s.frames = nil;
796	s.currfname = nil;
797	s.currfid = nil;
798	tkcmds(top, selectscr);
799	if (entryheight == nil) {
800		tkcmd(top, "entry .fselect.test");
801		entryheight = " -height " + tkcmd(top, ".fselect.test cget -height");
802		tkcmd(top, "destroy .fselect.test");
803	}
804	for (i := 1; i < 4; i++)
805		tkcmd(top, "bind .fselect.c <ButtonRelease-"+string i+"> {send "+s.tkchan+" release}");
806	return ref s;
807}
808
809selectscr := array[] of {
810	"frame .fselect",
811	"scrollbar .fselect.sy -command {.fselect.c yview}",
812	"scrollbar .fselect.sx -command {.fselect.c xview} -orient horizontal",
813	"canvas .fselect.c -yscrollcommand {.fselect.sy set} -xscrollcommand {.fselect.sx set} -bg white -width 414 -borderwidth 2 -relief sunken -height 180 -xscrollincrement 10 -yscrollincrement 19",
814
815	"grid .fselect.sy -row 0 -column 0 -sticky ns -rowspan 2",
816	"grid .fselect.sx -row 1 -column 1 -sticky ew",
817	"grid .fselect.c -row 0 -column 1",
818};
819
820Select.addframe(s: self ref Select, fname, title: string)
821{
822	if (isat(fname, " ") != -1)
823		return;
824	f := ".fselect.f"+fname;
825	tkcmd(s.top, "frame "+f+" -bg white");
826	if (title != nil){
827		tkcmd(s.top, "label "+f+".l -text {"+title+"} -bg white "+
828			"-font /fonts/charon/bold.normal.font; "+
829			"grid "+f+".l -row 0 -column 0 -columnspan 3 -sticky w");
830	}
831	fr: Frame;
832	fr.name = fname;
833	fr.path = f;
834	fr.selected = nil;
835	s.frames = ref fr :: s.frames;
836}
837
838getframe(s: ref Select, fname: string): ref Frame
839{
840	for (tmp := s.frames; tmp != nil; tmp = tl tmp)
841		if ((hd tmp).name == fname)
842			return hd tmp;
843	return nil;
844}
845
846Select.delframe(s: self ref Select, fname: string)
847{
848	if (s.currfname == fname) {
849		tkcmd(s.top, ".fselect.c delete " + s.currfid);
850		s.currfid = nil;
851		s.currfname = nil;
852	}
853	f := getframe(s,fname);
854	if (f != nil) {
855		tkcmd(s.top, "destroy "+f.path);
856		tmp: list of ref Frame = nil;
857		for (;s.frames != nil; s.frames = tl s.frames) {
858			if ((hd s.frames).name != fname)
859				tmp = hd s.frames :: tmp;
860		}
861		s.frames = tmp;
862	}
863}
864
865Select.showframe(s: self ref Select, fname: string)
866{
867	if (s.currfid != nil)
868		tkcmd(s.top, ".fselect.c delete " + s.currfid);
869	f := getframe(s, fname);
870	if (f != nil) {
871		s.currfid = tkcmd(s.top, ".fselect.c create window 0 0 "+
872				"-window "+f.path+" -anchor nw");
873		s.currfname = fname;
874	}
875}
876
877Select.addselection(s: self ref Select, fname, text: string, lp: list of ref Parameter, allowdups: int): string
878{
879	fr := getframe(s, fname);
880	if (fr == nil)
881		return nil;
882	f := fr.path;
883	if (!allowdups) {
884		slv := tkcmd(s.top, "grid slaves "+f+" -column 0");
885		(nil, slaves) := sys->tokenize(slv, " \t\n");
886		for (; slaves != nil; slaves = tl slaves) {
887			if (text == tkcmd(s.top, hd slaves+" cget -text"))
888				return nil;
889		}
890	}
891	font := " -font /fonts/charon/plain.normal.font";
892	fontb := " -font /fonts/charon/bold.normal.font";
893	(id, row) := newselected(s.top, f);
894	sid := string id;
895	label := f+".l"+sid;
896	tkcmd(s.top, "label "+label+" -text {"+text+"} -bg white"+entryheight+font);
897	gridpack := label+" ";
898	paramno := 0;
899	for (; lp != nil; lp = tl lp) {
900		spn := string paramno;
901		pframe := f+".f"+sid+"P"+spn;
902		tkcmd(s.top, "frame "+pframe+" -bg white");
903		pick p := hd lp {
904		ArgIn =>
905			tkp1 := pframe+".lA";
906			tkp2 := pframe+".eA";
907
908			tkcmd(s.top, "label "+tkp1+" -text {"+p.name+"} "+
909					"-bg white "+entryheight+fontb);
910			tkcmd(s.top, "entry "+tkp2+" -bg white -width 50 "+
911					"-borderwidth 1"+entryheight+font);
912			if (p.initval != nil)
913				tkcmd(s.top, tkp2+" insert end {"+p.initval+"}");
914			tkcmd(s.top, "grid "+tkp1+" "+tkp2+" -row 0");
915
916		IntIn =>
917			tkp1 := pframe+".sI";
918			tkp2 := pframe+".lI";
919			tkcmd(s.top, "scale "+tkp1+" -showvalue 0 -orient horizontal -height 20"+
920				" -from "+string p.min+" -to "+string p.max+" -command {send "+
921				s.tkchan+" scale "+tkp2+"}");
922			tkcmd(s.top, tkp1+" set "+string p.initval);
923			tkcmd(s.top, "label "+tkp2+" -text {"+string p.initval+"} "+
924					"-bg white "+entryheight+fontb);
925			tkcmd(s.top, "grid "+tkp1+" "+tkp2+" -row 0");
926
927		}
928		gridpack += " "+pframe;
929		paramno++;
930	}
931	tkcmd(s.top, "grid "+gridpack+" -row "+row+" -sticky w");
932
933	sendstr := " " + label + " %X %Y}";
934	tkcmd(s.top, "bind "+label+" <Double-Button-1> {send "+s.tkchan+" double1"+sendstr);
935	tkcmd(s.top, "bind "+label+" <Button-1> {send "+s.tkchan+" but1"+sendstr);
936	tkcmd(s.top, "bind "+label+" <ButtonRelease-1> {send "+s.tkchan+" release}");
937	tkcmd(s.top, "bind "+label+" <Button-2> {send "+s.tkchan+" but2"+sendstr);
938	tkcmd(s.top, "bind "+label+" <ButtonRelease-2> {send "+s.tkchan+" release}");
939	tkcmd(s.top, "bind "+label+" <Button-3> {send "+s.tkchan+" but3"+sendstr);
940	tkcmd(s.top, "bind "+label+" <ButtonRelease-3> {send "+s.tkchan+" release}");
941	setselectscrollr(s, f);
942	if (s.currfname == fname) {
943		y := int tkcmd(s.top, label+"  cget -acty") -
944			int tkcmd(s.top, f+" cget -acty");
945		h := int tkcmd(s.top, label+"  cget -height");
946		tkcmd(s.top, ".fselect.c see 0 "+string (h+y));
947	}
948	return label;
949}
950
951newselected(top: ref Tk->Toplevel, frame: string): (int, string)
952{
953	(n, slaves) := sys->tokenize(tkcmd(top, "grid slaves "+frame+" -column 0"), " \t\n");
954	id := 0;
955	slaves = tl slaves; # Ignore Title
956	for (;;) {
957		if (isin(slaves, frame+".l"+string id))
958			id++;
959		else break;
960	}
961	return (id, string n);
962}
963
964isin(l: list of string, test: string): int
965{
966	for(tmpl := l; tmpl != nil; tmpl = tl tmpl)
967		if (hd tmpl == test)
968			return 1;
969	return 0;
970}
971
972Select.delselection(s: self ref Select, fname, tkpath: string)
973{
974	f := getframe(s, fname);
975	(row, nil) := getrowcol(s.top, tkpath);
976	slaves := tkcmd(s.top, "grid slaves "+f.path+" -row "+row);
977	# sys->print("row %s: deleting: %s\n",row,slaves);
978	tkcmd(s.top, "grid rowdelete "+f.path+" "+row);
979	tkcmd(s.top, "destroy "+slaves);
980	# Select the next one if the item deleted was selected
981	if (f.selected == tkpath) {
982		f.selected = nil;
983		for (;;) {
984			slaves = tkcmd(s.top, "grid slaves "+f.path+" -row "+row);
985			if (slaves != nil)
986				break;
987			r := (int row) - 1;
988			if (r < 1)
989				return;
990			row = string r;
991		}
992		(nil, lst) := sys->tokenize(slaves, " ");
993		if (lst != nil)
994			s.select(fname, hd lst, SELECT);
995	}
996}
997
998getrowcol(top: ref Tk->Toplevel, s: string): (string, string)
999{
1000	row := "";
1001	col := "";
1002	(nil, lst) := sys->tokenize(tkcmd(top, "grid info "+s), " \t\n");
1003	for (; lst != nil; lst = tl lst) {
1004		if (hd lst == "-row")
1005			row = hd tl lst;
1006		else if (hd lst == "-column")
1007			col = hd tl lst;
1008	}
1009	return (row, col);
1010}
1011
1012Select.select(s: self ref Select, fname, tkpath: string, action: int)
1013{
1014	f := getframe(s, fname);
1015	if (action == SELECT && f.selected == tkpath)
1016		return;
1017	if (f.selected != nil)
1018		tkcmd(s.top, f.selected+" configure -bg "+bgnorm);
1019	if ((action == TOGGLE && f.selected == tkpath) || action == DESELECT)
1020		f.selected = nil;
1021	else {
1022		tkcmd(s.top, tkpath+" configure -bg "+bgselect);
1023		f.selected = tkpath;
1024	}
1025}
1026
1027Select.defaultaction(s: self ref Select, lst: list of string)
1028{
1029	case hd lst {
1030		"but1" =>
1031			s.select(s.currfname, hd tl lst, TOGGLE);
1032		"scale" =>
1033			tkcmd(s.top, hd tl lst+" configure -text {"+hd tl tl lst+"}");
1034	}
1035}
1036
1037Select.getselected(s: self ref Select, fname: string): string
1038{
1039	f := getframe(s, fname);
1040	return f.selected;
1041}
1042
1043Select.getselection(s: self ref Select, fname: string): list of (string, list of ref Parameter)
1044{
1045	retlist : list of (string, list of ref Parameter) = nil;
1046	row := 1;
1047	f := getframe(s, fname);
1048	for (;;) {
1049		slaves := tkcmd(s.top, "grid slaves "+f.path+" -row "+string (row++));
1050		# sys->print("slaves: %s\n",slaves);
1051		if (slaves == nil || slaves[0] == '!')
1052			break;
1053		(nil, lst) := sys->tokenize(slaves, " ");
1054		tkpath := hd lst;
1055		lst = tl lst;
1056		lp : list of ref Parameter = nil;
1057		for (; lst != nil; lst = tl lst) {
1058			pslaves := tkcmd(s.top, "grid slaves "+hd lst);
1059			(nil, plist) := sys->tokenize(pslaves, " ");
1060			# sys->print("slaves of %s - hd plist: '%s'\n",hd lst, hd plist);
1061			case (hd plist)[len hd plist - 3:] {
1062				".eA" or ".lA" =>
1063					argname := tkcmd(s.top, hd lst+".lA cget -text");
1064					argval := tkcmd(s.top, hd lst+".eA get");
1065					lp = ref Parameter.ArgOut(argname, argval) :: lp;
1066				".sI" or ".lI" =>
1067					val := int tkcmd(s.top, hd lst+".lI cget -text");
1068					lp = ref Parameter.IntOut(val) :: lp;
1069			}
1070		}
1071		retlist = (tkpath, lp) :: retlist;
1072	}
1073	return retlist;
1074}
1075
1076Select.resize(s: self ref Select, width, height: int)
1077{
1078	ws := int tkcmd(s.top, ".fselect.sy cget -width");
1079	hs := int tkcmd(s.top, ".fselect.sx cget -height");
1080
1081	tkcmd(s.top, ".fselect.c configure -width "+string (width - ws - 8)+
1082			" -height "+string (height - hs - 8));
1083	f := getframe(s, s.currfname);
1084	if (f != nil)
1085		setselectscrollr(s, f.path);
1086
1087	tkcmd(s.top, "update");
1088}
1089
1090File.eq(a,b: File): int
1091{
1092	if (a.path != b.path || a.qid != b.qid)
1093		return 0;
1094	return 1;
1095}
1096
1097
1098######################## General Functions ########################
1099
1100setcentre(top1, top2: ref Tk->Toplevel)
1101{
1102	x1 := int tkcmd(top1, ". cget -actx");
1103	y1 := int tkcmd(top1, ". cget -acty");
1104	h1 := int tkcmd(top1, ". cget -height");
1105	w1 := int tkcmd(top1, ". cget -width");
1106
1107	h2 := int tkcmd(top2, ".f cget -height");
1108	w2 := int tkcmd(top2, ".f cget -width");
1109
1110	newx := (x1 + (w1 / 2)) - (w2/2);
1111	newy := (y1 + (h1 / 2)) - (h2/2);
1112	tkcmd(top2, ". configure -x "+string newx+" -y "+string newy);
1113}
1114
1115abs(x: int): int
1116{
1117	if (x < 0)
1118		return -x;
1119	return x;
1120}
1121
1122prevpath(path: string): string
1123{
1124	if (path == nil)
1125		return nil;
1126	p := isatback(path[:len path - 1], "/");
1127	if (p == -1)
1128		return nil;
1129	return path[:p+1];
1130}
1131
1132isat(s, test: string): int
1133{
1134	if (len test > len s)
1135		return -1;
1136	for (i := 0; i < (1 + len s - len test); i++)
1137		if (test == s[i:i+len test])
1138			return i;
1139	return -1;
1140}
1141
1142isatback(s, test: string): int
1143{
1144	if (len test > len s)
1145		return -1;
1146	for (i := len s - len test; i >= 0; i--)
1147		if (test == s[i:i+len test])
1148			return i;
1149	return -1;
1150}
1151
1152tkcmd(top: ref Tk->Toplevel, cmd: string): string
1153{
1154	e := tk->cmd(top, cmd);
1155	if (e != "" && e[0] == '!')
1156		sys->print("Tk error: '%s': %s\n",cmd,e);
1157	return e;
1158}
1159
1160tkcmds(top: ref Tk->Toplevel, a: array of string)
1161{
1162	for (j := 0; j < len a; j++)
1163		tkcmd(top, a[j]);
1164}
1165
1166badmod(path: string)
1167{
1168	sys->print("Browser: failed to load: %s\n",path);
1169	exit;
1170}
1171