Files
2017-09-04 14:25:47 -05:00

351 lines
9.2 KiB
C

/*
* Theme engine resource cache.
*
* Copyright (c) 2004, Joe English
*
* The problem:
*
* Tk maintains reference counts for fonts, colors, and images,
* and deallocates them when the reference count goes to zero.
* With the theme engine, resources are allocated right before
* drawing an element and released immediately after.
* This causes a severe performance penalty, and on PseudoColor
* visuals it causes colormap cycling as colormap entries are
* released and reused.
*
* Solution: Acquire fonts, colors, and objects from a
* resource cache instead of directly from Tk; the cache
* holds a semipermanent reference to the resource to keep
* it from being deallocated.
*
* The plumbing and control flow here is quite contorted;
* it would be better to address this problem in the core instead.
*
* @@@ BUGS/TODO: Need distinct caches for each combination
* of display, visual, and colormap.
*
* @@@ Colormap flashing on PseudoColor visuals is still possible,
* but this will be a transient effect.
*/
#include <stdio.h> /* for sprintf */
#include <tk.h>
#include "ttkTheme.h"
struct Ttk_ResourceCache_ {
Tcl_Interp *interp; /* Interpreter for error reporting */
Tk_Window tkwin; /* Cache window. */
Tcl_HashTable fontTable; /* Entries: Tcl_Obj* holding FontObjs */
Tcl_HashTable colorTable; /* Entries: Tcl_Obj* holding ColorObjs */
Tcl_HashTable borderTable; /* Entries: Tcl_Obj* holding BorderObjs */
Tcl_HashTable imageTable; /* Entries: Tk_Images */
Tcl_HashTable namedColors; /* Entries: RGB values as Tcl_StringObjs */
};
/*
* Ttk_CreateResourceCache --
* Initialize a new resource cache.
*/
Ttk_ResourceCache Ttk_CreateResourceCache(Tcl_Interp *interp)
{
Ttk_ResourceCache cache = (Ttk_ResourceCache)ckalloc(sizeof(*cache));
cache->tkwin = NULL; /* initialized later */
cache->interp = interp;
Tcl_InitHashTable(&cache->fontTable, TCL_STRING_KEYS);
Tcl_InitHashTable(&cache->colorTable, TCL_STRING_KEYS);
Tcl_InitHashTable(&cache->borderTable, TCL_STRING_KEYS);
Tcl_InitHashTable(&cache->imageTable, TCL_STRING_KEYS);
Tcl_InitHashTable(&cache->namedColors, TCL_STRING_KEYS);
return cache;
}
/*
* Ttk_ClearCache --
* Release references to all cached resources.
*/
static void Ttk_ClearCache(Ttk_ResourceCache cache)
{
Tcl_HashSearch search;
Tcl_HashEntry *entryPtr;
/*
* Free fonts:
*/
entryPtr = Tcl_FirstHashEntry(&cache->fontTable, &search);
while (entryPtr != NULL) {
Tcl_Obj *fontObj = Tcl_GetHashValue(entryPtr);
if (fontObj) {
Tk_FreeFontFromObj(cache->tkwin, fontObj);
Tcl_DecrRefCount(fontObj);
}
entryPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&cache->fontTable);
Tcl_InitHashTable(&cache->fontTable, TCL_STRING_KEYS);
/*
* Free colors:
*/
entryPtr = Tcl_FirstHashEntry(&cache->colorTable, &search);
while (entryPtr != NULL) {
Tcl_Obj *colorObj = Tcl_GetHashValue(entryPtr);
if (colorObj) {
Tk_FreeColorFromObj(cache->tkwin, colorObj);
Tcl_DecrRefCount(colorObj);
}
entryPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&cache->colorTable);
Tcl_InitHashTable(&cache->colorTable, TCL_STRING_KEYS);
/*
* Free borders:
*/
entryPtr = Tcl_FirstHashEntry(&cache->borderTable, &search);
while (entryPtr != NULL) {
Tcl_Obj *borderObj = Tcl_GetHashValue(entryPtr);
if (borderObj) {
Tk_Free3DBorderFromObj(cache->tkwin, borderObj);
Tcl_DecrRefCount(borderObj);
}
entryPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&cache->borderTable);
Tcl_InitHashTable(&cache->borderTable, TCL_STRING_KEYS);
/*
* Free images:
*/
entryPtr = Tcl_FirstHashEntry(&cache->imageTable, &search);
while (entryPtr != NULL) {
Tk_Image image = Tcl_GetHashValue(entryPtr);
if (image) {
Tk_FreeImage(image);
}
entryPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&cache->imageTable);
Tcl_InitHashTable(&cache->imageTable, TCL_STRING_KEYS);
return;
}
/*
* Ttk_FreeResourceCache --
* Release references to all cached resources, delete the cache.
*/
void Ttk_FreeResourceCache(Ttk_ResourceCache cache)
{
Tcl_HashEntry *entryPtr;
Tcl_HashSearch search;
Ttk_ClearCache(cache);
Tcl_DeleteHashTable(&cache->colorTable);
Tcl_DeleteHashTable(&cache->fontTable);
Tcl_DeleteHashTable(&cache->imageTable);
/*
* Free named colors:
*/
entryPtr = Tcl_FirstHashEntry(&cache->namedColors, &search);
while (entryPtr != NULL) {
Tcl_Obj *colorNameObj = Tcl_GetHashValue(entryPtr);
Tcl_DecrRefCount(colorNameObj);
entryPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&cache->namedColors);
ckfree((ClientData)cache);
}
/*
* CacheWinEventHandler --
* Detect when the cache window is destroyed, clear cache.
*/
static void CacheWinEventHandler(ClientData clientData, XEvent *eventPtr)
{
Ttk_ResourceCache cache = clientData;
if (eventPtr->type != DestroyNotify) {
return;
}
Tk_DeleteEventHandler(cache->tkwin, StructureNotifyMask,
CacheWinEventHandler, clientData);
Ttk_ClearCache(cache);
cache->tkwin = NULL;
}
/*
* InitCacheWindow --
* Specify the cache window if not already set.
* @@@ SHOULD: use separate caches for each combination
* @@@ of display, visual, and colormap.
*/
static void InitCacheWindow(Ttk_ResourceCache cache, Tk_Window tkwin)
{
if (cache->tkwin == NULL) {
cache->tkwin = tkwin;
Tk_CreateEventHandler(tkwin, StructureNotifyMask,
CacheWinEventHandler, cache);
}
}
/*
* Ttk_RegisterNamedColor --
* Specify an RGB triplet as a named color.
* Overrides any previous named color specification.
*/
void Ttk_RegisterNamedColor(
Ttk_ResourceCache cache,
const char *colorName,
XColor *colorPtr)
{
int newEntry;
Tcl_HashEntry *entryPtr;
char nameBuf[14];
Tcl_Obj *colorNameObj;
sprintf(nameBuf, "#%04X%04X%04X",
colorPtr->red, colorPtr->green, colorPtr->blue);
colorNameObj = Tcl_NewStringObj(nameBuf, -1);
Tcl_IncrRefCount(colorNameObj);
entryPtr = Tcl_CreateHashEntry(&cache->namedColors, colorName, &newEntry);
if (!newEntry) {
Tcl_Obj *oldColor = Tcl_GetHashValue(entryPtr);
Tcl_DecrRefCount(oldColor);
}
Tcl_SetHashValue(entryPtr, colorNameObj);
}
/*
* CheckNamedColor(objPtr) --
* If objPtr is a registered color name, return a Tcl_Obj *
* containing the registered color value specification.
* Otherwise, return the input argument.
*/
static Tcl_Obj *CheckNamedColor(Ttk_ResourceCache cache, Tcl_Obj *objPtr)
{
Tcl_HashEntry *entryPtr =
Tcl_FindHashEntry(&cache->namedColors, Tcl_GetString(objPtr));
if (entryPtr) { /* Use named color instead */
objPtr = Tcl_GetHashValue(entryPtr);
}
return objPtr;
}
/*
* Template for allocation routines:
*/
typedef void *(*Allocator)(Tcl_Interp *, Tk_Window, Tcl_Obj *);
static Tcl_Obj *Ttk_Use(
Tcl_Interp *interp,
Tcl_HashTable *table,
Allocator allocate,
Tk_Window tkwin,
Tcl_Obj *objPtr)
{
int newEntry;
Tcl_HashEntry *entryPtr =
Tcl_CreateHashEntry(table,Tcl_GetString(objPtr),&newEntry);
Tcl_Obj *cacheObj;
if (!newEntry) {
return Tcl_GetHashValue(entryPtr);
}
cacheObj = Tcl_DuplicateObj(objPtr);
Tcl_IncrRefCount(cacheObj);
if (allocate(interp, tkwin, cacheObj)) {
Tcl_SetHashValue(entryPtr, cacheObj);
return cacheObj;
} else {
Tcl_DecrRefCount(cacheObj);
Tcl_SetHashValue(entryPtr, NULL);
Tcl_BackgroundError(interp);
return NULL;
}
}
/*
* Ttk_UseFont --
* Acquire a font from the cache.
*/
Tcl_Obj *Ttk_UseFont(Ttk_ResourceCache cache, Tk_Window tkwin, Tcl_Obj *objPtr)
{
InitCacheWindow(cache, tkwin);
return Ttk_Use(cache->interp,
&cache->fontTable,(Allocator)Tk_AllocFontFromObj, tkwin, objPtr);
}
/*
* Ttk_UseColor --
* Acquire a color from the cache.
*/
Tcl_Obj *Ttk_UseColor(Ttk_ResourceCache cache, Tk_Window tkwin, Tcl_Obj *objPtr)
{
objPtr = CheckNamedColor(cache, objPtr);
InitCacheWindow(cache, tkwin);
return Ttk_Use(cache->interp,
&cache->colorTable,(Allocator)Tk_AllocColorFromObj, tkwin, objPtr);
}
/*
* Ttk_UseBorder --
* Acquire a Tk_3DBorder from the cache.
*/
Tcl_Obj *Ttk_UseBorder(
Ttk_ResourceCache cache, Tk_Window tkwin, Tcl_Obj *objPtr)
{
objPtr = CheckNamedColor(cache, objPtr);
InitCacheWindow(cache, tkwin);
return Ttk_Use(cache->interp,
&cache->borderTable,(Allocator)Tk_Alloc3DBorderFromObj, tkwin, objPtr);
}
/* NullImageChanged --
* Tk_ImageChangedProc for Ttk_UseImage
*/
static void NullImageChanged(ClientData clientData,
int x, int y, int width, int height, int imageWidth, int imageHeight)
{ /* No-op */ }
/*
* Ttk_UseImage --
* Acquire a Tk_Image from the cache.
*/
Tk_Image Ttk_UseImage(Ttk_ResourceCache cache, Tk_Window tkwin, Tcl_Obj *objPtr)
{
const char *imageName = Tcl_GetString(objPtr);
int newEntry;
Tcl_HashEntry *entryPtr =
Tcl_CreateHashEntry(&cache->imageTable,imageName,&newEntry);
Tk_Image image;
InitCacheWindow(cache, tkwin);
if (!newEntry) {
return Tcl_GetHashValue(entryPtr);
}
image = Tk_GetImage(cache->interp, tkwin, imageName, NullImageChanged,0);
Tcl_SetHashValue(entryPtr, image);
if (!image) {
Tcl_BackgroundError(cache->interp);
}
return image;
}
/*EOF*/