xref: /inferno-os/appl/wm/view.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement View;
2
3include "sys.m";
4	sys: Sys;
5
6include "draw.m";
7	draw: Draw;
8	Context, Rect, Point, Display, Screen, Image: import draw;
9
10include "bufio.m";
11	bufio: Bufio;
12	Iobuf: import bufio;
13
14include "imagefile.m";
15	imageremap: Imageremap;
16	readgif: RImagefile;
17	readjpg: RImagefile;
18	readxbitmap: RImagefile;
19	readpng: RImagefile;
20
21include "tk.m";
22	tk: Tk;
23	Toplevel: import tk;
24
25include	"tkclient.m";
26	tkclient: Tkclient;
27
28include "selectfile.m";
29	selectfile: Selectfile;
30
31include	"arg.m";
32
33include	"plumbmsg.m";
34	plumbmsg: Plumbmsg;
35	Msg: import plumbmsg;
36
37stderr: ref Sys->FD;
38display: ref Display;
39x := 25;
40y := 25;
41img_patterns: list of string;
42plumbed := 0;
43background: ref Image;
44
45View: module
46{
47	init:	fn(ctxt: ref Draw->Context, argv: list of string);
48};
49
50init(ctxt: ref Draw->Context, argv: list of string)
51{
52	spawn realinit(ctxt, argv);
53}
54
55realinit(ctxt: ref Draw->Context, argv: list of string)
56{
57	sys = load Sys Sys->PATH;
58	if (ctxt == nil) {
59		sys->fprint(sys->fildes(2), "view: no window context\n");
60		raise "fail:bad context";
61	}
62	draw = load Draw Draw->PATH;
63	tk = load Tk Tk->PATH;
64	tkclient = load Tkclient Tkclient->PATH;
65	selectfile = load Selectfile Selectfile->PATH;
66
67	sys->pctl(Sys->NEWPGRP, nil);
68	tkclient->init();
69	selectfile->init();
70
71	stderr = sys->fildes(2);
72	display = ctxt.display;
73	background = display.color(16r222222ff);
74
75	arg := load Arg Arg->PATH;
76	if(arg == nil)
77		badload(Arg->PATH);
78
79	img_patterns = list of {
80		"*.bit (Compressed image files)",
81		"*.gif (GIF image files)",
82		"*.jpg (JPEG image files)",
83		"*.jpeg (JPEG image files)",
84		"*.png (PNG image files)",
85		"*.xbm (X Bitmap image files)"
86		};
87
88	imageremap = load Imageremap Imageremap->PATH;
89	if(imageremap == nil)
90		badload(Imageremap->PATH);
91
92	bufio = load Bufio Bufio->PATH;
93	if(bufio == nil)
94		badload(Bufio->PATH);
95
96
97	arg->init(argv);
98	errdiff := 1;
99	while((c := arg->opt()) != 0)
100		case c {
101		'f' =>
102			errdiff = 0;
103		'i' =>
104			if(!plumbed){
105				plumbmsg = load Plumbmsg Plumbmsg->PATH;
106				if(plumbmsg != nil && plumbmsg->init(1, "view", 1000) >= 0)
107					plumbed = 1;
108			}
109		}
110	argv = arg->argv();
111	arg = nil;
112	if(argv == nil && !plumbed){
113		f := selectfile->filename(ctxt, nil, "View file name", img_patterns, nil);
114		if(f == "") {
115			#spawn view(nil, nil, "");
116			return;
117		}
118		argv = f :: nil;
119	}
120
121
122	for(;;){
123		file: string;
124		if(argv != nil){
125			file = hd argv;
126			argv = tl argv;
127			if(file == "-f"){
128				errdiff = 0;
129				continue;
130			}
131		}else if(plumbed){
132			file = plumbfile();
133			if(file == nil)
134				break;
135			errdiff = 1;	# set this from attributes?
136		}else
137			break;
138
139		(ims, masks, err) := readimages(file, errdiff);
140
141		if(ims == nil)
142			sys->fprint(stderr, "view: can't read %s: %s\n", file, err);
143		else
144			spawn view(ctxt, ims, masks, file);
145	}
146}
147
148badload(s: string)
149{
150	sys->fprint(stderr, "view: can't load %s: %r\n", s);
151	raise "fail:load";
152}
153
154readimages(file: string, errdiff: int) : (array of ref Image, array of ref Image, string)
155{
156	im := display.open(file);
157
158	if(im != nil)
159		return (array[1] of {im}, array[1] of ref Image, nil);
160
161	fd := bufio->open(file, Sys->OREAD);
162	if(fd == nil)
163		return (nil, nil, sys->sprint("%r"));
164
165	(mod, err1) := filetype(file, fd);
166	if(mod == nil)
167		return (nil, nil, err1);
168
169	(ai, err2) := mod->readmulti(fd);
170	if(ai == nil)
171		return (nil, nil, err2);
172	if(err2 != "")
173		sys->fprint(stderr, "view: %s: %s\n", file, err2);
174	ims := array[len ai] of ref Image;
175	masks := array[len ai] of ref Image;
176	for(i := 0; i < len ai; i++){
177		masks[i] = transparency(ai[i], file);
178
179		# if transparency is enabled, errdiff==1 is probably a mistake,
180		# but there's no easy solution.
181		(ims[i], err2) = imageremap->remap(ai[i], display, errdiff);
182		if(ims[i] == nil)
183			return(nil, nil, err2);
184	}
185	return (ims, masks, nil);
186}
187
188viewcfg := array[] of {
189	"panel .p",
190	"menu .m",
191	".m add command -label Open -command {send cmd open}",
192	".m add command -label Grab -command {send cmd grab} -state disabled",
193	".m add command -label Save -command {send cmd save}",
194	"pack .p -side bottom -fill both -expand 1",
195	"bind .p <Button-3> {send cmd but3 %X %Y}",
196	"bind .p <Motion-Button-3> {}",
197	"bind .p <ButtonRelease-3> {}",
198	"bind .p <Button-1> {send but1 %X %Y}",
199};
200
201DT: con 250;
202
203timer(dt: int, ticks, pidc: chan of int)
204{
205	pidc <-= sys->pctl(0, nil);
206	for(;;){
207		sys->sleep(dt);
208		ticks <-= 1;
209	}
210}
211
212view(ctxt: ref Context, ims, masks: array of ref Image, file: string)
213{
214	file = lastcomponent(file);
215	(t, titlechan) := tkclient->toplevel(ctxt, "", "view: "+file, Tkclient->Hide);
216
217	cmd := chan of string;
218	tk->namechan(t, cmd, "cmd");
219	but1 := chan of string;
220	tk->namechan(t, but1, "but1");
221
222	for (c:=0; c<len viewcfg; c++)
223		tk->cmd(t, viewcfg[c]);
224	tk->cmd(t, "update");
225
226	image := display.newimage(ims[0].r, ims[0].chans, 0, Draw->White);
227	if (image == nil) {
228		sys->fprint(stderr, "view: can't create image: %r\n");
229		return;
230	}
231	imconfig(t, image);
232	image.draw(image.r, ims[0], masks[0], ims[0].r.min);
233	tk->putimage(t, ".p", image, nil);
234	tk->cmd(t, "update");
235
236	pid := -1;
237	ticks := chan of int;
238	if(len ims > 1){
239		pidc := chan of int;
240		spawn timer(DT, ticks, pidc);
241		pid = <-pidc;
242	}
243	imno := 0;
244	grabbing := 0;
245	tkclient->onscreen(t, nil);
246	tkclient->startinput(t, "kbd"::"ptr"::nil);
247
248
249	for(;;) alt{
250	s := <-t.ctxt.kbd =>
251		tk->keyboard(t, s);
252	s := <-t.ctxt.ptr =>
253		tk->pointer(t, *s);
254	s := <-t.ctxt.ctl or
255	s = <-t.wreq or
256	s = <-titlechan =>
257		tkclient->wmctl(t, s);
258
259	<-ticks =>
260		if(masks[imno] != nil)
261			paneldraw(t, image, image.r, background, nil, image.r.min);
262		++imno;
263		if(imno >= len ims)
264			imno = 0;
265		paneldraw(t, image, ims[imno].r, ims[imno], masks[imno], ims[imno].r.min);
266		tk->cmd(t, "update");
267
268	s := <-cmd =>
269		(nil, l) := sys->tokenize(s, " ");
270		case (hd l) {
271		"open" =>
272			spawn open(ctxt, t);
273		"grab" =>
274			tk->cmd(t, "cursor -bitmap cursor.drag; grab set .p");
275			grabbing = 1;
276		"save" =>
277			patterns := list of {
278				"*.bit (Inferno image files)",
279				"*.gif (GIF image files)",
280				"*.jpg (JPEG image files)",
281				"* (All files)"
282			};
283			f := selectfile->filename(ctxt, t.image, "Save file name",
284				patterns, nil);
285			if(f != "") {
286				fd := sys->create(f, Sys->OWRITE, 8r664);
287				if(fd != nil)
288					display.writeimage(fd, ims[0]);
289			}
290		"but3" =>
291			if(!grabbing) {
292				xx := int hd tl l - 50;
293				yy := int hd tl tl l - int tk->cmd(t, ".m yposition 0") - 10;
294				tk->cmd(t, ".m activate 0; .m post "+string xx+" "+string yy+
295					"; grab set .m; update");
296			}
297		}
298	s := <- but1 =>
299			if(grabbing) {
300				(nil, l) := sys->tokenize(s, " ");
301				xx := int hd l;
302				yy := int hd tl l;
303#				grabtop := tk->intop(ctxt.screen, xx, yy);
304#				if(grabtop != nil) {
305#					cim := grabtop.image;
306#					imr := Rect((0,0), (cim.r.dx(), cim.r.dy()));
307#					image = display.newimage(imr, cim.chans, 0, draw->White);
308#					if(image == nil){
309#						sys->fprint(stderr, "view: can't allocate image\n");
310#						exit;
311#					}
312#					image.draw(imr, cim, nil, cim.r.min);
313#					tk->cmd(t, ".Wm_t.title configure -text {View: grabbed}");
314#					imconfig(t, image);
315#					tk->putimage(t, ".p", image, nil);
316#					tk->cmd(t, "update");
317#					# Would be nicer if this could be spun off cleanly
318#					ims = array[1] of {image};
319#					masks = array[1] of ref Image;
320#					imno = 0;
321#					grabtop = nil;
322#					cim = nil;
323#				}
324				tk->cmd(t, "cursor -default; grab release .p");
325				grabbing = 0;
326			}
327	}
328}
329
330open(ctxt: ref Context, t: ref tk->Toplevel)
331{
332	f := selectfile->filename(ctxt, t.image, "View file name", img_patterns, nil);
333	t = nil;
334	if(f != "") {
335		(ims, masks, err) := readimages(f, 1);
336		if(ims == nil)
337			sys->fprint(stderr, "view: can't read %s: %s\n", f, err);
338		else
339			view(ctxt, ims, masks, f);
340	}
341}
342
343lastcomponent(path: string) : string
344{
345	for(k:=len path-2; k>=0; k--)
346		if(path[k] == '/'){
347			path = path[k+1:];
348			break;
349		}
350	return path;
351}
352
353imconfig(t: ref Toplevel, im: ref Draw->Image)
354{
355	width := im.r.dx();
356	height := im.r.dy();
357	tk->cmd(t, ".p configure -width " + string width
358		+ " -height " + string height + "; update");
359}
360
361plumbfile(): string
362{
363	if(!plumbed)
364		return nil;
365	for(;;){
366		msg := Msg.recv();
367		if(msg == nil){
368			sys->print("view: can't read /chan/plumb.view: %r\n");
369			return nil;
370		}
371		if(msg.kind != "text"){
372			sys->print("view: can't interpret '%s' kind of message\n", msg.kind);
373			continue;
374		}
375		file := string msg.data;
376		if(len file>0 && file[0]!='/' && len msg.dir>0){
377			if(msg.dir[len msg.dir-1] == '/')
378				file = msg.dir+file;
379			else
380				file = msg.dir+"/"+file;
381		}
382		return file;
383	}
384}
385
386Tab: adt
387{
388	suf:	string;
389	path:	string;
390	mod:	RImagefile;
391};
392
393GIF, JPG, PIC, PNG, XBM: con iota;
394
395tab := array[] of
396{
397	GIF => Tab(".gif",	RImagefile->READGIFPATH,	nil),
398	JPG => Tab(".jpg",	RImagefile->READJPGPATH,	nil),
399	PIC => Tab(".pic",	RImagefile->READPICPATH,	nil),
400	XBM => Tab(".xbm",	RImagefile->READXBMPATH,	nil),
401	PNG => Tab(".png",	RImagefile->READPNGPATH,	nil),
402};
403
404filetype(file: string, fd: ref Iobuf): (RImagefile, string)
405{
406	for(i:=0; i<len tab; i++){
407		n := len tab[i].suf;
408		if(len file>n && file[len file-n:]==tab[i].suf)
409			return loadmod(i);
410	}
411
412	# sniff the header looking for a magic number
413	buf := array[20] of byte;
414	if(fd.read(buf, len buf) != len buf)
415		return (nil, sys->sprint("%r"));
416	fd.seek(big 0, 0);
417	if(string buf[0:6]=="GIF87a" || string buf[0:6]=="GIF89a")
418		return loadmod(GIF);
419	if(string buf[0:5] == "TYPE=")
420		return loadmod(PIC);
421	jpmagic := array[] of {byte 16rFF, byte 16rD8, byte 16rFF, byte 16rE0,
422		byte 0, byte 0, byte 'J', byte 'F', byte 'I', byte 'F', byte 0};
423	if(eqbytes(buf, jpmagic))
424		return loadmod(JPG);
425	pngmagic := array[] of {byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10};
426	if(eqbytes(buf, pngmagic))
427		return loadmod(PNG);
428	if(string buf[0:7] == "#define")
429		return loadmod(XBM);
430	return (nil, "can't recognize file type");
431}
432
433eqbytes(buf, magic: array of byte): int
434{
435	for(i:=0; i<len magic; i++)
436		if(magic[i]>byte 0 && buf[i]!=magic[i])
437			return 0;
438	return i == len magic;
439}
440
441loadmod(i: int): (RImagefile, string)
442{
443	if(tab[i].mod == nil){
444		tab[i].mod = load RImagefile tab[i].path;
445		if(tab[i].mod == nil)
446			sys->fprint(stderr, "view: can't find %s reader: %r\n", tab[i].suf);
447		else
448			tab[i].mod->init(bufio);
449	}
450	return (tab[i].mod, nil);
451}
452
453transparency(r: ref RImagefile->Rawimage, file: string): ref Image
454{
455	if(r.transp == 0)
456		return nil;
457	if(r.nchans != 1){
458		sys->fprint(stderr, "view: can't do transparency for multi-channel image %s\n", file);
459		return nil;
460	}
461	i := display.newimage(r.r, display.image.chans, 0, 0);
462	if(i == nil){
463		sys->fprint(stderr, "view: can't allocate mask for %s: %r\n", file);
464		exit;
465	}
466	pic := r.chans[0];
467	npic := len pic;
468	mpic := array[npic] of byte;
469	index := r.trindex;
470	for(j:=0; j<npic; j++)
471		if(pic[j] == index)
472			mpic[j] = byte 0;
473		else
474			mpic[j] = byte 16rFF;
475	i.writepixels(i.r, mpic);
476	return i;
477}
478
479paneldraw(t: ref Tk->Toplevel, dst: ref Image, r: Rect, src, mask: ref Image, p: Point)
480{
481	dst.draw(r, src, mask, p);
482	s := sys->sprint(".p dirty %d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
483	tk->cmd(t, s);
484}
485