]> www.wagner.pp.ru Git - oss/ck.git/blob - ckOption.c
Ck console graphics toolkit
[oss/ck.git] / ckOption.c
1 /* 
2  * ckOption.c --
3  *
4  *      This module contains procedures to manage the option
5  *      database, which allows various strings to be associated
6  *      with windows either by name or by class or both.
7  *
8  * Copyright (c) 1990-1994 The Regents of the University of California.
9  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
10  * Copyright (c) 1995 Christian Werner
11  *
12  * See the file "license.terms" for information on usage and redistribution
13  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  */
15
16 #include "ckPort.h"
17 #include "ck.h"
18
19 /*
20  * The option database is stored as one tree for each main window.
21  * Each name or class field in an option is associated with a node or
22  * leaf of the tree.  For example, the options "x.y.z" and "x.y*a"
23  * each correspond to three nodes in the tree;  they share the nodes
24  * "x" and "x.y", but have different leaf nodes.  One of the following
25  * structures exists for each node or leaf in the option tree.  It is
26  * actually stored as part of the parent node, and describes a particular
27  * child of the parent.
28  */
29
30 typedef struct Element {
31     Ck_Uid nameUid;                     /* Name or class from one element of
32                                          * an option spec. */
33     union {
34         struct ElArray *arrayPtr;       /* If this is an intermediate node,
35                                          * a pointer to a structure describing
36                                          * the remaining elements of all
37                                          * options whose prefixes are the
38                                          * same up through this element. */
39         Ck_Uid valueUid;                /* For leaf nodes, this is the string
40                                          * value of the option. */
41     } child;
42     int priority;                       /* Used to select among matching
43                                          * options.  Includes both the
44                                          * priority level and a serial #.
45                                          * Greater value means higher
46                                          * priority.  Irrelevant except in
47                                          * leaf nodes. */
48     int flags;                          /* OR-ed combination of bits.  See
49                                          * below for values. */
50 } Element;
51
52 /*
53  * Flags in Element structures:
54  *
55  * CLASS -              Non-zero means this element refers to a class,
56  *                      Zero means this element refers to a name.
57  * NODE -               Zero means this is a leaf element (the child
58  *                      field is a value, not a pointer to another node).
59  *                      One means this is a node element.
60  * WILDCARD -           Non-zero means this there was a star in the
61  *                      original specification just before this element.
62  *                      Zero means there was a dot.
63  */
64
65 #define TYPE_MASK               0x7
66
67 #define CLASS                   0x1
68 #define NODE                    0x2
69 #define WILDCARD                0x4
70
71 #define EXACT_LEAF_NAME         0x0
72 #define EXACT_LEAF_CLASS        0x1
73 #define EXACT_NODE_NAME         0x2
74 #define EXACT_NODE_CLASS        0x3
75 #define WILDCARD_LEAF_NAME      0x4
76 #define WILDCARD_LEAF_CLASS     0x5
77 #define WILDCARD_NODE_NAME      0x6
78 #define WILDCARD_NODE_CLASS     0x7
79
80 /*
81  * The following structure is used to manage a dynamic array of
82  * Elements.  These structures are used for two purposes:  to store
83  * the contents of a node in the option tree, and for the option
84  * stacks described below.
85  */
86
87 typedef struct ElArray {
88     int arraySize;              /* Number of elements actually
89                                  * allocated in the "els" array. */
90     int numUsed;                /* Number of elements currently in
91                                  * use out of els. */
92     Element *nextToUse;         /* Pointer to &els[numUsed]. */
93     Element els[1];             /* Array of structures describing
94                                  * children of this node.  The
95                                  * array will actually contain enough
96                                  * elements for all of the children
97                                  * (and even a few extras, perhaps).
98                                  * This must be the last field in
99                                  * the structure. */
100 } ElArray;
101
102 #define EL_ARRAY_SIZE(numEls) ((unsigned) (sizeof(ElArray) \
103         + ((numEls)-1)*sizeof(Element)))
104 #define INITIAL_SIZE 5
105
106 /*
107  * In addition to the option tree, which is a relatively static structure,
108  * there are eight additional structures called "stacks", which are used
109  * to speed up queries into the option database.  The stack structures
110  * are designed for the situation where an individual widget makes repeated
111  * requests for its particular options.  The requests differ only in
112  * their last name/class, so during the first request we extract all
113  * the options pertaining to the particular widget and save them in a
114  * stack-like cache;  subsequent requests for the same widget can search
115  * the cache relatively quickly.  In fact, the cache is a hierarchical
116  * one, storing a list of relevant options for this widget and all of
117  * its ancestors up to the application root;  hence the name "stack".
118  *
119  * Each of the eight stacks consists of an array of Elements, ordered in
120  * terms of levels in the window hierarchy.  All the elements relevant
121  * for the top-level widget appear first in the array, followed by all
122  * those from the next-level widget on the path to the current widget,
123  * etc. down to those for the current widget.
124  *
125  * Cached information is divided into eight stacks according to the
126  * CLASS, NODE, and WILDCARD flags.  Leaf and non-leaf information is
127  * kept separate to speed up individual probes (non-leaf information is
128  * only relevant when building the stacks, but isn't relevant when
129  * making probes;  similarly, only non-leaf information is relevant
130  * when the stacks are being extended to the next widget down in the
131  * widget hierarchy).  Wildcard elements are handled separately from
132  * "exact" elements because once they appear at a particular level in
133  * the stack they remain active for all deeper levels;  exact elements
134  * are only relevant at a particular level.  For example, when searching
135  * for options relevant in a particular window, the entire wildcard
136  * stacks get checked, but only the portions of the exact stacks that
137  * pertain to the window's parent.  Lastly, name and class stacks are
138  * kept separate because different search keys are used when searching
139  * them;  keeping them separate speeds up the searches.
140  */
141
142 #define NUM_STACKS 8
143 static ElArray *stacks[NUM_STACKS];
144 static CkWindow *cachedWindow = NULL;   /* Lowest-level window currently
145                                          * loaded in stacks at present. 
146                                          * NULL means stacks have never
147                                          * been used, or have been
148                                          * invalidated because of a change
149                                          * to the database. */
150
151 /*
152  * One of the following structures is used to keep track of each
153  * level in the stacks.
154  */
155
156 typedef struct StackLevel {
157     CkWindow *winPtr;           /* Window corresponding to this stack
158                                  * level. */
159     int bases[NUM_STACKS];      /* For each stack, index of first
160                                  * element on stack corresponding to
161                                  * this level (used to restore "numUsed"
162                                  * fields when popping out of a level. */
163 } StackLevel;
164
165 /*
166  * Information about all of the stack levels that are currently
167  * active.  This array grows dynamically to become as large as needed.
168  */
169
170 static StackLevel *levels = NULL;
171                                 /* Array describing current stack. */
172 static int numLevels = 0;       /* Total space allocated. */
173 static int curLevel = -1;       /* Highest level currently in use.  Note:
174                                  * curLevel is never 0!  (I don't remember
175                                  * why anymore...) */
176
177 /*
178  * The variable below is a serial number for all options entered into
179  * the database so far.  It increments on each addition to the option
180  * database.  It is used in computing option priorities, so that the
181  * most recent entry wins when choosing between options at the same
182  * priority level.
183  */
184
185 static int serial = 0;
186
187 /*
188  * Special "no match" Element to use as default for searches.
189  */
190
191 static Element defaultMatch;
192
193 /*
194  * Forward declarations for procedures defined in this file:
195  */
196
197 static int              AddFromString _ANSI_ARGS_((Tcl_Interp *interp,
198                             CkWindow *winPtr, char *string, int priority));
199 static void             ClearOptionTree _ANSI_ARGS_((ElArray *arrayPtr));
200 static ElArray *        ExtendArray _ANSI_ARGS_((ElArray *arrayPtr,
201                             Element *elPtr));
202 static void             ExtendStacks _ANSI_ARGS_((ElArray *arrayPtr,
203                             int leaf));
204 static ElArray *        NewArray _ANSI_ARGS_((int numEls));     
205 static void             OptionInit _ANSI_ARGS_((CkMainInfo *mainPtr));
206 static int              ParsePriority _ANSI_ARGS_((Tcl_Interp *interp,
207                             char *string));
208 static int              ReadOptionFile _ANSI_ARGS_((Tcl_Interp *interp,
209                             CkWindow *winPtr, char *fileName, int priority));
210 static void             SetupStacks _ANSI_ARGS_((CkWindow *winPtr, int leaf));
211 \f
212 /*
213  *--------------------------------------------------------------
214  *
215  * Ck_AddOption --
216  *
217  *      Add a new option to the option database.
218  *
219  * Results:
220  *      None.
221  *
222  * Side effects:
223  *      Information is added to the option database.
224  *
225  *--------------------------------------------------------------
226  */
227
228 void
229 Ck_AddOption(winPtr, name, value, priority)
230     CkWindow *winPtr;           /* Window pointer; option will be associated
231                                  * with main window for this window. */
232     char *name;                 /* Multi-element name of option. */
233     char *value;                /* String value for option. */
234     int priority;               /* Overall priority level to use for
235                                  * this option, such as CK_USER_DEFAULT_PRIO
236                                  * or CK_INTERACTIVE_PRIO.  Must be between
237                                  * 0 and CK_MAX_PRIO. */
238 {
239     register ElArray **arrayPtrPtr;
240     register Element *elPtr;
241     Element newEl;
242     register char *p;
243     char *field;
244     int count, firstField, length;
245 #define TMP_SIZE 100
246     char tmp[TMP_SIZE+1];
247
248     winPtr = winPtr->mainPtr->winPtr;
249
250     if (winPtr->mainPtr->optionRootPtr == NULL) {
251         OptionInit(winPtr->mainPtr);
252     }
253     cachedWindow = NULL;        /* Invalidate the cache. */
254
255     /*
256      * Compute the priority for the new element, including both the
257      * overall level and the serial number (to disambiguate with the
258      * level).
259      */
260
261     if (priority < 0) {
262         priority = 0;
263     } else if (priority > CK_MAX_PRIO) {
264         priority = CK_MAX_PRIO;
265     }
266     newEl.priority = (priority << 24) + serial;
267     serial++;
268
269     /*
270      * Parse the option one field at a time.
271      */
272
273     arrayPtrPtr = &(winPtr->mainPtr->optionRootPtr);
274     p = name;
275     for (firstField = 1; ; firstField = 0) {
276
277         /*
278          * Scan the next field from the name and convert it to a Tk_Uid.
279          * Must copy the field before calling Tk_Uid, so that a terminating
280          * NULL may be added without modifying the source string.
281          */
282
283         if (*p == '*') {
284             newEl.flags = WILDCARD;
285             p++;
286         } else {
287             newEl.flags = 0;
288         }
289         field = p;
290         while ((*p != 0) && (*p != '.') && (*p != '*')) {
291             p++;
292         }
293         length = p - field;
294         if (length > TMP_SIZE) {
295             length = TMP_SIZE;
296         }
297         strncpy(tmp, field, (size_t) length);
298         tmp[length] = 0;
299         newEl.nameUid = Ck_GetUid(tmp);
300         if (isupper((unsigned char) *field)) {
301             newEl.flags |= CLASS;
302         }
303
304         if (*p != 0) {
305
306             /*
307              * New element will be a node.  If this option can't possibly
308              * apply to this main window, then just skip it.  Otherwise,
309              * add it to the parent, if it isn't already there, and descend
310              * into it.
311              */
312
313             newEl.flags |= NODE;
314             if (firstField && !(newEl.flags & WILDCARD)
315                     && (newEl.nameUid != winPtr->nameUid)
316                     && (newEl.nameUid != winPtr->classUid)) {
317                 return;
318             }
319             for (elPtr = (*arrayPtrPtr)->els, count = (*arrayPtrPtr)->numUsed;
320                     ; elPtr++, count--) {
321                 if (count == 0) {
322                     newEl.child.arrayPtr = NewArray(5);
323                     *arrayPtrPtr = ExtendArray(*arrayPtrPtr, &newEl);
324                     arrayPtrPtr = &((*arrayPtrPtr)->nextToUse[-1].child.arrayPtr);
325                     break;
326                 }
327                 if ((elPtr->nameUid == newEl.nameUid)
328                         && (elPtr->flags == newEl.flags)) {
329                     arrayPtrPtr = &(elPtr->child.arrayPtr);
330                     break;
331                 }
332             }
333             if (*p == '.') {
334                 p++;
335             }
336         } else {
337
338             /*
339              * New element is a leaf.  Add it to the parent, if it isn't
340              * already there.  If it exists already, keep whichever value
341              * has highest priority.
342              */
343
344             newEl.child.valueUid = Ck_GetUid(value);
345             for (elPtr = (*arrayPtrPtr)->els, count = (*arrayPtrPtr)->numUsed;
346                     ; elPtr++, count--) {
347                 if (count == 0) {
348                     *arrayPtrPtr = ExtendArray(*arrayPtrPtr, &newEl);
349                     return;
350                 }
351                 if ((elPtr->nameUid == newEl.nameUid)
352                         && (elPtr->flags == newEl.flags)) {
353                     if (elPtr->priority < newEl.priority) {
354                         elPtr->priority = newEl.priority;
355                         elPtr->child.valueUid = newEl.child.valueUid;
356                     }
357                     return;
358                 }
359             }
360         }
361     }
362 }
363 \f
364 /*
365  *--------------------------------------------------------------
366  *
367  * Ck_GetOption --
368  *
369  *      Retrieve an option from the option database.
370  *
371  * Results:
372  *      The return value is the value specified in the option
373  *      database for the given name and class on the given
374  *      window.  If there is nothing specified in the database
375  *      for that option, then NULL is returned.
376  *
377  * Side effects:
378  *      The internal caches used to speed up option mapping
379  *      may be modified, if this tkwin is different from the
380  *      last tkwin used for option retrieval.
381  *
382  *--------------------------------------------------------------
383  */
384
385 Ck_Uid
386 Ck_GetOption(winPtr, name, className)
387     CkWindow *winPtr;           /* Pointer to window that option is
388                                  * associated with. */
389     char *name;                 /* Name of option. */
390     char *className;            /* Class of option.  NULL means there
391                                  * is no class for this option:  just
392                                  * check for name. */
393 {
394     Ck_Uid nameId, classId;
395     register Element *elPtr, *bestPtr;
396     register int count;
397
398     /*
399      * Note:  no need to call OptionInit here:  it will be done by
400      * the SetupStacks call below (squeeze out those nanoseconds).
401      */
402
403     if (winPtr != cachedWindow) {
404         SetupStacks(winPtr, 1);
405     }
406
407     nameId = Ck_GetUid(name);
408     bestPtr = &defaultMatch;
409     for (elPtr = stacks[EXACT_LEAF_NAME]->els,
410             count = stacks[EXACT_LEAF_NAME]->numUsed; count > 0;
411             elPtr++, count--) {
412         if ((elPtr->nameUid == nameId)
413                 && (elPtr->priority > bestPtr->priority)) {
414             bestPtr = elPtr;
415         }
416     }
417     for (elPtr = stacks[WILDCARD_LEAF_NAME]->els,
418             count = stacks[WILDCARD_LEAF_NAME]->numUsed; count > 0;
419             elPtr++, count--) {
420         if ((elPtr->nameUid == nameId)
421                 && (elPtr->priority > bestPtr->priority)) {
422             bestPtr = elPtr;
423         }
424     }
425     if (className != NULL) {
426         classId = Ck_GetUid(className);
427         for (elPtr = stacks[EXACT_LEAF_CLASS]->els,
428                 count = stacks[EXACT_LEAF_CLASS]->numUsed; count > 0;
429                 elPtr++, count--) {
430             if ((elPtr->nameUid == classId)
431                     && (elPtr->priority > bestPtr->priority)) {
432                 bestPtr = elPtr;
433             }
434         }
435         for (elPtr = stacks[WILDCARD_LEAF_CLASS]->els,
436                 count = stacks[WILDCARD_LEAF_CLASS]->numUsed; count > 0;
437                 elPtr++, count--) {
438             if ((elPtr->nameUid == classId)
439                     && (elPtr->priority > bestPtr->priority)) {
440                 bestPtr = elPtr;
441             }
442         }
443     }
444     return bestPtr->child.valueUid;
445 }
446 \f
447 /*
448  *--------------------------------------------------------------
449  *
450  * Ck_OptionCmd --
451  *
452  *      This procedure is invoked to process the "option" Tcl command.
453  *      See the user documentation for details on what it does.
454  *
455  * Results:
456  *      A standard Tcl result.
457  *
458  * Side effects:
459  *      See the user documentation.
460  *
461  *--------------------------------------------------------------
462  */
463
464 int
465 Ck_OptionCmd(clientData, interp, argc, argv)
466     ClientData clientData;      /* Main window associated with
467                                  * interpreter. */
468     Tcl_Interp *interp;         /* Current interpreter. */
469     int argc;                   /* Number of arguments. */
470     char **argv;                /* Argument strings. */
471 {
472     CkWindow *winPtr = (CkWindow *) clientData;
473     size_t length;
474     char c;
475
476     if (argc < 2) {
477         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
478                 " cmd arg ?arg ...?\"", (char *) NULL);
479         return TCL_ERROR;
480     }
481     c = argv[1][0];
482     length = strlen(argv[1]);
483     if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)) {
484         int priority;
485
486         if ((argc != 4) && (argc != 5)) {
487             Tcl_AppendResult(interp, "wrong # args: should be \"",
488                     argv[0], " add pattern value ?priority?\"", (char *) NULL);
489             return TCL_ERROR;
490         }
491         if (argc == 4) {
492             priority = CK_INTERACTIVE_PRIO;
493         } else {
494             priority = ParsePriority(interp, argv[4]);
495             if (priority < 0) {
496                 return TCL_ERROR;
497             }
498         }
499         Ck_AddOption(winPtr, argv[2], argv[3], priority);
500         return TCL_OK;
501     } else if ((c == 'c') && (strncmp(argv[1], "clear", length) == 0)) {
502         CkMainInfo *mainPtr;
503
504         if (argc != 2) {
505             Tcl_AppendResult(interp, "wrong # args: should be \"",
506                     argv[0], " clear\"", (char *) NULL);
507             return TCL_ERROR;
508         }
509         mainPtr = winPtr->mainPtr;
510         if (mainPtr->optionRootPtr != NULL) {
511             ClearOptionTree(mainPtr->optionRootPtr);
512             mainPtr->optionRootPtr = NULL;
513         }
514         cachedWindow = NULL;
515         return TCL_OK;
516     } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
517         CkWindow *winPtr2;
518         Ck_Uid value;
519
520         if (argc != 5) {
521             Tcl_AppendResult(interp, "wrong # args: should be \"",
522                     argv[0], " get window name class\"", (char *) NULL);
523             return TCL_ERROR;
524         }
525         winPtr2 = Ck_NameToWindow(interp, argv[2], winPtr);
526         if (winPtr2 == NULL) {
527             return TCL_ERROR;
528         }
529         value = Ck_GetOption(winPtr2, argv[3], argv[4]);
530         if (value != NULL) {
531             interp->result = value;
532         }
533         return TCL_OK;
534     } else if ((c == 'r') && (strncmp(argv[1], "readfile", length) == 0)) {
535         int priority;
536
537         if ((argc != 3) && (argc != 4)) {
538             Tcl_AppendResult(interp, "wrong # args:  should be \"",
539                     argv[0], " readfile fileName ?priority?\"",
540                     (char *) NULL);
541             return TCL_ERROR;
542         }
543         if (argc == 4) {
544             priority = ParsePriority(interp, argv[3]);
545             if (priority < 0) {
546                 return TCL_ERROR;
547             }
548         } else {
549             priority = CK_INTERACTIVE_PRIO;
550         }
551         return ReadOptionFile(interp, winPtr, argv[2], priority);
552     } else {
553         Tcl_AppendResult(interp, "bad option \"", argv[1],
554                 "\": must be add, clear, get, or readfile", (char *) NULL);
555         return TCL_ERROR;
556     }
557 }
558 \f
559 /*
560  *--------------------------------------------------------------
561  *
562  * CkOptionDeadWindow --
563  *
564  *      This procedure is called whenever a window is deleted.
565  *      It cleans up any option-related stuff associated with
566  *      the window.
567  *
568  * Results:
569  *      None.
570  *
571  * Side effects:
572  *      Option-related resources are freed.  See code below
573  *      for details.
574  *
575  *--------------------------------------------------------------
576  */
577
578 void
579 CkOptionDeadWindow(winPtr)
580     register CkWindow *winPtr;          /* Window to be cleaned up. */
581 {
582     /*
583      * If this window is in the option stacks, then clear the stacks.
584      */
585
586     if (winPtr->optionLevel != -1) {
587         int i;
588
589         for (i = 1; i <= curLevel; i++) {
590             levels[i].winPtr->optionLevel = -1;
591         }
592         curLevel = -1;
593         cachedWindow = NULL;
594     }
595
596     /*
597      * If this window was a main window, then delete its option
598      * database.
599      */
600
601     if ((winPtr->mainPtr->winPtr == winPtr)
602             && (winPtr->mainPtr->optionRootPtr != NULL)) {
603         ClearOptionTree(winPtr->mainPtr->optionRootPtr);
604         winPtr->mainPtr->optionRootPtr = NULL;
605     }
606 }
607 \f
608 /*
609  *----------------------------------------------------------------------
610  *
611  * CkOptionClassChanged --
612  *
613  *      This procedure is invoked when a window's class changes.  If
614  *      the window is on the option cache, this procedure flushes
615  *      any information for the window, since the new class could change
616  *      what is relevant.
617  *
618  * Results:
619  *      None.
620  *
621  * Side effects:
622  *      The option cache may be flushed in part or in whole.
623  *
624  *----------------------------------------------------------------------
625  */
626
627 void
628 CkOptionClassChanged(winPtr)
629     CkWindow *winPtr;                   /* Window whose class changed. */
630 {
631     int i, j, *basePtr;
632     ElArray *arrayPtr;
633
634     if (winPtr->optionLevel == -1) {
635         return;
636     }
637
638     /*
639      * Find the lowest stack level that refers to this window, then
640      * flush all of the levels above the matching one.
641      */
642
643     for (i = 1; i <= curLevel; i++) {
644         if (levels[i].winPtr == winPtr) {
645             for (j = i; j <= curLevel; j++) {
646                 levels[j].winPtr->optionLevel = -1;
647             }
648             curLevel = i-1;
649             basePtr = levels[i].bases;
650             for (j = 0; j < NUM_STACKS; j++) {
651                 arrayPtr = stacks[j];
652                 arrayPtr->numUsed = basePtr[j];
653                 arrayPtr->nextToUse = &arrayPtr->els[arrayPtr->numUsed];
654             }
655             if (curLevel <= 0) {
656                 cachedWindow = NULL;
657             } else {
658                 cachedWindow = levels[curLevel].winPtr;
659             }
660             break;
661         }
662     }
663 }
664 \f
665 /*
666  *----------------------------------------------------------------------
667  *
668  * ParsePriority --
669  *
670  *      Parse a string priority value.
671  *
672  * Results:
673  *      The return value is the integer priority level corresponding
674  *      to string, or -1 if string doesn't point to a valid priority level.
675  *      In this case, an error message is left in interp->result.
676  *
677  * Side effects:
678  *      None.
679  *
680  *----------------------------------------------------------------------
681  */
682
683 static int
684 ParsePriority(interp, string)
685     Tcl_Interp *interp;         /* Interpreter to use for error reporting. */
686     char *string;               /* Describes a priority level, either
687                                  * symbolically or numerically. */
688 {
689     int priority, c;
690     size_t length;
691
692     c = string[0];
693     length = strlen(string);
694     if ((c == 'w')
695             && (strncmp(string, "widgetDefault", length) == 0)) {
696         return CK_WIDGET_DEFAULT_PRIO;
697     } else if ((c == 's')
698             && (strncmp(string, "startupFile", length) == 0)) {
699         return CK_STARTUP_FILE_PRIO;
700     } else if ((c == 'u')
701             && (strncmp(string, "userDefault", length) == 0)) {
702         return CK_USER_DEFAULT_PRIO;
703     } else if ((c == 'i')
704             && (strncmp(string, "interactive", length) == 0)) {
705         return CK_INTERACTIVE_PRIO;
706     } else {
707         char *end;
708
709         priority = strtoul(string, &end, 0);
710         if ((end == string) || (*end != 0) || (priority < 0)
711                 || (priority > 100)) {
712             Tcl_AppendResult(interp,  "bad priority level \"", string,
713                     "\": must be widgetDefault, startupFile, userDefault, ",
714                     "interactive, or a number between 0 and 100",
715                     (char *) NULL);
716             return -1;
717         }
718     }
719     return priority;
720 }
721 \f
722 /*
723  *----------------------------------------------------------------------
724  *
725  * AddFromString --
726  *
727  *      Given a string containing lines in the standard format for
728  *      X resources (see other documentation for details on what this
729  *      is), parse the resource specifications and enter them as options
730  *      for tkwin's main window.
731  *
732  * Results:
733  *      The return value is a standard Tcl return code.  In the case of
734  *      an error in parsing string, TCL_ERROR will be returned and an
735  *      error message will be left in interp->result.  The memory at
736  *      string is totally trashed by this procedure.  If you care about
737  *      its contents, make a copy before calling here.
738  *
739  * Side effects:
740  *      None.
741  *
742  *----------------------------------------------------------------------
743  */
744
745 static int
746 AddFromString(interp, winPtr, string, priority)
747     Tcl_Interp *interp;         /* Interpreter to use for reporting results. */
748     CkWindow *winPtr;           /* Pointer to window:  options are entered
749                                  * for this window's main window. */
750     char *string;               /* String containing option specifiers. */
751     int priority;               /* Priority level to use for options in
752                                  * this string, such as TK_USER_DEFAULT_PRIO
753                                  * or TK_INTERACTIVE_PRIO.  Must be between
754                                  * 0 and TK_MAX_PRIO. */
755 {
756     register char *src, *dst;
757     char *name, *value;
758     int lineNum;
759
760     src = string;
761     lineNum = 1;
762     while (1) {
763
764         /*
765          * Skip leading white space and empty lines and comment lines, and
766          * check for the end of the spec.
767          */
768
769         while ((*src == ' ') || (*src == '\t')) {
770             src++;
771         }
772         if ((*src == '#') || (*src == '!')) {
773             do {
774                 src++;
775                 if ((src[0] == '\\') && (src[1] == '\n')) {
776                     src += 2;
777                     lineNum++;
778                 }
779             } while ((*src != '\n') && (*src != 0));
780         }
781         if (*src == '\n') {
782             src++;
783             lineNum++;
784             continue;
785         } 
786         if (*src == '\0') {
787             break;
788         }
789
790         /*
791          * Parse off the option name, collapsing out backslash-newline
792          * sequences of course.
793          */
794
795         dst = name = src;
796         while (*src != ':') {
797             if ((*src == '\0') || (*src == '\n')) {
798                 sprintf(interp->result, "missing colon on line %d",
799                         lineNum);
800                 return TCL_ERROR;
801             }
802             if ((src[0] == '\\') && (src[1] == '\n')) {
803                 src += 2;
804                 lineNum++;
805             } else {
806                 *dst = *src;
807                 dst++;
808                 src++;
809             }
810         }
811
812         /*
813          * Eliminate trailing white space on the name, and null-terminate
814          * it.
815          */
816
817         while ((dst != name) && ((dst[-1] == ' ') || (dst[-1] == '\t'))) {
818             dst--;
819         }
820         *dst = '\0';
821
822         /*
823          * Skip white space between the name and the value.
824          */
825
826         src++;
827         while ((*src == ' ') || (*src == '\t')) {
828             src++;
829         }
830         if (*src == '\0') {
831             sprintf(interp->result, "missing value on line %d", lineNum);
832             return TCL_ERROR;
833         }
834
835         /*
836          * Parse off the value, squeezing out backslash-newline sequences
837          * along the way.
838          */
839
840         dst = value = src;
841         while (*src != '\n') {
842             if (*src == '\0') {
843                 sprintf(interp->result, "missing newline on line %d",
844                         lineNum);
845                 return TCL_ERROR;
846             }
847             if ((src[0] == '\\') && (src[1] == '\n')) {
848                 src += 2;
849                 lineNum++;
850             } else {
851                 *dst = *src;
852                 dst++;
853                 src++;
854             }
855         }
856         *dst = 0;
857
858         /*
859          * Enter the option into the database.
860          */
861
862         Ck_AddOption(winPtr, name, value, priority);
863         src++;
864         lineNum++;
865     }
866     return TCL_OK;
867 }
868 \f
869 /*
870  *----------------------------------------------------------------------
871  *
872  * ReadOptionFile --
873  *
874  *      Read a file of options ("resources" in the old X terminology)
875  *      and load them into the option database.
876  *
877  * Results:
878  *      The return value is a standard Tcl return code.  In the case of
879  *      an error in parsing string, TCL_ERROR will be returned and an
880  *      error message will be left in interp->result.
881  *
882  * Side effects:
883  *      None.
884  *
885  *----------------------------------------------------------------------
886  */
887
888 static int
889 ReadOptionFile(interp, winPtr, fileName, priority)
890     Tcl_Interp *interp;         /* Interpreter to use for reporting results. */
891     CkWindow *winPtr;           /* Pointer to window:  options are entered
892                                  * for this window's main window. */
893     char *fileName;             /* Name of file containing options. */
894     int priority;               /* Priority level to use for options in
895                                  * this file, such as TK_USER_DEFAULT_PRIO
896                                  * or TK_INTERACTIVE_PRIO.  Must be between
897                                  * 0 and TK_MAX_PRIO. */
898 {
899 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
900     char *realName, *buffer;
901     int fileId, result;
902     struct stat statBuf;
903     Tcl_DString newName;
904
905     realName = Tcl_TildeSubst(interp, fileName, &newName);
906     if (realName == NULL) {
907         return TCL_ERROR;
908     }
909     fileId = open(realName, O_RDONLY, 0);
910     Tcl_DStringFree(&newName);
911     if (fileId < 0) {
912         Tcl_AppendResult(interp, "couldn't read file \"", fileName, "\"",
913                 (char *) NULL);
914         return TCL_ERROR;
915     }
916     if (fstat(fileId, &statBuf) == -1) {
917         Tcl_AppendResult(interp, "couldn't stat file \"", fileName, "\"",
918                 (char *) NULL);
919         close(fileId);
920         return TCL_ERROR;
921     }
922     buffer = (char *) ckalloc((unsigned) statBuf.st_size+1);
923     if (read(fileId, buffer, (unsigned) statBuf.st_size) != statBuf.st_size) {
924         Tcl_AppendResult(interp, "error reading file \"", fileName, "\"",
925                 (char *) NULL);
926         close(fileId);
927         return TCL_ERROR;
928     }
929     close(fileId);
930     buffer[statBuf.st_size] = 0;
931     result = AddFromString(interp, winPtr, buffer, priority);
932     ckfree(buffer);
933     return result;
934 #else
935     char *realName, *buffer;
936     int result, bufferSize;
937     Tcl_Channel chan;
938     Tcl_DString newName;
939
940     realName = Tcl_TranslateFileName(interp, fileName, &newName);
941     if (realName == NULL) {
942         return TCL_ERROR;
943     }
944     chan = Tcl_OpenFileChannel(interp, realName, "r", 0);
945     Tcl_DStringFree(&newName);
946     if (chan == NULL) {
947         return TCL_ERROR;
948     }
949
950     /*
951      * Compute size of file by seeking to the end of the file.
952      */
953
954     bufferSize = Tcl_Seek(chan, 0L, SEEK_END);
955     if (bufferSize < 0) {
956         Tcl_AppendResult(interp, "error getting file size of \"",
957                 fileName, "\"", (char *) NULL);
958         Tcl_Close(NULL, chan);
959         return TCL_ERROR;
960     }
961     Tcl_Seek(chan, 0L, SEEK_SET);
962     buffer = (char *) ckalloc((unsigned) bufferSize + 1);
963     if (Tcl_Read(chan, buffer, bufferSize) != bufferSize) {
964         ckfree(buffer);
965         Tcl_AppendResult(interp, "error reading file \"", fileName, "\"",
966                 (char *) NULL);
967         Tcl_Close(NULL, chan);
968         return TCL_ERROR;
969     }
970     Tcl_Close(NULL, chan);
971     buffer[bufferSize] = 0;
972     result = AddFromString(interp, winPtr, buffer, priority);
973     ckfree(buffer);
974     return result;
975 #endif
976 }
977 \f
978 /*
979  *--------------------------------------------------------------
980  *
981  * NewArray --
982  *
983  *      Create a new ElArray structure of a given size.
984  *
985  * Results:
986  *      The return value is a pointer to a properly initialized
987  *      element array with "numEls" space.  The array is marked
988  *      as having no active elements.
989  *
990  * Side effects:
991  *      Memory is allocated.
992  *
993  *--------------------------------------------------------------
994  */
995
996 static ElArray *
997 NewArray(numEls)
998     int numEls;                 /* How many elements of space to allocate. */
999 {
1000     register ElArray *arrayPtr;
1001
1002     arrayPtr = (ElArray *) ckalloc(EL_ARRAY_SIZE(numEls));
1003     arrayPtr->arraySize = numEls;
1004     arrayPtr->numUsed = 0;
1005     arrayPtr->nextToUse = arrayPtr->els;
1006     return arrayPtr;
1007 }
1008 \f
1009 /*
1010  *--------------------------------------------------------------
1011  *
1012  * ExtendArray --
1013  *
1014  *      Add a new element to an array, extending the array if
1015  *      necessary.
1016  *
1017  * Results:
1018  *      The return value is a pointer to the new array, which
1019  *      will be different from arrayPtr if the array got expanded.
1020  *
1021  * Side effects:
1022  *      Memory may be allocated or freed.
1023  *
1024  *--------------------------------------------------------------
1025  */
1026
1027 static ElArray *
1028 ExtendArray(arrayPtr, elPtr)
1029     register ElArray *arrayPtr;         /* Array to be extended. */
1030     register Element *elPtr;            /* Element to be copied into array. */
1031 {
1032     /*
1033      * If the current array has filled up, make it bigger.
1034      */
1035
1036     if (arrayPtr->numUsed >= arrayPtr->arraySize) {
1037         register ElArray *newPtr;
1038
1039         newPtr = (ElArray *) ckalloc(EL_ARRAY_SIZE(2*arrayPtr->arraySize));
1040         newPtr->arraySize = 2*arrayPtr->arraySize;
1041         newPtr->numUsed = arrayPtr->numUsed;
1042         newPtr->nextToUse = &newPtr->els[newPtr->numUsed];
1043         memcpy((VOID *) newPtr->els, (VOID *) arrayPtr->els,
1044                 (arrayPtr->arraySize*sizeof(Element)));
1045         ckfree((char *) arrayPtr);
1046         arrayPtr = newPtr;
1047     }
1048
1049     *arrayPtr->nextToUse = *elPtr;
1050     arrayPtr->nextToUse++;
1051     arrayPtr->numUsed++;
1052     return arrayPtr;
1053 }
1054 \f
1055 /*
1056  *--------------------------------------------------------------
1057  *
1058  * SetupStacks --
1059  *
1060  *      Arrange the stacks so that they cache all the option
1061  *      information for a particular window.
1062  *
1063  * Results:
1064  *      None.
1065  *
1066  * Side effects:
1067  *      The stacks are modified to hold information for tkwin
1068  *      and all its ancestors in the window hierarchy.
1069  *
1070  *--------------------------------------------------------------
1071  */
1072
1073 static void
1074 SetupStacks(winPtr, leaf)
1075     CkWindow *winPtr;           /* Window for which information is to
1076                                  * be cached. */
1077     int leaf;                   /* Non-zero means this is the leaf
1078                                  * window being probed.  Zero means this
1079                                  * is an ancestor of the desired leaf. */
1080 {
1081     int level, i, *iPtr;
1082     register StackLevel *levelPtr;
1083     register ElArray *arrayPtr;
1084
1085     /*
1086      * The following array defines the order in which the current
1087      * stacks are searched to find matching entries to add to the
1088      * stacks.  Given the current priority-based scheme, the order
1089      * below is no longer relevant;  all that matters is that an
1090      * element is on the list *somewhere*.  The ordering is a relic
1091      * of the old days when priorities were determined differently.
1092      */
1093
1094     static int searchOrder[] = {WILDCARD_NODE_CLASS, WILDCARD_NODE_NAME,
1095             EXACT_NODE_CLASS, EXACT_NODE_NAME, -1};
1096
1097     if (winPtr->mainPtr->optionRootPtr == NULL) {
1098         OptionInit(winPtr->mainPtr);
1099     }
1100
1101     /*
1102      * Step 1:  make sure that options are cached for this window's
1103      * parent.
1104      */
1105
1106     if (winPtr->parentPtr != NULL) {
1107         level = winPtr->parentPtr->optionLevel;
1108         if ((level == -1) || (cachedWindow == NULL)) {
1109             SetupStacks(winPtr->parentPtr, 0);
1110             level = winPtr->parentPtr->optionLevel;
1111         }
1112         level++;
1113     } else {
1114         level = 1;
1115     }
1116
1117     /*
1118      * Step 2:  pop extra unneeded information off the stacks and
1119      * mark those windows as no longer having cached information.
1120      */
1121
1122     if (curLevel >= level) {
1123         while (curLevel >= level) {
1124             levels[curLevel].winPtr->optionLevel = -1;
1125             curLevel--;
1126         }
1127         levelPtr = &levels[level];
1128         for (i = 0; i < NUM_STACKS; i++) {
1129             arrayPtr = stacks[i];
1130             arrayPtr->numUsed = levelPtr->bases[i];
1131             arrayPtr->nextToUse = &arrayPtr->els[arrayPtr->numUsed];
1132         }
1133     }
1134     curLevel = winPtr->optionLevel = level;
1135
1136     /*
1137      * Step 3:  if the root database information isn't loaded or
1138      * isn't valid, initialize level 0 of the stack from the
1139      * database root (this only happens if winPtr is a main window).
1140      */
1141
1142     if ((curLevel == 1)
1143             && ((cachedWindow == NULL)
1144             || (cachedWindow->mainPtr != winPtr->mainPtr))) {
1145         for (i = 0; i < NUM_STACKS; i++) {
1146             arrayPtr = stacks[i];
1147             arrayPtr->numUsed = 0;
1148             arrayPtr->nextToUse = arrayPtr->els;
1149         }
1150         ExtendStacks(winPtr->mainPtr->optionRootPtr, 0);
1151     }
1152
1153     /*
1154      * Step 4: create a new stack level;  grow the level array if
1155      * we've run out of levels.  Clear the stacks for EXACT_LEAF_NAME
1156      * and EXACT_LEAF_CLASS (anything that was there is of no use
1157      * any more).
1158      */
1159
1160     if (curLevel >= numLevels) {
1161         StackLevel *newLevels;
1162
1163         newLevels = (StackLevel *) ckalloc((unsigned)
1164                 (numLevels*2*sizeof(StackLevel)));
1165         memcpy((VOID *) newLevels, (VOID *) levels,
1166                 (numLevels*sizeof(StackLevel)));
1167         ckfree((char *) levels);
1168         numLevels *= 2;
1169         levels = newLevels;
1170     }
1171     levelPtr = &levels[curLevel];
1172     levelPtr->winPtr = winPtr;
1173     arrayPtr = stacks[EXACT_LEAF_NAME];
1174     arrayPtr->numUsed = 0;
1175     arrayPtr->nextToUse = arrayPtr->els;
1176     arrayPtr = stacks[EXACT_LEAF_CLASS];
1177     arrayPtr->numUsed = 0;
1178     arrayPtr->nextToUse = arrayPtr->els;
1179     levelPtr->bases[EXACT_LEAF_NAME] = stacks[EXACT_LEAF_NAME]->numUsed;
1180     levelPtr->bases[EXACT_LEAF_CLASS] = stacks[EXACT_LEAF_CLASS]->numUsed;
1181     levelPtr->bases[EXACT_NODE_NAME] = stacks[EXACT_NODE_NAME]->numUsed;
1182     levelPtr->bases[EXACT_NODE_CLASS] = stacks[EXACT_NODE_CLASS]->numUsed;
1183     levelPtr->bases[WILDCARD_LEAF_NAME] = stacks[WILDCARD_LEAF_NAME]->numUsed;
1184     levelPtr->bases[WILDCARD_LEAF_CLASS] = stacks[WILDCARD_LEAF_CLASS]->numUsed;
1185     levelPtr->bases[WILDCARD_NODE_NAME] = stacks[WILDCARD_NODE_NAME]->numUsed;
1186     levelPtr->bases[WILDCARD_NODE_CLASS] = stacks[WILDCARD_NODE_CLASS]->numUsed;
1187
1188
1189     /*
1190      * Step 5: scan the current stack level looking for matches to this
1191      * window's name or class;  where found, add new information to the
1192      * stacks.
1193      */
1194
1195     for (iPtr = searchOrder; *iPtr != -1; iPtr++) {
1196         register Element *elPtr;
1197         int count;
1198         Ck_Uid id;
1199
1200         i = *iPtr;
1201         if (i & CLASS) {
1202             id = winPtr->classUid;
1203         } else {
1204             id = winPtr->nameUid;
1205         }
1206         elPtr = stacks[i]->els;
1207         count = levelPtr->bases[i];
1208
1209         /*
1210          * For wildcard stacks, check all entries;  for non-wildcard
1211          * stacks, only check things that matched in the parent.
1212          */
1213
1214         if (!(i & WILDCARD)) {
1215             elPtr += levelPtr[-1].bases[i];
1216             count -= levelPtr[-1].bases[i];
1217         }
1218         for ( ; count > 0; elPtr++, count--) {
1219             if (elPtr->nameUid != id) {
1220                 continue;
1221             }
1222             ExtendStacks(elPtr->child.arrayPtr, leaf);
1223         }
1224     }
1225     cachedWindow = winPtr;
1226 }
1227 \f
1228 /*
1229  *--------------------------------------------------------------
1230  *
1231  * ExtendStacks --
1232  *
1233  *      Given an element array, copy all the elements from the
1234  *      array onto the system stacks (except for irrelevant leaf
1235  *      elements).
1236  *
1237  * Results:
1238  *      None.
1239  *
1240  * Side effects:
1241  *      The option stacks are extended.
1242  *
1243  *--------------------------------------------------------------
1244  */
1245
1246 static void
1247 ExtendStacks(arrayPtr, leaf)
1248     ElArray *arrayPtr;          /* Array of elements to copy onto stacks. */
1249     int leaf;                   /* If zero, then don't copy exact leaf
1250                                  * elements. */
1251 {
1252     register int count;
1253     register Element *elPtr;
1254
1255     for (elPtr = arrayPtr->els, count = arrayPtr->numUsed;
1256             count > 0; elPtr++, count--) {
1257         if (!(elPtr->flags & (NODE|WILDCARD)) && !leaf) {
1258             continue;
1259         }
1260         stacks[elPtr->flags] = ExtendArray(stacks[elPtr->flags], elPtr);
1261     }
1262 }
1263 \f
1264 /*
1265  *--------------------------------------------------------------
1266  *
1267  * OptionInit --
1268  *
1269  *      Initialize data structures for option handling.
1270  *
1271  * Results:
1272  *      None.
1273  *
1274  * Side effects:
1275  *      Option-related data structures get initialized.
1276  *
1277  *--------------------------------------------------------------
1278  */
1279
1280 static void
1281 OptionInit(mainPtr)
1282     register CkMainInfo *mainPtr;       /* Top-level information about
1283                                          * window that isn't initialized
1284                                          * yet. */
1285 {
1286     int i;
1287
1288     /*
1289      * First, once-only initialization.
1290      */
1291
1292     if (numLevels == 0) {
1293
1294         numLevels = 5;
1295         levels = (StackLevel *) ckalloc((unsigned) (5*sizeof(StackLevel)));
1296         for (i = 0; i < NUM_STACKS; i++) {
1297             stacks[i] = NewArray(10);
1298             levels[0].bases[i] = 0;
1299         }
1300     
1301         defaultMatch.nameUid = NULL;
1302         defaultMatch.child.valueUid = NULL;
1303         defaultMatch.priority = -1;
1304         defaultMatch.flags = 0;
1305     }
1306
1307     /*
1308      * Then, per-main-window initialization.  Create and delete dummy
1309      * interpreter for message logging.
1310      */
1311
1312     mainPtr->optionRootPtr = NewArray(20);
1313 }
1314 \f
1315 /*
1316  *--------------------------------------------------------------
1317  *
1318  * ClearOptionTree --
1319  *
1320  *      This procedure is called to erase everything in a
1321  *      hierarchical option database.
1322  *
1323  * Results:
1324  *      None.
1325  *
1326  * Side effects:
1327  *      All the options associated with arrayPtr are deleted,
1328  *      along with all option subtrees.  The space pointed to
1329  *      by arrayPtr is freed.
1330  *
1331  *--------------------------------------------------------------
1332  */
1333
1334 static void
1335 ClearOptionTree(arrayPtr)
1336     ElArray *arrayPtr;          /* Array of options;  delete everything
1337                                  * referred to recursively by this. */
1338 {
1339     register Element *elPtr;
1340     int count;
1341
1342     for (count = arrayPtr->numUsed, elPtr = arrayPtr->els;  count > 0;
1343             count--, elPtr++) {
1344         if (elPtr->flags & NODE) {
1345             ClearOptionTree(elPtr->child.arrayPtr);
1346         }
1347     }
1348     ckfree((char *) arrayPtr);
1349 }