xref: /plan9/sys/src/cmd/page/view.c (revision 282e677fa45fb578cdb8bc2c412ac084c367776e)
1 /*
2  * the actual viewer that handles screen stuff
3  */
4 
5 #include <u.h>
6 #include <libc.h>
7 #include <draw.h>
8 #include <cursor.h>
9 #include <event.h>
10 #include <bio.h>
11 #include <plumb.h>
12 #include <ctype.h>
13 #include <keyboard.h>
14 #include "page.h"
15 
16 Document *doc;
17 Image *im;
18 int page;
19 int upside = 0;
20 int showbottom = 0;		/* on the next showpage, move the image so the bottom is visible. */
21 
22 Rectangle ulrange;	/* the upper left corner of the image must be in this rectangle */
23 Point ul;			/* the upper left corner of the image is at this point on the screen */
24 
25 Point pclip(Point, Rectangle);
26 Rectangle mkrange(Rectangle screenr, Rectangle imr);
27 void redraw(Image*);
28 
29 Cursor reading={
30 	{-1, -1},
31 	{0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00,
32 	 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0,
33 	 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0,
34 	 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
35 	{0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00,
36 	 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0,
37 	 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40,
38 	 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
39 };
40 
41 Cursor query = {
42 	{-7,-7},
43 	{0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe,
44 	 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8,
45 	 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0,
46 	 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
47 	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c,
48 	 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0,
49 	 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80,
50 	 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
51 };
52 
53 enum {
54 	Left = 1,
55 	Middle = 2,
56 	Right = 4,
57 
58 	RMenu = 3,
59 };
60 
61 void
62 unhide(void)
63 {
64 	static int wctl = -1;
65 
66 	if(wctl < 0)
67 		wctl = open("/dev/wctl", OWRITE);
68 	if(wctl < 0)
69 		return;
70 
71 	write(wctl, "unhide", 6);
72 }
73 
74 int
75 max(int a, int b)
76 {
77 	return a > b ? a : b;
78 }
79 
80 int
81 min(int a, int b)
82 {
83 	return a < b ? a : b;
84 }
85 
86 
87 char*
88 menugen(int n)
89 {
90 	static char menustr[32];
91 	char *p;
92 	int len;
93 
94 	if(n == doc->npage)
95 		return "exit";
96 	if(n > doc->npage)
97 		return nil;
98 
99 	if(reverse)
100 		n = doc->npage-1-n;
101 
102 	p = doc->pagename(doc, n);
103 	len = (sizeof menustr)-2;
104 
105 	if(strlen(p) > len && strrchr(p, '/'))
106 		p = strrchr(p, '/')+1;
107 	if(strlen(p) > len)
108 		p = p+strlen(p)-len;
109 
110 	strcpy(menustr+1, p);
111 	if(page == n)
112 		menustr[0] = '>';
113 	else
114 		menustr[0] = ' ';
115 	return menustr;
116 }
117 
118 void
119 showpage(int page, Menu *m)
120 {
121 	Image *tmp;
122 
123 	if(doc->fwdonly)
124 		m->lasthit = 0;	/* this page */
125 	else
126 		m->lasthit = reverse ? doc->npage-1-page : page;
127 
128 	esetcursor(&reading);
129 	freeimage(im);
130 	if((page < 0 || page >= doc->npage) && !doc->fwdonly){
131 		im = nil;
132 		return;
133 	}
134 	im = doc->drawpage(doc, page);
135 	if(im == nil) {
136 		if(doc->fwdonly)	/* this is how we know we're out of pages */
137 			wexits(0);
138 
139 		im = xallocimage(display, Rect(0,0,50,50), GREY1, 1, DBlack);
140 		if(im == nil) {
141 			fprint(2, "out of memory: %r\n");
142 			wexits("memory");
143 		}
144 		string(im, ZP, display->white, ZP, display->defaultfont, "?");
145 	}else if(resizing){
146 		resize(Dx(im->r), Dy(im->r));
147 	}
148 	if(im->r.min.x > 0 || im->r.min.y > 0) {
149 		tmp = xallocimage(display, Rect(0, 0, Dx(im->r), Dy(im->r)), im->chan, 0, DNofill);
150 		if(tmp == nil) {
151 			fprint(2, "out of memory during showpage: %r\n");
152 			wexits("memory");
153 		}
154 		drawop(tmp, tmp->r, im, nil, im->r.min, S);
155 		freeimage(im);
156 		im = tmp;
157 	}
158 
159 	if(upside)
160 		rot180(im);
161 
162 	esetcursor(nil);
163 	if(showbottom){
164 		ul.y = screen->r.max.y - Dy(im->r);
165 		showbottom = 0;
166 	}
167 
168 	redraw(screen);
169 	flushimage(display, 1);
170 }
171 
172 char*
173 writebitmap(void)
174 {
175 	char basename[64];
176 	char name[64+30];
177 	static char result[200];
178 	char *p, *q;
179 	int fd;
180 
181 	if(im == nil)
182 		return "no image";
183 
184 	memset(basename, 0, sizeof basename);
185 	if(doc->docname)
186 		strncpy(basename, doc->docname, sizeof(basename)-1);
187 	else if((p = menugen(page)) && p[0] != '\0')
188 		strncpy(basename, p+1, sizeof(basename)-1);
189 
190 	if(basename[0]) {
191 		if(q = strrchr(basename, '/'))
192 			q++;
193 		else
194 			q = basename;
195 		if(p = strchr(q, '.'))
196 			*p = 0;
197 
198 		memset(name, 0, sizeof name);
199 		snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1);
200 		if(access(name, 0) >= 0) {
201 			strcat(name, "XXXX");
202 			mktemp(name);
203 		}
204 		if(access(name, 0) >= 0)
205 			return "couldn't think of a name for bitmap";
206 	} else {
207 		strcpy(name, "bitXXXX");
208 		mktemp(name);
209 		if(access(name, 0) >= 0)
210 			return "couldn't think of a name for bitmap";
211 	}
212 
213 	if((fd = create(name, OWRITE, 0666)) < 0) {
214 		snprint(result, sizeof result, "cannot create %s: %r", name);
215 		return result;
216 	}
217 
218 	if(writeimage(fd, im, 0) < 0) {
219 		snprint(result, sizeof result, "cannot writeimage: %r");
220 		close(fd);
221 		return result;
222 	}
223 	close(fd);
224 
225 	snprint(result, sizeof result, "wrote %s", name);
226 	return result;
227 }
228 
229 static void translate(Point);
230 
231 static int
232 showdata(Plumbmsg *msg)
233 {
234 	char *s;
235 
236 	s = plumblookup(msg->attr, "action");
237 	return s && strcmp(s, "showdata")==0;
238 }
239 
240 /* correspond to entries in miditems[] below,
241  * changing one means you need to change
242  */
243 enum{
244 	Restore = 0,
245 	Zin,
246 	Fit,
247 	Rot,
248 	Upside,
249 	Empty1,
250 	Next,
251 	Prev,
252 	Zerox,
253 	Empty2,
254 	Reverse,
255 	Del,
256 	Write,
257 	Empty3,
258 	Exit,
259 };
260 
261 void
262 viewer(Document *dd)
263 {
264 	int i, fd, n, oldpage;
265 	int nxt;
266 	Menu menu, midmenu;
267 	Mouse m;
268 	Event e;
269 	Point dxy, oxy, xy0;
270 	Rectangle r;
271 	Image *tmp;
272 	static char *fwditems[] = { "this page", "next page", "exit", 0 };
273  	static char *miditems[] = {
274  		"orig size",
275  		"zoom in",
276  		"fit window",
277  		"rotate 90",
278  		"upside down",
279  		"",
280  		"next",
281  		"prev",
282 		"zerox",
283  		"",
284  		"reverse",
285  		"discard",
286  		"write",
287  		"",
288  		"quit",
289  		0
290  	};
291 	char *s;
292 	enum { Eplumb = 4 };
293 	Plumbmsg *pm;
294 
295 	doc = dd;    /* save global for menuhit */
296 	ul = screen->r.min;
297 	einit(Emouse|Ekeyboard);
298 	if(doc->addpage != nil)
299 		eplumb(Eplumb, "image");
300 
301 	esetcursor(&reading);
302 	r.min = ZP;
303 
304 	/*
305 	 * im is a global pointer to the current image.
306 	 * eventually, i think we will have a layer between
307 	 * the display routines and the ps/pdf/whatever routines
308 	 * to perhaps cache and handle images of different
309 	 * sizes, etc.
310 	 */
311 	im = 0;
312 	page = reverse ? doc->npage-1 : 0;
313 
314 	if(doc->fwdonly) {
315 		menu.item = fwditems;
316 		menu.gen = 0;
317 		menu.lasthit = 0;
318 	} else {
319 		menu.item = 0;
320 		menu.gen = menugen;
321 		menu.lasthit = 0;
322 	}
323 
324 	midmenu.item = miditems;
325 	midmenu.gen = 0;
326 	midmenu.lasthit = Next;
327 
328 	showpage(page, &menu);
329 	esetcursor(nil);
330 
331 	nxt = 0;
332 	for(;;) {
333 		/*
334 		 * throughout, if doc->fwdonly is set, we restrict the functionality
335 		 * a fair amount.  we don't care about doc->npage anymore, and
336 		 * all that can be done is select the next page.
337 		 */
338 		switch(eread(Emouse|Ekeyboard|Eplumb, &e)){
339 		case Ekeyboard:
340 			if(e.kbdc <= 0xFF && isdigit(e.kbdc)) {
341 				nxt = nxt*10+e.kbdc-'0';
342 				break;
343 			} else if(e.kbdc != '\n')
344 				nxt = 0;
345 			switch(e.kbdc) {
346 			case 'r':	/* reverse page order */
347 				if(doc->fwdonly)
348 					break;
349 				reverse = !reverse;
350 				menu.lasthit = doc->npage-1-menu.lasthit;
351 
352 				/*
353 				 * the theory is that if we are reversing the
354 				 * document order and are on the first or last
355 				 * page then we're just starting and really want
356 		 	 	 * to view the other end.  maybe the if
357 				 * should be dropped and this should happen always.
358 				 */
359 				if(page == 0 || page == doc->npage-1) {
360 					page = doc->npage-1-page;
361 					showpage(page, &menu);
362 				}
363 				break;
364 			case 'w':	/* write bitmap of current screen */
365 				esetcursor(&reading);
366 				s = writebitmap();
367 				if(s)
368 					string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
369 						display->defaultfont, s);
370 				esetcursor(nil);
371 				flushimage(display, 1);
372 				break;
373 			case 'd':	/* remove image from working set */
374 				if(doc->rmpage && page < doc->npage) {
375 					if(doc->rmpage(doc, page) >= 0) {
376 						if(doc->npage < 0)
377 							wexits(0);
378 						if(page >= doc->npage)
379 							page = doc->npage-1;
380 						showpage(page, &menu);
381 					}
382 				}
383 				break;
384 			case 'q':
385 			case 0x04: /* ctrl-d */
386 				wexits(0);
387 			case 'u':
388 				if(im==nil)
389 					break;
390 				esetcursor(&reading);
391 				rot180(im);
392 				esetcursor(nil);
393 				upside = !upside;
394 				redraw(screen);
395 				flushimage(display, 1);
396 				break;
397 			case '-':
398 			case '\b':
399 			case Kleft:
400 				if(page > 0 && !doc->fwdonly) {
401 					--page;
402 					showpage(page, &menu);
403 				}
404 				break;
405 			case '\n':
406 				if(nxt) {
407 					nxt--;
408 					if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly)
409 						showpage(page=nxt, &menu);
410 					nxt = 0;
411 					break;
412 				}
413 				goto Gotonext;
414 			case Kright:
415 			case ' ':
416 			Gotonext:
417 				if(doc->npage && ++page >= doc->npage && !doc->fwdonly)
418 					wexits(0);
419 				showpage(page, &menu);
420 				break;
421 
422 			/*
423 			 * The upper y coordinate of the image is at ul.y in screen->r.
424 			 * Panning up means moving the upper left corner down.  If the
425 			 * upper left corner is currently visible, we need to go back a page.
426 			 */
427 			case Kup:
428 				if(screen->r.min.y <= ul.y && ul.y < screen->r.max.y){
429 					if(page > 0 && !doc->fwdonly){
430 						--page;
431 						showbottom = 1;
432 						showpage(page, &menu);
433 					}
434 				} else {
435 					i = Dy(screen->r)/2;
436 					if(i > 10)
437 						i -= 10;
438 					if(i+ul.y > screen->r.min.y)
439 						i = screen->r.min.y - ul.y;
440 					translate(Pt(0, i));
441 				}
442 				break;
443 
444 			/*
445 			 * If the lower y coordinate is on the screen, we go to the next page.
446 			 * The lower y coordinate is at ul.y + Dy(im->r).
447 			 */
448 			case Kdown:
449 				i = ul.y + Dy(im->r);
450 				if(screen->r.min.y <= i && i <= screen->r.max.y){
451 					ul.y = screen->r.min.y;
452 					goto Gotonext;
453 				} else {
454 					i = -Dy(screen->r)/2;
455 					if(i < -10)
456 						i += 10;
457 					if(i+ul.y+Dy(im->r) <= screen->r.max.y)
458 						i = screen->r.max.y - Dy(im->r) - ul.y - 1;
459 					translate(Pt(0, i));
460 				}
461 				break;
462 			default:
463 				esetcursor(&query);
464 				sleep(1000);
465 				esetcursor(nil);
466 				break;
467 			}
468 			break;
469 
470 		case Emouse:
471 			m = e.mouse;
472 			switch(m.buttons){
473 			case Left:
474 				oxy = m.xy;
475 				xy0 = oxy;
476 				do {
477 					dxy = subpt(m.xy, oxy);
478 					oxy = m.xy;
479 					translate(dxy);
480 					m = emouse();
481 				} while(m.buttons == Left);
482 				if(m.buttons) {
483 					dxy = subpt(xy0, oxy);
484 					translate(dxy);
485 				}
486 				break;
487 
488 			case Middle:
489 				if(doc->npage == 0)
490 					break;
491 
492 				n = emenuhit(Middle, &m, &midmenu);
493 				if(n == -1)
494 					break;
495 				switch(n){
496 				case Next: 	/* next */
497 					if(reverse)
498 						page--;
499 					else
500 						page++;
501 					if(page < 0) {
502 						if(reverse) return;
503 						else page = 0;
504 					}
505 
506 					if((page >= doc->npage) && !doc->fwdonly)
507 						return;
508 
509 					showpage(page, &menu);
510 					nxt = 0;
511 					break;
512 				case Prev:	/* prev */
513 					if(reverse)
514 						page++;
515 					else
516 						page--;
517 					if(page < 0) {
518 						if(reverse) return;
519 						else page = 0;
520 					}
521 
522 					if((page >= doc->npage) && !doc->fwdonly && !reverse)
523 						return;
524 
525 					showpage(page, &menu);
526 					nxt = 0;
527 					break;
528 				case Zerox:	/* prev */
529 					zerox();
530 					break;
531 				case Zin:	/* zoom in */
532 					{
533 						double delta;
534 						Rectangle r;
535 
536 						r = egetrect(Middle, &m);
537 						if((rectclip(&r, rectaddpt(im->r, ul)) == 0) ||
538 							Dx(r) == 0 || Dy(r) == 0)
539 							break;
540 						/* use the smaller side to expand */
541 						if(Dx(r) < Dy(r))
542 							delta = (double)Dx(im->r)/(double)Dx(r);
543 						else
544 							delta = (double)Dy(im->r)/(double)Dy(r);
545 
546 						esetcursor(&reading);
547 						tmp = xallocimage(display,
548 								Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta)),
549 								im->chan, 0, DBlack);
550 						if(tmp == nil) {
551 							fprint(2, "out of memory during zoom: %r\n");
552 							wexits("memory");
553 						}
554 						resample(im, tmp);
555 						freeimage(im);
556 						im = tmp;
557 						esetcursor(nil);
558 						ul = screen->r.min;
559 						redraw(screen);
560 						flushimage(display, 1);
561 						break;
562 					}
563 				case Fit:	/* fit */
564 					{
565 						double delta;
566 						Rectangle r;
567 
568 						delta = (double)Dx(screen->r)/(double)Dx(im->r);
569 						if((double)Dy(im->r)*delta > Dy(screen->r))
570 							delta = (double)Dy(screen->r)/(double)Dy(im->r);
571 
572 						r = Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta));
573 						esetcursor(&reading);
574 						tmp = xallocimage(display, r, im->chan, 0, DBlack);
575 						if(tmp == nil) {
576 							fprint(2, "out of memory during fit: %r\n");
577 							wexits("memory");
578 						}
579 						resample(im, tmp);
580 						freeimage(im);
581 						im = tmp;
582 						esetcursor(nil);
583 						ul = screen->r.min;
584 						redraw(screen);
585 						flushimage(display, 1);
586 						break;
587 					}
588 				case Rot:	/* rotate 90 */
589 					esetcursor(&reading);
590 					im = rot90(im);
591 					esetcursor(nil);
592 					redraw(screen);
593 					flushimage(display, 1);
594 					break;
595 				case Upside: 	/* upside-down */
596 					if(im==nil)
597 						break;
598 					esetcursor(&reading);
599 					rot180(im);
600 					esetcursor(nil);
601 					upside = !upside;
602 					redraw(screen);
603 					flushimage(display, 1);
604 					break;
605 				case Restore:	/* restore */
606 					showpage(page, &menu);
607 					break;
608 				case Reverse:	/* reverse */
609 					if(doc->fwdonly)
610 						break;
611 					reverse = !reverse;
612 					menu.lasthit = doc->npage-1-menu.lasthit;
613 
614 					if(page == 0 || page == doc->npage-1) {
615 						page = doc->npage-1-page;
616 						showpage(page, &menu);
617 					}
618 					break;
619 				case Write: /* write */
620 					esetcursor(&reading);
621 					s = writebitmap();
622 					if(s)
623 						string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
624 							display->defaultfont, s);
625 					esetcursor(nil);
626 					flushimage(display, 1);
627 					break;
628 				case Del: /* delete */
629 					if(doc->rmpage && page < doc->npage) {
630 						if(doc->rmpage(doc, page) >= 0) {
631 							if(doc->npage < 0)
632 								wexits(0);
633 							if(page >= doc->npage)
634 								page = doc->npage-1;
635 							showpage(page, &menu);
636 						}
637 					}
638 					break;
639 				case Exit:	/* exit */
640 					return;
641 				case Empty1:
642 				case Empty2:
643 				case Empty3:
644 					break;
645 
646 				};
647 
648 
649 
650 			case Right:
651 				if(doc->npage == 0)
652 					break;
653 
654 				oldpage = page;
655 				n = emenuhit(RMenu, &m, &menu);
656 				if(n == -1)
657 					break;
658 
659 				if(doc->fwdonly) {
660 					switch(n){
661 					case 0:	/* this page */
662 						break;
663 					case 1:	/* next page */
664 						showpage(++page, &menu);
665 						break;
666 					case 2:	/* exit */
667 						return;
668 					}
669 					break;
670 				}
671 
672 				if(n == doc->npage)
673 					return;
674 				else
675 					page = reverse ? doc->npage-1-n : n;
676 
677 				if(oldpage != page)
678 					showpage(page, &menu);
679 				nxt = 0;
680 				break;
681 			}
682 			break;
683 
684 		case Eplumb:
685 			pm = e.v;
686 			if(pm->ndata <= 0){
687 				plumbfree(pm);
688 				break;
689 			}
690 			if(showdata(pm)) {
691 				s = estrdup("/tmp/pageplumbXXXXXXX");
692 				fd = opentemp(s);
693 				write(fd, pm->data, pm->ndata);
694 				/* lose fd reference on purpose; the file is open ORCLOSE */
695 			} else if(pm->data[0] == '/') {
696 				s = estrdup(pm->data);
697 			} else {
698 				s = emalloc(strlen(pm->wdir)+1+pm->ndata+1);
699 				sprint(s, "%s/%s", pm->wdir, pm->data);
700 				cleanname(s);
701 			}
702 			if((i = doc->addpage(doc, s)) >= 0) {
703 				page = i;
704 				unhide();
705 				showpage(page, &menu);
706 			}
707 			free(s);
708 			plumbfree(pm);
709 			break;
710 		}
711 	}
712 }
713 
714 Image *gray;
715 
716 /*
717  * A draw operation that touches only the area contained in bot but not in top.
718  * mp and sp get aligned with bot.min.
719  */
720 static void
721 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
722 	Image *src, Point sp, Image *mask, Point mp, int op)
723 {
724 	Rectangle r;
725 	Point origin;
726 	Point delta;
727 
728 	USED(op);
729 
730 	if(Dx(bot)*Dy(bot) == 0)
731 		return;
732 
733 	/* no points in bot - top */
734 	if(rectinrect(bot, top))
735 		return;
736 
737 	/* bot - top ≡ bot */
738 	if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
739 		gendrawop(dst, bot, src, sp, mask, mp, op);
740 		return;
741 	}
742 
743 	origin = bot.min;
744 	/* split bot into rectangles that don't intersect top */
745 	/* left side */
746 	if(bot.min.x < top.min.x){
747 		r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
748 		delta = subpt(r.min, origin);
749 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
750 		bot.min.x = top.min.x;
751 	}
752 
753 	/* right side */
754 	if(bot.max.x > top.max.x){
755 		r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
756 		delta = subpt(r.min, origin);
757 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
758 		bot.max.x = top.max.x;
759 	}
760 
761 	/* top */
762 	if(bot.min.y < top.min.y){
763 		r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
764 		delta = subpt(r.min, origin);
765 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
766 		bot.min.y = top.min.y;
767 	}
768 
769 	/* bottom */
770 	if(bot.max.y > top.max.y){
771 		r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
772 		delta = subpt(r.min, origin);
773 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
774 		bot.max.y = top.max.y;
775 	}
776 }
777 
778 static void
779 drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p, int op)
780 {
781 	gendrawdiff(dst, bot, top, src, p, mask, p, op);
782 }
783 
784 /*
785  * Translate the image in the window by delta.
786  */
787 static void
788 translate(Point delta)
789 {
790 	Point u;
791 	Rectangle r, or;
792 
793 	if(im == nil)
794 		return;
795 
796 	u = pclip(addpt(ul, delta), ulrange);
797 	delta = subpt(u, ul);
798 	if(delta.x == 0 && delta.y == 0)
799 		return;
800 
801 	/*
802 	 * The upper left corner of the image is currently at ul.
803 	 * We want to move it to u.
804 	 */
805 	or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul);
806 	r = rectaddpt(or, delta);
807 
808 	drawop(screen, r, screen, nil, ul, S);
809 	ul = u;
810 
811 	/* fill in gray where image used to be but isn't. */
812 	drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP, S);
813 
814 	/* fill in black border */
815 	drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP, S);
816 
817 	/* fill in image where it used to be off the screen. */
818 	if(rectclip(&or, screen->r))
819 		drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min, S);
820 	else
821 		drawop(screen, r, im, nil, im->r.min, S);
822 	flushimage(display, 1);
823 }
824 
825 void
826 redraw(Image *screen)
827 {
828 	Rectangle r;
829 
830 	if(im == nil)
831 		return;
832 
833 	ulrange.max = screen->r.max;
834 	ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r)));
835 
836 	ul = pclip(ul, ulrange);
837 	drawop(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min)), S);
838 
839 	if(im->repl)
840 		return;
841 
842 	/* fill in any outer edges */
843 	/* black border */
844 	r = rectaddpt(im->r, subpt(ul, im->r.min));
845 	border(screen, r, -2, display->black, ZP);
846 	r.min = subpt(r.min, Pt(2,2));
847 	r.max = addpt(r.max, Pt(2,2));
848 
849 	/* gray for the rest */
850 	if(gray == nil) {
851 		gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF);
852 		if(gray == nil) {
853 			fprint(2, "g out of memory: %r\n");
854 			wexits("mem");
855 		}
856 	}
857 	border(screen, r, -4000, gray, ZP);
858 //	flushimage(display, 0);
859 }
860 
861 void
862 eresized(int new)
863 {
864 	Rectangle r;
865 	r = screen->r;
866 	if(new && getwindow(display, Refnone) < 0)
867 		fprint(2,"can't reattach to window");
868 	ul = addpt(ul, subpt(screen->r.min, r.min));
869 	redraw(screen);
870 }
871 
872 /* clip p to be in r */
873 Point
874 pclip(Point p, Rectangle r)
875 {
876 	if(p.x < r.min.x)
877 		p.x = r.min.x;
878 	else if(p.x >= r.max.x)
879 		p.x = r.max.x-1;
880 
881 	if(p.y < r.min.y)
882 		p.y = r.min.y;
883 	else if(p.y >= r.max.y)
884 		p.y = r.max.y-1;
885 
886 	return p;
887 }
888 
889 /*
890  * resize is perhaps a misnomer.
891  * this really just grows the window to be at least dx across
892  * and dy high.  if the window hits the bottom or right edge,
893  * it is backed up until it hits the top or left edge.
894  */
895 void
896 resize(int dx, int dy)
897 {
898 	static Rectangle sr;
899 	Rectangle r, or;
900 
901 	dx += 2*Borderwidth;
902 	dy += 2*Borderwidth;
903 	if(wctlfd < 0){
904 		wctlfd = open("/dev/wctl", OWRITE);
905 		if(wctlfd < 0)
906 			return;
907 	}
908 
909 	r = insetrect(screen->r, -Borderwidth);
910 	if(Dx(r) >= dx && Dy(r) >= dy)
911 		return;
912 
913 	if(Dx(sr)*Dy(sr) == 0)
914 		sr = screenrect();
915 
916 	or = r;
917 
918 	r.max.x = max(r.min.x+dx, r.max.x);
919 	r.max.y = max(r.min.y+dy, r.max.y);
920 	if(r.max.x > sr.max.x){
921 		if(Dx(r) > Dx(sr)){
922 			r.min.x = 0;
923 			r.max.x = sr.max.x;
924 		}else
925 			r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0));
926 	}
927 	if(r.max.y > sr.max.y){
928 		if(Dy(r) > Dy(sr)){
929 			r.min.y = 0;
930 			r.max.y = sr.max.y;
931 		}else
932 			r = rectaddpt(r, Pt(0, sr.max.y-r.max.y));
933 	}
934 
935 	/*
936 	 * Sometimes we can't actually grow the window big enough,
937 	 * and resizing it to the same shape makes it flash.
938 	 */
939 	if(Dx(r) == Dx(or) && Dy(r) == Dy(or))
940 		return;
941 
942 	fprint(wctlfd, "resize -minx %d -miny %d -maxx %d -maxy %d\n",
943 		r.min.x, r.min.y, r.max.x, r.max.y);
944 }
945 
946 /*
947  * If we allocimage after a resize but before flushing the draw buffer,
948  * we won't have seen the reshape event, and we won't have called
949  * getwindow, and allocimage will fail.  So we flushimage before every alloc.
950  */
951 Image*
952 xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val)
953 {
954 	flushimage(display, 0);
955 	return allocimage(d, r, chan, repl, val);
956 }
957 
958 /* all code below this line should be in the library, but is stolen from colors instead */
959 static char*
960 rdenv(char *name)
961 {
962 	char *v;
963 	int fd, size;
964 
965 	fd = open(name, OREAD);
966 	if(fd < 0)
967 		return 0;
968 	size = seek(fd, 0, 2);
969 	v = malloc(size+1);
970 	if(v == 0){
971 		fprint(2, "page: can't malloc: %r\n");
972 		wexits("no mem");
973 	}
974 	seek(fd, 0, 0);
975 	read(fd, v, size);
976 	v[size] = 0;
977 	close(fd);
978 	return v;
979 }
980 
981 void
982 newwin(void)
983 {
984 	char *srv, *mntsrv;
985 	char spec[100];
986 	int srvfd, cons, pid;
987 
988 	switch(rfork(RFFDG|RFPROC|RFNAMEG|RFENVG|RFNOTEG|RFNOWAIT)){
989 	case -1:
990 		fprint(2, "page: can't fork: %r\n");
991 		wexits("no fork");
992 	case 0:
993 		break;
994 	default:
995 		wexits(0);
996 	}
997 
998 	srv = rdenv("/env/wsys");
999 	if(srv == 0){
1000 		mntsrv = rdenv("/mnt/term/env/wsys");
1001 		if(mntsrv == 0){
1002 			fprint(2, "page: can't find $wsys\n");
1003 			wexits("srv");
1004 		}
1005 		srv = malloc(strlen(mntsrv)+10);
1006 		sprint(srv, "/mnt/term%s", mntsrv);
1007 		free(mntsrv);
1008 		pid  = 0;			/* can't send notes to remote processes! */
1009 	}else
1010 		pid = getpid();
1011 	srvfd = open(srv, ORDWR);
1012 	free(srv);
1013 	if(srvfd == -1){
1014 		fprint(2, "page: can't open %s: %r\n", srv);
1015 		wexits("no srv");
1016 	}
1017 	sprint(spec, "new -pid %d", pid);
1018 	if(mount(srvfd, -1, "/mnt/wsys", 0, spec) == -1){
1019 		fprint(2, "page: can't mount /mnt/wsys: %r (spec=%s)\n", spec);
1020 		wexits("no mount");
1021 	}
1022 	close(srvfd);
1023 	unmount("/mnt/acme", "/dev");
1024 	bind("/mnt/wsys", "/dev", MBEFORE);
1025 	cons = open("/dev/cons", OREAD);
1026 	if(cons==-1){
1027 	NoCons:
1028 		fprint(2, "page: can't open /dev/cons: %r");
1029 		wexits("no cons");
1030 	}
1031 	dup(cons, 0);
1032 	close(cons);
1033 	cons = open("/dev/cons", OWRITE);
1034 	if(cons==-1)
1035 		goto NoCons;
1036 	dup(cons, 1);
1037 	dup(cons, 2);
1038 	close(cons);
1039 //	wctlfd = open("/dev/wctl", OWRITE);
1040 }
1041 
1042 Rectangle
1043 screenrect(void)
1044 {
1045 	int fd;
1046 	char buf[12*5];
1047 
1048 	fd = open("/dev/screen", OREAD);
1049 	if(fd == -1)
1050 		fd=open("/mnt/term/dev/screen", OREAD);
1051 	if(fd == -1){
1052 		fprint(2, "page: can't open /dev/screen: %r\n");
1053 		wexits("window read");
1054 	}
1055 	if(read(fd, buf, sizeof buf) != sizeof buf){
1056 		fprint(2, "page: can't read /dev/screen: %r\n");
1057 		wexits("screen read");
1058 	}
1059 	close(fd);
1060 	return Rect(atoi(buf+12), atoi(buf+24), atoi(buf+36), atoi(buf+48));
1061 }
1062 
1063 void
1064 zerox(void)
1065 {
1066 	int pfd[2];
1067 
1068 	pipe(pfd);
1069 	switch(rfork(RFFDG|RFREND|RFPROC)) {
1070 		case -1:
1071 			wexits("cannot fork in zerox: %r");
1072 		case 0:
1073 			dup(pfd[1], 0);
1074 			close(pfd[0]);
1075 			execl("/bin/page", "page", "-w", 0);
1076 			wexits("cannot exec in zerox: %r\n");
1077 		default:
1078 			close(pfd[1]);
1079 			writeimage(pfd[0], im, 0);
1080 			close(pfd[0]);
1081 			break;
1082 	}
1083 }
1084