/*
 * tkTreeDrag.c --
 *
 *	This module implements outline dragging for treectrl widgets.
 *
 * Copyright (c) 2002-2011 Tim Baker
 */

#include "tkTreeCtrl.h"

typedef struct TreeDragImage_ TreeDragImage_;
typedef struct DragElem DragElem;

/*
 * The following structure holds info about a single element of the drag
 * image.
 */
struct DragElem
{
    int x, y, width, height;
    DragElem *next;
};

/*
 * The following structure holds info about the drag image. There is one of
 * these per TreeCtrl.
 */
struct TreeDragImage_
{
    TreeCtrl *tree;
    Tk_OptionTable optionTable;
    int visible;
    int x, y; /* offset to draw at in canvas coords */
    TreeRectangle bounds; /* bounds of all DragElems */
    DragElem *elem;
    int onScreen; /* TRUE if is displayed */
    int sx, sy; /* Window coords where displayed */
    int sw, sh; /* Width/height of previously-displayed image */
#ifdef DRAG_PIXMAP
    int pixmapW, pixmapH;
    Pixmap pixmap;
    Tk_Image image;
#endif /* DRAG_PIXMAP */
#ifdef DRAGIMAGE_STYLE
    TreeStyle masterStyle; /* Style to create the drag image from. */
    TreeStyle instanceStyle; /* Style to create the drag image from. */
    int styleX, styleY; /* Mouse cursor hotspot offset into dragimage. */
    int styleW, styleH; /* Width/Height of dragimage style. */
    int pixmapW, pixmapH; /* Width/Height of 'pixmap'. */
    Pixmap pixmap; /* Pixmap -> Tk_Image. */
    Tk_Image tkimage; /* Transparent image that is drawn in the widget. */
#endif /* DRAGIMAGE_STYLE */
};

#define DRAG_CONF_VISIBLE		0x0001

static Tk_OptionSpec optionSpecs[] = {
#ifdef DRAGIMAGE_STYLE
    {TK_OPTION_CUSTOM, "-style", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeDragImage_, masterStyle),
     TK_OPTION_NULL_OK, (ClientData) &TreeCtrlCO_style, 0},
#endif /* DRAGIMAGE_STYLE */
    {TK_OPTION_BOOLEAN, "-visible", (char *) NULL, (char *) NULL,
	"0", -1, Tk_Offset(TreeDragImage_, visible),
	0, (ClientData) NULL, DRAG_CONF_VISIBLE},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, 0, -1, 0, 0, 0}
};

#ifdef DRAG_PIXMAP
static void
UpdateImage(
    TreeDragImage dragImage	/* Drag image record. */
    )
{
    TreeCtrl *tree = dragImage->tree;
    Tk_PhotoHandle photoH;
    XImage *ximage;
    int width = TreeRect_Width(dragImage->bounds);
    int height = TreeRect_Height(dragImage->bounds);
    int alpha = 128;
    XColor *colorPtr;

    if (dragImage->image != NULL) {
	Tk_FreeImage(dragImage->image);
	dragImage->image = NULL;
    }

    photoH = Tk_FindPhoto(tree->interp, "::TreeCtrl::ImageDrag");
    if (photoH == NULL) {
	Tcl_GlobalEval(tree->interp, "image create photo ::TreeCtrl::ImageDrag");
	photoH = Tk_FindPhoto(tree->interp, "::TreeCtrl::ImageDrag");
	if (photoH == NULL)
	    return;
    }

    /* Pixmap -> XImage */
    ximage = XGetImage(tree->display, dragImage->pixmap, 0, 0,
	    (unsigned int)width, (unsigned int)height, AllPlanes, ZPixmap);
    if (ximage == NULL)
	Tcl_Panic("tkTreeDrag.c:UpdateImage() ximage is NULL");

    /* XImage -> Tk_Image */
    colorPtr = Tk_GetColor(tree->interp, tree->tkwin, "pink");
    Tree_XImage2Photo(tree->interp, photoH, ximage, colorPtr->pixel, alpha);

    XDestroyImage(ximage);

    dragImage->image = Tk_GetImage(tree->interp, tree->tkwin,
	"::TreeCtrl::ImageDrag", NULL, (ClientData) NULL);
}

static void
UpdatePixmap(
    TreeDragImage dragImage	/* Drag image record. */
    )
{
    TreeCtrl *tree = dragImage->tree;
    int w, h;
    XColor *colorPtr;
    GC gc;
    DragElem *elem;
    unsigned long trans;

    w = TreeRect_Width(dragImage->bounds);
    h = TreeRect_Height(dragImage->bounds);
    if (w > dragImage->pixmapW || h > dragImage->pixmapH) {
	if (dragImage->pixmap != None)
	    Tk_FreePixmap(tree->display, dragImage->pixmap);
	dragImage->pixmap = Tk_GetPixmap(tree->display,
	    Tk_WindowId(tree->tkwin),
	    w, h, Tk_Depth(tree->tkwin));

	dragImage->pixmapW = w;
	dragImage->pixmapH = h;
    }

    colorPtr = Tk_GetColor(tree->interp, tree->tkwin, "pink");
    gc = Tk_GCForColor(colorPtr, Tk_WindowId(tree->tkwin));
    XFillRectangle(tree->display, dragImage->pixmap, gc,
	0, 0, w, h);

    trans = colorPtr->pixel;

    colorPtr = Tk_GetColor(tree->interp, tree->tkwin, "gray50");
    gc = Tk_GCForColor(colorPtr, Tk_WindowId(tree->tkwin));

    for (elem = dragImage->elem; elem != NULL; elem = elem->next) {
	XFillRectangle(tree->display, dragImage->pixmap, gc,
	    elem->x - TreeRect_Left(dragImage->bounds),
	    elem->y - TreeRect_Top(dragImage->bounds),
	    elem->width, elem->height);
    }

    if (dragImage->image != NULL) {
	Tk_FreeImage(dragImage->image);
	dragImage->image = NULL;
    }
}

static void
DrawPixmap(
    TreeDragImage dragImage,	/* Drag image record. */
    TreeDrawable td
    )
{
    TreeCtrl *tree = dragImage->tree;
    int ix, iy, iw, ih;

    if (!dragImage->visible)
	return;

    if (dragImage->image == NULL)
	UpdateImage(dragImage);

    if (dragImage->image == NULL)
	return;

    ix = iy = 0;
    iw = TreeRect_Width(dragImage->bounds);
    ih = TreeRect_Height(dragImage->bounds);

    /* FIXME: clip src image to area to be redrawn */

    Tree_RedrawImage(dragImage->image, ix, iy, iw, ih, td,
	dragImage->x + TreeRect_Left(dragImage->bounds) - tree->drawableXOrigin,
	dragImage->y + TreeRect_Top(dragImage->bounds) - tree->drawableYOrigin);
}
#endif /* DRAG_PIXMAP */

#ifdef DRAGIMAGE_STYLE
void
TreeDragImage_StyleDeleted(
    TreeDragImage dragImage,	/* Drag image record. */
    TreeStyle style		/* Style that was deleted. */
    )
{
    TreeCtrl *tree = dragImage->tree;

    if (dragImage->masterStyle == style) {
	TreeStyle_FreeResources(tree, dragImage->instanceStyle);
	dragImage->masterStyle = NULL;
	dragImage->instanceStyle = NULL;
    }
}

static void
DragImage_UpdateStyleTkImage(
    TreeDragImage dragImage	/* Drag image record. */
    )
{
    TreeCtrl *tree = dragImage->tree;
    Tk_PhotoHandle photoH;
    XImage *ximage;
    int width = dragImage->styleW;
    int height = dragImage->styleH;
    int alpha = 128;
    XColor *colorPtr;

    if (dragImage->tkimage != NULL) {
	Tk_FreeImage(dragImage->tkimage);
	dragImage->tkimage = NULL;
    }

    photoH = Tk_FindPhoto(tree->interp, "::TreeCtrl::ImageDrag");
    if (photoH == NULL) {
	Tcl_GlobalEval(tree->interp, "image create photo ::TreeCtrl::ImageDrag");
	photoH = Tk_FindPhoto(tree->interp, "::TreeCtrl::ImageDrag");
	if (photoH == NULL)
	    return;
    }

    /* Pixmap -> XImage */
    ximage = XGetImage(tree->display, dragImage->pixmap, 0, 0,
	    (unsigned int)width, (unsigned int)height, AllPlanes, ZPixmap);
    if (ximage == NULL)
	Tcl_Panic("tkTreeDrag.c:DragImage_UpdateStyleTkImage() ximage is NULL");

    /* XImage -> Tk_Image */
    colorPtr = Tk_GetColor(tree->interp, tree->tkwin, "pink");
    Tree_XImage2Photo(tree->interp, photoH, ximage, colorPtr->pixel, alpha);

    XDestroyImage(ximage);

    dragImage->tkimage = Tk_GetImage(tree->interp, tree->tkwin,
	"::TreeCtrl::ImageDrag", NULL, (ClientData) NULL);
}

static void
DragImage_UpdateStylePixmap(
    TreeDragImage dragImage	/* Drag image record. */
    )
{
    TreeCtrl *tree = dragImage->tree;
    int w, h, state = 0;
    XColor *colorPtr;
    GC gc;
    StyleDrawArgs drawArgs;

    w = dragImage->styleW = TreeStyle_NeededWidth(tree, dragImage->instanceStyle, state);
    h = dragImage->styleH = TreeStyle_NeededHeight(tree, dragImage->instanceStyle, state);
    if (w > dragImage->pixmapW || h > dragImage->pixmapH)
    {

	if (dragImage->pixmap != None)
	    Tk_FreePixmap(tree->display, dragImage->pixmap);
	dragImage->pixmap = Tk_GetPixmap(tree->display,
	    Tk_WindowId(tree->tkwin),
	    w, h, Tk_Depth(tree->tkwin));

	dragImage->pixmapW = w;
	dragImage->pixmapH = h;
    }

    colorPtr = Tk_GetColor(tree->interp, tree->tkwin, "pink");
    gc = Tk_GCForColor(colorPtr, Tk_WindowId(tree->tkwin));
    XFillRectangle(tree->display, dragImage->pixmap, gc,
	0, 0, w, h);

    drawArgs.tree = tree;

    drawArgs.td.drawable = dragImage->pixmap;
    drawArgs.td.width = w; drawArgs.td.height = h;

    drawArgs.bounds[0] = drawArgs.bounds[1] = 0;
    drawArgs.bounds[2] = w; drawArgs.bounds[3] = h;

    drawArgs.state = state;
    drawArgs.style = dragImage->instanceStyle;

    drawArgs.indent = 0;

    drawArgs.x = drawArgs.y = 0;
    drawArgs.width = w; drawArgs.height = h;

    drawArgs.justify = TK_JUSTIFY_LEFT;

    TreeStyle_Draw(&drawArgs);

    if (dragImage->tkimage != NULL) {
	Tk_FreeImage(dragImage->tkimage);
	dragImage->tkimage = NULL;
    }
}

static void
DragImage_DrawStyle(
    TreeDragImage dragImage,	/* Drag image record. */
    TreeDrawable td		/* Where to draw. */
    )
{
    TreeCtrl *tree = dragImage->tree;
    int ix, iy, iw, ih;

    if (dragImage->tkimage == NULL)
	DragImage_UpdateStyleTkImage(dragImage);

    if (dragImage->tkimage == NULL)
	return;

    ix = iy = 0;
    iw = dragImage->styleW; ih = dragImage->styleH;

    Tree_RedrawImage(dragImage->tkimage, ix, iy, iw, ih, td,
	dragImage->x + -dragImage->styleX - tree->drawableXOrigin,
	dragImage->y + -dragImage->styleY - tree->drawableYOrigin);
}
#endif /* DRAGIMAGE_STYLE */

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImage_Draw --
 *
 *	Draw the elements that make up the drag image if it is visible.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Stuff is drawn.
 *
 *----------------------------------------------------------------------
 */

void
TreeDragImage_Draw(
    TreeDragImage dragImage,	/* Drag image record. */
    TreeDrawable td		/* Where to draw. */
    )
{
#ifdef DRAG_PIXMAP
    DrawPixmap(tree->dragImage, tdrawable);

#elif 1 /* Use XOR dotted rectangles where possible. */
    TreeCtrl *tree = dragImage->tree;

    if (!dragImage->visible)
	return;

#ifdef DRAGIMAGE_STYLE
    if (dragImage->instanceStyle != NULL) {
	DragImage_DrawStyle(dragImage, td);
	return;
    }
#endif /* DRAGIMAGE_STYLE */

    /* Yes this is XOR drawing but we aren't erasing the previous
     * dragimage as when TreeDragImage_IsXOR() returns TRUE. */
    TreeDragImage_DrawXOR(dragImage, td.drawable,
	0 - tree->xOrigin, 0 - tree->yOrigin);
#else /* */
    TreeCtrl *tree = dragImage->tree;
    GC gc;
    DragElem *elem;
#if 1 /* Stippled rectangles: BUG not clipped to contentbox. */
    XGCValues gcValues;
    unsigned long mask;
    XPoint points[5];

    if (!dragImage->visible)
	return;

    gcValues.stipple = Tk_GetBitmap(tree->interp, tree->tkwin, "gray50");
    gcValues.fill_style = FillStippled;
    mask = GCStipple|GCFillStyle;
    gc = Tk_GetGC(tree->tkwin, mask, &gcValues);

    for (elem = dragImage->elem; elem != NULL; elem = elem->next) {
	XRectangle rect;
	rect.x = dragImage->x + elem->x /*- dragImage->bounds[0]*/ - tree->drawableXOrigin;
	rect.y = dragImage->y + elem->y /*- dragImage->bounds[1]*/ - tree->drawableYOrigin;
	rect.width = elem->width;
	rect.height = elem->height;

#ifdef WIN32
	/* XDrawRectangle ignores the stipple pattern. */
	points[0].x = rect.x, points[0].y = rect.y;
	points[1].x = rect.x + rect.width - 1, points[1].y = rect.y;
	points[2].x = rect.x + rect.width - 1, points[2].y = rect.y + rect.height - 1;
	points[3].x = rect.x, points[3].y = rect.y + rect.height - 1;
	points[4] = points[0];
	XDrawLines(tree->display, td.drawable, gc, points, 5, CoordModeOrigin);
#else /* !WIN32 */
	XDrawRectangle(tree->display, td.drawable, gc, rect.x, rect.y,
	    rect.width - 1, rect.height - 1);
#endif
    }

    Tk_FreeGC(tree->display, gc);
#else /* Debug/test: gray rectangles */
    XColor *colorPtr;
    TkRegion rgn;

    if (!dragImage->visible)
	return;

    colorPtr = Tk_GetColor(tree->interp, tree->tkwin, "gray50");
    gc = Tk_GCForColor(colorPtr, Tk_WindowId(tree->tkwin));

    rgn = Tree_GetRegion(tree);

    for (elem = dragImage->elem; elem != NULL; elem = elem->next) {
	XRectangle rect;
	rect.x = dragImage->x + elem->x /*- dragImage->bounds[0]*/ - tree->drawableXOrigin;
	rect.y = dragImage->y + elem->y /*- dragImage->bounds[1]*/ - tree->drawableYOrigin;
	rect.width = elem->width;
	rect.height = elem->height;
	TkUnionRectWithRegion(&rect, rgn, rgn);
    }

    Tree_FillRegion(tree->display, td.drawable, gc, rgn);

    Tree_FreeRegion(tree, rgn);
#endif /* Debug/test: gray rectangles */
#endif /* XOR */
}

/*
 *----------------------------------------------------------------------
 *
 * DragElem_Alloc --
 *
 *	Allocate and initialize a new DragElem record. Add the record
 *	to the list of records for the drag image.
 *
 * Results:
 *	Pointer to allocated DragElem.
 *
 * Side effects:
 *	Memory is allocated.
 *
 *----------------------------------------------------------------------
 */

static DragElem *
DragElem_Alloc(
    TreeDragImage dragImage	/* Drag image record. */
    )
{
    DragElem *elem = (DragElem *) ckalloc(sizeof(DragElem));
    DragElem *walk = dragImage->elem;
    memset(elem, '\0', sizeof(DragElem));
    if (dragImage->elem == NULL)
	dragImage->elem = elem;
    else {
	while (walk->next != NULL)
	    walk = walk->next;
	walk->next = elem;
    }
    return elem;
}

/*
 *----------------------------------------------------------------------
 *
 * DragElem_Free --
 *
 *	Free a DragElem.
 *
 * Results:
 *	Pointer to the next DragElem.
 *
 * Side effects:
 *	Memory is deallocated.
 *
 *----------------------------------------------------------------------
 */

static DragElem *
DragElem_Free(
    TreeDragImage dragImage,	/* Drag image record. */
    DragElem *elem		/* Drag element to free. */
    )
{
    DragElem *next = elem->next;
    WFREE(elem, DragElem);
    return next;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImage_InitWidget --
 *
 *	Perform drag-image-related initialization when a new TreeCtrl is
 *	created.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Memory is allocated.
 *
 *----------------------------------------------------------------------
 */

int
TreeDragImage_InitWidget(
    TreeCtrl *tree		/* Widget info. */
    )
{
    TreeDragImage dragImage;

    dragImage = (TreeDragImage) ckalloc(sizeof(TreeDragImage_));
    memset(dragImage, '\0', sizeof(TreeDragImage_));
    dragImage->tree = tree;
    dragImage->optionTable = Tk_CreateOptionTable(tree->interp, optionSpecs);
    if (Tk_InitOptions(tree->interp, (char *) dragImage, dragImage->optionTable,
	tree->tkwin) != TCL_OK) {
	WFREE(dragImage, TreeDragImage_);
	return TCL_ERROR;
    }
    tree->dragImage = (TreeDragImage) dragImage;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImage_FreeWidget --
 *
 *	Free drag-image-related resources when a TreeCtrl is deleted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is deallocated.
 *
 *----------------------------------------------------------------------
 */

void
TreeDragImage_FreeWidget(
    TreeCtrl *tree		/* Widget info. */
    )
{
    TreeDragImage dragImage = tree->dragImage;
    DragElem *elem = dragImage->elem;

    while (elem != NULL)
	elem = DragElem_Free(dragImage, elem);
#ifdef DRAG_PIXMAP
    if (dragImage->image != NULL)
	Tk_FreeImage(dragImage->image);
    if (dragImage->pixmap != None)
	Tk_FreePixmap(dragImage->tree->display, dragImage->pixmap);
#endif /* DRAG_PIXMAP */
    Tk_FreeConfigOptions((char *) dragImage, dragImage->optionTable,
	dragImage->tree->tkwin);
    WFREE(dragImage, TreeDragImage_);
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImage_IsXOR --
 *
 *	Return true if the dragimage is being drawn with XOR.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TreeDragImage_IsXOR(
    TreeDragImage dragImage	/* Drag image token. */
    )
{
#if defined(WIN32)
    return FALSE; /* TRUE on XP, FALSE on Win7 (lots of flickering) */
#elif defined(MAC_OSX_TK)
    return FALSE; /* Cocoa doesn't have XOR */
#else /* X11 */
    /* With VirtualBox+Ubuntu get extreme lag if TRUE with Compiz. */
    /* With VirtualBox+Ubuntu get lots of flickering if TRUE without Compiz. */
    return FALSE;
#endif
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImage_IsVisible --
 *
 *	Return true if the dragimage is being drawn.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TreeDragImage_IsVisible(
    TreeDragImage dragImage	/* Drag image token. */
    )
{
    return dragImage->visible;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImage_Display --
 *
 *	Draw the drag image if it is not already displayed and if
 *	it's -visible option is TRUE.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Stuff is drawn.
 *
 *----------------------------------------------------------------------
 */

void
TreeDragImage_Display(
    TreeDragImage dragImage	/* Drag image token. */
    )
{
    TreeCtrl *tree = dragImage->tree;

    if (!dragImage->onScreen && dragImage->visible) {
	if (TreeDragImage_IsXOR(dragImage) == FALSE) {
	    dragImage->sx = dragImage->x + TreeRect_Left(dragImage->bounds) - tree->xOrigin;
	    dragImage->sy = dragImage->y + TreeRect_Top(dragImage->bounds) - tree->yOrigin;
	    dragImage->sw = TreeRect_Width(dragImage->bounds);
	    dragImage->sh = TreeRect_Height(dragImage->bounds);
/*	    Tree_InvalidateItemArea(tree, dragImage->sx, dragImage->sy,
		dragImage->sx + dragImage->sw, dragImage->sy + dragImage->sh);*/
	    Tree_EventuallyRedraw(tree);
	} else {
	    dragImage->sx = 0 - tree->xOrigin;
	    dragImage->sy = 0 - tree->yOrigin;
	    TreeDragImage_DrawXOR(dragImage, Tk_WindowId(tree->tkwin),
		    dragImage->sx, dragImage->sy);
	}
	dragImage->onScreen = TRUE;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImage_Undisplay --
 *
 *	Erase the drag image if it is displayed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Stuff is drawn.
 *
 *----------------------------------------------------------------------
 */

void
TreeDragImage_Undisplay(
    TreeDragImage dragImage	/* Drag image token. */
    )
{
    TreeCtrl *tree = dragImage->tree;

    if (dragImage->onScreen) {
	if (TreeDragImage_IsXOR(dragImage) == FALSE) {
/*	    Tree_InvalidateItemArea(tree, dragImage->sx, dragImage->sy,
		dragImage->sx + dragImage->sw, dragImage->sy + dragImage->sh);*/
	    Tree_EventuallyRedraw(tree);
	} else {
	    TreeDragImage_DrawXOR(dragImage, Tk_WindowId(tree->tkwin),
		dragImage->sx, dragImage->sy);
	}
	dragImage->onScreen = FALSE;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DragImage_Config --
 *
 *	This procedure is called to process an objc/objv list to set
 *	configuration options for a DragImage.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then an error message is left in interp's result.
 *
 * Side effects:
 *	Configuration information, such as text string, colors, font,
 *	etc. get set for dragImage;  old resources get freed, if there
 *	were any.  Display changes may occur.
 *
 *----------------------------------------------------------------------
 */

static int
DragImage_Config(
    TreeDragImage dragImage,	/* Drag image record. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *CONST objv[]	/* Argument values. */
    )
{
    TreeCtrl *tree = dragImage->tree;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;
    int mask;

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tk_SetOptions(tree->interp, (char *) dragImage, dragImage->optionTable,
		objc, objv, tree->tkwin, &savedOptions, &mask) != TCL_OK) {
		mask = 0;
		continue;
	    }

	    /* xxx */

	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(tree->interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);

		/* xxx */

	    Tcl_SetObjResult(tree->interp, errorResult);
	    Tcl_DecrRefCount(errorResult);
	    return TCL_ERROR;
	}
    }

    if (mask & DRAG_CONF_VISIBLE) {
	TreeDragImage_Undisplay((TreeDragImage) dragImage);
	TreeDragImage_Display((TreeDragImage) dragImage);
    }

#ifdef DRAGIMAGE_STYLE
    if (dragImage->instanceStyle != NULL) {
	TreeStyle_FreeResources(tree, dragImage->instanceStyle);
	dragImage->instanceStyle = NULL;
    }
    if (dragImage->masterStyle != NULL) {
	dragImage->instanceStyle = TreeStyle_NewInstance(tree, dragImage->masterStyle);
	DragImage_UpdateStylePixmap(dragImage);
    }
#endif /* DRAGIMAGE_STYLE */

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImage_DrawXOR --
 *
 *	Draw (or erase) the elements that make up the drag image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Stuff is drawn (or erased, since this is XOR drawing).
 *
 *----------------------------------------------------------------------
 */

void
TreeDragImage_DrawXOR(
    TreeDragImage dragImage,	/* Drag image record. */
    Drawable drawable,		/* Where to draw. */
    int xOffset,		/* Offset of the drawable from the top-left */
    int yOffset			/* of the canvas. */
    )
{
    TreeCtrl *tree = dragImage->tree;
    DragElem *elem = dragImage->elem;
    DotState dotState;

/*	if (!dragImage->visible)
	return; */
    if (elem == NULL)
	return;

    TreeDotRect_Setup(tree, drawable, &dotState);

    while (elem != NULL) {
	TreeDotRect_Draw(&dotState,
	    xOffset + dragImage->x + elem->x,
	    yOffset + dragImage->y + elem->y,
	    elem->width, elem->height);
	elem = elem->next;
    }

    TreeDotRect_Restore(&dotState);
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDragImageCmd --
 *
 *	This procedure is invoked to process the [dragimage] widget
 *	command.  See the user documentation for details on what it
 *	does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TreeDragImageCmd(
    ClientData clientData,	/* Widget info. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *CONST objv[]	/* Argument values. */
    )
{
    TreeCtrl *tree = clientData;
    TreeDragImage dragImage = tree->dragImage;
    static CONST char *commandNames[] = { "add", "cget", "clear", "configure",
	"offset",
#ifdef DRAGIMAGE_STYLE
	"stylehotspot",
#endif /* DRAGIMAGE_STYLE */
	(char *) NULL };
    enum { COMMAND_ADD, COMMAND_CGET, COMMAND_CLEAR, COMMAND_CONFIGURE,
	COMMAND_OFFSET
#ifdef DRAGIMAGE_STYLE
	, COMMAND_STYLEHOTSPOT
#endif /* DRAGIMAGE_STYLE */
    };
    int index;

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "command ?arg arg ...?");
	return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[2], commandNames, "command", 0,
	&index) != TCL_OK) {
	return TCL_ERROR;
    }

    switch (index) {
	/* T dragimage add I ?C? ?E ...? */
	case COMMAND_ADD: {
	    TreeItem item;
	    TreeItemColumn itemColumn;
	    TreeColumn treeColumn;
	    TreeRectangle rects[128];
	    DragElem *elem;
	    int i, count, result = TCL_OK;
	    int minX, minY, maxX, maxY;

	    if (objc < 4) {
		Tcl_WrongNumArgs(interp, 3, objv, "item ?column? ?element ...?");
		return TCL_ERROR;
	    }

	    if (TreeItem_FromObj(tree, objv[3], &item, IFO_NOT_NULL) != TCL_OK)
		return TCL_ERROR;

	    TreeDragImage_Undisplay(tree->dragImage);

	    /* Every element in every column. */
	    if (objc == 4) {

		treeColumn = tree->columns;
		itemColumn = TreeItem_GetFirstColumn(tree, item);
		while (itemColumn != NULL) {
		    if (TreeItemColumn_GetStyle(tree, itemColumn) != NULL) {
			count = TreeItem_GetRects(tree, item, treeColumn,
			    -1, NULL, rects);
			if (count == -1) {
			    result = TCL_ERROR;
			    goto doneADD;
			}
			for (i = 0; i < count; i++) {
			    elem = DragElem_Alloc(dragImage);
			    elem->x = rects[i].x;
			    elem->y = rects[i].y;
			    elem->width = rects[i].width;
			    elem->height = rects[i].height;
			}
		    }
		    treeColumn = TreeColumn_Next(treeColumn);
		    itemColumn = TreeItemColumn_GetNext(tree, itemColumn);
		}

	    } else {

		if (TreeColumn_FromObj(tree, objv[4], &treeColumn,
			CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK) {
		    result = TCL_ERROR;
		    goto doneADD;
		}

		/* Every element in a column. */
		if (objc == 5) {
		    objc = -1;
		    objv = NULL;

		/* List of elements in a column. */
		} else {
		    objc -= 5;
		    objv += 5;
		}

		count = TreeItem_GetRects(tree, item, treeColumn, objc, objv,
			rects);
		if (count == -1) {
		    result = TCL_ERROR;
		    goto doneADD;
		}

		for (i = 0; i < count; i++) {
		    elem = DragElem_Alloc(dragImage);
		    elem->x = rects[i].x;
		    elem->y = rects[i].y;
		    elem->width = rects[i].width;
		    elem->height = rects[i].height;
		}
	    }

doneADD:
	    minX = 100000;
	    minY = 100000;
	    maxX = -100000;
	    maxY = -100000;
	    for (elem = dragImage->elem; elem != NULL; elem = elem->next) {
		if (elem->x < minX)
		    minX = elem->x;
		if (elem->y < minY)
		    minY = elem->y;
		if (elem->x + elem->width > maxX)
		    maxX = elem->x + elem->width;
		if (elem->y + elem->height > maxY)
		    maxY = elem->y + elem->height;
	    }
	    TreeRect_SetXYXY(dragImage->bounds, minX, minY, maxX, maxY);
#ifdef DRAG_PIXMAP
	    UpdatePixmap(dragImage);
#endif /* DRAG_PIXMAP */
	    TreeDragImage_Display(tree->dragImage);
	    return result;
	}

	/* T dragimage cget option */
	case COMMAND_CGET: {
	    Tcl_Obj *resultObjPtr;

	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 3, objv, "option");
		return TCL_ERROR;
	    }
	    resultObjPtr = Tk_GetOptionValue(interp, (char *) dragImage,
		dragImage->optionTable, objv[3], tree->tkwin);
	    if (resultObjPtr == NULL)
		return TCL_ERROR;
	    Tcl_SetObjResult(interp, resultObjPtr);
	    break;
	}

	/* T dragimage clear */
	case COMMAND_CLEAR: {
	    if (objc != 3) {
		Tcl_WrongNumArgs(interp, 3, objv, (char *) NULL);
		return TCL_ERROR;
	    }
	    if (dragImage->elem != NULL) {
		DragElem *elem = dragImage->elem;
		TreeDragImage_Undisplay(tree->dragImage);
/*				if (dragImage->visible)
		    DragImage_Redraw(dragImage); */
		while (elem != NULL)
		    elem = DragElem_Free(dragImage, elem);
		dragImage->elem = NULL;
	    }
	    break;
	}

	/* T dragimage configure ?option? ?value? ?option value ...? */
	case COMMAND_CONFIGURE: {
	    Tcl_Obj *resultObjPtr;

	    if (objc < 3) {
		Tcl_WrongNumArgs(interp, 3, objv, "?option? ?value?");
		return TCL_ERROR;
	    }
	    if (objc <= 4) {
		resultObjPtr = Tk_GetOptionInfo(interp, (char *) dragImage,
		    dragImage->optionTable,
		    (objc == 3) ? (Tcl_Obj *) NULL : objv[3],
		    tree->tkwin);
		if (resultObjPtr == NULL)
		    return TCL_ERROR;
		Tcl_SetObjResult(interp, resultObjPtr);
		break;
	    }
	    return DragImage_Config(dragImage, objc - 3, objv + 3);
	}

	/* T dragimage offset ?x y? */
	case COMMAND_OFFSET: {
	    int x, y;

	    if (objc != 3 && objc != 5) {
		Tcl_WrongNumArgs(interp, 3, objv, "?x y?");
		return TCL_ERROR;
	    }
	    if (objc == 3) {
		FormatResult(interp, "%d %d", dragImage->x, dragImage->y);
		break;
	    }
	    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK)
		return TCL_ERROR;
	    if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK)
		return TCL_ERROR;
	    TreeDragImage_Undisplay(tree->dragImage);
/*			if (dragImage->visible)
		DragImage_Redraw(dragImage); */
	    dragImage->x = x;
	    dragImage->y = y;
	    TreeDragImage_Display(tree->dragImage);
	    break;
	}

#ifdef DRAGIMAGE_STYLE
	/* T dragimage stylehotspot ?x y? */
	case COMMAND_STYLEHOTSPOT: {
	    int x, y;

	    if (objc != 3 && objc != 5) {
		Tcl_WrongNumArgs(interp, 3, objv, "?x y?");
		return TCL_ERROR;
	    }
	    if (objc == 3) {
		FormatResult(interp, "%d %d", dragImage->styleX, dragImage->styleY);
		break;
	    }
	    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK)
		return TCL_ERROR;
	    if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK)
		return TCL_ERROR;
	    TreeDragImage_Undisplay(tree->dragImage);
	    dragImage->styleX = x;
	    dragImage->styleY = y;
	    TreeDragImage_Display(tree->dragImage);
	    break;
	}
#endif /* DRAGIMAGE_STYLE */
    }

    return TCL_OK;
}
