|  | /* | 
|  | * the actual viewer that handles screen stuff | 
|  | */ | 
|  |  | 
|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <9pclient.h> | 
|  | #include <draw.h> | 
|  | #include <cursor.h> | 
|  | #include <mouse.h> | 
|  | #include <keyboard.h> | 
|  | #include <thread.h> | 
|  | #include <bio.h> | 
|  | #include <plumb.h> | 
|  | #include <ctype.h> | 
|  | #include "page.h" | 
|  |  | 
|  | Document *doc; | 
|  | Mousectl *mc; | 
|  | Image *im; | 
|  | Image *tofree; | 
|  | int page; | 
|  | int angle = 0; | 
|  | int showbottom = 0;		/* on the next showpage, move the image so the bottom is visible. */ | 
|  |  | 
|  | Rectangle ulrange;	/* the upper left corner of the image must be in this rectangle */ | 
|  | Point ul;			/* the upper left corner of the image is at this point on the screen */ | 
|  |  | 
|  | Point pclip(Point, Rectangle); | 
|  | Rectangle mkrange(Rectangle screenr, Rectangle imr); | 
|  | void redraw(Image*); | 
|  | void plumbproc(void*); | 
|  |  | 
|  | Cursor reading={ | 
|  | {-1, -1}, | 
|  | {0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, | 
|  | 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, | 
|  | 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, | 
|  | 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, }, | 
|  | {0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, | 
|  | 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, | 
|  | 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, | 
|  | 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, } | 
|  | }; | 
|  |  | 
|  | Cursor query = { | 
|  | {-7,-7}, | 
|  | {0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, | 
|  | 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8, | 
|  | 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0, | 
|  | 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, }, | 
|  | {0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c, | 
|  | 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0, | 
|  | 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80, | 
|  | 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, } | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | Left = 1, | 
|  | Middle = 2, | 
|  | Right = 4, | 
|  |  | 
|  | RMenu = 3, | 
|  | }; | 
|  |  | 
|  | static void | 
|  | delayfreeimage(Image *m) | 
|  | { | 
|  | if(m == tofree) | 
|  | return; | 
|  | if(tofree) | 
|  | freeimage(tofree); | 
|  | tofree = m; | 
|  | } | 
|  |  | 
|  | void | 
|  | unhide(void) | 
|  | { | 
|  | USED(nil); | 
|  | } | 
|  |  | 
|  | int | 
|  | max(int a, int b) | 
|  | { | 
|  | return a > b ? a : b; | 
|  | } | 
|  |  | 
|  | int | 
|  | min(int a, int b) | 
|  | { | 
|  | return a < b ? a : b; | 
|  | } | 
|  |  | 
|  |  | 
|  | char* | 
|  | menugen(int n) | 
|  | { | 
|  | static char menustr[32]; | 
|  | char *p; | 
|  | int len; | 
|  |  | 
|  | if(n == doc->npage) | 
|  | return "exit"; | 
|  | if(n > doc->npage) | 
|  | return nil; | 
|  |  | 
|  | if(reverse) | 
|  | n = doc->npage-1-n; | 
|  |  | 
|  | p = doc->pagename(doc, n); | 
|  | len = (sizeof menustr)-2; | 
|  |  | 
|  | if(strlen(p) > len && strrchr(p, '/')) | 
|  | p = strrchr(p, '/')+1; | 
|  | if(strlen(p) > len) | 
|  | p = p+strlen(p)-len; | 
|  |  | 
|  | strcpy(menustr+1, p); | 
|  | if(page == n) | 
|  | menustr[0] = '>'; | 
|  | else | 
|  | menustr[0] = ' '; | 
|  | return menustr; | 
|  | } | 
|  |  | 
|  | void | 
|  | showpage(int page, Menu *m) | 
|  | { | 
|  | if(doc->fwdonly) | 
|  | m->lasthit = 0;	/* this page */ | 
|  | else | 
|  | m->lasthit = reverse ? doc->npage-1-page : page; | 
|  |  | 
|  | setcursor(mc, &reading); | 
|  | delayfreeimage(nil); | 
|  | im = cachedpage(doc, angle, page); | 
|  | if(im == nil) | 
|  | wexits(0); | 
|  | if(resizing) | 
|  | resize(Dx(im->r), Dy(im->r)); | 
|  |  | 
|  | setcursor(mc, nil); | 
|  | if(showbottom){ | 
|  | ul.y = screen->r.max.y - Dy(im->r); | 
|  | showbottom = 0; | 
|  | } | 
|  |  | 
|  | if((doc->type == Tgfx) && fitwin) | 
|  | fit(); | 
|  | else{ | 
|  | redraw(screen); | 
|  | flushimage(display, 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | char* | 
|  | writebitmap(void) | 
|  | { | 
|  | char basename[64]; | 
|  | char name[64+30]; | 
|  | static char result[200]; | 
|  | char *p, *q; | 
|  | int fd = -1; | 
|  |  | 
|  | if(im == nil) | 
|  | return "no image"; | 
|  |  | 
|  | memset(basename, 0, sizeof basename); | 
|  | if(doc->docname) | 
|  | strncpy(basename, doc->docname, sizeof(basename)-1); | 
|  | else if((p = menugen(page)) && p[0] != '\0') | 
|  | strncpy(basename, p+1, sizeof(basename)-1); | 
|  |  | 
|  | if(basename[0]) { | 
|  | if(q = strrchr(basename, '/')) | 
|  | q++; | 
|  | else | 
|  | q = basename; | 
|  | if(p = strchr(q, '.')) | 
|  | *p = 0; | 
|  |  | 
|  | memset(name, 0, sizeof name); | 
|  | snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1); | 
|  | if(access(name, 0) >= 0) { | 
|  | strcat(name, "XXXX"); | 
|  | fd = mkstemp(name); | 
|  | } | 
|  | if(fd < 0) | 
|  | return "couldn't think of a name for bitmap"; | 
|  | } else { | 
|  | strcpy(name, "bitXXXX"); | 
|  | mkstemp(name); | 
|  | if(fd < 0) | 
|  | return "couldn't think of a name for bitmap"; | 
|  | } | 
|  |  | 
|  | if(fd < 0) { | 
|  | snprint(result, sizeof result, "cannot create %s: %r", name); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if(writeimage(fd, im, 0) < 0) { | 
|  | snprint(result, sizeof result, "cannot writeimage: %r"); | 
|  | close(fd); | 
|  | return result; | 
|  | } | 
|  | close(fd); | 
|  |  | 
|  | snprint(result, sizeof result, "wrote %s", name); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static void translate(Point); | 
|  |  | 
|  | static int | 
|  | showdata(Plumbmsg *msg) | 
|  | { | 
|  | char *s; | 
|  |  | 
|  | s = plumblookup(msg->attr, "action"); | 
|  | return s && strcmp(s, "showdata")==0; | 
|  | } | 
|  |  | 
|  | /* correspond to entries in miditems[] below, | 
|  | * changing one means you need to change | 
|  | */ | 
|  | enum{ | 
|  | Restore = 0, | 
|  | Zin, | 
|  | Fit, | 
|  | Rot, | 
|  | Upside, | 
|  | Empty1, | 
|  | Next, | 
|  | Prev, | 
|  | Zerox, | 
|  | Empty2, | 
|  | Reverse, | 
|  | Del, | 
|  | Write, | 
|  | Empty3, | 
|  | Exit, | 
|  | }; | 
|  |  | 
|  | void | 
|  | viewer(Document *dd) | 
|  | { | 
|  | int i, fd, n, oldpage; | 
|  | int nxt, a; | 
|  | Channel *cp; | 
|  | Menu menu, midmenu; | 
|  | Mouse m; | 
|  | Keyboardctl *kc; | 
|  | Point dxy, oxy, xy0; | 
|  | Rune run; | 
|  | Rectangle r; | 
|  | int size[2]; | 
|  | Image *tmp; | 
|  | PDFInfo *pdf; | 
|  | PSInfo *ps; | 
|  | static char *fwditems[] = { "this page", "next page", "exit", 0 }; | 
|  | static char *miditems[] = { | 
|  | "orig size", | 
|  | "zoom in", | 
|  | "fit window", | 
|  | "rotate 90", | 
|  | "upside down", | 
|  | "", | 
|  | "next", | 
|  | "prev", | 
|  | "zerox", | 
|  | "", | 
|  | "reverse", | 
|  | "discard", | 
|  | "write", | 
|  | "", | 
|  | "quit", | 
|  | 0 | 
|  | }; | 
|  | char *s; | 
|  | enum { | 
|  | CMouse, | 
|  | CResize, | 
|  | CKeyboard, | 
|  | CPlumb, | 
|  | CN | 
|  | }; | 
|  | Alt alts[CN+1]; | 
|  | Plumbmsg *pm; | 
|  |  | 
|  | cp = chancreate(sizeof pm, 0); | 
|  | assert(cp); | 
|  |  | 
|  | doc = dd;    /* save global for menuhit */ | 
|  | ul = screen->r.min; | 
|  | mc = initmouse(nil, screen); | 
|  | kc = initkeyboard(nil); | 
|  | alts[CMouse].c = mc->c; | 
|  | alts[CMouse].v = &m; | 
|  | alts[CMouse].op = CHANRCV; | 
|  | alts[CResize].c = mc->resizec; | 
|  | alts[CResize].v = &size; | 
|  | alts[CResize].op = CHANRCV; | 
|  | alts[CKeyboard].c = kc->c; | 
|  | alts[CKeyboard].v = &run; | 
|  | alts[CKeyboard].op = CHANRCV; | 
|  | alts[CPlumb].c = cp; | 
|  | alts[CPlumb].v = ± | 
|  | alts[CPlumb].op = CHANNOP; | 
|  | alts[CN].op = CHANEND; | 
|  |  | 
|  | /* XXX: Event */ | 
|  | if(doc->addpage != nil) { | 
|  | alts[CPlumb].op = CHANRCV; | 
|  | proccreate(plumbproc, cp, 16384); | 
|  | } | 
|  |  | 
|  | setcursor(mc, &reading); | 
|  | r.min = ZP; | 
|  |  | 
|  | /* | 
|  | * im is a global pointer to the current image. | 
|  | * eventually, i think we will have a layer between | 
|  | * the display routines and the ps/pdf/whatever routines | 
|  | * to perhaps cache and handle images of different | 
|  | * sizes, etc. | 
|  | */ | 
|  | im = 0; | 
|  | page = reverse ? doc->npage-1 : 0; | 
|  |  | 
|  | if(doc->fwdonly) { | 
|  | menu.item = fwditems; | 
|  | menu.gen = 0; | 
|  | menu.lasthit = 0; | 
|  | } else { | 
|  | menu.item = 0; | 
|  | menu.gen = menugen; | 
|  | menu.lasthit = 0; | 
|  | } | 
|  |  | 
|  | midmenu.item = miditems; | 
|  | midmenu.gen = 0; | 
|  | midmenu.lasthit = Next; | 
|  |  | 
|  | showpage(page, &menu); | 
|  | setcursor(mc, nil); | 
|  |  | 
|  | nxt = 0; | 
|  | for(;;) { | 
|  | /* | 
|  | * throughout, if doc->fwdonly is set, we restrict the functionality | 
|  | * a fair amount.  we don't care about doc->npage anymore, and | 
|  | * all that can be done is select the next page. | 
|  | */ | 
|  | unlockdisplay(display); | 
|  | a = alt(alts); | 
|  | lockdisplay(display); | 
|  | switch(a) { | 
|  | case CKeyboard: | 
|  | if(run <= 0xFF && isdigit(run)) { | 
|  | nxt = nxt*10+run-'0'; | 
|  | break; | 
|  | } else if(run != '\n') | 
|  | nxt = 0; | 
|  | switch(run) { | 
|  | case 'r':	/* reverse page order */ | 
|  | if(doc->fwdonly) | 
|  | break; | 
|  | reverse = !reverse; | 
|  | menu.lasthit = doc->npage-1-menu.lasthit; | 
|  |  | 
|  | /* | 
|  | * the theory is that if we are reversing the | 
|  | * document order and are on the first or last | 
|  | * page then we're just starting and really want | 
|  | * to view the other end.  maybe the if | 
|  | * should be dropped and this should happen always. | 
|  | */ | 
|  | if(page == 0 || page == doc->npage-1) { | 
|  | page = doc->npage-1-page; | 
|  | showpage(page, &menu); | 
|  | } | 
|  | break; | 
|  | case 'w':	/* write bitmap of current screen */ | 
|  | setcursor(mc, &reading); | 
|  | s = writebitmap(); | 
|  | if(s) | 
|  | string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP, | 
|  | display->defaultfont, s); | 
|  | setcursor(mc, nil); | 
|  | flushimage(display, 1); | 
|  | break; | 
|  | case 'd':	/* remove image from working set */ | 
|  | if(doc->rmpage && page < doc->npage) { | 
|  | if(doc->rmpage(doc, page) >= 0) { | 
|  | if(doc->npage < 0) | 
|  | wexits(0); | 
|  | if(page >= doc->npage) | 
|  | page = doc->npage-1; | 
|  | showpage(page, &menu); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case 'q': | 
|  | case 0x04: /* ctrl-d */ | 
|  | wexits(0); | 
|  | case 'u': | 
|  | if(im==nil) | 
|  | break; | 
|  | setcursor(mc, &reading); | 
|  | rot180(im); | 
|  | setcursor(mc, nil); | 
|  | angle = (angle+180) % 360; | 
|  | redraw(screen); | 
|  | flushimage(display, 1); | 
|  | break; | 
|  | case '-': | 
|  | case '\b': | 
|  | case Kleft: | 
|  | if(page > 0 && !doc->fwdonly) { | 
|  | --page; | 
|  | showpage(page, &menu); | 
|  | } | 
|  | break; | 
|  | case '\n': | 
|  | if(nxt) { | 
|  | nxt--; | 
|  | if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly) | 
|  | showpage(page=nxt, &menu); | 
|  | nxt = 0; | 
|  | break; | 
|  | } | 
|  | goto Gotonext; | 
|  | case Kright: | 
|  | case ' ': | 
|  | Gotonext: | 
|  | if(doc->npage && ++page >= doc->npage && !doc->fwdonly) | 
|  | wexits(0); | 
|  | showpage(page, &menu); | 
|  | break; | 
|  |  | 
|  | /* | 
|  | * The upper y coordinate of the image is at ul.y in screen->r. | 
|  | * Panning up means moving the upper left corner down.  If the | 
|  | * upper left corner is currently visible, we need to go back a page. | 
|  | */ | 
|  | case Kup: | 
|  | if(screen->r.min.y <= ul.y && ul.y < screen->r.max.y){ | 
|  | if(page > 0 && !doc->fwdonly){ | 
|  | --page; | 
|  | showbottom = 1; | 
|  | showpage(page, &menu); | 
|  | } | 
|  | } else { | 
|  | i = Dy(screen->r)/2; | 
|  | if(i > 10) | 
|  | i -= 10; | 
|  | if(i+ul.y > screen->r.min.y) | 
|  | i = screen->r.min.y - ul.y; | 
|  | translate(Pt(0, i)); | 
|  | } | 
|  | break; | 
|  |  | 
|  | /* | 
|  | * If the lower y coordinate is on the screen, we go to the next page. | 
|  | * The lower y coordinate is at ul.y + Dy(im->r). | 
|  | */ | 
|  | case Kdown: | 
|  | i = ul.y + Dy(im->r); | 
|  | if(screen->r.min.y <= i && i <= screen->r.max.y){ | 
|  | ul.y = screen->r.min.y; | 
|  | goto Gotonext; | 
|  | } else { | 
|  | i = -Dy(screen->r)/2; | 
|  | if(i < -10) | 
|  | i += 10; | 
|  | if(i+ul.y+Dy(im->r) <= screen->r.max.y) | 
|  | i = screen->r.max.y - Dy(im->r) - ul.y - 1; | 
|  | translate(Pt(0, i)); | 
|  | } | 
|  | break; | 
|  | default: | 
|  | setcursor(mc, &query); | 
|  | sleep(1000); | 
|  | setcursor(mc, nil); | 
|  | break; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case CMouse: | 
|  | switch(m.buttons){ | 
|  | case Left: | 
|  | oxy = m.xy; | 
|  | xy0 = oxy; | 
|  | do { | 
|  | dxy = subpt(m.xy, oxy); | 
|  | oxy = m.xy; | 
|  | translate(dxy); | 
|  | recv(mc->c, &m); | 
|  | } while(m.buttons == Left); | 
|  | if(m.buttons) { | 
|  | dxy = subpt(xy0, oxy); | 
|  | translate(dxy); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case Middle: | 
|  | if(doc->npage == 0) | 
|  | break; | 
|  |  | 
|  | n = menuhit(Middle, mc, &midmenu, nil); | 
|  | if(n == -1) | 
|  | break; | 
|  | switch(n){ | 
|  | case Next: 	/* next */ | 
|  | if(reverse) | 
|  | page--; | 
|  | else | 
|  | page++; | 
|  | if(page < 0) { | 
|  | if(reverse) return; | 
|  | else page = 0; | 
|  | } | 
|  |  | 
|  | if((page >= doc->npage) && !doc->fwdonly) | 
|  | return; | 
|  |  | 
|  | showpage(page, &menu); | 
|  | nxt = 0; | 
|  | break; | 
|  | case Prev:	/* prev */ | 
|  | if(reverse) | 
|  | page++; | 
|  | else | 
|  | page--; | 
|  | if(page < 0) { | 
|  | if(reverse) return; | 
|  | else page = 0; | 
|  | } | 
|  |  | 
|  | if((page >= doc->npage) && !doc->fwdonly && !reverse) | 
|  | return; | 
|  |  | 
|  | showpage(page, &menu); | 
|  | nxt = 0; | 
|  | break; | 
|  | case Zerox:	/* prev */ | 
|  | zerox(); | 
|  | break; | 
|  | case Zin:	/* zoom in */ | 
|  | if (dd->type == Tpdf){		/* pdf */ | 
|  | pdf = (PDFInfo *) dd->extra; | 
|  | if (pdf != nil){ | 
|  | ppi+= 50; | 
|  | setdim(&pdf->gs, Rect(0,0,0,0), ppi, 0); | 
|  | showpage(page, &menu); | 
|  | } | 
|  | break; | 
|  | } | 
|  | if (dd->type == Tps){		/* ps */ | 
|  | ps = (PSInfo *) dd->extra; | 
|  | if (ps != nil){ | 
|  | ppi+= 50; | 
|  | setdim(&ps->gs, Rect(0,0,0,0), ppi, 0); | 
|  | showpage(page, &menu); | 
|  | } | 
|  | break; | 
|  | } | 
|  | else{ 	/* image */ | 
|  | double delta; | 
|  | Rectangle r; | 
|  |  | 
|  | r = getrect(Middle, mc); | 
|  | if((rectclip(&r, rectaddpt(im->r, ul)) == 0) || | 
|  | Dx(r) == 0 || Dy(r) == 0) | 
|  | break; | 
|  | /* use the smaller side to expand */ | 
|  | if(Dx(r) < Dy(r)) | 
|  | delta = (double)Dx(im->r)/(double)Dx(r); | 
|  | else | 
|  | delta = (double)Dy(im->r)/(double)Dy(r); | 
|  |  | 
|  | setcursor(mc, &reading); | 
|  | tmp = xallocimage(display, | 
|  | Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta)), | 
|  | im->chan, 0, DBlack); | 
|  | if(tmp == nil) { | 
|  | fprint(2, "out of memory during zoom: %r\n"); | 
|  | wexits("memory"); | 
|  | } | 
|  | resample(im, tmp); | 
|  | im = tmp; | 
|  | delayfreeimage(tmp); | 
|  | setcursor(mc, nil); | 
|  | ul = screen->r.min; | 
|  | redraw(screen); | 
|  | flushimage(display, 1); | 
|  | break; | 
|  | } | 
|  | case Fit:	/* fit */ | 
|  | /* no op if pdf or ps*/ | 
|  | if (dd->type == Tgfx){ | 
|  | fitwin = 1; | 
|  | fit(); | 
|  | } | 
|  | break; | 
|  | case Rot:	/* rotate 90 */ | 
|  | angle = (angle+90) % 360; | 
|  | showpage(page, &menu); | 
|  | break; | 
|  | case Upside: 	/* upside-down */ | 
|  | angle = (angle+180) % 360; | 
|  | showpage(page, &menu); | 
|  | break; | 
|  | case Restore:	/* restore */ | 
|  | if (dd->type == Tpdf){		/* pdf */ | 
|  | pdf = (PDFInfo *) dd->extra; | 
|  | if (pdf != nil){ | 
|  | ppi = 100; | 
|  | setdim(&pdf->gs, Rect(0,0,0,0), ppi, 0); | 
|  | } | 
|  | showpage(page, &menu); | 
|  | break; | 
|  | } | 
|  | if (dd->type == Tps){		/* ps */ | 
|  | ps = (PSInfo *) dd->extra; | 
|  | if (ps != nil){ | 
|  | ppi = 100; | 
|  | setdim(&ps->gs, Rect(0,0,0,0), ppi, 0); | 
|  | } | 
|  | showpage(page, &menu); | 
|  | break; | 
|  | } | 
|  | fitwin = 0; | 
|  | showpage(page, &menu); | 
|  | break; | 
|  | case Reverse:	/* reverse */ | 
|  | if(doc->fwdonly) | 
|  | break; | 
|  | reverse = !reverse; | 
|  | menu.lasthit = doc->npage-1-menu.lasthit; | 
|  |  | 
|  | if(page == 0 || page == doc->npage-1) { | 
|  | page = doc->npage-1-page; | 
|  | showpage(page, &menu); | 
|  | } | 
|  | break; | 
|  | case Write: /* write */ | 
|  | setcursor(mc, &reading); | 
|  | s = writebitmap(); | 
|  | if(s) | 
|  | string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP, | 
|  | display->defaultfont, s); | 
|  | setcursor(mc, nil); | 
|  | flushimage(display, 1); | 
|  | break; | 
|  | case Del: /* delete */ | 
|  | if(doc->rmpage && page < doc->npage) { | 
|  | if(doc->rmpage(doc, page) >= 0) { | 
|  | if(doc->npage < 0) | 
|  | wexits(0); | 
|  | if(page >= doc->npage) | 
|  | page = doc->npage-1; | 
|  | showpage(page, &menu); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case Exit:	/* exit */ | 
|  | return; | 
|  | case Empty1: | 
|  | case Empty2: | 
|  | case Empty3: | 
|  | break; | 
|  |  | 
|  | }; | 
|  |  | 
|  |  | 
|  |  | 
|  | case Right: | 
|  | if(doc->npage == 0) | 
|  | break; | 
|  |  | 
|  | oldpage = page; | 
|  | n = menuhit(RMenu, mc, &menu, nil); | 
|  | if(n == -1) | 
|  | break; | 
|  |  | 
|  | if(doc->fwdonly) { | 
|  | switch(n){ | 
|  | case 0:	/* this page */ | 
|  | break; | 
|  | case 1:	/* next page */ | 
|  | showpage(++page, &menu); | 
|  | break; | 
|  | case 2:	/* exit */ | 
|  | return; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | if(n == doc->npage) | 
|  | return; | 
|  | else | 
|  | page = reverse ? doc->npage-1-n : n; | 
|  |  | 
|  | if(oldpage != page) | 
|  | showpage(page, &menu); | 
|  | nxt = 0; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case CResize: | 
|  | r = screen->r; | 
|  | if(getwindow(display, Refnone) < 0) | 
|  | fprint(2,"can't reattach to window"); | 
|  | ul = addpt(ul, subpt(screen->r.min, r.min)); | 
|  | redraw(screen); | 
|  | flushimage(display, 1); | 
|  | break; | 
|  | case CPlumb: | 
|  | if(pm->ndata <= 0){ | 
|  | plumbfree(pm); | 
|  | break; | 
|  | } | 
|  | if(showdata(pm)) { | 
|  | s = estrdup("/tmp/pageplumbXXXXXXX"); | 
|  | fd = opentemp(s, ORDWR|ORCLOSE); | 
|  | write(fd, pm->data, pm->ndata); | 
|  | /* lose fd reference on purpose; the file is open ORCLOSE */ | 
|  | } else if(pm->data[0] == '/') { | 
|  | s = estrdup(pm->data); | 
|  | } else { | 
|  | s = emalloc(strlen(pm->wdir)+1+pm->ndata+1); | 
|  | sprint(s, "%s/%s", pm->wdir, pm->data); | 
|  | cleanname(s); | 
|  | } | 
|  | if((i = doc->addpage(doc, s)) >= 0) { | 
|  | page = i; | 
|  | unhide(); | 
|  | showpage(page, &menu); | 
|  | } | 
|  | free(s); | 
|  | plumbfree(pm); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | Image *gray; | 
|  |  | 
|  | /* | 
|  | * A draw operation that touches only the area contained in bot but not in top. | 
|  | * mp and sp get aligned with bot.min. | 
|  | */ | 
|  | static void | 
|  | gendrawdiff(Image *dst, Rectangle bot, Rectangle top, | 
|  | Image *src, Point sp, Image *mask, Point mp, int op) | 
|  | { | 
|  | Rectangle r; | 
|  | Point origin; | 
|  | Point delta; | 
|  |  | 
|  | USED(op); | 
|  |  | 
|  | if(Dx(bot)*Dy(bot) == 0) | 
|  | return; | 
|  |  | 
|  | /* no points in bot - top */ | 
|  | if(rectinrect(bot, top)) | 
|  | return; | 
|  |  | 
|  | /* bot - top ≡ bot */ | 
|  | if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){ | 
|  | gendrawop(dst, bot, src, sp, mask, mp, op); | 
|  | return; | 
|  | } | 
|  |  | 
|  | origin = bot.min; | 
|  | /* split bot into rectangles that don't intersect top */ | 
|  | /* left side */ | 
|  | if(bot.min.x < top.min.x){ | 
|  | r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y); | 
|  | delta = subpt(r.min, origin); | 
|  | gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op); | 
|  | bot.min.x = top.min.x; | 
|  | } | 
|  |  | 
|  | /* right side */ | 
|  | if(bot.max.x > top.max.x){ | 
|  | r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y); | 
|  | delta = subpt(r.min, origin); | 
|  | gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op); | 
|  | bot.max.x = top.max.x; | 
|  | } | 
|  |  | 
|  | /* top */ | 
|  | if(bot.min.y < top.min.y){ | 
|  | r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y); | 
|  | delta = subpt(r.min, origin); | 
|  | gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op); | 
|  | bot.min.y = top.min.y; | 
|  | } | 
|  |  | 
|  | /* bottom */ | 
|  | if(bot.max.y > top.max.y){ | 
|  | r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y); | 
|  | delta = subpt(r.min, origin); | 
|  | gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op); | 
|  | bot.max.y = top.max.y; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p, int op) | 
|  | { | 
|  | gendrawdiff(dst, bot, top, src, p, mask, p, op); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Translate the image in the window by delta. | 
|  | */ | 
|  | static void | 
|  | translate(Point delta) | 
|  | { | 
|  | Point u; | 
|  | Rectangle r, or; | 
|  |  | 
|  | if(im == nil) | 
|  | return; | 
|  |  | 
|  | u = pclip(addpt(ul, delta), ulrange); | 
|  | delta = subpt(u, ul); | 
|  | if(delta.x == 0 && delta.y == 0) | 
|  | return; | 
|  |  | 
|  | /* | 
|  | * The upper left corner of the image is currently at ul. | 
|  | * We want to move it to u. | 
|  | */ | 
|  | or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul); | 
|  | r = rectaddpt(or, delta); | 
|  |  | 
|  | drawop(screen, r, screen, nil, ul, S); | 
|  | ul = u; | 
|  |  | 
|  | /* fill in gray where image used to be but isn't. */ | 
|  | drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP, S); | 
|  |  | 
|  | /* fill in black border */ | 
|  | drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP, S); | 
|  |  | 
|  | /* fill in image where it used to be off the screen. */ | 
|  | if(rectclip(&or, screen->r)) | 
|  | drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min, S); | 
|  | else | 
|  | drawop(screen, r, im, nil, im->r.min, S); | 
|  | flushimage(display, 1); | 
|  | } | 
|  |  | 
|  | void | 
|  | redraw(Image *screen) | 
|  | { | 
|  | Rectangle r; | 
|  |  | 
|  | if(im == nil) | 
|  | return; | 
|  |  | 
|  | ulrange.max = screen->r.max; | 
|  | ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r))); | 
|  |  | 
|  | ul = pclip(ul, ulrange); | 
|  | drawop(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min)), S); | 
|  |  | 
|  | if(im->repl) | 
|  | return; | 
|  |  | 
|  | /* fill in any outer edges */ | 
|  | /* black border */ | 
|  | r = rectaddpt(im->r, subpt(ul, im->r.min)); | 
|  | border(screen, r, -2, display->black, ZP); | 
|  | r.min = subpt(r.min, Pt(2,2)); | 
|  | r.max = addpt(r.max, Pt(2,2)); | 
|  |  | 
|  | /* gray for the rest */ | 
|  | if(gray == nil) { | 
|  | gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF); | 
|  | if(gray == nil) { | 
|  | fprint(2, "g out of memory: %r\n"); | 
|  | wexits("mem"); | 
|  | } | 
|  | } | 
|  | border(screen, r, -4000, gray, ZP); | 
|  | //	flushimage(display, 0); | 
|  | } | 
|  |  | 
|  | /* clip p to be in r */ | 
|  | Point | 
|  | pclip(Point p, Rectangle r) | 
|  | { | 
|  | if(p.x < r.min.x) | 
|  | p.x = r.min.x; | 
|  | else if(p.x >= r.max.x) | 
|  | p.x = r.max.x-1; | 
|  |  | 
|  | if(p.y < r.min.y) | 
|  | p.y = r.min.y; | 
|  | else if(p.y >= r.max.y) | 
|  | p.y = r.max.y-1; | 
|  |  | 
|  | return p; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * resize is perhaps a misnomer. | 
|  | * this really just grows the window to be at least dx across | 
|  | * and dy high.  if the window hits the bottom or right edge, | 
|  | * it is backed up until it hits the top or left edge. | 
|  | */ | 
|  | void | 
|  | resize(int dx, int dy) | 
|  | { | 
|  | static Rectangle sr; | 
|  | Rectangle r, or; | 
|  |  | 
|  | r = screen->r; | 
|  | if(Dx(sr)*Dy(sr) == 0) { | 
|  | sr = screenrect(); | 
|  | /* Start with the size of the first image */ | 
|  | r.max.x = r.min.x; | 
|  | r.max.y = r.min.y; | 
|  | } | 
|  |  | 
|  | if(Dx(r) >= dx && Dy(r) >= dy) | 
|  | return; | 
|  |  | 
|  | or = r; | 
|  |  | 
|  | r.max.x = max(r.min.x+dx, r.max.x); | 
|  | r.max.y = max(r.min.y+dy, r.max.y); | 
|  | if(r.max.x > sr.max.x){ | 
|  | if(Dx(r) > Dx(sr)){ | 
|  | r.min.x = 0; | 
|  | r.max.x = sr.max.x; | 
|  | }else | 
|  | r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0)); | 
|  | } | 
|  | if(r.max.y > sr.max.y){ | 
|  | if(Dy(r) > Dy(sr)){ | 
|  | r.min.y = 0; | 
|  | r.max.y = sr.max.y; | 
|  | }else | 
|  | r = rectaddpt(r, Pt(0, sr.max.y-r.max.y)); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Sometimes we can't actually grow the window big enough, | 
|  | * and resizing it to the same shape makes it flash. | 
|  | */ | 
|  | if(Dx(r) == Dx(or) && Dy(r) == Dy(or)) | 
|  | return; | 
|  |  | 
|  | drawresizewindow(r); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If we allocimage after a resize but before flushing the draw buffer, | 
|  | * we won't have seen the reshape event, and we won't have called | 
|  | * getwindow, and allocimage will fail.  So we flushimage before every alloc. | 
|  | */ | 
|  | Image* | 
|  | xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val) | 
|  | { | 
|  | flushimage(display, 0); | 
|  | return allocimage(d, r, chan, repl, val); | 
|  | } | 
|  |  | 
|  | void | 
|  | plumbproc(void *c) | 
|  | { | 
|  | Channel *cp; | 
|  | CFid *fd; | 
|  |  | 
|  | cp = c; | 
|  | fd = plumbopenfid("image", OREAD|OCEXEC); | 
|  | if(fd == nil) { | 
|  | fprint(2, "Cannot connect to the plumber"); | 
|  | threadexits("plumber"); | 
|  | } | 
|  | for(;;) { | 
|  | send(cp, plumbrecvfid(fd)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* XXX: This function is ugly and hacky. There may be a better way... or not */ | 
|  | Rectangle | 
|  | screenrect(void) | 
|  | { | 
|  | int fd[3], pfd[2]; | 
|  | int n, w, h; | 
|  | char buf[64]; | 
|  | char *p, *pr; | 
|  |  | 
|  | if(pipe(pfd) < 0) | 
|  | wexits("pipe failed"); | 
|  |  | 
|  | fd[0] = open("/dev/null", OREAD); | 
|  | fd[1] = pfd[1]; | 
|  | fd[2] = dup(2, -1); | 
|  | if(threadspawnl(fd, "rc", "rc", "-c", "xdpyinfo | grep 'dimensions:'", nil) == -1) | 
|  | wexits("threadspawnl failed"); | 
|  |  | 
|  | if((n = read(pfd[0], buf, 63)) <= 0) | 
|  | wexits("read xdpyinfo failed"); | 
|  | close(fd[0]); | 
|  |  | 
|  | buf[n] = '\0'; | 
|  | for(p = buf; *p; p++) | 
|  | if(*p >= '0' && *p <= '9') break; | 
|  | if(*p == '\0') | 
|  | wexits("xdpyinfo parse failed"); | 
|  |  | 
|  | w = strtoul(p, &pr, 10); | 
|  | if(p == pr || *pr == '\0' || *(++pr) == '\0') | 
|  | wexits("xdpyinfo parse failed"); | 
|  | h = strtoul(pr, &p, 10); | 
|  | if(p == pr) | 
|  | wexits("xdpyinfo parse failed"); | 
|  |  | 
|  | return Rect(0, 0, w, h); | 
|  | } | 
|  |  | 
|  | void | 
|  | zerox(void) | 
|  | { | 
|  | int pfd[2]; | 
|  | int fd[3]; | 
|  |  | 
|  | pipe(pfd); | 
|  | fd[0] = pfd[0]; | 
|  | fd[1] = dup(1, -1); | 
|  | fd[2] = dup(2, -1); | 
|  | threadspawnl(fd, "page", "page", "-R", nil); | 
|  |  | 
|  | writeimage(pfd[1], im, 0); | 
|  | close(pfd[1]); | 
|  | } | 
|  |  | 
|  | void | 
|  | fit() | 
|  | { | 
|  | double delta; | 
|  | Rectangle r; | 
|  | Image* tmp; | 
|  |  | 
|  | delta = (double)Dx(screen->r)/(double)Dx(im->r); | 
|  | if((double)Dy(im->r)*delta > Dy(screen->r)) | 
|  | delta = (double)Dy(screen->r)/(double)Dy(im->r); | 
|  | r = Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta)); | 
|  | setcursor(mc, &reading); | 
|  | tmp = xallocimage(display, r, im->chan, 0, DBlack); | 
|  | if(tmp == nil) { | 
|  | fprint(2, "out of memory during fit: %r\n"); | 
|  | wexits("memory"); | 
|  | } | 
|  | resample(im, tmp); | 
|  | im = tmp; | 
|  | delayfreeimage(tmp); | 
|  | setcursor(mc, nil); | 
|  | ul = screen->r.min; | 
|  | redraw(screen); | 
|  | flushimage(display, 1); | 
|  | } |