ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/wm/view.b/
implement View;
include "sys.m";
sys: Sys;
include "draw.m";
draw: Draw;
Context, Rect, Point, Display, Screen, Image: import draw;
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "imagefile.m";
imageremap: Imageremap;
readgif: RImagefile;
readjpg: RImagefile;
readxbitmap: RImagefile;
readpng: RImagefile;
include "tk.m";
tk: Tk;
Toplevel: import tk;
include "tkclient.m";
tkclient: Tkclient;
include "selectfile.m";
selectfile: Selectfile;
include "arg.m";
include "plumbmsg.m";
plumbmsg: Plumbmsg;
Msg: import plumbmsg;
stderr: ref Sys->FD;
display: ref Display;
x := 25;
y := 25;
img_patterns: list of string;
plumbed := 0;
background: ref Image;
View: module
{
init: fn(ctxt: ref Draw->Context, argv: list of string);
};
init(ctxt: ref Draw->Context, argv: list of string)
{
spawn realinit(ctxt, argv);
}
realinit(ctxt: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
if (ctxt == nil) {
sys->fprint(sys->fildes(2), "view: no window context\n");
raise "fail:bad context";
}
draw = load Draw Draw->PATH;
tk = load Tk Tk->PATH;
tkclient = load Tkclient Tkclient->PATH;
selectfile = load Selectfile Selectfile->PATH;
sys->pctl(Sys->NEWPGRP, nil);
tkclient->init();
selectfile->init();
stderr = sys->fildes(2);
display = ctxt.display;
background = display.color(16r222222ff);
arg := load Arg Arg->PATH;
if(arg == nil)
badload(Arg->PATH);
img_patterns = list of {
"*.bit (Compressed image files)",
"*.gif (GIF image files)",
"*.jpg (JPEG image files)",
"*.jpeg (JPEG image files)",
"*.png (PNG image files)",
"*.xbm (X Bitmap image files)"
};
imageremap = load Imageremap Imageremap->PATH;
if(imageremap == nil)
badload(Imageremap->PATH);
bufio = load Bufio Bufio->PATH;
if(bufio == nil)
badload(Bufio->PATH);
arg->init(argv);
errdiff := 1;
while((c := arg->opt()) != 0)
case c {
'f' =>
errdiff = 0;
'i' =>
if(!plumbed){
plumbmsg = load Plumbmsg Plumbmsg->PATH;
if(plumbmsg != nil && plumbmsg->init(1, "view", 1000) >= 0)
plumbed = 1;
}
}
argv = arg->argv();
arg = nil;
if(argv == nil && !plumbed){
f := selectfile->filename(ctxt, nil, "View file name", img_patterns, nil);
if(f == "") {
#spawn view(nil, nil, "");
return;
}
argv = f :: nil;
}
for(;;){
file: string;
if(argv != nil){
file = hd argv;
argv = tl argv;
if(file == "-f"){
errdiff = 0;
continue;
}
}else if(plumbed){
file = plumbfile();
if(file == nil)
break;
errdiff = 1; # set this from attributes?
}else
break;
(ims, masks, err) := readimages(file, errdiff);
if(ims == nil)
sys->fprint(stderr, "view: can't read %s: %s\n", file, err);
else
spawn view(ctxt, ims, masks, file);
}
}
badload(s: string)
{
sys->fprint(stderr, "view: can't load %s: %r\n", s);
raise "fail:load";
}
readimages(file: string, errdiff: int) : (array of ref Image, array of ref Image, string)
{
im := display.open(file);
if(im != nil)
return (array[1] of {im}, array[1] of ref Image, nil);
fd := bufio->open(file, Sys->OREAD);
if(fd == nil)
return (nil, nil, sys->sprint("%r"));
(mod, err1) := filetype(file, fd);
if(mod == nil)
return (nil, nil, err1);
(ai, err2) := mod->readmulti(fd);
if(ai == nil)
return (nil, nil, err2);
if(err2 != "")
sys->fprint(stderr, "view: %s: %s\n", file, err2);
ims := array[len ai] of ref Image;
masks := array[len ai] of ref Image;
for(i := 0; i < len ai; i++){
masks[i] = transparency(ai[i], file);
# if transparency is enabled, errdiff==1 is probably a mistake,
# but there's no easy solution.
(ims[i], err2) = imageremap->remap(ai[i], display, errdiff);
if(ims[i] == nil)
return(nil, nil, err2);
}
return (ims, masks, nil);
}
viewcfg := array[] of {
"panel .p",
"menu .m",
".m add command -label Open -command {send cmd open}",
".m add command -label Grab -command {send cmd grab} -state disabled",
".m add command -label Save -command {send cmd save}",
"pack .p -side bottom -fill both -expand 1",
"bind .p <Button-3> {send cmd but3 %X %Y}",
"bind .p <Motion-Button-3> {}",
"bind .p <ButtonRelease-3> {}",
"bind .p <Button-1> {send but1 %X %Y}",
};
DT: con 250;
timer(dt: int, ticks, pidc: chan of int)
{
pidc <-= sys->pctl(0, nil);
for(;;){
sys->sleep(dt);
ticks <-= 1;
}
}
view(ctxt: ref Context, ims, masks: array of ref Image, file: string)
{
file = lastcomponent(file);
(t, titlechan) := tkclient->toplevel(ctxt, "", "view: "+file, Tkclient->Hide);
cmd := chan of string;
tk->namechan(t, cmd, "cmd");
but1 := chan of string;
tk->namechan(t, but1, "but1");
for (c:=0; c<len viewcfg; c++)
tk->cmd(t, viewcfg[c]);
tk->cmd(t, "update");
image := display.newimage(ims[0].r, ims[0].chans, 0, Draw->White);
if (image == nil) {
sys->fprint(stderr, "view: can't create image: %r\n");
return;
}
imconfig(t, image);
image.draw(image.r, ims[0], masks[0], ims[0].r.min);
tk->putimage(t, ".p", image, nil);
tk->cmd(t, "update");
pid := -1;
ticks := chan of int;
if(len ims > 1){
pidc := chan of int;
spawn timer(DT, ticks, pidc);
pid = <-pidc;
}
imno := 0;
grabbing := 0;
tkclient->onscreen(t, nil);
tkclient->startinput(t, "kbd"::"ptr"::nil);
for(;;) alt{
s := <-t.ctxt.kbd =>
tk->keyboard(t, s);
s := <-t.ctxt.ptr =>
tk->pointer(t, *s);
s := <-t.ctxt.ctl or
s = <-t.wreq or
s = <-titlechan =>
tkclient->wmctl(t, s);
<-ticks =>
if(masks[imno] != nil)
paneldraw(t, image, image.r, background, nil, image.r.min);
++imno;
if(imno >= len ims)
imno = 0;
paneldraw(t, image, ims[imno].r, ims[imno], masks[imno], ims[imno].r.min);
tk->cmd(t, "update");
s := <-cmd =>
(nil, l) := sys->tokenize(s, " ");
case (hd l) {
"open" =>
spawn open(ctxt, t);
"grab" =>
tk->cmd(t, "cursor -bitmap cursor.drag; grab set .p");
grabbing = 1;
"save" =>
patterns := list of {
"*.bit (Inferno image files)",
"*.gif (GIF image files)",
"*.jpg (JPEG image files)",
"* (All files)"
};
f := selectfile->filename(ctxt, t.image, "Save file name",
patterns, nil);
if(f != "") {
fd := sys->create(f, Sys->OWRITE, 8r664);
if(fd != nil)
display.writeimage(fd, ims[0]);
}
"but3" =>
if(!grabbing) {
xx := int hd tl l - 50;
yy := int hd tl tl l - int tk->cmd(t, ".m yposition 0") - 10;
tk->cmd(t, ".m activate 0; .m post "+string xx+" "+string yy+
"; grab set .m; update");
}
}
s := <- but1 =>
if(grabbing) {
(nil, l) := sys->tokenize(s, " ");
xx := int hd l;
yy := int hd tl l;
# grabtop := tk->intop(ctxt.screen, xx, yy);
# if(grabtop != nil) {
# cim := grabtop.image;
# imr := Rect((0,0), (cim.r.dx(), cim.r.dy()));
# image = display.newimage(imr, cim.chans, 0, draw->White);
# if(image == nil){
# sys->fprint(stderr, "view: can't allocate image\n");
# exit;
# }
# image.draw(imr, cim, nil, cim.r.min);
# tk->cmd(t, ".Wm_t.title configure -text {View: grabbed}");
# imconfig(t, image);
# tk->putimage(t, ".p", image, nil);
# tk->cmd(t, "update");
# # Would be nicer if this could be spun off cleanly
# ims = array[1] of {image};
# masks = array[1] of ref Image;
# imno = 0;
# grabtop = nil;
# cim = nil;
# }
tk->cmd(t, "cursor -default; grab release .p");
grabbing = 0;
}
}
}
open(ctxt: ref Context, t: ref tk->Toplevel)
{
f := selectfile->filename(ctxt, t.image, "View file name", img_patterns, nil);
t = nil;
if(f != "") {
(ims, masks, err) := readimages(f, 1);
if(ims == nil)
sys->fprint(stderr, "view: can't read %s: %s\n", f, err);
else
view(ctxt, ims, masks, f);
}
}
lastcomponent(path: string) : string
{
for(k:=len path-2; k>=0; k--)
if(path[k] == '/'){
path = path[k+1:];
break;
}
return path;
}
imconfig(t: ref Toplevel, im: ref Draw->Image)
{
width := im.r.dx();
height := im.r.dy();
tk->cmd(t, ".p configure -width " + string width
+ " -height " + string height + "; update");
}
plumbfile(): string
{
if(!plumbed)
return nil;
for(;;){
msg := Msg.recv();
if(msg == nil){
sys->print("view: can't read /chan/plumb.view: %r\n");
return nil;
}
if(msg.kind != "text"){
sys->print("view: can't interpret '%s' kind of message\n", msg.kind);
continue;
}
file := string msg.data;
if(len file>0 && file[0]!='/' && len msg.dir>0){
if(msg.dir[len msg.dir-1] == '/')
file = msg.dir+file;
else
file = msg.dir+"/"+file;
}
return file;
}
}
Tab: adt
{
suf: string;
path: string;
mod: RImagefile;
};
GIF, JPG, PIC, PNG, XBM: con iota;
tab := array[] of
{
GIF => Tab(".gif", RImagefile->READGIFPATH, nil),
JPG => Tab(".jpg", RImagefile->READJPGPATH, nil),
PIC => Tab(".pic", RImagefile->READPICPATH, nil),
XBM => Tab(".xbm", RImagefile->READXBMPATH, nil),
PNG => Tab(".png", RImagefile->READPNGPATH, nil),
};
filetype(file: string, fd: ref Iobuf): (RImagefile, string)
{
for(i:=0; i<len tab; i++){
n := len tab[i].suf;
if(len file>n && file[len file-n:]==tab[i].suf)
return loadmod(i);
}
# sniff the header looking for a magic number
buf := array[20] of byte;
if(fd.read(buf, len buf) != len buf)
return (nil, sys->sprint("%r"));
fd.seek(big 0, 0);
if(string buf[0:6]=="GIF87a" || string buf[0:6]=="GIF89a")
return loadmod(GIF);
if(string buf[0:5] == "TYPE=")
return loadmod(PIC);
jpmagic := array[] of {byte 16rFF, byte 16rD8, byte 16rFF, byte 16rE0,
byte 0, byte 0, byte 'J', byte 'F', byte 'I', byte 'F', byte 0};
if(eqbytes(buf, jpmagic))
return loadmod(JPG);
pngmagic := array[] of {byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10};
if(eqbytes(buf, pngmagic))
return loadmod(PNG);
if(string buf[0:7] == "#define")
return loadmod(XBM);
return (nil, "can't recognize file type");
}
eqbytes(buf, magic: array of byte): int
{
for(i:=0; i<len magic; i++)
if(magic[i]>byte 0 && buf[i]!=magic[i])
return 0;
return i == len magic;
}
loadmod(i: int): (RImagefile, string)
{
if(tab[i].mod == nil){
tab[i].mod = load RImagefile tab[i].path;
if(tab[i].mod == nil)
sys->fprint(stderr, "view: can't find %s reader: %r\n", tab[i].suf);
else
tab[i].mod->init(bufio);
}
return (tab[i].mod, nil);
}
transparency(r: ref RImagefile->Rawimage, file: string): ref Image
{
if(r.transp == 0)
return nil;
if(r.nchans != 1){
sys->fprint(stderr, "view: can't do transparency for multi-channel image %s\n", file);
return nil;
}
i := display.newimage(r.r, display.image.chans, 0, 0);
if(i == nil){
sys->fprint(stderr, "view: can't allocate mask for %s: %r\n", file);
exit;
}
pic := r.chans[0];
npic := len pic;
mpic := array[npic] of byte;
index := r.trindex;
for(j:=0; j<npic; j++)
if(pic[j] == index)
mpic[j] = byte 0;
else
mpic[j] = byte 16rFF;
i.writepixels(i.r, mpic);
return i;
}
paneldraw(t: ref Tk->Toplevel, dst: ref Image, r: Rect, src, mask: ref Image, p: Point)
{
dst.draw(r, src, mask, p);
s := sys->sprint(".p dirty %d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
tk->cmd(t, s);
}