/*
 * bltTile.c --
 *
 *	This module manages images for tiled backgrounds for the BLT toolkit.
 *
 * Copyright 1995-1998 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 */

#include "bltInt.h"
#include "bltChain.h"
#include "bltHash.h"
#include "bltImage.h"
#include <X11/Xutil.h>

#include "bltTile.h"

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

typedef struct {
    Blt_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;
    Blt_HashEntry *hashPtr;	/* Pointer to hash table location */
    Blt_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. */
    Blt_Chain *clients;		/* Chain of clients sharing this tile. */
    int width, height;		
} Tile;

#define NOTIFY_PENDING	1	/* 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 FIXED_TS_ORIGIN	2	/* TsOrigin is alway the widget. */
#define TILE_ALPHA	4	/* Always fill with border color as image has an alpha. */

typedef struct Blt_TileClientStruct {
    unsigned int magic;
    Tk_Window tkwin;		/* Client window. */
    int xOrigin, yOrigin;	/* Tiling origin in relation to the
				 * client window. */
    Blt_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 */
    Blt_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 TileInterpData *GetTileInterpData _ANSI_ARGS_((Tcl_Interp *interp));

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

static void DestroyClient _ANSI_ARGS_((TileClient *clientPtr));
static void DestroyTile _ANSI_ARGS_((Tile *tilePtr));


/*
 *----------------------------------------------------------------------
 *
 * RedrawTile --
 *
 *	Generates a pixmap and draws the tile image into it.  Also
 *	a tranparency mask is possibly generated from the image.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
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);
    if (height<=0 || width<=0) return;

    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 = Blt_FindPhoto(tilePtr->interp, 
			  Blt_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 = Blt_PhotoImageMask(tkwin, src);
	}
    }
}

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

    tilePtr->flags &= ~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 = Blt_ChainFirstLink(tilePtr->clients);
	clientPtr = Blt_ChainGetValue(linkPtr);
	RedrawTile(clientPtr->tkwin, tilePtr);
    }

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

    for (linkPtr = Blt_ChainFirstLink(tilePtr->clients); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	clientPtr = Blt_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.
 *
 *----------------------------------------------------------------------
 */
/* ARGSUSED */
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 & NOTIFY_PENDING)) {
	Tcl_DoWhenIdle(UpdateTile, tilePtr);
	tilePtr->flags |= NOTIFY_PENDING;
    }
}

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

    if (tilePtr->flags & NOTIFY_PENDING) {
	Tcl_CancelIdleCall(UpdateTile, tilePtr);
    }
    for (linkPtr = Blt_ChainFirstLink(tilePtr->clients); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	clientPtr = Blt_ChainGetValue(linkPtr);
	Blt_Free(clientPtr);
    }
    Blt_ChainDestroy(tilePtr->clients);

    if (tilePtr->hashPtr != NULL) {
	Blt_DeleteHashEntry(tilePtr->tablePtr, 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) {
	Blt_Free(tilePtr->name);
    }
    Blt_Free(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 Blt_TileRectangle and Blt_TilePolygon).
 *
 * Results:
 *	Returns a pointer to the new tile server.  If the image name
 *	does not represent a Tk image, NULL is returned.
 *
 *---------------------------------------------------------------------- 
 */
static Tile *
CreateTile(
    Tcl_Interp *interp,
    Tk_Window tkwin,
    char *imageName)
{
    Tile *tilePtr;
    Tk_Image tkImage;

    tilePtr = Blt_Calloc(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) {
	Blt_Free(tilePtr);
	return NULL;
    }

    /*
     * Initialize the tile server.
     */
    tilePtr->display = Tk_Display(tkwin);
    tilePtr->interp = interp;
    tilePtr->name = Blt_Strdup(imageName);
    tilePtr->clients = Blt_ChainCreate();
    tilePtr->tkImage = tkImage;
    if (strchr(imageName, '!')) {
        tilePtr->flags |= FIXED_TS_ORIGIN;
    }
    if (strchr(imageName, '|')) {
        tilePtr->flags |= TILE_ALPHA;
    }
    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.
 *
 *---------------------------------------------------------------------- 
 */
static void
DestroyClient(TileClient *clientPtr)
{
    Tile *tilePtr;
    tilePtr = clientPtr->tilePtr;

    /* Remove the client from the server's list */
    if (clientPtr->linkPtr != NULL) {
	Blt_ChainDeleteLink(tilePtr->clients, clientPtr->linkPtr);
    }
    if (Blt_ChainGetLength(tilePtr->clients) == 0) {
	/*
	 * If there are no more clients of the tile, then remove the
	 * pixmap, image, and the server record.
	 */
	DestroyTile(tilePtr);
    }
    Blt_Free(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).
 *
 *---------------------------------------------------------------------- 
 */
static TileClient *
CreateClient(
    Tcl_Interp *interp,
    Tk_Window tkwin,
    char *name)
{
    TileClient *clientPtr;
    Tile *tilePtr;
    TileInterpData *dataPtr;
    Blt_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 = Blt_CreateHashEntry(&dataPtr->tileTable, (char *)&key, &isNew);
    if (isNew) {
	tilePtr = CreateTile(interp, tkwin, name);
	if (tilePtr == NULL) {
	    Blt_DeleteHashEntry(&(dataPtr->tileTable), hPtr);
	    return NULL;
	}
	tilePtr->hashPtr = hPtr;
	tilePtr->tablePtr = &(dataPtr->tileTable);
	Blt_SetHashValue(hPtr, tilePtr);
    } else {
	tilePtr = Blt_GetHashValue(hPtr);
    }
    clientPtr = Blt_Calloc(1, sizeof(TileClient));
    assert(clientPtr);

    /* Initialize client information. */
    clientPtr->magic = TILE_MAGIC;
    clientPtr->tkwin = tkwin;
    clientPtr->linkPtr = Blt_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.
 *
 * ------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
TileInterpDeleteProc(
    ClientData clientData,	/* Thread-specific data. */
    Tcl_Interp *interp)
{
    TileInterpData *dataPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Tile *tilePtr;
    
    for (hPtr = Blt_FirstHashEntry(&(dataPtr->tileTable), &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	tilePtr = Blt_GetHashValue(hPtr);
	tilePtr->hashPtr = NULL;
	DestroyTile(tilePtr);
    }
    Blt_DeleteHashTable(&(dataPtr->tileTable));
    Tcl_DeleteAssocData(interp, TILE_THREAD_KEY);
    Blt_Free(dataPtr);
}

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 = Blt_Malloc(sizeof(TileInterpData));
	assert(dataPtr);
	dataPtr->interp = interp;
	Tcl_SetAssocData(interp, TILE_THREAD_KEY, TileInterpDeleteProc, 
		dataPtr);
	Blt_InitHashTable(&(dataPtr->tileTable), sizeof(TileKey)/sizeof(int));
    }
    return dataPtr;
}


/* Public API for tiles. */

/*
 *----------------------------------------------------------------------
 *
 * Blt_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
 *	interp->result.
 *
 *----------------------------------------------------------------------
 */
/*LINTLIBRARY*/
int
Blt_GetTile(
    Tcl_Interp *interp,		/* Interpreter to report results back to */
    Tk_Window tkwin,		/* Window on the same display as tile */
    char *imageName,		/* Name of image */
    Blt_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;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_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.
 *
 *----------------------------------------------------------------------
 */
/*LINTLIBRARY*/
void
Blt_FreeTile(TileClient *clientPtr) /* Tile to be deleted */
{
    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	return;			/* No tile */
    }
    DestroyClient(clientPtr);
}

void
Blt_FixedTile(TileClient *clientPtr) 
{
    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
        return;			/* No tile */
    }
    clientPtr->tilePtr->flags |= FIXED_TS_ORIGIN;
}

int
Blt_TileFlags(TileClient *clientPtr)
{
    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	return 0;			/* No tile */
    }
    return clientPtr->tilePtr->flags;
}



/*
 *----------------------------------------------------------------------
 *
 * Blt_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.
 *
 *----------------------------------------------------------------------
 */
/*LINTLIBRARY*/
char *
Blt_NameOfTile(TileClient *clientPtr) /* Tile to query */
{
    if (clientPtr == NULL) {
	return "";
    }
    if (clientPtr->magic != TILE_MAGIC) {
	return "not a tile";
    }
    return clientPtr->tilePtr->name;
}

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

/* Return 1 if has a non-zero size tile. */
int
Blt_HasTile( TileClient *clientPtr)
{
    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	return 0;
    }
    if (clientPtr->tilePtr->width <= 1) return 0;
    if (clientPtr->tilePtr->height <= 1) return 0;
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_SizeOfTile
 *
 *	Returns the width and height of the tile.
 *
 * Results:
 *	The width and height of the tile are returned.
 *
 *----------------------------------------------------------------------
 */
/*LINTLIBRARY*/
void
Blt_SizeOfTile(
    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;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_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.
 *
 *----------------------------------------------------------------------
 */
/*LINTLIBRARY*/
void
Blt_SetTileChangedProc(
    TileClient *clientPtr,		/* Tile to query */
    Blt_TileChangedProc *notifyProc,
    ClientData clientData)
{
    if ((clientPtr != NULL) && (clientPtr->magic == TILE_MAGIC)) {
	clientPtr->notifyProc = notifyProc;
	clientPtr->clientData = clientData;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_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.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
void
Blt_SetTileOrigin(
    Tk_Window tkwin,
    TileClient *clientPtr,
    int x, int y)
{
    while (!Tk_IsTopLevel(tkwin)) {
        if (Tk_IsBgTileTop(tkwin)) break;
	x += Tk_X(tkwin) + Tk_Changes(tkwin)->border_width;
	y += Tk_Y(tkwin) + Tk_Changes(tkwin)->border_width;
	tkwin = Tk_Parent(tkwin);
    }
    if (clientPtr->tilePtr->gc) {
        XSetTSOrigin(Tk_Display(tkwin), clientPtr->tilePtr->gc, -x, -y);
    }
    clientPtr->xOrigin = -x;
    clientPtr->yOrigin = -y;
}

void
Blt_SetTSOrigin(
    Tk_Window tkwin,
    TileClient *clientPtr,
    int x, int y)
{
    if (clientPtr->tilePtr->gc) {
        XSetTSOrigin(Tk_Display(tkwin), clientPtr->tilePtr->gc, x, y);
    }
    clientPtr->xOrigin = x;
    clientPtr->yOrigin = y;
}

#ifdef WIN32
static int tkpWinRopModes[] =
{
    R2_BLACK,			/* GXclear */
    R2_MASKPEN,			/* GXand */
    R2_MASKPENNOT,		/* GXandReverse */
    R2_COPYPEN,			/* GXcopy */
    R2_MASKNOTPEN,		/* GXandInverted */
    R2_NOT,			/* GXnoop */
    R2_XORPEN,			/* GXxor */
    R2_MERGEPEN,		/* GXor */
    R2_NOTMERGEPEN,		/* GXnor */
    R2_NOTXORPEN,		/* GXequiv */
    R2_NOT,			/* GXinvert */
    R2_MERGEPENNOT,		/* GXorReverse */
    R2_NOTCOPYPEN,		/* GXcopyInverted */
    R2_MERGENOTPEN,		/* GXorInverted */
    R2_NOTMASKPEN,		/* GXnand */
    R2_WHITE			/* GXset */
};
#define MASKPAT		0x00E20746 /* dest = (src & pat) | (!src & dst) */
#define COPYFG		0x00CA0749 /* dest = (pat & src) | (!pat & dst) */
#define COPYBG		0x00AC0744 /* dest = (!pat & src) | (pat & dst) */

static void
TileRegion(
    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);
	    }
	}
    }
}

void
Blt_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;
    if (tilePtr->gc == NULL) return;

    /* 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 = Blt_Malloc(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);
    Blt_Free(winPts);

    twdPtr = (TkWinDrawable *)tilePtr->pixmap;
    memDC = CreateCompatibleDC(hDC);
    oldBitmap = SelectBitmap(memDC, twdPtr->bitmap.handle);
    
    if (tilePtr->flags & FIXED_TS_ORIGIN) {
        Blt_SetTSOrigin(tkwin, clientPtr, Tk_X(tkwin), Tk_Y(tkwin));
    }
    
    /* 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);
}


void
Blt_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;
    if (tilePtr->gc == NULL) return;
    if (tilePtr->flags & FIXED_TS_ORIGIN) {
        Blt_SetTSOrigin(tkwin, clientPtr, x, y);
       /* XSetTSOrigin(Tk_Display(tkwin), clientPtr->tilePtr->gc, x, y); */
    }
    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);
}

void
Blt_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;
    if (tilePtr->gc == NULL) return;
    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.
 *
 *----------------------------------------------------------------------
 */
static Pixmap
RectangleMask(display, drawable, x, y, width, height, mask, xOrigin, yOrigin)
    Display *display;
    Drawable drawable;
    int x, y;
    unsigned int width, height;
    Pixmap mask;
    int xOrigin, 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);
    Blt_FreePrivateGC(display, gc);
    return bitmap;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_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
Blt_TileRectangle(
    Tk_Window tkwin,
    Drawable drawable,
    TileClient *clientPtr,
    int x, int y,
    unsigned int width, 
    unsigned int height)
{
    Tile *tilePtr;
    Display *display;

    if (height==0 || width == 0) return;
    display = Tk_Display(tkwin);
    tilePtr = clientPtr->tilePtr;
    if (tilePtr->gc == NULL) return;
    if (tilePtr->flags & FIXED_TS_ORIGIN) {
        XSetTSOrigin(display, clientPtr->tilePtr->gc, x, y);
    }
    if (clientPtr->tilePtr->mask != None) {
	Pixmap mask;
	int xm, ym;

        xm = clientPtr->xOrigin;
        ym = clientPtr->yOrigin;
        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);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_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
Blt_TileRectangles(
    Tk_Window tkwin,
    Drawable drawable,
    TileClient *clientPtr,
    XRectangle rectArr[],
    int nRectangles)
{
    Tile *tilePtr;

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

	endPtr = rectArr + nRectangles;
	for (rectPtr = rectArr; rectPtr < endPtr; rectPtr++) {
	    Blt_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.
 *
 *----------------------------------------------------------------------
 */
static Pixmap
PolygonMask(display, pointArr, nPoints, regionPtr, mask, xOrigin, yOrigin)
    Display *display;
    XPoint *pointArr;
    int nPoints;
    Region2D *regionPtr;
    Pixmap mask;
    int xOrigin, 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 = Blt_Malloc(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);
    Blt_Free(destArr);
    return bitmap;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_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
Blt_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->gc == NULL) return;
    if (tilePtr->mask != None) {
	XPoint *pointPtr, *endPtr;
	Region2D region;
	Pixmap mask;

        if (tilePtr->flags & FIXED_TS_ORIGIN) {
            Blt_SetTSOrigin(tkwin, clientPtr, Tk_X(tkwin), Tk_Y(tkwin));
        }
         /* 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

void
Blt_Fill3DRectangleTile(tkwin, drawable, border, x, y, width, height, borderWidth, 
	relief, tilePtr, scrollTile, flags)
    Tk_Window tkwin;		/* Window for which border was allocated. */
    Drawable drawable;		/* X window or pixmap in which to draw. */
    Tk_3DBorder border;		/* Token for border to draw. */
    int x, y, width, height;	/* Outside area of rectangular region. */
    int borderWidth;		/* Desired width for border, in
				 * pixels. Border will be *inside* region. */
    int relief;			/* Indicates 3D effect: TK_RELIEF_FLAT,
				 * TK_RELIEF_RAISED, or TK_RELIEF_SUNKEN. */
    TileClient *tilePtr;
    int scrollTile;
    int flags;
{

    if (Blt_HasTile(tilePtr)==0 || (flags&1)) {
        Blt_Fill3DRectangle(tkwin, drawable, border, x, y, width, height, borderWidth, relief);
        return;
    }
    if (border && (tilePtr->tilePtr->flags & TILE_ALPHA)) {
        Blt_Fill3DRectangle(tkwin, drawable, border, x, y, width, height, borderWidth, relief);
    }
    Blt_SetTileOrigin(tkwin, tilePtr, x, 0);
    if (scrollTile) {
        Blt_SetTSOrigin(tkwin, tilePtr, x, y);
    } else {
        Blt_SetTileOrigin(tkwin, tilePtr, x, 0);
    }
    Blt_TileRectangle(tkwin, drawable, tilePtr, x, y, width, height);
    Blt_Draw3DRectangle(tkwin, drawable, border, x, y, width, height,
        borderWidth, relief);
}
