/*
* tkOption.c --
*
* This module contains functions to manage the option database, which
* allows various strings to be associated with windows either by name or
* by class or both.
*
* Copyright (c) 1990-1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tkInt.h"
/*
* The option database is stored as one tree for each main window. Each name
* or class field in an option is associated with a node or leaf of the tree.
* For example, the options "x.y.z" and "x.y*a" each correspond to three nodes
* in the tree; they share the nodes "x" and "x.y", but have different leaf
* nodes. One of the following structures exists for each node or leaf in the
* option tree. It is actually stored as part of the parent node, and
* describes a particular child of the parent.
*
* The structure of the option db tree is a little confusing. There are four
* different kinds of nodes in the tree:
* interior class nodes
* interior name nodes
* leaf class nodes
* leaf name nodes
*
* All interior nodes refer to _window_ classes and names; all leaf nodes
* refer to _option_ classes and names. When looking for a particular option,
* therefore, you must compare interior node values to corresponding window
* values, and compare leaf node values to corresponding option values.
*
* The tree is actually stored in a collection of arrays; there is one each
* combination of WILDCARD/EXACT and CLASS/NAME and NODE/LEAF. The NODE arrays
* contain the interior nodes of the tree; each element has a pointer to an
* array of elements which are the leaves of the tree. The LEAF arrays, rather
* than holding the leaves of the tree, hold a cached subset of the option
* database, consisting of the values of all defined options for a single
* window, and some additional information about each ancestor of the window
* (since some options may be inherited from a parent), all the way back to
* the root window.
*
* Each time a call is made to Tk_GetOption, Tk will attempt to use the cached
* information to satisfy the lookup. If the call is for a window other than
* that for which options are currently cached, the portion of the cache that
* contains information for common ancestors of the two windows is retained
* and the remainder is discarded and rebuilt with new information for the new
* window.
*/
typedef struct Element {
Tk_Uid nameUid; /* Name or class from one element of an option
* spec. */
union {
struct ElArray *arrayPtr;
/* If this is an intermediate node, a pointer
* to a structure describing the remaining
* elements of all options whose prefixes are
* the same up through this element. */
Tk_Uid valueUid; /* For leaf nodes, this is the string value of
* the option. */
} child;
int priority; /* Used to select among matching options.
* Includes both the priority level and a
* serial #. Greater value means higher
* priority. Irrelevant except in leaf
* nodes. */
int flags; /* OR-ed combination of bits. See below for
* values. */
} Element;
/*
* Flags in Element structures:
*
* CLASS - Non-zero means this element refers to a class, zero
* means this element refers to a name.
* NODE - Zero means this is a leaf element (the child field is
* a value, not a pointer to another node). One means
* this is a node element.
* WILDCARD - Non-zero means this there was a star in the original
* specification just before this element. Zero means
* there was a dot.
*/
#define TYPE_MASK 0x7
#define CLASS 0x1
#define NODE 0x2
#define WILDCARD 0x4
#define EXACT_LEAF_NAME 0x0
#define EXACT_LEAF_CLASS 0x1
#define EXACT_NODE_NAME 0x2
#define EXACT_NODE_CLASS 0x3
#define WILDCARD_LEAF_NAME 0x4
#define WILDCARD_LEAF_CLASS 0x5
#define WILDCARD_NODE_NAME 0x6
#define WILDCARD_NODE_CLASS 0x7
/*
* The following structure is used to manage a dynamic array of Elements.
* These structures are used for two purposes: to store the contents of a node
* in the option tree, and for the option stacks described below.
*/
typedef struct ElArray {
int arraySize; /* Number of elements actually allocated in
* the "els" array. */
int numUsed; /* Number of elements currently in use out of
* els. */
Element *nextToUse; /* Pointer to &els[numUsed]. */
Element els[1]; /* Array of structures describing children of
* this node. The array will actually contain
* enough elements for all of the children
* (and even a few extras, perhaps). This must
* be the last field in the structure. */
} ElArray;
#define EL_ARRAY_SIZE(numEls) ((unsigned) (sizeof(ElArray) \
+ ((numEls)-1)*sizeof(Element)))
#define INITIAL_SIZE 5
/*
* In addition to the option tree, which is a relatively static structure,
* there are eight additional structures called "stacks", which are used to
* speed up queries into the option database. The stack structures are
* designed for the situation where an individual widget makes repeated
* requests for its particular options. The requests differ only in their last
* name/class, so during the first request we extract all the options
* pertaining to the particular widget and save them in a stack-like cache;
* subsequent requests for the same widget can search the cache relatively
* quickly. In fact, the cache is a hierarchical one, storing a list of
* relevant options for this widget and all of its ancestors up to the
* application root; hence the name "stack".
*
* Each of the eight stacks consists of an array of Elements, ordered in terms
* of levels in the window hierarchy. All the elements relevant for the
* top-level widget appear first in the array, followed by all those from the
* next-level widget on the path to the current widget, etc. down to those for
* the current widget.
*
* Cached information is divided into eight stacks according to the CLASS,
* NODE, and WILDCARD flags. Leaf and non-leaf information is kept separate to
* speed up individual probes (non-leaf information is only relevant when
* building the stacks, but isn't relevant when making probes; similarly, only
* non-leaf information is relevant when the stacks are being extended to the
* next widget down in the widget hierarchy). Wildcard elements are handled
* separately from "exact" elements because once they appear at a particular
* level in the stack they remain active for all deeper levels; exact elements
* are only relevant at a particular level. For example, when searching for
* options relevant in a particular window, the entire wildcard stacks get
* checked, but only the portions of the exact stacks that pertain to the
* window's parent. Lastly, name and class stacks are kept separate because
* different search keys are used when searching them; keeping them separate
* speeds up the searches.
*/
#define NUM_STACKS 8
/*
* One of the following structures is used to keep track of each level in the
* stacks.
*/
typedef struct StackLevel {
TkWindow *winPtr; /* Window corresponding to this stack
* level. */
int bases[NUM_STACKS]; /* For each stack, index of first element on
* stack corresponding to this level (used to
* restore "numUsed" fields when popping out
* of a level. */
} StackLevel;
typedef struct ThreadSpecificData {
int initialized; /* 0 means the ThreadSpecific Data structure
* for the current thread needs to be
* initialized. */
ElArray *stacks[NUM_STACKS];
TkWindow *cachedWindow; /* Lowest-level window currently loaded in
* stacks at present. NULL means stacks have
* never been used, or have been invalidated
* because of a change to the database. */
/*
* Information about all of the stack levels that are currently active.
* This array grows dynamically to become as large as needed.
*/
StackLevel *levels; /* Array describing current stack. */
int numLevels; /* Total space allocated. */
int curLevel; /* Highest level currently in use. Note:
* curLevel is never 0! (I don't remember why
* anymore...) */
int serial; /* A serial number for all options entered
* into the database so far. It increments on
* each addition to the option database. It is
* used in computing option priorities, so
* that the most recent entry wins when
* choosing between options at the same
* priority level. */
Element defaultMatch; /* Special "no match" Element to use as
* default for searches.*/
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;
/*
* Forward declarations for functions defined in this file:
*/
static int AddFromString(Tcl_Interp *interp, Tk_Window tkwin,
char *string, int priority);
static void ClearOptionTree(ElArray *arrayPtr);
static ElArray * ExtendArray(ElArray *arrayPtr, Element *elPtr);
static void ExtendStacks(ElArray *arrayPtr, int leaf);
static int GetDefaultOptions(Tcl_Interp *interp,
TkWindow *winPtr);
static ElArray * NewArray(int numEls);
static void OptionThreadExitProc(ClientData clientData);
static void OptionInit(TkMainInfo *mainPtr);
static int ParsePriority(Tcl_Interp *interp, const char *string);
static int ReadOptionFile(Tcl_Interp *interp, Tk_Window tkwin,
const char *fileName, int priority);
static void SetupStacks(TkWindow *winPtr, int leaf);
/*
*--------------------------------------------------------------
*
* Tk_AddOption --
*
* Add a new option to the option database.
*
* Results:
* None.
*
* Side effects:
* Information is added to the option database.
*
*--------------------------------------------------------------
*/
void
Tk_AddOption(
Tk_Window tkwin, /* Window token; option will be associated
* with main window for this window. */
const char *name, /* Multi-element name of option. */
const char *value, /* String value for option. */
int priority) /* Overall priority level to use for this
* option, such as TK_USER_DEFAULT_PRIO or
* TK_INTERACTIVE_PRIO. Must be between 0 and
* TK_MAX_PRIO. */
{
TkWindow *winPtr = ((TkWindow *) tkwin)->mainPtr->winPtr;
register ElArray **arrayPtrPtr;
register Element *elPtr;
Element newEl;
register const char *p;
const char *field;
int count, firstField;
ptrdiff_t length;
#define TMP_SIZE 100
char tmp[TMP_SIZE+1];
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
if (winPtr->mainPtr->optionRootPtr == NULL) {
OptionInit(winPtr->mainPtr);
}
tsdPtr->cachedWindow = NULL;/* Invalidate the cache. */
/*
* Compute the priority for the new element, including both the overall
* level and the serial number (to disambiguate with the level).
*/
if (priority < 0) {
priority = 0;
} else if (priority > TK_MAX_PRIO) {
priority = TK_MAX_PRIO;
}
newEl.priority = (priority << 24) + tsdPtr->serial;
tsdPtr->serial++;
/*
* Parse the option one field at a time.
*/
arrayPtrPtr = &(((TkWindow *) tkwin)->mainPtr->optionRootPtr);
p = name;
for (firstField = 1; ; firstField = 0) {
/*
* Scan the next field from the name and convert it to a Tk_Uid. Must
* copy the field before calling Tk_Uid, so that a terminating NULL
* may be added without modifying the source string.
*/
if (*p == '*') {
newEl.flags = WILDCARD;
p++;
} else {
newEl.flags = 0;
}
field = p;
while ((*p != 0) && (*p != '.') && (*p != '*')) {
p++;
}
length = p - field;
if (length > TMP_SIZE) {
length = TMP_SIZE;
}
strncpy(tmp, field, (size_t) length);
tmp[length] = 0;
newEl.nameUid = Tk_GetUid(tmp);
if (isupper(UCHAR(*field))) {
newEl.flags |= CLASS;
}
if (*p != 0) {
/*
* New element will be a node. If this option can't possibly apply
* to this main window, then just skip it. Otherwise, add it to
* the parent, if it isn't already there, and descend into it.
*/
newEl.flags |= NODE;
if (firstField && !(newEl.flags & WILDCARD)
&& (newEl.nameUid != winPtr->nameUid)
&& (newEl.nameUid != winPtr->classUid)) {
return;
}
for (elPtr = (*arrayPtrPtr)->els, count = (*arrayPtrPtr)->numUsed;
; elPtr++, count--) {
if (count == 0) {
newEl.child.arrayPtr = NewArray(5);
*arrayPtrPtr = ExtendArray(*arrayPtrPtr, &newEl);
arrayPtrPtr = &((*arrayPtrPtr)
->nextToUse[-1].child.arrayPtr);
break;
}
if ((elPtr->nameUid == newEl.nameUid)
&& (elPtr->flags == newEl.flags)) {
arrayPtrPtr = &(elPtr->child.arrayPtr);
break;
}
}
if (*p == '.') {
p++;
}
} else {
/*
* New element is a leaf. Add it to the parent, if it isn't
* already there. If it exists already, keep whichever value has
* highest priority.
*/
newEl.child.valueUid = Tk_GetUid(value);
for (elPtr = (*arrayPtrPtr)->els, count = (*arrayPtrPtr)->numUsed;
; elPtr++, count--) {
if (count == 0) {
*arrayPtrPtr = ExtendArray(*arrayPtrPtr, &newEl);
return;
}
if ((elPtr->nameUid == newEl.nameUid)
&& (elPtr->flags == newEl.flags)) {
if (elPtr->priority < newEl.priority) {
elPtr->priority = newEl.priority;
elPtr->child.valueUid = newEl.child.valueUid;
}
return;
}
}
}
}
}
/*
*--------------------------------------------------------------
*
* Tk_GetOption --
*
* Retrieve an option from the option database.
*
* Results:
* The return value is the value specified in the option database for the
* given name and class on the given window. If there is nothing
* specified in the database for that option, then NULL is returned.
*
* Side effects:
* The internal caches used to speed up option mapping may be modified,
* if this tkwin is different from the last tkwin used for option
* retrieval.
*
*--------------------------------------------------------------
*/
Tk_Uid
Tk_GetOption(
Tk_Window tkwin, /* Token for window that option is associated
* with. */
const char *name, /* Name of option. */
const char *className) /* Class of option. NULL means there is no
* class for this option: just check for
* name. */
{
Tk_Uid nameId, classId = NULL;
char *masqName;
register Element *elPtr, *bestPtr;
register int count;
StackLevel *levelPtr;
int stackDepth[NUM_STACKS];
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
/*
* Note: no need to call OptionInit here: it will be done by the
* SetupStacks call below (squeeze out those nanoseconds).
*/
if (tkwin != (Tk_Window) tsdPtr->cachedWindow) {
SetupStacks((TkWindow *) tkwin, 1);
}
/*
* Get a default "best" match.
*/
bestPtr = &tsdPtr->defaultMatch;
/*
* For megawidget support, we want to have some widget options masquerade
* as options for other widgets. For example, a combobox has a button in
* it; this button ought to pick up the *Button.background, etc., options.
* But because the class of the widget is Combobox, our normal search
* won't get that option.
*
* To work around this, the option name field syntax was extended to allow
* for a "." in the name; if this character occurs in the name, then it
* indicates that this name contains a new window class and an option
* name, ie, "Button.foreground". If we see this form in the name field,
* we query the option database directly (since the option stacks will not
* have the information we need).
*/
masqName = strchr(name, (int)'.');
if (masqName != NULL) {
/*
* This option is masquerading with a different window class. Search
* the stack to the depth it was before the current window's
* information was pushed (the value for which is stored in the bases
* field).
*/
levelPtr = &tsdPtr->levels[tsdPtr->curLevel];
nameId = Tk_GetUid(masqName+1);
for (count = 0; count < NUM_STACKS; count++) {
stackDepth[count] = levelPtr->bases[count];
}
} else {
/*
* No option masquerading here. Just use the current level to get the
* stack depths.
*/
nameId = Tk_GetUid(name);
for (count = 0; count < NUM_STACKS; count++) {
stackDepth[count] = tsdPtr->stacks[count]->numUsed;
}
}
/*
* Probe the stacks for matches.
*/
for (elPtr = tsdPtr->stacks[EXACT_LEAF_NAME]->els,
count = stackDepth[EXACT_LEAF_NAME]; count > 0;
elPtr++, count--) {
if ((elPtr->nameUid == nameId)
&& (elPtr->priority > bestPtr->priority)) {
bestPtr = elPtr;
}
}
for (elPtr = tsdPtr->stacks[WILDCARD_LEAF_NAME]->els,
count = stackDepth[WILDCARD_LEAF_NAME]; count > 0;
elPtr++, count--) {
if ((elPtr->nameUid == nameId)
&& (elPtr->priority > bestPtr->priority)) {
bestPtr = elPtr;
}
}
if (className != NULL) {
classId = Tk_GetUid(className);
for (elPtr = tsdPtr->stacks[EXACT_LEAF_CLASS]->els,
count = stackDepth[EXACT_LEAF_CLASS]; count > 0;
elPtr++, count--) {
if ((elPtr->nameUid == classId)
&& (elPtr->priority > bestPtr->priority)) {
bestPtr = elPtr;
}
}
for (elPtr = tsdPtr->stacks[WILDCARD_LEAF_CLASS]->els,
count = stackDepth[WILDCARD_LEAF_CLASS]; count > 0;
elPtr++, count--) {
if ((elPtr->nameUid == classId)
&& (elPtr->priority > bestPtr->priority)) {
bestPtr = elPtr;
}
}
}
/*
* If this option was masquerading with a different window class, probe
* the option database now. Note that this will be inefficient if the
* option database is densely populated, or if the widget has many
* masquerading options.
*/
if (masqName != NULL) {
char *masqClass;
Tk_Uid nodeId, winClassId, winNameId;
unsigned int classNameLength;
register Element *nodePtr, *leafPtr;
static const int searchOrder[] = {
EXACT_NODE_NAME, WILDCARD_NODE_NAME, EXACT_NODE_CLASS,
WILDCARD_NODE_CLASS, -1
};
const int *currentPtr;
int currentStack, leafCount;
/*
* Extract the masquerade class name from the name field.
*/
classNameLength = (unsigned) (masqName - name);
masqClass = ckalloc(classNameLength + 1);
strncpy(masqClass, name, classNameLength);
masqClass[classNameLength] = '\0';
winClassId = Tk_GetUid(masqClass);
ckfree(masqClass);
winNameId = ((TkWindow *) tkwin)->nameUid;
levelPtr = &tsdPtr->levels[tsdPtr->curLevel];
for (currentPtr = searchOrder; *currentPtr != -1; currentPtr++) {
currentStack = *currentPtr;
nodePtr = tsdPtr->stacks[currentStack]->els;
count = levelPtr->bases[currentStack];
/*
* For wildcard stacks, check all entries; for non-wildcard
* stacks, only check things that matched in the parent.
*/
if (!(currentStack & WILDCARD)) {
nodePtr += levelPtr[-1].bases[currentStack];
count -= levelPtr[-1].bases[currentStack];
}
if (currentStack && CLASS) {
nodeId = winClassId;
} else {
nodeId = winNameId;
}
for ( ; count > 0; nodePtr++, count--) {
if (nodePtr->nameUid == nodeId) {
leafPtr = nodePtr->child.arrayPtr->els;
leafCount = nodePtr->child.arrayPtr->numUsed;
for ( ; leafCount > 0; leafPtr++, leafCount--) {
if (leafPtr->flags & CLASS && className != NULL) {
if (leafPtr->nameUid == classId &&
leafPtr->priority > bestPtr->priority) {
bestPtr = leafPtr;
}
} else {
if (leafPtr->nameUid == nameId &&
leafPtr->priority > bestPtr->priority) {
bestPtr = leafPtr;
}
}
}
}
}
}
}
return bestPtr->child.valueUid;
}
/*
*--------------------------------------------------------------
*
* Tk_OptionObjCmd --
*
* This function is invoked to process the "option" Tcl command. See the
* user documentation for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*--------------------------------------------------------------
*/
int
Tk_OptionObjCmd(
ClientData clientData, /* Main window associated with interpreter. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of Tcl_Obj arguments. */
Tcl_Obj *const objv[]) /* Tcl_Obj arguments. */
{
Tk_Window tkwin = clientData;
int index, result;
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
static const char *const optionCmds[] = {
"add", "clear", "get", "readfile", NULL
};
enum optionVals {
OPTION_ADD, OPTION_CLEAR, OPTION_GET, OPTION_READFILE
};
if (objc < 2) {
Tcl_WrongNumArgs(interp, 1, objv, "cmd arg ?arg ...?");
return TCL_ERROR;
}
result = Tcl_GetIndexFromObjStruct(interp, objv[1], optionCmds,
sizeof(char *), "option", 0, &index);
if (result != TCL_OK) {
return result;
}
result = TCL_OK;
switch ((enum optionVals) index) {
case OPTION_ADD: {
int priority;
if ((objc != 4) && (objc != 5)) {
Tcl_WrongNumArgs(interp, 2, objv, "pattern value ?priority?");
return TCL_ERROR;
}
if (objc == 4) {
priority = TK_INTERACTIVE_PRIO;
} else {
priority = ParsePriority(interp, Tcl_GetString(objv[4]));
if (priority < 0) {
return TCL_ERROR;
}
}
Tk_AddOption(tkwin, Tcl_GetString(objv[2]), Tcl_GetString(objv[3]),
priority);
break;
}
case OPTION_CLEAR: {
TkMainInfo *mainPtr = ((TkWindow *) tkwin)->mainPtr;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
if (mainPtr->optionRootPtr != NULL) {
ClearOptionTree(mainPtr->optionRootPtr);
mainPtr->optionRootPtr = NULL;
}
tsdPtr->cachedWindow = NULL;
break;
}
case OPTION_GET: {
Tk_Window window;
Tk_Uid value;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 2, objv, "window name class");
return TCL_ERROR;
}
window = Tk_NameToWindow(interp, Tcl_GetString(objv[2]), tkwin);
if (window == NULL) {
return TCL_ERROR;
}
value = Tk_GetOption(window, Tcl_GetString(objv[3]),
Tcl_GetString(objv[4]));
if (value != NULL) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(value, -1));
}
break;
}
case OPTION_READFILE: {
int priority;
if ((objc != 3) && (objc != 4)) {
Tcl_WrongNumArgs(interp, 2, objv, "fileName ?priority?");
return TCL_ERROR;
}
if (objc == 4) {
priority = ParsePriority(interp, Tcl_GetString(objv[3]));
if (priority < 0) {
return TCL_ERROR;
}
} else {
priority = TK_INTERACTIVE_PRIO;
}
result = ReadOptionFile(interp, tkwin, Tcl_GetString(objv[2]),
priority);
break;
}
}
return result;
}
/*
*--------------------------------------------------------------
*
* TkOptionDeadWindow --
*
* This function is called whenever a window is deleted. It cleans up any
* option-related stuff associated with the window.
*
* Results:
* None.
*
* Side effects:
* Option-related resources are freed. See code below for details.
*
*--------------------------------------------------------------
*/
void
TkOptionDeadWindow(
register TkWindow *winPtr) /* Window to be cleaned up. */
{
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
/*
* If this window is in the option stacks, then clear the stacks.
*
* XXX: OptionThreadExitProc will be invoked before DeleteWindowsExitProc
* XXX: if it is thread-specific (which it should be), invalidating the
* XXX: tsd. Tk shutdown needs to be verified to handle this correctly.
*/
if (tsdPtr->initialized && (winPtr->optionLevel != -1)) {
int i;
for (i = 1; i <= tsdPtr->curLevel; i++) {
tsdPtr->levels[i].winPtr->optionLevel = -1;
}
tsdPtr->curLevel = -1;
tsdPtr->cachedWindow = NULL;
}
/*
* If this window was a main window, then delete its option database.
*/
if ((winPtr->mainPtr != NULL) && (winPtr->mainPtr->winPtr == winPtr)
&& (winPtr->mainPtr->optionRootPtr != NULL)) {
ClearOptionTree(winPtr->mainPtr->optionRootPtr);
winPtr->mainPtr->optionRootPtr = NULL;
}
}
/*
*----------------------------------------------------------------------
*
* TkOptionClassChanged --
*
* This function is invoked when a window's class changes. If the window
* is on the option cache, this function flushes any information for the
* window, since the new class could change what is relevant.
*
* Results:
* None.
*
* Side effects:
* The option cache may be flushed in part or in whole.
*
*----------------------------------------------------------------------
*/
void
TkOptionClassChanged(
TkWindow *winPtr) /* Window whose class changed. */
{
int i, j, *basePtr;
ElArray *arrayPtr;
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
if (winPtr->optionLevel == -1) {
return;
}
/*
* Find the lowest stack level that refers to this window, then flush all
* of the levels above the matching one.
*/
for (i = 1; i <= tsdPtr->curLevel; i++) {
if (tsdPtr->levels[i].winPtr == winPtr) {
for (j = i; j <= tsdPtr->curLevel; j++) {
tsdPtr->levels[j].winPtr->optionLevel = -1;
}
tsdPtr->curLevel = i-1;
basePtr = tsdPtr->levels[i].bases;
for (j = 0; j < NUM_STACKS; j++) {
arrayPtr = tsdPtr->stacks[j];
arrayPtr->numUsed = basePtr[j];
arrayPtr->nextToUse = &arrayPtr->els[arrayPtr->numUsed];
}
if (tsdPtr->curLevel <= 0) {
tsdPtr->cachedWindow = NULL;
} else {
tsdPtr->cachedWindow = tsdPtr->levels[tsdPtr->curLevel].winPtr;
}
break;
}
}
}
/*
*----------------------------------------------------------------------
*
* ParsePriority --
*
* Parse a string priority value.
*
* Results:
* The return value is the integer priority level corresponding to
* string, or -1 if string doesn't point to a valid priority level. In
* this case, an error message is left in the interp's result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
ParsePriority(
Tcl_Interp *interp, /* Interpreter to use for error reporting. */
const char *string) /* Describes a priority level, either
* symbolically or numerically. */
{
int priority, c;
size_t length;
c = string[0];
length = strlen(string);
if ((c == 'w')
&& (strncmp(string, "widgetDefault", length) == 0)) {
return TK_WIDGET_DEFAULT_PRIO;
} else if ((c == 's')
&& (strncmp(string, "startupFile", length) == 0)) {
return TK_STARTUP_FILE_PRIO;
} else if ((c == 'u')
&& (strncmp(string, "userDefault", length) == 0)) {
return TK_USER_DEFAULT_PRIO;
} else if ((c == 'i')
&& (strncmp(string, "interactive", length) == 0)) {
return TK_INTERACTIVE_PRIO;
} else {
char *end;
priority = strtoul(string, &end, 0);
if ((end == string) || (*end != 0) || (priority < 0)
|| (priority > 100)) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"bad priority level \"%s\": must be "
"widgetDefault, startupFile, userDefault, "
"interactive, or a number between 0 and 100", string));
Tcl_SetErrorCode(interp, "TK", "VALUE", "PRIORITY", NULL);
return -1;
}
}
return priority;
}
/*
*----------------------------------------------------------------------
*
* AddFromString --
*
* Given a string containing lines in the standard format for X resources
* (see other documentation for details on what this is), parse the
* resource specifications and enter them as options for tkwin's main
* window.
*
* Results:
* The return value is a standard Tcl return code. In the case of an
* error in parsing string, TCL_ERROR will be returned and an error
* message will be left in the interp's result. The memory at string is
* totally trashed by this function. If you care about its contents, make
* a copy before calling here.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
AddFromString(
Tcl_Interp *interp, /* Interpreter to use for reporting results. */
Tk_Window tkwin, /* Token for window: options are entered for
* this window's main window. */
char *string, /* String containing option specifiers. */
int priority) /* Priority level to use for options in this
* string, such as TK_USER_DEFAULT_PRIO or
* TK_INTERACTIVE_PRIO. Must be between 0 and
* TK_MAX_PRIO. */
{
register char *src, *dst;
char *name, *value;
int lineNum;
src = string;
lineNum = 1;
while (1) {
/*
* Skip leading white space and empty lines and comment lines, and
* check for the end of the spec.
*/
while ((*src == ' ') || (*src == '\t')) {
src++;
}
if ((*src == '#') || (*src == '!')) {
do {
src++;
if ((src[0] == '\\') && (src[1] == '\n')) {
src += 2;
lineNum++;
}
} while ((*src != '\n') && (*src != 0));
}
if (*src == '\n') {
src++;
lineNum++;
continue;
}
if (*src == '\0') {
break;
}
/*
* Parse off the option name, collapsing out backslash-newline
* sequences of course.
*/
dst = name = src;
while (*src != ':') {
if ((*src == '\0') || (*src == '\n')) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"missing colon on line %d", lineNum));
Tcl_SetErrorCode(interp, "TK", "OPTIONDB", "COLON", NULL);
return TCL_ERROR;
}
if ((src[0] == '\\') && (src[1] == '\n')) {
src += 2;
lineNum++;
} else {
*dst = *src;
dst++;
src++;
}
}
/*
* Eliminate trailing white space on the name, and null-terminate
* it.
*/
while ((dst != name) && ((dst[-1] == ' ') || (dst[-1] == '\t'))) {
dst--;
}
*dst = '\0';
/*
* Skip white space between the name and the value.
*/
src++;
while ((*src == ' ') || (*src == '\t')) {
src++;
}
if (*src == '\0') {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"missing value on line %d", lineNum));
Tcl_SetErrorCode(interp, "TK", "OPTIONDB", "VALUE", NULL);
return TCL_ERROR;
}
/*
* Parse off the value, squeezing out backslash-newline sequences
* along the way.
*/
dst = value = src;
while (*src != '\n') {
if (*src == '\0') {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"missing newline on line %d", lineNum));
Tcl_SetErrorCode(interp, "TK", "OPTIONDB", "NEWLINE", NULL);
return TCL_ERROR;
}
if (*src == '\\'){
if (src[1] == '\n') {
src += 2;
lineNum++;
continue;
} else if (src[1] == 'n') {
src += 2;
*dst++ = '\n';
continue;
} else if (src[1] == '\t' || src[1] == ' ' || src[1] == '\\') {
++src;
} else if (src[1] >= '0' && src[1] <= '3' && src[2] >= '0' &&
src[2] <= '9' && src[3] >= '0' && src[3] <= '9') {
*dst++ = ((src[1]&7)<<6) | ((src[2]&7)<<3) | (src[3]&7);
src += 4;
continue;
}
}
*dst++ = *src++;
}
*dst = 0;
/*
* Enter the option into the database.
*/
Tk_AddOption(tkwin, name, value, priority);
src++;
lineNum++;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ReadOptionFile --
*
* Read a file of options ("resources" in the old X terminology) and load
* them into the option database.
*
* Results:
* The return value is a standard Tcl return code. In the case of an
* error in parsing string, TCL_ERROR will be returned and an error
* message will be left in the interp's result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
ReadOptionFile(
Tcl_Interp *interp, /* Interpreter to use for reporting results. */
Tk_Window tkwin, /* Token for window: options are entered for
* this window's main window. */
const char *fileName, /* Name of file containing options. */
int priority) /* Priority level to use for options in this
* file, such as TK_USER_DEFAULT_PRIO or
* TK_INTERACTIVE_PRIO. Must be between 0 and
* TK_MAX_PRIO. */
{
const char *realName;
char *buffer;
int result, bufferSize;
Tcl_Channel chan;
Tcl_DString newName;
/*
* Prevent file system access in a safe interpreter.
*/
if (Tcl_IsSafe(interp)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"can't read options from a file in a safe interpreter", -1));
Tcl_SetErrorCode(interp, "TK", "SAFE", "OPTION_FILE", NULL);
return TCL_ERROR;
}
realName = Tcl_TranslateFileName(interp, fileName, &newName);
if (realName == NULL) {
return TCL_ERROR;
}
chan = Tcl_OpenFileChannel(interp, realName, "r", 0);
Tcl_DStringFree(&newName);
if (chan == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf("couldn't open \"%s\": %s",
fileName, Tcl_PosixError(interp)));
return TCL_ERROR;
}
/*
* Compute size of file by seeking to the end of the file. This will
* overallocate if we are performing CRLF translation.
*/
bufferSize = (int) Tcl_Seek(chan, (Tcl_WideInt) 0, SEEK_END);
Tcl_Seek(chan, (Tcl_WideInt) 0, SEEK_SET);
if (bufferSize < 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"error seeking to end of file \"%s\": %s",
fileName, Tcl_PosixError(interp)));
Tcl_Close(NULL, chan);
return TCL_ERROR;
}
buffer = ckalloc(bufferSize + 1);
bufferSize = Tcl_Read(chan, buffer, bufferSize);
if (bufferSize < 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"error reading file \"%s\": %s",
fileName, Tcl_PosixError(interp)));
Tcl_Close(NULL, chan);
return TCL_ERROR;
}
Tcl_Close(NULL, chan);
buffer[bufferSize] = 0;
result = AddFromString(interp, tkwin, buffer, priority);
ckfree(buffer);
return result;
}
/*
*--------------------------------------------------------------
*
* NewArray --
*
* Create a new ElArray structure of a given size.
*
* Results:
* The return value is a pointer to a properly initialized element array
* with "numEls" space. The array is marked as having no active elements.
*
* Side effects:
* Memory is allocated.
*
*--------------------------------------------------------------
*/
static ElArray *
NewArray(
int numEls) /* How many elements of space to allocate. */
{
register ElArray *arrayPtr = ckalloc(EL_ARRAY_SIZE(numEls));
arrayPtr->arraySize = numEls;
arrayPtr->numUsed = 0;
arrayPtr->nextToUse = arrayPtr->els;
return arrayPtr;
}
/*
*--------------------------------------------------------------
*
* ExtendArray --
*
* Add a new element to an array, extending the array if necessary.
*
* Results:
* The return value is a pointer to the new array, which will be
* different from arrayPtr if the array got expanded.
*
* Side effects:
* Memory may be allocated or freed.
*
*--------------------------------------------------------------
*/
static ElArray *
ExtendArray(
register ElArray *arrayPtr, /* Array to be extended. */
register Element *elPtr) /* Element to be copied into array. */
{
/*
* If the current array has filled up, make it bigger.
*/
if (arrayPtr->numUsed >= arrayPtr->arraySize) {
register int newSize = 2*arrayPtr->arraySize;
arrayPtr = ckrealloc(arrayPtr, EL_ARRAY_SIZE(newSize));
arrayPtr->arraySize = newSize;
arrayPtr->nextToUse = &arrayPtr->els[arrayPtr->numUsed];
}
*arrayPtr->nextToUse = *elPtr;
arrayPtr->nextToUse++;
arrayPtr->numUsed++;
return arrayPtr;
}
/*
*--------------------------------------------------------------
*
* SetupStacks --
*
* Arrange the stacks so that they cache all the option information for a
* particular window.
*
* Results:
* None.
*
* Side effects:
* The stacks are modified to hold information for tkwin and all its
* ancestors in the window hierarchy.
*
*--------------------------------------------------------------
*/
static void
SetupStacks(
TkWindow *winPtr, /* Window for which information is to be
* cached. */
int leaf) /* Non-zero means this is the leaf window
* being probed. Zero means this is an
* ancestor of the desired leaf. */
{
int level, i;
const int *iPtr;
register StackLevel *levelPtr;
register ElArray *arrayPtr;
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
/*
* The following array defines the order in which the current stacks are
* searched to find matching entries to add to the stacks. Given the
* current priority-based scheme, the order below is no longer relevant;
* all that matters is that an element is on the list *somewhere*. The
* ordering is a relic of the old days when priorities were determined
* differently.
*/
static const int searchOrder[] = {WILDCARD_NODE_CLASS, WILDCARD_NODE_NAME,
EXACT_NODE_CLASS, EXACT_NODE_NAME, -1};
if (winPtr->mainPtr->optionRootPtr == NULL) {
OptionInit(winPtr->mainPtr);
}
/*
* Step 1: make sure that options are cached for this window's parent.
*/
if (winPtr->parentPtr != NULL) {
level = winPtr->parentPtr->optionLevel;
if ((level == -1) || (tsdPtr->cachedWindow == NULL)) {
SetupStacks(winPtr->parentPtr, 0);
level = winPtr->parentPtr->optionLevel;
}
level++;
} else {
level = 1;
}
/*
* Step 2: pop extra unneeded information off the stacks and mark those
* windows as no longer having cached information.
*/
if (tsdPtr->curLevel >= level) {
while (tsdPtr->curLevel >= level) {
tsdPtr->levels[tsdPtr->curLevel].winPtr->optionLevel = -1;
tsdPtr->curLevel--;
}
levelPtr = &tsdPtr->levels[level];
for (i = 0; i < NUM_STACKS; i++) {
arrayPtr = tsdPtr->stacks[i];
arrayPtr->numUsed = levelPtr->bases[i];
arrayPtr->nextToUse = &arrayPtr->els[arrayPtr->numUsed];
}
}
tsdPtr->curLevel = winPtr->optionLevel = level;
/*
* Step 3: if the root database information isn't loaded or isn't valid,
* initialize level 0 of the stack from the database root (this only
* happens if winPtr is a main window).
*/
if ((tsdPtr->curLevel == 1)
&& ((tsdPtr->cachedWindow == NULL)
|| (tsdPtr->cachedWindow->mainPtr != winPtr->mainPtr))) {
for (i = 0; i < NUM_STACKS; i++) {
arrayPtr = tsdPtr->stacks[i];
arrayPtr->numUsed = 0;
arrayPtr->nextToUse = arrayPtr->els;
}
ExtendStacks(winPtr->mainPtr->optionRootPtr, 0);
}
/*
* Step 4: create a new stack level; grow the level array if we've run out
* of levels. Clear the stacks for EXACT_LEAF_NAME and EXACT_LEAF_CLASS
* (anything that was there is of no use any more).
*/
if (tsdPtr->curLevel >= tsdPtr->numLevels) {
StackLevel *newLevels =
ckalloc(tsdPtr->numLevels * 2 * sizeof(StackLevel));
memcpy(newLevels, tsdPtr->levels,
tsdPtr->numLevels * sizeof(StackLevel));
ckfree(tsdPtr->levels);
tsdPtr->numLevels *= 2;
tsdPtr->levels = newLevels;
}
levelPtr = &tsdPtr->levels[tsdPtr->curLevel];
levelPtr->winPtr = winPtr;
arrayPtr = tsdPtr->stacks[EXACT_LEAF_NAME];
arrayPtr->numUsed = 0;
arrayPtr->nextToUse = arrayPtr->els;
arrayPtr = tsdPtr->stacks[EXACT_LEAF_CLASS];
arrayPtr->numUsed = 0;
arrayPtr->nextToUse = arrayPtr->els;
for (i = 0; i < NUM_STACKS; i++) {
levelPtr->bases[i] = tsdPtr->stacks[i]->numUsed;
}
/*
* Step 5: scan the current stack level looking for matches to this
* window's name or class; where found, add new information to the stacks.
*/
for (iPtr = searchOrder; *iPtr != -1; iPtr++) {
register Element *elPtr;
int count;
Tk_Uid id;
i = *iPtr;
if (i & CLASS) {
id = winPtr->classUid;
} else {
id = winPtr->nameUid;
}
elPtr = tsdPtr->stacks[i]->els;
count = levelPtr->bases[i];
/*
* For wildcard stacks, check all entries; for non-wildcard stacks,
* only check things that matched in the parent.
*/
if (!(i & WILDCARD)) {
elPtr += levelPtr[-1].bases[i];
count -= levelPtr[-1].bases[i];
}
for ( ; count > 0; elPtr++, count--) {
if (elPtr->nameUid != id) {
continue;
}
ExtendStacks(elPtr->child.arrayPtr, leaf);
}
}
tsdPtr->cachedWindow = winPtr;
}
/*
*--------------------------------------------------------------
*
* ExtendStacks --
*
* Given an element array, copy all the elements from the array onto the
* system stacks (except for irrelevant leaf elements).
*
* Results:
* None.
*
* Side effects:
* The option stacks are extended.
*
*--------------------------------------------------------------
*/
static void
ExtendStacks(
ElArray *arrayPtr, /* Array of elements to copy onto stacks. */
int leaf) /* If zero, then don't copy exact leaf
* elements. */
{
register int count;
register Element *elPtr;
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
for (elPtr = arrayPtr->els, count = arrayPtr->numUsed;
count > 0; elPtr++, count--) {
if (!(elPtr->flags & (NODE|WILDCARD)) && !leaf) {
continue;
}
tsdPtr->stacks[elPtr->flags] =
ExtendArray(tsdPtr->stacks[elPtr->flags], elPtr);
}
}
/*
*--------------------------------------------------------------
*
* OptionThreadExitProc --
*
* Free data structures for option handling.
*
* Results:
* None.
*
* Side effects:
* Option-related data structures get freed.
*
*--------------------------------------------------------------
*/
static void
OptionThreadExitProc(
ClientData clientData) /* not used */
{
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
if (tsdPtr->initialized) {
int i;
for (i = 0; i < NUM_STACKS; i++) {
ckfree(tsdPtr->stacks[i]);
}
ckfree(tsdPtr->levels);
tsdPtr->initialized = 0;
}
}
/*
*--------------------------------------------------------------
*
* OptionInit --
*
* Initialize data structures for option handling.
*
* Results:
* None.
*
* Side effects:
* Option-related data structures get initialized.
*
*--------------------------------------------------------------
*/
static void
OptionInit(
register TkMainInfo *mainPtr)
/* Top-level information about window that
* isn't initialized yet. */
{
int i;
Tcl_Interp *interp;
ThreadSpecificData *tsdPtr =
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
Element *defaultMatchPtr = &tsdPtr->defaultMatch;
/*
* First, once-only initialization.
*/
if (tsdPtr->initialized == 0) {
tsdPtr->initialized = 1;
tsdPtr->cachedWindow = NULL;
tsdPtr->numLevels = 5;
tsdPtr->curLevel = -1;
tsdPtr->serial = 0;
tsdPtr->levels = ckalloc(5 * sizeof(StackLevel));
for (i = 0; i < NUM_STACKS; i++) {
tsdPtr->stacks[i] = NewArray(10);
tsdPtr->levels[0].bases[i] = 0;
}
defaultMatchPtr->nameUid = NULL;
defaultMatchPtr->child.valueUid = NULL;
defaultMatchPtr->priority = -1;
defaultMatchPtr->flags = 0;
Tcl_CreateThreadExitHandler(OptionThreadExitProc, NULL);
}
/*
* Then, per-main-window initialization. Create and delete dummy
* interpreter for message logging.
*/
mainPtr->optionRootPtr = NewArray(20);
interp = Tcl_CreateInterp();
GetDefaultOptions(interp, mainPtr->winPtr);
Tcl_DeleteInterp(interp);
}
/*
*--------------------------------------------------------------
*
* ClearOptionTree --
*
* This function is called to erase everything in a hierarchical option
* database.
*
* Results:
* None.
*
* Side effects:
* All the options associated with arrayPtr are deleted, along with all
* option subtrees. The space pointed to by arrayPtr is freed.
*
*--------------------------------------------------------------
*/
static void
ClearOptionTree(
ElArray *arrayPtr) /* Array of options; delete everything
* referred to recursively by this. */
{
register Element *elPtr;
int count;
for (count = arrayPtr->numUsed, elPtr = arrayPtr->els; count > 0;
count--, elPtr++) {
if (elPtr->flags & NODE) {
ClearOptionTree(elPtr->child.arrayPtr);
}
}
ckfree(arrayPtr);
}
/*
*--------------------------------------------------------------
*
* GetDefaultOptions --
*
* This function is invoked to load the default set of options for a
* window.
*
* Results:
* None.
*
* Side effects:
* Options are added to those for winPtr's main window. If there exists a
* RESOURCE_MANAGER proprety for winPtr's display, that is used.
* Otherwise, the .Xdefaults file in the user's home directory is used.
*
*--------------------------------------------------------------
*/
static int
GetDefaultOptions(
Tcl_Interp *interp, /* Interpreter to use for error reporting. */
TkWindow *winPtr) /* Fetch option defaults for main window
* associated with this. */
{
char *regProp, **regPropPtr = ®Prop;
int result, actualFormat;
unsigned long numItems, bytesAfter;
Atom actualType;
/*
* Try the RESOURCE_MANAGER property on the root window first.
*/
regProp = NULL;
result = XGetWindowProperty(winPtr->display,
RootWindow(winPtr->display, 0), XA_RESOURCE_MANAGER, 0, 100000,
False, XA_STRING, &actualType, &actualFormat, &numItems,
&bytesAfter, (unsigned char **) regPropPtr);
if ((result == Success) && (actualType == XA_STRING)
&& (actualFormat == 8)) {
result = AddFromString(interp, (Tk_Window) winPtr, regProp,
TK_USER_DEFAULT_PRIO);
XFree(regProp);
return result;
}
/*
* No luck there. Try a .Xdefaults file in the user's home directory.
*/
if (regProp != NULL) {
XFree(regProp);
}
result = ReadOptionFile(interp, (Tk_Window) winPtr, "~/.Xdefaults",
TK_USER_DEFAULT_PRIO);
return result;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/