/* 
 * tkUnixDraw.c --
 *
 *	This file contains X specific drawing routines.
 *
 * Copyright (c) 1995 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * SCCS: @(#) tkUnixDraw.c 1.9 97/03/21 11:16:18
 */

#include "tkPort.h"
#include "tkInt.h"

/*
 * The following structure is used to pass information to
 * ScrollRestrictProc from TkScrollWindow.
 */

typedef struct ScrollInfo {
    int done;			/* Flag is 0 until filtering is done. */
    Display *display;		/* Display to filter. */
    Window window;		/* Window to filter. */
    TkRegion region;		/* Region into which damage is accumulated. */
    int dx, dy;			/* Amount by which window was shifted. */
} ScrollInfo;

/*
 * Forward declarations for procedures declared later in this file:
 */

static Tk_RestrictAction	ScrollRestrictProc _ANSI_ARGS_((
    				    ClientData arg, XEvent *eventPtr));

/*
 *----------------------------------------------------------------------
 *
 * TkScrollWindow --
 *
 *	Scroll a rectangle of the specified window and accumulate
 *	damage information in the specified Region.
 *
 * Results:
 *	Returns 0 if no damage additional damage was generated.  Sets
 *	damageRgn to contain the damaged areas and returns 1 if
 *	GraphicsExpose events were detected.
 *
 * Side effects:
 *	Scrolls the bits in the window and enters the event loop
 *	looking for damage events.
 *
 *----------------------------------------------------------------------
 */

int
TkScrollWindow(tkwin, gc, x, y, width, height, dx, dy, damageRgn)
    Tk_Window tkwin;		/* The window to be scrolled. */
    GC gc;			/* GC for window to be scrolled. */
    int x, y, width, height;	/* Position rectangle to be scrolled. */
    int dx, dy;			/* Distance rectangle should be moved. */
    TkRegion damageRgn;		/* Region to accumulate damage in. */
{
    Tk_RestrictProc *oldProc;
    ClientData oldArg, dummy;
    ScrollInfo info;
    
    XCopyArea(Tk_Display(tkwin), Tk_WindowId(tkwin), Tk_WindowId(tkwin), gc,
	    x, y, (unsigned int) width, (unsigned int) height, x + dx, y + dy);

    info.done = 0;
    info.window = Tk_WindowId(tkwin);
    info.display = Tk_Display(tkwin);
    info.region = damageRgn;
    info.dx = dx;
    info.dy = dy;

    /*
     * Sync the event stream so all of the expose events will be on the
     * Tk event queue before we start filtering.  This avoids busy waiting
     * while we filter events.
     */

    TkpSync(info.display);
    oldProc = Tk_RestrictEvents(ScrollRestrictProc, (ClientData) &info,
	    &oldArg);
    while (!info.done) {
	Tcl_ServiceEvent(TCL_WINDOW_EVENTS);
    }
    Tk_RestrictEvents(oldProc, oldArg, &dummy);

    return XEmptyRegion((Region) damageRgn) ? 0 : 1;
}

/*
 *----------------------------------------------------------------------
 *
 * ScrollRestrictProc --
 *
 *	A Tk_RestrictProc used by TkScrollWindow to gather up Expose
 *	information into a single damage region.  It accumulates damage
 *	events on the specified window until a NoExpose or the last
 *	GraphicsExpose event is detected.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Discards Expose events after accumulating damage information
 *	for a particular window.
 *
 *----------------------------------------------------------------------
 */

static Tk_RestrictAction
ScrollRestrictProc(arg, eventPtr)
    ClientData arg;
    XEvent *eventPtr;
{
    ScrollInfo *info = (ScrollInfo *) arg;
    XRectangle rect;

    /*
     * Defer events which aren't for the specified window.
     */

    if (info->done || (eventPtr->xany.display != info->display)
	    || (eventPtr->xany.window != info->window)) {
	return TK_DEFER_EVENT;
    }

    if (eventPtr->type == NoExpose) {
	info->done = 1;
    } else if (eventPtr->type == GraphicsExpose) {
	rect.x = eventPtr->xgraphicsexpose.x;
	rect.y = eventPtr->xgraphicsexpose.y;
	rect.width = eventPtr->xgraphicsexpose.width;
	rect.height = eventPtr->xgraphicsexpose.height;
	XUnionRectWithRegion(&rect, (Region) info->region,
		(Region) info->region);

	if (eventPtr->xgraphicsexpose.count == 0) {
	    info->done = 1;
	}
    } else if (eventPtr->type == Expose) {

	/*
	 * This case is tricky.  This event was already queued before
	 * the XCopyArea was issued.  If this area overlaps the area
	 * being copied, then some of the copied area may be invalid.
	 * The easiest way to handle this case is to mark both the
	 * original area and the shifted area as damaged.
	 */

	rect.x = eventPtr->xexpose.x;
	rect.y = eventPtr->xexpose.y;
	rect.width = eventPtr->xexpose.width;
	rect.height = eventPtr->xexpose.height;
	XUnionRectWithRegion(&rect, (Region) info->region,
		(Region) info->region);
	rect.x += info->dx;
	rect.y += info->dy;
	XUnionRectWithRegion(&rect, (Region) info->region,
		(Region) info->region);
    } else {
	return TK_DEFER_EVENT;
    }
    return TK_DISCARD_EVENT;
}

