/*
 * rbcTile.c --
 *
 *      This module manages images for tiled backgrounds for the rbc toolkit.
 *
 * Copyright (c) 2009 Samuel Green, Nicholas Hudson, Stanton Sievers, Jarrod Stormo
 * All rights reserved.
 *
 * See "license.terms" for details.
 */

#include "rbcInt.h"
#include "rbcChain.h"
#include "rbcImage.h"
#include <X11/Xutil.h>

#include "rbcTile.h"

#define TILE_THREAD_KEY	"Rbc Tile Data"
#define TILE_MAGIC ((unsigned int) 0x46170277)

typedef struct {
    Tcl_HashTable tileTable; /* Hash table of tile structures keyed by
			      * the name of the image. */
    Tcl_Interp *interp;
} TileInterpData;

typedef struct {
    char *name; /* Name of image used to generate the pixmap.*/
    Display *display; /* Display where pixmap was created. */
    int flags; /* See definitions below. */
    Tcl_Interp *interp;
    Tcl_HashEntry *hashPtr; /* Pointer to hash table location */
    Tcl_HashTable *tablePtr;
    Pixmap pixmap; /* Pixmap generated from image. */
    Pixmap mask; /* Monochrome pixmap used as
		  * transparency mask. */
    GC gc; /* GC */
    Tk_Image tkImage; /* Tk image token. */
    Rbc_Chain *clients; /* Chain of clients sharing this tile. */
    int width, height;
} Tile;

/* If set, indicates that the image
* associated with the tile has been
* updated or deleted.  The tile pixmap
* will be changed and the clients of the
* tile will be notified (if they supplied
* a TileChangedProc routine. */
#define TILE_NOTIFY_PENDING	1

typedef struct Rbc_TileClientStruct {
    unsigned int magic;
    Tk_Window tkwin; /* Client window. */
    int xOrigin, yOrigin; /* Tiling origin in relation to the
			   * client window. */
    Rbc_TileChangedProc *notifyProc; /* If non-NULL, routine to
				      * call to when tile image changes. */
    ClientData clientData; /* Data to pass to when calling the above
			    * routine. */
    Tile *tilePtr; /* Pointer to actual tile information */
    Rbc_ChainLink *linkPtr; /* Pointer to client entry in the server's
			     * client list.  Used to delete the client */
} TileClient;

typedef struct {
    Display *display;
    Tk_Uid nameId;
    int depth;
} TileKey;

static Tcl_IdleProc UpdateTile;
static Tk_ImageChangedProc ImageChangedProc;
static Tcl_InterpDeleteProc TileInterpDeleteProc;

static TileInterpData *GetTileInterpData (Tcl_Interp *interp);
static void DestroyClient (TileClient *clientPtr);
static void DestroyTile (Tile *tilePtr);
static void RedrawTile (Tk_Window tkwin, Tile *tilePtr);
static Tile *CreateTile (Tcl_Interp *interp, Tk_Window tkwin, const char *imageName);
static TileClient *CreateClient (Tcl_Interp *interp, Tk_Window tkwin, const char *name);

#ifdef WIN32
static void TileRegion (HDC srcDC, HDC destDC, HDC maskDC, TileClient *clientPtr, int x, int y, int width, int height);
#endif

/*
 *--------------------------------------------------------------
 *
 * RedrawTile --
 *
 *      Generates a pixmap and draws the tile image into it.  Also
 *      a tranparency mask is possibly generated from the image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static void
RedrawTile(tkwin, tilePtr)
    Tk_Window tkwin;
    Tile *tilePtr;
{
    GC newGC;
    Tk_PhotoHandle photo;
    XGCValues gcValues;
    int width, height;
    unsigned int gcMask;

    Tk_SizeOfImage(tilePtr->tkImage, &width, &height);

    Tk_MakeWindowExist(tkwin);
    if ((width != tilePtr->width) || (height != tilePtr->height)) {
	Pixmap pixmap;

	/*
	 * Create the new pixmap *before* destroying the old one.  I don't
	 * why this happens, but if you delete the old pixmap first, the
	 * old pixmap sometimes gets used in the client's GCs.  I suspect
	 * it has something to do with the way Tk reallocates X resource
	 * identifiers.
	 */
	pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin), width,
			      height, Tk_Depth(tkwin));
	if (tilePtr->pixmap != None) {
	    Tk_FreePixmap(Tk_Display(tkwin), tilePtr->pixmap);
	}
	tilePtr->pixmap = pixmap;
    }
    Tk_RedrawImage(tilePtr->tkImage, 0, 0, width, height, tilePtr->pixmap,
		   0, 0);

    gcMask = (GCTile | GCFillStyle);
    gcValues.fill_style = FillTiled;
    gcValues.tile = tilePtr->pixmap;
    newGC = Tk_GetGC(tkwin, gcMask, &gcValues);
    if (tilePtr->gc != NULL) {
	Tk_FreeGC(Tk_Display(tkwin), tilePtr->gc);
    }
    tilePtr->gc = newGC;
    tilePtr->width = width;
    tilePtr->height = height;

    if (tilePtr->mask != None) {
#ifdef WIN32
	Tk_FreePixmap(Tk_Display(tkwin), tilePtr->mask);
#else
	XFreePixmap(Tk_Display(tkwin), tilePtr->mask);
#endif /* WIN32 */
	tilePtr->mask = None;
    }
    photo = Tk_FindPhoto(tilePtr->interp,
			  Rbc_NameOfImage(tilePtr->tkImage));
    if (photo != NULL) {
	Tk_PhotoImageBlock src;

	Tk_PhotoGetImage(photo, &src);
	if ((src.offset[3] < src.pixelSize) && (src.offset[3] >= 0)) {
	    tilePtr->mask = Rbc_PhotoImageMask(tkwin, src);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateTile --
 *
 *      It would be better if Tk checked for NULL proc pointers.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
UpdateTile(clientData)
    ClientData clientData;
{
    Tile *tilePtr = (Tile *)clientData;
    TileClient *clientPtr;
    Rbc_ChainLink *linkPtr;

    tilePtr->flags &= ~TILE_NOTIFY_PENDING;
    if (Tk_ImageIsDeleted(tilePtr->tkImage)) {
	if (tilePtr->pixmap != None) {
	    Tk_FreePixmap(tilePtr->display, tilePtr->pixmap);
	}
	tilePtr->pixmap = None;
    } else {
	/* Pick any client window to generate the new pixmap. */
	linkPtr = Rbc_ChainFirstLink(tilePtr->clients);
	clientPtr = Rbc_ChainGetValue(linkPtr);
	RedrawTile(clientPtr->tkwin, tilePtr);
    }

    /* Notify each of the tile's clients that the pixmap has changed. */

    for (linkPtr = Rbc_ChainFirstLink(tilePtr->clients); linkPtr != NULL;
    linkPtr = Rbc_ChainNextLink(linkPtr)) {
	clientPtr = Rbc_ChainGetValue(linkPtr);
	if (clientPtr->notifyProc != NULL) {
	    (*clientPtr->notifyProc) (clientPtr->clientData, clientPtr);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ImageChangedProc
 *
 *      The Tk image has changed or been deleted, redraw the pixmap
 *      tile.
 *
 *      Note: As of Tk 4.2 (rechecked in 8.3), if you redraw Tk
 *      images from a Tk_ImageChangedProc you'll get a
 *      coredump.  As a workaround, we have to simulate
 *      how the Tk widgets use images and redraw within
 *      an idle event.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
ImageChangedProc(clientData, x, y, width, height, imageWidth, imageHeight)
    ClientData clientData;
    int x, y, width, height;	/* Not used. */
    int imageWidth, imageHeight; /* Not used. */
{
    Tile *tilePtr = (Tile *) clientData;

    if (!(tilePtr->flags & TILE_NOTIFY_PENDING)) {
	Tcl_DoWhenIdle(UpdateTile, tilePtr);
	tilePtr->flags |= TILE_NOTIFY_PENDING;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyTile --
 *
 *      Deletes the core tile structure, including the pixmap
 *      representing the tile.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
DestroyTile(tilePtr)
    Tile *tilePtr;
{
    Rbc_ChainLink *linkPtr;
    TileClient *clientPtr;

    if (tilePtr->flags & TILE_NOTIFY_PENDING) {
	Tcl_CancelIdleCall(UpdateTile, tilePtr);
    }
    for (linkPtr = Rbc_ChainFirstLink(tilePtr->clients); linkPtr != NULL;
    linkPtr = Rbc_ChainNextLink(linkPtr)) {
	clientPtr = Rbc_ChainGetValue(linkPtr);
	ckfree((char *)clientPtr);
    }
    Rbc_ChainDestroy(tilePtr->clients);

    if (tilePtr->hashPtr != NULL) {
	Tcl_DeleteHashEntry(tilePtr->hashPtr);
    }
    if (tilePtr->pixmap != None) {
	Tk_FreePixmap(tilePtr->display, tilePtr->pixmap);
    }
    Tk_FreeImage(tilePtr->tkImage);

    if (tilePtr->gc != NULL) {
	Tk_FreeGC(tilePtr->display, tilePtr->gc);
    }
    if (tilePtr->name != NULL) {
	ckfree((char *)tilePtr->name);
    }
    ckfree((char *)tilePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateTile --
 *
 *      Creates a tile server.  A tile server manages a single image,
 *      possibly shared by several clients.  Clients will be updated
 *      (if requested) by the server if the image changes, so they
 *      know to redraw themselves.  For X11 the image is drawn into a
 *      pixmap that is used in a new GC as its tile.  For Windows we
 *      have to do the tiling ourselves by redrawing the image across
 *      the drawing area (see Rbc_TileRectangle and Rbc_TilePolygon).
 *
 * Results:
 *      Returns a pointer to the new tile server.  If the image name
 *      does not represent a Tk image, NULL is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static Tile *
CreateTile(interp, tkwin, imageName)
    Tcl_Interp *interp;
    Tk_Window tkwin;
    const char *imageName;
{
    Tile *tilePtr;
    Tk_Image tkImage;

    tilePtr = RbcCalloc(1, sizeof(Tile));
    assert(tilePtr);
    /*
     * Get the image. Funnel all change notifications to a single routine.
     */
    tkImage = Tk_GetImage(interp, tkwin, imageName, ImageChangedProc,
			  tilePtr);
    if (tkImage == NULL) {
	ckfree((char *)tilePtr);
	return NULL;
    }

    /*
     * Initialize the tile server.
     */
    tilePtr->display = Tk_Display(tkwin);
    tilePtr->interp = interp;
    tilePtr->name = RbcStrdup(imageName);
    tilePtr->clients = Rbc_ChainCreate();
    tilePtr->tkImage = tkImage;
    RedrawTile(tkwin, tilePtr);
    return tilePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyClient --
 *
 *      Removes the client from the servers's list of clients and
 *      memory used by the client token is released.  When the last
 *      client is deleted, the server is also removed.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
DestroyClient(clientPtr)
    TileClient *clientPtr;
{
    Tile *tilePtr;
    tilePtr = clientPtr->tilePtr;

    /* Remove the client from the server's list */
    if (clientPtr->linkPtr != NULL) {
	Rbc_ChainDeleteLink(tilePtr->clients, clientPtr->linkPtr);
    }
    if (Rbc_ChainGetLength(tilePtr->clients) == 0) {
	/*
	 * If there are no more clients of the tile, then remove the
	 * pixmap, image, and the server record.
	 */
	DestroyTile(tilePtr);
    }
    ckfree((char *)clientPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateClient --
 *
 *      Returns a token to a tile (possibly shared by many clients).
 *      A client uses the token to query or display the tile.  Clients
 *      request tiles by their image names.  Each tile is known by its
 *      display, screen depth, and image name.  The tile server tracks
 *      what clients are using the tile and notifies them (via a
 *      callback) whenever the tile changes. If no server exists
 *      already, one is created on-the-fly.
 *
 * Results:
 *      A pointer to the newly created client (i.e. tile).
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static TileClient *
CreateClient(
    Tcl_Interp *interp,
    Tk_Window tkwin,
    const char *name)
{
    TileClient *clientPtr;
    Tile *tilePtr;
    TileInterpData *dataPtr;
    Tcl_HashEntry *hPtr;
    int isNew;
    TileKey key;

    dataPtr = GetTileInterpData(interp);

    key.nameId = Tk_GetUid(name);
    key.display = Tk_Display(tkwin);
    key.depth = Tk_Depth(tkwin);
    hPtr = Tcl_CreateHashEntry(&dataPtr->tileTable, (char *)&key, &isNew);
    if (isNew) {
	tilePtr = CreateTile(interp, tkwin, name);
	if (tilePtr == NULL) {
	    Tcl_DeleteHashEntry(hPtr);
	    return NULL;
	}
	tilePtr->hashPtr = hPtr;
	tilePtr->tablePtr = &(dataPtr->tileTable);
	Tcl_SetHashValue(hPtr, tilePtr);
    } else {
	tilePtr = Tcl_GetHashValue(hPtr);
    }
    clientPtr = RbcCalloc(1, sizeof(TileClient));
    assert(clientPtr);

    /* Initialize client information. */
    clientPtr->magic = TILE_MAGIC;
    clientPtr->tkwin = tkwin;
    clientPtr->linkPtr = Rbc_ChainAppend(tilePtr->clients, clientPtr);
    clientPtr->tilePtr = tilePtr;
    return clientPtr;
}

/*
 * -----------------------------------------------------------------------
 *
 * TileInterpDeleteProc --
 *
 *      This is called when the interpreter is deleted. All the tiles
 *      are specific to that interpreter are destroyed.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Destroys the tile table.
 *
 * ------------------------------------------------------------------------
 */
static void
TileInterpDeleteProc(clientData, interp)
    ClientData clientData; /* Thread-specific data. */
    Tcl_Interp *interp;
{
    TileInterpData *dataPtr = clientData;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Tile *tilePtr;

    for (hPtr = Tcl_FirstHashEntry(&(dataPtr->tileTable), &cursor);
    hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	tilePtr = Tcl_GetHashValue(hPtr);
	tilePtr->hashPtr = NULL;
	DestroyTile(tilePtr);
    }
    Tcl_DeleteHashTable(&(dataPtr->tileTable));
    Tcl_DeleteAssocData(interp, TILE_THREAD_KEY);
    ckfree((char *)dataPtr);
}

/*
 *--------------------------------------------------------------
 *
 * GetTileInterpData --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static TileInterpData *
GetTileInterpData(interp)
    Tcl_Interp *interp;
{
    TileInterpData *dataPtr;
    Tcl_InterpDeleteProc *proc;

    dataPtr = (TileInterpData *)
	      Tcl_GetAssocData(interp, TILE_THREAD_KEY, &proc);
    if (dataPtr == NULL) {
	dataPtr = (TileInterpData *)ckalloc(sizeof(TileInterpData));
	assert(dataPtr);
	dataPtr->interp = interp;
	Tcl_SetAssocData(interp, TILE_THREAD_KEY, TileInterpDeleteProc,
			 dataPtr);
	Tcl_InitHashTable(&(dataPtr->tileTable), sizeof(TileKey)/sizeof(int));
    }
    return dataPtr;
}


/* Public API for tiles. */

/*
 *----------------------------------------------------------------------
 *
 * Rbc_GetTile
 *
 *      Convert the named image into a tile.
 *
 * Results:
 *      If the image is valid, a new tile is returned.  If the name
 *      does not represent a proper image, an error message is left in
 *      the interpreter result.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
int
Rbc_GetTile(interp, tkwin, imageName, tokenPtr)
    Tcl_Interp *interp; /* Interpreter to report results back to */
    Tk_Window tkwin; /* Window on the same display as tile */
    const char *imageName; /* Name of image */
    Rbc_Tile *tokenPtr; /* (out) Returns the allocated tile token. */
{
    TileClient *clientPtr;

    clientPtr = CreateClient(interp, tkwin, imageName);
    if (clientPtr == NULL) {
	return TCL_ERROR;
    }
    *tokenPtr = clientPtr;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_FreeTile
 *
 *      Release the resources associated with the tile.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Memory and X resources are freed.  Bookkeeping information
 *      about the tile (i.e. width, height, and name) is discarded.
 *
 *----------------------------------------------------------------------
 */
void
Rbc_FreeTile(clientPtr)
    TileClient *clientPtr; /* Tile to be deleted */
{
    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	return;			/* No tile */
    }
    DestroyClient(clientPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_NameOfTile
 *
 *      Returns the name of the image from which the tile was
 *      generated.
 *
 * Results:
 *      The name of the image is returned.  The name is not unique.
 *      Many tiles may use the same image.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
char *
Rbc_NameOfTile(clientPtr)
    TileClient *clientPtr; /* Tile to query */
{
    if (clientPtr == NULL) {
	return "";
    }
    if (clientPtr->magic != TILE_MAGIC) {
	return "not a tile";
    }
    return clientPtr->tilePtr->name;
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_PixmapOfTile
 *
 *      Returns the pixmap of the tile.
 *
 * Results:
 *      The X pixmap used as the tile is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
Pixmap
Rbc_PixmapOfTile(clientPtr)
    TileClient *clientPtr; /* Tile to query */
{
    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	return None;
    }
    return clientPtr->tilePtr->pixmap;
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_SizeOfTile
 *
 *      Returns the width and height of the tile.
 *
 * Results:
 *      The width and height of the tile are returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
void
Rbc_SizeOfTile(clientPtr, widthPtr, heightPtr)
    TileClient *clientPtr; /* Tile to query */
    int *widthPtr;
    int *heightPtr; /* Returned dimensions of the tile (out) */
{
    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	*widthPtr = *heightPtr = 0;
	return;			/* No tile given. */
    }
    *widthPtr = clientPtr->tilePtr->width;
    *heightPtr = clientPtr->tilePtr->height;
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_SetTileChangedProc
 *
 *      Sets the routine to called when an image changes.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      The designated routine will be called the next time the
 *      image associated with the tile changes.
 *
 *----------------------------------------------------------------------
 */
void
Rbc_SetTileChangedProc(clientPtr, notifyProc, clientData)
    TileClient *clientPtr; /* Tile to query */
    Rbc_TileChangedProc *notifyProc;
    ClientData clientData;
{
    if ((clientPtr != NULL) && (clientPtr->magic == TILE_MAGIC)) {
	clientPtr->notifyProc = notifyProc;
	clientPtr->clientData = clientData;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_SetTileOrigin --
 *
 *      Set the pattern origin of the tile to a common point (i.e. the
 *      origin (0,0) of the top level window) so that tiles from two
 *      different widgets will match up.  This done by setting the
 *      GCTileStipOrigin field is set to the translated origin of the
 *      toplevel window in the hierarchy.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      The GCTileStipOrigin is reset in the GC.  This will cause the
 *      tile origin to change when the GC is used for drawing.
 *
 *----------------------------------------------------------------------
 */
void
Rbc_SetTileOrigin(tkwin, clientPtr, x, y)
    Tk_Window tkwin;
    TileClient *clientPtr;
    int x;
    int y;
{
    while (!Tk_IsTopLevel(tkwin)) {
	x += Tk_X(tkwin) + Tk_Changes(tkwin)->border_width;
	y += Tk_Y(tkwin) + Tk_Changes(tkwin)->border_width;
	tkwin = Tk_Parent(tkwin);
    }
    XSetTSOrigin(Tk_Display(tkwin), clientPtr->tilePtr->gc, -x, -y);
    clientPtr->xOrigin = -x;
    clientPtr->yOrigin = -y;
}

/*
 *--------------------------------------------------------------
 *
 * Rbc_SetTSOrigin --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
void
Rbc_SetTSOrigin(tkwin, clientPtr, x, y)
    Tk_Window tkwin;
    TileClient *clientPtr;
    int x;
    int y;
{
    XSetTSOrigin(Tk_Display(tkwin), clientPtr->tilePtr->gc, x, y);
    clientPtr->xOrigin = x;
    clientPtr->yOrigin = y;
}

#ifdef WIN32
MODULE_SCOPE const int tkpWinRopModes[];
#define MASKPAT		0x00E20746 /* dest = (src & pat) | (!src & dst) */
#define COPYFG		0x00CA0749 /* dest = (pat & src) | (!pat & dst) */
#define COPYBG		0x00AC0744 /* dest = (!pat & src) | (pat & dst) */

/*
 *--------------------------------------------------------------
 *
 * TileRegion --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static void
TileRegion(srcDC, destDC, maskDC, clientPtr, x, y, width, height)
    HDC srcDC; /* Source device context. */
    HDC destDC; /* Destination device context. */
    HDC maskDC; /* If non-NULL, device context of the
		 * mask tile mask. */
    TileClient *clientPtr;
    int x;
    int y;
    int width;
    int height;
{
    Tile *tilePtr = clientPtr->tilePtr;
    int destX, destY;
    int destWidth, destHeight;
    int srcX, srcY;
    int startX, startY;		/* Starting upper left corner of region. */
    int delta;
    int left, top, right, bottom;

    startX = x;
    if (x < clientPtr->xOrigin) {
	delta = (clientPtr->xOrigin - x) % tilePtr->width;
	if (delta > 0) {
	    startX -= (tilePtr->width - delta);
	}
    } else if (x > clientPtr->xOrigin) {
	delta = (x - clientPtr->xOrigin) % tilePtr->width;
	if (delta > 0) {
	    startX -= delta;
	}
    }
    startY = y;
    if (y < clientPtr->yOrigin) {
	delta = (clientPtr->yOrigin - y) % tilePtr->height;
	if (delta > 0) {
	    startY -= (tilePtr->height - delta);
	}
    } else if (y >= clientPtr->yOrigin) {
	delta = (y - clientPtr->yOrigin) % tilePtr->height;
	if (delta > 0) {
	    startY -= delta;
	}
    }
#ifdef notdef
    PurifyPrintf("tile is (%d,%d,%d,%d)\n",
		 clientPtr->xOrigin, clientPtr->yOrigin,
		 tilePtr->width, tilePtr->height);
    PurifyPrintf("region is (%d,%d,%d,%d)\n", x, y, width, height);
    PurifyPrintf("starting at %d,%d\n", startX, startY);
#endif
    left = x;
    right = x + width;
    top = y;
    bottom = y + height;
    for (y = startY; y < bottom; y += tilePtr->height) {
	srcY = 0;
	destY = y;
	destHeight = tilePtr->height;
	if (y < top) {
	    srcY = (top - y);
	    destHeight = tilePtr->height - srcY;
	    destY = top;
	}
	if ((destY + destHeight) > bottom) {
	    destHeight = (bottom - destY);
	}
	for (x = startX; x < right; x += tilePtr->width) {
	    srcX = 0;
	    destX = x;
	    destWidth = tilePtr->width;
	    if (x < left) {
		srcX = (left - x);
		destWidth = tilePtr->width - srcX;
		destX = left;
	    }
	    if ((destX + destWidth) > right) {
		destWidth = (right - destX);
	    }
#ifdef notdef
	    PurifyPrintf("drawing pattern (%d,%d,%d,%d) at %d,%d\n",
			 srcX , srcY, destWidth, destHeight, destX, destY);
#endif
	    if (tilePtr->mask != None) { /* With transparency. */
#ifdef notdef
		HDC maskDC;
		TkWinDCState maskState;

		maskDC = TkWinGetDrawableDC(tilePtr->display,
					    tilePtr->mask, &maskState);
		SetBkColor(destDC, RGB(255, 255, 255));
		SetTextColor(destDC, RGB(0, 0, 0));
#endif
		BitBlt(destDC, destX, destY, destWidth, destHeight, maskDC,
		       0, 0, SRCAND);
		BitBlt(destDC, destX, destY, destWidth, destHeight, srcDC,
		       srcX, srcY, SRCPAINT);
#ifdef notdef
		TkWinReleaseDrawableDC(tilePtr->mask, maskDC, &maskState);
#endif
	    } else {		/* Opaque tile. */
		BitBlt(destDC, destX, destY, destWidth, destHeight,
		       srcDC, srcX, srcY, SRCCOPY);
	    }
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * Rbc_TilePolygon --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
void
Rbc_TilePolygon(
    Tk_Window tkwin,
    Drawable drawable,
    TileClient *clientPtr,
    XPoint pointArr[],
    int nPoints)
{
    HBITMAP oldBitmap;
    HDC hDC, memDC;
    HRGN hRgn;
    POINT *p, *winPts;
    Region2D bbox;
    Tile *tilePtr;
    TkWinDCState state;
    TkWinDrawable *twdPtr;
    XPoint *endPtr, *pointPtr;
    int fillMode;
    int width, height;

    if (drawable == None) {
	return;
    }
    tilePtr = clientPtr->tilePtr;

    /* Determine the bounding box of the polygon. */
    bbox.left = bbox.right = pointArr[0].x;
    bbox.top = bbox.bottom = pointArr[0].y;

    endPtr = pointArr + nPoints;
    for (pointPtr = pointArr; pointPtr < endPtr; pointPtr++) {
	if (pointPtr->x < bbox.left) {
	    bbox.left = pointPtr->x;
	}
	if (pointPtr->x > bbox.right) {
	    bbox.right = pointPtr->x;
	}
	if (pointPtr->y < bbox.top) {
	    bbox.top = pointPtr->y;
	}
	if (pointPtr->y > bbox.bottom) {
	    bbox.bottom = pointPtr->y;
	}
    }
    width = bbox.right - bbox.left + 1;
    height = bbox.bottom - bbox.top + 1;


    /* Allocate and fill an array of POINTS to create the polygon path. */
    p = winPts = (POINT *)ckalloc(sizeof(POINT) * nPoints);
    for (pointPtr = pointArr; pointPtr < endPtr; pointPtr++) {
	p->x = pointPtr->x - bbox.left;
	p->y = pointPtr->y - bbox.top;
	p++;
    }

    hDC = TkWinGetDrawableDC(Tk_Display(tkwin), drawable, &state);
    SetROP2(hDC, tkpWinRopModes[tilePtr->gc->function]);
    fillMode = (tilePtr->gc->fill_rule == EvenOddRule) ? ALTERNATE : WINDING;
    /* Use the polygon as a clip path. */
    LPtoDP(hDC, winPts, nPoints);
    hRgn = CreatePolygonRgn(winPts, nPoints, fillMode);
    SelectClipRgn(hDC, hRgn);
    OffsetClipRgn(hDC, bbox.left, bbox.top);
    ckfree((char *)winPts);

    twdPtr = (TkWinDrawable *)tilePtr->pixmap;
    memDC = CreateCompatibleDC(hDC);
    oldBitmap = SelectBitmap(memDC, twdPtr->bitmap.handle);

    /* Tile the bounding box. */
    if (tilePtr->mask != None) {
	TkWinDCState maskState;
	HDC maskDC;

	maskDC = TkWinGetDrawableDC(tilePtr->display, tilePtr->mask,
				    &maskState);
	SetBkColor(hDC, RGB(255, 255, 255));
	SetTextColor(hDC, RGB(0, 0, 0));
	TileRegion(memDC, hDC, maskDC, clientPtr, bbox.left, bbox.top, width,
		   height);
	TkWinReleaseDrawableDC(tilePtr->mask, maskDC, &maskState);
    } else {
	TileRegion(memDC, hDC, NULL, clientPtr, bbox.left, bbox.top, width,
		   height);
    }
    SelectBitmap(memDC, oldBitmap);
    DeleteDC(memDC);
    SelectClipRgn(hDC, NULL);
    DeleteRgn(hRgn);
    TkWinReleaseDrawableDC(drawable, hDC, &state);
}

/*
 *--------------------------------------------------------------
 *
 * Rbc_TileRectangle --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
void
Rbc_TileRectangle(
    Tk_Window tkwin,
    Drawable drawable,
    TileClient *clientPtr,
    int x, int y,
    unsigned int width,
    unsigned int height)
{
    HBITMAP oldBitmap;
    HDC hDC, memDC;
    Tile *tilePtr;
    TkWinDCState state;
    TkWinDrawable *twdPtr;

    if (drawable == None) {
	return;
    }
    tilePtr = clientPtr->tilePtr;
    hDC = TkWinGetDrawableDC(Tk_Display(tkwin), drawable, &state);
    SetROP2(hDC, tkpWinRopModes[tilePtr->gc->function]);

    twdPtr = (TkWinDrawable *)tilePtr->pixmap;
    memDC = CreateCompatibleDC(hDC);
    oldBitmap = SelectBitmap(memDC, twdPtr->bitmap.handle);

    /* Tile the bounding box. */
    if (tilePtr->mask != None) {
	TkWinDCState maskState;
	HDC maskDC;

	maskDC = TkWinGetDrawableDC(tilePtr->display, tilePtr->mask,
				    &maskState);
	SetBkColor(hDC, RGB(255, 255, 255));
	SetTextColor(hDC, RGB(0, 0, 0));
	TileRegion(memDC, hDC, maskDC, clientPtr, x, y, width, height);
	TkWinReleaseDrawableDC(tilePtr->mask, maskDC, &maskState);
    } else {
	TileRegion(memDC, hDC, NULL, clientPtr, x, y, width, height);
    }
    SelectBitmap(memDC, oldBitmap);
    DeleteDC(memDC);
    TkWinReleaseDrawableDC(drawable, hDC, &state);
}

/*
 *--------------------------------------------------------------
 *
 * Rbc_TileRectangles --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
void
Rbc_TileRectangles(
    Tk_Window tkwin,
    Drawable drawable,
    TileClient *clientPtr,
    XRectangle rectArr[],
    int nRectangles)
{
    HBITMAP oldBitmap;
    HDC hDC, memDC;
    Tile *tilePtr;
    TkWinDCState state;
    TkWinDrawable *twdPtr;
    XRectangle *rectPtr, *endPtr;

    if (drawable == None) {
	return;
    }
    tilePtr = clientPtr->tilePtr;
    hDC = TkWinGetDrawableDC(Tk_Display(tkwin), drawable, &state);
    SetROP2(hDC, tkpWinRopModes[tilePtr->gc->function]);

    twdPtr = (TkWinDrawable *)tilePtr->pixmap;
    memDC = CreateCompatibleDC(hDC);
    oldBitmap = SelectBitmap(memDC, twdPtr->bitmap.handle);

    endPtr = rectArr + nRectangles;
    /* Tile the bounding box. */
    if (tilePtr->mask != None) {
	TkWinDCState maskState;
	HDC maskDC;

	maskDC = TkWinGetDrawableDC(tilePtr->display, tilePtr->mask,
				    &maskState);
	SetBkColor(hDC, RGB(255, 255, 255));
	SetTextColor(hDC, RGB(0, 0, 0));
	for (rectPtr = rectArr; rectPtr < endPtr; rectPtr++) {
	    TileRegion(memDC, hDC, maskDC, clientPtr, (int)rectPtr->x,
		       (int)rectPtr->y, (int)rectPtr->width, (int)rectPtr->height);
	}
	TkWinReleaseDrawableDC(tilePtr->mask, maskDC, &maskState);
    } else {
	for (rectPtr = rectArr; rectPtr < endPtr; rectPtr++) {
	    TileRegion(memDC, hDC, NULL, clientPtr, (int)rectPtr->x,
		       (int)rectPtr->y, (int)rectPtr->width, (int)rectPtr->height);
	}
    }
    SelectBitmap(memDC, oldBitmap);
    DeleteDC(memDC);
    TkWinReleaseDrawableDC(drawable, hDC, &state);
}

#else

/*
 *----------------------------------------------------------------------
 *
 * RectangleMask --
 *
 *      Creates a rectangular mask also stippled by the mask of the
 *      tile.  This is used to draw the tiled polygon images with
 *      transparent areas.
 *
 * Results:
 *      A bitmap mask is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static Pixmap
RectangleMask(display, drawable, x, y, width, height, mask, xOrigin, yOrigin)
    Display *display;
    Drawable drawable;
    int x;
    int y;
    unsigned int width, height;
    Pixmap mask;
    int xOrigin;
    int yOrigin;
{
    GC gc;
    Pixmap bitmap;
    XGCValues gcValues;
    unsigned long gcMask;

    bitmap = Tk_GetPixmap(display, drawable, width, height, 1);
    gcMask = (GCForeground | GCBackground | GCFillStyle |
	      GCTileStipXOrigin | GCTileStipYOrigin | GCStipple);
    gcValues.foreground = 0x1;
    gcValues.background = 0x0;
    gcValues.fill_style = FillOpaqueStippled;
    gcValues.ts_x_origin = xOrigin - x;
    gcValues.ts_y_origin = yOrigin - y;
    gcValues.stipple = mask;
    gc = XCreateGC(display, bitmap, gcMask, &gcValues);
    XFillRectangle(display, bitmap, gc, 0, 0, width, height);
    Rbc_FreePrivateGC(display, gc);
    return bitmap;
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_TileRectangle --
 *
 *      Draws a rectangle filled by a tiled image.  This differs from
 *      the normal XFillRectangle call in that we also try to handle
 *      a transparency mask.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Draws the rectangle.
 *
 *----------------------------------------------------------------------
 */
void
Rbc_TileRectangle(
    Tk_Window tkwin,
    Drawable drawable,
    TileClient *clientPtr,
    int x, int y,
    unsigned int width,
    unsigned int height)
{
    Tile *tilePtr;
    Display *display;

    display = Tk_Display(tkwin);
    tilePtr = clientPtr->tilePtr;
    if (clientPtr->tilePtr->mask != None) {
	Pixmap mask;

	mask = RectangleMask(display, drawable, x, y, width, height,
			     tilePtr->mask, clientPtr->xOrigin, clientPtr->yOrigin);
	XSetClipMask(display, tilePtr->gc, mask);
	XSetClipOrigin(display, tilePtr->gc, x, y);
	XFillRectangle(display, drawable, tilePtr->gc, x, y, width, height);
	XSetClipMask(display, tilePtr->gc, None);
	XSetClipOrigin(display, tilePtr->gc, 0, 0);
	Tk_FreePixmap(display, mask);
    } else {
	XFillRectangle(display, drawable, tilePtr->gc, x, y, width, height);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_TileRectangles --
 *
 *      Draws rectangles filled by a tiled image.  This differs from
 *      the normal XFillRectangles call in that we also try to handle
 *      a transparency mask.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Draws the given rectangles.
 *
 *----------------------------------------------------------------------
 */
void
Rbc_TileRectangles(
    Tk_Window tkwin,
    Drawable drawable,
    TileClient *clientPtr,
    XRectangle rectArr[],
    int nRectangles)
{
    Tile *tilePtr;

    tilePtr = clientPtr->tilePtr;
    if (tilePtr->mask != None) {
	XRectangle *rectPtr, *endPtr;

	endPtr = rectArr + nRectangles;
	for (rectPtr = rectArr; rectPtr < endPtr; rectPtr++) {
	    Rbc_TileRectangle(tkwin, drawable, clientPtr, rectPtr->x,
			      rectPtr->y, rectPtr->width, rectPtr->height);
	}
    } else {
	XFillRectangles(Tk_Display(tkwin), drawable, tilePtr->gc, rectArr,
			nRectangles);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PolygonMask --
 *
 *      Creates a polygon shaped mask also stippled by the mask
 *      of the tile.  This is used to draw the tiled polygon images
 *      with transparent areas.
 *
 * Results:
 *      A bitmap mask is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static Pixmap
PolygonMask(display, pointArr, nPoints, regionPtr, mask, xOrigin, yOrigin)
    Display *display;
    XPoint *pointArr;
    int nPoints;
    Region2D *regionPtr;
    Pixmap mask;
    int xOrigin;
    int yOrigin;
{
    unsigned int width, height;
    Pixmap bitmap;
    GC gc;
    XPoint *destArr;
    register XPoint *srcPtr, *destPtr, *endPtr;

    width = regionPtr->right - regionPtr->left + 1;
    height = regionPtr->bottom - regionPtr->top + 1;
    bitmap =
	Tk_GetPixmap(display, DefaultRootWindow(display), width, height, 1);

    destArr = (XPoint *)ckalloc(sizeof(XPoint) * nPoints);
    endPtr = destArr + nPoints;
    srcPtr = pointArr;
    for (destPtr = destArr; destPtr < endPtr; destPtr++) {
	destPtr->x = srcPtr->x - regionPtr->left;
	destPtr->y = srcPtr->y - regionPtr->top;
	srcPtr++;
    }
    gc = XCreateGC(display, bitmap, 0, NULL);
    XFillRectangle(display, bitmap, gc, 0, 0, width, height);
    XSetForeground(display, gc, 0x01);
    XSetFillStyle(display, gc, FillStippled);
    XSetTSOrigin(display, gc, xOrigin - regionPtr->left,
		 yOrigin - regionPtr->top);
    XSetStipple(display, gc, mask);
    XFillPolygon(display, bitmap, gc, destArr, nPoints, Complex,
		 CoordModeOrigin);
    XFreeGC(display, gc);
    ckfree((char *)destArr);
    return bitmap;
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_TilePolygon --
 *
 *      Draws a polygon filled by a tiled image.  This differs from
 *      the normal XFillPolygon call in that we also try to handle
 *      a transparency mask.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Draws the polygon.
 *
 *----------------------------------------------------------------------
 */
void
Rbc_TilePolygon(
    Tk_Window tkwin,
    Drawable drawable,
    TileClient *clientPtr,
    XPoint pointArr[],
    int nPoints)
{
    Tile *tilePtr;
    Display *display;

    display = Tk_Display(tkwin);
    tilePtr = clientPtr->tilePtr;
    if (tilePtr->mask != None) {
	XPoint *pointPtr, *endPtr;
	Region2D region;
	Pixmap mask;

	/* Determine the bounding box of the polygon. */
	pointPtr = pointArr;
	region.left = region.right = pointPtr->x;
	region.top = region.bottom = pointPtr->y;

	endPtr = pointArr + nPoints;
	for (pointPtr = pointArr; pointPtr < endPtr; pointPtr++) {
	    if (region.left > pointPtr->x) {
		region.left = pointPtr->x;
	    } else if (region.right < pointPtr->x) {
		region.right = pointPtr->x;
	    }
	    if (region.top > pointPtr->y) {
		region.top = pointPtr->y;
	    } else if (region.bottom < pointPtr->y) {
		region.bottom = pointPtr->y;
	    }
	}
	mask = PolygonMask(display, pointArr, nPoints, &region,
			   tilePtr->mask, clientPtr->xOrigin, clientPtr->yOrigin);
	XSetClipMask(display, tilePtr->gc, mask);
	XSetClipOrigin(display, tilePtr->gc, region.left, region.top);
	XFillPolygon(display, drawable, tilePtr->gc, pointArr,
		     nPoints, Complex, CoordModeOrigin);
	XSetClipMask(display, tilePtr->gc, None);
	XSetClipOrigin(display, tilePtr->gc, 0, 0);
	Tk_FreePixmap(display, mask);
    } else {
	XFillPolygon(display, drawable, tilePtr->gc, pointArr,
		     nPoints, Complex, CoordModeOrigin);
    }
}
#endif
