diff options
Diffstat (limited to 'generic/ttk/ttkCache.c')
-rw-r--r-- | generic/ttk/ttkCache.c | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/generic/ttk/ttkCache.c b/generic/ttk/ttkCache.c new file mode 100644 index 0000000..e3aeaba --- /dev/null +++ b/generic/ttk/ttkCache.c @@ -0,0 +1,350 @@ +/* + * 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*/ |