/*
 * tkImage.c --
 *
 *      This module implements the image protocol, which allows lots
 *      of different kinds of images to be used in lots of different
 *      widgets.
 *
 * Copyright (c) 1994 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkImage.c,v 1.19.2.2 2003/09/17 23:45:05 dgp Exp $
 */

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

/*
 * Each call to Tk_GetImage returns a pointer to one of the following
 * structures, which is used as a token by clients (widgets) that
 * display images.
 */

typedef struct Image {
    Tk_Window tkwin;            /* Window passed to Tk_GetImage (needed to
				 * "re-get" the image later if the manager
				 * changes). */
    Display *display;           /* Display for tkwin.  Needed because when
				 * the image is eventually freed tkwin may
				 * not exist anymore. */
    struct ImageMaster *masterPtr;
				/* Master for this image (identifiers image
				 * manager, for example). */
    ClientData instanceData;
				/* One word argument to pass to image manager
				 * when dealing with this image instance. */
    Tk_ImageChangedProc *changeProc;
				/* Code in widget to call when image changes
				 * in a way that affects redisplay. */
    ClientData widgetClientData;
				/* Argument to pass to changeProc. */
    struct Image *nextPtr;      /* Next in list of all image instances
				 * associated with the same name. */

} Image;

/*
 * For each image master there is one of the following structures,
 * which represents a name in the image table and all of the images
 * instantiated from it.  Entries in mainPtr->imageTable point to
 * these structures.
 */

typedef struct ImageMaster {
    Tk_ImageType *typePtr;      /* Information about image type.  NULL means
				 * that no image manager owns this image:  the
				 * image was deleted. */
    ClientData masterData;      /* One-word argument to pass to image mgr
				 * when dealing with the master, as opposed
				 * to instances. */
    int width, height;          /* Last known dimensions for image. */
    Tcl_HashTable *tablePtr;    /* Pointer to hash table containing image
				 * (the imageTable field in some TkMainInfo
				 * structure). */
    Tcl_HashEntry *hPtr;        /* Hash entry in mainPtr->imageTable for
				 * this structure (used to delete the hash
				 * entry). */
    Image *instancePtr;         /* Pointer to first in list of instances
				 * derived from this name. */
    int deleted;                /* Flag set when image is being deleted. */
    TkWindow *winPtr;           /* Main window of interpreter (used to
				 * detect when the world is falling apart.) */
} ImageMaster;

typedef struct ThreadSpecificData {
    Tk_ImageType *imageTypeList;/* First in a list of all known image
				 * types. */
    Tk_ImageType *oldImageTypeList;/* First in a list of all known old-style image
				 * types. */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;

/*
 * Prototypes for local procedures:
 */

static void     DeleteImage _ANSI_ARGS_((ImageMaster *masterPtr));
static void     EventuallyDeleteImage _ANSI_ARGS_((ImageMaster *masterPtr,
						   int forgetHashEntryNow));

/*
 *----------------------------------------------------------------------
 *
 * Tk_CreateOldImageType, Tk_CreateImageType --
 *
 *      This procedure is invoked by an image manager to tell Tk about
 *      a new kind of image and the procedures that manage the new type.
 *      The procedure is typically invoked during Tcl_AppInit.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The new image type is entered into a table used in the "image
 *      create" command.
 *
 *----------------------------------------------------------------------
 */

void
Tk_CreateOldImageType(typePtr)
    Tk_ImageType *typePtr;      /* Structure describing the type.  All of
				 * the fields except "nextPtr" must be filled
				 * in by caller.  Must not have been passed
				 * to Tk_CreateImageType previously. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    typePtr->nextPtr = tsdPtr->oldImageTypeList;
    tsdPtr->oldImageTypeList = typePtr;
}

void
Tk_CreateImageType(typePtr)
    Tk_ImageType *typePtr;      /* Structure describing the type.  All of
				 * the fields except "nextPtr" must be filled
				 * in by caller.  Must not have been passed
				 * to Tk_CreateImageType previously. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    typePtr->nextPtr = tsdPtr->imageTypeList;
    tsdPtr->imageTypeList = typePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_ImageObjCmd --
 *
 *      This procedure is invoked to process the "image" Tcl command.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
Tk_ImageObjCmd(clientData, interp, objc, objv)
    ClientData clientData;      /* Main window associated with interpreter. */
    Tcl_Interp *interp;         /* Current interpreter. */
    int objc;                   /* Number of arguments. */
    Tcl_Obj *CONST objv[];      /* Argument strings. */
{
    static CONST char *imageOptions[] = {
	"create", "delete", "height", "inuse", "names", "type", "types",
	    "width",          NULL
    };
    enum options {
	IMAGE_CREATE, IMAGE_DELETE, IMAGE_HEIGHT, IMAGE_INUSE, IMAGE_NAMES,
	IMAGE_TYPE, IMAGE_TYPES, IMAGE_WIDTH
    };
    TkWindow *winPtr = (TkWindow *) clientData;
    int i, new, firstOption,  index;
    Tk_ImageType *typePtr;
    ImageMaster *masterPtr;
    Image *imagePtr;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    char idString[16 + TCL_INTEGER_SPACE], *name;
    TkDisplay *dispPtr = winPtr->dispPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?args?");
	return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[1], imageOptions, "option", 0,
	    &index) != TCL_OK) {
	return TCL_ERROR;
    }
    switch ((enum options) index) {
	case IMAGE_CREATE: {
	    char *arg;
	    Tcl_Obj **args;
	    int oldimage = 0;
	    if (objc < 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "type ?name? ?options?");
		return TCL_ERROR;
	    }

	    /*
	     * Look up the image type.
	     */

	    arg = Tcl_GetString(objv[2]);
	    for (typePtr = tsdPtr->imageTypeList; typePtr != NULL;
		 typePtr = typePtr->nextPtr) {
		if ((*arg == typePtr->name[0])
			&& (strcmp(arg, typePtr->name) == 0)) {
		    break;
		}
	    }
	    if (typePtr == NULL) {
		oldimage = 1;
		for (typePtr = tsdPtr->oldImageTypeList; typePtr != NULL;
		     typePtr = typePtr->nextPtr) {
		    if ((*arg == typePtr->name[0])
			    && (strcmp(arg, typePtr->name) == 0)) {
			break;
		    }
		}
	    }
	    if (typePtr == NULL) {
		Tcl_AppendResult(interp, "image type \"", arg,
			"\" doesn't exist",          NULL);
		return TCL_ERROR;
	    }

	    /*
	     * Figure out a name to use for the new image.
	     */

	    if ((objc == 3) || (*(arg = Tcl_GetString(objv[3])) == '-')) {
                Tcl_CmdInfo dummy;
                do {
		    dispPtr->imageId++;
		    sprintf(idString, "image%d", dispPtr->imageId);
		    name = idString;
                } while (Tcl_GetCommandInfo(interp, name, &dummy) != 0);
		firstOption = 3;
	    } else {
		TkWindow *topWin;

		name = arg;
		firstOption = 4;
		/*
		 * Need to check if the _command_ that we are about to
		 * create is the name of the current master widget
		 * command (normally "." but could have been renamed)
		 * and fail in that case before a really nasty and
		 * hard to stop crash happens.
		 */
		topWin = (TkWindow *) TkToplevelWindowForCommand(interp, name);
		if (topWin != NULL && winPtr->mainPtr->winPtr == topWin) {
		    Tcl_AppendResult(interp, "images may not be named the ",
			    "same as the main window",          NULL);
		    return TCL_ERROR;
		}
	    }

	    /*
	     * Create the data structure for the new image.
	     */

	    hPtr = Tcl_CreateHashEntry(&winPtr->mainPtr->imageTable,
		    name, &new);
	    if (new) {
		masterPtr = (ImageMaster *) ckalloc(sizeof(ImageMaster));
		masterPtr->typePtr = NULL;
		masterPtr->masterData = NULL;
		masterPtr->width = masterPtr->height = 1;
		masterPtr->tablePtr = &winPtr->mainPtr->imageTable;
		masterPtr->hPtr = hPtr;
		masterPtr->instancePtr = NULL;
		masterPtr->deleted = 0;
		masterPtr->winPtr = winPtr->mainPtr->winPtr;
		Tcl_Preserve((ClientData) masterPtr->winPtr);
		Tcl_SetHashValue(hPtr, masterPtr);
	    } else {
		/*
		 * An image already exists by this name.  Disconnect the
		 * instances from the master.
		 */

		masterPtr = (ImageMaster *) Tcl_GetHashValue(hPtr);
		if (masterPtr->typePtr != NULL) {
		    for (imagePtr = masterPtr->instancePtr; imagePtr != NULL;
			 imagePtr = imagePtr->nextPtr) {
			(*masterPtr->typePtr->freeProc)(
			    imagePtr->instanceData, imagePtr->display);
			(*imagePtr->changeProc)(imagePtr->widgetClientData,
				0, 0, masterPtr->width, masterPtr->height,
				masterPtr->width, masterPtr->height);
		    }
		    (*masterPtr->typePtr->deleteProc)(masterPtr->masterData);
		    masterPtr->typePtr = NULL;
		}
	    }

	    /*
	     * Call the image type manager so that it can perform its own
	     * initialization, then re-"get" for any existing instances of
	     * the image.
	     */

	    objv += firstOption;
	    objc -= firstOption;
	    args = (Tcl_Obj **) objv;
	    if (oldimage) {
		int i;
		args = (Tcl_Obj **) ckalloc((objc+1) * sizeof(char *));
		for (i = 0; i < objc; i++) {
		    args[i] = (Tcl_Obj *) Tcl_GetString(objv[i]);
		}
		args[objc] = NULL;
	    }
	    Tcl_Preserve((ClientData) masterPtr);
	    if ((*typePtr->createProc)(interp, name, objc,
		    args, typePtr, (Tk_ImageMaster) masterPtr,
		    &masterPtr->masterData) != TCL_OK) {
		EventuallyDeleteImage(masterPtr, 0);
		Tcl_Release((ClientData) masterPtr);
		if (oldimage) {
		    ckfree((char *) args);
		}
		return TCL_ERROR;
	    }
	    Tcl_Release((ClientData) masterPtr);
	    if (oldimage) {
		ckfree((char *) args);
	    }
	    masterPtr->typePtr = typePtr;
	    for (imagePtr = masterPtr->instancePtr; imagePtr != NULL;
		 imagePtr = imagePtr->nextPtr) {
		imagePtr->instanceData = (*typePtr->getProc)(
		    imagePtr->tkwin, masterPtr->masterData);
	    }
            Tcl_SetObjResult(interp, LangObjectObj( interp, Tcl_GetHashKey(&winPtr->mainPtr->imageTable, hPtr)));
	    break;
	}
	case IMAGE_DELETE: {
	    for (i = 2; i < objc; i++) {
		char *arg = Tcl_GetString(objv[i]);
		hPtr = Tcl_FindHashEntry(&winPtr->mainPtr->imageTable, arg);
		if (hPtr == NULL) {
		    Tcl_AppendResult(interp, "image \"", arg,
			    "\" doesn't exist",          NULL);
		    return TCL_ERROR;
		}
		DeleteImage((ImageMaster *) Tcl_GetHashValue(hPtr));
	    }
	    break;
	}
	case IMAGE_HEIGHT: {
	    char *arg;
	    if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "name");
		return TCL_ERROR;
	    }
	    arg = Tcl_GetString(objv[2]);
	    hPtr = Tcl_FindHashEntry(&winPtr->mainPtr->imageTable, arg);
	    if (hPtr == NULL) {
		Tcl_AppendResult(interp, "image \"", arg,
			"\" doesn't exist",          NULL);
		return TCL_ERROR;
	    }
	    masterPtr = (ImageMaster *) Tcl_GetHashValue(hPtr);
	    Tcl_SetIntObj(Tcl_GetObjResult(interp), masterPtr->height);
	    break;
	}

	case IMAGE_INUSE: {
	    int count = 0;
	    char *arg;
	    if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "name");
		return TCL_ERROR;
	    }
	    arg = Tcl_GetString(objv[2]);
	    hPtr = Tcl_FindHashEntry(&winPtr->mainPtr->imageTable, arg);
	    if (hPtr == NULL) {
		Tcl_AppendResult(interp, "image \"", arg,
			"\" doesn't exist",          NULL);
		return TCL_ERROR;
	    }
	    masterPtr = (ImageMaster *) Tcl_GetHashValue(hPtr);
	    if (masterPtr->typePtr != NULL && masterPtr->instancePtr != NULL) {
		count = 1;
	    }
	    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), count);
	    break;
	}

	case IMAGE_NAMES: {
	    if (objc != 2) {
		Tcl_WrongNumArgs(interp, 2, objv, NULL);
		return TCL_ERROR;
	    }
	    hPtr = Tcl_FirstHashEntry(&winPtr->mainPtr->imageTable, &search);
	    for ( ; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
		Tcl_ListObjAppendElement(interp, Tcl_GetObjResult(interp),
			LangObjectObj(interp,Tcl_GetHashKey(&winPtr->mainPtr->imageTable, hPtr)));
	    }
	    break;
	}

	case IMAGE_TYPE: {
	    char *arg;
	    if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "name");
		return TCL_ERROR;
	    }
	    arg = Tcl_GetString(objv[2]);
	    hPtr = Tcl_FindHashEntry(&winPtr->mainPtr->imageTable, arg);
	    if (hPtr == NULL) {
		Tcl_AppendResult(interp, "image \"", arg,
			"\" doesn't exist",          NULL);
		return TCL_ERROR;
	    }
	    masterPtr = (ImageMaster *) Tcl_GetHashValue(hPtr);
	    if (masterPtr->typePtr != NULL) {
		Tcl_SetResult(interp, masterPtr->typePtr->name, TCL_STATIC);
	    }
	    break;
	}
	case IMAGE_TYPES: {
	    if (objc != 2) {
		Tcl_WrongNumArgs(interp, 2, objv, NULL);
		return TCL_ERROR;
	    }
	    for (typePtr = tsdPtr->imageTypeList; typePtr != NULL;
		 typePtr = typePtr->nextPtr) {
		Tcl_AppendElement(interp, typePtr->name);
	    }
	    for (typePtr = tsdPtr->oldImageTypeList; typePtr != NULL;
		 typePtr = typePtr->nextPtr) {
		Tcl_AppendElement(interp, typePtr->name);
	    }
	    break;
	}
	case IMAGE_WIDTH: {
	    char *arg;
	    if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "name");
		return TCL_ERROR;
	    }
	    arg = Tcl_GetString(objv[2]);
	    hPtr = Tcl_FindHashEntry(&winPtr->mainPtr->imageTable, arg);
	    if (hPtr == NULL) {
		Tcl_AppendResult(interp, "image \"", arg,
			"\" doesn't exist",          NULL);
		return TCL_ERROR;
	    }
	    masterPtr = (ImageMaster *) Tcl_GetHashValue(hPtr);
	    Tcl_SetIntObj(Tcl_GetObjResult(interp), masterPtr->width);
	    break;
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_ImageChanged --
 *
 *      This procedure is called by an image manager whenever something
 *      has happened that requires the image to be redrawn (some of its
 *      pixels have changed, or its size has changed).
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Any widgets that display the image are notified so that they
 *      can redisplay themselves as appropriate.
 *
 *----------------------------------------------------------------------
 */

void
Tk_ImageChanged(imageMaster, x, y, width, height, imageWidth,
	imageHeight)
    Tk_ImageMaster imageMaster; /* Image that needs redisplay. */
    int x, y;                   /* Coordinates of upper-left pixel of
				 * region of image that needs to be
				 * redrawn. */
    int width, height;          /* Dimensions (in pixels) of region of
				 * image to redraw.  If either dimension
				 * is zero then the image doesn't need to
				 * be redrawn (perhaps all that happened is
				 * that its size changed). */
    int imageWidth, imageHeight;/* New dimensions of image. */
{
    ImageMaster *masterPtr = (ImageMaster *) imageMaster;
    Image *imagePtr;

    masterPtr->width = imageWidth;
    masterPtr->height = imageHeight;
    for (imagePtr = masterPtr->instancePtr; imagePtr != NULL;
	 imagePtr = imagePtr->nextPtr) {
	(*imagePtr->changeProc)(imagePtr->widgetClientData, x, y,
		width, height, imageWidth, imageHeight);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_NameOfImage --
 *
 *      Given a token for an image master, this procedure returns
 *      the name of the image.
 *
 * Results:
 *      The return value is the string name for imageMaster.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

CONST char *
Tk_NameOfImage(imageMaster)
    Tk_ImageMaster imageMaster;         /* Token for image. */
{
    ImageMaster *masterPtr = (ImageMaster *) imageMaster;

    if (masterPtr->hPtr == NULL) {
	return NULL;
    }
    return Tcl_GetHashKey(masterPtr->tablePtr, masterPtr->hPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetImage --
 *
 *      This procedure is invoked by a widget when it wants to use
 *      a particular image in a particular window.
 *
 * Results:
 *      The return value is a token for the image.  If there is no image
 *      by the given name, then NULL is returned and an error message is
 *      left in the interp's result.
 *
 * Side effects:
 *      Tk records the fact that the widget is using the image, and
 *      it will invoke changeProc later if the widget needs redisplay
 *      (i.e. its size changes or some of its pixels change).  The
 *      caller must eventually invoke Tk_FreeImage when it no longer
 *      needs the image.
 *
 *----------------------------------------------------------------------
 */

Tk_Image
Tk_GetImage(interp, tkwin, name, changeProc, clientData)
    Tcl_Interp *interp;         /* Place to leave error message if image
				 * can't be found. */
    Tk_Window tkwin;            /* Token for window in which image will
				 * be used. */
    CONST char *name;           /* Name of desired image. */
    Tk_ImageChangedProc *changeProc;
				/* Procedure to invoke when redisplay is
				 * needed because image's pixels or size
				 * changed. */
    ClientData clientData;      /* One-word argument to pass to damageProc. */
{
    Tcl_HashEntry *hPtr;
    ImageMaster *masterPtr;
    Image *imagePtr;

    hPtr = Tcl_FindHashEntry(&((TkWindow *) tkwin)->mainPtr->imageTable, name);
    if (hPtr == NULL) {
	goto noSuchImage;
    }
    masterPtr = (ImageMaster *) Tcl_GetHashValue(hPtr);
    if (masterPtr->typePtr == NULL) {
	goto noSuchImage;
    }
    imagePtr = (Image *) ckalloc(sizeof(Image));
    imagePtr->tkwin = tkwin;
    imagePtr->display = Tk_Display(tkwin);
    imagePtr->masterPtr = masterPtr;
    imagePtr->instanceData =
	    (*masterPtr->typePtr->getProc)(tkwin, masterPtr->masterData);
    imagePtr->changeProc = changeProc;
    imagePtr->widgetClientData = clientData;
    imagePtr->nextPtr = masterPtr->instancePtr;
    masterPtr->instancePtr = imagePtr;
    return (Tk_Image) imagePtr;

    noSuchImage:
    Tcl_AppendResult(interp, "image \"", name, "\" doesn't exist",
	             NULL);
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_FreeImage --
 *
 *      This procedure is invoked by a widget when it no longer needs
 *      an image acquired by a previous call to Tk_GetImage.  For each
 *      call to Tk_GetImage there must be exactly one call to Tk_FreeImage.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The association between the image and the widget is removed.
 *
 *----------------------------------------------------------------------
 */

void
Tk_FreeImage(image)
    Tk_Image image;             /* Token for image that is no longer
				 * needed by a widget. */
{
    Image *imagePtr = (Image *) image;
    ImageMaster *masterPtr = imagePtr->masterPtr;
    Image *prevPtr;

    /*
     * Clean up the particular instance.
     */

    if (masterPtr->typePtr != NULL) {
	(*masterPtr->typePtr->freeProc)(imagePtr->instanceData,
		imagePtr->display);
    }
    prevPtr = masterPtr->instancePtr;
    if (prevPtr == imagePtr) {
	masterPtr->instancePtr = imagePtr->nextPtr;
    } else {
	while (prevPtr->nextPtr != imagePtr) {
	    prevPtr = prevPtr->nextPtr;
	}
	prevPtr->nextPtr = imagePtr->nextPtr;
    }
    ckfree((char *) imagePtr);

    /*
     * If there are no more instances left for the master, and if the
     * master image has been deleted, then delete the master too.
     */

    if ((masterPtr->typePtr == NULL) && (masterPtr->instancePtr == NULL)) {
	if (masterPtr->hPtr != NULL) {
	    Tcl_DeleteHashEntry(masterPtr->hPtr);
	}
	Tcl_Release(masterPtr->winPtr);
	ckfree((char *) masterPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_PostscriptImage --
 *
 *      This procedure is called by widgets that contain images in order
 *      to redisplay an image on the screen or an off-screen pixmap.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The image's manager is notified, and it redraws the desired
 *      portion of the image before returning.
 *
 *----------------------------------------------------------------------
 */

int
Tk_PostscriptImage(image, interp, tkwin, psinfo, x, y, width, height, prepass)
    Tk_Image image;             /* Token for image to redisplay. */
    Tcl_Interp *interp;
    Tk_Window tkwin;
    Tk_PostscriptInfo psinfo;   /* postscript info */
    int x, y;                   /* Upper-left pixel of region in image that
				 * needs to be redisplayed. */
    int width, height;          /* Dimensions of region to redraw. */
    int prepass;
{
    Image *imagePtr = (Image *) image;
    int result;
    XImage *ximage;
    Pixmap pmap;
    GC newGC;
    XGCValues gcValues;

    if (imagePtr->masterPtr->typePtr == NULL) {
	/*
	 * No master for image, so nothing to display on postscript.
	 */
	return TCL_OK;
    }

    /*
     * Check if an image specific postscript-generation function
     * exists; otherwise go on with generic code.
     */

    if (imagePtr->masterPtr->typePtr->postscriptProc != NULL) {
	return (*imagePtr->masterPtr->typePtr->postscriptProc)(
	    imagePtr->masterPtr->masterData, interp, tkwin, psinfo,
	    x, y, width, height, prepass);
    }

    if (prepass) {
	return TCL_OK;
    }

    /*
     * Create a Pixmap, tell the image to redraw itself there, and then
     * generate an XImage from the Pixmap.  We can then read pixel
     * values out of the XImage.
     */

    pmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
			width, height, Tk_Depth(tkwin));

    gcValues.foreground = WhitePixelOfScreen(Tk_Screen(tkwin));
    newGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
    if (newGC != None) {
	XFillRectangle(Tk_Display(tkwin), pmap, newGC,
		0, 0, (unsigned int)width, (unsigned int)height);
	Tk_FreeGC(Tk_Display(tkwin), newGC);
    }

    Tk_RedrawImage(image, x, y, width, height, pmap, 0, 0);

    ximage = XGetImage(Tk_Display(tkwin), pmap, 0, 0,
	    (unsigned int)width, (unsigned int)height, AllPlanes, ZPixmap);

    Tk_FreePixmap(Tk_Display(tkwin), pmap);

    if (ximage == NULL) {
	/* The XGetImage() function is apparently not
	 * implemented on this system. Just ignore it.
	 */
	return TCL_OK;
    }
    result = TkPostscriptImage(interp, tkwin, psinfo, ximage, x, y,
	    width, height);

    XDestroyImage(ximage);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_RedrawImage --
 *
 *      This procedure is called by widgets that contain images in order
 *      to redisplay an image on the screen or an off-screen pixmap.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The image's manager is notified, and it redraws the desired
 *      portion of the image before returning.
 *
 *----------------------------------------------------------------------
 */

void
Tk_RedrawImage(image, imageX, imageY, width, height, drawable,
	drawableX, drawableY)
    Tk_Image image;             /* Token for image to redisplay. */
    int imageX, imageY;         /* Upper-left pixel of region in image that
				 * needs to be redisplayed. */
    int width, height;          /* Dimensions of region to redraw. */
    Drawable drawable;          /* Drawable in which to display image
				 * (window or pixmap).  If this is a pixmap,
				 * it must have the same depth as the window
				 * used in the Tk_GetImage call for the
				 * image. */
    int drawableX, drawableY;   /* Coordinates in drawable that correspond
				 * to imageX and imageY. */
{
    Image *imagePtr = (Image *) image;

    if (imagePtr->masterPtr->typePtr == NULL) {
	/*
	 * No master for image, so nothing to display.
	 */

	return;
    }

    /*
     * Clip the redraw area to the area of the image.
     */

    if (imageX < 0) {
	width += imageX;
	drawableX -= imageX;
	imageX = 0;
    }
    if (imageY < 0) {
	height += imageY;
	drawableY -= imageY;
	imageY = 0;
    }
    if ((imageX + width) > imagePtr->masterPtr->width) {
	width = imagePtr->masterPtr->width - imageX;
    }
    if ((imageY + height) > imagePtr->masterPtr->height) {
	height = imagePtr->masterPtr->height - imageY;
    }
    (*imagePtr->masterPtr->typePtr->displayProc)(
	    imagePtr->instanceData, imagePtr->display, drawable,
	    imageX, imageY, width, height, drawableX, drawableY);
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_SizeOfImage --
 *
 *      This procedure returns the current dimensions of an image.
 *
 * Results:
 *      The width and height of the image are returned in *widthPtr
 *      and *heightPtr.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

void
Tk_SizeOfImage(image, widthPtr, heightPtr)
    Tk_Image image;             /* Token for image whose size is wanted. */
    int *widthPtr;              /* Return width of image here. */
    int *heightPtr;             /* Return height of image here. */
{
    Image *imagePtr = (Image *) image;

    *widthPtr = imagePtr->masterPtr->width;
    *heightPtr = imagePtr->masterPtr->height;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_DeleteImage --
 *
 *      Given the name of an image, this procedure destroys the
 *      image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The image is destroyed; existing instances will display as
 *      blank areas.  If no such image exists then the procedure does
 *      nothing.
 *
 *----------------------------------------------------------------------
 */

void
Tk_DeleteImage(interp, name)
    Tcl_Interp *interp;         /* Interpreter in which the image was
				 * created. */
    CONST char *name;           /* Name of image. */
{
    Tcl_HashEntry *hPtr;
    TkWindow *winPtr;

    winPtr = (TkWindow *) Tk_MainWindow(interp);
    if (winPtr == NULL) {
	return;
    }
    hPtr = Tcl_FindHashEntry(&winPtr->mainPtr->imageTable, name);
    if (hPtr == NULL) {
	return;
    }
    DeleteImage((ImageMaster *)Tcl_GetHashValue(hPtr));
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteImage --
 *
 *      This procedure is responsible for deleting an image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The connection is dropped between instances of this image and
 *      an image master.  Image instances will redisplay themselves
 *      as empty areas, but existing instances will not be deleted.
 *
 *----------------------------------------------------------------------
 */

static void
DeleteImage(masterPtr)
    ImageMaster *masterPtr;     /* Pointer to main data structure for image. */
{
    Image *imagePtr;
    Tk_ImageType *typePtr;

    typePtr = masterPtr->typePtr;
    masterPtr->typePtr = NULL;
    if (typePtr != NULL) {
	for (imagePtr = masterPtr->instancePtr; imagePtr != NULL;
		imagePtr = imagePtr->nextPtr) {
	   (*typePtr->freeProc)(imagePtr->instanceData,
		   imagePtr->display);
	   (*imagePtr->changeProc)(imagePtr->widgetClientData, 0, 0,
		    masterPtr->width, masterPtr->height, masterPtr->width,
		    masterPtr->height);
	}
	(*typePtr->deleteProc)(masterPtr->masterData);
    }
    if (masterPtr->instancePtr == NULL) {
	if (masterPtr->hPtr != NULL) {
	    Tcl_DeleteHashEntry(masterPtr->hPtr);
	}
	Tcl_Release((ClientData) masterPtr->winPtr);
	ckfree((char *) masterPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EventuallyDeleteImage --
 *
 *      Arrange for an image to be deleted when it is safe to do so.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Image will get freed, though not until it is no longer
 *      Tcl_Preserve()d by anything.  May be called multiple times on
 *      the same image without ill effects.
 *
 *----------------------------------------------------------------------
 */

static void
EventuallyDeleteImage(masterPtr, forgetHashEntryNow)
    ImageMaster *masterPtr;     /* Pointer to main data structure for image. */
    int forgetHashEntryNow;
{
    if (forgetHashEntryNow) {
	masterPtr->hPtr = NULL;
    }
    if (!masterPtr->deleted) {
	masterPtr->deleted = 1;
	Tcl_EventuallyFree((ClientData) masterPtr,
		(Tcl_FreeProc *)DeleteImage);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkDeleteAllImages --
 *
 *      This procedure is called when an application is deleted.  It
 *      calls back all of the managers for all images so that they
 *      can cleanup, then it deletes all of Tk's internal information
 *      about images.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      All information for all images gets deleted.
 *
 *----------------------------------------------------------------------
 */

void
TkDeleteAllImages(mainPtr)
    TkMainInfo *mainPtr;        /* Structure describing application that is
				 * going away. */
{
    Tcl_HashSearch search;
    Tcl_HashEntry *hPtr;

    for (hPtr = Tcl_FirstHashEntry(&mainPtr->imageTable, &search);
	    hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
	EventuallyDeleteImage((ImageMaster *) Tcl_GetHashValue(hPtr), 1);
    }
    Tcl_DeleteHashTable(&mainPtr->imageTable);
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetImageMasterData --
 *
 *      Given the name of an image, this procedure returns the type
 *      of the image and the clientData associated with its master.
 *
 * Results:
 *      If there is no image by the given name, then NULL is returned
 *      and a NULL value is stored at *typePtrPtr.  Otherwise the return
 *      value is the clientData returned by the createProc when the
 *      image was created and a pointer to the type structure for the
 *      image is stored at *typePtrPtr.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

ClientData
Tk_GetImageMasterData(interp, name, typePtrPtr)
    Tcl_Interp *interp;         /* Interpreter in which the image was
				 * created. */
    CONST char *name;           /* Name of image. */
    Tk_ImageType **typePtrPtr;  /* Points to location to fill in with
				 * pointer to type information for image. */
{
    Tcl_HashEntry *hPtr;
    TkWindow *winPtr;
    ImageMaster *masterPtr;

    winPtr = (TkWindow *) Tk_MainWindow(interp);
    hPtr = Tcl_FindHashEntry(&winPtr->mainPtr->imageTable, name);
    if (hPtr == NULL) {
	*typePtrPtr = NULL;
	return NULL;
    }
    masterPtr = (ImageMaster *) Tcl_GetHashValue(hPtr);
    *typePtrPtr = masterPtr->typePtr;
    return masterPtr->masterData;
}


/*========================================================================
 * Tile support - reimplemented avoiding BLTisms
 *========================================================================*/

typedef struct Tk_TileChange_
{
 struct Tk_TileChange_ *next;
 Tk_TileChangedProc *changeProc;
 ClientData clientData;
} Tk_TileChange;

struct Tk_Tile_
{
 Tk_Image image;
 int width;
 int height;
 Tk_Window tkwin;
 Pixmap   pixmap;
 Tk_TileChange *handlers;
};

static void             TileImageChanged _ANSI_ARGS_((ClientData clientData,
			    int x, int y, int width, int height,
			    int imgWidth, int imgHeight));

void
TileImageChanged(clientData, x, y, width, height, imgWidth, imgHeight)
ClientData clientData;
int x;
int y;
int width;
int height;
{
    Tk_Tile tile = (Tk_Tile) clientData;
    Tk_TileChange *handler;
    if (tile->pixmap == None ||
        tile->width != imgWidth || tile->height != imgHeight) {
	Tk_Window tkwin = tile->tkwin;
	if (tile->pixmap) {
	    Tk_FreePixmap(Tk_Display(tkwin),tile->pixmap);
	    tile->width  = 0;
	    tile->height = 0;
	    tile->pixmap = None;
	}
	if (imgWidth >= 0 && imgHeight >= 0) {
	    tile->pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
				imgWidth, imgHeight, Tk_Depth(tkwin));
	    if (tile->pixmap) {
		tile->width  = imgWidth;
		tile->height = imgHeight;
	    }
	}
    }
    if (tile->pixmap != None) {
	Tk_RedrawImage(tile->image, 0, 0, tile->width, tile->height,
		       tile->pixmap, 0, 0);
    }
    for (handler = tile->handlers; handler != NULL; handler = handler->next) {
	(*handler->changeProc)(handler->clientData, tile);

    }
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_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.
 *
 * Side Effects:
 *      Memory and X resources are allocated.  Bookkeeping is
 *      maintained on the tile (i.e. width, height, and name).
 *
 *----------------------------------------------------------------------
 */
Tk_Tile
Tk_GetTile(interp, tkwin, imageName)
    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 */
{
    Tk_Tile tile = (Tk_Tile) ckalloc(sizeof(struct Tk_Tile_));
    memset(tile,0,sizeof(*tile));
    tile->tkwin = tkwin;
    tile->image = Tk_GetImage(interp, tkwin, (char *) imageName,
			TileImageChanged, (ClientData) tile);
    if (!tile->image) {
	Tk_FreeTile(tile);
	return NULL;
    }
    return tile;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_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
Tk_FreeTile(tile)
    Tk_Tile tile;               /* Tile to be deleted */
{
 if (tile->image)
  Tk_FreeImage(tile->image);
 if (tile->pixmap)
  Tk_FreePixmap(Tk_Display(tile->tkwin),tile->pixmap);
 while (tile->handlers) {
    Tk_TileChange *handler = tile->handlers;
    tile->handlers = handler->next;
    ckfree((char *) handler);
 }
 ckfree((char *) tile);
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_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.
 *
 *----------------------------------------------------------------------
 */
CONST char *
Tk_NameOfTile(tile)
    Tk_Tile tile;               /* Tile to query */
{
 if (tile)
  {
   Image *imagePtr = (Image *) tile->image;
   return Tk_NameOfImage((Tk_ImageMaster)(imagePtr->masterPtr));
  }
 return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_PixmapOfTile
 *
 *      Returns the pixmap of the tile.
 *
 * Results:
 *      The X pixmap used as the tile is returned.
 *
 *----------------------------------------------------------------------
 */
Pixmap
Tk_PixmapOfTile(tile)
    Tk_Tile tile;               /* Tile to query */
{
    if (tile) {
	if (tile->image && tile->pixmap == None) {
	    Tk_Window tkwin = tile->tkwin;
	    int width  = 0;
	    int height = 0;
	    Tk_SizeOfImage(tile->image,&width,&height);
	    if (width >= 0 && height >= 0) {
		Tk_MakeWindowExist(tkwin);
		tile->pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
				width, height, Tk_Depth(tkwin));
		if (tile->pixmap != None) {
		    tile->width  = width;
		    tile->height = height;
		    Tk_RedrawImage(tile->image, 0, 0, width, height,  tile->pixmap, 0, 0);
		}
	    }
	}
	return tile->pixmap;
    }
    return None;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_SizeOfTile
 *
 *      Returns the width and height of the tile.
 *
 * Results:
 *      The width and height of the tile are returned.
 *
 *----------------------------------------------------------------------
 */
void
Tk_SizeOfTile(tile, widthPtr, heightPtr)
    Tk_Tile tile;               /* Tile to query */
    int *widthPtr, *heightPtr;  /* Returned dimensions of the tile (out) */
{
    if (tile) {
	if (tile->image && tile->pixmap == None) {
	    Tk_SizeOfImage(tile->image,widthPtr,heightPtr);
	}
	else {
	    *widthPtr  = tile->height;
	    *heightPtr = tile->width;
	}
    }
    else {
	*widthPtr  = 0;
	*heightPtr = 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_SetTileChangedProc
 *
 *      Sets the routine to called when an image changes.  If
 *      *changeProc* is NULL, no callback will be performed.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      The designated routine will be called the next time the
 *      image associated with the tile changes.
 *
 *----------------------------------------------------------------------
 */
void
Tk_SetTileChangedProc(tile, changeProc, clientData)
    Tk_Tile tile;               /* Tile to query */
    Tk_TileChangedProc *changeProc;
    ClientData clientData;
{
    if (tile) {
	Tk_TileChange **link = &tile->handlers;
	Tk_TileChange *handler;

	while ((handler = *link)) {
	    if (handler->clientData == clientData) {
		break;
	    }
	    link = &handler->next;
	}
	if (changeProc == NULL ) {
	    if (handler) {
		*link = handler->next;
		ckfree((char *) handler);
	    }
	}
	else {
	    if (handler == NULL) {
		handler = (Tk_TileChange *) ckalloc(sizeof(Tk_TileChange));
		memset(handler,0,sizeof(*handler));
		handler->next = NULL;
		*link = handler;
		handler->clientData = clientData;
	    }
	    handler->changeProc = changeProc;
	}
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Tk_SetTSOrigin --
 *
 *      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
Tk_SetTSOrigin(tkwin, gc, x, y)
    Tk_Window tkwin;
    GC gc;
    int x, y;
{
    while (!Tk_TopWinHierarchy(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), gc, x, y);
}


