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