/*
 * Copyright (c) 1994 Paul Vojta.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * NOTE:
 *	xdvi is based on prior work as noted in the modification history, below.
 */

/*
 * DVI previewer for X.
 *
 * Eric Cooper, CMU, September 1985.
 *
 * Code derived from dvi-imagen.c.
 *
 * Modification history:
 * 1/1986	Modified for X.10	--Bob Scheifler, MIT LCS.
 * 7/1988	Modified for X.11	--Mark Eichin, MIT
 * 12/1988	Added 'R' option, toolkit, magnifying glass
 *					--Paul Vojta, UC Berkeley.
 * 2/1989	Added tpic support	--Jeffrey Lee, U of Toronto
 * 4/1989	Modified for System V	--Donald Richardson, Clarkson Univ.
 * 3/1990	Added VMS support	--Scott Allendorf, U of Iowa
 * 7/1990	Added reflection mode	--Michael Pak, Hebrew U of Jerusalem
 * 1/1992	Added greyscale code	--Till Brychcy, Techn. Univ. Muenchen
 *					  and Lee Hetherington, MIT
 * 4/1994	Added DPS support, bounding box
 *					--Ricardo Telichevesky
 *					  and Luis Miguel Silveira, MIT RLE.
 */

#include <config.h>

/* to get a prototype for pow() */
#include <math.h>

/* Xlib and Xutil are already included */
#ifdef	TOOLKIT
#ifdef	OLD_X11_TOOLKIT
#include <X11/Atoms.h>
#else /* not OLD_X11_TOOLKIT */
#include <X11/Xatom.h>
#include <X11/StringDefs.h>
#endif /* not OLD_X11_TOOLKIT */
#include <X11/Shell.h>	/* needed for def. of XtNiconX */
#ifndef	XtSpecificationRelease
#define	XtSpecificationRelease	0
#endif
#if	XtSpecificationRelease >= 4
#include <X11/Xaw/Viewport.h>
#ifdef	BUTTONS
#include <X11/Xaw/Command.h>
#endif
#else	/* XtSpecificationRelease < 4 */
#define	XtPointer caddr_t
#include <X11/Viewport.h>
#ifdef	BUTTONS
#include <X11/Command.h>
#endif
#endif	/* XtSpecificationRelease */
#else	/* !TOOLKIT */
typedef	int		Position;
#define	XtPending()	XPending(DISP)
#endif	/* TOOLKIT */

#if	HAS_SIGIO
#include <fcntl.h>
#include <signal.h>
#ifndef	FASYNC
#undef	HAS_SIGIO
#define	HAS_SIGIO 0
#endif
#endif

#ifndef	X11HEIGHT
#define	X11HEIGHT	8	/* Height of server default font */
#endif

#define	MAGBORD	1	/* border size for magnifier */

/*
 * Command line flags.
 */

#define	fore_Pixel	resource._fore_Pixel
#define	back_Pixel	resource._back_Pixel
#ifdef	TOOLKIT
extern struct _resource	resource;
#define	brdr_Pixel	resource._brdr_Pixel
#endif	/* TOOLKIT */

static	int	pageno_correct	= 1;
static	Boolean	colors_allocated = False;

#define	clip_w	mane.width
#define	clip_h	mane.height
static	Position main_x, main_y;
static	Position mag_x, mag_y, new_mag_x, new_mag_y;
static	Boolean	mag_moved = False;

#ifdef	TOOLKIT
#ifdef	BUTTONS
static	Widget	line_widget, panel_widget;
#endif
static	Widget	x_bar, y_bar;	/* horizontal and vertical scroll bars */

static	Arg	resize_args[] = {
	{XtNwidth,	(XtArgVal) 0},
	{XtNheight,	(XtArgVal) 0},
};

#define	XdviResizeWidget(widget, w, h)	\
		(resize_args[0].value = (XtArgVal) (w), \
		resize_args[1].value = (XtArgVal) (h), \
		XtSetValues(widget, resize_args, XtNumber(resize_args)) )

#ifdef	BUTTONS

static	Arg	resizable_on[] = {
	{XtNresizable,	(XtArgVal) True},
};

static	Arg	resizable_off[] = {
	{XtNresizable,	(XtArgVal) False},
};

static	Arg	line_args[] = {
	{XtNbackground,	(XtArgVal) 0},
	{XtNwidth,	(XtArgVal) 1},
	{XtNheight,	(XtArgVal) 0},
	{XtNfromHoriz,	(XtArgVal) NULL},
	{XtNborderWidth, (XtArgVal) 0},
	{XtNtop,	(XtArgVal) XtChainTop},
	{XtNbottom,	(XtArgVal) XtChainBottom},
	{XtNleft,	(XtArgVal) XtChainRight},
	{XtNright,	(XtArgVal) XtChainRight},
};

static	Arg	panel_args[] = {
	{XtNfromHoriz,	(XtArgVal) NULL},
	{XtNwidth,	(XtArgVal) (XTRA_WID - 1)},
	{XtNheight,	(XtArgVal) 0},
	{XtNborderWidth, (XtArgVal) 0},
	{XtNtop,	(XtArgVal) XtChainTop},
	{XtNbottom,	(XtArgVal) XtChainBottom},
	{XtNleft,	(XtArgVal) XtChainRight},
	{XtNright,	(XtArgVal) XtChainRight},
};

static	struct {
	_Xconst	char	*label;
	_Xconst	char	*name;
	int	closure;
	int	y_pos;
	}
	command_table[] = {
#ifdef SELFILE
/* Reposition "Quit" button to fit "File" button in JSP */
		{"Quit",	"quit",		'q',		10},
		{"File",	"file", 	'F',		60},
#else  /* SELFILE */
		{"Quit",	"quit",		'q',		50},
#endif  /* SELFILE */
		{"Shrink1",	"sh1",		1 << 8 | 's',	150},
		{"Shrink2",	"sh2",		2 << 8 | 's',	200},
		{"Shrink3",	"sh3",		3 << 8 | 's',	250},
		{"Shrink4",	"sh4",		4 << 8 | 's',	300},
		{"Page-10",	"prev10",	10 << 8 | 'p',	400},
		{"Page-5",	"prev5",	5 << 8 | 'p',	450},
		{"Prev",	"prev",		'p',		500},
		{"Next",	"next",		'n',		600},
		{"Page+5",	"next5",	5 << 8 | 'n',	650},
		{"Page+10",	"next10",	10 << 8 | 'n',	700},
#if	PS
		{"View PS",	"postscript",	'v',		750},
#endif
};

static	void	handle_command();

static	XtCallbackRec	command_call[] = {
	{handle_command, NULL},
	{NULL,		NULL},
};

static	Arg	command_args[] = {
	{XtNlabel,	(XtArgVal) NULL},
	{XtNx,		(XtArgVal) 6},
	{XtNy,		(XtArgVal) 0},
	{XtNwidth,	(XtArgVal) 64},
	{XtNheight,	(XtArgVal) 30},
	{XtNcallback,	(XtArgVal) command_call},
};

void
create_buttons(h)
	XtArgVal	h;
{
	int i;

	line_args[0].value = (XtArgVal) resource._hl_Pixel;
	line_args[2].value = h;
	line_args[3].value = (XtArgVal) vport_widget;
	line_widget = XtCreateManagedWidget("line", widgetClass, form_widget,
		line_args, XtNumber(line_args));
	panel_args[0].value = (XtArgVal) line_widget;
	panel_args[2].value = h;
	panel_widget = XtCreateManagedWidget("panel", compositeWidgetClass,
		form_widget, panel_args, XtNumber(panel_args));

	command_args[2].value = (XtArgVal) vport_widget;
	for (i = 0; i < XtNumber(command_table); ++i) {
	    command_args[0].value = (XtArgVal) command_table[i].label;
	    command_args[2].value = (XtArgVal) command_table[i].y_pos;
	    command_call[0].closure = (XtPointer) &command_table[i].closure;
	    (void) XtCreateManagedWidget(command_table[i].name,
		commandWidgetClass, panel_widget,
		command_args, XtNumber(command_args));
	}
}
#endif	/* BUTTONS */

#else	/* !TOOLKIT */
static	Window	x_bar, y_bar;
static	int	x_bgn, x_end, y_bgn, y_end;	/* scrollbar positions */
#endif	/* TOOLKIT */

/*
 *	Mechanism to keep track of the magnifier window.  The problems are,
 *	(a) if the button is released while the window is being drawn, this
 *	could cause an X error if we continue drawing in it after it is
 *	destroyed, and
 *	(b) creating and destroying the window too quickly confuses the window
 *	manager, which is avoided by waiting for an expose event before
 *	destroying it.
 */
static	short	alt_stat;	/* 1 = wait for expose, */
				/* -1 = destroy upon expose */
static	Boolean	alt_canit;	/* stop drawing this window */

/*
 *	Data for buffered events.
 */

static	Boolean	has_arg		= False;
static	VOLATILE short	event_freq	= 70;
static	int	number		= 0;
static	int	sign		= 1;

static	void	can_exposures(), keystroke();

#ifdef	GREY
#define	gamma	resource._gamma

void
init_pix(warn)
	Boolean	warn;
{
	static	int	shrink_allocated_for = 0;
	int	i;

	if (!colors_allocated)
	{
	    Pixel plane_masks[4];
	    Pixel pixel;
	    XColor color, fc, bc;
	    XGCValues	values;

	    if (gamma == 0.0) gamma = 1.0;

	    if (!resource.copy)
		/* allocate 4 color planes for 16 colors (for GXor drawing) */
		if (!XAllocColorCells(DISP, DefaultColormapOfScreen(SCRN),
					  False, plane_masks, 4, &pixel, 1))
		    resource.copy = warn = True;

	    /* get foreground and background RGB values for interpolating */
	    fc.pixel = fore_Pixel;
	    XQueryColor(DISP, DefaultColormapOfScreen(SCRN), &fc);
	    bc.pixel = back_Pixel;
	    XQueryColor(DISP, DefaultColormapOfScreen(SCRN), &bc);

	    for (i = 0; i < 16; ++i) {
/* math.h should know this declaration better */
#if 0
		double	pow();
#endif
		double	frac = gamma > 0 ? pow((double) i / 15, 1 / gamma)
		    : 1 - pow((double) (15 - i) / 15, -gamma);

		color.red = frac * ((double) fc.red - bc.red) + bc.red;
		color.green = frac * ((double) fc.green - bc.green) + bc.green;
		color.blue = frac * ((double) fc.blue - bc.blue) + bc.blue;

		color.pixel = pixel;
		color.flags = DoRed | DoGreen | DoBlue;

		if (!resource.copy) {
		    if (i & 1) color.pixel |= plane_masks[0];
		    if (i & 2) color.pixel |= plane_masks[1];
		    if (i & 4) color.pixel |= plane_masks[2];
		    if (i & 8) color.pixel |= plane_masks[3];
		    XStoreColor(DISP, DefaultColormapOfScreen(SCRN), &color);
		    palette[i] = color.pixel;
		}
		else {
		    if (!XAllocColor(DISP, DefaultColormapOfScreen(SCRN),
			&color))
			palette[i] = (i * 100 >= density * 15)
			    ? fore_Pixel : back_Pixel;
		    else
			palette[i] = color.pixel;
		}
	    }

	    /* Make sure fore_ and back_Pixel are a part of the palette */
	    fore_Pixel = palette[15];
	    back_Pixel = palette[0];
	    if (mane.win != (Window) 0)
		XSetWindowBackground(DISP, mane.win, palette[0]);

#define	MakeGC(fcn, fg, bg)	(values.function = fcn, values.foreground=fg,\
		values.background=bg,\
		XCreateGC(DISP, RootWindowOfScreen(SCRN),\
			GCFunction|GCForeground|GCBackground, &values))

	    foreGC = ruleGC = MakeGC(resource.copy ? GXcopy : GXor,
		fore_Pixel, back_Pixel);
	    foreGC2 = NULL;

	    colors_allocated = True;
#ifdef	TOOLKIT
#define CopyByDefault() (resource.copy && !resource.copy_arg)
#else
#define CopyByDefault() (resource.copy == 2)
#endif
	    /* warn only if copy was not explicitly requested */
	    if (CopyByDefault() && warn)
		Puts("Note:  overstrike characters may be incorrect.");
	}
#undef	MakeGC

	if (mane.shrinkfactor == 1) return;

	if (shrink_allocated_for < mane.shrinkfactor) {
	    if (pixeltbl != NULL) free((char *) pixeltbl);
	    pixeltbl = (Pixel *) xmalloc((unsigned)
		(mane.shrinkfactor * mane.shrinkfactor + 1) * sizeof(Pixel),
		"pixel table");
	    shrink_allocated_for = mane.shrinkfactor;
	}

	for (i = 0; i <= mane.shrinkfactor * mane.shrinkfactor; ++i)
	    pixeltbl[i] =
		palette[(i * 30 + mane.shrinkfactor * mane.shrinkfactor)
		    / (2 * mane.shrinkfactor * mane.shrinkfactor)];
}
#endif	/* GREY */

/*
 *	Event-handling routines
 */

static	void
expose(windowrec, x, y, w, h)
	register struct WindowRec *windowrec;
	int		x, y;
	unsigned int	w, h;
{
	if (windowrec->min_x > x) windowrec->min_x = x;
	if (windowrec->max_x < x + w)
	    windowrec->max_x = x + w;
	if (windowrec->min_y > y) windowrec->min_y = y;
	if (windowrec->max_y < y + h)
	    windowrec->max_y = y + h;
}

static	void
clearexpose(windowrec, x, y, w, h)
	struct WindowRec *windowrec;
	int		x, y;
	unsigned int	w, h;
{
	XClearArea(DISP, windowrec->win, x, y, w, h, False);
	expose(windowrec, x, y, w, h);
}

static	void
scrollwindow(windowrec, x0, y0)
	register struct WindowRec *windowrec;
	int	x0, y0;
{
	int	x, y;
	int	x2 = 0, y2 = 0;
	int	ww, hh;

	x = x0 - windowrec->base_x;
	y = y0 - windowrec->base_y;
	ww = windowrec->width - x;
	hh = windowrec->height - y;
	windowrec->base_x = x0;
	windowrec->base_y = y0;
	if (currwin.win == windowrec->win) {
	    currwin.base_x = x0;
	    currwin.base_y = y0;
	}
	windowrec->min_x -= x;
	if (windowrec->min_x < 0) windowrec->min_x = 0;
	windowrec->max_x -= x;
	if (windowrec->max_x > windowrec->width)
	    windowrec->max_x = windowrec->width;
	windowrec->min_y -= y;
	if (windowrec->min_y < 0) windowrec->min_y = 0;
	windowrec->max_y -= y;
	if (windowrec->max_y > windowrec->height)
	    windowrec->max_y = windowrec->height;
	if (x < 0) {
	    x2 = -x;
	    x = 0;
	    ww = windowrec->width - x2;
	}
	if (y < 0) {
	    y2 = -y;
	    y = 0;
	    hh = windowrec->height - y2;
	}
	if (ww <= 0 || hh <= 0) {
	    XClearWindow(DISP, windowrec->win);
	    windowrec->min_x = windowrec->min_y = 0;
	    windowrec->max_x = windowrec->width;
	    windowrec->max_y = windowrec->height;
	}
	else {
	    XCopyArea(DISP, windowrec->win, windowrec->win,
		DefaultGCOfScreen(SCRN), x, y,
		(unsigned int) ww, (unsigned int) hh, x2, y2);
	    if (x > 0)
		clearexpose(windowrec, ww, 0,
		    (unsigned int) x, windowrec->height);
	    if (x2 > 0)
		clearexpose(windowrec, 0, 0,
		    (unsigned int) x2, windowrec->height);
	    if (y > 0)
		clearexpose(windowrec, 0, hh,
		    windowrec->width, (unsigned int) y);
	    if (y2 > 0)
		clearexpose(windowrec, 0, 0,
		    windowrec->width, (unsigned int) y2);
	}
}

#ifdef	TOOLKIT
/*
 *	routines for X11 toolkit
 */

static	Arg	arg_wh[] = {
	{XtNwidth,	(XtArgVal) &window_w},
	{XtNheight,	(XtArgVal) &window_h},
};

static	Position	window_x, window_y;
static	Arg	arg_xy[] = {
	{XtNx,		(XtArgVal) &window_x},
	{XtNy,		(XtArgVal) &window_y},
};

#define	get_xy()	XtGetValues(draw_widget, arg_xy, XtNumber(arg_xy))

#define	mane_base_x	0
#define	mane_base_y	0

void /* not static because SELFILE stuff in dvi_init.c needs it */
home(scrl)
	Boolean	scrl;
{
#if	PS
	psp.interrupt();
#endif
	if (!scrl) XUnmapWindow(DISP, mane.win);
	get_xy();
	if (x_bar != NULL) {
#if defined (POINTER_IS_NOT_INT)
          register long coord = (page_w - clip_w) / 2;
#else
          register int  coord = (page_w - clip_w) / 2;
#endif /* POINTER_IS_NOT_INT */

	    if (coord > home_x / mane.shrinkfactor)
		coord = home_x / mane.shrinkfactor;
	    XtCallCallbacks(x_bar, XtNscrollProc,
		(XtPointer) (window_x + coord));
	}
	if (y_bar != NULL) {
#if defined (POINTER_IS_NOT_INT)
          register long coord = (page_h - clip_h) / 2;
#else
          register int  coord = (page_h - clip_h) / 2;
#endif /* POINTER_IS_NOT_INT */

	    if (coord > home_y / mane.shrinkfactor)
		coord = home_y / mane.shrinkfactor;
	    XtCallCallbacks(y_bar, XtNscrollProc,
		(XtPointer) (window_y + coord));
	}
	if (!scrl) {
	    XMapWindow(DISP, mane.win);
	    /* Wait for the server to catch up---this eliminates flicker. */
	    XSync(DISP, False);
	}
}

static	Boolean	resized	= False;

static	void
get_geom()
{
	static	Dimension	new_clip_w, new_clip_h;
	static	Arg	arg_wh_clip[] = {
		{XtNwidth,	(XtArgVal) &new_clip_w},
		{XtNheight,	(XtArgVal) &new_clip_h},
	};
	register int	old_clip_w;

	XtGetValues(vport_widget, arg_wh, XtNumber(arg_wh));
	XtGetValues(clip_widget, arg_wh_clip, XtNumber(arg_wh_clip));
	/* Note:  widgets may be destroyed but not forgotten */
	x_bar = page_w <= new_clip_w ? NULL
	    : XtNameToWidget(vport_widget, "horizontal");
	y_bar = page_h <= new_clip_h ? NULL
	    : XtNameToWidget(vport_widget, "vertical");
	old_clip_w = clip_w;
			/* we need to do this because */
			/* sizeof(Dimension) != sizeof(int) */
	clip_w = new_clip_w;
	clip_h = new_clip_h;
	if (old_clip_w == 0) home(False);
	resized = False;
}

static	void
center(x, y)
#ifdef POINTER_IS_NOT_INT
        long x,y;
#else
	int x, y;
#endif /* POINTER_IS_NOT_INT */
{
/*	We use the clip widget here because it gives a more exact value. */
	x -= clip_w/2;
	y -= clip_h/2;
	if (x_bar) XtCallCallbacks(x_bar, XtNscrollProc, (XtPointer) x);
	if (y_bar) XtCallCallbacks(y_bar, XtNscrollProc, (XtPointer) y);
	XWarpPointer(DISP, None, None, 0, 0, 0, 0, -x, -y);
}

/*
 *	callback routines
 */

/* The following callback routine should never be called. */
	/*ARGSUSED*/
void
handle_key(widget, junk, event, cont)
	Widget	widget;
	XtPointer junk;
	XEvent	*event;
	Boolean	*cont;		/* unused */
{
	XBell(DISP, 20);
}

	/*ARGSUSED*/
void
handle_resize(widget, junk, event, cont)
	Widget	widget;
	XtPointer junk;
	XEvent	*event;
	Boolean	*cont;		/* unused */
{
	resized = True;
}

#ifdef	BUTTONS
	/*ARGSUSED*/
static	void
handle_command(widget, client_data_p, call_data)
	Widget	widget;
	XtPointer client_data_p;
	XtPointer call_data;
{
	int	client_data	= * (int *) client_data_p;

	keystroke((client_data) & 0xff, (client_data) >> 8,
		((client_data) >> 8) != 0, (XEvent *) NULL);
}
#endif	/* BUTTONS */

void
reconfig()
{
#ifdef	BUTTONS
	XtSetValues(vport_widget, resizable_off, XtNumber(resizable_off));
#endif
	XdviResizeWidget(draw_widget, page_w, page_h);
	get_geom();
}

#else	/* !TOOLKIT */

/*
 *	brute force scrollbar routines
 */

static	void
paint_x_bar()
{
	register int	new_x_bgn = mane.base_x * clip_w / page_w;
	register int	new_x_end = (mane.base_x + clip_w) * clip_w / page_w;

	if (new_x_bgn >= x_end || x_bgn >= new_x_end) {	/* no overlap */
	    XClearArea(DISP, x_bar, x_bgn, 1, x_end - x_bgn, BAR_WID, False);
	    XFillRectangle(DISP, x_bar, ruleGC,
		new_x_bgn, 1, new_x_end - new_x_bgn, BAR_WID);
	}
	else {		/* this stuff avoids flicker */
	    if (x_bgn < new_x_bgn)
		XClearArea(DISP, x_bar, x_bgn, 1, new_x_bgn - x_bgn,
		    BAR_WID, False);
	    else
		XFillRectangle(DISP, x_bar, ruleGC,
		    new_x_bgn, 1, x_bgn - new_x_bgn, BAR_WID);
	    if (new_x_end < x_end)
		XClearArea(DISP, x_bar, new_x_end, 1, x_end - new_x_end,
		    BAR_WID, False);
	    else
		XFillRectangle(DISP, x_bar, ruleGC,
		    x_end, 1, new_x_end - x_end, BAR_WID);
	}
	x_bgn = new_x_bgn;
	x_end = new_x_end;
}

static	void
paint_y_bar()
{
	register int	new_y_bgn = mane.base_y * clip_h / page_h;
	register int	new_y_end = (mane.base_y + clip_h) * clip_h / page_h;

	if (new_y_bgn >= y_end || y_bgn >= new_y_end) {	/* no overlap */
	    XClearArea(DISP, y_bar, 1, y_bgn, BAR_WID, y_end - y_bgn, False);
	    XFillRectangle(DISP, y_bar, ruleGC,
		1, new_y_bgn, BAR_WID, new_y_end - new_y_bgn);
	}
	else {		/* this stuff avoids flicker */
	    if (y_bgn < new_y_bgn)
		XClearArea(DISP, y_bar, 1, y_bgn, BAR_WID, new_y_bgn - y_bgn,
		    False);
	    else
		XFillRectangle(DISP, y_bar, ruleGC,
		    1, new_y_bgn, BAR_WID, y_bgn - new_y_bgn);
	    if (new_y_end < y_end)
		XClearArea(DISP, y_bar, 1, new_y_end,
		    BAR_WID, y_end - new_y_end, False);
	    else
		XFillRectangle(DISP, y_bar, ruleGC,
		    1, y_end, BAR_WID, new_y_end - y_end);
	}
	y_bgn = new_y_bgn;
	y_end = new_y_end;
}

static	void
scrollmane(x, y)
	int	x, y;
{
	register int	old_base_x = mane.base_x;
	register int	old_base_y = mane.base_y;

#if	PS
	psp.interrupt();
#endif
	if (x > (int) (page_w - clip_w)) x = page_w - clip_w;
	if (x < 0) x = 0;
	if (y > (int) (page_h - clip_h)) y = page_h - clip_h;
	if (y < 0) y = 0;
	scrollwindow(&mane, x, y);
	if (old_base_x != mane.base_x && x_bar) paint_x_bar();
	if (old_base_y != mane.base_y && y_bar) paint_y_bar();
}

void
reconfig()
{
	int	x_thick = 0;
	int	y_thick = 0;

		/* determine existence of scrollbars */
	if (window_w < page_w) x_thick = BAR_THICK;
	if (window_h - x_thick < page_h) y_thick = BAR_THICK;
	clip_w = window_w - y_thick;
	if (clip_w < page_w) x_thick = BAR_THICK;
	clip_h = window_h - x_thick;

		/* process drawing (clip) window */
	if (mane.win == (Window) 0) {	/* initial creation */
	    XWindowAttributes attrs;

	    mane.win = XCreateSimpleWindow(DISP, top_level, y_thick, x_thick,
			(unsigned int) clip_w, (unsigned int) clip_h, 0,
			brdr_Pixel, back_Pixel);
	    XSelectInput(DISP, mane.win, ExposureMask |
			ButtonPressMask | ButtonMotionMask | ButtonReleaseMask);
	    (void) XGetWindowAttributes(DISP, mane.win, &attrs);
	    backing_store = attrs.backing_store;
	    XMapWindow(DISP, mane.win);
	}
	else
	    XMoveResizeWindow(DISP, mane.win, y_thick, x_thick, clip_w, clip_h);

		/* process scroll bars */
	if (x_thick) {
	    if (x_bar) {
		XMoveResizeWindow(DISP, x_bar,
		    y_thick - 1, -1, clip_w, BAR_THICK - 1);
		paint_x_bar();
	    }
	    else {
		x_bar = XCreateSimpleWindow(DISP, top_level, y_thick - 1, -1,
				(unsigned int) clip_w, BAR_THICK - 1, 1,
				brdr_Pixel, back_Pixel);
		XSelectInput(DISP, x_bar,
			ExposureMask | ButtonPressMask | Button2MotionMask);
		XMapWindow(DISP, x_bar);
	    }
	    x_bgn = mane.base_x * clip_w / page_w;
	    x_end = (mane.base_x + clip_w) * clip_w / page_w;
	}
	else
	    if (x_bar) {
		XDestroyWindow(DISP, x_bar);
		x_bar = (Window) 0;
	    }

	if (y_thick) {
	    if (y_bar) {
		XMoveResizeWindow(DISP, y_bar,
		    -1, x_thick - 1, BAR_THICK - 1, clip_h);
		paint_y_bar();
	    }
	    else {
		y_bar = XCreateSimpleWindow(DISP, top_level, -1, x_thick - 1,
				BAR_THICK - 1, (unsigned int) clip_h, 1,
				brdr_Pixel, back_Pixel);
		XSelectInput(DISP, y_bar,
			ExposureMask | ButtonPressMask | Button2MotionMask);
		XMapWindow(DISP, y_bar);
	    }
	    y_bgn = mane.base_y * clip_h / page_h;
	    y_end = (mane.base_y + clip_h) * clip_h / page_h;
	}
	else
	    if (y_bar) {
		XDestroyWindow(DISP, y_bar);
		y_bar = (Window) 0;
	    }
}

static	void
home(scrl)
	Boolean	scrl;
{
	int	x = 0, y = 0;

	if (page_w > clip_w) {
	    x = (page_w - clip_w) / 2;
	    if (x > home_x / mane.shrinkfactor)
		x = home_x / mane.shrinkfactor;
	}
	if (page_h > clip_h) {
	    y = (page_h - clip_h) / 2;
	    if (y > home_y / mane.shrinkfactor)
		y = home_y / mane.shrinkfactor;
	}
	if (scrl)
	    scrollmane(x, y);
	else {
	    mane.base_x = x;
	    mane.base_y = y;
	    if (currwin.win == mane.win) {
		currwin.base_x = x;
		currwin.base_y = y;
	    }
	    if (x_bar) paint_x_bar();
	    if (y_bar) paint_y_bar();
	}
}

#define	get_xy()
#define	window_x 0
#define	window_y 0
#define	mane_base_x	mane.base_x
#define	mane_base_y	mane.base_y
#endif	/* TOOLKIT */

static	void
compute_mag_pos(xp, yp)
	int	*xp, *yp;
{
	register int t;

	t = mag_x + main_x - alt.width/2;
	if (t > WidthOfScreen(SCRN) - (int) alt.width - 2*MAGBORD)
	    t = WidthOfScreen(SCRN) - (int) alt.width - 2*MAGBORD;
	if (t < 0) t = 0;
	*xp = t;
	t = mag_y + main_y - alt.height/2;
	if (t > HeightOfScreen(SCRN) - (int) alt.height - 2*MAGBORD)
	    t = HeightOfScreen(SCRN) - (int) alt.height - 2*MAGBORD;
	if (t < 0) t = 0;
	*yp = t;
}

#ifdef	TOOLKIT
	/*ARGSUSED*/
void
handle_button(widget, junk, ev, cont)
	Widget	widget;
	XtPointer junk;
	XEvent *ev;
#define	event	(&(ev->xbutton))
	Boolean	*cont;		/* unused */
#else	/* !TOOLKIT */
void
handle_button(event)
	XButtonEvent *event;
#endif	/* TOOLKIT */
{
	int	x, y;
	struct mg_size_rec	*size_ptr = mg_size + event->button - 1;
	XSetWindowAttributes attr;

	if (alt.win != (Window) 0 || mane.shrinkfactor == 1 || size_ptr->w <= 0)
	    XBell(DISP, 20);
	else {
	    mag_x = event->x;
	    mag_y = event->y;
	    alt.width = size_ptr->w;
	    alt.height = size_ptr->h;
	    main_x = event->x_root - mag_x;
	    main_y = event->y_root - mag_y;
	    compute_mag_pos(&x, &y);
	    alt.base_x = (event->x + mane_base_x) * mane.shrinkfactor -
		alt.width/2;
	    alt.base_y = (event->y + mane_base_y) * mane.shrinkfactor -
		alt.height/2;
	    attr.save_under = True;
	    attr.border_pixel = brdr_Pixel;
	    attr.background_pixel = back_Pixel;
	    attr.override_redirect = True;
	    alt.win = XCreateWindow(DISP, RootWindowOfScreen(SCRN),
			x, y, alt.width, alt.height, MAGBORD,
			0,	/* depth from parent */
			InputOutput, (Visual *) CopyFromParent,
			CWSaveUnder | CWBorderPixel | CWBackPixel |
			CWOverrideRedirect, &attr);
	    XSelectInput(DISP, alt.win, ExposureMask);
	    XMapWindow(DISP, alt.win);
	    alt_stat = 1;	/* waiting for exposure */
	}
}

#ifdef	TOOLKIT
#undef	event

	/*ARGSUSED*/
void
handle_motion(widget, junk, ev, cont)
	Widget	widget;
	XtPointer junk;
	XEvent *ev;
#define	event	(&(ev->xmotion))
	Boolean	*cont;		/* unused */
{
	new_mag_x = event->x;
	main_x = event->x_root - new_mag_x;
	new_mag_y = event->y;
	main_y = event->y_root - new_mag_y;
	mag_moved = (new_mag_x != mag_x || new_mag_y != mag_y);
}

#undef	event
#endif	/* TOOLKIT */

static	void
movemag(x, y)
	int	x, y;
{
	int	xx, yy;

	mag_x = x;
	mag_y = y;
	if (mag_x == new_mag_x && mag_y == new_mag_y) mag_moved = False;
	compute_mag_pos(&xx, &yy);
	XMoveWindow(DISP, alt.win, xx, yy);
	scrollwindow(&alt,
	    (x + mane_base_x) * mane.shrinkfactor - (int) alt.width/2,
	    (y + mane_base_y) * mane.shrinkfactor - (int) alt.height/2);
}

#ifdef	TOOLKIT
	/*ARGSUSED*/
void
handle_release(widget, junk, ev, cont)
	Widget	widget;
	XtPointer junk;
	XEvent *ev;
#define	event	(&(ev->xbutton))
	Boolean	*cont;		/* unused */
#else	/* !TOOLKIT */
void
handle_release()
#endif	/* TOOLKIT */
{
	if (alt.win != (Window) 0)
	    if (alt_stat) alt_stat = -1;	/* destroy upon expose */
	    else {
		XDestroyWindow(DISP, alt.win);
		if (currwin.win == alt.win) alt_canit = True;
		alt.win = (Window) 0;
		mag_moved = False;
		can_exposures(&alt);
	    }
}

#ifdef	TOOLKIT
#undef	event

	/*ARGSUSED*/
void
handle_exp(widget, closure, ev, cont)
	Widget	widget;
	XtPointer closure;
	register XEvent *ev;
#define	event	(&(ev->xexpose))
	Boolean	*cont;		/* unused */
{
	struct WindowRec *windowrec = (struct WindowRec *) closure;

	if (windowrec == &alt)
	    if (alt_stat < 0) {	/* destroy upon exposure */
		alt_stat = 0;
		handle_release(widget, (caddr_t) NULL, ev, (Boolean *) NULL);
		return;
	    }
	    else
		alt_stat = 0;
	expose(windowrec, event->x, event->y,
	    (unsigned int) event->width, (unsigned int) event->height);
}

#undef	event
#endif	/* TOOLKIT */

/* |||
 *	Currently the event handler does not coordinate XCopyArea requests
 *	with GraphicsExpose events.  This can lead to problems if the window
 *	is partially obscured and one, for example, drags a scrollbar.
 */

static	void
keystroke(ch, number0, arg0, eventp)
	char	ch;
	int	number0;
	Boolean	arg0;
	XEvent	*eventp;
{
	int	next_page;
#ifdef	TOOLKIT
	Window	ww;
#endif

	next_page = current_page;
	switch (ch) {
	    case 'q':
	    case '\003':	/* control-C */
	    case '\004':	/* control-D */
#ifdef	VMS
	    case '\032':	/* control-Z */
#endif
#if	PS
		psp.destroy();
#endif
		exit(0);

	    case 'n':
	    case 'f':
	    case ' ':
	    case '\r':
	    case '\n':
		/* scroll forward; i.e. go to relative page */
		next_page = current_page + (arg0 ? number0 : 1);
		break;
	    case 'p':
	    case 'b':
	    case '\b':
	    case '\177':	/* Del */
		/* scroll backward */
		next_page = current_page - (arg0 ? number0 : 1);
		break;
	    case 'g':
		/* go to absolute page */
		next_page = (arg0 ? number0 - pageno_correct :
		    total_pages - 1);
		break;
	    case 'P':		/* declare current page */
		pageno_correct = arg0 * number0 - current_page;
		return;
	    case 'k':		/* toggle keep-position flag */
		resource.keep_flag = (arg0 ? number0 : !resource.keep_flag);
		return;
	    case '\f':
		/* redisplay current page */
		break;
	    case '^':
		home(True);
		return;
#ifdef	TOOLKIT
	    case 'l':
		if (!x_bar) goto bad;
#ifdef POINTER_IS_NOT_INT
		XtCallCallbacks(x_bar, XtNscrollProc,
		    (XtPointer) (-2 * (long) clip_w / 3));
#else
		XtCallCallbacks(x_bar, XtNscrollProc,
		    (XtPointer) (-2 * (int) clip_w / 3));
#endif /* POINTER_IS_NOT_INT */
		return;
	    case 'r':
		if (!x_bar) goto bad;
#ifdef POINTER_IS_NOT_INT
		XtCallCallbacks(x_bar, XtNscrollProc,
		    (XtPointer) (2 * (long) clip_w / 3));
#else
		XtCallCallbacks(x_bar, XtNscrollProc,
		    (XtPointer) (2 * (int) clip_w / 3));
#endif /* POINTER_IS_NOT_INT */
		return;
	    case 'u':
		if (!y_bar) goto bad;
#ifdef POINTER_IS_NOT_INT
		XtCallCallbacks(y_bar, XtNscrollProc,
		    (XtPointer) (-2 * (long) clip_h / 3));
#else
		XtCallCallbacks(y_bar, XtNscrollProc,
		    (XtPointer) (-2 * (int) clip_h / 3));
#endif /* POINTER_IS_NOT_INT */
		return;
	    case 'd':
		if (!y_bar) goto bad;
#ifdef POINTER_IS_NOT_INT
		XtCallCallbacks(y_bar, XtNscrollProc,
		    (XtPointer) (2 * (long) clip_h / 3));
#else
		XtCallCallbacks(y_bar, XtNscrollProc,
		    (XtPointer) (2 * (int) clip_h / 3));
#endif /* POINTER_IS_NOT_INT */
		return;
	    case 'c':
		center(eventp->xkey.x, eventp->xkey.y);
		return;
	    case 'M':
		(void) XTranslateCoordinates(DISP, eventp->xkey.window,
			mane.win, eventp->xkey.x, eventp->xkey.y,
			&home_x, &home_y, &ww);	/* throw away last argument */
		home_x *= mane.shrinkfactor;
		home_y *= mane.shrinkfactor;
		return;
#ifdef	BUTTONS
	    case 'x':
		if (arg0 && resource.expert == (number0 != 0)) return;
		XtSetValues(vport_widget, resizable_on,
		    XtNumber(resizable_off));
		if (resource.expert) {	/* create buttons */
		    XdviResizeWidget(vport_widget,
			window_w -= XTRA_WID, window_h);
		    create_buttons((XtArgVal) window_h);
		    resource.expert = False;
		}
		else {		/* destroy buttons */
		    XtDestroyWidget(panel_widget);
		    XtDestroyWidget(line_widget);
		    XdviResizeWidget(vport_widget,
			window_w += XTRA_WID, window_h);
		    resource.expert = True;
		}
		return;
#endif	/* BUTTONS */
#else	/* !TOOLKIT */
	    case 'l':
		if (mane.base_x <= 0) goto bad;
		scrollmane(mane.base_x - 2 * (int) clip_w / 3, mane.base_y);
		return;
	    case 'r':
		if (mane.base_x >= page_w - clip_w) goto bad;
		scrollmane(mane.base_x + 2 * (int) clip_w / 3, mane.base_y);
		return;
	    case 'u':
		if (mane.base_y <= 0) goto bad;
		scrollmane(mane.base_x, mane.base_y - 2 * (int) clip_h / 3);
		return;
	    case 'd':
		if (mane.base_y >= page_h - clip_h) goto bad;
		scrollmane(mane.base_x, mane.base_y + 2 * (int) clip_h / 3);
		return;
	    case 'c':	/* unchecked scrollmane() */
		scrollwindow(&mane, mane.base_x + eventp->xkey.x - clip_w/2,
		    mane.base_y + eventp->xkey.y - clip_h/2);
		if (x_bar) paint_x_bar();
		if (y_bar) paint_y_bar();
		XWarpPointer(DISP, None, None, 0, 0, 0, 0,
		    clip_w/2 - eventp->xkey.x, clip_h/2 - eventp->xkey.y);
		return;
	    case 'M':
		home_x = (eventp->xkey.x - (y_bar ? BAR_THICK : 0)
		    + mane.base_x) * mane.shrinkfactor;
		home_y = (eventp->xkey.y - (x_bar ? BAR_THICK : 0)
		    + mane.base_y) * mane.shrinkfactor;
		return;
#endif	/* TOOLKIT */

	    case '\020':	/* Control P */
		Printf("Unit = %d, bitord = %d, byteord = %d\n",
		    BitmapUnit(DISP), BitmapBitOrder(DISP),
		    ImageByteOrder(DISP));
		return;
	    case 's':
		if (!arg0) {
		    int temp;
		    number0 = ROUNDUP(unshrunk_page_w, window_w - 2);
		    temp = ROUNDUP(unshrunk_page_h, window_h - 2);
		    if (number0 < temp) number0 = temp;
		}
		if (number0 <= 0) goto bad;
		if (number0 == mane.shrinkfactor) return;
		mane.shrinkfactor = number0;
		init_page();
		if (number0 != 1 && number0 != bak_shrink) {
		    bak_shrink = number0;
#ifdef	GREY
		    if (use_grey) init_pix(False);
#endif
		    reset_fonts();
		}
		reconfig();
		home(False);
		break;
	    case 'S':
		if (!arg0) goto bad;
		if (number0 < 0) goto bad;
		if (number0 == density) return;
		density = number0;
		reset_fonts();
		if (mane.shrinkfactor == 1) return;
		break;
#ifdef	GREY
	    case 'G':
		use_grey = (arg0 ? number0 : !use_grey);
	 	if (use_grey) {
		  if (arg0 && (number0 != 1)) {
		    gamma = number0 / 100.0;
		    colors_allocated = 0;	/* Rethink this in init_pix. */
		  }
		  init_pix(False);
		}
		reset_fonts();
		break;
#endif

#if	PS
	    case 'v':
		if (!arg0 || resource._postscript != !number0) {
		    resource._postscript = !resource._postscript;
		    psp.toggle();
		}
		break;
#endif

#ifdef SELFILE
            case 'F':
		++dvi_time ; /* notice we want a new file in check_dvi_file */
		break ;
#endif  /* SELFILE */

	    case 'R':
		/* reread DVI file */
		--dvi_time;	/* then it will notice a change */
		break;
	    default:
		goto bad;
	}
	if (0 <= next_page && next_page < total_pages) {
	    if (current_page != next_page) {
		current_page = next_page;
		hush_spec_now = hush_spec;
		if (!resource.keep_flag) home(False);
	    }
	    canit = True;
	    XFlush(DISP);
	    return;	/* don't use longjmp here:  it might be called from
			 * within the toolkit, and we don't want to longjmp out
			 * of Xt routines. */
	}
	bad:  XBell(DISP, 10);
}


#define	TRSIZE	100

void
read_events(wait)
	wide_bool	wait;
{
	char	ch;
	Boolean	arg0;
	int	number0;
	XEvent	event;
	char	trbuf[TRSIZE];
#define string xdvi_string /* kpathsea has a type `string', and cc chokes */
	char	*string;
	int	nbytes;

	alt_canit = False;
	for (;;) {
	    ch = '\0';
	    event_counter = event_freq;
	    /*
	     * The above line clears the flag indicating that an event is
	     * pending.  So if an event comes in right now, the flag will be
	     * set again needlessly, but we just end up making an extra call.
	     * Also, be careful about destroying the magnifying glass while
	     * writing it.
	     */
	    if (!XtPending() && (!wait || canit || mane.min_x < MAXDIM ||
		    alt.min_x < MAXDIM || mag_moved)) {
		if (!wait && (canit | alt_canit)) {
#if	PS
		    psp.interrupt();
#endif
		    if (allow_can) longjmp(canit_env, 1);
		}
		return;
	    }
#ifdef	TOOLKIT
	    XtNextEvent(&event);
	    if (resized) get_geom();
	    if (event.xany.window == alt.win && event.type == Expose) {
		handle_exp((Widget) NULL, (XtPointer) &alt, &event,
		    (Boolean *) NULL);
		continue;
	    }
	    if (event.type != KeyPress) {
		(void) XtDispatchEvent(&event);
		continue;
	    }
	    string = trbuf;
	    nbytes = XLookupString(&event.xkey, string, TRSIZE, (KeySym *) NULL,
		(XComposeStatus *) NULL);
	    if (nbytes > 1) ch = '?';
	    if (nbytes != 0) ch = *string;
#else	/* !TOOLKIT */

	    XNextEvent(DISP, &event);
	    if (event.xany.window == mane.win || event.xany.window == alt.win) {
		struct WindowRec *wr = &mane;

		if (event.xany.window == alt.win) {
		    wr = &alt;
		    /* check in case we already destroyed the window */
		    if (alt_stat < 0) { /* destroy upon exposure */
			alt_stat = 0;
			handle_release();
			continue;
		    }
		    else
			alt_stat = 0;
		}
		switch (event.type) {
		case GraphicsExpose:
		case Expose:
		    expose(wr, event.xexpose.x, event.xexpose.y,
			event.xexpose.width, event.xexpose.height);
		    break;

		case MotionNotify:
		    new_mag_x = event.xmotion.x;
		    new_mag_y = event.xmotion.y;
		    mag_moved = (new_mag_x != mag_x || new_mag_y != mag_y);
		    break;

		case ButtonPress:
		    handle_button(&event.xbutton);
		    break;

		case ButtonRelease:
		    handle_release();
		    break;
		}	/* end switch */
	    }	/* end if window == {mane,alt}.win */

	    else if (event.xany.window == x_bar) {
		if (event.type == Expose)
		    XFillRectangle(DISP, x_bar, ruleGC,
			x_bgn, 1, x_end - x_bgn, BAR_WID);
		else if (event.type == MotionNotify)
		    scrollmane(event.xmotion.x * page_w / clip_w,
			mane.base_y);
		else switch (event.xbutton.button)
		{
		    case 1:
			scrollmane(mane.base_x + event.xbutton.x, mane.base_y);
			break;
		    case 2:
			scrollmane(event.xbutton.x * page_w / clip_w,
			    mane.base_y);
			break;
		    case 3:
			scrollmane(mane.base_x - event.xbutton.x, mane.base_y);
		}
	    }

	    else if (event.xany.window == y_bar) {
		if (event.type == Expose)
		    XFillRectangle(DISP, y_bar, ruleGC,
			1, y_bgn, BAR_WID, y_end - y_bgn);
		else if (event.type == MotionNotify)
		    scrollmane(mane.base_x,
			event.xmotion.y * page_h / clip_h);
		else switch (event.xbutton.button)
		{
		    case 1:
			scrollmane(mane.base_x, mane.base_y + event.xbutton.y);
			break;
		    case 2:
			scrollmane(mane.base_x,
			    event.xbutton.y * page_h / clip_h);
			break;
		    case 3:
			scrollmane(mane.base_x, mane.base_y - event.xbutton.y);
		}
	    }

	    else if (event.xany.window == top_level)
		switch (event.type) {
		case ConfigureNotify:
		    if (event.xany.window == top_level &&
			(event.xconfigure.width != window_w ||
			event.xconfigure.height != window_h)) {
			    register Window old_mane_win = mane.win;

			    window_w = event.xconfigure.width;
			    window_h = event.xconfigure.height;
			    reconfig();
			    if (old_mane_win == (Window) 0) home(False);
		    }
		    break;

		case MapNotify:		/* if running w/o WM */
		    if (mane.win == (Window) 0) {
			reconfig();
			home(False);
		    }
		    break;

		case KeyPress:
		    string = trbuf;
		    nbytes = XLookupString(&event.xkey, string, TRSIZE, NULL,
			NULL);
		    if (nbytes != 0) ch = *string;
		    if (nbytes > 1) ch = '?';
		    break;
		}
#endif	/* TOOLKIT */
	    if (ch == '\0') continue;
	    if (ch >= '0' && ch <= '9') {
		has_arg = True;
		number = number * 10 + sign * (ch - '0');
		continue;
	    }
	    else if (ch == '-') {
		has_arg = True;
		sign = -1;
		number = 0;
		continue;
	    }
	    number0 = number;
	    number = 0;
	    sign = 1;
	    arg0 = has_arg;
	    has_arg = False;
	    keystroke(ch, number0, arg0, &event);
	}
}

static	void
redraw(windowrec)
	struct WindowRec *windowrec;
{

	currwin = *windowrec;
	min_x = currwin.min_x + currwin.base_x;
	min_y = currwin.min_y + currwin.base_y;
	max_x = currwin.max_x + currwin.base_x;
	max_y = currwin.max_y + currwin.base_y;
	can_exposures(windowrec);

	if (debug & DBG_EVENT)
	    Printf("Redraw %d x %d at (%d, %d) (base=%d,%d)\n", max_x - min_x,
		max_y - min_y, min_x, min_y, currwin.base_x, currwin.base_y);
	XDefineCursor(DISP, mane.win, redraw_cursor);
	XFlush(DISP);
	if (setjmp(dvi_env)) {
	    XClearWindow(DISP, mane.win);
	    get_xy();
	    XDrawString(DISP, mane.win, foreGC,
		5 - window_x, 5 + X11HEIGHT - window_y,
		dvi_oops_msg, strlen(dvi_oops_msg));
	    if (dvi_file) {
		Fclose(dvi_file);
		dvi_file = NULL;
	    }
	}
	else {
	    draw_page();
	    hush_spec_now = True;
	}
}

void
redraw_page()
{
	if (debug & DBG_EVENT) Fputs("Redraw page:  ", stdout);
	XClearWindow(DISP, mane.win);
	if (backing_store != NotUseful) {
	    mane.min_x = mane.min_y = 0;
	    mane.max_x = page_w;
	    mane.max_y = page_h;
	}
	else {
	    get_xy();
	    mane.min_x = -window_x;
	    mane.max_x = -window_x + clip_w;
	    mane.min_y = -window_y;
	    mane.max_y = -window_y + clip_h;
	}
	redraw(&mane);
}

/*
 *	Interrupt system for receiving events.  The program sets a flag
 *	whenever an event comes in, so that at the proper time (i.e., when
 *	reading a new dvi item), we can check incoming events to see if we
 *	still want to go on printing this page.  This way, one can stop
 *	displaying a page if it is about to be erased anyway.  We try to read
 *	as many events as possible before doing anything and base the next
 *	action on all events read.
 *	Note that the Xlib and Xt routines are not reentrant, so the most we
 *	can do is set a flag in the interrupt routine and check it later.
 *	Also, sometimes the interrupts are not generated (some systems only
 *	guarantee that SIGIO is generated for terminal files, and on the system
 *	I use, the interrupts are not generated if I use "(xdvi foo &)" instead
 *	of "xdvi foo").  Therefore, there is also a mechanism to check the
 *	event queue every 70 drawing operations or so.  This mechanism is
 *	disabled if it turns out that the interrupts do work.
 *	For a fuller discussion of some of the above, see xlife in
 *	comp.sources.x.
 */

static	void
can_exposures(windowrec)
	struct WindowRec *windowrec;
{
	windowrec->min_x = windowrec->min_y = MAXDIM;
	windowrec->max_x = windowrec->max_y = 0;
}

#if	HAS_SIGIO
/* ARGSUSED */
static	void
handle_intr(signo)
	int	signo;
{
	event_counter = 1;
	event_freq = -1;	/* forget Plan B */
}

static	void
enable_intr() {
	int	socket	= ConnectionNumber(DISP);

#ifdef SA_RESTART
	/* Subprocess handling, e.g., MakeTeXPK, fails on the Alpha without
	   this, because SIGIO interrupts the call of system(3), since OSF/1
	   doesn't retry interrupted wait calls by default.  From code by
	   maj@cl.cam.ac.uk.  */
	{
          struct sigaction a;
          a.sa_handler = handle_intr;
          sigemptyset (&a.sa_mask);
          sigaddset (&a.sa_mask, SIGIO);
          a.sa_flags = SA_RESTART;
          sigaction (SIGIO, &a, NULL);
        }
#else
        (void) signal(SIGIO, handle_intr);
#endif /* SA_RESTART */
	(void) fcntl(socket, F_SETOWN, getpid());
	(void) fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) | FASYNC);
}
#endif	/* HAS_SIGIO */

void
do_pages()
{
	if (debug & DBG_BATCH) {
#ifdef	TOOLKIT
	    while (mane.min_x == MAXDIM) read_events(True);
#else	/* !TOOLKIT */
	    while (mane.min_x == MAXDIM)
		if (setjmp(canit_env)) break;
		else read_events(True);
#endif	/* TOOLKIT */
	    for (current_page = 0; current_page < total_pages; ++current_page)
		redraw_page();
	}
	else {	/* normal operation */
#if	HAS_SIGIO
	    enable_intr();
#endif
#ifdef	__convex__
	    /* convex C turns off optimization for the entire function
	       if setjmp return value is discarded.*/
	    if (setjmp(canit_env))	/*optimize me*/;
#else
	    (void) setjmp(canit_env);
#endif
	    for (;;) {
		if (mane.win != (Window) 0)
		    XDefineCursor(DISP, mane.win, ready_cursor);
		read_events(True);
		if (canit) {
		    canit = False;
		    can_exposures(&mane);
		    can_exposures(&alt);
		    redraw_page();
		}
		else if (mag_moved) {
		    if (alt.win == (Window) 0) mag_moved = False;
		    else if (abs(new_mag_x - mag_x) >
			2 * abs(new_mag_y - mag_y))
			    movemag(new_mag_x, mag_y);
		    else if (abs(new_mag_y - mag_y) >
			2 * abs(new_mag_x - mag_x))
			    movemag(mag_x, new_mag_y);
		    else movemag(new_mag_x, new_mag_y);
		}
		else if (alt.min_x < MAXDIM) redraw(&alt);
		else if (mane.min_x < MAXDIM) redraw(&mane);
		XFlush(DISP);
	    }
	}
}
