 /* 
 * DPSScrollW.c
 *
 * (c) Copyright 1993-1994 Adobe Systems Incorporated.
 * All rights reserved.
 * 
 * Permission to use, copy, modify, distribute, and sublicense this software
 * and its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notices appear in all copies and that
 * both those copyright notices and this permission notice appear in
 * supporting documentation and that the name of Adobe Systems Incorporated
 * not be used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  No trademark license
 * to use the Adobe trademarks is hereby granted.  If the Adobe trademark
 * "Display PostScript"(tm) is used to describe this software, its
 * functionality or for any other purpose, such use shall be limited to a
 * statement that this software works in conjunction with the Display
 * PostScript system.  Proper trademark attribution to reflect Adobe's
 * ownership of the trademark shall be given whenever any such reference to
 * the Display PostScript system is made.
 * 
 * ADOBE MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THE SOFTWARE FOR
 * ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 * ADOBE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON- INFRINGEMENT OF THIRD PARTY RIGHTS.  IN NO EVENT SHALL ADOBE BE LIABLE
 * TO YOU OR ANY OTHER PARTY FOR ANY SPECIAL, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE, STRICT LIABILITY OR ANY OTHER ACTION ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.  ADOBE WILL NOT
 * PROVIDE ANY TRAINING OR OTHER SUPPORT FOR THE SOFTWARE.
 * 
 * Adobe, PostScript, and Display PostScript are trademarks of Adobe Systems
 * Incorporated which may be registered in certain jurisdictions
 * 
 * Author:  Adobe Systems Incorporated
 */
/* $XFree86$ */

#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/ShellP.h>
#include <X11/Xproto.h>
#include <stdlib.h>
#include <Xm/Xm.h>

/* There are no words to describe how I feel about having to do this */

#if XmVersion > 1001		
#include <Xm/ManagerP.h>
#else
#include <Xm/XmP.h>
#endif

#include <Xm/DrawingA.h>
#include <Xm/ScrolledW.h>
#include <Xm/ScrollBar.h>

#include <DPS/dpsXclient.h>
#include "dpsXcommonI.h"
#include <DPS/dpsXshare.h>
#include "DSWwraps.h"
#include <stdio.h>
#include <DPS/DPSScrollWP.h>

#undef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#undef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#undef ABS
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#undef CEIL
#define CEIL(x) ((int) ((float)((int)(x)) == (x) ? (x) : (x) + 1))

/* Define macros to get rectangle entries.  All rectangles are stored as
   x, y, width, height.  NOTE:  ONLY FOR USER SPACE RECTANGLES, NOT X
   RECTANGLES!!!! */

#define LEFT(r) ((r)[0])
#define RIGHT(r) ((r)[0] + (r)[2])
#define BOTTOM(r) ((r)[1])
#define TOP(r) ((r)[1] + (r)[3])
#define WIDTH(r) ((r)[2])
#define HEIGHT(r) ((r)[3])

/* This is used in converting bounding boxes into user space to ensure
   that we don't end up slopping over into another pixel */

#define DELTA .001

#define Offset(field) XtOffsetOf(DPSScrolledWindowRec, sw.field)

static float initScale = 1.0;

static XtResource resources[] = {
    {XtNcontext, XtCContext, XtRDPSContext, sizeof(DPSContext),
	Offset(context), XtRImmediate, (XtPointer) NULL},
    {XtNareaWidth, XtCAreaWidth, XtRInt, sizeof(int),
	Offset(area_width), XtRImmediate, (XtPointer) ((int) (8.5*72))},
    {XtNareaHeight, XtCAreaHeight, XtRInt, sizeof(int),
	Offset(area_height), XtRImmediate, (XtPointer) (11*72)},
    {XtNscale, XtCScale, XtRFloat, sizeof(float),
	Offset(scale), XtRFloat, (XtPointer) &initScale},
    {XtNctm, XtCCtm, XtRFloatArray, sizeof(float *),
	Offset(ctm_ptr), XtRImmediate, (XtPointer) NULL},
    {XtNinvCtm, XtCInvCtm, XtRFloatArray, sizeof(float *),
	Offset(inv_ctm_ptr), XtRImmediate, (XtPointer) NULL},
    {XtNuseBackingPixmap, XtCUseBackingPixmap, XtRBoolean, sizeof(Boolean),
	Offset(use_backing_pixmap), XtRImmediate, (XtPointer) True},
    {XtNuseFeedbackPixmap, XtCUseFeedbackPixmap, XtRBoolean, sizeof(Boolean),
	Offset(use_feedback_pixmap), XtRImmediate, (XtPointer) True},
    {XtNbackingPixmap, XtCBackingPixmap, XtRPixmap, sizeof(Pixmap),
	Offset(backing_pixmap), XtRImmediate, (XtPointer) None},
    {XtNfeedbackPixmap, XtCFeedbackPixmap, XtRPixmap, sizeof(Pixmap),
	Offset(feedback_pixmap), XtRImmediate, (XtPointer) None},
    {XtNdocumentSizePixmaps, XtCDocumentSizePixmaps,
	XtRBoolean, sizeof(Boolean),
	Offset(document_size_pixmaps), XtRImmediate, (XtPointer) False},
    {XtNwindowGState, XtCWindowGState, XtRDPSGState, sizeof(DPSGState),
	Offset(window_gstate), XtRImmediate, (XtPointer) 0},
    {XtNbackingGState, XtCBackingGState, XtRDPSGState, sizeof(DPSGState),
	Offset(backing_gstate), XtRImmediate, (XtPointer) 0},
    {XtNfeedbackGState, XtCFeedbackGState, XtRDPSGState, sizeof(DPSGState),
	Offset(feedback_gstate), XtRImmediate, (XtPointer) 0},
    {XtNdirtyAreas, XtCDirtyAreas, XtRFloatArray, sizeof(float *),
	Offset(dirty_areas), XtRImmediate, (XtPointer) NULL},
    {XtNnumDirtyAreas, XtCNumDirtyAreas, XtRShort, sizeof(short),
	Offset(num_dirty_areas), XtRImmediate, (XtPointer) 0},
    {XtNpixmapLimit, XtCPixmapLimit, XtRInt, sizeof(int),
	Offset(pixmap_limit), XtRImmediate, (XtPointer) -1},
    {XtNabsolutePixmapLimit, XtCAbsolutePixmapLimit, XtRInt, sizeof(int),
	Offset(absolute_pixmap_limit), XtRImmediate, (XtPointer) 0},
    {XtNwatchProgress, XtCWatchProgress, XtRBoolean, sizeof(Boolean),
	Offset(watch_progress), XtRImmediate, (XtPointer) False},
    {XtNwatchProgressDelay, XtCWatchProgressDelay, XtRInt, sizeof(int),
	Offset(watch_progress_delay), XtRImmediate, (XtPointer) 1000},
    {XtNminimalDrawing, XtCMinimalDrawing, XtRBoolean, sizeof(Boolean),
	Offset(minimal_drawing), XtRImmediate, (XtPointer) False},
    {XtNapplicationScrolling, XtCApplicationScrolling,
	XtRBoolean, sizeof(Boolean),
	Offset(application_scrolling), XtRImmediate, (XtPointer) False},
    {XtNsetupCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
	Offset(setup_callback), XtRCallback, (XtPointer) NULL},
    {XtNexposeCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
	Offset(expose_callback), XtRCallback, (XtPointer) NULL},
    {XtNbackgroundCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
	Offset(background_callback), XtRCallback, (XtPointer) NULL},
    {XtNfeedbackCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
	Offset(feedback_callback), XtRCallback, (XtPointer) NULL},
    {XtNresizeCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
	Offset(resize_callback), XtRCallback, (XtPointer) NULL},
};

static Boolean GiveFeedbackPixmap(Widget w, Pixmap p, int width, int height, int depth, Screen *screen);
static Boolean SetValues(Widget old, Widget req, Widget new, ArgList args, Cardinal *num_args);
static Boolean TakeFeedbackPixmap(Widget w, Pixmap *p, int *width, int *height, int *depth, Screen **screen);
static XtGeometryResult GeometryManager(Widget w, XtWidgetGeometry *desired, XtWidgetGeometry *allowed);
static XtGeometryResult QueryGeometry(Widget w, XtWidgetGeometry *desired, XtWidgetGeometry *allowed);
static void AbortPendingDrawing(Widget w);
static void AddExposureToPending(DPSScrolledWindowWidget dsw, XExposeEvent *ev);
static void AddRectsToDirtyArea(DPSScrolledWindowWidget dsw, float *newRect, int n);
static void AddRectsToPending(DPSScrolledWindowWidget dsw, int *newRect, int n);
static void AddToDirtyArea(Widget w, float *rect, long n);
static void AddUserSpaceRectsToPending(DPSScrolledWindowWidget dsw, float *newRect, int n);
static void CallFeedbackCallback(DPSScrolledWindowWidget dsw, float *r, int n);
static void CheckFeedbackPixmap(DPSScrolledWindowWidget dsw);
static void ClassPartInitialize(WidgetClass widget_class);
static void ConvertPSToX(Widget w, double psX, double psY, int *xX, int *xY);
static void ConvertToOrigPS(DPSScrolledWindowWidget dsw, int xX, int xY, float *psX, float *psY);
static void ConvertToPS(DPSScrolledWindowWidget dsw, float xX, float xY, float *psX, float *psY);
static void ConvertToX(DPSScrolledWindowWidget dsw, float psX, float psY, int *xX, int *xY);
static void ConvertXToPS(Widget w, long xX, long xY, float *psX, float *psY);
static void CopyRectsToCurrentDrawing(DPSScrolledWindowWidget dsw, float *newRect, int n);
static void CopyRectsToDirtyArea(DPSScrolledWindowWidget dsw, float *newRect, int n);
static void CopyToFeedbackPixmap(DPSScrolledWindowWidget dsw, float *rects, int n);
static void Destroy(Widget widget);
static void DrawingAreaExpose(Widget w, XtPointer clientData, XtPointer callData);
static void DrawingAreaGraphicsExpose(Widget w, XtPointer clientData, XEvent *event, Boolean *goOn);
static void EndFeedbackDrawing(Widget w, int restore);
static void FinishDrawing(DPSScrolledWindowWidget dsw);
static void FinishPendingDrawing(Widget w);
static void GetDrawingInfo(Widget w, DSWDrawableType *type, Drawable *drawable, DPSGState *gstate, DPSContext *context);
static void GetScrollInfo(Widget w, int *h_value, int *h_size, int *h_max, int *v_value, int *v_size, int *v_max);
static void HScrollCallback(Widget w, XtPointer clientData, XtPointer callData);
static void Initialize(Widget request, Widget new, ArgList args, Cardinal *num_args);
static void Realize(Widget w, XtValueMask *mask, XSetWindowAttributes *attr);
static void Resize(Widget w);
static void ScrollBy(Widget w, long dx, long dy);
static void ScrollMoved(DPSScrolledWindowWidget dsw);
static void ScrollPoint(Widget w, double psX, double psY, long xX, long xY);
static void ScrollTo(Widget w, long x, long y);
static void SetFeedbackDirtyArea(Widget w, float *rects, int count, XtPointer continue_feedback_data);
static void SetScale(Widget w, double scale, long fixedX, long fixedY);
static void SetScaleAndScroll(Widget w, double scale, double psX, double psY, long xX, long xY);
static void StartFeedbackDrawing(Widget w, XtPointer start_feedback_data);
static void UpdateDrawing(Widget w, float *rects, int count);
static void VScrollCallback(Widget w, XtPointer clientData, XtPointer callData);

DPSScrolledWindowClassRec dpsScrolledWindowClassRec = {
    /* Core class part */
  {
    /* superclass	     */	(WidgetClass) &xmManagerClassRec,
    /* class_name	     */ "DPSScrolledWindow",
    /* widget_size	     */ sizeof(DPSScrolledWindowRec),
    /* class_initialize      */ NULL,
    /* class_part_initialize */ ClassPartInitialize,
    /* class_inited          */	False,
    /* initialize	     */	Initialize,
    /* initialize_hook       */	NULL,
    /* realize		     */	Realize,
    /* actions		     */	NULL,
    /* num_actions	     */	0,
    /* resources	     */	resources,
    /* num_resources	     */	XtNumber(resources),
    /* xrm_class	     */	NULLQUARK,
    /* compress_motion	     */	True,
    /* compress_exposure     */	XtExposeCompressMultiple,
    /* 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 offsets      */	NULL,
    /* tm_table              */	NULL,
    /* query_geometry	     */	QueryGeometry,
    /* display_accelerator   */	NULL,
    /* extension	     */	NULL,
  },
   /* Composite class part */
  {
    /* geometry_manager	     */	GeometryManager,
    /* change_managed	     */	NULL,
    /* insert_child	     */	XtInheritInsertChild,
    /* delete_child	     */	XtInheritDeleteChild,
    /* extension	     */	NULL,
  },
   /* Constraint class part */
  {
    /* resources	     */ NULL,
    /* num_resources	     */ 0,
    /* constraint_size	     */ 0,
    /* initialize	     */ NULL,
    /* destroy		     */ NULL,
    /* set_values	     */ NULL,
    /* extension	     */ NULL,
  },
   /* Manager class part */
  {
    /* translations	     */ XtInheritTranslations,
    /* syn_resources	     */ NULL,
    /* num_syn_resources     */ 0,
    /* syn_constraint_resources */ NULL,
    /* num_syn_constraint_resources */ 0,
    /* parent_process	     */ XmInheritParentProcess,
    /* extension	     */ NULL,
  },
   /* DPSScrolledWindow class part */
  {
    /* set_scale	     */ SetScale,
    /* scroll_point	     */ ScrollPoint,
    /* scroll_by	     */ ScrollBy,
    /* scroll_to	     */ ScrollTo,
    /* set_scale_and_scroll  */ SetScaleAndScroll,
    /* convert_x_to_ps	     */ ConvertXToPS,
    /* convert_ps_to_x	     */ ConvertPSToX,
    /* add_to_dirty_area     */ AddToDirtyArea,
    /* take_feedback_pixmap  */ TakeFeedbackPixmap,
    /* give_feedback_pixmap  */ GiveFeedbackPixmap,
    /* start_feedback_drawing */ StartFeedbackDrawing,
    /* end_feedback_drawing  */ EndFeedbackDrawing,
    /* set_feedback_dirty_area */ SetFeedbackDirtyArea,
    /* finish_pending_drawing */ FinishPendingDrawing,
    /* abort_pending_drawing */ AbortPendingDrawing,
    /* get_drawing_info	     */ GetDrawingInfo,
    /* update_drawing	     */ UpdateDrawing,
    /* get_scroll_info	     */ GetScrollInfo,				
    /* extension	     */	NULL,
  }
};

WidgetClass dpsScrolledWindowWidgetClass =
	(WidgetClass) &dpsScrolledWindowClassRec;

/***** UTILITY FUNCTIONS *****/

static void PrintRectList(float *r, short num_r)
{
    int i;

    for (i = 0; i < num_r; i++) {
	printf("Rectangle %d:  ", i);
	printf("X %g Y %g W %g H %g\n", r[0], r[1], r[2], r[3]);
	r += 4;
    }
}

/* Make sure the list pointed to by r can hold n more rectangles.  Always
   grow by at least min_grow */

static void GrowRectList(
    float **r,
    short *r_size,
    short num_r,
    int n, int min_grow)
{
    if (*r_size < num_r + n) {
	if (min_grow > 1 && num_r + n - *r_size < min_grow) {
	    *r_size += min_grow;
	} else *r_size = num_r + n;
	*r = (float *) XtRealloc((char *) *r, *r_size * 4 * sizeof(float));
    }
}

static void GrowIntRectList(
    int **r,
    short *r_size,
    short num_r,
    int n, int min_grow)
{
    if (*r_size < num_r + n) {
	if (min_grow > 1 && num_r + n - *r_size < min_grow) {
	    *r_size += min_grow;
	} else *r_size = num_r + n;
	*r = (int *) XtRealloc((char *) *r, *r_size * 4 * sizeof(int));
    }
}

static Boolean Intersects(float *r1, float *r2)
{
    if (RIGHT(r1) <= LEFT(r2)) return False;
    if (RIGHT(r2) <= LEFT(r1)) return False;
    if (TOP(r1) <= BOTTOM(r2)) return False;
    if (TOP(r2) <= BOTTOM(r1)) return False;

    return True;
}

/* Subtract sub from src, putting result into dst.  Return rectangle count */

static int Subtract(float *src, float *sub, float *dst)
{
    int n = 0;

    /* If bottom of sub is greater than bottom of src, there's a
       rectangle across the bottom */
    if (BOTTOM(sub) > BOTTOM(src)) {
	LEFT(dst) = LEFT(src);
	BOTTOM(dst) = BOTTOM(src);
	WIDTH(dst) = WIDTH(src);
	HEIGHT(dst) = BOTTOM(sub) - BOTTOM(src);
	n++;
	dst += 4;
    }

    /* If left of sub is greater than left of src, there's a left rectangle. */
    if (LEFT(sub) > LEFT(src)) {
	LEFT(dst) = LEFT(src);
	BOTTOM(dst) = MAX(BOTTOM(src), BOTTOM(sub));
	WIDTH(dst) = LEFT(sub) - LEFT(src);
	HEIGHT(dst) = MIN(TOP(src), TOP(sub)) - BOTTOM(dst);
	n++;
	dst += 4;
    }
    
    /* If right of sub is less than right of src, there's a right rect */
    if (RIGHT(sub) < RIGHT(src)) {
	LEFT(dst) = RIGHT(sub);
	BOTTOM(dst) = MAX(BOTTOM(src), BOTTOM(sub));
	WIDTH(dst) = RIGHT(src) - RIGHT(sub);	
	HEIGHT(dst) = MIN(TOP(src), TOP(sub)) - BOTTOM(dst);
	n++;
	dst += 4;
    }

    /* If top of sub is less than top of src, there's a top rectangle */
    if (TOP(sub) < TOP(src)) {
	LEFT(dst) = LEFT(src);
	BOTTOM(dst) = TOP(sub);
	WIDTH(dst) = WIDTH(src);
	HEIGHT(dst) = TOP(src) - TOP(sub);
	n++;
	dst += 4;
    }

    return n;
}

static void Copy(float *src, float *dst)
{
    LEFT(dst) = LEFT(src);
    BOTTOM(dst) = BOTTOM(src);
    WIDTH(dst) = WIDTH(src);
    HEIGHT(dst) = HEIGHT(src);
}

static void Intersection(float *r1, float *r2, float *dst)
{
    LEFT(dst) = MAX(LEFT(r1), LEFT(r2));
    BOTTOM(dst) = MAX(BOTTOM(r1), BOTTOM(r2));
    WIDTH(dst) = MIN(RIGHT(r1), RIGHT(r2)) - LEFT(dst);
    HEIGHT(dst) = MIN(TOP(r1), TOP(r2)) - BOTTOM(dst);
}

/* These are used by the SubtractRects and IntersectRects procedures */

static float *rbuf = NULL;
static short rbuf_size = 0;
#define GROW_BUF 10

/* Replace the rectangle list in src with src minus sub */

static void SubtractRects(
    float **src,
    short *src_size,
    short *num_src,
    float *sub,
    int num_sub)
{
    short num_rbuf;
    float *r;
    int i;

    /* Go through, subtracting the first sub rectangle from each src
       rectangle.  Put the result in the internal buffer, then copy this
       list to the src.  Repeat for each sub rectangle. */

    while (num_sub > 0) {
	num_rbuf = 0;
	for (r = *src, i = 0; i < *num_src; r += 4, i++) {
	    if (Intersects(r, sub)) {
		/* Subtract sub from r, putting result into rbuf.  First
		   make sure there are at least 4 spaces in the buffer */
		GrowRectList(&rbuf, &rbuf_size, num_rbuf, 4, GROW_BUF);

		/* Do the subtraction */
		num_rbuf += Subtract(r, sub, rbuf + (num_rbuf*4));
	    } else {
		/* Copy r into buffer */
		GrowRectList(&rbuf, &rbuf_size, num_rbuf, 1, GROW_BUF);
		Copy(r, rbuf + (num_rbuf*4));
		num_rbuf++;
	    }
	}

	/* Copy buffered rectangles back into src */
	GrowRectList(src, src_size, 0, num_rbuf, 1);
	for (i = 0; i < num_rbuf * 4; i++) (*src)[i] = rbuf[i];
	*num_src = num_rbuf;

	/* Check if we've taken everything away */
	if (*num_src == 0) return;

	/* Skip on to the next sub rectangle */
	num_sub--;
	sub += 4;
    }
}

/* Replace list r1 with the intersection of r1 and r2 */

static void IntersectRects(
    float **r1,
    short *r1_size,
    short *num_r1,
    float *r2,
    int num_r2)
{
    short num_rbuf = 0;
    float *r;
    int i;

    /* Fairly straightforward.  Intersect each rectangle in r1 with each
       rectangle in r2, then copy the results to r1 */

    while (num_r2 > 0) {
	for (r = *r1, i = 0; i < *num_r1; r += 4, i++) {
	    if (Intersects(r, r2)) {
		GrowRectList(&rbuf, &rbuf_size, num_rbuf, 1, GROW_BUF);
		Intersection(r, r2, rbuf + (num_rbuf*4));
		num_rbuf++;
	    }
	}
	num_r2--;
	r2 += 4;
    }
    
    /* Copy intersection rectangles back into r1 */
    GrowRectList(r1, r1_size, 0, num_rbuf, 1);
    for (i = 0; i < num_rbuf * 4; i++) (*r1)[i] = rbuf[i];
    *num_r1 = num_rbuf;
}

static void SimplifyRects(float *rect, short *num)
{
    int i, j, k;
    float *r, *r1;

    i = 0;
    while (i < *num) {
	r = rect + (i * 4);
	if (WIDTH(r) == 0 || HEIGHT(r) == 0) {
	    for (k = 4*(i+1); k < *num * 4; k++) rect[k-4] = rect[k];
	    (*num)--;
	    goto LOOPEND;
	}
	j = i+1;
	while (j < *num) {
	    r1 = rect + (j * 4);
	    if (TOP(r1) <= TOP(r) && BOTTOM(r1) >= BOTTOM(r) &&
		LEFT(r1) >= LEFT(r) && RIGHT(r1) <= RIGHT(r)) {
		for (k = 4*(j+1); k < *num * 4; k++) rect[k-4] = rect[k];
		(*num)--;
	    } else if (TOP(r) <= TOP(r1) && BOTTOM(r) >= BOTTOM(r1) &&
		       LEFT(r) >= LEFT(r1) && RIGHT(r) <= RIGHT(r1)) {
		for (k = 4*(i+1); k < *num * 4; k++) rect[k-4] = rect[k];
		(*num)--;
		goto LOOPEND;
	    } else j++;
	}
	i++;
LOOPEND:;
    }
}

static void ComputeOffsets(DPSScrolledWindowWidget dsw, int *dx, int *dy)
{
    if (dsw->sw.doing_feedback && dsw->sw.feedback_pixmap != None) {
	*dx = *dy = 0;
    } else {
	if (dsw->sw.pixmap_width == dsw->sw.drawing_area->core.width) *dx = 0;
	else *dx = -dsw->sw.origin_x;
	if (dsw->sw.pixmap_height == dsw->sw.drawing_area->core.height) *dy = 0;
	else *dy = CEIL(dsw->sw.drawing_height) - dsw->sw.origin_y;
    }
}

static void ClassPartInitialize(WidgetClass widget_class)
{
    register DPSScrolledWindowWidgetClass wc =
	    (DPSScrolledWindowWidgetClass) widget_class;
    DPSScrolledWindowWidgetClass super =
	    (DPSScrolledWindowWidgetClass) wc->core_class.superclass;

    if (wc->sw_class.set_scale == InheritSetScale) {
	wc->sw_class.set_scale = super->sw_class.set_scale;
    }
    if (wc->sw_class.scroll_point == InheritScrollPoint) {
	wc->sw_class.scroll_point = super->sw_class.scroll_point;
    }
    if (wc->sw_class.scroll_by == InheritScrollBy) {
	wc->sw_class.scroll_by = super->sw_class.scroll_by;
    }
    if (wc->sw_class.scroll_to == InheritScrollTo) {
	wc->sw_class.scroll_to = super->sw_class.scroll_to;
    }
    if (wc->sw_class.set_scale_and_scroll == InheritSetScaleAndScroll) {
	wc->sw_class.set_scale_and_scroll =
		super->sw_class.set_scale_and_scroll;
    }
    if (wc->sw_class.convert_x_to_ps == InheritConvertXToPS) {
	wc->sw_class.convert_x_to_ps = super->sw_class.convert_x_to_ps;
    }
    if (wc->sw_class.convert_ps_to_x == InheritConvertPSToX) {
	wc->sw_class.convert_ps_to_x = super->sw_class.convert_ps_to_x;
    }
    if (wc->sw_class.add_to_dirty_area == InheritAddToDirtyArea) {
	wc->sw_class.add_to_dirty_area = super->sw_class.add_to_dirty_area;
    }
    if (wc->sw_class.take_feedback_pixmap == InheritTakeFeedbackPixmap) {
	wc->sw_class.take_feedback_pixmap =
		super->sw_class.take_feedback_pixmap;
    }
    if (wc->sw_class.give_feedback_pixmap == InheritGiveFeedbackPixmap) {
	wc->sw_class.give_feedback_pixmap =
		super->sw_class.give_feedback_pixmap;
    }
    if (wc->sw_class.start_feedback_drawing == InheritStartFeedbackDrawing) {
	wc->sw_class.start_feedback_drawing =
		super->sw_class.start_feedback_drawing;
    }
    if (wc->sw_class.end_feedback_drawing == InheritEndFeedbackDrawing) {
	wc->sw_class.end_feedback_drawing =
		super->sw_class.end_feedback_drawing;
    }
    if (wc->sw_class.set_feedback_dirty_area == InheritSetFeedbackDirtyArea) {
	wc->sw_class.set_feedback_dirty_area =
		super->sw_class.set_feedback_dirty_area;
    }
    if (wc->sw_class.finish_pending_drawing == InheritFinishPendingDrawing) {
	wc->sw_class.finish_pending_drawing =
		super->sw_class.finish_pending_drawing;
    }
    if (wc->sw_class.abort_pending_drawing == InheritAbortPendingDrawing) {
	wc->sw_class.abort_pending_drawing =
		super->sw_class.abort_pending_drawing;
    }
    if (wc->sw_class.get_drawing_info == InheritGetDrawingInfo) {
	wc->sw_class.get_drawing_info = super->sw_class.get_drawing_info;
    }
    if (wc->sw_class.update_drawing == InheritUpdateDrawing) {
	wc->sw_class.update_drawing = super->sw_class.update_drawing;
    }
    if (wc->sw_class.get_scroll_info == InheritGetScrollInfo) {
	wc->sw_class.get_scroll_info = super->sw_class.get_scroll_info;
    }
}

static void CreateChildren(DPSScrolledWindowWidget dsw)
{
    Widget w;

    w = dsw->sw.scrolled_window =
	    XtVaCreateManagedWidget("scrolledWindow",
				    xmScrolledWindowWidgetClass,
				    (Widget) dsw,
				    XtNwidth, dsw->core.width,
				    XtNheight, dsw->core.height,
				    XmNscrollingPolicy, XmAPPLICATION_DEFINED,
				    NULL);

    dsw->sw.h_scroll =
	    XtVaCreateManagedWidget("horizontalScrollBar",
				    xmScrollBarWidgetClass, w,
				    XmNorientation, XmHORIZONTAL,
				    NULL);
    XtAddCallback(dsw->sw.h_scroll, XmNvalueChangedCallback, HScrollCallback,
		  (XtPointer) dsw);
    XtAddCallback(dsw->sw.h_scroll, XmNdragCallback, HScrollCallback,
		  (XtPointer) dsw);

    dsw->sw.v_scroll =
	    XtVaCreateManagedWidget("verticalScrollBar",
				    xmScrollBarWidgetClass, w,
				    XmNorientation, XmVERTICAL,
				    NULL);
    XtAddCallback(dsw->sw.v_scroll, XmNvalueChangedCallback, VScrollCallback,
		  (XtPointer) dsw);
    XtAddCallback(dsw->sw.v_scroll, XmNdragCallback, VScrollCallback,
		  (XtPointer) dsw);


    dsw->sw.drawing_area =
	    XtVaCreateManagedWidget("drawingArea",
				    xmDrawingAreaWidgetClass, w, NULL);
    XtAddCallback(dsw->sw.drawing_area, XtNexposeCallback, DrawingAreaExpose,
		  (XtPointer) dsw);
    XtAddRawEventHandler(dsw->sw.drawing_area, 0, True,
			 DrawingAreaGraphicsExpose, (XtPointer) dsw);

    XmScrolledWindowSetAreas(w, dsw->sw.h_scroll, dsw->sw.v_scroll,
			     dsw->sw.drawing_area);
}

/* ARGSUSED */

static void Initialize(Widget request, Widget new, ArgList args, Cardinal *num_args)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) new;
    XGCValues gcVal;
    Bool inited;

    if (dsw->sw.area_width <= 0) dsw->sw.area_width = 8.5*72;
    if (dsw->sw.area_height <= 0) dsw->sw.area_height = 11*72;
    if (dsw->sw.scale <= 0) dsw->sw.scale = 1.0;
    dsw->sw.ctm_ptr = dsw->sw.ctm;
    dsw->sw.inv_ctm_ptr = dsw->sw.inv_ctm;
    dsw->sw.backing_pixmap = None;
    dsw->sw.feedback_pixmap = None;
    dsw->sw.window_gstate = 0;
    dsw->sw.backing_gstate = 0;
    dsw->sw.feedback_gstate = 0;
    dsw->sw.scrolling = False;
    dsw->sw.num_pending_expose = dsw->sw.pending_expose_size = 0;
    dsw->sw.pending_expose = NULL;
    dsw->sw.num_pending_dirty = dsw->sw.pending_dirty_size = 0;
    dsw->sw.pending_dirty = NULL;
    dsw->sw.num_current_drawing = dsw->sw.current_drawing_size = 0;
    dsw->sw.current_drawing = NULL;
    dsw->sw.num_prev_dirty_areas = dsw->sw.prev_dirty_areas_size = 0;
    dsw->sw.prev_dirty_areas = NULL;
    dsw->sw.drawing_stage = DSWStart;
    dsw->sw.work = 0;
    dsw->sw.big_pixmap = False;

    /* Set the initial dirty area to everything */

    dsw->sw.dirty_areas_size = 0;
    dsw->sw.dirty_areas = NULL;

    GrowRectList(&dsw->sw.dirty_areas, &dsw->sw.dirty_areas_size, 0, 1, 1);
    dsw->sw.num_dirty_areas = 1;
    LEFT(dsw->sw.dirty_areas) = 0.0;
    BOTTOM(dsw->sw.dirty_areas) = 0.0;
    WIDTH(dsw->sw.dirty_areas) = dsw->sw.area_width;
    HEIGHT(dsw->sw.dirty_areas) = dsw->sw.area_height;

    /* Make the scratch list have at least one element */

    dsw->sw.num_scratch = dsw->sw.scratch_size = 0;
    dsw->sw.scratch = NULL;
    GrowRectList(&dsw->sw.scratch, &dsw->sw.scratch_size, 0, 1, 1);

    /* Get the context */

    if (dsw->sw.context == NULL) {
	dsw->sw.context = XDPSGetSharedContext(XtDisplay(dsw));
    }

    /* Watch progress only works with pass-through event dispatching */

    if (dsw->sw.watch_progress &&
	XDPSSetEventDelivery(XtDisplay(dsw), dps_event_query) !=
		dps_event_pass_through) dsw->sw.watch_progress = False;

    if (_XDPSTestComponentInitialized(dsw->sw.context,
				      dps_init_bit_dsw, &inited) ==
	dps_status_unregistered_context) {
	XDPSRegisterContext(dsw->sw.context, False);
    }

    dsw->sw.use_saved_scroll = False;
    dsw->sw.context_inited = False;
    dsw->sw.doing_feedback = False;
    dsw->sw.feedback_displayed = False;

    CreateChildren(dsw);

    dsw->sw.ge_gc = XtGetGC(dsw->sw.drawing_area, 0, (XGCValues *) NULL);

    gcVal.graphics_exposures = False;
    dsw->sw.no_ge_gc = XtGetGC(dsw->sw.drawing_area, GCGraphicsExposures,
			       &gcVal);
}

static void Destroy(Widget widget)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) widget;

    if (dsw->sw.backing_pixmap != None) {
	XFreePixmap(XtDisplay(dsw), dsw->sw.backing_pixmap);
    }
    if (dsw->sw.feedback_pixmap != None) {
	XFreePixmap(XtDisplay(dsw), dsw->sw.feedback_pixmap);
    }

    if (dsw->sw.window_gstate != 0) {
	XDPSFreeContextGState(dsw->sw.context, dsw->sw.window_gstate);
    }
    if (dsw->sw.backing_gstate != 0) {
	XDPSFreeContextGState(dsw->sw.context, dsw->sw.backing_gstate);
    }
    if (dsw->sw.feedback_gstate != 0) {
	XDPSFreeContextGState(dsw->sw.context, dsw->sw.feedback_gstate);
    }

    if (dsw->sw.pending_expose != NULL) {
	XtFree((char *) dsw->sw.pending_expose);
    }
    if (dsw->sw.current_drawing != NULL) {
	XtFree((char *) dsw->sw.current_drawing);
    }
    if (dsw->sw.prev_dirty_areas != NULL) {
	XtFree((char *) dsw->sw.prev_dirty_areas);
    }
    if (dsw->sw.dirty_areas != NULL) XtFree((char *) dsw->sw.dirty_areas);
    if (dsw->sw.pending_dirty != NULL) XtFree((char *) dsw->sw.pending_dirty);
    if (dsw->sw.scratch != NULL) XtFree((char *) dsw->sw.scratch);

    XtReleaseGC(widget, dsw->sw.ge_gc);
    XtReleaseGC(widget, dsw->sw.no_ge_gc);
}

static void SetOriginAndGetTransform(DPSScrolledWindowWidget dsw)
{
    float psX, psY;

    ConvertToOrigPS(dsw, dsw->sw.origin_x, dsw->sw.origin_y, &psX, &psY);
    _DPSSWSetMatrixAndGetTransform(dsw->sw.context, psX, psY, dsw->sw.scale,
				   dsw->sw.origin_x, dsw->sw.origin_y,
				   dsw->sw.ctm, dsw->sw.inv_ctm,
				   &dsw->sw.x_offset, &dsw->sw.y_offset);
}

static void SetPixmapOrigin(DPSScrolledWindowWidget dsw)
{
    float psX, psY;

    ConvertToOrigPS(dsw, dsw->sw.origin_x, dsw->sw.origin_y, &psX, &psY);
    _DPSSWSetMatrix(dsw->sw.context, psX, psY, dsw->sw.scale,
		    dsw->sw.origin_x, dsw->sw.origin_y);
}

static void SetPixmapOffset(DPSScrolledWindowWidget dsw)
{
    int ox, oy;

    if (dsw->sw.pixmap_width <= (int) dsw->sw.drawing_area->core.width) ox = 0;
    else ox = -dsw->sw.origin_x;
    if (dsw->sw.pixmap_height <= (int) dsw->sw.drawing_area->core.height) {
	oy = dsw->sw.drawing_area->core.height;
    } else oy = dsw->sw.pixmap_height - dsw->sw.origin_y +
	    dsw->sw.drawing_area->core.height;
    
    DPSsetXoffset(dsw->sw.context, ox, oy);
}

static Boolean pixmapError;
static int (*oldHandler)(Display *, XErrorEvent *);

static int PixmapHandler(Display *dpy, XErrorEvent *error)
{
    if (error->error_code == BadAlloc &&
        error->request_code == X_CreatePixmap) {
        pixmapError = True;
        return 0;
    } else return (*oldHandler) (dpy, error);
}

static Pixmap AllocPixmap(DPSScrolledWindowWidget dsw, unsigned w, unsigned h)
{
    Pixmap p;
    unsigned int dBytes;
    Widget wid = dsw->sw.drawing_area;
    unsigned area = (w * h);

    if (dsw->sw.pixmap_limit > 0) {
	if (area > (unsigned)dsw->sw.pixmap_limit) return None;
    } else if (dsw->sw.pixmap_limit < 0
       && area > (unsigned)(dsw->sw.unscaled_width * dsw->sw.unscaled_height)
       && area > (unsigned)(wid->core.width * wid->core.height)) return None;

    if (dsw->sw.absolute_pixmap_limit > 0) {
	dBytes = (wid->core.depth + 7) / 8;	/* Convert into bytes */
	if (area * dBytes > (unsigned)dsw->sw.absolute_pixmap_limit * 1024) {
	    return None;
	}
    }

    XSync(XtDisplay(dsw), False);
    oldHandler = XSetErrorHandler(PixmapHandler);
    pixmapError = False;
    p = XCreatePixmap(XtDisplay(dsw), XtWindow(dsw->sw.drawing_area), w, h,
		      wid->core.depth);
    XSync(XtDisplay(dsw), False);
    (void) XSetErrorHandler(oldHandler);
    if (pixmapError) return None;
    else return p;
}

static void CreateBackingPixmap(DPSScrolledWindowWidget dsw)
{
    Pixmap p;

    if (dsw->sw.document_size_pixmaps) {
	dsw->sw.pixmap_width =
		MAX(CEIL(dsw->sw.drawing_width),
		    (int) dsw->sw.drawing_area->core.width);
	dsw->sw.pixmap_height =
		MAX(CEIL(dsw->sw.drawing_height),
		    (int) dsw->sw.drawing_area->core.height);

	p = dsw->sw.backing_pixmap =
		AllocPixmap(dsw, dsw->sw.pixmap_width, dsw->sw.pixmap_height);
	if (p != None) {
	    dsw->sw.big_pixmap =
		    dsw->sw.pixmap_width >
			    (int) dsw->sw.drawing_area->core.width ||
		    dsw->sw.pixmap_height >
			    (int) dsw->sw.drawing_area->core.height;
	    return;
	}
    }
    dsw->sw.big_pixmap = False;
    dsw->sw.pixmap_width = dsw->sw.drawing_area->core.width;
    dsw->sw.pixmap_height = dsw->sw.drawing_area->core.height;
    p = dsw->sw.backing_pixmap =
	    AllocPixmap(dsw, dsw->sw.pixmap_width, dsw->sw.pixmap_height);
    if (p == None) dsw->sw.pixmap_width = dsw->sw.pixmap_height = 0;
}

static void FreeBackingPixmap(DPSScrolledWindowWidget dsw)
{
    if (dsw->sw.backing_pixmap == None) return;
    XFreePixmap(XtDisplay(dsw), dsw->sw.backing_pixmap);
    dsw->sw.backing_pixmap = None;
    dsw->sw.big_pixmap = False;
    dsw->sw.pixmap_width = dsw->sw.pixmap_height = 0;
    XDPSFreeContextGState(dsw->sw.context, dsw->sw.backing_gstate);
}    

static void SetDrawingAreaPosition(
    DPSScrolledWindowWidget dsw,
    float ix,
    float iy,
    int vx,
    int vy,
    Boolean setOrigin)
{
    int xoff, yoff;
    int hSize, vSize;
    float scrollX, scrollY;

    /* Convert ix, iy into X units */

    ix *= dsw->sw.drawing_width / dsw->sw.area_width;
    iy *= dsw->sw.drawing_height / dsw->sw.area_height;

    if ((int)dsw->sw.drawing_area->core.width >= CEIL(dsw->sw.drawing_width)) {
	/* The scaled width is narrower than the view window, so
	   center the picture and set scroll bar to be unscrollable */

	xoff = ((int) dsw->sw.drawing_area->core.width -
		CEIL(dsw->sw.drawing_width))
		/ 2.0;
	scrollX = 0;
	hSize = CEIL(dsw->sw.drawing_width);
    } else {
	/* The scaled width is larger than the view window, so
	   turn on the scroll bar, and set up its maximum and
	   slider size.  Do this by converting the image offset into X
	   coordinates and subtracting the view offset */

	scrollX = ix - vx;
	scrollX = MAX(scrollX, 0);
	scrollX = MIN(scrollX, CEIL(dsw->sw.drawing_width) -
		               (int) dsw->sw.drawing_area->core.width);
	hSize = dsw->sw.drawing_area->core.width;
	xoff = -(int) (scrollX + 0.5);
    }

    /* Now do the same thing for the height.  We want to compute the offset
       relative to the lower left corner, but X coordinates are relative
       to the upper left, so the drawing height must be added in.  Also, since
       the coordinates go in the other direction, the view offset must be
       added, not subtracted. */

    if ((int) dsw->sw.drawing_area->core.height >=
	CEIL(dsw->sw.drawing_height)) {
	yoff = ((int) dsw->sw.drawing_area->core.height -
		CEIL(dsw->sw.drawing_height)) / 2.0;
	scrollY = CEIL(dsw->sw.drawing_height) -
		(int) dsw->sw.drawing_area->core.height;
	vSize = CEIL(dsw->sw.drawing_height);
    } else {
	scrollY = iy + vy - (int) dsw->sw.drawing_area->core.height;
	scrollY = MAX(scrollY, 0);
	scrollY = MIN(scrollY, CEIL(dsw->sw.drawing_height) -
		               (int) dsw->sw.drawing_area->core.height);
	vSize = dsw->sw.drawing_area->core.height;
	yoff = -(int) (scrollY + 0.5);
    }

    /* Update the scrollbars */
    dsw->sw.scroll_x = (int) (scrollX + 0.5); 
    dsw->sw.scroll_y = (int) (CEIL(dsw->sw.drawing_height) -
			      (int) dsw->sw.drawing_area->core.height -
			      scrollY + 0.5);

    yoff = dsw->sw.drawing_area->core.height - yoff;

    dsw->sw.scroll_h_value = dsw->sw.scroll_x;
    dsw->sw.scroll_h_size = hSize;
    dsw->sw.scroll_h_max = CEIL(dsw->sw.drawing_width);
    dsw->sw.scroll_v_value = dsw->sw.scroll_y;
    dsw->sw.scroll_v_size = vSize;
    dsw->sw.scroll_v_max = CEIL(dsw->sw.drawing_height);

    if (!dsw->sw.application_scrolling) {
	XtVaSetValues(dsw->sw.h_scroll, XmNmaximum, dsw->sw.scroll_h_max,
		      XmNvalue, dsw->sw.scroll_x, XmNsliderSize, hSize, NULL);
	XtVaSetValues(dsw->sw.v_scroll, XmNmaximum, dsw->sw.scroll_v_max,
		      XmNvalue, dsw->sw.scroll_y, XmNsliderSize, vSize, NULL);
    }
    
    if (setOrigin) {
	/* Set the origin in the X window to reflect the new location */
	dsw->sw.origin_x = xoff;
	dsw->sw.origin_y = yoff;
    }
}

static void DrawBackground(
    DPSScrolledWindowWidget dsw,
    DSWDrawableType which)
{
    DSWExposeCallbackRec e;

    e.type = which;
    e.directions = DSWFinish;
    e.results = DSWUndefined;
    e.first = True;
    e.background = True;
    if (which == DSWBackingPixmap) {
	e.drawable = dsw->sw.backing_pixmap;
	e.gstate = dsw->sw.backing_gstate;
    } else if (which == DSWFeedbackPixmap) {
	e.drawable = dsw->sw.feedback_pixmap;
	e.gstate = dsw->sw.feedback_gstate;
    } else {
	e.drawable = XtWindow(dsw->sw.drawing_area);
	e.gstate = dsw->sw.window_gstate;
    }
    e.context = dsw->sw.context;

    SimplifyRects(dsw->sw.current_drawing, &dsw->sw.num_current_drawing);

    XDPSSetContextGState(dsw->sw.context, e.gstate);
    _DPSSWSetRectViewClip(dsw->sw.context, dsw->sw.current_drawing,
				  dsw->sw.num_current_drawing * 4);
    
    e.rects = dsw->sw.current_drawing;
    e.rect_count = dsw->sw.num_current_drawing;

    do {
	XtCallCallbackList((Widget) dsw, dsw->sw.background_callback,
			   (XtPointer) &e);
	if (e.results == DSWUndefined) {
	    if (XtHasCallbacks((Widget) dsw, XtNbackgroundCallback) !=
		XtCallbackHasNone) {
		XtAppWarningMsg(XtWidgetToApplicationContext((Widget) dsw),
				"returnError", "backgroundCallback",
				"DPSScrollError",
				"Background callback did not set result field",
				(String *) NULL, (Cardinal *) NULL);
	    }
	    e.results = DSWFinished;
	}
    } while (e.results != DSWFinished);


    DPSinitviewclip(dsw->sw.context);
}

static void ClipToDrawingSize(
    DPSScrolledWindowWidget dsw,
    DSWDrawableType which)
{
    int i;
    float r[4];

    if (CEIL(dsw->sw.drawing_width) >= (int) dsw->sw.drawing_area->core.width &&
	CEIL(dsw->sw.drawing_height) >= (int) dsw->sw.drawing_area->core.height) return;

    /* Copy current drawing area to scratch */

    GrowRectList(&dsw->sw.scratch, &dsw->sw.scratch_size, 0,
		 dsw->sw.num_current_drawing, 1);
    dsw->sw.num_scratch = dsw->sw.num_current_drawing;
    for (i = 0; i < dsw->sw.num_current_drawing * 4; i++) {
	dsw->sw.scratch[i] = dsw->sw.current_drawing[i];
    }

    /* Construct a rectangle of the drawing area */

    ConvertToPS(dsw, dsw->sw.origin_x + DELTA, dsw->sw.origin_y - DELTA,
		r, r+1);
    ConvertToPS(dsw, dsw->sw.origin_x + CEIL(dsw->sw.drawing_width) - DELTA,
		dsw->sw.origin_y - CEIL(dsw->sw.drawing_height) + DELTA,
		r+2, r+3);
    r[2] -= r[0];
    r[3] -= r[1];

    /* Subtract the area of the drawing from the current drawing list */

    SubtractRects(&dsw->sw.current_drawing, &dsw->sw.current_drawing_size,
		  &dsw->sw.num_current_drawing, r, 1);

    if (dsw->sw.num_current_drawing != 0) {
	DrawBackground(dsw, which);
	/* Now intersect the rectangle with the current drawing area */
	IntersectRects(&dsw->sw.scratch, &dsw->sw.scratch_size,
		  &dsw->sw.num_scratch, r, 1);
	/* If nothing left, we won't be drawing anything more, so
	   synchronize.  Otherwise wait until we're done drawing */
	if (dsw->sw.num_scratch == 0) DPSWaitContext(dsw->sw.context);
    }

    /* Copy scratch back into the current drawing list */
    GrowRectList(&dsw->sw.current_drawing, &dsw->sw.current_drawing_size, 0,
		 dsw->sw.num_scratch, 1);
    dsw->sw.num_current_drawing = dsw->sw.num_scratch;
    for (i = 0; i < dsw->sw.num_scratch * 4; i++) {
	dsw->sw.current_drawing[i] = dsw->sw.scratch[i];
    }
}    

static DSWResults ClipAndDraw(
    DPSScrolledWindowWidget dsw,
    DSWDrawableType which,
    DSWDirections howMuch,
    Boolean first)
{
    DSWExposeCallbackRec e;

    e.type = which;
    e.directions = howMuch;
    e.results = DSWUndefined;
    e.first = first;
    e.background = False;
    if (which == DSWBackingPixmap) {
	e.drawable = dsw->sw.backing_pixmap;
	e.gstate = dsw->sw.backing_gstate;
    } else if (which == DSWFeedbackPixmap) {
	e.drawable = dsw->sw.feedback_pixmap;
	e.gstate = dsw->sw.feedback_gstate;
    } else {
	e.drawable = XtWindow(dsw->sw.drawing_area);
	e.gstate = dsw->sw.window_gstate;
    }
    e.context = dsw->sw.context;

    if (first) {
	XDPSSetContextGState(dsw->sw.context, e.gstate);
	if (howMuch != DSWAbort) {
	    ClipToDrawingSize(dsw, which);
	    SimplifyRects(dsw->sw.current_drawing,
			  &dsw->sw.num_current_drawing);
	    if (dsw->sw.num_current_drawing == 0) return DSWFinished;
	    _DPSSWSetRectViewClip(dsw->sw.context, dsw->sw.current_drawing,
				  dsw->sw.num_current_drawing * 4);
	}
    }
    
    e.rects = dsw->sw.current_drawing;
    e.rect_count = dsw->sw.num_current_drawing;

    do {
	XtCallCallbackList((Widget) dsw, dsw->sw.expose_callback,
			   (XtPointer) &e);
	if (e.results == DSWUndefined) {
	    if (XtHasCallbacks((Widget) dsw,
			       XtNexposeCallback) != XtCallbackHasNone) {
		XtAppWarningMsg(XtWidgetToApplicationContext((Widget) dsw),
				"returnError", "exposeCallback",
				"DPSScrollError",
				"Expose callback did not set result field",
				(String *) NULL, (Cardinal *) NULL);
	    }
	    e.results = DSWFinished;
	}
    } while ((e.results != DSWFinished && howMuch == DSWFinish) ||
	     (e.results != DSWFinished && e.results != DSWAborted &&
	      howMuch == DSWAbortOrFinish) ||
	     (e.results != DSWAborted && howMuch == DSWAbort));

    if (e.results == DSWFinished) {
	DPSinitviewclip(dsw->sw.context);
	DPSWaitContext(dsw->sw.context);
    }
    return e.results;
}

static void SplitExposeEvent(
    DPSScrolledWindowWidget dsw,
    XExposeEvent *ev)
{
    float *r;
    float llx, lly, urx, ury;
    int xr[4];
    int i;
    int dx, dy;

    ComputeOffsets(dsw, &dx, &dy);

    /* Put the expose event into the scratch list */
    dsw->sw.num_scratch = 1;
    r = dsw->sw.scratch;
    ConvertToPS(dsw, ev->x + DELTA, ev->y + ev->height - DELTA, &llx, &lly);
    ConvertToPS(dsw, ev->x + ev->width - DELTA, ev->y + DELTA, &urx, &ury);
    LEFT(r) = llx;
    BOTTOM(r) = lly;
    WIDTH(r) = urx - llx;
    HEIGHT(r) = ury - lly;

    /* Subtract the dirty area from the exposed area and copy the resulting
       area to the window */
    SubtractRects(&dsw->sw.scratch, &dsw->sw.scratch_size,
		  &dsw->sw.num_scratch,
		  dsw->sw.dirty_areas, dsw->sw.num_dirty_areas);
    for (i = 0; i < dsw->sw.num_scratch; i++) {
	r = dsw->sw.scratch + 4*i;
	ConvertToX(dsw, LEFT(r), TOP(r), xr, xr+1);
	ConvertToX(dsw, RIGHT(r), BOTTOM(r), xr+2, xr+3);
	xr[2] -= xr[0];
	xr[3] -= xr[1];
	XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
		  XtWindow(dsw->sw.drawing_area), dsw->sw.no_ge_gc,
		  xr[0] + dx, xr[1] + dy, xr[2], xr[3], xr[0], xr[1]);
    }

    /* Now do it again, but intersect the exposed area with the dirty area
       and add the intersection to the pending list */

    dsw->sw.num_scratch = 1;
    r = dsw->sw.scratch;
    LEFT(r) = llx;
    BOTTOM(r) = lly;
    WIDTH(r) = urx - llx;
    HEIGHT(r) = ury - lly;
    IntersectRects(&dsw->sw.scratch, &dsw->sw.scratch_size,
		   &dsw->sw.num_scratch,
		   dsw->sw.dirty_areas, dsw->sw.num_dirty_areas);
    AddUserSpaceRectsToPending(dsw, dsw->sw.scratch, dsw->sw.num_scratch);
}

/* ARGSUSED */

static Bool CheckWatchProgressEvent(
    Display *dpy,
    XEvent *e,
    char *arg)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) arg;
    
    return (e->xany.window == dsw->sw.backing_pixmap &&
	    (e->type == GraphicsExpose || e->type == NoExpose)) ||
	   (e->xany.window == XtWindow(dsw->sw.drawing_area) &&
	    e->type == Expose);
}

static void CopyWindowToBackingPixmap(
    DPSScrolledWindowWidget dsw)
{
    int llx, lly, urx, ury;
    XEvent e;
    XExposeEvent *ev = (XExposeEvent *) &e;
    int i;
    float *r;
    int copies = 0;
    int dx, dy;

    ComputeOffsets(dsw, &dx, &dy);

    for (i = 0; i < dsw->sw.num_dirty_areas; i++) {
	r = dsw->sw.dirty_areas + i*4;
	ConvertToX(dsw, LEFT(r), BOTTOM(r), &llx, &lly);
	ConvertToX(dsw, RIGHT(r), TOP(r), &urx, &ury);
	XCopyArea(XtDisplay(dsw), XtWindow(dsw->sw.drawing_area),
		  dsw->sw.backing_pixmap, dsw->sw.ge_gc,
		  llx, ury, urx-llx, lly-ury, llx + dx, ury + dy);
	copies++;
    }

    /* Unfortunately the Intrinsics won't let us get ahold of the the
       GraphicsExpose events for the pixmap, so we have to wait and
       get them the old fashioned way.  Yuck. */

    while (copies > 0) {
	XIfEvent(XtDisplay(dsw), &e, CheckWatchProgressEvent,
		 (char *) dsw);
	if (e.type == Expose) {
	    SplitExposeEvent(dsw, ev);
	    continue;
	} else if (e.type == GraphicsExpose) {
	    ev->x -= dx;
	    ev->y -= dy;
	    AddExposureToPending(dsw, ev);
	    if (ev->count == 0) copies--;
	} else copies--;		    /* NoExpose */
    }
    CopyRectsToCurrentDrawing(dsw, dsw->sw.pending_dirty,
			     dsw->sw.num_pending_dirty);

    dsw->sw.num_pending_dirty = 0;
    dsw->sw.num_pending_expose = 0;
    if (dsw->sw.num_current_drawing == 0) {
	dsw->sw.drawing_stage = DSWDone;
	dsw->sw.num_dirty_areas = 0;
    } else {
	/* The dirty area is now the intersection of the old dirty area and
	   the newly-created current drawing list */
	IntersectRects(&dsw->sw.dirty_areas, &dsw->sw.dirty_areas_size,
		       &dsw->sw.num_dirty_areas,
		       dsw->sw.current_drawing, dsw->sw.num_current_drawing);
    }	
}

/* Subtract the window area from the dirty area, and make the
   result be the new current drawing list */

static void SetCurrentDrawingToBackground(
    DPSScrolledWindowWidget dsw)
{
    int i;
    float r[4];

    ConvertToPS(dsw, 0 + DELTA, dsw->sw.drawing_area->core.height - DELTA,
		r, r+1);
    ConvertToPS(dsw, dsw->sw.drawing_area->core.width - DELTA, 0 + DELTA,
		r+2, r+3);
    r[2] -= r[0];
    r[3] -= r[1];

    SubtractRects(&dsw->sw.dirty_areas, &dsw->sw.dirty_areas_size,
		  &dsw->sw.num_dirty_areas, r, 1);

    GrowRectList(&dsw->sw.current_drawing, &dsw->sw.current_drawing_size,
		 0, dsw->sw.num_dirty_areas, 1);
    for (i = 0; i < 4 * dsw->sw.num_dirty_areas; i++) {
	dsw->sw.current_drawing[i] = dsw->sw.dirty_areas[i];
    }
    dsw->sw.num_current_drawing = dsw->sw.num_dirty_areas;
}

static void CopyPendingExpose(DPSScrolledWindowWidget dsw)
{
    int dx, dy;
    int i;
    int *r;

    ComputeOffsets(dsw, &dx, &dy);

    for (i = 0; i < dsw->sw.num_pending_expose; i++) {
	r = dsw->sw.pending_expose + 4*i;
	XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
		  XtWindow(dsw->sw.drawing_area),
		  dsw->sw.no_ge_gc,
		  LEFT(r) + dx, BOTTOM(r) + dy, WIDTH(r), HEIGHT(r),
		  LEFT(r), BOTTOM(r));
    }
    dsw->sw.num_pending_expose = dsw->sw.num_pending_dirty = 0;
}

static void UpdateWindowFromBackingPixmap(
    DPSScrolledWindowWidget dsw,
    float *rects,
    int n)
{
    int dx, dy;
    int llx, lly, urx, ury;
    int i;
    float *r;

    ComputeOffsets(dsw, &dx, &dy);

    for (i = 0; i < n; i++) {
	r = rects + 4*i;
	ConvertToX(dsw, LEFT(r), BOTTOM(r), &llx, &lly);
	ConvertToX(dsw, RIGHT(r), TOP(r), &urx, &ury);

	XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
		  XtWindow(dsw->sw.drawing_area),
		  dsw->sw.no_ge_gc,
		  llx+dx-1, ury+dy-1, urx-llx+2, lly-ury+2, llx-1, ury-1);
    }
}

static void UpdateWindowFromFeedbackPixmap(
    DPSScrolledWindowWidget dsw,
    float *rects,
    int n)
{
    int llx, lly, urx, ury;
    int i;
    float *r;

    for (i = 0; i < n; i++) {
	r = rects + (i * 4);
	ConvertToX(dsw, LEFT(r), BOTTOM(r), &llx, &lly);
	ConvertToX(dsw, RIGHT(r), TOP(r), &urx, &ury);

	XCopyArea(XtDisplay(dsw), dsw->sw.feedback_pixmap,
		  XtWindow(dsw->sw.drawing_area), dsw->sw.no_ge_gc,
		  llx-1, ury-1, urx-llx+2, lly-ury+2, llx-1, ury-1);
    }
}    

/* This is the heart of the drawing code; it does one piece of drawing.
   It can be called either directly or as a work procedure */

static Boolean DoDrawing(XtPointer clientData)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) clientData;
    DSWResults results;
    DSWDrawableType which;

    if (dsw->sw.drawing_stage == DSWStart && dsw->sw.watch_progress &&
	dsw->sw.backing_pixmap != None && dsw->sw.num_current_drawing == 0) {
	dsw->sw.drawing_stage = DSWDrewVisible;
	CopyWindowToBackingPixmap(dsw);
    }

    switch (dsw->sw.drawing_stage) {
	case DSWStart:
	case DSWDrawingVisible:
	    if (dsw->sw.watch_progress || dsw->sw.backing_pixmap == None) {
		which = DSWWindow;
	    } else which = DSWBackingPixmap;
	    results = ClipAndDraw(dsw, which, DSWDrawSome,
				  (dsw->sw.drawing_stage == DSWStart));
	    if (results == DSWFinished) {
		if (dsw->sw.watch_progress && dsw->sw.backing_pixmap != None) {
		    dsw->sw.drawing_stage = DSWDrewVisible;
		    CopyWindowToBackingPixmap(dsw);
		} else {
		    if (dsw->sw.minimal_drawing && dsw->sw.big_pixmap) {
			dsw->sw.drawing_stage = DSWDrewVisible;
			SetCurrentDrawingToBackground(dsw);
		    } else {
			dsw->sw.drawing_stage = DSWDone;
			dsw->sw.num_dirty_areas = 0;
		    }
		    if (dsw->sw.num_pending_expose != 0 &&
			dsw->sw.backing_pixmap != None) {
			CopyPendingExpose(dsw);
		    }
		}
	    } else dsw->sw.drawing_stage = DSWDrawingVisible;
	    break;

	case DSWDrewVisible:
	case DSWDrawingBackground:
	    results = ClipAndDraw(dsw, DSWBackingPixmap, DSWDrawSome,
				  (dsw->sw.drawing_stage == DSWDrewVisible));
	    if (results == DSWFinished) {
		dsw->sw.drawing_stage = DSWDone;
		dsw->sw.num_dirty_areas = 0;
	    }
	    else dsw->sw.drawing_stage = DSWDrawingBackground;
	    break;

	case DSWDone:
	    break;
    }

    if (dsw->sw.drawing_stage == DSWDone && dsw->sw.num_pending_dirty != 0) {
	CopyRectsToCurrentDrawing(dsw, dsw->sw.pending_dirty,
				  dsw->sw.num_pending_dirty);
	CopyRectsToDirtyArea(dsw, dsw->sw.pending_dirty,
			     dsw->sw.num_pending_dirty);
	dsw->sw.num_pending_dirty = 0;
	dsw->sw.num_pending_expose = 0;
	dsw->sw.drawing_stage = DSWStart;
    }
	
    if (dsw->sw.drawing_stage == DSWDone) {
	dsw->sw.work = 0;
	if (dsw->sw.watch_progress) {
	    /* Some of the background drawing may have been to areas that
	       are visible */
	    UpdateWindowFromBackingPixmap(dsw, dsw->sw.current_drawing,
					  dsw->sw.num_current_drawing);
	}
	dsw->sw.num_current_drawing = 0;
	if (dsw->sw.scrolling) {
	    dsw->sw.scrolling = False;
	    ScrollMoved(dsw);
	}
	return True;
    } else return False;
}

static void StartDrawing(DPSScrolledWindowWidget dsw)
{
    float r[4];

    CopyRectsToCurrentDrawing(dsw, dsw->sw.dirty_areas,
			      dsw->sw.num_dirty_areas);

    if (dsw->sw.watch_progress || dsw->sw.backing_pixmap == None) {
	/* Intersect the current drawing area with the pending dirty area 
	   (The pending dirty area represents the window exposures that
	   have happened so far) */
	IntersectRects(&dsw->sw.current_drawing, &dsw->sw.current_drawing_size,
		       &dsw->sw.num_current_drawing,
		       dsw->sw.pending_dirty, dsw->sw.num_pending_dirty);
	dsw->sw.num_pending_dirty = dsw->sw.num_pending_expose = 0;
    } else {
	if (!dsw->sw.big_pixmap || dsw->sw.minimal_drawing) {
	    /* Intersect the current drawing area to the window to start */
	    ConvertToPS(dsw, 0 + DELTA,
			dsw->sw.drawing_area->core.height - DELTA, r, r+1);
	    ConvertToPS(dsw, dsw->sw.drawing_area->core.width - DELTA,
			0 + DELTA, r+2, r+3);
	    r[2] -= r[0];
	    r[3] -= r[1];
	    IntersectRects(&dsw->sw.current_drawing,
			   &dsw->sw.current_drawing_size,
			   &dsw->sw.num_current_drawing, r, 1);
	}
    }

    if (dsw->sw.num_current_drawing == 0 && !dsw->sw.watch_progress) {
	dsw->sw.drawing_stage = DSWFinished;
	dsw->sw.num_dirty_areas = 0;
	return;
    }

    dsw->sw.drawing_stage = DSWStart;
    if (!DoDrawing((XtPointer) dsw)) {
	dsw->sw.work =
		XtAppAddWorkProc(XtWidgetToApplicationContext((Widget) dsw),
				 DoDrawing, (XtPointer) dsw);
    }
}

static void RedisplaySliver(DPSScrolledWindowWidget dsw, int deltaX, int deltaY)
{
    float r[8];
    int xr[8];
    int n;
    int xllx, xlly, xurx, xury;
    float llx, lly, urx, ury;

    /* If one of the deltas is 0, then the area to update is just a
       single rectangle. */
    if (deltaX == 0 || deltaY == 0) {
	if (deltaX == 0) {
	    /* Just a single horizontal rectangle */

	    xllx = 0;
	    xurx = dsw->sw.drawing_area->core.width;
	    if (deltaY > 0) {
		xlly = dsw->sw.drawing_area->core.height;
		xury = dsw->sw.drawing_area->core.height - deltaY;
	    } else {
		xlly = -deltaY;
		xury = 0;
	    }

	} else if (deltaY == 0) {
	    /* Just a single vertical rectangle */
	    xlly = dsw->sw.drawing_area->core.height;
	    xury = 0;
	    if (deltaX > 0) {
		xllx = dsw->sw.drawing_area->core.width - deltaX;
		xurx = dsw->sw.drawing_area->core.width;
	    } else {
		xllx = 0;
		xurx = -deltaX;
	    }
	}
	/* Convert the rectangle into PS coordinates */
	ConvertToPS(dsw, xllx + DELTA, xlly - DELTA, &llx, &lly);
	ConvertToPS(dsw, xurx - DELTA, xury + DELTA, &urx, &ury);
	r[0] = llx;
	r[1] = lly;
	r[2] = urx - llx;
	r[3] = ury - lly;
	xr[0] = xllx;
	xr[1] = xury;
	xr[2] = xurx - xllx;
	xr[3] = xlly - xury;
	n = 1;

    } else {
	/* Scrolling in both directions, so there are two rectangles.
	   It's easiest to do if we let them overlap; fortunately that is
	   legal!  First do the horizontal rectangle. */
	xllx = 0;
	xurx = dsw->sw.drawing_area->core.width;
	if (deltaY > 0) {
	    xlly = dsw->sw.drawing_area->core.height;
	    xury = dsw->sw.drawing_area->core.height - deltaY;
	} else {
	    xlly = -deltaY;
	    xury = 0;
	}
	ConvertToPS(dsw, xllx + DELTA, xlly - DELTA, &llx, &lly);
	ConvertToPS(dsw, xurx - DELTA, xury + DELTA, &urx, &ury);

	/* Store in point array */
	r[0] = llx;
	r[1] = lly;
	r[2] = urx - llx;
	r[3] = ury - lly;
	xr[0] = xllx;
	xr[1] = xury;
	xr[2] = xurx - xllx;
	xr[3] = xlly - xury;

	/* Now do vertical rectangle and store in point array */
	xlly = dsw->sw.drawing_area->core.height;
	xury = 0;
	if (deltaX > 0) {
	    xllx = dsw->sw.drawing_area->core.width - deltaX;
	    xurx = dsw->sw.drawing_area->core.width;
	} else {
	    xllx = 0;
	    xurx = -deltaX;
	}
	ConvertToPS(dsw, xllx + DELTA, xlly - DELTA, &llx, &lly);
	ConvertToPS(dsw, xurx - DELTA, xury + DELTA, &urx, &ury);
	r[4] = llx;
	r[5] = lly;
	r[6] = urx - llx;
	r[7] = ury - lly;
	xr[4] = xllx;
	xr[5] = xury;
	xr[6] = xurx - xllx;
	xr[7] = xlly - xury;
	n = 2;
    }

    AddRectsToDirtyArea(dsw, r, n);
    AddRectsToPending(dsw, xr, n);
    StartDrawing(dsw);
}

static void SetUpInitialPixmap(DPSScrolledWindowWidget dsw)
{
    float *r = dsw->sw.dirty_areas;
    int llx, lly, urx, ury;

    CreateBackingPixmap(dsw);
    if (dsw->sw.backing_pixmap != None) {
	XDPSSetContextDrawable(dsw->sw.context, dsw->sw.backing_pixmap,
			       dsw->sw.pixmap_height);

	SetPixmapOffset(dsw);
	SetPixmapOrigin(dsw);
	XDPSCaptureContextGState(dsw->sw.context, &dsw->sw.backing_gstate);

	if (dsw->sw.pixmap_width != CEIL(dsw->sw.drawing_width) ||
	    dsw->sw.pixmap_height != CEIL(dsw->sw.drawing_height)) {

	    /* Make the dirty area match the window */
	    if (dsw->sw.pixmap_width > (int)dsw->sw.drawing_area->core.width) {
		llx = dsw->sw.origin_x;
		urx = llx + CEIL(dsw->sw.drawing_width);
	    } else {
		llx = 0;
		urx = dsw->sw.drawing_area->core.width;
	    }
	    if (dsw->sw.pixmap_height >
		(int) dsw->sw.drawing_area->core.height) {
		lly = dsw->sw.origin_y;
		ury = dsw->sw.origin_y - CEIL(dsw->sw.drawing_height);
	    } else {
		lly = dsw->sw.drawing_area->core.height;
		ury = 0;
	    }
	    ConvertToPS(dsw, llx + DELTA, lly - DELTA, r, r+1);
	    ConvertToPS(dsw, urx - DELTA, ury + DELTA, r+2, r+3);
	    r[2] -= r[0];
	    r[3] -= r[1];
	    dsw->sw.num_dirty_areas = 1;
	}
	if (dsw->sw.doing_feedback) {
	    CopyRectsToCurrentDrawing(dsw, dsw->sw.dirty_areas,
				      dsw->sw.num_dirty_areas);
	    FinishDrawing(dsw);
	} else if (!dsw->sw.watch_progress) StartDrawing(dsw);
    }
}

/* ARGSUSED */

static void TimerStart(XtPointer clientData, XtIntervalId *id)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) clientData;

    if (dsw->sw.drawing_stage == DSWStart) StartDrawing(dsw);
}

static void SetUpInitialInformation(DPSScrolledWindowWidget dsw)
{
    int i;
    float llx, lly, urx, ury;
    float xScale, yScale;
    XExposeEvent ev;
    XStandardColormap colorCube, grayRamp;
    int match;

    /* If the context's colormap matches the widget's colormap, assume that
       everything is already set up right in the color cube department.  This
       allows an application to supply us with a custom color cube by
       installing it in the context before calling us */

    _DPSSWColormapMatch(dsw->sw.context,
			dsw->sw.drawing_area->core.colormap, &match);

    if (match) {
	XDPSSetContextParameters(dsw->sw.context, XtScreen(dsw),
				 dsw->sw.drawing_area->core.depth,
				 XtWindow(dsw->sw.drawing_area),
				 dsw->sw.drawing_area->core.height, NULL, NULL,
				 XDPSContextScreenDepth | XDPSContextDrawable);
    } else {
	grayRamp.colormap = colorCube.colormap =
		dsw->sw.drawing_area->core.colormap;

	XDPSCreateStandardColormaps(XtDisplay(dsw), XtWindow(dsw),
				    (Visual *) NULL, 0, 0, 0, 0,
				    &colorCube, &grayRamp, False);

	XDPSSetContextParameters(dsw->sw.context, XtScreen(dsw),
				 dsw->sw.drawing_area->core.depth,
				 XtWindow(dsw->sw.drawing_area),
				 dsw->sw.drawing_area->core.height,
				 (XDPSStandardColormap *) &colorCube,
				 (XDPSStandardColormap *) &grayRamp,
				 XDPSContextScreenDepth | XDPSContextDrawable |
				 XDPSContextRGBMap | XDPSContextGrayMap);
    }

    DPSinitgraphics(dsw->sw.context);
    _DPSSWGetTransform(dsw->sw.context, dsw->sw.ctm, dsw->sw.orig_inv_ctm);
    dsw->sw.x_offset = 0;
    dsw->sw.y_offset = dsw->sw.drawing_area->core.height;
    for (i = 0; i < 6; i++) dsw->sw.inv_ctm[i] = dsw->sw.orig_inv_ctm[i];

    ConvertToPS(dsw, 0.0, 100.0, &llx, &lly);
    ConvertToPS(dsw, 100.0, 0.0, &urx, &ury);
    xScale = ABS(100.0 / (urx - llx));
    yScale = ABS(100.0 / (ury - lly));

    if (dsw->sw.scale != 1.0) {
	DPSscale(dsw->sw.context, dsw->sw.scale, dsw->sw.scale);
	_DPSSWGetTransform(dsw->sw.context, dsw->sw.ctm, dsw->sw.inv_ctm);
    }
	
    dsw->sw.drawing_width = dsw->sw.area_width * xScale * dsw->sw.scale;
    dsw->sw.drawing_height = dsw->sw.area_height * yScale * dsw->sw.scale;

    dsw->sw.unscaled_width = dsw->sw.drawing_width / dsw->sw.scale + 1;
    dsw->sw.unscaled_height = dsw->sw.drawing_height / dsw->sw.scale + 1;

    if (!dsw->sw.use_saved_scroll) {
	dsw->sw.scroll_pic_x = dsw->sw.area_width / 2;
	dsw->sw.scroll_pic_y = dsw->sw.area_height / 2;
	dsw->sw.scroll_win_x = dsw->sw.drawing_area->core.width / 2;
	dsw->sw.scroll_win_y = dsw->sw.drawing_area->core.height / 2;
    }

    SetDrawingAreaPosition(dsw, dsw->sw.scroll_pic_x, dsw->sw.scroll_pic_y,
			   dsw->sw.scroll_win_x, dsw->sw.scroll_win_y, True);
    SetOriginAndGetTransform(dsw);
    XDPSCaptureContextGState(dsw->sw.context, &dsw->sw.window_gstate);

    dsw->sw.drawing_stage = DSWStart;

    if (dsw->sw.use_backing_pixmap) SetUpInitialPixmap(dsw);

    if (dsw->sw.doing_feedback) return;

    if (dsw->sw.watch_progress || dsw->sw.backing_pixmap == None) {
	/* If watching progress or no pixmap, clear the window to ensure
	   that things get started */
	XClearArea(XtDisplay(dsw), XtWindow(dsw->sw.drawing_area),
		   0, 0, 0, 0, True);
	XSync(XtDisplay(dsw), False);
	if (dsw->sw.watch_progress && dsw->sw.watch_progress_delay > 0) {
	    /* Set a timer so that we start drawing if nothing is visible */
	    (void) XtAppAddTimeOut(XtWidgetToApplicationContext((Widget) dsw),
				   dsw->sw.watch_progress_delay,
				   TimerStart, (XtPointer) dsw);
	}
    } else {
	/* Put a synthetic expose event in the pending queue */
	ev.x = ev.y = 0;
	ev.width = dsw->sw.drawing_area->core.width;
	ev.height = dsw->sw.drawing_area->core.height;
	SplitExposeEvent(dsw, &ev);
    }
}

static void Realize(
    Widget w,
    XtValueMask *mask,
    XSetWindowAttributes *attr)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    DSWSetupCallbackRec setup;

    /* Let my parent do all the hard work */
    (*dpsScrolledWindowClassRec.core_class.superclass->core_class.realize)
	    (w, mask, attr);

    /* We delay calling the setup callback so that the caller can use
       XtAddCallback to add it */
    setup.context = dsw->sw.context;
    XtCallCallbackList((Widget) dsw, dsw->sw.setup_callback,
		       (XtPointer) &setup);

    /* Now, explicitly realize my children */
    XtRealizeWidget(dsw->sw.scrolled_window);

    /* Et voila, now we have windows! */
    SetUpInitialInformation(dsw);
}

static void AbortOrFinish(DPSScrolledWindowWidget dsw)
{
    DSWResults results;
    DSWDrawableType which;
    
    if (dsw->sw.work != 0) {
	XtRemoveWorkProc(dsw->sw.work);
	dsw->sw.work = 0;
    }

    switch (dsw->sw.drawing_stage) {
	case DSWStart:
	    return;
	    /* break; */

	case DSWDrawingVisible:
	    if (dsw->sw.watch_progress || dsw->sw.backing_pixmap == None) {
		which = DSWWindow;
	    } else which = DSWBackingPixmap;
	    results = ClipAndDraw(dsw, which, DSWAbortOrFinish, False);
	    if (results == DSWAborted) {
		dsw->sw.drawing_stage = DSWStart;
		if (dsw->sw.backing_pixmap == None || dsw->sw.watch_progress) {
		    /* Add the current drawing area back into the pending
		       expose area */
		    AddUserSpaceRectsToPending(dsw, dsw->sw.current_drawing,
					       dsw->sw.num_current_drawing);
		}
		return;
	    }
	    if (dsw->sw.watch_progress && dsw->sw.backing_pixmap != None) {
		dsw->sw.drawing_stage = DSWDrewVisible;
		CopyWindowToBackingPixmap(dsw);
		if (dsw->sw.num_current_drawing != 0) {
		    results = ClipAndDraw(dsw, DSWBackingPixmap,
					  DSWAbortOrFinish, True);
		}
		if (results == DSWAborted) {
		    dsw->sw.drawing_stage = DSWStart;
		    return;
		}
	    } else {
		if (dsw->sw.backing_pixmap != None) {
		    if (dsw->sw.num_pending_expose != 0) {
			CopyPendingExpose(dsw);
		    }

		    if (dsw->sw.minimal_drawing && dsw->sw.big_pixmap) {
			dsw->sw.drawing_stage = DSWDrewVisible;
			SetCurrentDrawingToBackground(dsw);
			results = ClipAndDraw(dsw, which,
					      DSWAbortOrFinish, True);
			if (results == DSWAborted) {
			    dsw->sw.drawing_stage = DSWStart;
			    return;
			}
		    }
		}
	    }
	    break;

	case DSWDrewVisible:
	case DSWDrawingBackground:
	    results = ClipAndDraw(dsw, DSWBackingPixmap, DSWAbortOrFinish,
				  (dsw->sw.drawing_stage == DSWDrewVisible));
	    if (results == DSWAborted) {
		dsw->sw.drawing_stage = DSWStart;
		return;
	    }
	    break;

	case DSWDone:
	    break;
    }

    dsw->sw.drawing_stage = DSWDone;
    dsw->sw.num_dirty_areas = 0;
    return;
}

static void AbortDrawing(DPSScrolledWindowWidget dsw)
{
    DSWDrawableType which;

    if (dsw->sw.work != 0) {
	XtRemoveWorkProc(dsw->sw.work);
	dsw->sw.work = 0;
    }

    switch (dsw->sw.drawing_stage) {
	case DSWStart:
	case DSWDone:
	case DSWDrewVisible:
	    break;

	case DSWDrawingVisible:
	    if (dsw->sw.watch_progress || dsw->sw.backing_pixmap == None) {
		which = DSWWindow;
	    } else which = DSWBackingPixmap;
	    (void) ClipAndDraw(dsw, which, DSWAbort, False);
	    break;

	case DSWDrawingBackground:
	    (void) ClipAndDraw(dsw, DSWBackingPixmap, DSWAbort, False);
	    break;

    }

    dsw->sw.num_pending_expose = dsw->sw.num_pending_dirty = 0;
}

static void FinishDrawing(DPSScrolledWindowWidget dsw)
{
    DSWDrawableType which;
    
    if (dsw->sw.work != 0) {
	XtRemoveWorkProc(dsw->sw.work);
	dsw->sw.work = 0;
    }

    switch (dsw->sw.drawing_stage) {
	case DSWStart:
	case DSWDrawingVisible:
	    if (dsw->sw.watch_progress || dsw->sw.backing_pixmap == None) {
		which = DSWWindow;
	    } else which = DSWBackingPixmap;
	    if (dsw->sw.drawing_stage == DSWStart) {
		ClipToDrawingSize(dsw, which);
		if (dsw->sw.num_current_drawing == 0) return;
	    }
	    (void) ClipAndDraw(dsw, which, DSWFinish,
			       dsw->sw.drawing_stage == DSWStart);
	    if (dsw->sw.watch_progress && dsw->sw.backing_pixmap != None) {
		dsw->sw.drawing_stage = DSWDrewVisible;
		CopyWindowToBackingPixmap(dsw);
		if (dsw->sw.num_current_drawing != 0) {
		    (void) ClipAndDraw(dsw, DSWBackingPixmap, DSWFinish, True);
		}
	    } else {
		if (dsw->sw.backing_pixmap != None) {
		    if (dsw->sw.num_pending_expose != 0) {
			CopyPendingExpose(dsw);
		    }

		    if (dsw->sw.minimal_drawing && dsw->sw.big_pixmap) {
			dsw->sw.drawing_stage = DSWDrewVisible;
			SetCurrentDrawingToBackground(dsw);
			(void) ClipAndDraw(dsw, which, DSWFinish, True);
		    }
		}
	    }
	    break;

	case DSWDrewVisible:
	case DSWDrawingBackground:
	     (void) ClipAndDraw(dsw, DSWBackingPixmap, DSWFinish,
				(dsw->sw.drawing_stage == DSWDrewVisible));
	    break;

	case DSWDone:
	    break;
    }

    dsw->sw.drawing_stage = DSWDone;
    dsw->sw.num_dirty_areas = 0;
}

static void DoScroll(
    DPSScrolledWindowWidget dsw,
    int deltaX, int deltaY)
{
    /* Set the PS origin in the X window to the new settings of 
       the scrollbars */
    dsw->sw.origin_x -= deltaX;
    dsw->sw.origin_y -= deltaY;

    /* Update the graphics states for the window and backing pixmap to
       reflect new origin */
    (void) XDPSSetContextGState(dsw->sw.context, dsw->sw.window_gstate);
    SetOriginAndGetTransform(dsw);
    (void) XDPSUpdateContextGState(dsw->sw.context, dsw->sw.window_gstate);

    if (dsw->sw.backing_pixmap != None && !dsw->sw.big_pixmap) {
	(void) XDPSSetContextGState(dsw->sw.context, dsw->sw.backing_gstate);
	SetPixmapOrigin(dsw);
	(void) XDPSUpdateContextGState(dsw->sw.context,
				       dsw->sw.backing_gstate);
    }

    /* Update the stored position of the scroll bars */
    dsw->sw.scroll_x += deltaX;
    dsw->sw.scroll_y += deltaY;
}

static void ShiftPendingExpose(
    DPSScrolledWindowWidget dsw,
    int deltaX, int deltaY)
{
    int i;
    int *r;

    for (i = 0; i < dsw->sw.num_pending_expose; i++) {
	r = dsw->sw.pending_expose + 4*i;
	r[0] -= deltaX;
	r[1] -= deltaY;
    }
}

static void HScrollCallback(
    Widget w,
    XtPointer clientData, XtPointer callData)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) clientData;
    XmScrollBarCallbackStruct *sb = (XmScrollBarCallbackStruct *) callData;

    if (!dsw->sw.application_scrolling) {
	dsw->sw.scroll_h_value = sb->value;
	ScrollMoved(dsw);
    }
}

static void VScrollCallback(
    Widget w,
    XtPointer clientData, XtPointer callData)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) clientData;
    XmScrollBarCallbackStruct *sb = (XmScrollBarCallbackStruct *) callData;

    if (!dsw->sw.application_scrolling) {
	dsw->sw.scroll_v_value = sb->value;
	ScrollMoved(dsw);
    }
}

/* ARGSUSED */

static void ScrollMoved(DPSScrolledWindowWidget dsw)
{
    int deltaX, deltaY;

    /* If we haven't started drawing yet, it must be because we're waiting
       for GraphicsExpose events.  Delay scrolling until they come in */
    if (dsw->sw.scrolling && dsw->sw.drawing_stage == DSWStart) return;

    /* Calculate the delta in the scrolling */
    deltaX = dsw->sw.scroll_h_value - dsw->sw.scroll_x;
    deltaY = dsw->sw.scroll_v_value - dsw->sw.scroll_y;

    /* If there is some scrolling to do, then scroll the pixmap and
       copy the pixmap to the window */
    if (deltaX == 0 && deltaY == 0) return;

    ShiftPendingExpose(dsw, deltaX, deltaY);

    AbortOrFinish(dsw);

    DoScroll(dsw, deltaX, deltaY);

    if (!dsw->sw.big_pixmap) {
	/* Copy visible area to new location */
	if (dsw->sw.backing_pixmap != None) {
	    XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
		      dsw->sw.backing_pixmap, dsw->sw.no_ge_gc,
		      deltaX, deltaY, dsw->sw.drawing_area->core.width,
		      dsw->sw.drawing_area->core.height, 0, 0);
	    if (!dsw->sw.doing_feedback || dsw->sw.feedback_pixmap == None) {
		XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
			  XtWindow(dsw->sw.drawing_area), dsw->sw.no_ge_gc,
			  0, 0, dsw->sw.drawing_area->core.width,
			  dsw->sw.drawing_area->core.height, 0, 0);
	    }
	    RedisplaySliver(dsw, deltaX, deltaY);
	} else {
	    if (!dsw->sw.doing_feedback || dsw->sw.feedback_pixmap == None) {
		XCopyArea(XtDisplay(dsw), XtWindow(dsw->sw.drawing_area),
			  XtWindow(dsw->sw.drawing_area), dsw->sw.ge_gc,
			  deltaX, deltaY, dsw->sw.drawing_area->core.width,
			  dsw->sw.drawing_area->core.height, 0, 0);
	    }
	    if (dsw->sw.doing_feedback) RedisplaySliver(dsw, deltaX, deltaY);
	    else dsw->sw.drawing_stage = DSWStart;
	}
    }

    if (dsw->sw.doing_feedback) {
	float *r;

	FinishDrawing(dsw);

	r = dsw->sw.prev_dirty_areas;
	ConvertToPS(dsw, 0 + DELTA, dsw->sw.drawing_area->core.height - DELTA,
		    r, r+1);
	ConvertToPS(dsw, dsw->sw.drawing_area->core.width - DELTA, 0 + DELTA,
		    r+2, r+3);
	r[2] -= r[0];
	r[3] -= r[1];
	dsw->sw.num_prev_dirty_areas = 1;

	if (dsw->sw.feedback_pixmap != None) {
	    XDPSSetContextDrawable(dsw->sw.context, dsw->sw.feedback_pixmap,
				   dsw->sw.drawing_area->core.height);
	    SetPixmapOrigin(dsw);
	    XDPSCaptureContextGState(dsw->sw.context,
				     &dsw->sw.feedback_gstate);

	    /* Initialize the feedback pixmap with a copy of the drawing */
	    if (dsw->sw.backing_pixmap != None) {
		CopyToFeedbackPixmap(dsw, dsw->sw.prev_dirty_areas,
				     dsw->sw.num_prev_dirty_areas);
	    } else {
		CopyRectsToCurrentDrawing(dsw, dsw->sw.prev_dirty_areas,
					  dsw->sw.num_prev_dirty_areas);
		(void) ClipAndDraw(dsw, DSWFeedbackPixmap, DSWFinish, True);
	    }
	    if (!dsw->sw.feedback_displayed) {
		XCopyArea(XtDisplay(dsw), dsw->sw.feedback_pixmap,
			  XtWindow(dsw->sw.drawing_area),
			  dsw->sw.no_ge_gc, 0, 0, 
			  dsw->sw.drawing_area->core.width,
			  dsw->sw.drawing_area->core.height, 0, 0);
	    }
	}

	if (dsw->sw.feedback_displayed) {
	    CallFeedbackCallback(dsw, dsw->sw.prev_dirty_areas,
				 dsw->sw.num_prev_dirty_areas);
	}
	if (dsw->sw.feedback_pixmap != None) {
	    UpdateWindowFromFeedbackPixmap(dsw, dsw->sw.prev_dirty_areas,
					   dsw->sw.num_prev_dirty_areas);
	}
	dsw->sw.num_pending_dirty = dsw->sw.num_pending_expose = 0;

    } else {
	if (dsw->sw.backing_pixmap != None &&
	    dsw->sw.drawing_stage == DSWDone) {
	    ComputeOffsets(dsw, &deltaX, &deltaY);
	    if (!dsw->sw.big_pixmap) dsw->sw.scrolling = True;

	    XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
		      XtWindow(dsw->sw.drawing_area), dsw->sw.no_ge_gc,
		      deltaX, deltaY,
		      dsw->sw.drawing_area->core.width,
		      dsw->sw.drawing_area->core.height, 0, 0);
	} else dsw->sw.scrolling = True;
    }
}

static void AddExposureToPending(
    DPSScrolledWindowWidget dsw,
    XExposeEvent *ev)
{
    float *f;
    int *i;

    if (dsw->sw.backing_pixmap == None || dsw->sw.watch_progress ||
	dsw->sw.doing_feedback) {
	GrowRectList(&dsw->sw.pending_dirty, &dsw->sw.pending_dirty_size,
		     dsw->sw.num_pending_dirty, 1, 1);

	f = dsw->sw.pending_dirty + (dsw->sw.num_pending_dirty * 4);
	ConvertToPS(dsw, ev->x + DELTA, ev->y + ev->height - DELTA, f, f+1);
	ConvertToPS(dsw, ev->x + ev->width - DELTA, ev->y + DELTA, f+2, f+3);
	f[2] -= f[0];
	f[3] -= f[1];
	dsw->sw.num_pending_dirty++;
    }

    GrowIntRectList(&dsw->sw.pending_expose, &dsw->sw.pending_expose_size,
		    dsw->sw.num_pending_expose, 1, 1);

    i = dsw->sw.pending_expose + (dsw->sw.num_pending_expose * 4);
    i[0] = ev->x;
    i[1] = ev->y;
    i[2] = ev->width;
    i[3] = ev->height;
    dsw->sw.num_pending_expose++;
}

static void AddRectsToPending(
    DPSScrolledWindowWidget dsw,
    int *newRect,
    int n)
{
    int *r;
    int i;
    float *f;

    if (dsw->sw.backing_pixmap == None || dsw->sw.watch_progress) {
	GrowRectList(&dsw->sw.pending_dirty, &dsw->sw.pending_dirty_size,
		     dsw->sw.num_pending_dirty, n, 1);

	for (i = 0; i < n; i++) {
	    f = dsw->sw.pending_dirty + (dsw->sw.num_pending_dirty * 4);
	    r = newRect + (i * 4);
	    ConvertToPS(dsw, r[0] + DELTA, r[1] + r[3] - DELTA, f, f+1);
	    ConvertToPS(dsw, r[0] + r[2] - DELTA, r[1] + DELTA, f+2, f+3);
	    f[2] -= f[0];
	    f[3] -= f[1];
	    dsw->sw.num_pending_dirty++;
	}
    }

    GrowIntRectList(&dsw->sw.pending_expose, &dsw->sw.pending_expose_size,
		    dsw->sw.num_pending_expose, n, 1);

    r = dsw->sw.pending_expose + (dsw->sw.num_pending_expose * 4);
    for (i = 0; i < 4*n; i++) r[i] = newRect[i];
    dsw->sw.num_pending_expose += n;
}

static void AddUserSpaceRectsToPending(
    DPSScrolledWindowWidget dsw,
    float *newRect,
    int n)
{
    int *r;
    int i;
    float *f;

    if (dsw->sw.backing_pixmap == None || dsw->sw.watch_progress) {
	GrowRectList(&dsw->sw.pending_dirty, &dsw->sw.pending_dirty_size,
		     dsw->sw.num_pending_dirty, n, 1);

	f = dsw->sw.pending_dirty + (dsw->sw.num_pending_dirty * 4);
	for (i = 0; i < 4*n; i++) f[i] = newRect[i];
	dsw->sw.num_pending_dirty += n;
    }

    GrowIntRectList(&dsw->sw.pending_expose, &dsw->sw.pending_expose_size,
		    dsw->sw.num_pending_expose, n, 1);

    for (i = 0; i < n; i++) {
	r = dsw->sw.pending_expose + (dsw->sw.num_pending_expose * 4);
	f = newRect + (i * 4);
	ConvertToX(dsw, LEFT(f), TOP(f), r, r+1);
	ConvertToX(dsw, RIGHT(f), BOTTOM(f), r+2, r+3);
	r[2] -= r[0];
	r[3] -= r[1];
	dsw->sw.num_pending_expose++;
    }
}

static void CopyRectsToCurrentDrawing(
    DPSScrolledWindowWidget dsw,
    float *newRect,
    int n)
{
    float *r;
    int i;

    GrowRectList(&dsw->sw.current_drawing, &dsw->sw.current_drawing_size,
		 0, n, 1);

    r = dsw->sw.current_drawing;
    for (i = 0; i < 4*n; i++) r[i] = newRect[i];
    dsw->sw.num_current_drawing = n;
}

static void CopyRectsToDirtyArea(
    DPSScrolledWindowWidget dsw,
    float *newRect,
    int n)
{
    float *r;
    int i;

    GrowRectList(&dsw->sw.dirty_areas, &dsw->sw.dirty_areas_size, 0, n, 1);

    r = dsw->sw.dirty_areas;
    for (i = 0; i < 4*n; i++) r[i] = newRect[i];
    dsw->sw.num_dirty_areas = n;
}

static void AddRectsToDirtyArea(
    DPSScrolledWindowWidget dsw,
    float *newRect,
    int n)
{
    float *r;
    int i;

    GrowRectList(&dsw->sw.dirty_areas, &dsw->sw.dirty_areas_size,
		 dsw->sw.num_dirty_areas, n, 1);

    r = dsw->sw.dirty_areas + (4 * dsw->sw.num_dirty_areas);
    for (i = 0; i < 4*n; i++) r[i] = newRect[i];
    dsw->sw.num_dirty_areas += n;
}

static void CopyRectsToPrevDirtyArea(
    DPSScrolledWindowWidget dsw,
    float *newRect,
    int n)
{
    float *r;
    int i;

    GrowRectList(&dsw->sw.prev_dirty_areas,
		 &dsw->sw.prev_dirty_areas_size, 0, n, 1);

    r = dsw->sw.prev_dirty_areas;
    for (i = 0; i < 4*n; i++) r[i] = newRect[i];
    dsw->sw.num_prev_dirty_areas = n;
}

/* ARGSUSED */

static void DrawingAreaGraphicsExpose(
    Widget w,
    XtPointer clientData,
    XEvent *event,
    Boolean *goOn)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) clientData;
    XExposeEvent *ev = (XExposeEvent *) event;

    switch (event->type) {
	case GraphicsExpose:
	    /* GraphicsExpose occur during unbuffered scrolling */
	    if (dsw->sw.backing_pixmap == None && dsw->sw.scrolling) {
		/* Unbuffered scrolling case */
		AddExposureToPending(dsw, ev);

		if (ev->count == 0) {
		    AddRectsToDirtyArea(dsw, dsw->sw.pending_dirty,
					 dsw->sw.num_pending_dirty);
		    StartDrawing(dsw);
		    dsw->sw.num_pending_dirty = dsw->sw.num_pending_expose = 0;
		}
	    }
	    break;
    }
}

static Boolean MoreExposes(Widget w)
{
    XEvent event;

    if (XPending(XtDisplay(w)) > 0) {
	if (XCheckTypedWindowEvent(XtDisplay(w), XtWindow(w),
				   Expose, &event)) {
	    XPutBackEvent(XtDisplay(w), &event);
	    return True;;
	}
    }
    return False;
}

static void DrawingAreaExpose(
    Widget w,
    XtPointer clientData, XtPointer callData)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) clientData;
    XmDrawingAreaCallbackStruct *d = (XmDrawingAreaCallbackStruct *) callData;
    XExposeEvent *ev = (XExposeEvent *) d->event;
    int dx, dy;

    if (dsw->sw.doing_feedback) {
	if (dsw->sw.feedback_pixmap != None) {
	    XCopyArea(XtDisplay(dsw), dsw->sw.feedback_pixmap,
		      XtWindow(dsw->sw.drawing_area),
		      dsw->sw.no_ge_gc,
		      ev->x, ev->y, ev->width, ev->height, ev->x, ev->y);
	} else {
	    if (dsw->sw.backing_pixmap != None) {
		ComputeOffsets(dsw, &dx, &dy);

		XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
			  XtWindow(dsw->sw.drawing_area),
			  dsw->sw.no_ge_gc, ev->x + dx, ev->y + dy,
			  ev->width, ev->height, ev->x, ev->y);
	    }
	    AddExposureToPending(dsw, ev);
	    if (ev->count != 0 || MoreExposes(w)) return;

	    if (dsw->sw.backing_pixmap == None) {
		CopyRectsToCurrentDrawing(dsw, dsw->sw.pending_dirty,
					  dsw->sw.num_pending_dirty);
		dsw->sw.drawing_stage = DSWStart;
		FinishDrawing(dsw);
	    }
	    if (dsw->sw.feedback_displayed) {
		CallFeedbackCallback(dsw, dsw->sw.pending_dirty,
				     dsw->sw.num_pending_dirty);
	    }
	    dsw->sw.num_pending_dirty = dsw->sw.num_pending_expose = 0;
	}
	return;
    }

    if (dsw->sw.backing_pixmap != None) {
	if (dsw->sw.drawing_stage == DSWStart && dsw->sw.watch_progress) {
	    SplitExposeEvent(dsw, ev);
	    if (ev->count == 0) {
		if (MoreExposes(w)) return;
		StartDrawing(dsw);
		dsw->sw.num_pending_dirty = dsw->sw.num_pending_expose = 0;
	    }
	    return;
	}

	if (dsw->sw.drawing_stage < DSWDrewVisible) {
	    SplitExposeEvent(dsw, ev);
	    return;
	}
	ComputeOffsets(dsw, &dx, &dy);

	XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
		  XtWindow(dsw->sw.drawing_area),
		  dsw->sw.no_ge_gc,
		  ev->x + dx, ev->y + dy, ev->width, ev->height, ev->x, ev->y);
    } else {
	AddExposureToPending(dsw, ev);
	if (ev->count == 0) {
	    if (MoreExposes(w)) return;
	    if (dsw->sw.drawing_stage == DSWDone ||
		dsw->sw.drawing_stage == DSWStart) {
		CopyRectsToDirtyArea(dsw, dsw->sw.pending_dirty,
				    dsw->sw.num_pending_dirty);
		StartDrawing(dsw);
		dsw->sw.num_pending_dirty = dsw->sw.num_pending_expose = 0;
	    }
	}
    }
}	      

static void Resize(Widget w)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    DSWResizeCallbackRec r;
    float x, y;

    if (XtIsRealized(w)) (void) AbortOrFinish(dsw);

    r.oldw = dsw->sw.scrolled_window->core.width;
    r.oldh = dsw->sw.scrolled_window->core.height;
    r.neww = dsw->core.width;
    r.newh = dsw->core.height;
    r.x = r.y = 0;

    XtCallCallbackList(w, dsw->sw.resize_callback, (XtPointer) &r);

    if (XtIsRealized(w)) {
	ConvertToPS(dsw, (float) r.x, (float) r.y, &x, &y);
    } else if (r.x != 0 || r.y != 0) ScrollBy(w, r.x, r.y);

    XtResizeWidget(dsw->sw.scrolled_window, w->core.width, w->core.height, 0);

    if (!XtIsRealized(w)) return;

    if (dsw->sw.backing_pixmap != None &&
	dsw->sw.pixmap_width == CEIL(dsw->sw.drawing_width) &&
	dsw->sw.pixmap_height == CEIL(dsw->sw.drawing_height) &&
	dsw->sw.pixmap_width >= (int) dsw->sw.drawing_area->core.width &&
	dsw->sw.pixmap_height >= (int) dsw->sw.drawing_area->core.width) {
	
	XDPSSetContextGState(dsw->sw.context, dsw->sw.window_gstate);
	DPSinitclip(dsw->sw.context);
	DPSinitviewclip(dsw->sw.context);
	SetDrawingAreaPosition(dsw, x, y, r.x, r.y, True);
	SetOriginAndGetTransform(dsw);
	XDPSUpdateContextGState(dsw->sw.context, dsw->sw.window_gstate);
	XClearArea(XtDisplay(dsw), XtWindow(dsw->sw.drawing_area),
		   0, 0, 0, 0, True);
    } else {
	dsw->sw.use_saved_scroll = True;
	dsw->sw.scroll_pic_x = x;
	dsw->sw.scroll_pic_y = y;
	dsw->sw.scroll_win_x = r.x;
	dsw->sw.scroll_win_y = r.y;

	if (dsw->sw.backing_pixmap != None) {
	    XFreePixmap(XtDisplay(dsw), dsw->sw.backing_pixmap);
	    XDPSFreeContextGState(dsw->sw.context, dsw->sw.backing_gstate);
	}
	dsw->sw.backing_pixmap = None;
	dsw->sw.big_pixmap = False;
	dsw->sw.pixmap_width = dsw->sw.pixmap_height = 0;

	dsw->sw.num_dirty_areas = 1;
	LEFT(dsw->sw.dirty_areas) = 0.0;
	BOTTOM(dsw->sw.dirty_areas) = 0.0;
	WIDTH(dsw->sw.dirty_areas) = dsw->sw.area_width;
	HEIGHT(dsw->sw.dirty_areas) = dsw->sw.area_height;

	SetUpInitialInformation(dsw);
    }

    if (dsw->sw.doing_feedback) {
	float *r;
	int dx, dy;

	CheckFeedbackPixmap(dsw);
	r = dsw->sw.prev_dirty_areas;
	ConvertToPS(dsw, 0 + DELTA, dsw->sw.drawing_area->core.height - DELTA,
		    r, r+1);
	ConvertToPS(dsw, dsw->sw.drawing_area->core.width - DELTA, 0 + DELTA,
		    r+2, r+3);
	r[2] -= r[0];
	r[3] -= r[1];
	dsw->sw.num_prev_dirty_areas = 1;

	if (dsw->sw.feedback_pixmap != None) {
	    /* Initialize the feedback pixmap with a copy of the drawing */
	    if (dsw->sw.backing_pixmap != None) {
		CopyToFeedbackPixmap(dsw, dsw->sw.prev_dirty_areas,
				     dsw->sw.num_prev_dirty_areas);
	    } else {
		CopyRectsToCurrentDrawing(dsw, dsw->sw.prev_dirty_areas,
					  dsw->sw.num_prev_dirty_areas);
		(void) ClipAndDraw(dsw, DSWFeedbackPixmap, DSWFinish, True);
	    }
	    XCopyArea(XtDisplay(dsw), dsw->sw.feedback_pixmap,
		      XtWindow(dsw->sw.drawing_area),
		      dsw->sw.no_ge_gc, 0, 0, 
		      dsw->sw.drawing_area->core.width,
		      dsw->sw.drawing_area->core.height, 0, 0);
	} else {
	    if (dsw->sw.backing_pixmap != None) {
		ComputeOffsets(dsw, &dx, &dy);

		XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
			  XtWindow(dsw->sw.drawing_area),
			  dsw->sw.no_ge_gc, dx, dy,
			  dsw->sw.drawing_area->core.width,
			  dsw->sw.drawing_area->core.height, 0, 0);
	    } else {
		CopyRectsToCurrentDrawing(dsw, dsw->sw.prev_dirty_areas,
					  dsw->sw.num_prev_dirty_areas);
		dsw->sw.drawing_stage = DSWStart;
		FinishDrawing(dsw);
	    }
	}
	if (dsw->sw.feedback_displayed) {
	    CallFeedbackCallback(dsw, dsw->sw.prev_dirty_areas,
				 dsw->sw.num_prev_dirty_areas);
	}
	if (dsw->sw.feedback_pixmap != None) {
	    UpdateWindowFromFeedbackPixmap(dsw, dsw->sw.prev_dirty_areas,
					   dsw->sw.num_prev_dirty_areas);
	}
	dsw->sw.num_pending_dirty = dsw->sw.num_pending_expose = 0;
    }
}

static void CheckFeedbackPixmap(DPSScrolledWindowWidget dsw)
{
    if (dsw->sw.feedback_pixmap != None &&
	(dsw->sw.feedback_width < (int) dsw->sw.drawing_area->core.width ||
	 dsw->sw.feedback_height < (int) dsw->sw.drawing_area->core.height)) {
	XFreePixmap(XtDisplay(dsw), dsw->sw.feedback_pixmap);
	dsw->sw.feedback_pixmap = None;
	dsw->sw.feedback_width = dsw->sw.feedback_height = 0;
    }
    if (dsw->sw.use_feedback_pixmap && dsw->sw.feedback_pixmap == None) {
	dsw->sw.feedback_pixmap =
		AllocPixmap(dsw, dsw->sw.drawing_area->core.width,
			    dsw->sw.drawing_area->core.height);
	if (dsw->sw.feedback_pixmap != None) {
	    dsw->sw.feedback_width = dsw->sw.drawing_area->core.width;
	    dsw->sw.feedback_height = dsw->sw.drawing_area->core.height;
	}
    }
    if (dsw->sw.feedback_pixmap != None) {
	XDPSSetContextDrawable(dsw->sw.context, dsw->sw.feedback_pixmap,
			       dsw->sw.drawing_area->core.height);
	SetPixmapOrigin(dsw);
	XDPSCaptureContextGState(dsw->sw.context, &dsw->sw.feedback_gstate);
    }
}

static void UpdateGStates(DPSScrolledWindowWidget dsw)
{
    /* Create graphics states for the window and backing pixmap in
       the new context */
    XDPSSetContextDrawable(dsw->sw.context, XtWindow(dsw->sw.drawing_area),
			   dsw->sw.drawing_area->core.height);
    DPSinitgraphics(dsw->sw.context);
    if (dsw->sw.scale != 1.0) {
	DPSscale(dsw->sw.context, dsw->sw.scale, dsw->sw.scale);
    }

    SetOriginAndGetTransform(dsw);
    (void) XDPSCaptureContextGState(dsw->sw.context, &dsw->sw.window_gstate);
    if (dsw->sw.backing_pixmap != None) {
	XDPSSetContextDrawable(dsw->sw.context, dsw->sw.backing_pixmap,
			       dsw->sw.pixmap_height);

	SetPixmapOffset(dsw);
	SetPixmapOrigin(dsw);
	XDPSCaptureContextGState(dsw->sw.context, &dsw->sw.backing_gstate);
    }
}

static void CheckPixmapSize(DPSScrolledWindowWidget dsw)
{
    Boolean freeIt = False;
    int w = dsw->sw.pixmap_width, h = dsw->sw.pixmap_height;
    Widget wid = dsw->sw.drawing_area;
    unsigned int dBytes;

    if (dsw->sw.pixmap_limit > 0) {
	if (w * h > dsw->sw.pixmap_limit) freeIt = True;
    } else if (dsw->sw.pixmap_limit < 0 &&
	       w * h > dsw->sw.unscaled_width * dsw->sw.unscaled_height &&
	       w * h > (int) wid->core.width * (int) wid->core.height) {
	freeIt = True;
    }

    if (dsw->sw.absolute_pixmap_limit > 0) {
	dBytes = (wid->core.depth + 7) / 8;	  /* Convert into bytes */
	if (w * h * dBytes > (unsigned)dsw->sw.absolute_pixmap_limit * 1024) {
	    freeIt = True;
	}
    }
    if (freeIt) {
	XFreePixmap(XtDisplay(dsw), dsw->sw.backing_pixmap);
	dsw->sw.backing_pixmap = None;
	dsw->sw.big_pixmap = False;
	dsw->sw.pixmap_width = dsw->sw.pixmap_height = 0;
	XDPSFreeContextGState(dsw->sw.context, dsw->sw.backing_gstate);
    }
}

static void ResizeArea(DPSScrolledWindowWidget dsw)
{
    AbortDrawing(dsw);

    /* Make everything dirty */
    dsw->sw.num_dirty_areas = 1;
    LEFT(dsw->sw.dirty_areas) = 0.0;
    BOTTOM(dsw->sw.dirty_areas) = 0.0;
    WIDTH(dsw->sw.dirty_areas) = dsw->sw.area_width;
    HEIGHT(dsw->sw.dirty_areas) = dsw->sw.area_height;
    
    if (dsw->sw.big_pixmap) {
	XFreePixmap(XtDisplay(dsw), dsw->sw.backing_pixmap);
	dsw->sw.backing_pixmap = None;
	dsw->sw.big_pixmap = False;
	dsw->sw.pixmap_width = dsw->sw.pixmap_height = 0;
	XDPSFreeContextGState(dsw->sw.context, dsw->sw.backing_gstate);
    }

    if (!dsw->sw.use_saved_scroll) {
	/* Keep the upper left in the same place */
	dsw->sw.scroll_win_x = 0;
	dsw->sw.scroll_win_y = 0;
	ConvertToPS(dsw, 0.0, 0.0,
		    &dsw->sw.scroll_pic_x, &dsw->sw.scroll_pic_y);
	dsw->sw.use_saved_scroll = True;
    }

    SetUpInitialInformation(dsw);
}

static void ClearDirtyAreas(DPSScrolledWindowWidget dsw)
{
    int i;
    float *r;
    int llx, lly, urx, ury;

    for (i = 0; i < dsw->sw.num_dirty_areas; i++) {
	r = dsw->sw.dirty_areas + (i * 4);
	ConvertToX(dsw, LEFT(r), BOTTOM(r), &llx, &lly);
	ConvertToX(dsw, RIGHT(r), TOP(r), &urx, &ury);
	XClearArea(XtDisplay(dsw), XtWindow(dsw->sw.drawing_area),
		  llx, ury, urx-llx, lly-ury, True);
    }
}

static void HandleFeedbackPixmapChange(DPSScrolledWindowWidget dsw)
{
    if (!dsw->sw.use_feedback_pixmap) {
	/* Get rid of one if we have it */
	if (dsw->sw.feedback_pixmap != None) {
	    XFreePixmap(XtDisplay(dsw), dsw->sw.feedback_pixmap);
	    dsw->sw.feedback_pixmap = None;
	    dsw->sw.feedback_width = dsw->sw.feedback_height = 0;
	}
    } else {
	if (dsw->sw.doing_feedback) {
	    float *r;

	    CheckFeedbackPixmap(dsw);
	    if (dsw->sw.feedback_pixmap == None) return;

	    r = dsw->sw.prev_dirty_areas;
	    ConvertToPS(dsw, 0 + DELTA,
			dsw->sw.drawing_area->core.height - DELTA, r, r+1);
	    ConvertToPS(dsw, dsw->sw.drawing_area->core.width - DELTA,
			0 + DELTA, r+2, r+3);
	    r[2] -= r[0];
	    r[3] -= r[1];
	    dsw->sw.num_prev_dirty_areas = 1;

	    /* Initialize the feedback pixmap with a copy of the drawing */
	    if (dsw->sw.backing_pixmap != None) {
		CopyToFeedbackPixmap(dsw, dsw->sw.prev_dirty_areas,
				     dsw->sw.num_prev_dirty_areas);
	    } else {
		CopyRectsToCurrentDrawing(dsw, dsw->sw.prev_dirty_areas,
					  dsw->sw.num_prev_dirty_areas);
		(void) ClipAndDraw(dsw, DSWFeedbackPixmap, DSWFinish, True);
	    }
	    if (dsw->sw.feedback_displayed) {
		CallFeedbackCallback(dsw, dsw->sw.prev_dirty_areas,
				     dsw->sw.num_prev_dirty_areas);
	    }
	}
    }
}	    

/* ARGSUSED */

static Boolean SetValues(
    Widget old, Widget req, Widget new,
    ArgList args,
    Cardinal *num_args)
{
    DPSScrolledWindowWidget olddsw = (DPSScrolledWindowWidget) old;
    DPSScrolledWindowWidget newdsw = (DPSScrolledWindowWidget) new;
    Bool inited;

#define NE(field) newdsw->sw.field != olddsw->sw.field
#define DONT_CHANGE(field) \
    if (NE(field)) newdsw->sw.field = olddsw->sw.field;

    DONT_CHANGE(ctm_ptr);
    DONT_CHANGE(inv_ctm_ptr);
    DONT_CHANGE(backing_pixmap);
    DONT_CHANGE(feedback_pixmap);
    DONT_CHANGE(window_gstate);
    DONT_CHANGE(backing_gstate);
    DONT_CHANGE(feedback_gstate);
    
    if (NE(context)) {
	DSWSetupCallbackRec setup;

	if (newdsw->sw.context == NULL) {
	    newdsw->sw.context = XDPSGetSharedContext(XtDisplay(newdsw));
	} 
	if (_XDPSTestComponentInitialized(newdsw->sw.context,
					  dps_init_bit_dsw, &inited) ==
	    dps_status_unregistered_context) {
	    XDPSRegisterContext(newdsw->sw.context, False);
	}
	if (XtIsRealized(newdsw)) {
	    setup.context = newdsw->sw.context;
	    XtCallCallbackList((Widget) newdsw, newdsw->sw.setup_callback,
			       (XtPointer) &setup);
	}
	UpdateGStates(newdsw);
    }

    /* Watch progress only works with pass-through event dispatching */

    if (NE(watch_progress)) {
	if (newdsw->sw.watch_progress &&
	    XDPSSetEventDelivery(XtDisplay(newdsw), dps_event_query) !=
		    dps_event_pass_through) newdsw->sw.watch_progress = False;
    }

    if (NE(application_scrolling) && !newdsw->sw.application_scrolling) {
	XtVaSetValues(newdsw->sw.h_scroll, XmNmaximum, newdsw->sw.scroll_h_max,
		      XmNvalue, newdsw->sw.scroll_h_value,
		      XmNsliderSize, newdsw->sw.scroll_h_size, NULL);
	XtVaSetValues(newdsw->sw.v_scroll, XmNmaximum, newdsw->sw.scroll_v_max,
		      XmNvalue, newdsw->sw.scroll_v_value,
		      XmNsliderSize, newdsw->sw.scroll_v_size, NULL);
    }
	    
    if (newdsw->sw.doing_feedback) {
	DONT_CHANGE(scale);
	DONT_CHANGE(area_width);
	DONT_CHANGE(area_height);
    }

    if (NE(pixmap_limit) || NE(absolute_pixmap_limit)) CheckPixmapSize(newdsw);

    if (NE(area_width) || NE(area_height) || NE(scale)) ResizeArea(newdsw);

    /* It's too confusing to let any of these things change in the middle
       of drawing */

    if (NE(use_backing_pixmap) || NE(watch_progress) ||
	NE(minimal_drawing) || NE(document_size_pixmaps)) {
	Boolean freeIt = False, setUp = False;
	AbortOrFinish(newdsw);
	if (NE(use_backing_pixmap)) {
	    if (newdsw->sw.use_backing_pixmap) setUp = True;
	    else freeIt = True;
	}
	if (NE(document_size_pixmaps)) {
	    if (newdsw->sw.backing_pixmap != None) freeIt = True;
	    setUp = True;
	}
	if (freeIt) FreeBackingPixmap(newdsw);
	if (setUp) SetUpInitialPixmap(newdsw);
    }

    if (NE(dirty_areas)) {
	float *r = newdsw->sw.dirty_areas;
	int n = newdsw->sw.num_dirty_areas;
	DONT_CHANGE(dirty_areas);
	DONT_CHANGE(num_dirty_areas);
	AbortOrFinish(newdsw);
	AddRectsToDirtyArea(newdsw, r, n);
	if (newdsw->sw.watch_progress || newdsw->sw.backing_pixmap == None) {
	    ClearDirtyAreas(newdsw);
	    newdsw->sw.drawing_stage = DSWStart;
	} else {
	    AddUserSpaceRectsToPending(newdsw, r, n);
	    StartDrawing(newdsw);
	}
    }

    if (NE(use_feedback_pixmap)) HandleFeedbackPixmapChange(newdsw);

    return False;
#undef DONT_CHANGE
}

static XtGeometryResult GeometryManager(
    Widget w,
    XtWidgetGeometry *desired, XtWidgetGeometry *allowed)
{
    /* Pass geometry requests up to our parent */
    return XtMakeGeometryRequest(XtParent(w), desired, allowed);
}

static XtGeometryResult QueryGeometry(
    Widget w,
    XtWidgetGeometry *desired, XtWidgetGeometry *allowed)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;

    /* Pass geometry requests down to our child */
    return XtQueryGeometry(dsw->sw.scrolled_window, desired, allowed);
}

static void CopyToFeedbackPixmap(
    DPSScrolledWindowWidget dsw,
    float *rects,
    int n)
{
    int llx, lly, urx, ury;
    int dx, dy;
    int i;
    float *r;

    ComputeOffsets(dsw, &dx, &dy);

    for (i = 0; i < n; i++) {
	r = rects + (i * 4);
	ConvertToX(dsw, LEFT(r), BOTTOM(r), &llx, &lly);
	ConvertToX(dsw, RIGHT(r), TOP(r), &urx, &ury);

	XCopyArea(XtDisplay(dsw), dsw->sw.backing_pixmap,
		  dsw->sw.feedback_pixmap, dsw->sw.no_ge_gc,
		  llx+dx-1, ury+dy-1, urx-llx+2, lly-ury+2, llx-1, ury-1);
    }
}

static void CallFeedbackCallback(
    DPSScrolledWindowWidget dsw,
    float *r,
    int n)
{
    DSWFeedbackCallbackRec f;

    f.start_feedback_data = dsw->sw.start_feedback_data;
    f.continue_feedback_data = dsw->sw.continue_feedback_data;
    if (dsw->sw.feedback_pixmap == None) {
	f.type = DSWWindow;
	f.drawable = XtWindow(dsw->sw.drawing_area);
	f.gstate = dsw->sw.window_gstate;
    } else {
	f.type = DSWFeedbackPixmap;
	f.drawable = dsw->sw.feedback_pixmap;
	f.gstate = dsw->sw.feedback_gstate;
    }
    f.context = dsw->sw.context;
    f.dirty_rects = r;
    f.dirty_count = n;

    XDPSSetContextGState(dsw->sw.context, f.gstate);
    _DPSSWSetRectViewClip(dsw->sw.context, r, n * 4);
    XtCallCallbackList((Widget) dsw, dsw->sw.feedback_callback,
		       (XtPointer) &f);
    DPSWaitContext(dsw->sw.context);
}

static void SetScale(
    Widget w,
    double scale,
    long fixedX, long fixedY)
{
    float psX, psY;

    ConvertToPS((DPSScrolledWindowWidget) w, (float) fixedX, (float) fixedY,
		&psX, &psY);
    SetScaleAndScroll(w, scale, psX, psY, fixedX, fixedY);
}

void DSWSetScale(
    Widget w,
    double scale,
    long fixedX, long fixedY)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.set_scale) (w, scale, fixedX, fixedY);
}

static void ScrollPoint(
    Widget w,
    double psX, double psY,
    long xX, long xY)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;

    if (!XtIsRealized(w)) {
	dsw->sw.use_saved_scroll = True;
	dsw->sw.scroll_pic_x = psX;
	dsw->sw.scroll_pic_y = psY;
	dsw->sw.scroll_win_x = xX;
	dsw->sw.scroll_win_y = xY;
	return;
    } else {
	SetDrawingAreaPosition(dsw, psX, psY, xX, xY, False);
	ScrollMoved(dsw);
    }
}

void DSWScrollPoint(
    Widget w,
    double psX, double psY,
    long xX, long xY)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.scroll_point) (w, psX, psY, xX, xY);
}

static void ScrollBy(Widget w, long dx, long dy)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    int value;

    if (dx == 0 && dy == 0) return;

    if (!XtIsRealized(w) && dsw->sw.use_saved_scroll) {
	dsw->sw.scroll_win_x += dx;
	dsw->sw.scroll_win_y += dy;
    } else {
	value = dsw->sw.scroll_h_value + dx;

	if (value < 0) value = 0;
	else if (value > dsw->sw.scroll_h_max - dsw->sw.scroll_h_size) {
	    value = dsw->sw.scroll_h_max - dsw->sw.scroll_h_size;
	}
	dsw->sw.scroll_h_value = value;

	if (!dsw->sw.application_scrolling) {
	    XtVaSetValues(dsw->sw.h_scroll, XmNvalue, value, NULL);
	}

	value = dsw->sw.scroll_v_value + dy;

	if (value < 0) value = 0;
	else if (value > dsw->sw.scroll_v_max - dsw->sw.scroll_v_size) {
	    value = dsw->sw.scroll_v_max - dsw->sw.scroll_v_size;
	}
	dsw->sw.scroll_v_value = value;

	if (!dsw->sw.application_scrolling) {
	    XtVaSetValues(dsw->sw.v_scroll, XmNvalue, value, NULL);
	}

	ScrollMoved(dsw);
    }
}

void DSWScrollBy(Widget w, long dx, long dy)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.scroll_by) (w, dx, dy);
}

static void ScrollTo(Widget w, long x, long y)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    int max, size;

    if (XtIsRealized(w)) {
	if (x < 0) x = 0;
	else if (x > dsw->sw.scroll_h_max - dsw->sw.scroll_h_size) {
	    x = dsw->sw.scroll_h_max - dsw->sw.scroll_h_size;
	}
	dsw->sw.scroll_h_value = x;

	if (y < 0) y = 0;
	else if (y > dsw->sw.scroll_v_max - dsw->sw.scroll_v_size) {
	    y = dsw->sw.scroll_v_max - dsw->sw.scroll_v_size;
	}
	dsw->sw.scroll_v_value = y;

	if (!dsw->sw.application_scrolling) {
	    XtVaSetValues(dsw->sw.h_scroll, XmNvalue, x, NULL);
	    XtVaSetValues(dsw->sw.v_scroll, XmNvalue, y, NULL);
	}

	ScrollMoved(dsw);
    }
}

void DSWScrollTo(Widget w, long x, long y)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.scroll_to) (w, x, y);
}

static void SetScaleAndScroll(
    Widget w,
    double scale,
    double psX, double psY,
    long xX, long xY)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    Arg arg;
    union {
	int i;
	float f;
    } kludge;

    dsw->sw.use_saved_scroll = True;
    dsw->sw.scroll_pic_x = psX;
    dsw->sw.scroll_pic_y = psY;
    dsw->sw.scroll_win_x = xX;
    dsw->sw.scroll_win_y = xY;

    kludge.f = scale;
    arg.name = XtNscale;
    if (sizeof(float) > sizeof(XtArgVal)) arg.value = (XtArgVal) &kludge.f;
    else arg.value = (XtArgVal) kludge.i;
    XtSetValues(w, &arg, 1);
}

void DSWSetScaleAndScroll(
    Widget w,
    double scale,
    double psX, double psY,
    long xX, long xY)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.set_scale_and_scroll) (w, scale, psX, psY, xX, xY);
}

static void ConvertXToPS(
    Widget w,
    long xX, long xY,
    float *psX, float *psY)
{
    ConvertToPS((DPSScrolledWindowWidget) w, (float) xX, (float) xY, psX, psY);
}

void DSWConvertXToPS(
    Widget w,
    long xX, long xY,
    float *psX, float *psY)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.convert_x_to_ps) (w, xX, xY, psX, psY);
}

static void ConvertPSToX(
    Widget w,
    double psX, double psY,
    int *xX, int *xY)
{
    ConvertToX((DPSScrolledWindowWidget) w, psX, psY, xX, xY);
}

void DSWConvertPSToX(
    Widget w,
    double psX, double psY,
    int *xX, int *xY)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.convert_ps_to_x) (w, psX, psY, xX, xY);
}

static void AddToDirtyArea(
    Widget w,
    float *rect,
    long n)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;

    if (n == 1 && rect[0] == 0 && rect[1] == 0 &&
	rect[2] == -1 && rect[2] == -1) {
	rect[2] = dsw->sw.area_width;
	rect[3] = dsw->sw.area_height;
    }

    XtVaSetValues(w, XtNdirtyAreas, rect, XtNnumDirtyAreas, n, NULL);
}

void DSWAddToDirtyArea(
    Widget w,
    float *rect,
    long n)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.add_to_dirty_area) (w, rect, n);
}

static Boolean TakeFeedbackPixmap(
    Widget w,
    Pixmap *p,
    int *width, int *height, int *depth,
    Screen **screen)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    
    if (dsw->sw.doing_feedback) return False;

    *p = dsw->sw.feedback_pixmap;
    if (*p == None) {
	*width = *height = *depth;
	*screen = NULL;
	return True;
    }

    *width = dsw->sw.feedback_width;
    *height = dsw->sw.feedback_height;
    *depth = dsw->sw.drawing_area->core.depth;
    *screen = dsw->core.screen;

    dsw->sw.feedback_pixmap = None;
    dsw->sw.feedback_width = dsw->sw.feedback_height = 0;
    return True;
}

Boolean DSWTakeFeedbackPixmap(
    Widget w,
    Pixmap *p,
    int *width, int *height, int *depth,
    Screen **screen)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    return (*((DPSScrolledWindowWidgetClass) XtClass(w))->
	    sw_class.take_feedback_pixmap) (w, p, width, height,
					    depth, screen);
}

static void StartFeedbackDrawing(
    Widget w,
    XtPointer start_feedback_data)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    float *r;

    FinishDrawing(dsw);
    CheckFeedbackPixmap(dsw);
    if (dsw->sw.feedback_pixmap != None) {
	/* Initialize the feedback pixmap with a copy of the drawing */
	GrowRectList(&dsw->sw.prev_dirty_areas, &dsw->sw.prev_dirty_areas_size,
		     0, 1, 1);
	r = dsw->sw.prev_dirty_areas;
	ConvertToPS(dsw, 0 + DELTA, dsw->sw.drawing_area->core.height - DELTA,
		    r, r+1);
	ConvertToPS(dsw, dsw->sw.drawing_area->core.width - DELTA, 0 + DELTA,
		    r+2, r+3);
	r[2] -= r[0];
	r[3] -= r[1];
	dsw->sw.num_prev_dirty_areas = 1;

	if (dsw->sw.backing_pixmap != None) {
	    CopyToFeedbackPixmap(dsw, dsw->sw.prev_dirty_areas,
				 dsw->sw.num_prev_dirty_areas);
	} else {
	    CopyRectsToCurrentDrawing(dsw, dsw->sw.prev_dirty_areas,
				      dsw->sw.num_prev_dirty_areas);
	    (void) ClipAndDraw(dsw, DSWFeedbackPixmap, DSWFinish, True);
	}
    }
    dsw->sw.num_prev_dirty_areas = 0;
    dsw->sw.doing_feedback = True;
    dsw->sw.start_feedback_data = start_feedback_data;
}

void DSWStartFeedbackDrawing(
    Widget w,
    XtPointer start_feedback_data)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.start_feedback_drawing) (w, start_feedback_data);
}

static void EndFeedbackDrawing(
    Widget w,
    int restore)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;

    if (restore) {
	if (dsw->sw.backing_pixmap != None) {
	    UpdateWindowFromBackingPixmap(dsw, dsw->sw.prev_dirty_areas,
					  dsw->sw.num_prev_dirty_areas);
	} else {
	    CopyRectsToCurrentDrawing(dsw, dsw->sw.prev_dirty_areas,
				      dsw->sw.num_prev_dirty_areas);
	    (void) ClipAndDraw(dsw, DSWWindow, DSWFinish, True);
	}
    }
    if (dsw->sw.feedback_gstate != 0) {
	XDPSFreeContextGState(dsw->sw.context, dsw->sw.feedback_gstate);
    }
    dsw->sw.doing_feedback = dsw->sw.feedback_displayed = False;
}

void DSWEndFeedbackDrawing(
    Widget w,
    Bool restore)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.end_feedback_drawing) (w, restore);
}

static void SetFeedbackDirtyArea(
    Widget w,
    float *rects,
    int count,
    XtPointer continue_feedback_data)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    int i;
    float *r;

    for (i = 0; i < count; i++) {
	r = rects + (i * 4);
	if (WIDTH(r) < 0) {
	    LEFT(r) += WIDTH(r);
	    WIDTH(r) = -WIDTH(r);
	}
	if (HEIGHT(r) < 0) {
	    BOTTOM(r) += HEIGHT(r);
	    HEIGHT(r) = -HEIGHT(r);
	}
    }

    if (dsw->sw.backing_pixmap != None) {
	if (dsw->sw.feedback_pixmap != None) {
	    CopyToFeedbackPixmap(dsw, dsw->sw.prev_dirty_areas,
				 dsw->sw.num_prev_dirty_areas);
	} else {
	    UpdateWindowFromBackingPixmap(dsw, dsw->sw.prev_dirty_areas,
					  dsw->sw.num_prev_dirty_areas);
	}
    } else {
	CopyRectsToCurrentDrawing(dsw, dsw->sw.prev_dirty_areas,
				  dsw->sw.num_prev_dirty_areas);
	(void) ClipAndDraw(dsw, (dsw->sw.feedback_pixmap == None ?
				 DSWWindow : DSWFeedbackPixmap),
			   DSWFinish, True);
    }
    dsw->sw.continue_feedback_data = continue_feedback_data;
    CallFeedbackCallback(dsw, rects, count);

    if (dsw->sw.feedback_pixmap != None) {
	CopyRectsToDirtyArea(dsw, dsw->sw.prev_dirty_areas,
			     dsw->sw.num_prev_dirty_areas);
	AddRectsToDirtyArea(dsw, rects, count);
	SimplifyRects(dsw->sw.dirty_areas, &dsw->sw.num_dirty_areas);
	UpdateWindowFromFeedbackPixmap(dsw, dsw->sw.dirty_areas,
				       dsw->sw.num_dirty_areas);
	dsw->sw.num_dirty_areas = 0;
    }
    CopyRectsToPrevDirtyArea(dsw, rects, count);
    dsw->sw.feedback_displayed = True;
}

void DSWSetFeedbackDirtyArea(
    Widget w,
    float *rects,
    int count,
    XtPointer continue_feedback_data)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.set_feedback_dirty_area) (w, rects, count,
					continue_feedback_data);
}

static void FinishPendingDrawing(Widget w)
{
    FinishDrawing((DPSScrolledWindowWidget) w);
}

void DSWFinishPendingDrawing(Widget w)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.finish_pending_drawing) (w);
}

static void AbortPendingDrawing(Widget w)
{
    AbortDrawing((DPSScrolledWindowWidget) w);
}

void DSWAbortPendingDrawing(Widget w)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.abort_pending_drawing) (w);
}

static void UpdateDrawing(
    Widget w,
    float *rects,
    int count)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    int i;
    float *r;
    int llx, lly, urx, ury;
    int dx, dy;

    if (dsw->sw.backing_pixmap == None) {
	AddToDirtyArea(w, rects, count);
	return;
    }

    ComputeOffsets(dsw, &dx, &dy);

    for (i = 0; i < count; i++) {
	r = rects + (i * 4);
	ConvertToX(dsw, LEFT(r), BOTTOM(r), &llx, &lly);
	ConvertToX(dsw, RIGHT(r), TOP(r), &urx, &ury);
	XCopyArea(XtDisplay(dsw), XtWindow(dsw->sw.drawing_area),
		  dsw->sw.backing_pixmap, dsw->sw.no_ge_gc,
		  llx-1, ury-1, urx-llx+2, lly-ury+2, llx+dx-1, ury+dy-1);
    }
}

void DSWUpdateDrawing(
    Widget w,
    float *rects,
    int count)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.update_drawing) (w, rects, count);
}

static void GetScrollInfo(
    Widget w,
    int *h_value, int *h_size, int *h_max, int *v_value, int *v_size, int *v_max)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;

    if (h_value != NULL) *h_value = dsw->sw.scroll_h_value;
    if (h_size != NULL) *h_size = dsw->sw.scroll_h_size;
    if (h_max != NULL) *h_max = dsw->sw.scroll_h_max;
    if (v_value != NULL) *v_value = dsw->sw.scroll_v_value;
    if (v_size != NULL) *v_size = dsw->sw.scroll_v_size;
    if (v_max != NULL) *v_max = dsw->sw.scroll_v_max;
}

void DSWGetScrollInfo(
    Widget w,
    int *h_value, int *h_size, int *h_max, int *v_value, int *v_size, int *v_max)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.get_scroll_info) (w, h_value, h_size, h_max,
				v_value, v_size, v_max);
}

static void GetDrawingInfo(
    Widget w,
    DSWDrawableType *type,
    Drawable *drawable,
    DPSGState *gstate,
    DPSContext *context)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;

    if (dsw->sw.backing_pixmap != None) {
	*type = DSWBackingPixmap;
	*drawable = dsw->sw.backing_pixmap;
	*gstate = dsw->sw.backing_gstate;
    } else {
	*type = DSWWindow;
	*drawable = XtWindow(dsw->sw.drawing_area);
	*gstate = dsw->sw.window_gstate;
    }
    *context = dsw->sw.context;
}

void DSWGetDrawingInfo(
    Widget w,
    DSWDrawableType *type,
    Drawable *drawable,
    DPSGState *gstate,
    DPSContext *context)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    (*((DPSScrolledWindowWidgetClass) XtClass(w))->
     sw_class.get_drawing_info) (w, type, drawable, gstate, context);
}

static Boolean GiveFeedbackPixmap(
    Widget w,
    Pixmap p,
    int width, int height, int depth,
    Screen *screen)
{
    DPSScrolledWindowWidget dsw = (DPSScrolledWindowWidget) w;
    
    if ((unsigned) depth != dsw->sw.drawing_area->core.depth
     || screen != dsw->core.screen
     || dsw->sw.feedback_pixmap != None) return False;

    dsw->sw.feedback_pixmap = p;
    dsw->sw.feedback_width = width;
    dsw->sw.feedback_height = height;

    return True;
}

Boolean DSWGiveFeedbackPixmap(
    Widget w,
    Pixmap p,
    int width, int height, int depth,
    Screen *screen)
{
    XtCheckSubclass(w, dpsScrolledWindowWidgetClass, NULL);

    return (*((DPSScrolledWindowWidgetClass) XtClass(w))->
	    sw_class.give_feedback_pixmap) (w, p, width, height,
					    depth, screen);
}

static void ConvertToX(
    DPSScrolledWindowWidget dsw,
    float psX,
    float psY,
    int *xX,
    int *xY)
{
    *xX = dsw->sw.ctm[0] * psX + dsw->sw.ctm[2] * psY + dsw->sw.ctm[4] +
	    dsw->sw.x_offset + 0.5;
    *xY = dsw->sw.ctm[1] * psX + dsw->sw.ctm[3] * psY + dsw->sw.ctm[5] +
	    dsw->sw.y_offset + 0.5;
}

static void ConvertToPS(
    DPSScrolledWindowWidget dsw,
    float xX, float xY,
    float *psX, float *psY)
{
    xX -= dsw->sw.x_offset;
    xY -= dsw->sw.y_offset;

    *psX = dsw->sw.inv_ctm[0] * xX + dsw->sw.inv_ctm[2] * xY +
	    dsw->sw.inv_ctm[4];
    *psY = dsw->sw.inv_ctm[1] * xX + dsw->sw.inv_ctm[3] * xY +
	    dsw->sw.inv_ctm[5];
}

static void ConvertToOrigPS(
    DPSScrolledWindowWidget dsw,
    int xX, int xY,
    float *psX, float *psY)
{
    xX -= dsw->sw.x_offset;
    xY -= dsw->sw.y_offset;

    *psX = dsw->sw.orig_inv_ctm[0] * xX + dsw->sw.orig_inv_ctm[2] * xY +
	    dsw->sw.orig_inv_ctm[4];
    *psY = dsw->sw.orig_inv_ctm[1] * xX + dsw->sw.orig_inv_ctm[3] * xY +
	    dsw->sw.orig_inv_ctm[5];
}
