/* 
 * tkCursor.c --
 *
 *	This file maintains a database of read-only cursors for the Tk
 *	toolkit.  This allows cursors to be shared between widgets and
 *	also avoids round-trips to the X server.
 *
 * Copyright (c) 1990-1994 The Regents of the University of California.
 * Copyright (c) 1994-1995 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkCursor.c,v 1.2 1998/09/14 18:23:09 stanton Exp $
 */

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

/*
 * A TkCursor structure exists for each cursor that is currently
 * active.  Each structure is indexed with two hash tables defined
 * below.  One of the tables is idTable, and the other is either
 * nameTable or dataTable, also defined below.
 */

/*
 * Hash table to map from a textual description of a cursor to the
 * TkCursor record for the cursor, and key structure used in that
 * hash table:
 */

static Tcl_HashTable nameTable;
typedef struct {
    Tk_Uid name;		/* Textual name for desired cursor. */
    Display *display;		/* Display for which cursor will be used. */
} NameKey;

/*
 * Hash table to map from a collection of in-core data about a
 * cursor (bitmap contents, etc.) to a TkCursor structure:
 */

static Tcl_HashTable dataTable;
typedef struct {
    char *source;		/* Cursor bits. */
    char *mask;			/* Mask bits. */
    int width, height;		/* Dimensions of cursor (and data
				 * and mask). */
    int xHot, yHot;		/* Location of cursor hot-spot. */
    Tk_Uid fg, bg;		/* Colors for cursor. */
    Display *display;		/* Display on which cursor will be used. */
} DataKey;

/*
 * Hash table that maps from <display + cursor id> to the TkCursor structure
 * for the cursor.  This table is used by Tk_FreeCursor.
 */

static Tcl_HashTable idTable;
typedef struct {
    Display *display;		/* Display for which cursor was allocated. */
    Tk_Cursor cursor;		/* Cursor identifier. */
} IdKey;

static int initialized = 0;	/* 0 means static structures haven't been
				 * initialized yet. */

/*
 * Forward declarations for procedures defined in this file:
 */

static void		CursorInit _ANSI_ARGS_((void));

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetCursor --
 *
 *	Given a string describing a cursor, locate (or create if necessary)
 *	a cursor that fits the description.
 *
 * Results:
 *	The return value is the X identifer for the desired cursor,
 *	unless string couldn't be parsed correctly.  In this case,
 *	None is returned and an error message is left in interp->result.
 *	The caller should never modify the cursor that is returned, and
 *	should eventually call Tk_FreeCursor when the cursor is no longer
 *	needed.
 *
 * Side effects:
 *	The cursor is added to an internal database with a reference count.
 *	For each call to this procedure, there should eventually be a call
 *	to Tk_FreeCursor, so that the database can be cleaned up when cursors
 *	aren't needed anymore.
 *
 *----------------------------------------------------------------------
 */

Tk_Cursor
Tk_GetCursor(interp, tkwin, string)
    Tcl_Interp *interp;		/* Interpreter to use for error reporting. */
    Tk_Window tkwin;		/* Window in which cursor will be used. */
    Tk_Uid string;		/* Description of cursor.  See manual entry
				 * for details on legal syntax. */
{
    NameKey nameKey;
    IdKey idKey;
    Tcl_HashEntry *nameHashPtr, *idHashPtr;
    register TkCursor *cursorPtr;
    int new;

    if (!initialized) {
	CursorInit();
    }

    nameKey.name = string;
    nameKey.display = Tk_Display(tkwin);
    nameHashPtr = Tcl_CreateHashEntry(&nameTable, (char *) &nameKey, &new);
    if (!new) {
	cursorPtr = (TkCursor *) Tcl_GetHashValue(nameHashPtr);
	cursorPtr->refCount++;
	return cursorPtr->cursor;
    }

    cursorPtr = TkGetCursorByName(interp, tkwin, string);

    if (cursorPtr == NULL) {
	Tcl_DeleteHashEntry(nameHashPtr);
	return None;
    }

    /*
     * Add information about this cursor to our database.
     */

    cursorPtr->refCount = 1;
    cursorPtr->otherTable = &nameTable;
    cursorPtr->hashPtr = nameHashPtr;
    idKey.display = nameKey.display;
    idKey.cursor = cursorPtr->cursor;
    idHashPtr = Tcl_CreateHashEntry(&idTable, (char *) &idKey, &new);
    if (!new) {
	panic("cursor already registered in Tk_GetCursor");
    }
    Tcl_SetHashValue(nameHashPtr, cursorPtr);
    Tcl_SetHashValue(idHashPtr, cursorPtr);

    return cursorPtr->cursor;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetCursorFromData --
 *
 *	Given a description of the bits and colors for a cursor,
 *	make a cursor that has the given properties.
 *
 * Results:
 *	The return value is the X identifer for the desired cursor,
 *	unless it couldn't be created properly.  In this case, None is
 *	returned and an error message is left in interp->result.  The
 *	caller should never modify the cursor that is returned, and
 *	should eventually call Tk_FreeCursor when the cursor is no
 *	longer needed.
 *
 * Side effects:
 *	The cursor is added to an internal database with a reference count.
 *	For each call to this procedure, there should eventually be a call
 *	to Tk_FreeCursor, so that the database can be cleaned up when cursors
 *	aren't needed anymore.
 *
 *----------------------------------------------------------------------
 */

Tk_Cursor
Tk_GetCursorFromData(interp, tkwin, source, mask, width, height,
	xHot, yHot, fg, bg)
    Tcl_Interp *interp;		/* Interpreter to use for error reporting. */
    Tk_Window tkwin;		/* Window in which cursor will be used. */
    char *source;		/* Bitmap data for cursor shape. */
    char *mask;			/* Bitmap data for cursor mask. */
    int width, height;		/* Dimensions of cursor. */
    int xHot, yHot;		/* Location of hot-spot in cursor. */
    Tk_Uid fg;			/* Foreground color for cursor. */
    Tk_Uid bg;			/* Background color for cursor. */
{
    DataKey dataKey;
    IdKey idKey;
    Tcl_HashEntry *dataHashPtr, *idHashPtr;
    register TkCursor *cursorPtr;
    int new;
    XColor fgColor, bgColor;

    if (!initialized) {
	CursorInit();
    }

    dataKey.source = source;
    dataKey.mask = mask;
    dataKey.width = width;
    dataKey.height = height;
    dataKey.xHot = xHot;
    dataKey.yHot = yHot;
    dataKey.fg = fg;
    dataKey.bg = bg;
    dataKey.display = Tk_Display(tkwin);
    dataHashPtr = Tcl_CreateHashEntry(&dataTable, (char *) &dataKey, &new);
    if (!new) {
	cursorPtr = (TkCursor *) Tcl_GetHashValue(dataHashPtr);
	cursorPtr->refCount++;
	return cursorPtr->cursor;
    }

    /*
     * No suitable cursor exists yet.  Make one using the data
     * available and add it to the database.
     */

    if (XParseColor(dataKey.display, Tk_Colormap(tkwin), fg, &fgColor) == 0) {
	Tcl_AppendResult(interp, "invalid color name \"", fg, "\"",
		(char *) NULL);
	goto error;
    }
    if (XParseColor(dataKey.display, Tk_Colormap(tkwin), bg, &bgColor) == 0) {
	Tcl_AppendResult(interp, "invalid color name \"", bg, "\"",
		(char *) NULL);
	goto error;
    }

    cursorPtr = TkCreateCursorFromData(tkwin, source, mask, width, height,
	    xHot, yHot, fgColor, bgColor);

    if (cursorPtr == NULL) {
	goto error;
    }

    cursorPtr->refCount = 1;
    cursorPtr->otherTable = &dataTable;
    cursorPtr->hashPtr = dataHashPtr;
    idKey.display = dataKey.display;
    idKey.cursor = cursorPtr->cursor;
    idHashPtr = Tcl_CreateHashEntry(&idTable, (char *) &idKey, &new);
    if (!new) {
	panic("cursor already registered in Tk_GetCursorFromData");
    }
    Tcl_SetHashValue(dataHashPtr, cursorPtr);
    Tcl_SetHashValue(idHashPtr, cursorPtr);
    return cursorPtr->cursor;

    error:
    Tcl_DeleteHashEntry(dataHashPtr);
    return None;
}

/*
 *--------------------------------------------------------------
 *
 * Tk_NameOfCursor --
 *
 *	Given a cursor, return a textual string identifying it.
 *
 * Results:
 *	If cursor was created by Tk_GetCursor, then the return
 *	value is the "string" that was used to create it.
 *	Otherwise the return value is a string giving the X
 *	identifier for the cursor.  The storage for the returned
 *	string is only guaranteed to persist up until the next
 *	call to this procedure.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

char *
Tk_NameOfCursor(display, cursor)
    Display *display;		/* Display for which cursor was allocated. */
    Tk_Cursor cursor;		/* Identifier for cursor whose name is
				 * wanted. */
{
    IdKey idKey;
    Tcl_HashEntry *idHashPtr;
    TkCursor *cursorPtr;
    static char string[20];

    if (!initialized) {
	printid:
	sprintf(string, "cursor id 0x%x", (unsigned int) cursor);
	return string;
    }
    idKey.display = display;
    idKey.cursor = cursor;
    idHashPtr = Tcl_FindHashEntry(&idTable, (char *) &idKey);
    if (idHashPtr == NULL) {
	goto printid;
    }
    cursorPtr = (TkCursor *) Tcl_GetHashValue(idHashPtr);
    if (cursorPtr->otherTable != &nameTable) {
	goto printid;
    }
    return ((NameKey *) cursorPtr->hashPtr->key.words)->name;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_FreeCursor --
 *
 *	This procedure is called to release a cursor allocated by
 *	Tk_GetCursor or TkGetCursorFromData.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The reference count associated with cursor is decremented, and
 *	it is officially deallocated if no-one is using it anymore.
 *
 *----------------------------------------------------------------------
 */

void
Tk_FreeCursor(display, cursor)
    Display *display;		/* Display for which cursor was allocated. */
    Tk_Cursor cursor;		/* Identifier for cursor to be released. */
{
    IdKey idKey;
    Tcl_HashEntry *idHashPtr;
    register TkCursor *cursorPtr;

    if (!initialized) {
	panic("Tk_FreeCursor called before Tk_GetCursor");
    }

    idKey.display = display;
    idKey.cursor = cursor;
    idHashPtr = Tcl_FindHashEntry(&idTable, (char *) &idKey);
    if (idHashPtr == NULL) {
	panic("Tk_FreeCursor received unknown cursor argument");
    }
    cursorPtr = (TkCursor *) Tcl_GetHashValue(idHashPtr);
    cursorPtr->refCount--;
    if (cursorPtr->refCount == 0) {
	Tcl_DeleteHashEntry(cursorPtr->hashPtr);
	Tcl_DeleteHashEntry(idHashPtr);
	TkFreeCursor(cursorPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CursorInit --
 *
 *	Initialize the structures used for cursor management.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Read the code.
 *
 *----------------------------------------------------------------------
 */

static void
CursorInit()
{
    initialized = 1;
    Tcl_InitHashTable(&nameTable, sizeof(NameKey)/sizeof(int));
    Tcl_InitHashTable(&dataTable, sizeof(DataKey)/sizeof(int));

    /*
     * The call below is tricky:  can't use sizeof(IdKey) because it
     * gets padded with extra unpredictable bytes on some 64-bit
     * machines.
     */

    Tcl_InitHashTable(&idTable, (sizeof(Display *) + sizeof(Tk_Cursor))
	    /sizeof(int));
}
