/*
 * Ghostview.c -- Ghostview widget.
 * Copyright (C) 1992  Timothy O. Theisen
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   Author: Tim Theisen           Systems Programmer
 * Internet: tim@cs.wisc.edu       Department of Computer Sciences
 *     UUCP: uwvax!tim             University of Wisconsin-Madison
 *    Phone: (608)262-0438         1210 West Dayton Street
 *      FAX: (608)262-9777         Madison, WI   53706
 */

/* This file is part of the hacked version of the ghostview package */
/* which is distributed under the terms of the gnu license. The */
/* modification referred to above is by Tanmoy Bhattacharya, */
/* <tanmoy@qcd.lanl.gov> on Nov 17, 1994. Neither the modification, */
/* nor the original program provides any warranty. */

#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>
#include <X11/Xos.h>
#include "GhostviewP.h"
#include "pdf.h"
#include <ctype.h>

#ifndef XlibSpecificationRelease
typedef char *XPointer;
#endif

#include <signal.h>
#ifdef SIGNALRETURNSINT
#define SIGVAL int
#else
#define SIGVAL void
#endif

#ifdef NON_BLOCKING_IO
#include <fcntl.h>
/* if POSIX O_NONBLOCK is not available, use O_NDELAY */
#if !defined(O_NONBLOCK) && defined(O_NDELAY)
#define O_NONBLOCK O_NDELAY
#endif
#endif

#include <errno.h>
/* BSD 4.3 errno.h does not declare errno */
extern int errno;
/* Both error returns are checked for non-blocking I/O. */
/* Manufacture the other error code if only one exists. */
#if !defined(EWOULDBLOCK) && defined(EAGAIN)
#define EWOULDBLOCK EAGAIN
#endif
#if !defined(EAGAIN) && defined(EWOULDBLOCK)
#define EAGAIN EWOULDBLOCK
#endif

#ifndef VMS
/* GV_BUFSIZ is set to the minimum POSIX PIPE_BUF to ensure that
 * nonblocking writes to ghostscript will work properly.
 */
#define GV_BUFSIZ 1024*1024
#else /* VMS */
/*
** GV_BUFSIZ is the maximum length line we can handle, so we up it to 1024
*/
#define GV_BUFSIZ 1024*1024
#endif /* VMS */

static void ComputeXdpi();
static void ComputeYdpi();

static XtResource resources[] = {
#define offset(field) XtOffsetOf(GhostviewRec, ghostview.field)
    { XtNarguments, XtCArguments, XtRString, sizeof(String),
	  offset(arguments), XtRString, (XtPointer)NULL },
    { XtNbottomMargin, XtCMargin, XtRInt, sizeof(int),
	  offset(bottom_margin), XtRImmediate, (XtPointer)0 },
    { XtNbusyCursor, XtCCursor, XtRCursor, sizeof(XtPointer),
	  offset(busy_cursor), XtRString, "watch" },
    { XtNcallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	  offset(callback), XtRCallback, (XtPointer)NULL },
    { XtNcursor, XtCCursor, XtRCursor, sizeof(XtPointer),
	  offset(cursor), XtRString, "crosshair" },
    { XtNfilename, XtCFilename, XtRString, sizeof(String),
	  offset(filename), XtRString, (XtPointer)NULL },
    { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
	  offset(foreground), XtRString, XtDefaultForeground},
    { XtNinterpreter, XtCInterpreter, XtRString, sizeof(String),
	  offset(interpreter), XtRString, "gs" },
    { XtNleftMargin, XtCMargin, XtRInt, sizeof(int),
	  offset(left_margin), XtRImmediate, (XtPointer)0 },
    { XtNllx, XtCBoundingBox, XtRInt, sizeof(int),
	  offset(llx), XtRImmediate, (XtPointer)0 },
    { XtNlly, XtCBoundingBox, XtRInt, sizeof(int),
	  offset(lly), XtRImmediate, (XtPointer)0 },
    { XtNmessageCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	  offset(message_callback), XtRCallback, (XtPointer)NULL },
    { XtNorientation, XtCOrientation, XtRPageOrientation,
	  sizeof(XtPageOrientation), offset(orientation), XtRImmediate,
	  (XtPointer)XtPageOrientationPortrait },
    { XtNoutputCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	  offset(output_callback), XtRCallback, (XtPointer)NULL },
    { XtNpalette, XtCPalette, XtRPalette, sizeof(XtPalette),
	  offset(palette), XtRImmediate, (XtPointer)XtPaletteColor },
    { XtNquiet, XtCQuiet, XtRBoolean, sizeof(Boolean),
	  offset(quiet), XtRImmediate, (XtPointer)True },
    { XtNrightMargin, XtCMargin, XtRInt, sizeof(int),
	  offset(right_margin), XtRImmediate, (XtPointer)0 },
    { XtNsafer, XtCSafer, XtRBoolean, sizeof(Boolean),
	  offset(safer), XtRImmediate, (XtPointer)True },
    { XtNtopMargin, XtCMargin, XtRInt, sizeof(int),
	  offset(top_margin), XtRImmediate, (XtPointer)0 },
    { XtNuseBackingPixmap, XtCUseBackingPixmap, XtRBoolean, sizeof(Boolean),
	  offset(use_bpixmap), XtRImmediate, (XtPointer)True },
    { XtNurx, XtCBoundingBox, XtRInt, sizeof(int),
	  offset(urx), XtRImmediate, (XtPointer)612 },
    { XtNury, XtCBoundingBox, XtRInt, sizeof(int),
	  offset(ury), XtRImmediate, (XtPointer)792 },
    { XtNxdpi, XtCResolution, XtRFloat, sizeof(float),
	  offset(xdpi), XtRCallProc, (XtPointer)ComputeXdpi },
    { XtNydpi, XtCResolution, XtRFloat, sizeof(float),
	  offset(ydpi), XtRCallProc, (XtPointer)ComputeYdpi },
#undef offset
};

static void Message();
static void Notify();
static void Input();
static void Output();

static void ClassInitialize();
static void ClassPartInitialize();
static void Initialize();
static void Realize();
static void Destroy();
static void Resize();
static Boolean SetValues();
static XtGeometryResult QueryGeometry();

static void Layout();
static Boolean ComputeSize();
static void ChangeSize();
static Boolean Setup();
static void StartInterpreter();
static void StopInterpreter();
static void InterpreterFailed();

static XtActionsRec actions[] =
{
    {"message",	Message},
    {"notify",	Notify},
};

/* notify takes zero to four parameters.  The first two give the width and
 * height of the zoom requested in the default user coordinate system.
 * If they are omitted, a default value of 72 is provided.  If the second
 * parameter is omitted, the zoom area is assumed to be a square.
 * The next two parameters give the desired resolution of the zoom window.
 * If they are omitted, a default value of 300 is provided. If the four
 * parameter is omitted, the y resolution is assumed to be equal to the
 * x resolution.
 */
static char translations[] =
"<Message>:		message()	\n\
<Btn1Down>:		notify(72)	\n\
<Btn2Down>:		notify(108)	\n\
<Btn3Down>:		notify(144)	\n\
";

GhostviewClassRec ghostviewClassRec = {
  { /* core fields */
    /* superclass		*/	(WidgetClass) &coreClassRec,
    /* class_name		*/	"Ghostview",
    /* widget_size		*/	sizeof(GhostviewRec),
    /* class_initialize		*/	ClassInitialize,
    /* class_part_initialize	*/	ClassPartInitialize,
    /* class_inited		*/	FALSE,
    /* initialize		*/	Initialize,
    /* initialize_hook		*/	NULL,
    /* realize			*/	Realize,
    /* actions			*/	actions,
    /* num_actions		*/	XtNumber(actions),
    /* resources		*/	resources,
    /* num_resources		*/	XtNumber(resources),
    /* xrm_class		*/	NULLQUARK,
    /* compress_motion		*/	TRUE,
    /* compress_exposure	*/	TRUE,
    /* compress_enterleave	*/	TRUE,
    /* visible_interest		*/	FALSE,
    /* destroy			*/	Destroy,
    /* resize			*/	Resize,
    /* expose			*/	NULL,
    /* set_values		*/	SetValues,
    /* set_values_hook		*/	NULL,
    /* set_values_almost	*/	XtInheritSetValuesAlmost,
    /* get_values_hook		*/	NULL,
    /* accept_focus		*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* tm_table			*/	translations,
    /* query_geometry		*/	QueryGeometry,
    /* display_accelerator	*/	XtInheritDisplayAccelerator,
    /* extension		*/	NULL
  },
  { /* ghostview fields */
    /* ghostview		*/	NULL,
    /* gv_colors		*/	NULL,
    /* next			*/	NULL,
    /* page			*/	NULL,
    /* done			*/	NULL
  }
};

WidgetClass ghostviewWidgetClass = (WidgetClass)&ghostviewClassRec;

/* Procedures that compute the default xdpi and ydpi from display parameters */

static void
ComputeXdpi(w, offset, value)
    Widget w;
    int offset;
    XrmValue *value;
{
    static float xdpi;
    xdpi = 25.4 * WidthOfScreen(XtScreen(w)) / WidthMMOfScreen(XtScreen(w));
    value->addr = (XtPointer) &xdpi;
}

static void
ComputeYdpi(w, offset, value)
    Widget w;
    int offset;
    XrmValue *value;
{
    static float ydpi;
    ydpi = 25.4 * HeightOfScreen(XtScreen(w)) / HeightMMOfScreen(XtScreen(w));
    value->addr = (XtPointer) &ydpi;
}

/* Message action routine.
 * Passes ghostscript message events back to application via
 * the message callback.  It also marks the interpreter as
 * being not busy at the end of page, and stops the interpreter
 * when it send a "done" message.
 */
static void
Message(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;		/* unused */
    Cardinal *num_params;	/* unused */
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    GhostviewWidgetClass gvc = (GhostviewWidgetClass) XtClass(w);

    gvw->ghostview.mwin = event->xclient.data.l[0];
    if (event->xclient.message_type ==
	XmuInternAtom(XtDisplay(w), gvc->ghostview_class.page)) {
	gvw->ghostview.busy = False;
	XDefineCursor(XtDisplay(gvw), XtWindow(gvw), gvw->ghostview.cursor);
	XtCallCallbackList(w, gvw->ghostview.message_callback, "Page");
    } else if (event->xclient.message_type ==
	       XmuInternAtom(XtDisplay(w), gvc->ghostview_class.done)) {
	StopInterpreter(w);
	XtCallCallbackList(w, gvw->ghostview.message_callback, "Done");
    }
}

/* Notify action routine.
 * Calculates where the user clicked in the default user coordinate system.
 * Call the callbacks with the point of click and size of zoom window
 * requested.
 */
static void
Notify(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    GhostviewReturnStruct ret_val;

    /* notify takes zero to four parameters.  The first two give the width and
     * height of the zoom requested in the default user coordinate system.
     * If they are omitted, a default value of 72 is provided.  If the second
     * parameter is omitted, the zoom area is assumed to be a square.
     * The next two parameters give the desired resolution of the zoom window.
     * If they are omitted, a default value of 300 is provided. If the four
     * parameter is omitted, the y resolution is assumed to be equal to the
     * x resolution.
     */
    switch (*num_params) {
    case 0:
	ret_val.width = ret_val.height = 72;
	ret_val.xdpi = ret_val.ydpi = 300;
	break;
    case 1:
	ret_val.width = ret_val.height = atoi(params[0]);
	ret_val.xdpi = ret_val.ydpi = 300;
	break;
    case 2:
	ret_val.width = atoi(params[0]);
	ret_val.height = atoi(params[1]);
	ret_val.xdpi = ret_val.ydpi = 300;
	break;
    case 3:
	ret_val.width = atoi(params[0]);
	ret_val.height = atoi(params[1]);
	ret_val.xdpi = ret_val.ydpi = atoi(params[2]);
	break;
    default:
	ret_val.width = atoi(params[0]);
	ret_val.height = atoi(params[1]);
	ret_val.xdpi = atoi(params[2]);
	ret_val.ydpi = atoi(params[3]);
	break;
    }

    switch (gvw->ghostview.orientation) {
    case XtPageOrientationPortrait:
	ret_val.psx = gvw->ghostview.llx + 
		      event->xbutton.x * 72.0 / gvw->ghostview.xdpi;
	ret_val.psy = gvw->ghostview.ury - 
		      event->xbutton.y * 72.0 / gvw->ghostview.ydpi;
	break;
    case XtPageOrientationLandscape:
	ret_val.psx = gvw->ghostview.llx + 
		      event->xbutton.y * 72.0 / gvw->ghostview.ydpi;
	ret_val.psy = gvw->ghostview.lly + 
		      event->xbutton.x * 72.0 / gvw->ghostview.xdpi;
	break;
    case XtPageOrientationUpsideDown:
	ret_val.psx = gvw->ghostview.urx - 
		      event->xbutton.x * 72.0 / gvw->ghostview.xdpi;
	ret_val.psy = gvw->ghostview.lly + 
		      event->xbutton.y * 72.0 / gvw->ghostview.ydpi;
	break;
    case XtPageOrientationSeascape:
	ret_val.psx = gvw->ghostview.urx - 
		      event->xbutton.y * 72.0 / gvw->ghostview.ydpi;
	ret_val.psy = gvw->ghostview.ury - 
		      event->xbutton.x * 72.0 / gvw->ghostview.xdpi;
	break;
    }
    XtCallCallbackList(w, gvw->ghostview.callback, (XtPointer) &ret_val);
}

#ifndef SEEK_SET
#define SEEK_SET 0
#endif

static Boolean broken_pipe = False;

static SIGVAL
CatchPipe(i)
    int i;
{
    broken_pipe = True;
#ifdef SIGNALRETURNSINT
    return 0;
#endif
}

#ifndef VMS

/* Input - Feed data to ghostscript's stdin.
 * Write bytes to ghostscript using non-blocking I/O.
 * Also, pipe signals are caught during writing.  The return
 * values are checked and the appropriate action is taken.  I do
 * this at this low level, because it may not be appropriate for
 * SIGPIPE to be caught for the overall application.
 */

static void
Input(client_data, source, id)
    XtPointer client_data;
    int *source;
    XtInputId *id;
{
    Widget w = (Widget) client_data;
    GhostviewWidget gvw = (GhostviewWidget) w;
    int bytes_written;
    SIGVAL (*oldsig)();

    oldsig = signal(SIGPIPE, CatchPipe);

#ifdef NON_BLOCKING_IO
    do {
#endif

	if (gvw->ghostview.buffer_bytes_left == 0) {

	    /* Get a new section if required */
	    if (gvw->ghostview.ps_input && gvw->ghostview.bytes_left == 0) {
		struct record_list *ps_old = gvw->ghostview.ps_input;
		gvw->ghostview.ps_input = ps_old->next;
		if (ps_old->close) fclose(ps_old->fp);
		XtFree((char *)ps_old);
	    }

	    /* Have to seek at the beginning of each section */
	    if (gvw->ghostview.ps_input &&
		gvw->ghostview.ps_input->seek_needed) {
		if (gvw->ghostview.ps_input->len > 0)
		    fseek(gvw->ghostview.ps_input->fp,
			  gvw->ghostview.ps_input->begin, SEEK_SET);
		gvw->ghostview.ps_input->seek_needed = False;
		gvw->ghostview.bytes_left = gvw->ghostview.ps_input->len;
	    }

	    if (gvw->ghostview.bytes_left > GV_BUFSIZ) {
		gvw->ghostview.buffer_bytes_left =
			fread(gvw->ghostview.input_buffer,
			      sizeof (char), GV_BUFSIZ,
			      gvw->ghostview.ps_input->fp);
	    } else if (gvw->ghostview.bytes_left > 0) {
		gvw->ghostview.buffer_bytes_left =
			fread(gvw->ghostview.input_buffer,
			      sizeof (char), gvw->ghostview.bytes_left,
			      gvw->ghostview.ps_input->fp);
	    } else {
		gvw->ghostview.buffer_bytes_left = 0;
	    }
	    if (gvw->ghostview.bytes_left > 0 &&
		gvw->ghostview.buffer_bytes_left == 0) {
		InterpreterFailed(w);	/* Error occurred */
	    }
	    gvw->ghostview.input_buffer_ptr = gvw->ghostview.input_buffer;
	    gvw->ghostview.bytes_left -= gvw->ghostview.buffer_bytes_left;
	}

	if (gvw->ghostview.buffer_bytes_left > 0) {
	    bytes_written = write(gvw->ghostview.interpreter_input,
				  gvw->ghostview.input_buffer_ptr,
				  gvw->ghostview.buffer_bytes_left);

	    if (broken_pipe) {
		broken_pipe = False;
		InterpreterFailed(w);		/* Something bad happened */
	    } else if (bytes_written == -1) {
		if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) {
		    InterpreterFailed(w);	/* Something bad happened */
		}
	    } else {
		gvw->ghostview.buffer_bytes_left -= bytes_written;
		gvw->ghostview.input_buffer_ptr += bytes_written;
	    }
	}
#ifdef NON_BLOCKING_IO
    } while(gvw->ghostview.ps_input &&
	    gvw->ghostview.buffer_bytes_left == 0);
#endif
    signal(SIGPIPE, oldsig);
    if (gvw->ghostview.ps_input == NULL &&
	gvw->ghostview.buffer_bytes_left == 0) {
	if (gvw->ghostview.interpreter_input_id != None) {
	    XtRemoveInput(gvw->ghostview.interpreter_input_id);
	    gvw->ghostview.interpreter_input_id = None;
	}
    }
}

/* Output - receive I/O from ghostscript's stdout and stderr.
 * Pass this to the application via the output_callback. */
static void
Output(client_data, source, id)
    XtPointer client_data;
    int *source;
    XtInputId *id;
{
    Widget w = (Widget) client_data;
    GhostviewWidget gvw = (GhostviewWidget) w;
    char buf[GV_BUFSIZ+1];
    int bytes = 0;

    if (*source == gvw->ghostview.interpreter_output) {
	bytes = read(gvw->ghostview.interpreter_output, buf, GV_BUFSIZ);
	if (bytes == 0) { /* EOF occurred */
	    close(gvw->ghostview.interpreter_output);
	    gvw->ghostview.interpreter_output = -1;
	    XtRemoveInput(gvw->ghostview.interpreter_output_id);
	    return;
	} else if (bytes == -1) {
	    InterpreterFailed(w);		/* Something bad happened */
	    return;
	}
    } else if (*source == gvw->ghostview.interpreter_error) {
	bytes = read(gvw->ghostview.interpreter_error, buf, GV_BUFSIZ);
	if (bytes == 0) { /* EOF occurred */
	    close(gvw->ghostview.interpreter_error);
	    gvw->ghostview.interpreter_error = -1;
	    XtRemoveInput(gvw->ghostview.interpreter_error_id);
	    return;
	} else if (bytes == -1) {
	    InterpreterFailed(w);		/* Something bad happened */
	    return;
	}
    }
    if (bytes > 0) {
	buf[bytes] = '\0';
        pdf_process(buf);
	if (*buf)  {
	  XtCallCallbackList(w, gvw->ghostview.output_callback, (XtPointer) buf);
	}
        else {
	 bytes = 0;
       }
    }
}

#endif /* VMS */

/* Register the type converter required for the PageOrientation. */
/* Register the type converter required for the Palette. */
/* This routine is called exactly once. */
static void
ClassInitialize()
{
    XtSetTypeConverter(XtRString, XtRPageOrientation,
		       XmuCvtStringToPageOrientation, NULL, 0,
		       XtCacheAll, NULL);
    XtSetTypeConverter(XtRString, XtRPalette,
		       XmuCvtStringToPalette, NULL, 0,
		       XtCacheAll, NULL);
}

/* Get atoms needed to communicate with ghostscript. */
/* This routine is called once per display. */
static void
ClassPartInitialize(class)
    WidgetClass class;
{
    GhostviewWidgetClass gvc = (GhostviewWidgetClass)class;
    gvc->ghostview_class.ghostview = XmuMakeAtom("GHOSTVIEW");
    gvc->ghostview_class.gv_colors = XmuMakeAtom("GHOSTVIEW_COLORS");
    gvc->ghostview_class.next = XmuMakeAtom("NEXT");
    gvc->ghostview_class.page = XmuMakeAtom("PAGE");
    gvc->ghostview_class.done = XmuMakeAtom("DONE");
}

/* Initialize private state. */

static void
Initialize(request, new, args, num_args)
    Widget request, new;
    ArgList args;		/* unused */
    Cardinal *num_args;	/* unused */
{
    XGCValues	values;
    XtGCMask	mask;
    GhostviewWidget ngvw = (GhostviewWidget) new;
    GhostviewWidget rgvw = (GhostviewWidget) request;

    values.foreground = new->core.background_pixel;
    mask = GCForeground;
    ngvw->ghostview.gc = XtGetGC(new, mask, &values);
    ngvw->ghostview.mwin = None;
    ngvw->ghostview.disable_start = False;
    ngvw->ghostview.interpreter_pid = -1;
    ngvw->ghostview.input_buffer = NULL;
    ngvw->ghostview.bytes_left = 0;
#ifndef VMS
    ngvw->ghostview.input_buffer_ptr = NULL;
    ngvw->ghostview.buffer_bytes_left = 0;
#endif
    ngvw->ghostview.ps_input = NULL;
    ngvw->ghostview.interpreter_input = -1;
    ngvw->ghostview.interpreter_output = -1;
#ifndef VMS
    ngvw->ghostview.interpreter_error = -1;
    ngvw->ghostview.interpreter_input_id = None;
    ngvw->ghostview.interpreter_output_id = None;
    ngvw->ghostview.interpreter_error_id = None;
#else /* VMS */
    memset(ngvw->ghostview.interpreter_input_iosb, 0, 8);
    memset(ngvw->ghostview.interpreter_output_iosb, 0, 8);
    ngvw->ghostview.output_buffer = NULL;
#endif /* VMS */
    ngvw->ghostview.gs_width = 0;
    ngvw->ghostview.gs_height = 0;
    ngvw->ghostview.changed = False;
    ngvw->ghostview.busy = False;

    /* Compute window size */
    Layout(new, (rgvw->core.width == 0), (rgvw->core.height == 0));
}

/* Create Window and start interpreter if needed */
static void
Realize(w, valueMask, attributes)
    Widget w;
    Mask *valueMask;
    XSetWindowAttributes *attributes;
{
    GhostviewWidget gvw = (GhostviewWidget) w;

    if (gvw->ghostview.cursor != None) {
	attributes->cursor = gvw->ghostview.cursor;
	*valueMask |= CWCursor;
    }

    XtCreateWindow(w, (unsigned int) InputOutput, (Visual *) CopyFromParent,
		   *valueMask, attributes);

    Setup(w);
}

/* Destroy routine: kill the interpreter and release the GC */
static void
Destroy(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;

    StopInterpreter(w);
    XtReleaseGC(w, gvw->ghostview.gc);
    if (gvw->ghostview.input_buffer) XtFree(gvw->ghostview.input_buffer);
#ifdef VMS
    if (gvw->ghostview.output_buffer) XtFree(gvw->ghostview.output_buffer);
#endif /* VMS */
    if (gvw->core.background_pixmap != XtUnspecifiedPixmap)
	XFreePixmap(XtDisplay(w), gvw->core.background_pixmap);
}

/* Process resize request.  Requested size cannot be changed.
 * NOTE: This routine may be called before the widget is realized.
 * (It was a surprise to me.)
 * If the widget is realized, start a new interpreter by calling Setup().
 * If Setup() actually started a new interpreter and it is taking input
 * from stdin, send a refresh message to the application.  This is the
 * only way that the application can be notified that it needs to resend
 * the input because someone forced a new window size on the widget.
 */
static void
Resize(w)
    Widget w;
{
    Layout(w, False, False);
    if (!XtIsRealized(w)) return;
    if (Setup(w)) {
	GhostviewWidget gvw = (GhostviewWidget) w;
	if (gvw->ghostview.filename == NULL) {
	    XtCallCallbackList(w, gvw->ghostview.message_callback, "Refresh");
	}
    }
}

/* SetValues routine.  Set new private state, based on changed values
 * in the widget.  Always returns False, because redisplay is never needed.
 */
static Boolean
SetValues(current, request, new)
    Widget current, request, new;
{
    GhostviewWidget cgvw = (GhostviewWidget) current;
    GhostviewWidget rgvw = (GhostviewWidget) request;
    GhostviewWidget ngvw = (GhostviewWidget) new;
    String cfilename;
    String rfilename;
    String carguments;
    String rarguments;

    cfilename = cgvw->ghostview.filename;
    if (cfilename == NULL) cfilename = "(null)";
    rfilename = rgvw->ghostview.filename;
    if (rfilename == NULL) rfilename = "(null)";
    carguments = cgvw->ghostview.arguments;
    if (carguments == NULL) carguments = "(null)";
    rarguments = rgvw->ghostview.arguments;
    if (rarguments == NULL) rarguments = "(null)";

    if (XtIsRealized(new) && !ngvw->ghostview.busy &&
	(cgvw->ghostview.cursor != ngvw->ghostview.cursor)) {
	XDefineCursor(XtDisplay(new), XtWindow(new), ngvw->ghostview.cursor);
    }
    if (XtIsRealized(new) && ngvw->ghostview.busy &&
	(cgvw->ghostview.busy_cursor != ngvw->ghostview.busy_cursor)) {
	XDefineCursor(XtDisplay(new), XtWindow(new),
		      ngvw->ghostview.busy_cursor);
    }
    if (cgvw->core.background_pixel != rgvw->core.background_pixel) {
	XGCValues	values;
	XtGCMask	mask;

	XtReleaseGC(current, cgvw->ghostview.gc);
	values.foreground = new->core.background_pixel;
	mask = GCForeground;
	ngvw->ghostview.gc = XtGetGC(new, mask, &values);
    }
    if ((cgvw->core.width != rgvw->core.width) ||
	(cgvw->core.height != rgvw->core.height) ||
	(cgvw->core.background_pixel != rgvw->core.background_pixel) ||
	(cgvw->ghostview.foreground != rgvw->ghostview.foreground) ||
	(cgvw->ghostview.palette != rgvw->ghostview.palette) ||
	strcmp(cgvw->ghostview.interpreter, rgvw->ghostview.interpreter) ||
	strcmp(carguments, rarguments) ||
	(cgvw->ghostview.quiet != rgvw->ghostview.quiet) ||
	(cgvw->ghostview.safer != rgvw->ghostview.safer) ||
	strcmp(cfilename, rfilename) ||
	(cgvw->ghostview.orientation != rgvw->ghostview.orientation) ||
	(cgvw->ghostview.use_bpixmap != rgvw->ghostview.use_bpixmap) ||
	(cgvw->ghostview.xdpi != rgvw->ghostview.xdpi) ||
	(cgvw->ghostview.ydpi != rgvw->ghostview.ydpi) ||
	(cgvw->ghostview.bottom_margin != rgvw->ghostview.bottom_margin) ||
	(cgvw->ghostview.left_margin != rgvw->ghostview.left_margin) ||
	(cgvw->ghostview.right_margin != rgvw->ghostview.right_margin) ||
	(cgvw->ghostview.top_margin != rgvw->ghostview.top_margin) ||
	(cgvw->ghostview.llx != rgvw->ghostview.llx) ||
	(cgvw->ghostview.lly != rgvw->ghostview.lly) ||
	(cgvw->ghostview.urx != rgvw->ghostview.urx) ||
	(cgvw->ghostview.ury != rgvw->ghostview.ury)) {

	ngvw->ghostview.changed = True;
	Layout(new, True, True);
    }

    if (ngvw->ghostview.changed && XtIsRealized(current)) Setup(new);
    return(False);
}

/*	Function Name: QueryGeometry
 *	Description: This tells the parent what size we would like to be
 *                   given certain constraints.
 *	Arguments: w - the widget.
 *                 intended - what the parent intends to do with us.
 *                 requested - what we want to happen.
 */

static XtGeometryResult 
QueryGeometry(w, intended, requested)
Widget w;
XtWidgetGeometry *intended, *requested;
{
    Dimension new_width, new_height;
    Boolean change, width_req, height_req;
    
    width_req = intended->request_mode & CWWidth;
    height_req = intended->request_mode & CWHeight;

    if (width_req)
      new_width = intended->width;
    else
      new_width = w->core.width;

    if (height_req)
      new_height = intended->height;
    else
      new_height = w->core.height;

    requested->request_mode = 0;
    
/*
 * We only care about our height and width.
 */

    if (!width_req && !height_req)
      return(XtGeometryYes);
    
    change = ComputeSize(w, !width_req, !height_req, &new_width, &new_height);

    requested->request_mode |= CWWidth;
    requested->width = new_width;
    requested->request_mode |= CWHeight;
    requested->height = new_height;

    if (change)
        return(XtGeometryAlmost);
    return(XtGeometryYes);
}

/* Layout the widget. */

static void
Layout(w, xfree, yfree)
    Widget w;
    Boolean xfree, yfree;
{
    Dimension width = w->core.width;
    Dimension height = w->core.height;
    Boolean different_size = ComputeSize(w, xfree, yfree, &width, &height);
    if (different_size) ChangeSize(w, width, height);
}

/* Compute new size of window, sets xdpi and ydpi if necessary.
 * returns True if new window size is different */
static Boolean
ComputeSize(w, xfree, yfree, width, height)
    Widget w;
    Boolean xfree, yfree;	/* Am I allowed to change width or height */
    Dimension *width, *height;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    Dimension new_width = *width;
    Dimension new_height = *height;
    float newxdpi, newydpi;
    Boolean change;

    if (xfree && yfree) {
	/* width and height can be changed, calculate window size according */
	/* to xpdi and ydpi */
	switch (gvw->ghostview.orientation) {
	case XtPageOrientationPortrait:
	case XtPageOrientationUpsideDown:
	    new_width = (gvw->ghostview.urx - gvw->ghostview.llx) / 72.0 *
			 gvw->ghostview.xdpi + 0.5;
	    new_height = (gvw->ghostview.ury - gvw->ghostview.lly) / 72.0 *
			  gvw->ghostview.ydpi + 0.5;
	    break;
	case XtPageOrientationLandscape:
	case XtPageOrientationSeascape:
	    new_width = (gvw->ghostview.ury - gvw->ghostview.lly) / 72.0 *
			 gvw->ghostview.xdpi + 0.5;
	    new_height = (gvw->ghostview.urx - gvw->ghostview.llx) / 72.0 *
			  gvw->ghostview.ydpi + 0.5;
	    break;
	}
    } else if (xfree) {
	/* height is fixed.  Preserve aspect ratio by recomputing */
	/* ydpi and xdpi */
	switch (gvw->ghostview.orientation) {
	case XtPageOrientationPortrait:
	case XtPageOrientationUpsideDown:
	    newydpi = gvw->core.height * 72.0 /
		      (gvw->ghostview.ury - gvw->ghostview.lly);
	    newxdpi = newydpi * gvw->ghostview.xdpi / gvw->ghostview.ydpi;
	    gvw->ghostview.xdpi = newxdpi;
	    gvw->ghostview.ydpi = newydpi;
	    new_width = (gvw->ghostview.urx - gvw->ghostview.llx) / 72.0 *
			 gvw->ghostview.xdpi + 0.5;
	    break;
	case XtPageOrientationLandscape:
	case XtPageOrientationSeascape:
	    newydpi = gvw->core.height * 72.0 /
		      (gvw->ghostview.urx - gvw->ghostview.llx);
	    newxdpi = newydpi * gvw->ghostview.xdpi / gvw->ghostview.ydpi;
	    gvw->ghostview.xdpi = newxdpi;
	    gvw->ghostview.ydpi = newydpi;
	    new_width = (gvw->ghostview.ury - gvw->ghostview.lly) / 72.0 *
			 gvw->ghostview.xdpi + 0.5;
	    break;
	}
    } else if (yfree) {
	/* width is fixed.  Preserve aspect ratio by recomputing */
	/* xdpi and ydpi */
	switch (gvw->ghostview.orientation) {
	case XtPageOrientationPortrait:
	case XtPageOrientationUpsideDown:
	    newxdpi = gvw->core.width * 72.0 /
		      (gvw->ghostview.urx - gvw->ghostview.llx);
	    newydpi = newxdpi * gvw->ghostview.ydpi / gvw->ghostview.xdpi;
	    gvw->ghostview.xdpi = newxdpi;
	    gvw->ghostview.ydpi = newydpi;
	    new_height = (gvw->ghostview.ury - gvw->ghostview.lly) / 72.0 *
			  gvw->ghostview.ydpi + 0.5;
	    break;
	case XtPageOrientationLandscape:
	case XtPageOrientationSeascape:
	    newxdpi = gvw->core.width * 72.0 /
		      (gvw->ghostview.ury - gvw->ghostview.lly);
	    newydpi = newxdpi * gvw->ghostview.ydpi / gvw->ghostview.xdpi;
	    gvw->ghostview.xdpi = newxdpi;
	    gvw->ghostview.ydpi = newydpi;
	    new_height = (gvw->ghostview.urx - gvw->ghostview.llx) / 72.0 *
			  gvw->ghostview.ydpi + 0.5;
	    break;
	}
    } else {
	/* height and width are fixed.  Just have to live with it. */
	switch (gvw->ghostview.orientation) {
	case XtPageOrientationPortrait:
	case XtPageOrientationUpsideDown:
	    gvw->ghostview.xdpi = gvw->core.width * 72.0 /
				  (gvw->ghostview.urx - gvw->ghostview.llx);
	    gvw->ghostview.ydpi = gvw->core.height * 72.0 /
				  (gvw->ghostview.ury - gvw->ghostview.lly);
	    break;
	case XtPageOrientationLandscape:
	case XtPageOrientationSeascape:
	    gvw->ghostview.xdpi = gvw->core.width * 72.0 /
				  (gvw->ghostview.ury - gvw->ghostview.lly);
	    gvw->ghostview.ydpi = gvw->core.height * 72.0 /
				  (gvw->ghostview.urx - gvw->ghostview.llx);
	    break;
	}
    }

    change = (new_width != *width) || (new_height != *height);
    *width = new_width;
    *height = new_height;
    return (change);
}

/*	Function Name: ChangeSize.
 *	Description: Request a size change.
 *	Arguments: w - the widget to try change the size of.
 */

static void
ChangeSize(w, width, height)
Widget w;
Dimension width, height;
{
    XtWidgetGeometry request, reply;
    Boolean changed = False;

    request.request_mode = CWWidth | CWHeight;
    request.width = width;
    request.height = height;
    
    switch ( XtMakeGeometryRequest(w, &request, &reply) ) {
    case XtGeometryYes:
	changed = True;
        break;
    case XtGeometryNo:
        break;
    case XtGeometryAlmost:
	ComputeSize(w, (request.height != reply.height),
		       (request.width != reply.width),
		       &(reply.width), &(reply.height));
	request = reply;
	switch (XtMakeGeometryRequest(w, &request, &reply) ) {
	case XtGeometryYes:
	    changed = True;
	    break;
	case XtGeometryNo:
	    break;
	case XtGeometryAlmost:
	    request = reply;
	    ComputeSize(w, FALSE, FALSE, &(request.width), &(request.height));
	    request.request_mode = CWWidth | CWHeight;
	    XtMakeGeometryRequest(w, &request, &reply);
	    changed = True;
	    break;
	}
	break;
    }

    /* If success, setup the widet for the new size. */
    if (changed && XtIsRealized(w)) Setup(w);
}

/* Catch the alloc error when there is not enough resources for the
 * backing pixmap.  Automatically shut off backing pixmap and let the
 * user know when this happens.
 */
static Boolean alloc_error;
static XErrorHandler oldhandler;

static int
catch_alloc (dpy, err)
Display *dpy;
XErrorEvent *err;
{
    if (err->error_code == BadAlloc) {
	alloc_error = True;
    }
    if (alloc_error) return 0;
    return oldhandler(dpy, err);
}

/* Setup - sets up the backing pixmap, and GHOSTVIEW property and
 * starts interpreter if needed.
 * NOTE: the widget must be realized before calling Setup().
 * Returns True if a new interpreter was started, False otherwise.
 */
 
static Boolean
Setup(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    GhostviewWidgetClass gvc = (GhostviewWidgetClass) XtClass(w);
    char buf[GV_BUFSIZ];
    Pixmap bpixmap;
    XSetWindowAttributes xswa;

    if (!gvw->ghostview.changed &&
	(gvw->core.width == gvw->ghostview.gs_width) &&
	(gvw->core.height == gvw->ghostview.gs_height)) return False;

    StopInterpreter(w);

    if ((gvw->core.width != gvw->ghostview.gs_width) ||
	(gvw->core.height != gvw->ghostview.gs_height) ||
	(!gvw->ghostview.use_bpixmap)) {
	if (gvw->core.background_pixmap != XtUnspecifiedPixmap) {
	    XFreePixmap(XtDisplay(w), gvw->core.background_pixmap);
	    gvw->core.background_pixmap = XtUnspecifiedPixmap;
	    XSetWindowBackgroundPixmap(XtDisplay(w), XtWindow(w), None);
	}
    }

    if (gvw->ghostview.use_bpixmap) {
	if (gvw->core.background_pixmap == XtUnspecifiedPixmap) {
	    /* Get a Backing Pixmap, but be ready for the BadAlloc. */
	    XSync(XtDisplay(w), False);  /* Get to known state */
	    oldhandler = XSetErrorHandler(catch_alloc);
	    alloc_error = False;
	    bpixmap = XCreatePixmap(XtDisplay(w), XtWindow(w),
				    gvw->core.width, gvw->core.height,
				    gvw->core.depth);
	    XSync(XtDisplay(w), False);  /* Force the error */
	    if (alloc_error) {
		XtCallCallbackList(w, gvw->ghostview.message_callback,
				   "BadAlloc");
		if (bpixmap != None) {
		    XFreePixmap(XtDisplay(w), bpixmap);
		    XSync(XtDisplay(w), False);  /* Force the error */
		    bpixmap = None;
		}
	    }
	    oldhandler = XSetErrorHandler(oldhandler);
	    if (bpixmap != None) {
		gvw->core.background_pixmap = bpixmap;
		XSetWindowBackgroundPixmap(XtDisplay(w), XtWindow(w),
					   gvw->core.background_pixmap);
	    }
	} else {
	    bpixmap = gvw->core.background_pixmap;
	}
    } else {
	if (gvw->core.background_pixmap != XtUnspecifiedPixmap) {
	    XFreePixmap(XtDisplay(w), gvw->core.background_pixmap);
	    gvw->core.background_pixmap = XtUnspecifiedPixmap;
	    XSetWindowBackgroundPixmap(XtDisplay(w), XtWindow(w), None);
	}
	bpixmap = None;
    }

    if (bpixmap != None) {
	xswa.backing_store = NotUseful;
	XChangeWindowAttributes(XtDisplay(w), XtWindow(w),
				CWBackingStore, &xswa);
    } else {
	xswa.backing_store = Always;
	XChangeWindowAttributes(XtDisplay(w), XtWindow(w),
				CWBackingStore, &xswa);
    }

    gvw->ghostview.gs_width = gvw->core.width;
    gvw->ghostview.gs_height = gvw->core.height;

    sprintf(buf, "%d %d %d %d %d %d %g %g %d %d %d %d",
	    bpixmap, gvw->ghostview.orientation,
	    gvw->ghostview.llx, gvw->ghostview.lly,
	    gvw->ghostview.urx, gvw->ghostview.ury,
	    gvw->ghostview.xdpi, gvw->ghostview.ydpi,
	    gvw->ghostview.left_margin, gvw->ghostview.bottom_margin,
	    gvw->ghostview.right_margin, gvw->ghostview.top_margin);
    XChangeProperty(XtDisplay(w), XtWindow(w),
		   XmuInternAtom(XtDisplay(w), gvc->ghostview_class.ghostview),
		   XA_STRING, 8, PropModeReplace,
		   (unsigned char *)buf, strlen(buf));

    sprintf(buf, "%s %d %d",
	    gvw->ghostview.palette == XtPaletteMonochrome ? "Monochrome" :
	    gvw->ghostview.palette == XtPaletteGrayscale  ? "Grayscale" :
	    gvw->ghostview.palette == XtPaletteColor      ? "Color" : "?",
	    gvw->ghostview.foreground, gvw->core.background_pixel);
    XChangeProperty(XtDisplay(w), XtWindow(w),
		   XmuInternAtom(XtDisplay(w), gvc->ghostview_class.gv_colors),
		   XA_STRING, 8, PropModeReplace,
		   (unsigned char *)buf, strlen(buf));

    XSync(XtDisplay(w), False);  /* Be sure to update properties */
    StartInterpreter(w);
    return True;
}

#ifndef VMS

/* This routine starts the interpreter.  It sets the DISPLAY and 
 * GHOSTVIEW environment variables.  The GHOSTVIEW environment variable
 * contains the Window that ghostscript should write on.
 *
 * This routine also opens pipes for stdout and stderr and initializes
 * application input events for them.  If input to ghostscript is not
 * from a file, a pipe for stdin is created.  This pipe is setup for
 * non-blocking I/O so that the user interface never "hangs" because of
 * a write to ghostscript.
 */
static void
StartInterpreter(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    int	std_in[2];
    int	std_out[2];
    int	std_err[2];
    char buf[GV_BUFSIZ];
#define NUM_ARGS 100
    char *argv[NUM_ARGS];
    char *arguments = NULL;
    char *cptr;
    int argc = 0;
    int ret;

    StopInterpreter(w);

    /* Clear the window before starting a new interpreter. */
    if (gvw->core.background_pixmap != XtUnspecifiedPixmap) {
	XFillRectangle(XtDisplay(w), gvw->core.background_pixmap,
		       gvw->ghostview.gc,
		       0, 0, gvw->core.width, gvw->core.height);
    }
    XClearArea(XtDisplay(w), XtWindow(w),
	       0, 0, gvw->core.width, gvw->core.height, False);

    if (gvw->ghostview.disable_start) return;

    argv[argc++] = gvw->ghostview.interpreter;
    argv[argc++] = "-sDEVICE=x11";
    argv[argc++] = "-dNOPAUSE";
    if (gvw->ghostview.quiet) argv[argc++] = "-dQUIET";
    if (gvw->ghostview.safer) argv[argc++] = "-dSAFER";
    argv[argc++] = "gvpdf.pro";
    if (gvw->ghostview.arguments) {
	cptr = arguments = XtNewString(gvw->ghostview.arguments);
	while (isspace(*cptr)) cptr++;
	while (*cptr) {
	    argv[argc++] = cptr;
	    while (*cptr && !isspace(*cptr)) cptr++;
	    if (*cptr) *cptr++ = '\0';
	    if (argc + 2 >= NUM_ARGS) {
		fprintf(stderr, "Too many arguments to interpreter.\n");
		exit(1);
	    }
	    while (isspace(*cptr)) cptr++;
	}
    }
    argv[argc++] = "-";
    argv[argc++] = NULL;

    if (gvw->ghostview.filename == NULL) {
	ret = pipe(std_in);
	if (ret == -1) {
	    perror("Could not create pipe");
	    exit(1);
	}
    } else if (strcmp(gvw->ghostview.filename, "-")) {
	std_in[0] = open(gvw->ghostview.filename, O_RDONLY, 0);
    }
    ret = pipe(std_out);
    if (ret == -1) {
	perror("Could not create pipe");
	exit(1);
    }
    ret = pipe(std_err);
    if (ret == -1) {
	perror("Could not create pipe");
	exit(1);
    }

    gvw->ghostview.changed = False;
    gvw->ghostview.busy = True;
    XDefineCursor(XtDisplay(gvw), XtWindow(gvw), gvw->ghostview.busy_cursor);
#if defined(SYSV) || defined(USG)
#define vfork fork
#endif
    gvw->ghostview.interpreter_pid = vfork();

    if (gvw->ghostview.interpreter_pid == 0) { /* child */
	close(std_out[0]);
	close(std_err[0]);
	dup2(std_out[1], 1);
	close(std_out[1]);
	dup2(std_err[1], 2);
	close(std_err[1]);
	sprintf(buf, "%d", XtWindow(w));
	setenv("GHOSTVIEW", buf, True);
	setenv("DISPLAY", XDisplayString(XtDisplay(w)), True);
	if (gvw->ghostview.filename == NULL) {
	    close(std_in[1]);
	    dup2(std_in[0], 0);
	    close(std_in[0]);
	} else if (strcmp(gvw->ghostview.filename, "-")) {
	    dup2(std_in[0], 0);
	    close(std_in[0]);
	}
	execvp(argv[0], argv);
	sprintf(buf, "Exec of %s failed", argv[0]);
	perror(buf);
	_exit(1);
    } else {
	if (gvw->ghostview.filename == NULL) {
#ifdef NON_BLOCKING_IO
	    int result;
#endif
	    close(std_in[0]);

#ifdef NON_BLOCKING_IO
	    result = fcntl(std_in[1], F_GETFL, 0);
	    result = result | O_NONBLOCK;
	    result = fcntl(std_in[1], F_SETFL, result);
#endif
	    gvw->ghostview.interpreter_input = std_in[1];
	    gvw->ghostview.interpreter_input_id = None;
	} else if (strcmp(gvw->ghostview.filename, "-")) {
	    close(std_in[0]);
	}
	close(std_out[1]);
	gvw->ghostview.interpreter_output = std_out[0];
	gvw->ghostview.interpreter_output_id = 
	    XtAppAddInput(XtWidgetToApplicationContext(w), std_out[0],
			  (XtPointer)XtInputReadMask, Output, (XtPointer)w);
	close(std_err[1]);
	gvw->ghostview.interpreter_error = std_err[0];
	gvw->ghostview.interpreter_error_id = 
	    XtAppAddInput(XtWidgetToApplicationContext(w), std_err[0],
			  (XtPointer)XtInputReadMask, Output, (XtPointer)w);
    }
    if (arguments) XtFree(arguments);
}

/* Stop the interperter, if present, and remove any Input sources. */
/* Also reset the busy state. */
static void
StopInterpreter(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    if (gvw->ghostview.interpreter_pid >= 0) {
	kill(gvw->ghostview.interpreter_pid, SIGTERM);
	wait(0);
	gvw->ghostview.interpreter_pid = -1;
    }
    if (gvw->ghostview.interpreter_input >= 0) {
	close(gvw->ghostview.interpreter_input);
	gvw->ghostview.interpreter_input = -1;
	if (gvw->ghostview.interpreter_input_id != None) {
	    XtRemoveInput(gvw->ghostview.interpreter_input_id);
	    gvw->ghostview.interpreter_input_id = None;
	}
	while (gvw->ghostview.ps_input) {
	    struct record_list *ps_old = gvw->ghostview.ps_input;
	    gvw->ghostview.ps_input = ps_old->next;
	    if (ps_old->close) fclose(ps_old->fp);
	    XtFree((char *)ps_old);
	}
    }
    if (gvw->ghostview.interpreter_output >= 0) {
	close(gvw->ghostview.interpreter_output);
	gvw->ghostview.interpreter_output = -1;
	XtRemoveInput(gvw->ghostview.interpreter_output_id);
    }
    if (gvw->ghostview.interpreter_error >= 0) {
	close(gvw->ghostview.interpreter_error);
	gvw->ghostview.interpreter_error = -1;
	XtRemoveInput(gvw->ghostview.interpreter_error_id);
    }
    gvw->ghostview.busy = False;
    XDefineCursor(XtDisplay(gvw), XtWindow(gvw), gvw->ghostview.cursor);
}

#endif /* VMS */

/* The interpeter failed, Stop what's left and notify application */
static void
InterpreterFailed(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    StopInterpreter(w);
    XtCallCallbackList(w, gvw->ghostview.message_callback, "Failed");
}

/*
 *	Public Routines
 */

/* GhostviewDisableInterpreter:
 * Stop any interpreter and disable new ones from starting.
 */
void
GhostviewDisableInterpreter(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    gvw->ghostview.disable_start = True;
    if (XtIsRealized(w)) StopInterpreter(w);
}

/* GhostviewDisableInterpreter:
 * Allow an interpreter to start and start one if the widget is
 * currently realized.
 */
void
GhostviewEnableInterpreter(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    gvw->ghostview.disable_start = False;
    if (XtIsRealized(w)) StartInterpreter(w);
}

/* GhostviewIsInterpreterReady:
 * Returns True if the interpreter is ready for new input.
 */
Boolean
GhostviewIsInterpreterReady(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    return gvw->ghostview.interpreter_pid != -1 &&
	   !gvw->ghostview.busy &&
	   gvw->ghostview.ps_input == NULL;
}

/* GhostviewIsInterpreterRunning:
 * Returns True if the interpreter is running.
 */
Boolean
GhostviewIsInterpreterRunning(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    return gvw->ghostview.interpreter_pid != -1;
}

/* GhostviewGetBackingPixmap:
 * Returns the current backing pixmap.
 */
Pixmap
GhostviewGetBackingPixmap(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    if (gvw->core.background_pixmap != XtUnspecifiedPixmap)
	return(gvw->core.background_pixmap);
    else
	return(None);
}

#ifndef VMS

/* GhostviewSendPS:
 *   Queue a portion of a PostScript file for output to ghostscript.
 *   fp: FILE * of the file in question.  NOTE: if you have several
 *   Ghostview widgets reading from the same file.  You must open
 *   a unique FILE * for each widget.
 *   SendPS does not actually send the PostScript, it merely queues it
 *   for output.
 *   begin: position in file (returned from ftell()) to start.
 *   len:   number of bytes to write.
 *
 *   If an interpreter is not running, nothing is queued and
 *   False is returned.
 */
Boolean
GhostviewSendPS(w, fp, begin, len, close)
    Widget w;
    FILE *fp;
    long begin;
    unsigned int len;
    Bool close;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    struct record_list *ps_new;

    if (gvw->ghostview.interpreter_input < 0) return False;
    ps_new = (struct record_list *) XtMalloc(sizeof (struct record_list));
    ps_new->fp = fp;
    ps_new->begin = begin;
    ps_new->len = len;
    ps_new->seek_needed = True;
    ps_new->close = close;
    ps_new->next = NULL;

    if (gvw->ghostview.input_buffer == NULL) {
	gvw->ghostview.input_buffer = XtMalloc(GV_BUFSIZ);
    }

    if (gvw->ghostview.ps_input == NULL) {
	gvw->ghostview.input_buffer_ptr = gvw->ghostview.input_buffer;
	gvw->ghostview.bytes_left = len;
	gvw->ghostview.buffer_bytes_left = 0;
	gvw->ghostview.ps_input = ps_new;
	gvw->ghostview.interpreter_input_id =
		XtAppAddInput(XtWidgetToApplicationContext(w),
			      gvw->ghostview.interpreter_input,
			      (XtPointer)XtInputWriteMask, Input, (XtPointer)w);
    } else {
	struct record_list *p = gvw->ghostview.ps_input;
	while (p->next != NULL) {
	    p = p->next;
	}
	p->next = ps_new;
    }
    return True;
}

#endif /* VMS */

/* GhostviewNextPage:
 *   Tell ghostscript to start the next page.
 *   Returns False if ghostscript is not running, or not ready to start
 *   another page.
 *   If another page is started.  Sets the busy flag and cursor.
 */
Boolean
GhostviewNextPage(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    GhostviewWidgetClass gvc = (GhostviewWidgetClass) XtClass(w);
    XEvent event;

    if (gvw->ghostview.interpreter_pid < 0) return False;
    if (gvw->ghostview.mwin == None) return False;

    if (!gvw->ghostview.busy) {
	gvw->ghostview.busy = True;
	XDefineCursor(XtDisplay(gvw), XtWindow(gvw),
		      gvw->ghostview.busy_cursor);

	event.xclient.type = ClientMessage;
	event.xclient.display = XtDisplay(w);
	event.xclient.window = gvw->ghostview.mwin;
	event.xclient.message_type =
		XmuInternAtom(XtDisplay(w), gvc->ghostview_class.next);
	event.xclient.format = 32;
	XSendEvent(XtDisplay(w), gvw->ghostview.mwin, False, 0, &event);
	XFlush(XtDisplay(w));	/* And push it out */
	return True;
    } else {
	return False;
    }
}

#define	done(type, value) \
	{							\
	    if (toVal->addr != NULL) {				\
		if (toVal->size < sizeof(type)) {		\
		    toVal->size = sizeof(type);			\
		    return False;				\
		}						\
		*(type*)(toVal->addr) = (value);		\
	    }							\
	    else {						\
		static type static_val;				\
		static_val = (value);				\
		toVal->addr = (XPointer)&static_val;		\
	    }							\
	    toVal->size = sizeof(type);				\
	    return True;					\
	}

/* PageOrienation Conversion Routine.
 * Returns True if Conversion is successful.
 */
Boolean
XmuCvtStringToPageOrientation(dpy, args, num_args, fromVal, toVal, data)
    Display	*dpy;
    XrmValue	*args;		/* unused */
    Cardinal	*num_args;	/* unused */
    XrmValue	*fromVal;
    XrmValue	*toVal;
    XtPointer	*data;		/* unused */
{
    static XrmQuark		XrmQEportrait;
    static XrmQuark		XrmQElandscape;
    static XrmQuark		XrmQEupsideDown;
    static XrmQuark		XrmQEseascape;
    static int			haveQuarks;
    XrmQuark    q;
    char	*str = (XPointer) fromVal->addr;
    char        lowerName[1000];

    if (str == NULL) return False;

    if (!haveQuarks) {
	XrmQEportrait   = XrmStringToQuark(XtEportrait);
	XrmQElandscape  = XrmStringToQuark(XtElandscape);
	XrmQEupsideDown = XrmStringToQuark(XtEupsideDown);
	XrmQEseascape   = XrmStringToQuark(XtEseascape);
	haveQuarks = 1;
    }

    XmuCopyISOLatin1Lowered(lowerName, str);

    q = XrmStringToQuark(lowerName);

    if (q == XrmQEportrait)
	done(XtPageOrientation, XtPageOrientationPortrait);
    if (q == XrmQElandscape)
	done(XtPageOrientation, XtPageOrientationLandscape);
    if (q == XrmQEupsideDown)
	done(XtPageOrientation, XtPageOrientationUpsideDown);
    if (q == XrmQEseascape)
	done(XtPageOrientation, XtPageOrientationSeascape);

    XtDisplayStringConversionWarning(dpy, str, XtRPageOrientation);
    return False;
}

/* Palette Conversion Routine.
 * Returns True if Conversion is successful.
 */
Boolean
XmuCvtStringToPalette(dpy, args, num_args, fromVal, toVal, data)
    Display	*dpy;
    XrmValue	*args;		/* unused */
    Cardinal	*num_args;	/* unused */
    XrmValue	*fromVal;
    XrmValue	*toVal;
    XtPointer	*data;		/* unused */
{
    static XrmQuark		XrmQEmonochrome;
    static XrmQuark		XrmQEgrayscale;
    static XrmQuark		XrmQEcolor;
    static int			haveQuarks;
    XrmQuark    q;
    char	*str = (XPointer) fromVal->addr;
    char        lowerName[1000];

    if (str == NULL) return False;

    if (!haveQuarks) {
	XrmQEmonochrome = XrmStringToQuark(XtEmonochrome);
	XrmQEgrayscale  = XrmStringToQuark(XtEgrayscale);
	XrmQEcolor      = XrmStringToQuark(XtEcolor);
	haveQuarks = 1;
    }

    XmuCopyISOLatin1Lowered(lowerName, str);

    q = XrmStringToQuark(lowerName);

    if (q == XrmQEmonochrome)
	done(XtPalette, XtPaletteMonochrome);
    if (q == XrmQEgrayscale)
	done(XtPalette, XtPaletteGrayscale);
    if (q == XrmQEcolor)
	done(XtPalette, XtPaletteColor);

    XtDisplayStringConversionWarning(dpy, str, XtRPalette);
    return False;
}

#ifdef VMS

/*
** VMS specific include files
*/
#include <descrip.h>
#include <ssdef.h>
#include <clidef.h>
#include <lnmdef.h>
#include <iodef.h>
#include <dvidef.h>
#include "vms_types.h"

#define ERR_SIGNAL(s) if(!((s) & 1))lib$signal((s), 0, 0)
#define XtEFN 23

struct g_l_i
{
    GhostviewWidget w;
    struct g_l_i *next;
};

typedef struct g_l_i GhostListItem, *GLI_p;

static GhostListItem glhead = {(GhostviewWidget) -1, NULL};
static GLI_p GL = &glhead;
static size_t GLI_Size = sizeof(GhostListItem);
static XtInputId EventId;

/*
** This routine is passed to XtAppAddInput(). It is called whenever the event
** flag number XtEFN is set and the Xt main loop becomes idle. It clears the
** event flag and then scans all the ghostview widgets for completed I/O
** requests, processing each as they are found. We have to do them all because
** there is no way to have Xt dispatch them individually without a window of
** vulnerability that can cause missed events, or by using a separate event
** flag for each I/O stream. Event flags are, unfortunately, a limited
** resource.
*/
static Boolean
IOProcess()
{
    GhostviewWidget gvw;
    GLI_p cur;

    /*
    ** Before we process any I/O's, clear the event flag.
    */
    sys$clref(XtEFN);
    /*
    ** Scan all the ghostview widgets and check for completed I/O's
    */
    for(cur = GL->next; cur; cur = cur->next){
	/*
	** Get the widget and check for I/O complete on either mailbox.
	*/
	gvw = cur->w;
	if(gvw->ghostview.interpreter_input_iosb[0])Input(gvw);
	if(gvw->ghostview.interpreter_output_iosb[0])Output(gvw);
    }
}
    
/*
** This is an AST routine. It is called asynchronously whenever one of our
** mailbox I/O's completes.
*/
static void
IOComplete(client_data)
    XtPointer client_data;
{
    /*
    ** Set the event flag to tell Xt to call IOProcess.
    */
    sys$setef(XtEFN);
}

static void
GLInsert(w)
    GhostviewWidget w;
{
    GLI_p new;
    int first;
    
    /*
    ** Insert this widget after the list head
    */
    first = (GL->next == NULL);
    new = XtMalloc(GLI_Size);
    new->w = w;
    new->next = GL->next;
    GL->next = new;
    /*
    ** If this is the first item on the list, call XtAppAddInput()
    */
    if(first)EventId = XtAppAddInput(XtWidgetToApplicationContext(w), XtEFN, 0, 
	IOProcess, 0);
}

static void
GLRemove(w)
    GhostviewWidget w;
{
    GLI_p prev, cur;
    int last = 0;

    /*
    ** Find and remove this widget from the list.
    */
    prev = GL;
    cur = prev->next;
    while(cur && cur->w != w){
	prev = cur;
	cur = cur->next;
    }
    if(cur){
       prev->next = cur->next;
       XtFree(cur);
       last = (GL->next == NULL);
    }
    /*
    ** If this was the last item on the list, call XtRemoveInput()
    */
    if(last)XtRemoveInput(EventId);
}

/* Input - Feed data to ghostscript's stdin.
 * Write bytes to ghostscript using non-blocking I/O.
 * Also, pipe signals are caught during writing.  The return
 * values are checked and the appropriate action is taken.  I do
 * this at this low level, because it may not be appropriate for
 * SIGPIPE to be caught for the overall application.
 */

static void
Input(gvw)
    GhostviewWidget gvw;
{
    int stat, bbytes;
    char *ch;

    /*
    ** Check for error on previous I/O.
    */
    stat = gvw->ghostview.interpreter_input_iosb[0];
    if(stat != SS$_NORMAL){
	InterpreterFailed(gvw);
    } else {

	/* Get a new section if required */
	
	if (gvw->ghostview.ps_input && gvw->ghostview.bytes_left == 0) {
	    struct record_list *ps_old = gvw->ghostview.ps_input;
	    gvw->ghostview.ps_input = ps_old->next;
	    if (ps_old->close) fclose(ps_old->fp);
	    XtFree((char *)ps_old);
	}
	if(gvw->ghostview.ps_input){
	    /* Have to seek at the beginning of each section */
	    if (gvw->ghostview.ps_input->seek_needed) {
		if (gvw->ghostview.ps_input->len > 0)
		    fseek(gvw->ghostview.ps_input->fp,
			  gvw->ghostview.ps_input->begin, SEEK_SET);
		gvw->ghostview.ps_input->seek_needed = False;
		gvw->ghostview.bytes_left = gvw->ghostview.ps_input->len;
	    }
	    /*
	    ** Read a line from the file.
	    */
	    ch = fgets(gvw->ghostview.input_buffer, GV_BUFSIZ,
		gvw->ghostview.ps_input->fp);
	    if(!ch){
		/*
		** Error, EOF when there's supposed to be data left. 
		*/
		InterpreterFailed(gvw);
	    } else {
		/*
		** Write it to the mailbox.
		*/
		bbytes = strlen(gvw->ghostview.input_buffer);
		gvw->ghostview.bytes_left -= bbytes;
		stat = sys$qio(0, (short)gvw->ghostview.interpreter_input,
		    IO$_WRITEVBLK, &gvw->ghostview.interpreter_input_iosb,
		    IOComplete, 0, gvw->ghostview.input_buffer, bbytes,
		    0, 0, 0, 0);
		ERR_SIGNAL(stat);
	    }
	}
    }
}

/* Output - receive I/O from ghostscript's stdout and stderr.
 * Pass this to the application via the output_callback. */
static void
Output(gvw)
    GhostviewWidget gvw;
{
    char buf[GV_BUFSIZ+1];
    int bytes, stat;

    stat = gvw->ghostview.interpreter_output_iosb[0];
    bytes = gvw->ghostview.interpreter_output_iosb[1];
    if (stat == SS$_NORMAL) {
	/*
	** Got a message. If line complete, pass to the output_callback.
	**
	** HACK ALERT, if bytes is -1 nothing happens, but an I/O is queued.
	** This is our first time code, since Xt doesn't queue the I/O for us
	** under VMS, just watches for completion. In StartInterpreter We setup
	** an IOSB with a success status and -1 bytes so Xt will call us the
	** first time to get the I/O queued.
	*/
	if (bytes == 0) {
	    strcpy(buf, "\n");
	} else if (bytes == 1) {
	    buf[0] = gvw->ghostview.output_buffer[0];
	    buf[1] = '\0';
	} else if (bytes > 1) {
	    /*
	    ** Copy the message to a local buffer and pass it to the callback.
	    */
	    memcpy(buf, gvw->ghostview.output_buffer, bytes);
	    buf[bytes] = '\0';
	}
	if(bytes >= 0)XtCallCallbackList(gvw, gvw->ghostview.output_callback,
	    (XtPointer) buf);
	/*
	** Queue a new read to the mailbox
	*/
	stat = sys$qio(0, (short)gvw->ghostview.interpreter_output,
	    IO$_READVBLK, &gvw->ghostview.interpreter_output_iosb, IOComplete,
	    0, gvw->ghostview.output_buffer, GV_BUFSIZ, 0, 0, 0, 0);
	ERR_SIGNAL(stat);
    } else {
	InterpreterFailed(gvw);		/* Something bad happened */
    }
}

/* This routine starts the interpreter.  It sets the DISPLAY and 
 * GHOSTVIEW environment variables.  The GHOSTVIEW environment variable
 * contains the Window that ghostscript should write on.
 *
 * This routine also opens pipes for stdout and stderr and initializes
 * application input events for them.  If input to ghostscript is not
 * from a file, a pipe for stdin is created.  This pipe is setup for
 * non-blocking I/O so that the user interface never "hangs" because of
 * a write to ghostscript.
 */
static void
StartInterpreter(w)
    Widget w;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    char buf[GV_BUFSIZ];
    char cmd[512];
    int ret;
    short ch1, ch2;
    char in_mbx_name[65], out_mbx_name[65];
    long pid, nowait = CLI$M_NOWAIT;
    const $DESCRIPTOR(ghostview_desc, "GHOSTVIEW");
    const $DESCRIPTOR(display_desc, "DECW$DISPLAY");
    const $DESCRIPTOR(lnt_desc, "LNM$PROCESS");
    $DESCRIPTOR(in_desc, "");
    $DESCRIPTOR(out_desc, "");
    $DESCRIPTOR(lnm_desc, "");
    $DESCRIPTOR(cmd_desc, cmd);
    ITEM_LIST_3_T(gv_list, 1) = {{{0, LNM$_STRING, buf, NULL}}, 0};
    ITEM_LIST_3_T(dis_list, 1) = {{{0, LNM$_STRING, NULL, NULL}}, 0};
    ITEM_LIST_3_T(dvi_list, 1) = {{{64, DVI$_DEVNAM, NULL, NULL}}, 0};
    IOSB_GET_T dvi_iosb;

    /*
    ** Stop interpreter if running
    */
    StopInterpreter(w);
    /*
    ** Clear the window before starting a new interpreter.
    */
    if (gvw->core.background_pixmap != XtUnspecifiedPixmap) {
	XFillRectangle(XtDisplay(w), gvw->core.background_pixmap,
		       gvw->ghostview.gc,
		       0, 0, gvw->core.width, gvw->core.height);
    }
    XClearArea(XtDisplay(w), XtWindow(w),
	       0, 0, gvw->core.width, gvw->core.height, False);
    /*
    ** Check for disabled.
    */
    if (gvw->ghostview.disable_start) return;
    /*
    ** Build Ghostscript startup command
    */
    strcpy(cmd, gvw->ghostview.interpreter);
    strcat(cmd, " \"-sDEVICE=x11\" \"-dNOPAUSE\" ");
    if (gvw->ghostview.quiet) strcat(cmd, "\"-dQUIET\" ");
    if (gvw->ghostview.safer) strcat(cmd, "\"-dSAFER\" ");
    if (gvw->ghostview.arguments) {
	strcat(cmd, gvw->ghostview.arguments);
	strcat(cmd, " ");
    }
    strcat(cmd, "\"-\" ");

    /*
    ** Determine input source.
    */
    if (gvw->ghostview.filename == NULL) {
	/*
	** Create a mailbox to feed input to Ghostscript and get its name.
	*/
	ret = sys$crembx(0, &ch1, GV_BUFSIZ, GV_BUFSIZ, 0, 0, 0, 0);
	ERR_SIGNAL(ret);
	dvi_list.item[0].buffer_p = in_mbx_name;
	ret = sys$getdvi(0, ch1, 0, &dvi_list, &dvi_iosb, 0, 0, 0);
	ERR_SIGNAL(ret); ERR_SIGNAL(dvi_iosb.status);
	in_mbx_name[64] = '\0';
	in_desc.dsc$a_pointer = in_mbx_name;
	in_desc.dsc$w_length = strlen(in_mbx_name);
    } else {
	/*
	** Set up file name to give Ghostscript as standard input.
	*/
	in_desc.dsc$a_pointer = gvw->ghostview.filename;
	in_desc.dsc$w_length = strlen(gvw->ghostview.filename);
    }
    /*
    ** Create mailbox to receive Ghostscript's output
    */
    ret = sys$crembx(0, &ch2, GV_BUFSIZ, GV_BUFSIZ, 0, 0, 0, 0);
    ERR_SIGNAL(ret);
    dvi_list.item[0].buffer_p = out_mbx_name;
    ret = sys$getdvi(0, ch2, 0, &dvi_list, &dvi_iosb, 0, 0, 0);
    ERR_SIGNAL(ret); ERR_SIGNAL(dvi_iosb.status);
    out_mbx_name[64] = '\0';
    out_desc.dsc$a_pointer = out_mbx_name;
    out_desc.dsc$w_length = strlen(out_mbx_name);
    /*
    ** Create GHOSTVIEW and DECW$DISPLAY logical names.
    **
    ** We use CRELNM rather than LIB$SET_LOGICAL because we want these to be
    ** user mode and go away when the program exits. It doesn't matter that we
    ** may set them multiple times, as with the mailbox logicals, since once
    ** Ghostscript starts we don't need them any more.
    */
    sprintf(buf, "%d", XtWindow(w));
    gv_list.item[0].buffer_size = strlen(buf);
    ret = sys$crelnm(0, &lnt_desc, &ghostview_desc, 0, &gv_list);
    ERR_SIGNAL(ret);
    dis_list.item[0].buffer_p = XDisplayString(XtDisplay(w));
    dis_list.item[0].buffer_size = strlen(dis_list.item[0].buffer_p);
    ret = sys$crelnm(0, &lnt_desc, &display_desc, 0, &dis_list);
    ERR_SIGNAL(ret);
    /*
    ** Spawn Ghostscript process
    */
    gvw->ghostview.changed = False;
    gvw->ghostview.busy = True;
    cmd_desc.dsc$w_length = strlen(cmd);
    ret = lib$spawn(&cmd_desc, &in_desc, &out_desc, &nowait, 0, &pid, 0, 0,
	0, 0, 0, 0, 0);
    ERR_SIGNAL(ret);
    XDefineCursor(XtDisplay(gvw), XtWindow(gvw), gvw->ghostview.busy_cursor);
    /*
    ** Everything worked, initialize IOSBs and save info about interpretter.
    */
    gvw->ghostview.interpreter_pid = pid;
    if (gvw->ghostview.filename == NULL) {
	gvw->ghostview.interpreter_input = ch1;
	gvw->ghostview.interpreter_input_iosb[0] = 0;
    }
    gvw->ghostview.interpreter_output = ch2;
    if (gvw->ghostview.output_buffer == NULL) {
	gvw->ghostview.output_buffer = XtMalloc(GV_BUFSIZ);
    }
    GLInsert(gvw);
    /*
    ** Fake a completed I/O so Output will get called to queue the first I/O.
    */
    gvw->ghostview.interpreter_output_iosb[0] = SS$_NORMAL;
    gvw->ghostview.interpreter_output_iosb[1] = -1;
    IOComplete();
}

/* Stop the interperter, if present, and remove any Input sources. */
/* Also reset the busy state. */
static void
StopInterpreter(w)
    Widget w;
{
    int ret;
    
    GhostviewWidget gvw = (GhostviewWidget) w;
    if (gvw->ghostview.interpreter_pid >= 0) {
	ret = sys$delprc(&gvw->ghostview.interpreter_pid, 0);
	if(ret != SS$_NORMAL && ret != SS$_NONEXPR)lib$signal(ret, 0, 0);
	gvw->ghostview.interpreter_pid = -1;
    }
    if (gvw->ghostview.interpreter_input >= 0) {
	(void) sys$dassgn(gvw->ghostview.interpreter_input);
	gvw->ghostview.interpreter_input = -1;
	while (gvw->ghostview.ps_input) {
	    struct record_list *ps_old = gvw->ghostview.ps_input;
	    gvw->ghostview.ps_input = ps_old->next;
	    if (ps_old->close) fclose(ps_old->fp);
	    XtFree((char *)ps_old);
	}
    }
    if (gvw->ghostview.interpreter_output >= 0) {
	(void) sys$dassgn(gvw->ghostview.interpreter_output);
	gvw->ghostview.interpreter_output = -1;
    }
    gvw->ghostview.busy = False;
    XDefineCursor(XtDisplay(gvw), XtWindow(gvw), gvw->ghostview.cursor);
    GLRemove(gvw);
}

/* GhostviewSendPS:
 *   Queue a portion of a PostScript file for output to ghostscript.
 *   fp: FILE * of the file in question.  NOTE: if you have several
 *   Ghostview widgets reading from the same file.  You must open
 *   a unique FILE * for each widget.
 *   SendPS does not actually send the PostScript, it merely queues it
 *   for output.
 *   begin: position in file (returned from ftell()) to start.
 *   len:   number of bytes to write.
 *
 *   If an interpreter is not running, nothing is queued and
 *   False is returned.
 */
Boolean
GhostviewSendPS(w, fp, begin, len, close)
    Widget w;
    FILE *fp;
    long begin;
    unsigned int len;
    Bool close;
{
    GhostviewWidget gvw = (GhostviewWidget) w;
    struct record_list *ps_new;

    if (gvw->ghostview.interpreter_input < 0) return False;
    if(len != 0){
	ps_new = (struct record_list *) XtMalloc(sizeof (struct record_list));
	ps_new->fp = fp;
	ps_new->begin = begin;
	ps_new->len = len;
	ps_new->seek_needed = True;
	ps_new->close = close;
	ps_new->next = NULL;

	if (gvw->ghostview.input_buffer == NULL) {
	    gvw->ghostview.input_buffer = XtMalloc(GV_BUFSIZ);
	}

	if (gvw->ghostview.ps_input == NULL) {
	    gvw->ghostview.bytes_left = len;
	    gvw->ghostview.ps_input = ps_new;
	    /*
	    ** Fake a completed I/O so Input will get called to queue the
	    ** first I/O.
	    */
	    gvw->ghostview.interpreter_input_iosb[0] = SS$_NORMAL;
	    gvw->ghostview.interpreter_input_iosb[1] = -1;
	    IOComplete();
	} else {
	    struct record_list *p = gvw->ghostview.ps_input;
	    while (p->next != NULL) {
		p = p->next;
	    }
	    p->next = ps_new;
	}
    }
    return True;
}
#endif /* VMS */
