351 lines
9.2 KiB
C
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*/
|