/*
 * 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 "bltImage.h"
#include <X11/Xutil.h>

#define TILE_THREAD_KEY	"BLT 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 */

    Pixmap pixmap;		/* Pixmap generated from image. */
    GC gc;			/* GC */
    Tk_Image tkImage;		/* Tk image token. */
    Blt_Chain *chainPtr;	/* Chain of clients sharing this tile. */
} Server;

#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. */
typedef struct {
    unsigned int magic;
    Tk_Window tkwin;		/* Window of client. */
    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. */
    Server *serverPtr;		/* Pointer to actual tile information */
    Blt_ChainLink *linkPtr;	/* Pointer to client entry in the server's
				 * client list.  Used to delete the client */
} Client;

typedef struct {
    Display *display;
    Tk_Uid nameId;
    int depth;
} TileKey;
    
static TileInterpData *GetTileInterpData _ANSI_ARGS_((Tcl_Interp *interp));

#ifdef __STDC__
static Tcl_IdleProc RedrawTile;
static Tk_ImageChangedProc ImageChangedProc;
static Tcl_InterpDeleteProc TileInterpDeleteProc;
#endif

static void DestroyClient _ANSI_ARGS_((Client *clientPtr));
static void DestroyServer _ANSI_ARGS_((Server *serverPtr));

/*
 *----------------------------------------------------------------------
 *
 * RedrawTile
 *
 *	It would be better if Tk checked for NULL proc pointers.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
/* ARGSUSED */
static void
RedrawTile(clientData)
    ClientData clientData;
{
    Server *serverPtr = (Server *) clientData;
    Client *clientPtr;
    Blt_ChainLink *linkPtr;

    serverPtr->flags &= ~NOTIFY_PENDING;
    if (Blt_TkImageDeleted(serverPtr->tkImage)) {
	if (serverPtr->pixmap != None) {
	    Tk_FreePixmap(serverPtr->display, serverPtr->pixmap);
	}
	serverPtr->pixmap = None;
    } else {
	int width, height;
	Pixmap pixmap;
	unsigned long gcMask;
	XGCValues gcValues;
	GC newGC;

	/* Pick a window used by any client to generate the new pixmap. */
	linkPtr = Blt_ChainFirstLink(serverPtr->chainPtr);
	clientPtr = (Client *)Blt_ChainGetValue(linkPtr);

	/*
	 * 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.
	 */
	Tk_SizeOfImage(serverPtr->tkImage, &width, &height);
	pixmap = Tk_GetPixmap(Tk_Display(clientPtr->tkwin), 
		Tk_WindowId(clientPtr->tkwin), width, height,
		Tk_Depth(clientPtr->tkwin));
	if (serverPtr->pixmap != None) {
	    Tk_FreePixmap(Tk_Display(clientPtr->tkwin), serverPtr->pixmap);
	}
	serverPtr->pixmap = pixmap;
	Tk_RedrawImage(serverPtr->tkImage, 0, 0, width, height, 
	    (Drawable)serverPtr->pixmap, 0, 0);
	gcMask = (GCTile | GCFillStyle);
	gcValues.fill_style = FillTiled;
	gcValues.tile = pixmap;
	newGC = Tk_GetGC(clientPtr->tkwin, gcMask, &gcValues);
	if (serverPtr->gc != NULL) {
	    Tk_FreeGC(serverPtr->display, serverPtr->gc);
	}
	serverPtr->gc = newGC;
    }
    /*
     * Now call back each of the tile clients to notify them that the
     * pixmap has changed.
     */
    for (linkPtr = Blt_ChainFirstLink(serverPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	clientPtr = (Client *)Blt_ChainGetValue(linkPtr);
	if (clientPtr->notifyProc != NULL) {
	    (*clientPtr->notifyProc) (clientPtr->clientData, 
		      (Blt_Tile)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. */
{
    Server *serverPtr = (Server *) clientData;

    if (!(serverPtr->flags & NOTIFY_PENDING)) {
	Tcl_DoWhenIdle(RedrawTile, (ClientData)serverPtr);
	serverPtr->flags |= NOTIFY_PENDING;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyServer --
 *
 *	Deletes the tile server structure, including the pixmap
 *	representing the tile.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyServer(serverPtr)
    Server *serverPtr;
{
    Blt_ChainLink *linkPtr;
    Client *clientPtr;

    if (serverPtr->flags & NOTIFY_PENDING) {
	Tcl_CancelIdleCall(RedrawTile, (ClientData)serverPtr);
    }
    for (linkPtr = Blt_ChainFirstLink(serverPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	clientPtr = (Client *)Blt_ChainGetValue(linkPtr);
	clientPtr->linkPtr = NULL;
	DestroyClient(clientPtr);
    }
    Blt_ChainDestroy(serverPtr->chainPtr);

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

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

/*
 *----------------------------------------------------------------------
 *
 * CreateServer --
 *
 *	Creates a tile server.  A tile server manages a single pixmap
 *	which it draws into whenever the image changes.  Clients use
 *	this pixmap and are notified by the server (if requested)
 *	whenever they need to reuse the pixmap.
 *
 * Results:
 *	A pointer to the new tile server.  If the image name does not
 *	represent a Tk image, NULL instead.
 *
 *----------------------------------------------------------------------
 */
static Server *
CreateServer(interp, tkwin, imageName)
    Tcl_Interp *interp;
    Tk_Window tkwin;
    char *imageName;
{
    unsigned long gcMask;
    XGCValues gcValues;
    Server *serverPtr;
    Tk_Image tkImage;
    int width, height;
    Pixmap pixmap;
    Window root;

    serverPtr = (Server *) calloc(1, sizeof(Server));
    assert(serverPtr);

    /*
     * Initialize the tile server.
     */
    serverPtr->display = Tk_Display(tkwin);
    serverPtr->interp = interp;

    /*
     * Get the image. Funnel all change notifications to a single routine.
     */
    tkImage = Tk_GetImage(interp, tkwin, imageName, ImageChangedProc,
	(ClientData)serverPtr);
    if (tkImage == NULL) {
	free((char *)serverPtr);
	return NULL;
    }
    /*
     * Create a pixmap the same size and draw the image into it.
     */
    Tk_SizeOfImage(tkImage, &width, &height);
    root = RootWindow(serverPtr->display, Tk_ScreenNumber(tkwin));
    pixmap = Tk_GetPixmap(serverPtr->display, root, width, height, Tk_Depth(tkwin));
    Tk_RedrawImage(tkImage, 0, 0, width, height, pixmap, 0, 0);

    gcMask = (GCTile | GCFillStyle);
    gcValues.fill_style = FillTiled;
    gcValues.tile = pixmap;

    serverPtr->gc = Tk_GetGC(tkwin, gcMask, &gcValues);
    serverPtr->pixmap = pixmap;
    serverPtr->tkImage = tkImage;
    serverPtr->name = strdup(imageName);
    serverPtr->chainPtr = Blt_ChainCreate();
    return serverPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyClient --
 *
 *	Deletes bookkeeping for a client of a tile.  The client is
 *	removed from the server's list of clients and memory if
 *	released.
 *
 *	If the server has no more clients, the server is also deleted.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyClient(clientPtr)
    Client *clientPtr;
{
    Server *serverPtr;
    serverPtr = clientPtr->serverPtr;

    /* Remove the client from the server's list */
    if (clientPtr->linkPtr != NULL) {
	Blt_ChainDeleteLink(serverPtr->chainPtr, clientPtr->linkPtr);
    }
    if (Blt_ChainGetLength(serverPtr->chainPtr) == 0) {
	/*
	 * If there are no more clients of the tile, then remove the
	 * pixmap, image, and the server record.
	 */
	DestroyServer(serverPtr);
    }
    free((char *)clientPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateClient --
 *
 *	Creates bookkeeping for a client of a tile.  The tile it
 *	uses is keyed by the display and image name.   The client
 *	is added to the server's list of clients.  It will be notified
 *	(if requested) whenever the tile changes. If no server exists
 *	already, one is created.
 *
 * Results:
 *	A pointer to the newly created client (i.e. tile).
 *
 *----------------------------------------------------------------------
 */
static Client *
CreateClient(interp, tkwin, name)
    Tcl_Interp *interp;
    Tk_Window tkwin;
    char *name;
{
    Client *clientPtr;
    Server *serverPtr;
    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) {
	serverPtr = CreateServer(interp, tkwin, name);
	if (serverPtr == NULL) {
	    return NULL;
	}
	serverPtr->hashPtr = hPtr;
	Tcl_SetHashValue(hPtr, (ClientData)serverPtr);
    } else {
	serverPtr = (Server *)Tcl_GetHashValue(hPtr);
    }
    clientPtr = (Client *)calloc(1, sizeof(Client));
    assert(clientPtr);

    /* Initialize client information. */
    clientPtr->magic = TILE_MAGIC;
    clientPtr->tkwin = tkwin;
    clientPtr->linkPtr = Blt_ChainAppend(serverPtr->chainPtr,
	(ClientData)clientPtr);
    clientPtr->serverPtr = serverPtr;
    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, interp)
    ClientData clientData;	/* Thread-specific data. */
    Tcl_Interp *interp;
{
    TileInterpData *dataPtr = (TileInterpData *)clientData;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Server *serverPtr;
    
    for (hPtr = Tcl_FirstHashEntry(&(dataPtr->tileTable), &cursor);
	 hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	serverPtr = (Server *)Tcl_GetHashValue(hPtr);
	serverPtr->hashPtr = NULL;
	DestroyServer(serverPtr);
    }
    Tcl_DeleteHashTable(&(dataPtr->tileTable));
    Tcl_DeleteAssocData(interp, TILE_THREAD_KEY);
    free((char *)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 = (TileInterpData *)malloc(sizeof(TileInterpData));
	assert(dataPtr);
	dataPtr->interp = interp;
	Tcl_SetAssocData(interp, TILE_THREAD_KEY, TileInterpDeleteProc, 
		(ClientData)dataPtr);
	Tcl_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(interp, tkwin, imageName, tilePtr)
    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 *tilePtr;		/* (out) Returns the allocated tile. */
{
    Client *clientPtr;

    clientPtr = CreateClient(interp, tkwin, imageName);
    if (clientPtr == NULL) {
	return TCL_ERROR;
    } 
    *tilePtr = (Blt_Tile)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(tile)
    Blt_Tile tile;		/* Tile to be deleted */
{
    Client *clientPtr = (Client *)tile;

    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	return;			/* No tile */
    }
    DestroyClient(clientPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * 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(tile)
    Blt_Tile tile;		/* Tile to query */
{
    Client *clientPtr = (Client *)tile;

    if (clientPtr == NULL) {
	return "";
    }
    if (clientPtr->magic != TILE_MAGIC) {
	return "not a tile";
    }
    return clientPtr->serverPtr->name;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_PixmapOfTile
 *
 *	Returns the pixmap of the tile.
 *
 * Results:
 *	The X pixmap used as the tile is returned.
 *
 *----------------------------------------------------------------------
 */
/*LINTLIBRARY*/
Pixmap
Blt_PixmapOfTile(tile)
    Blt_Tile tile;		/* Tile to query */
{
    Client *clientPtr = (Client *)tile;

    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	return None;
    }
    return clientPtr->serverPtr->pixmap;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_SizeOfTile
 *
 *	Returns the width and height of the tile.
 *
 * Results:
 *	The width and height of the tile are returned.
 *
 *----------------------------------------------------------------------
 */
/*LINTLIBRARY*/
void
Blt_SizeOfTile(tile, widthPtr, heightPtr)
    Blt_Tile tile;		/* Tile to query */
    int *widthPtr, *heightPtr;	/* Returned dimensions of the tile (out) */
{
    Client *clientPtr = (Client *)tile;

    if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
	*widthPtr = *heightPtr = 0;
	return;			/* No tile given. */
    }
    Tk_SizeOfImage(clientPtr->serverPtr->tkImage, widthPtr, heightPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * 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(tile, notifyProc, clientData)
    Blt_Tile tile;		/* Tile to query */
    Blt_TileChangedProc *notifyProc;
    ClientData clientData;
{
    Client *clientPtr = (Client *)tile;

    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(tkwin, tile, x, y)
    Tk_Window tkwin;
    Blt_Tile tile;
    int x, y;
{
    Client *clientPtr = (Client *)tile;

    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->serverPtr->gc, -x, -y);
}

void
Blt_SetTSOrigin(tkwin, tile, x, y)
    Tk_Window tkwin;
    Blt_Tile tile;
    int x, y;
{
    Client *clientPtr = (Client *)tile;

    XSetTSOrigin(Tk_Display(tkwin), clientPtr->serverPtr->gc, x, y);
}

void
Blt_TileRectangles(display, drawable, tile, rectArr, nRects)
    Display *display;
    Drawable drawable;
    Blt_Tile tile;
    XRectangle rectArr[];
    int nRects;
{
    Client *clientPtr = (Client *)tile;

    XFillRectangles(display, drawable, clientPtr->serverPtr->gc, rectArr, 
	nRects);
}

void
Blt_TileRectangle(display, drawable, tile, x, y, width, height)
    Display *display;
    Drawable drawable;
    Blt_Tile tile;
    int x, y;
    unsigned int width, height;
{
    Client *clientPtr = (Client *)tile;

    XFillRectangle(display, drawable, clientPtr->serverPtr->gc, x, y, width, 
	height);
}

void
Blt_TilePolygon(display, drawable, tile, pointArr, nPoints, shape, mode)
    Display *display;
    Drawable drawable;
    Blt_Tile tile;
    XPoint pointArr[];
    int nPoints;
    int shape, mode;
{
    Client *clientPtr = (Client *)tile;

    XFillPolygon(display, drawable, clientPtr->serverPtr->gc, pointArr, nPoints,
	Convex, CoordModeOrigin);
}
