/* $XTermId: cachedGCs.c,v 1.64 2016/05/16 09:26:15 tom Exp $ */

/*
 * Copyright 2007-2014,2016 by Thomas E. Dickey
 *
 *                         All Rights Reserved
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the
 * sale, use or other dealings in this Software without prior written
 * authorization.
 */

#include <data.h>
#include <xstrings.h>

#include <X11/Xmu/Drawing.h>

#include <stdio.h>

/*
 * hide (or eliminate) calls to
 *	XCreateGC()
 *	XFreeGC()
 *	XGetGCValues()
 *	XSetBackground()
 *	XSetFont()
 *	XSetForeground()
 *	XtGetGC()
 *	XtReleaseGC()
 * by associating an integer with each GC, maintaining a cache which
 * reflects frequency of use rather than most recent usage.
 *
 * FIXME: XTermFonts should hold gc, font, fs.
 */
typedef struct {
    GC gc;
    unsigned used;
    unsigned cset;
    XTermFonts *font;
    Pixel tile;
    Pixel fg;
    Pixel bg;
} CgsCacheData;

#define DEPTH 8
#define ITEM()      (int) (me->data - me->list)
#define LIST(item)  me->list[item]
#define LINK(item)  me->data = (me->list + (item))
#define THIS(field) me->data->field
#define NEXT(field) me->next.field

#define HaveFont(font) (Boolean) ((font) != 0 && (font)->fs != 0)

#define GC_CSet GCFunction

typedef struct {
    CgsCacheData list[DEPTH];
    CgsCacheData *data;		/* points to current list[] entry */
    XtGCMask mask;		/* changes since the last getCgsGC() */
    CgsCacheData next;		/* updated values, apply in getCgsGC() */
} CgsCache;

#if OPT_TRACE
#define CASE(name) case gc##name: result = #name; break
static const char *
traceCgsEnum(CgsEnum value)
{
    const char *result = "?";
    switch (value) {
	CASE(Norm);
	CASE(Bold);
	CASE(NormReverse);
	CASE(BoldReverse);
#if OPT_BOX_CHARS
	CASE(Line);
	CASE(Dots);
#endif
#if OPT_DEC_CHRSET
	CASE(CNorm);
	CASE(CBold);
#endif
#if OPT_WIDE_CHARS
	CASE(Wide);
	CASE(WBold);
	CASE(WideReverse);
	CASE(WBoldReverse);
#endif
	CASE(VTcursNormal);
	CASE(VTcursFilled);
	CASE(VTcursReverse);
	CASE(VTcursOutline);
#if OPT_TEK4014
	CASE(TKcurs);
#endif
	CASE(MAX);
    }
    return result;
}

#undef CASE

static const char *
traceVTwin(XtermWidget xw, VTwin *value)
{
    const char *result = "?";
    if (value == 0)
	result = "null";
    else if (value == &(TScreenOf(xw)->fullVwin))
	result = "fullVwin";
#ifndef NO_ACTIVE_ICON
    else if (value == &(TScreenOf(xw)->iconVwin))
	result = "iconVwin";
#endif
    return result;
}

#if OPT_TRACE > 1
static String
traceCSet(unsigned cset)
{
    static char result[80];
    switch (cset) {
    case CSET_SWL:
	strcpy(result, "SWL");
	break;
    case CSET_DHL_TOP:
	strcpy(result, "DHL_TOP");
	break;
    case CSET_DHL_BOT:
	strcpy(result, "DHL_BOT");
	break;
    case CSET_DWL:
	strcpy(result, "DWL");
	break;
    default:
	sprintf(result, "%#x", cset);
	break;
    }
    return result;
}

static String
traceFont(XTermFonts * font)
{
    static char result[80];

    if (HaveFont(font)) {
	XFontStruct *fs = font->fs;
	sprintf(result, "%p(%dx%d %d %#lx)",
		fs,
		fs->max_bounds.width,
		fs->max_bounds.ascent + fs->max_bounds.descent,
		fs->max_bounds.descent,
		(unsigned long) (fs->fid));
    } else {
	strcpy(result, "null");
    }
    return result;
}

static String
tracePixel(XtermWidget xw, Pixel value)
{
#define CASE(name) { name, #name }
    static struct {
	TermColors code;
	String name;
    } t_colors[] = {
	CASE(TEXT_FG),
	    CASE(TEXT_BG),
	    CASE(TEXT_CURSOR),
	    CASE(MOUSE_FG),
	    CASE(MOUSE_BG),
#if OPT_TEK4014
	    CASE(TEK_FG),
	    CASE(TEK_BG),
#endif
#if OPT_HIGHLIGHT_COLOR
	    CASE(HIGHLIGHT_BG),
	    CASE(HIGHLIGHT_FG),
#endif
#if OPT_TEK4014
	    CASE(TEK_CURSOR),
#endif
    };
    TScreen *screen = TScreenOf(xw);
    String result = 0;
    int n;

    for (n = 0; n < NCOLORS; ++n) {
	if (value == T_COLOR(screen, t_colors[n].code)) {
	    result = t_colors[n].name;
	    break;
	}
    }

    if (result == 0) {
	for (n = 0; n < MAXCOLORS; ++n) {
#if OPT_COLOR_RES
	    if (screen->Acolors[n].mode > 0
		&& value == screen->Acolors[n].value) {
		result = screen->Acolors[n].resource;
		break;
	    }
#else
	    if (value == screen->Acolors[n]) {
		char temp[80];
		sprintf(temp, "Acolors[%d]", n);
		result = x_strdup(temp);
		break;
	    }
#endif
	}
    }

    if (result == 0) {
	char temp[80];
	sprintf(temp, "%#lx", value);
	result = x_strdup(temp);
    }

    return result;
}

#undef CASE

#endif /* OPT_TRACE > 1 */
#endif /* OPT_TRACE */

static CgsCache *
allocCache(void **cache_pointer)
{
    if (*cache_pointer == 0) {
	*cache_pointer = TypeCallocN(CgsCache, gcMAX);
	TRACE(("allocCache %p\n", *cache_pointer));
    }
    return *((CgsCache **) cache_pointer);
}

static int
dataIndex(CgsCache * me)
{
    return ITEM();
}

static void
relinkData(CgsCache * me, int item)
{
    LINK(item);
}

/*
 * Returns the appropriate cache pointer.
 */
static CgsCache *
myCache(XtermWidget xw, VTwin *cgsWin, CgsEnum cgsId)
{
    CgsCache *result = 0;

    if ((int) cgsId >= 0 && cgsId < gcMAX) {
#ifdef NO_ACTIVE_ICON
	(void) xw;
	(void) cgsWin;
#else
	if (cgsWin == &(TScreenOf(xw)->iconVwin))
	    result = allocCache(&(TScreenOf(xw)->icon_cgs_cache));
	else
#endif
	    result = allocCache(&(TScreenOf(xw)->main_cgs_cache));

	result += cgsId;
	if (result->data == 0) {
	    result->data = result->list;
	}
    }

    return result;
}

static Display *
myDisplay(XtermWidget xw)
{
    return TScreenOf(xw)->display;
}

static Drawable
myDrawable(XtermWidget xw, VTwin *cgsWin)
{
    Drawable drawable = 0;

    if (cgsWin != 0 && cgsWin->window != 0)
	drawable = cgsWin->window;
    if (drawable == 0)
	drawable = RootWindowOfScreen(XtScreen(xw));
    return drawable;
}

static GC
newCache(XtermWidget xw, VTwin *cgsWin, CgsEnum cgsId, CgsCache * me)
{
    XGCValues xgcv;
    XtGCMask mask;

    THIS(font) = NEXT(font);
    THIS(cset) = NEXT(cset);
    THIS(fg) = NEXT(fg);
    THIS(bg) = NEXT(bg);

    memset(&xgcv, 0, sizeof(xgcv));
    xgcv.font = NEXT(font)->fs->fid;
    mask = (GCForeground | GCBackground | GCFont);

    switch (cgsId) {
    case gcNorm:
    case gcBold:
    case gcNormReverse:
    case gcBoldReverse:
#if OPT_WIDE_CHARS
    case gcWide:
    case gcWBold:
    case gcWideReverse:
    case gcWBoldReverse:
#endif
	mask |= (GCGraphicsExposures | GCFunction);
	xgcv.graphics_exposures = True;		/* default */
	xgcv.function = GXcopy;
	break;
#if OPT_BOX_CHARS
    case gcLine:
	mask |= (GCGraphicsExposures | GCFunction);
	xgcv.graphics_exposures = True;		/* default */
	xgcv.function = GXcopy;
	break;
    case gcDots:
	xgcv.fill_style = FillTiled;
	xgcv.tile =
	    XmuCreateStippledPixmap(XtScreen((Widget) xw),
				    THIS(fg),
				    THIS(bg),
				    xw->core.depth);
	THIS(tile) = xgcv.tile;
	mask = (GCForeground | GCBackground);
	mask |= (GCGraphicsExposures | GCFunction | GCTile | GCFillStyle);
	xgcv.graphics_exposures = True;		/* default */
	xgcv.function = GXcopy;
	break;
#endif
#if OPT_DEC_CHRSET
    case gcCNorm:
    case gcCBold:
	break;
#endif
    case gcVTcursNormal:	/* FALLTHRU */
    case gcVTcursFilled:	/* FALLTHRU */
    case gcVTcursReverse:	/* FALLTHRU */
    case gcVTcursOutline:	/* FALLTHRU */
	break;
#if OPT_TEK4014
    case gcTKcurs:		/* FALLTHRU */
	/* FIXME */
#endif
    case gcMAX:		/* should not happen */
	return 0;
    }
    xgcv.foreground = NEXT(fg);
    xgcv.background = NEXT(bg);

    THIS(gc) = XCreateGC(myDisplay(xw), myDrawable(xw, cgsWin), mask, &xgcv);
    TRACE(("getCgsGC(%s) created gc %p(%d)\n",
	   traceCgsEnum(cgsId), (void *) THIS(gc), ITEM()));

    THIS(used) = 0;
    return THIS(gc);
}

static Boolean
SameFont(XTermFonts * a, XTermFonts * b)
{
    return (Boolean) (HaveFont(a)
		      && HaveFont(b)
		      && ((a->fs == b->fs)
			  || !memcmp(a->fs, b->fs, sizeof(*(a->fs)))));
}

#define SameColor(a,b) ((a) == (b))
#define SameCSet(a,b)  ((a) == (b))

static GC
chgCache(XtermWidget xw, CgsEnum cgsId GCC_UNUSED, CgsCache * me, Bool both)
{
    XGCValues xgcv;
    XtGCMask mask = (GCForeground | GCBackground | GCFont);

    memset(&xgcv, 0, sizeof(xgcv));

    TRACE2(("chgCache(%s) old data fg=%s, bg=%s, font=%s cset %s\n",
	    traceCgsEnum(cgsId),
	    tracePixel(xw, THIS(fg)),
	    tracePixel(xw, THIS(bg)),
	    traceFont(THIS(font)),
	    traceCSet(THIS(cset))));
#if OPT_TRACE > 1
    if (!SameFont(THIS(font), NEXT(font)))
	TRACE2(("...chgCache new font=%s\n", traceFont(NEXT(font))));
    if (!SameCSet(THIS(cset), NEXT(cset)))
	TRACE2(("...chgCache new cset=%s\n", traceCSet(NEXT(cset))));
    if (!SameColor(THIS(fg), NEXT(fg)))
	TRACE2(("...chgCache new fg=%s\n", tracePixel(xw, NEXT(fg))));
    if (!SameColor(THIS(bg), NEXT(bg)))
	TRACE2(("...chgCache new bg=%s\n", tracePixel(xw, NEXT(bg))));
#endif

    if (both) {
	THIS(font) = NEXT(font);
	THIS(cset) = NEXT(cset);
    }
    THIS(fg) = NEXT(fg);
    THIS(bg) = NEXT(bg);

    xgcv.font = THIS(font)->fs->fid;
    xgcv.foreground = THIS(fg);
    xgcv.background = THIS(bg);

    XChangeGC(myDisplay(xw), THIS(gc), mask, &xgcv);
    TRACE2(("...chgCache(%s) updated gc %p(%d)\n",
	    traceCgsEnum(cgsId), THIS(gc), ITEM()));

    THIS(used) = 0;
    return THIS(gc);
}

/*
 * Use the "setCgsXXXX()" calls to initialize parameters for a new GC.
 */
void
setCgsFore(XtermWidget xw, VTwin *cgsWin, CgsEnum cgsId, Pixel fg)
{
    CgsCache *me;

    if ((me = myCache(xw, cgsWin, cgsId)) != 0) {
	NEXT(fg) = fg;
	me->mask |= GCForeground;
    }
}

void
setCgsBack(XtermWidget xw, VTwin *cgsWin, CgsEnum cgsId, Pixel bg)
{
    CgsCache *me;

    if ((me = myCache(xw, cgsWin, cgsId)) != 0) {
	NEXT(bg) = bg;
	me->mask |= GCBackground;
    }
}

#if OPT_DEC_CHRSET
void
setCgsCSet(XtermWidget xw, VTwin *cgsWin, CgsEnum cgsId, unsigned cset)
{
    CgsCache *me;

    if ((me = myCache(xw, cgsWin, cgsId)) != 0) {
	NEXT(cset) = cset;
	me->mask |= GC_CSet;
    }
}
#else
#define setCgsCSet(xw, cgsWin, dstCgsId, cset)	/* nothing */
#endif

void
setCgsFont(XtermWidget xw, VTwin *cgsWin, CgsEnum cgsId, XTermFonts * font)
{
    CgsCache *me;

    if ((me = myCache(xw, cgsWin, cgsId)) != 0) {
	if (!HaveFont(font)) {
	    if (cgsId != gcNorm)
		(void) getCgsGC(xw, cgsWin, gcNorm);
#ifndef NO_ACTIVE_ICON
	    if (cgsWin == &(TScreenOf(xw)->iconVwin))
		font = &(TScreenOf(xw)->fnt_icon);
	    else
#endif
		font = &(TScreenOf(xw)->fnts[fNorm]);
	}
	if (HaveFont(font) && okFont(font->fs)) {
	    TRACE2(("setCgsFont next: %s for %s slot %p, gc %p\n",
		    traceFont(font), traceCgsEnum(cgsId),
		    me, THIS(gc)));
	    TRACE2(("...next font was %s\n", traceFont(NEXT(font))));
	    NEXT(font) = font;
	    me->mask |= GCFont;
	} else {
	    /* EMPTY */
	    TRACE2(("...NOT updated font for %s\n",
		    traceCgsEnum(cgsId)));
	}
    }
}

/*
 * Discard all of the font information, e.g., we are resizing the font.
 * Keep the GC's so we can simply change them rather than creating new ones.
 */
void
clrCgsFonts(XtermWidget xw, VTwin *cgsWin, XTermFonts * font)
{
    if (HaveFont(font)) {
	int j;
	for_each_gc(j) {
	    CgsCache *me;
	    if ((me = myCache(xw, cgsWin, (CgsEnum) j)) != 0) {
		int k;
		for (k = 0; k < DEPTH; ++k) {
		    if (SameFont(LIST(k).font, font)) {
			TRACE2(("clrCgsFonts %s gc %p(%d) %s\n",
				traceCgsEnum((CgsEnum) j),
				LIST(k).gc,
				k,
				traceFont(font)));
			LIST(k).font = 0;
			LIST(k).cset = 0;
		    }
		}
		if (SameFont(NEXT(font), font)) {
		    TRACE2(("clrCgsFonts %s next %s\n",
			    traceCgsEnum((CgsEnum) j),
			    traceFont(font)));
		    NEXT(font) = 0;
		    NEXT(cset) = 0;
		    me->mask &= (unsigned) ~(GCFont | GC_CSet);
		}
	    }
	}
    }
}

/*
 * Return a GC associated with the given id, allocating if needed.
 */
GC
getCgsGC(XtermWidget xw, VTwin *cgsWin, CgsEnum cgsId)
{
    CgsCache *me;
    GC result = 0;

    if ((me = myCache(xw, cgsWin, cgsId)) != 0) {
	TRACE2(("getCgsGC(%s, %s)\n",
		traceVTwin(xw, cgsWin), traceCgsEnum(cgsId)));
	if (me->mask != 0) {
	    int j;
	    unsigned used = 0;

	    /* fill in the unchanged fields */
	    if (!(me->mask & GC_CSet))
		NEXT(cset) = 0;	/* OPT_DEC_CHRSET */
	    if (!(me->mask & GCFont))
		NEXT(font) = THIS(font);
	    if (!(me->mask & GCForeground))
		NEXT(fg) = THIS(fg);
	    if (!(me->mask & GCBackground))
		NEXT(bg) = THIS(bg);

	    if (NEXT(font) == 0) {
		setCgsFont(xw, cgsWin, cgsId, 0);
	    }

	    TRACE2(("...Cgs new data fg=%s, bg=%s, font=%s cset %s\n",
		    tracePixel(xw, NEXT(fg)),
		    tracePixel(xw, NEXT(bg)),
		    traceFont(NEXT(font)),
		    traceCSet(NEXT(cset))));

	    /* try to find the given data in an already-created GC */
	    for (j = 0; j < DEPTH; ++j) {
		if (LIST(j).gc != 0
		    && SameFont(LIST(j).font, NEXT(font))
		    && SameCSet(LIST(j).cset, NEXT(cset))
		    && SameColor(LIST(j).fg, NEXT(fg))
		    && SameColor(LIST(j).bg, NEXT(bg))) {
		    LINK(j);
		    result = THIS(gc);
		    TRACE2(("getCgsGC existing %p(%d)\n", result, ITEM()));
		    break;
		}
	    }

	    if (result == 0) {
		/* try to find an empty slot, to create a new GC */
		used = 0;
		for (j = 0; j < DEPTH; ++j) {
		    if (LIST(j).gc == 0) {
			LINK(j);
			result = newCache(xw, cgsWin, cgsId, me);
			break;
		    }
		    if (used < LIST(j).used)
			used = LIST(j).used;
		}
	    }

	    if (result == 0) {
		int k;
		/* if none were empty, pick the least-used slot, to modify */
		for (j = 0, k = -1; j < DEPTH; ++j) {
		    if (used >= LIST(j).used) {
			used = LIST(j).used;
			k = j;
		    }
		}
		LINK(k);
		TRACE2(("...getCgsGC least-used(%d) was %d\n", k, THIS(used)));
		result = chgCache(xw, cgsId, me, True);
	    }
	    me->next = *(me->data);
	} else {
	    result = THIS(gc);
	}
	me->mask = 0;
	THIS(used) += 1;
	TRACE2(("...getCgsGC(%s, %s) gc %p(%d), used %d\n",
		traceVTwin(xw, cgsWin),
		traceCgsEnum(cgsId), result, ITEM(), THIS(used)));
    }
    return result;
}

/*
 * Return the font for the given GC.
 */
CgsEnum
getCgsId(XtermWidget xw, VTwin *cgsWin, GC gc)
{
    int n;
    CgsEnum result = gcNorm;

    for_each_gc(n) {
	CgsCache *me;

	if ((me = myCache(xw, cgsWin, (CgsEnum) n)) != 0) {
	    if (THIS(gc) == gc) {
		result = (CgsEnum) n;
		break;
	    }
	}
    }
    return result;
}

/*
 * Return the font for the given GC.
 */
XTermFonts *
getCgsFont(XtermWidget xw, VTwin *cgsWin, GC gc)
{
    int n;
    XTermFonts *result = 0;

    for_each_gc(n) {
	CgsCache *me;

	if ((me = myCache(xw, cgsWin, (CgsEnum) n)) != 0) {
	    if (THIS(gc) == gc) {
		result = THIS(font);
		break;
	    }
	}
    }
    return result;
}

/*
 * Return the foreground color for the given GC.
 */
Pixel
getCgsFore(XtermWidget xw, VTwin *cgsWin, GC gc)
{
    int n;
    Pixel result = 0;

    for_each_gc(n) {
	CgsCache *me;

	if ((me = myCache(xw, cgsWin, (CgsEnum) n)) != 0) {
	    if (THIS(gc) == gc) {
		result = THIS(fg);
		break;
	    }
	}
    }
    return result;
}

/*
 * Return the background color for the given GC.
 */
Pixel
getCgsBack(XtermWidget xw, VTwin *cgsWin, GC gc)
{
    int n;
    Pixel result = 0;

    for_each_gc(n) {
	CgsCache *me;

	if ((me = myCache(xw, cgsWin, (CgsEnum) n)) != 0) {
	    if (THIS(gc) == gc) {
		result = THIS(bg);
		break;
	    }
	}
    }
    return result;
}

/*
 * Copy the parameters (except GC of course) from one cache record to another.
 */
void
copyCgs(XtermWidget xw, VTwin *cgsWin, CgsEnum dstCgsId, CgsEnum srcCgsId)
{
    if (dstCgsId != srcCgsId) {
	CgsCache *me;

	if ((me = myCache(xw, cgsWin, srcCgsId)) != 0) {
	    TRACE(("copyCgs from %s to %s\n",
		   traceCgsEnum(srcCgsId),
		   traceCgsEnum(dstCgsId)));
	    TRACE2(("copyCgs from %s (me %p, fg %s, bg %s, cset %s) to %s {{\n",
		    traceCgsEnum(srcCgsId),
		    me,
		    tracePixel(xw, THIS(fg)),
		    tracePixel(xw, THIS(bg)),
		    traceCSet(THIS(cset)),
		    traceCgsEnum(dstCgsId)));
	    setCgsCSet(xw, cgsWin, dstCgsId, THIS(cset));
	    setCgsFore(xw, cgsWin, dstCgsId, THIS(fg));
	    setCgsBack(xw, cgsWin, dstCgsId, THIS(bg));
	    setCgsFont(xw, cgsWin, dstCgsId, THIS(font));
	    TRACE2(("...copyCgs }}\n"));
	}
    }
}

/*
 * Interchange colors in the cache, e.g., for reverse-video.
 */
void
redoCgs(XtermWidget xw, Pixel fg, Pixel bg, CgsEnum cgsId)
{
    VTwin *cgsWin = WhichVWin(TScreenOf(xw));
    CgsCache *me = myCache(xw, cgsWin, cgsId);

    if (me != 0) {
	CgsCacheData *save_data = me->data;
	int n;

	for (n = 0; n < DEPTH; ++n) {
	    if (LIST(n).gc != 0 && HaveFont(LIST(n).font)) {
		LINK(n);

		if (LIST(n).fg == fg
		    && LIST(n).bg == bg) {
		    setCgsFore(xw, cgsWin, cgsId, bg);
		    setCgsBack(xw, cgsWin, cgsId, fg);
		} else if (LIST(n).fg == bg
			   && LIST(n).bg == fg) {
		    setCgsFore(xw, cgsWin, cgsId, fg);
		    setCgsBack(xw, cgsWin, cgsId, bg);
		} else {
		    continue;
		}

		(void) chgCache(xw, cgsId, me, False);
	    }
	}
	me->data = save_data;
    }
}

/*
 * Swap the cache records, e.g., when doing reverse-video.
 */
void
swapCgs(XtermWidget xw, VTwin *cgsWin, CgsEnum dstCgsId, CgsEnum srcCgsId)
{
    if (dstCgsId != srcCgsId) {
	CgsCache *src;

	if ((src = myCache(xw, cgsWin, srcCgsId)) != 0) {
	    CgsCache *dst;

	    if ((dst = myCache(xw, cgsWin, dstCgsId)) != 0) {
		CgsCache tmp;
		int srcIndex = dataIndex(src);
		int dstIndex = dataIndex(dst);

		EXCHANGE(*src, *dst, tmp);

		relinkData(src, dstIndex);
		relinkData(dst, srcIndex);
	    }
	}
    }
}

/*
 * Free any GC associated with the given id.
 */
GC
freeCgs(XtermWidget xw, VTwin *cgsWin, CgsEnum cgsId)
{
    CgsCache *me;

    if ((me = myCache(xw, cgsWin, cgsId)) != 0) {
	int j;

	for (j = 0; j < DEPTH; ++j) {
	    if (LIST(j).gc != 0) {
		TRACE(("freeCgs(%s, %s) gc %p(%d)\n",
		       traceVTwin(xw, cgsWin),
		       traceCgsEnum(cgsId), (void *) LIST(j).gc, j));
		clrCgsFonts(xw, cgsWin, LIST(j).font);
#if OPT_BOX_CHARS
		if (cgsId == gcDots) {
		    XmuReleaseStippledPixmap(XtScreen((Widget) xw), LIST(j).tile);
		}
#endif
		XFreeGC(TScreenOf(xw)->display, LIST(j).gc);
		memset(&LIST(j), 0, sizeof(LIST(j)));
	    }
	    LINK(0);
	}
    }
    return 0;
}

#ifdef NO_LEAKS
void
noleaks_cachedCgs(XtermWidget xw)
{
#ifndef NO_ACTIVE_ICON
    free(TScreenOf(xw)->icon_cgs_cache);
#endif
    free(TScreenOf(xw)->main_cgs_cache);
}
#endif
