/* * Copyright (C) 2000, Matias Atria * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "mdvi.h" #include "private.h" #include "x11.h" #define WIN_BORDER 0 #define STATUS_BORDER 0 #define PAGEMOVE_TINY 0 #define PAGEMOVE_SMALL 1 #define PAGEMOVE_NORMAL 2 #define PAGEMOVE_LARGE 3 #include #include typedef struct { Window win; Uint w; Uint h; int xorig; int yorig; int minx; int miny; int maxx; int maxy; int dirty; } WinArea; #define MAX_MARKS 16 typedef struct { DrawArea area; char **strings; int size; int top; } StatusLine; typedef void (*RulerDrawer) __PROTO((DviContext *, int, int, int, int)); typedef struct { TopWindow dd_top; WinArea dd_dvi; Ulong dd_fg; Ulong dd_bg; Uint dd_page_w; Uint dd_page_h; Uint dd_paper_w; Uint dd_paper_h; double dd_hruler_inches; double dd_vruler_inches; short dd_hsteps; short dd_vsteps; Uint dd_base_x; Uint dd_base_y; short dd_marks[MAX_MARKS]; GC dd_gc; short dd_busy; int dd_ruler_xorig; int dd_ruler_yorig; int dd_ruler_x; int dd_ruler_y; int dd_inruler; RulerDrawer dd_ruler_draw; int dd_reset_origin; DviAppConfig *dd_app; } DviDrawData; /* these are constant after initialization */ static Cursor busy, point; static GC rulergc; static GC scrollgc; extern DviAppConfig *mdvi_app; static DviOrientation pos_rotate_sequence[] = { MDVI_ORIENT_RP90, MDVI_ORIENT_IRP90, MDVI_ORIENT_IRM90, MDVI_ORIENT_RM90, MDVI_ORIENT_BTRL, MDVI_ORIENT_TBLR, MDVI_ORIENT_BTLR, MDVI_ORIENT_TBRL }; static DviOrientation neg_rotate_sequence[] = { MDVI_ORIENT_RM90, MDVI_ORIENT_IRM90, MDVI_ORIENT_IRP90, MDVI_ORIENT_RP90, MDVI_ORIENT_TBLR, MDVI_ORIENT_BTRL, MDVI_ORIENT_TBRL, MDVI_ORIENT_BTLR }; static DviOrientation h_flip_sequence[] = { MDVI_ORIENT_TBRL, MDVI_ORIENT_TBLR, MDVI_ORIENT_BTRL, MDVI_ORIENT_BTLR, MDVI_ORIENT_IRM90, MDVI_ORIENT_IRP90, MDVI_ORIENT_RM90, MDVI_ORIENT_RP90 }; static DviOrientation v_flip_sequence[] = { MDVI_ORIENT_BTLR, MDVI_ORIENT_BTRL, MDVI_ORIENT_TBLR, MDVI_ORIENT_TBRL, MDVI_ORIENT_IRP90, MDVI_ORIENT_IRM90, MDVI_ORIENT_RP90, MDVI_ORIENT_RM90 }; /* * I know this is poor style, but it saves a lot of routine * typing. */ #define GET_DRAW_DATA \ DviDrawData *dd = (DviDrawData *)dvi->device.device_data #define top dd->dd_top #define dviwin dd->dd_dvi #define page_w dd->dd_page_w #define page_h dd->dd_page_h #define paper_w dd->dd_paper_w #define paper_h dd->dd_paper_h #define hruler_inches dd->dd_hruler_inches #define vruler_inches dd->dd_vruler_inches #define hsteps dd->dd_hsteps #define vsteps dd->dd_vsteps #define base_x dd->dd_base_x #define base_y dd->dd_base_y #define marks dd->dd_marks #define drawgc dd->dd_gc #define inruler dd->dd_inruler #define reset_origin dd->dd_reset_origin #define dviapp dd->dd_app #define NORULER 0 #define HRULER 1 #define VRULER 2 #define VHRULER 3 #define SET_RULER_ORIGIN(t,x,y) do { \ XSync(dpy, False); \ dd->dd_ruler_xorig = dd->dd_ruler_x = (x); \ dd->dd_ruler_yorig = dd->dd_ruler_y = (y); \ switch(t) { \ case NORULER: dd->dd_ruler_draw = NULL; break; \ case HRULER: dd->dd_ruler_draw = draw_hruler; break; \ case VRULER: dd->dd_ruler_draw = draw_vruler; break; \ case VHRULER: dd->dd_ruler_draw = draw_ruler; break; \ } \ dd->dd_inruler = (t); \ } while(0) #define UNSET_RULER() do { \ dd->dd_ruler_draw = NULL; dd->dd_inruler = NORULER; \ } while(0) #define DRAW_RULER() do { \ if(dd->dd_ruler_draw) \ dd->dd_ruler_draw(dvi, \ dd->dd_ruler_xorig, \ dd->dd_ruler_yorig, \ dd->dd_ruler_x, \ dd->dd_ruler_y); \ } while(0) #define MOVE_RULER(x,y) do { \ DRAW_RULER(); \ dd->dd_ruler_x = (x); \ dd->dd_ruler_y = (y); \ DRAW_RULER(); \ } while(0) #define WORKING() do { \ if(++dd->dd_busy == 1 && busy != None) \ XDefineCursor(dpy, top.win, busy); \ } while(0) #define RESTING() do { \ if(--dd->dd_busy == 0 && busy != None) \ XUndefineCursor(dpy, top.win); \ } while(0) int mdvi_create_window(DviContext *dvi, const char *geometry); void view_page __PROTO((DviContext *dvi, int pageno)); static void print_key_help __PROTO((void)); static void handle_special_key __PROTO((DviContext *dvi, int argno, int den, KeySym sym, int state)); static void handle_key __PROTO((DviContext *dvi, XKeyEvent *ev)); static void do_redraw __PROTO((DviContext *dvi)); static void do_expose __PROTO((DviContext *dvi, int x, int y, Uint w, Uint h)); static void handle_dvi __PROTO((DviContext *dvi, XEvent *ev)); static void x11_draw_glyph __PROTO((DviContext *dvi, DviFontChar *ch, int x0, int y0)); static void x11_draw_rule __PROTO((DviContext *dvi, int x, int y, Uint w, Uint h, int)); static void draw_hticks __PROTO((DviContext *dvi, int x, int y, int x2)); static void draw_vticks __PROTO((DviContext *dvi, int x, int y, int y2)); static void draw_hruler __PROTO((DviContext *dvi, int x1, int y1, int x2, int y2)); static void draw_vruler __PROTO((DviContext *dvi, int x1, int y1, int x2, int y2)); static void draw_ruler __PROTO((DviContext *dvi, int x, int y, int x2, int y2)); static void reset_page __PROTO((DviContext *dvi)); static void reset_view_origin __PROTO((DviContext *dvi)); static void position_windows __PROTO((DviContext *dvi)); static int configure_window __PROTO((DviContext *dvi)); static void scroll_window __PROTO((DviContext *dvi, int newx, int newy)); static void pageup __PROTO((DviContext *dvi, int n, int mode)); static void pageleft __PROTO((DviContext *dvi, int n, int mode)); static int set_pageno __PROTO((DviContext *dvi, int page)); static int set_tex_pageno __PROTO((DviContext *dvi, int page)); static void top_events __PROTO((DviContext *dvi, XEvent *ev)); extern void font_print __PROTO((void)); /* functions that are used as DviDevice in the DVI context */ static void x11_draw_glyph(DviContext *dvi, DviFontChar *ch, int x0, int y0) { int rx, ry; int x, y, w, h; int isbitmap; int isbox; DviGlyph *glyph; GET_DRAW_DATA; /* coordinates into the page */ rx = base_x + x0; ry = base_y + y0; isbitmap = 1; if(dvi->params.hshrink == 1 && dvi->params.vshrink == 1) glyph = &ch->glyph; else if(dvi->params.flags & MDVI_PARAM_ANTIALIASED) { glyph = &ch->grey; isbitmap = 0; } else glyph = &ch->shrunk; isbox = (glyph->data == NULL || (dvi->params.flags & MDVI_PARAM_CHARBOXES)); x = glyph->x; y = glyph->y; w = glyph->w; h = glyph->h; switch(dvi->params.orientation) { case MDVI_ORIENT_TBLR: break; case MDVI_ORIENT_TBRL: rx = page_w - rx; break; case MDVI_ORIENT_BTLR: ry = page_h - ry; break; case MDVI_ORIENT_BTRL: rx = page_w - rx; ry = page_h - ry; break; case MDVI_ORIENT_RP90: SWAPINT(rx, ry); ry = page_h - ry; break; case MDVI_ORIENT_RM90: SWAPINT(rx, ry); rx = page_w - rx; break; case MDVI_ORIENT_IRM90: SWAPINT(rx, ry); rx = page_w - rx; ry = page_h - ry; break; case MDVI_ORIENT_IRP90: SWAPINT(rx, ry); break; default: break; } rx -= x + dviwin.xorig; ry -= y + dviwin.yorig; /* * drawing is allowed only in the rectangle inside * [minx, miny] - [maxx, maxy] * of dviwin */ if(rx > dviwin.maxx || dviwin.minx > rx + w || ry > dviwin.maxy || dviwin.miny > ry + h) return; if(isbox) XDrawRectangle(dpy, dviwin.win, drawgc, rx, ry, w, h); else if(isbitmap) draw_bitmap(dviwin.win, (BITMAP *)glyph->data, rx, ry); else put_image(dviwin.win, glyph->data, rx, ry, w, h); } static void x11_draw_rule(DviContext *dvi, int x, int y, Uint w, Uint h, int fill) { int rx, ry; GET_DRAW_DATA; rx = base_x + x; ry = base_y + y; switch(dvi->params.orientation) { case MDVI_ORIENT_TBLR: break; case MDVI_ORIENT_TBRL: rx = page_w - rx - w; break; case MDVI_ORIENT_BTLR: ry = page_h - ry - h; break; case MDVI_ORIENT_BTRL: rx = page_w - rx - w; ry = page_h - ry - h; break; case MDVI_ORIENT_RP90: SWAPINT(rx, ry); SWAPINT(w, h); ry = page_h - ry - h; break; case MDVI_ORIENT_RM90: SWAPINT(rx, ry); SWAPINT(w, h); rx = page_w - rx - w; break; case MDVI_ORIENT_IRM90: SWAPINT(rx, ry); SWAPINT(w, h); rx = page_w - rx - w; ry = page_h - ry - h; break; case MDVI_ORIENT_IRP90: SWAPINT(rx, ry); SWAPINT(w, h); break; } rx -= dviwin.xorig; ry -= dviwin.yorig; if(rx <= dviwin.maxx && dviwin.minx <= rx + w && ry <= dviwin.maxy && dviwin.miny <= ry + h) { if(fill == 0) XDrawRectangle(dpy, dviwin.win, image_gc, rx, ry, w ? w : 1, h ? h : 1); else XFillRectangle(dpy, dviwin.win, image_gc, rx, ry, w ? w : 1, h ? h : 1); } } static int x11_interpolate_colors(void *device_data, Ulong *pixels, int nlevels, Ulong fg, Ulong bg, double g, int density) { double frac; XColor xc, xfg, xbg; int i, n; xfg.pixel = fg; xbg.pixel = bg; XQueryColor(dpy, colormap, &xfg); XQueryColor(dpy, colormap, &xbg); n = nlevels - 1; for(i = 0; i < nlevels; i++) { if(g > 0) frac = pow((double)i / n, 1 / g); else frac = 1 - pow((double)(n - i) / n, -g); xc.red = frac * ((double)xfg.red - xbg.red) + xbg.red; xc.green = frac * ((double)xfg.green - xbg.green) + xbg.green; xc.blue = frac * ((double)xfg.blue - xbg.blue) + xbg.blue; if(XAllocColor(dpy, colormap, &xc)) pixels[i] = xc.pixel; else pixels[i] = (i * 100 >= density * 15) ? xfg.pixel : xbg.pixel; } return nlevels; } static void *x11_create_image(void *device_data, Uint w, Uint h, Uint bpp) { XImage *image; image = XCreateImage(dpy, visual, depth, ZPixmap, 0, (char *)0, w, h, bpp, 0); image->data = xcalloc(image->bytes_per_line, h); return image; } static void x11_free_image(void *ptr) { XImage *image = (XImage *)ptr; char *data; data = image->data; image->data = NULL; XDestroyImage(image); xfree(data); } static void x11_put_pixel(void *image, int x, int y, Ulong color) { XPutPixel((XImage *)image, x, y, color); } static void x11_refresh(DviContext *dvi, void *device_data) { GET_DRAW_DATA; configure_window(dvi); if(!reset_origin) set_pageno(dvi, dvi->currpage); } static void x11_set_color(void *device_data, Ulong fg, Ulong bg) { DviDrawData *dd = (DviDrawData *)device_data; if(fg != dd->dd_fg) { DEBUG((DBG_DEVICE, "foreground %lu -> %lu\n", dd->dd_fg, fg)); XSetForeground(dpy, image_gc, fg); dd->dd_fg = fg; } if(bg != dd->dd_bg) { DEBUG((DBG_DEVICE, "background %lu -> %lu\n", dd->dd_bg, bg)); XSetBackground(dpy, image_gc, bg); dd->dd_bg = bg; } } /* draws tick marks for horizontal rulers */ static void draw_hticks(DviContext *dvi, int x, int y, int x2) { int i, n; double dh; GET_DRAW_DATA; dh = hruler_inches * dvi->params.dpi / (hsteps * dvi->params.hshrink); if(x > x2) dh = -dh; for(n = 0, i = x; (x < x2 ? i < x2 : i > x2); ) { if(hsteps > 1 && (n % hsteps) == 0) XDrawLine(dpy, dviwin.win, rulergc, i, y+6, i, y-6); else if((hsteps & 1) || (n % (hsteps >> 1))) XDrawLine(dpy, dviwin.win, rulergc, i, y+2, i, y-2); else XDrawLine(dpy, dviwin.win, rulergc, i, y+4, i, y-4); i = x + FROUND(++n * dh); } } /* draws tick marks for vertical rulers */ static void draw_vticks(DviContext *dvi, int x, int y, int y2) { int i, n; double dh; GET_DRAW_DATA; dh = vruler_inches * dvi->params.vdpi / (vsteps * dvi->params.vshrink); if(y > y2) dh = -dh; for(n = 0, i = y; (y < y2 ? i < y2 : i > y2); ) { if(vsteps > 1 && (n % vsteps) == 0) XDrawLine(dpy, dviwin.win, rulergc, x+6, i, x-6, i); else if((vsteps & 1) || (n % (vsteps >> 1))) XDrawLine(dpy, dviwin.win, rulergc, x+2, i, x-2, i); else XDrawLine(dpy, dviwin.win, rulergc, x+4, i, x-4, i); i = y + FROUND(++n * dh); } } static void draw_hruler(DviContext *dvi, int x1, int y1, int x2, int y2) { int tx, ty, n, d; double fraction; char str[64]; GET_DRAW_DATA; /* grab the server */ XRaiseWindow(dpy, dviwin.win); XSync(dpy, True); XGrabServer(dpy); /* draw a horizontal line */ XDrawLine(dpy, dviwin.win, rulergc, x1, y1, x2, y1); /* draw tick marks */ draw_hticks(dvi, x1, y1, x2); /* draw a small rectangle at the end of the ruler */ XFillRectangle(dpy, dviwin.win, rulergc, x2-1, y1-1, 3, 3); XDrawLine(dpy, dviwin.win, rulergc, x2, y1, x2, y2); /* draw text */ if(x1 != x2) { /* make the text centered under the ruler */ fraction = (double)abs(x1 - x2) * dvi->params.hshrink / (dvi->params.dpi * hruler_inches); d = sprintf(str, "%.2f", fraction); n = XTextWidth(default_font, str, d); tx = x2 + 15; ty = y2 - FONT_HEIGHT(default_font) / 2 + default_font->max_bounds.ascent; if(ty - 2 < default_font->max_bounds.ascent) ty = default_font->max_bounds.ascent + 2; if(ty + default_font->max_bounds.descent + 2 > dviwin.h) ty = dviwin.h - default_font->max_bounds.descent - 2; if(tx < 2) tx = 2; if(tx + n + 2 > dviwin.w) tx = Min(x2, dviwin.w) - n - 10; XDrawString(dpy, dviwin.win, rulergc, tx, ty, str, d); } XUngrabServer(dpy); } static void draw_vruler(DviContext *dvi, int x1, int y1, int x2, int y2) { int tx, ty, n, d; char str[64]; double fraction; GET_DRAW_DATA; /* grab the server */ XGrabServer(dpy); /* draw a horizontal line */ XDrawLine(dpy, dviwin.win, rulergc, x1, y1, x1, y2); /* draw tick marks */ draw_vticks(dvi, x1, y1, y2); /* draw a small rectangle at the end of the ruler */ XFillRectangle(dpy, dviwin.win, rulergc, x1-1, y2-1, 3, 3); XDrawLine(dpy, dviwin.win, rulergc, x1, y2, x2, y2); /* draw text */ if(y1 != y2) { /* make the text centered under the ruler */ fraction = (double)abs(y1 - y2) * dvi->params.vshrink / (dvi->params.vdpi * vruler_inches); d = sprintf(str, "%.2f", fraction); n = XTextWidth(default_font, str, d); if(n % 2) n++; tx = x2 - n / 2; ty = y2 + default_font->max_bounds.ascent + 10; /* make sure the text is visible */ if(tx < 2) tx = 2; if(tx + n + 2 > dviwin.w) tx = dviwin.w - n - 2; if(ty > 0 && ty > dviwin.h - 2) ty = Min(y2, dviwin.h) - default_font->max_bounds.descent - 10; if(ty < default_font->max_bounds.ascent + 2) ty = default_font->max_bounds.ascent + 2; XDrawString(dpy, dviwin.win, rulergc, tx, ty, str, d); } XUngrabServer(dpy); } /*ARGSUSED*/ static void draw_ruler(DviContext *dvi, int x0, int y0, int x, int y) { GET_DRAW_DATA; /* Grab the server */ XGrabServer(dpy); /* Draw horizontal line */ XDrawLine(dpy, dviwin.win, rulergc, 0, y, dviwin.w, y); /* Draw vertical line */ XDrawLine(dpy, dviwin.win, rulergc, x, 0, x, dviwin.h); /* hand back the server */ XUngrabServer(dpy); /* draw ticks */ /* horizontal ticks */ draw_hticks(dvi, x, y, dviwin.w); draw_hticks(dvi, x, y, 0); /* vertical ticks */ draw_vticks(dvi, x, y, dviwin.h); draw_vticks(dvi, x, y, 0); } static void reset_page(DviContext *dvi) { GET_DRAW_DATA; dviwin.minx = 0; dviwin.miny = 0; dviwin.maxx = dviwin.w; dviwin.maxy = dviwin.h; dviwin.dirty = 1; } static void reset_view_origin(DviContext *dvi) { GET_DRAW_DATA; switch(dvi->params.orientation) { case MDVI_ORIENT_TBLR: case MDVI_ORIENT_IRP90: dviwin.xorig = 0; dviwin.yorig = 0; break; case MDVI_ORIENT_TBRL: case MDVI_ORIENT_RM90: dviwin.xorig = page_w - dviwin.w; dviwin.yorig = 0; break; case MDVI_ORIENT_BTLR: case MDVI_ORIENT_RP90: dviwin.xorig = 0; dviwin.yorig = page_h - dviwin.h; break; case MDVI_ORIENT_BTRL: case MDVI_ORIENT_IRM90: dviwin.xorig = page_w - dviwin.w; dviwin.yorig = page_h - dviwin.h; break; } if(dviwin.xorig < 0) dviwin.xorig = 0; if(dviwin.yorig < 0) dviwin.yorig = 0; } static void position_windows(DviContext *dvi) { int tw, th; GET_DRAW_DATA; tw = top.w - 2*WIN_BORDER; th = top.h - 2*WIN_BORDER; XMoveResizeWindow(dpy, dviwin.win, 0, 0, tw, th); dviwin.w = tw; dviwin.h = th; reset_page(dvi); } static int configure_window(DviContext *dvi) { Uint w, h; double ratio; int hs, vs; int changed = 0; GET_DRAW_DATA; ratio = dvi->paper.inches_tall / dvi->paper.inches_wide; base_x = unit2pix(dvi->params.dpi, dviapp->hmargin); base_y = unit2pix(dvi->params.vdpi, dviapp->vmargin); page_w = FROUND(dvi->dviconv * dvi->dvi_page_w); page_h = FROUND(dvi->dvivconv * dvi->dvi_page_h); hs = dvi->params.hshrink; vs = dvi->params.vshrink; if(!hs && !vs) { /* compute both */ w = root_w; h = FROUND(w * ratio); if(h > root_h) { h = root_h; w = FROUND(h / ratio); } /* determine shrinking factors */ hs = ROUND(page_w + 2*base_x, w); vs = ROUND(page_h + 2*base_y, h); if(hs < 1) hs = 1; if(vs < 1) vs = 1; } else if(!hs) { h = FROUND(dvi->params.vdpi * dvi->paper.inches_tall / dvi->params.vshrink) + 2; w = FROUND(h / ratio); hs = ROUND(page_w + 2*base_x, w); } else if(!vs) { w = FROUND(dvi->params.dpi * dvi->paper.inches_wide / dvi->params.hshrink) + 2; h = FROUND(w * ratio); vs = ROUND(page_h + 2*base_y, h); } else { w = FROUND(dvi->params.dpi * dvi->paper.inches_wide / dvi->params.hshrink) + 2; h = FROUND(dvi->params.vdpi * dvi->paper.inches_tall / dvi->params.vshrink) + 2; } /* convert page dimensions */ if(!dvi->params.hshrink) { dvi->params.hshrink = hs; dvi->params.conv /= hs; } if(!dvi->params.vshrink) { dvi->params.vshrink = vs; dvi->params.vconv /= vs; } /* fix margins */ base_x = unit2pix(dvi->params.dpi, dviapp->hmargin) / dvi->params.hshrink; base_y = unit2pix(dvi->params.vdpi, dviapp->vmargin) / dvi->params.vshrink; /* recompute things now that we have all the information */ page_w = pixel_round(dvi, dvi->dvi_page_w) + base_x; page_h = vpixel_round(dvi, dvi->dvi_page_h) + base_y; /* get paper dimensions */ paper_w = FROUND(dvi->params.dpi * dvi->paper.inches_wide / dvi->params.hshrink) + 2; paper_h = FROUND(dvi->params.vdpi * dvi->paper.inches_tall / dvi->params.vshrink) + 2; /* this is the new window size */ if(page_w < paper_w) page_w = paper_w; if(page_h < paper_h) page_h = paper_h; switch(dvi->params.orientation) { case MDVI_ORIENT_TBLR: case MDVI_ORIENT_TBRL: case MDVI_ORIENT_BTLR: case MDVI_ORIENT_BTRL: break; case MDVI_ORIENT_RP90: case MDVI_ORIENT_RM90: case MDVI_ORIENT_IRP90: case MDVI_ORIENT_IRM90: SWAPINT(paper_w, paper_h); SWAPINT(page_w, page_h); break; } w = Min(page_w, root_w); h = Min(page_h, root_h); changed = 1; if(top.win == None) { create_top_window(&top, dvi->filename, w, h); expose_window(top.win, False); get_win_size(top.win, &top.w, &top.h); } else if(w != top.w || h != top.h) XResizeWindow(dpy, top.win, w, h); else changed = 0; /* setup ruler units */ hruler_inches = unit2pix_factor(dviapp->hrule_units); vruler_inches = unit2pix_factor(dviapp->vrule_units); ratio = hruler_inches * dvi->params.dpi; if(ratio < 10) hsteps = 1; else if(ratio < 100) hsteps = 5; else hsteps = 10; ratio = vruler_inches * dvi->params.vdpi; if(ratio < 10) vsteps = 1; else if(ratio < 100) vsteps = 5; else vsteps = 10; return changed; } int mdvi_create_window(DviContext *dvi, const char *geometry) { DviDrawData *dd; int i; dd = xalloc(DviDrawData); memzero(dd, sizeof(DviDrawData)); dvi->device.device_data = dd; /* if this looks weird, see the comment about bad style above */ dviapp = mdvi_app; top.win = None; configure_window(dvi); if(top.win == None) return -1; XSelectInput(dpy, top.win, ExposureMask|StructureNotifyMask|KeyPressMask); /* GC for drawing */ drawgc = create_gc(GXcopy, top.win, dvi->params.fg, dvi->params.bg); /* GC for rulers */ rulergc = create_gc(GXxor, top.win, white, black); /* create our child window */ dviwin.win = create_window(top.win, dvi->params.bg, top.w - 2*WIN_BORDER, top.h - 2*WIN_BORDER, WIN_BORDER); XSelectInput(dpy, dviwin.win, ExposureMask|ButtonPressMask|ButtonReleaseMask|ButtonMotionMask); get_win_size(dviwin.win, &dviwin.w, &dviwin.h); dviwin.minx = 0; dviwin.miny = 0; dviwin.maxx = dviwin.w; dviwin.maxy = dviwin.h; reset_view_origin(dvi); add_window_events(dviwin.win, ButtonMotionMask); /* GC for scrolling */ scrollgc = create_gc(GXcopy, dviwin.win, dvi->params.fg, dvi->params.bg); XSetGraphicsExposures(dpy, scrollgc, True); /* setup the functions in the dvi context */ dvi->device.draw_glyph = x11_draw_glyph; dvi->device.draw_rule = x11_draw_rule; dvi->device.alloc_colors = x11_interpolate_colors; dvi->device.create_image = x11_create_image; dvi->device.free_image = x11_free_image; dvi->device.put_pixel = x11_put_pixel; dvi->device.set_color = x11_set_color; dvi->device.refresh = NULL; /*x11_refresh;*/ /* initialize marks */ for(i = 0; i < MAX_MARKS; i++) marks[i] = -1; position_windows(dvi); printf("Type `q' to quit, `?' to see keyboard and mouse commands\n"); return 0; } static void scroll_window(DviContext *dvi, int newx, int newy) { int x, y, w, h; int src_x, dest_x; int src_y, dest_y; GET_DRAW_DATA; x = newx - dviwin.xorig; y = newy - dviwin.yorig; dviwin.xorig = newx; dviwin.yorig = newy; dviwin.minx -= x; dviwin.maxx -= x; dviwin.miny -= y; dviwin.maxy -= y; if(dviwin.minx < 0) dviwin.minx = 0; if(dviwin.miny < 0) dviwin.miny = 0; if(dviwin.maxx > dviwin.w) dviwin.maxx = dviwin.w; if(dviwin.maxy > dviwin.h) dviwin.maxy = dviwin.h; if(x > 0) { src_x = x; dest_x = 0; } else { src_x = 0; dest_x = -x; } if(y > 0) { src_y = y; dest_y = 0; } else { src_y = 0; dest_y = -y; } w = dviwin.w - abs(x); h = dviwin.h - abs(y); if(w <= 0 || h <= 0) { /* need to refresh the entire window */ reset_page(dvi); XClearArea(dpy, dviwin.win, 0, 0, dviwin.w, dviwin.h, True); } else { XCopyArea(dpy, dviwin.win, dviwin.win, scrollgc, src_x, src_y, w, h, dest_x, dest_y); if(dest_x > 0 && h) XClearArea(dpy, dviwin.win, 0, dest_y, dviwin.w - w, h, True); else if(dest_x == 0 && h) XClearArea(dpy, dviwin.win, w, dest_y, dviwin.w - w, h, True); if(dest_y > 0 && w) XClearArea(dpy, dviwin.win, dest_x, 0, w, dviwin.h - h, True); else if(dest_y == 0 && w) XClearArea(dpy, dviwin.win, dest_x, h, w, dviwin.h - h, True); if(w < dviwin.w && h < dviwin.h) { XClearArea(dpy, dviwin.win, dest_x ? 0 : w, dest_y ? 0 : h, dviwin.w - w, dviwin.h - h, True); } } } static void pageup(DviContext *dvi, int n, int mode) { int z; int maxh; GET_DRAW_DATA; n = n * (int)page_h; switch(mode) { case PAGEMOVE_TINY: n /= 80; break; case PAGEMOVE_SMALL: n /= 40; break; case PAGEMOVE_NORMAL: n /= 10; break; case PAGEMOVE_LARGE: break; } z = dviwin.yorig + n; maxh = page_h - dviwin.h; if(z > maxh) z = maxh; if(z < 0) z = 0; if(z != dviwin.yorig) scroll_window(dvi, dviwin.xorig, z); else beep_sound(); } static void pageleft(DviContext *dvi, int n, int mode) { int z; int maxw; GET_DRAW_DATA; n = n * (int)page_w; switch(mode) { case PAGEMOVE_TINY: n /= 80; break; case PAGEMOVE_SMALL: n /= 40; break; case PAGEMOVE_NORMAL: n /= 10; break; case PAGEMOVE_LARGE: break; } z = dviwin.xorig + n; maxw = page_w - dviwin.w; if(z > maxw) z = maxw; if(z < 0) z = 0; if(z != dviwin.xorig) scroll_window(dvi, z, dviwin.yorig); else beep_sound(); } static int set_pageno(DviContext *dvi, int page) { GET_DRAW_DATA; if(page < 0 || page > dvi->npages - 1) return -1; dvi->currpage = page; reset_page(dvi); XClearArea(dpy, dviwin.win, 0, 0, dviwin.w, dviwin.h, True); return 0; } static int set_tex_pageno(DviContext *dvi, int tex_page) { int page; page = mdvi_find_tex_page(dvi, tex_page); if(page == dvi->npages) return -1; return set_pageno(dvi, page); } void print_key_help(void) { printf( "Keystroke commands: (S=Shift, C=Control)\n" " PageUp/PageDown go to previous/next page\n" " C-PageUp/PageDown go to first/last page\n" " [S/C] Left/Right scroll document horizontally\n" " Up/Down scroll document vertically\n" " q quit\n" " b switch between your eyes and TeX's\n" " a toggle antialiasing\n" " l redraw page\n" " L reload the DVI file from disk\n" " R/r rotate page clockwise/counter-clockwise\n" " F/f flip page vertically/horizontally\n" " ? this help\n" " (NUM) p go to current page + NUM (-\\infty < NUM < \\infty)\n" " (NUM) j go to output page number NUM (last+NUM if NUM < 0)\n" " (NUM) t go to TeX's (i.e. document's) page number NUM\n" " (NUM) d change resolution\n" " (NUM) m change magnification\n" " (NUM) s change shrinking factor (both set to NUM)\n" " (NUM) x change horizontal shrinking factor\n" " (NUM) y change vertical shrinking factor\n" " (NUM) n change pixel density (for bitmap shrinking)\n" " (NUM) g change gamma correction factor\n" " C-Fn set bookmark\n" " Fn goto bookmark\n" "Mouse usage:\n" " Button 1 display ruler\n" " Button 1 + Shift display horizontal ruler\n" " Button 1 + Control display vertical ruler\n" ""); } void handle_special_key(DviContext *dvi, int argno, int den, KeySym sym, int state) { int arg; GET_DRAW_DATA; switch(sym) { case XK_Page_Down: if(state & ControlMask) arg = dvi->npages - 1; else arg = dvi->currpage + 1; if(arg != dvi->currpage && set_pageno(dvi, arg) < 0) beep_sound(); break; case XK_Page_Up: if(state & ControlMask) arg = 0; else arg = dvi->currpage - 1; if(arg != dvi->currpage && set_pageno(dvi, arg) < 0) beep_sound(); break; case XK_Left: if(state & ControlMask) arg = PAGEMOVE_TINY; else if(state & ShiftMask) arg = PAGEMOVE_SMALL; else arg = PAGEMOVE_NORMAL; pageleft(dvi, -1, arg); break; case XK_Right: if(state & ControlMask) arg = PAGEMOVE_TINY; else if(state & ShiftMask) arg = PAGEMOVE_SMALL; else arg = PAGEMOVE_NORMAL; pageleft(dvi, 1, arg); break; case XK_Up: if(state & ControlMask) arg = PAGEMOVE_TINY; else if(state & ShiftMask) arg = PAGEMOVE_SMALL; else arg = PAGEMOVE_NORMAL; pageup(dvi, -1, arg); break; case XK_Down: if(state & ControlMask) arg = PAGEMOVE_TINY; else if(state & ShiftMask) arg = PAGEMOVE_SMALL; else arg = PAGEMOVE_NORMAL; pageup(dvi, 1, arg); break; case XK_F1: arg = 0; goto setmark; case XK_F2: arg = 1; goto setmark; case XK_F3: arg = 2; goto setmark; case XK_F4: arg = 3; goto setmark; case XK_F5: arg = 4; goto setmark; case XK_F6: arg = 5; goto setmark; case XK_F7: arg = 6; goto setmark; case XK_F8: arg = 7; goto setmark; case XK_F9: arg = 8; goto setmark; case XK_F10: arg = 9; goto setmark; case XK_F11: arg = 10; goto setmark; case XK_F12: arg = 11; goto setmark; case XK_F13: arg = 12; goto setmark; case XK_F14: arg = 13; goto setmark; case XK_F15: arg = 14; goto setmark; case XK_F16: arg = 15; goto setmark; setmark: if(arg < 0 || arg >= MAX_MARKS) { beep_sound(); break; } if(state & ControlMask) { /* set a mark */ marks[arg] = dvi->currpage; } else if(marks[arg] >= 0) { if(set_pageno(dvi, marks[arg]) < 0) beep_sound(); } break; default: break; } } #define NEWNUMBER \ (has_number = 1, has_dot = 0, number = 0, logten = 1, sign = 1) #define RESETNUMBER \ (has_number = has_dot = 0, number = 0, sign = 1) void handle_key(DviContext *dvi, XKeyEvent *ev) { char num_buf[256]; int length; char ch; KeySym sym; XComposeStatus unused; int reconfigure = 0; static int has_number = 0; static int sign = 1; static int number; static int has_dot = 0; static double logten = 1; GET_DRAW_DATA; length = XLookupString(ev, num_buf, 256, &sym, &unused); if(length == 0) { sym = XLookupKeysym(ev, 0); if(sym != NoSymbol) { handle_special_key(dvi, has_number ? number : 0, has_dot ? logten : 1, sym, ev->state); } RESETNUMBER; return; } if(length == 1) ch = num_buf[0]; else ch = 0; /* if user is typing a number, update our state */ if(ch >= '0' && ch <= '9') { has_number = 1; number = number * 10 + sign * (ch - '0'); if(has_dot) logten *= 10; return; } else if(ch == '-') { NEWNUMBER; sign = -1; return; } else if(ch == '+') { NEWNUMBER; return; } else if(ch == '.' && !has_dot) { /* it's a floating point number */ has_number = 1; has_dot = 1; logten = 1; return; } switch(ch) { case 8: if(has_number) dvi->params.layer = FROUND((double)number / logten); else if(dvi->params.layer) dvi->params.layer--; set_pageno(dvi, dvi->currpage); break; case 9: if(has_number) dvi->params.layer = FROUND((double)number / logten); else dvi->params.layer++; reset_page(dvi); break; case 'q': top.active = 0; break; case 'b': dvi->params.flags ^= MDVI_PARAM_CHARBOXES; set_pageno(dvi, dvi->currpage); break; case 'a': dvi->params.flags ^= MDVI_PARAM_ANTIALIASED; set_pageno(dvi, dvi->currpage); break; case 'p': if(!has_number) break; number += dvi->currpage; if(number < 0) number = 0; if(number > dvi->npages-1) number = dvi->npages-1; set_pageno(dvi, number); break; case 'j': if(!has_number) break; if(number < 0) has_number = (set_pageno(dvi, dvi->npages+number) < 0); else has_number = (set_pageno(dvi, number) < 0); if(has_number) beep_sound(); break; case 't': if(!has_number) break; if(set_tex_pageno(dvi, number) < 0) beep_sound(); break; case 'l': set_pageno(dvi, dvi->currpage); break; case 'L': /* reload! */ WORKING(); if(mdvi_reload(dvi, NULL) == -1) beep_sound(); else reconfigure = 1; RESTING(); break; case 'd': /* change DPI */ if(!has_number || number == dvi->params.dpi) break; WORKING(); reconfigure = mdvi_set_dpi(dvi, number); RESTING(); break; case 's': if(!has_number || number < 0) break; WORKING(); reconfigure = mdvi_set_shrink(dvi, number, number); RESTING(); break; case 'x': if(!has_number || number < 0) break; WORKING(); reconfigure = mdvi_set_hshrink(dvi, number); RESTING(); break; case 'y': if(!has_number || number < 0) break; WORKING(); reconfigure = mdvi_set_vshrink(dvi, number); RESTING(); break; case 'm': { double x; if(!has_number) break; x = has_dot ? (double)number / logten : (double)number; if(x <= 0) { warning(_("%s: magnification must be positive\n"), dvi->filename); break; } WORKING(); reconfigure = mdvi_set_mag(dvi, x); RESTING(); break; } case '?': print_key_help(); break; case 'r': /* rotate +90 */ reconfigure = mdvi_set_orientation(dvi, pos_rotate_sequence[dvi->params.orientation]); reset_origin = 1; break; case 'R': /* rotate -90 */ reconfigure = mdvi_set_orientation(dvi, neg_rotate_sequence[dvi->params.orientation]); reset_origin = 1; break; case 'f': /* flip horizontally */ reconfigure = mdvi_set_orientation(dvi, h_flip_sequence[dvi->params.orientation]); reset_origin = 1; break; case 'F': /* flip vertically */ reconfigure = mdvi_set_orientation(dvi, v_flip_sequence[dvi->params.orientation]); reset_origin = 1; break; case 'g': { double x; if(!has_number) break; x = has_dot ? (double)number / logten : (double)number; reconfigure = mdvi_set_gamma(dvi, x); break; } case 'n': if(!has_number) break; if(number < 0) beep_sound(); else reconfigure = mdvi_set_density(dvi, number); break; case '/': font_print(); break; } RESETNUMBER; if(reconfigure) { configure_window(dvi); if(!reset_origin) set_pageno(dvi, dvi->currpage); } } static void top_events(DviContext *dvi, XEvent *ev) { int tw, th; GET_DRAW_DATA; tw = top.w; th = top.h; if(handle_top_events(&top, ev)) return; switch(ev->type) { case ConfigureNotify: if(reset_origin || ev->xconfigure.width != tw || ev->xconfigure.height != th) { position_windows(dvi); if(reset_origin) { reset_view_origin(dvi); reset_origin = 0; set_pageno(dvi, dvi->currpage); } } break; case KeyPress: handle_key(dvi, &ev->xkey); break; } } void do_redraw(DviContext *dvi) { GET_DRAW_DATA; WORKING(); if(mdvi_dopage(dvi, dvi->currpage) == -1) fatal("%s: failed to display page\n", dvi->filename); RESTING(); XDrawRectangle(dpy, dviwin.win, normal_gc, -dviwin.xorig, -dviwin.yorig, paper_w-1, paper_h-1); reset_page(dvi); dviwin.dirty = 0; } void do_expose(DviContext *dvi, int x, int y, Uint w, Uint h) { GET_DRAW_DATA; if(!dviwin.dirty) { dviwin.minx = x; dviwin.miny = y; dviwin.maxx = x + w; dviwin.maxy = y + h; } else { if(x < dviwin.minx) dviwin.minx = x; if(y < dviwin.miny) dviwin.miny = y; if(x + w > dviwin.maxx) dviwin.maxx = x + w; if(y + h > dviwin.maxy) dviwin.maxy = y + h; } } void handle_dvi(DviContext *dvi, XEvent *ev) { GET_DRAW_DATA; switch(ev->type) { case ButtonPress: if(inruler == NORULER && ev->xbutton.button == Button1) { int type; /* Raise our window */ XRaiseWindow(dpy, top.win); XFlush(dpy); /* rulers */ if(ev->xbutton.state & ControlMask) type = VRULER; else if(ev->xbutton.state & ShiftMask) type = HRULER; else type = VHRULER; SET_RULER_ORIGIN(type, ev->xbutton.x, ev->xbutton.y); DRAW_RULER(); XDefineCursor(dpy, top.win, point); } break; case ButtonRelease: if(inruler != NORULER) { XUndefineCursor(dpy, top.win); DRAW_RULER(); UNSET_RULER(); } break; case MotionNotify: if(inruler != NORULER) MOVE_RULER(ev->xmotion.x, ev->xmotion.y); break; case GraphicsExpose: case Expose: do_expose(dvi, ev->xexpose.x, ev->xexpose.y, ev->xexpose.width, ev->xexpose.height); dviwin.dirty = 1; break; } } void view_page(DviContext *dvi, int pageno) { GET_DRAW_DATA; if(busy == None) busy = XCreateFontCursor(dpy, XC_watch); if(point == None) point = XCreateFontCursor(dpy, XC_crosshair); do_expose(dvi, 0, 0, dviwin.w, dviwin.h); set_pageno(dvi, pageno); expose_window(dviwin.win, True); while(top.active) { XEvent ev; if(!XPending(dpy)) { if(dviwin.dirty) do_redraw(dvi); else if(reset_origin) { position_windows(dvi); reset_view_origin(dvi); set_pageno(dvi, dvi->currpage); reset_origin = 0; } } XNextEvent(dpy, &ev); if(ev.xany.window == top.win) top_events(dvi, &ev); else if(ev.xany.window == dviwin.win) handle_dvi(dvi, &ev); } /* destroy everything */ XFreeGC(dpy, drawgc); XFreeGC(dpy, rulergc); XFreeGC(dpy, scrollgc); XDestroyWindow(dpy, dviwin.win); /* destroy our private data */ xfree(dvi->device.device_data); dvi->device.device_data = NULL; }