]> www.wagner.pp.ru Git - oss/ck.git/blob - ckTree.c
Ck console graphics toolkit
[oss/ck.git] / ckTree.c
1 /* 
2  * ckTree.c --
3  *
4  *      This module implements a tree widget.
5  *
6  * Copyright (c) 1996 Christian Werner
7  *
8  * See the file "license.terms" for information on usage and redistribution
9  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10  */
11
12 #include "ckPort.h"
13 #include "ck.h"
14 #include "default.h"
15
16 /*
17  * Widget defaults:
18  */
19
20 #define DEF_TREE_ACTIVE_ATTR_COLOR "normal"
21 #define DEF_TREE_ACTIVE_ATTR_MONO  "reverse"
22 #define DEF_TREE_ACTIVE_BG_COLOR   "white"
23 #define DEF_TREE_ACTIVE_BG_MONO    "black"
24 #define DEF_TREE_ACTIVE_FG_COLOR   "black"
25 #define DEF_TREE_ACTIVE_FG_MONO    "white"
26 #define DEF_TREE_ATTR_COLOR        "normal"
27 #define DEF_TREE_ATTR_MONO         "normal"
28 #define DEF_TREE_BG_COLOR          "black"
29 #define DEF_TREE_BG_MONO           "black"
30 #define DEF_TREE_FG_COLOR          "white"
31 #define DEF_TREE_FG_MONO           "white"
32 #define DEF_TREE_HEIGHT            "10"
33 #define DEF_TREE_SELECT_ATTR_COLOR "bold"
34 #define DEF_TREE_SELECT_ATTR_MONO  "bold"
35 #define DEF_TREE_SELECT_BG_COLOR   "black"
36 #define DEF_TREE_SELECT_BG_MONO    "black"
37 #define DEF_TREE_SELECT_FG_COLOR   "white"
38 #define DEF_TREE_SELECT_FG_MONO    "white"
39 #define DEF_TREE_TAKE_FOCUS        "1"
40 #define DEF_TREE_WIDTH             "40"
41 #define DEF_TREE_SCROLL_COMMAND    NULL
42
43 /*
44  * A node in the tree is represented by this data structure.
45  */
46
47 #define TAG_SPACE 5
48
49 typedef struct Node {
50     int id;                     /* Unique id of the node. */
51     int level;                  /* Level in tree, 0 means root. */
52     struct Tree *tree;          /* Pointer to widget. */
53     struct Node *parent;        /* Pointer to parent node or NULL. */
54     struct Node *next;          /* Pointer to next node in this level. */
55     struct Node *firstChild, *lastChild;
56     Ck_Uid staticTagSpace[TAG_SPACE];
57     Ck_Uid *tagPtr;             /* Pointer to tag array. */
58     int tagSpace;               /* Total size of tag array. */
59     int numTags;                /* Number of tags in tag array. */
60     int fg;                     /* Foreground color of node's text. */
61     int bg;                     /* Background color of node's text. */
62     int attr;                   /* Video attributes of node's text. */
63     char *text;                 /* Text to display for this node. */
64     int textWidth;              /* Width of node's text. */
65     int flags;                  /* Flag bits (see below). */
66 } Node;
67
68 /*
69  * Flag bits for node:
70  *
71  * SELECTED:                    Non-zero means node is selected
72  * SHOWCHILDREN:                Non-zero means if node has children
73  *                              they shall be displayed.
74  */
75
76 #define SELECTED     1
77 #define SHOWCHILDREN 2
78
79 /*
80  * Custom option for handling "-tags" options for tree nodes:
81  */
82
83 static int              TreeTagsParseProc _ANSI_ARGS_((ClientData clientData,
84                             Tcl_Interp *interp, CkWindow *winPtr, char *value,
85                             char *widgRec, int offset));
86 static char *           TreeTagsPrintProc _ANSI_ARGS_((ClientData clientData,
87                             CkWindow *winPtr, char *widgRec, int offset,
88                             Tcl_FreeProc **freeProcPtr));
89
90 Ck_CustomOption treeTagsOption = {
91     TreeTagsParseProc,
92     TreeTagsPrintProc,
93     (ClientData) NULL
94 };
95
96 /*
97  * Information used for parsing configuration specs for nodes:
98  */
99
100 static Ck_ConfigSpec nodeConfigSpecs[] = {
101     {CK_CONFIG_ATTR, "-attributes", (char *) NULL, (char *) NULL,
102         "", Ck_Offset(Node, attr), CK_CONFIG_DONT_SET_DEFAULT },
103     {CK_CONFIG_COLOR, "-background", (char *) NULL, (char *) NULL,
104         "", Ck_Offset(Node, bg), CK_CONFIG_DONT_SET_DEFAULT },
105     {CK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
106         (char *) NULL, 0, 0},
107     {CK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
108         (char *) NULL, 0, 0},
109     {CK_CONFIG_COLOR, "-foreground", (char *) NULL, (char *) NULL,
110         "", Ck_Offset(Node, fg), CK_CONFIG_DONT_SET_DEFAULT},
111     {CK_CONFIG_CUSTOM, "-tags", (char *) NULL, (char *) NULL,
112         (char *) NULL, 0, CK_CONFIG_NULL_OK, &treeTagsOption},
113     {CK_CONFIG_STRING, "-text", (char *) NULL, (char *) NULL,
114         NULL, Ck_Offset(Node, text), CK_CONFIG_NULL_OK},
115     {CK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
116         (char *) NULL, 0, 0}
117 };
118
119 /*
120  * A data structure of the following type is kept for each
121  * widget managed by this file:
122  */
123
124 typedef struct Tree {
125     CkWindow *winPtr;           /* Window that embodies the widget.  NULL
126                                  * means that the window has been destroyed
127                                  * but the data structures haven't yet been
128                                  * cleaned up.*/
129     Tcl_Interp *interp;         /* Interpreter associated with menubutton. */
130     Tcl_Command widgetCmd;      /* Token for menubutton's widget command. */
131
132     int idCount;                /* For unique ids for nodes. */
133
134     /*
135      * Information about what's displayed in the menu button:
136      */
137
138     Node *firstChild, *lastChild;
139     Tcl_HashTable nodeTable;
140
141     /*
142      * Information used when displaying widget:
143      */
144
145     int normalFg;               /* Foreground color in normal mode. */
146     int normalBg;               /* Background color in normal mode. */
147     int normalAttr;             /* Attributes in normal mode. */
148     int activeFg;               /* Foreground color in active mode. */
149     int activeBg;               /* Ditto, background color. */
150     int activeAttr;             /* Attributes in active mode. */
151     int selectFg;               /* Foreground color for selected nodes. */
152     int selectBg;               /* Ditto, background color. */
153     int selectAttr;             /* Attributes for selected nodes. */
154
155     int width, height;          /* If > 0, these specify dimensions to request
156                                  * for window, in characters for text and in
157                                  * pixels for bitmaps.  In this case the actual
158                                  * size of the text string or bitmap is
159                                  * ignored in computing desired window size. */
160
161     int visibleNodes;           /* Total number of visible nodes. */
162     int topIndex;               /* Index of starting line. */
163     Node *topNode;              /* Node at top line of window. */
164     Node *activeNode;           /* Node which has active tag or NULL. */
165
166     int leadingSpace;           /* For displaying: size of leadingString. */
167     int *leadingString;         /* Malloc'ed leading vertical lines for
168                                  * displaying. */
169
170     /*
171      * Miscellaneous information:
172      */
173
174     char *takeFocus;            /* Value of -takefocus option;  not used in
175                                  * the C code, but used by keyboard traversal
176                                  * scripts.  Malloc'ed, but may be NULL. */
177     char *yScrollCmd;           /* Command prefix for communicating with
178                                  * vertical scrollbar.  NULL means no command
179                                  * to issue.  Malloc'ed. */
180     char *xScrollCmd;           /* Command prefix for communicating with
181                                  * horizontal scrollbar.  NULL means no command
182                                  * to issue.  Malloc'ed. */
183     int flags;                  /* Various flags;  see below for
184                                  * definitions. */
185 } Tree;
186
187 /*
188  * Flag bits for entire tree:
189  *
190  * REDRAW_PENDING:              Non-zero means a DoWhenIdle handler
191  *                              has already been queued to redraw
192  *                              this window.
193  * GOT_FOCUS:                   Non-zero means this button currently
194  *                              has the input focus.
195  * UPDATE_V_SCROLLBAR:          Non-zero means vertical scrollbar needs
196  *                              to be updated.
197  * UPDATE_H_SCROLLBAR:          Non-zero means horizontal scrollbar needs
198  *                              to be updated.
199  */
200
201 #define REDRAW_PENDING          1
202 #define GOT_FOCUS               2
203 #define UPDATE_V_SCROLLBAR      4
204 #define UPDATE_H_SCROLLBAR      4
205
206 /*
207  * Information used for parsing configuration specs:
208  */
209
210 static Ck_ConfigSpec configSpecs[] = {
211     {CK_CONFIG_ATTR, "-activeattributes", "activeAttributes",
212         "ActiveAttributes", DEF_TREE_ACTIVE_ATTR_COLOR,
213         Ck_Offset(Tree, activeAttr), CK_CONFIG_COLOR_ONLY},
214     {CK_CONFIG_ATTR, "-activeattributes", "activeAttributes",
215         "ActiveAttributes", DEF_TREE_ACTIVE_ATTR_MONO,
216         Ck_Offset(Tree, activeAttr), CK_CONFIG_MONO_ONLY},
217     {CK_CONFIG_ATTR, "-attributes", "attributes", "Attributes",
218         DEF_TREE_ATTR_COLOR, Ck_Offset(Tree, normalAttr),
219         CK_CONFIG_COLOR_ONLY},
220     {CK_CONFIG_ATTR, "-attributes", "attributes", "Attributes",
221         DEF_TREE_ATTR_MONO, Ck_Offset(Tree, normalAttr),
222         CK_CONFIG_MONO_ONLY},
223     {CK_CONFIG_COLOR, "-activebackground", "activeBackground", "Foreground",
224         DEF_TREE_ACTIVE_BG_COLOR, Ck_Offset(Tree, activeBg),
225         CK_CONFIG_COLOR_ONLY},
226     {CK_CONFIG_COLOR, "-activebackground", "activeBackground", "Foreground",
227         DEF_TREE_ACTIVE_BG_MONO, Ck_Offset(Tree, activeBg),
228         CK_CONFIG_MONO_ONLY},
229     {CK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
230         DEF_TREE_ACTIVE_FG_COLOR, Ck_Offset(Tree, activeFg),
231         CK_CONFIG_COLOR_ONLY},
232     {CK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
233         DEF_TREE_ACTIVE_FG_MONO, Ck_Offset(Tree, activeFg),
234         CK_CONFIG_MONO_ONLY},
235     {CK_CONFIG_COLOR, "-background", "background", "Background",
236         DEF_TREE_BG_COLOR, Ck_Offset(Tree, normalBg),
237         CK_CONFIG_COLOR_ONLY},
238     {CK_CONFIG_COLOR, "-background", "background", "Background",
239         DEF_TREE_BG_MONO, Ck_Offset(Tree, normalBg),
240         CK_CONFIG_MONO_ONLY},
241     {CK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
242         (char *) NULL, 0, 0},
243     {CK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
244         (char *) NULL, 0, 0},
245     {CK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
246         DEF_TREE_FG_COLOR, Ck_Offset(Tree, normalFg), CK_CONFIG_COLOR_ONLY},
247     {CK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
248         DEF_TREE_FG_MONO, Ck_Offset(Tree, normalFg), CK_CONFIG_MONO_ONLY},
249     {CK_CONFIG_COORD, "-height", "height", "Height",
250         DEF_TREE_HEIGHT, Ck_Offset(Tree, height), 0},
251     {CK_CONFIG_ATTR, "-selectattributes", "selectAttributes",
252         "SelectAttributes", DEF_TREE_SELECT_ATTR_COLOR,
253         Ck_Offset(Tree, selectAttr), CK_CONFIG_COLOR_ONLY},
254     {CK_CONFIG_ATTR, "-selectattributes", "selectAttributes",
255         "SelectAttributes", DEF_TREE_SELECT_ATTR_MONO,
256         Ck_Offset(Tree, selectAttr), CK_CONFIG_MONO_ONLY},
257     {CK_CONFIG_COLOR, "-selectbackground", "selectBackground", "Foreground",
258         DEF_TREE_SELECT_BG_COLOR, Ck_Offset(Tree, selectBg),
259         CK_CONFIG_COLOR_ONLY},
260     {CK_CONFIG_COLOR, "-selectbackground", "selectBackground", "Foreground",
261         DEF_TREE_SELECT_BG_MONO, Ck_Offset(Tree, selectBg),
262         CK_CONFIG_MONO_ONLY},
263     {CK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
264         DEF_TREE_SELECT_FG_COLOR, Ck_Offset(Tree, selectFg),
265         CK_CONFIG_COLOR_ONLY},
266     {CK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
267         DEF_TREE_SELECT_FG_MONO, Ck_Offset(Tree, selectFg),
268         CK_CONFIG_MONO_ONLY},
269     {CK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
270         DEF_TREE_TAKE_FOCUS, Ck_Offset(Tree, takeFocus),
271         CK_CONFIG_NULL_OK},
272     {CK_CONFIG_COORD, "-width", "width", "Width",
273         DEF_TREE_WIDTH, Ck_Offset(Tree, width), 0},
274     {CK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
275         DEF_TREE_SCROLL_COMMAND, Ck_Offset(Tree, xScrollCmd),
276         CK_CONFIG_NULL_OK},
277     {CK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
278         DEF_TREE_SCROLL_COMMAND, Ck_Offset(Tree, yScrollCmd),
279         CK_CONFIG_NULL_OK},
280     {CK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
281         (char *) NULL, 0, 0}
282 };
283
284 /*
285  * The structure defined below is used to keep track of a tag search
286  * in progress.  Only the "prevPtr" field should be accessed by anyone
287  * other than StartTagSearch and NextNode.
288  */
289
290 typedef struct TagSearch {
291     Tree *treePtr;              /* Tree widget being searched. */
292     Tcl_HashSearch search;      /* Hash search for nodeTable. */
293     Ck_Uid tag;                 /* Tag to search for. 0 means return
294                                  * all nodes. */
295     int searchOver;             /* Non-zero means NextNode should always
296                                  * return NULL. */
297 } TagSearch;
298
299 static Ck_Uid allUid = NULL;
300 static Ck_Uid hideChildrenUid = NULL;
301 static Ck_Uid activeUid = NULL;
302
303 /*
304  * Forward declarations for procedures defined later in this file:
305  */
306
307 static Node *           StartTagSearch _ANSI_ARGS_((Tree *treePtr,
308                             char *tag, TagSearch *searchPtr));
309 static Node *           NextNode _ANSI_ARGS_((TagSearch *searchPtr));
310 static void             DoNode _ANSI_ARGS_((Tcl_Interp *interp,
311                             Node *nodePtr, Ck_Uid tag));
312 static void             TreeCmdDeletedProc _ANSI_ARGS_((
313                             ClientData clientData));
314 static void             TreeEventProc _ANSI_ARGS_((ClientData clientData,
315                             CkEvent *eventPtr));
316 static int              TreeWidgetCmd _ANSI_ARGS_((ClientData clientData,
317                             Tcl_Interp *interp, int argc, char **argv));
318 static int              ConfigureTree _ANSI_ARGS_((Tcl_Interp *interp,
319                             Tree *treePtr, int argc, char **argv,
320                             int flags));
321 static void             DestroyTree _ANSI_ARGS_((ClientData clientData));
322 static void             DisplayTree _ANSI_ARGS_((ClientData clientData));
323 static void             TreeEventuallyRedraw _ANSI_ARGS_((Tree *treePtr));
324 static int              FindNodes _ANSI_ARGS_((Tcl_Interp *interp,
325                             Tree *treePtr, int argc, char **argv,
326                             char *newTag, char *cmdName, char *option));
327 static void             DeleteNode _ANSI_ARGS_((Tree *treePtr, Node *nodePtr));
328 static void             RecomputeVisibleNodes _ANSI_ARGS_((Tree *treePtr));
329 static void             ChangeTreeView _ANSI_ARGS_((Tree *treePtr, int index));
330 static void             TreeUpdateVScrollbar _ANSI_ARGS_((Tree *treePtr));
331 static int              GetNodeYCoord _ANSI_ARGS_((Tree *treePtr,
332                             Node *thisPtr, int *yPtr));
333 \f
334 /*
335  *--------------------------------------------------------------
336  *
337  * Ck_TreeCmd --
338  *
339  *      This procedure is invoked to process the "tree"
340  *      Tcl commands.  See the user documentation for details
341  *      on what it does.
342  *
343  * Results:
344  *      A standard Tcl result.
345  *
346  * Side effects:
347  *      See the user documentation.
348  *
349  *--------------------------------------------------------------
350  */
351
352 int
353 Ck_TreeCmd(clientData, interp, argc, argv)
354     ClientData clientData;      /* Main window associated with
355                                  * interpreter. */
356     Tcl_Interp *interp;         /* Current interpreter. */
357     int argc;                   /* Number of arguments. */
358     char **argv;                /* Argument strings. */
359 {
360     Tree *treePtr;
361     CkWindow *mainPtr = (CkWindow *) clientData;
362     CkWindow *new;
363
364     allUid = Ck_GetUid("all");
365     hideChildrenUid = Ck_GetUid("hidechildren");
366     activeUid = Ck_GetUid("active");
367
368     if (argc < 2) {
369         Tcl_AppendResult(interp, "wrong # args:  should be \"",
370                 argv[0], " pathName ?options?\"", (char *) NULL);
371         return TCL_ERROR;
372     }
373
374     /*
375      * Create the new window.
376      */
377
378     new = Ck_CreateWindowFromPath(interp, mainPtr, argv[1], 0);
379     if (new == NULL) {
380         return TCL_ERROR;
381     }
382
383     /*
384      * Initialize the data structure for the button.
385      */
386
387     treePtr = (Tree *) ckalloc(sizeof (Tree));
388     treePtr->winPtr = new;
389     treePtr->interp = interp;
390     treePtr->widgetCmd = Tcl_CreateCommand(interp, treePtr->winPtr->pathName,
391             TreeWidgetCmd, (ClientData) treePtr, TreeCmdDeletedProc);
392     treePtr->idCount = 0;
393     treePtr->firstChild = treePtr->lastChild = NULL;
394     Tcl_InitHashTable(&treePtr->nodeTable, TCL_ONE_WORD_KEYS);
395     treePtr->normalBg = 0;
396     treePtr->normalFg = 0;
397     treePtr->normalAttr = 0;
398     treePtr->activeBg = 0;
399     treePtr->activeFg = 0;
400     treePtr->activeAttr = 0;
401     treePtr->selectBg = 0;
402     treePtr->selectFg = 0;
403     treePtr->selectAttr = 0;
404     treePtr->width = 0;
405     treePtr->height = 0;
406     treePtr->visibleNodes = 0;
407     treePtr->topIndex = 0;
408     treePtr->topNode = NULL;
409     treePtr->activeNode = NULL;
410     treePtr->leadingSpace = 0;
411     treePtr->leadingString = NULL;
412     treePtr->takeFocus = NULL;
413     treePtr->xScrollCmd = NULL;
414     treePtr->yScrollCmd = NULL;
415     treePtr->flags = 0;
416
417     Ck_SetClass(treePtr->winPtr, "Tree");
418     Ck_CreateEventHandler(treePtr->winPtr,
419         CK_EV_EXPOSE | CK_EV_MAP | CK_EV_DESTROY |
420         CK_EV_FOCUSIN | CK_EV_FOCUSOUT, TreeEventProc, (ClientData) treePtr);
421     if (ConfigureTree(interp, treePtr, argc-2, argv+2, 0) != TCL_OK) {
422         Ck_DestroyWindow(treePtr->winPtr);
423         return TCL_ERROR;
424     }
425
426     interp->result = treePtr->winPtr->pathName;
427     return TCL_OK;
428 }
429 \f
430 /*
431  *--------------------------------------------------------------
432  *
433  * TreeWidgetCmd --
434  *
435  *      This procedure is invoked to process the Tcl command
436  *      that corresponds to a widget managed by this module.
437  *      See the user documentation for details on what it does.
438  *
439  * Results:
440  *      A standard Tcl result.
441  *
442  * Side effects:
443  *      See the user documentation.
444  *
445  *--------------------------------------------------------------
446  */
447
448 static int
449 TreeWidgetCmd(clientData, interp, argc, argv)
450     ClientData clientData;      /* Information about button widget. */
451     Tcl_Interp *interp;         /* Current interpreter. */
452     int argc;                   /* Number of arguments. */
453     char **argv;                /* Argument strings. */
454 {
455     Tree *treePtr = (Tree *) clientData;
456     int result = TCL_OK, redraw = 0, recompute = 0;
457     size_t length;
458     int c;
459
460     if (argc < 2) {
461         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
462                 " option ?arg arg ...?\"", (char *) NULL);
463         return TCL_ERROR;
464     }
465     Ck_Preserve((ClientData) treePtr);
466     c = argv[1][0];
467     length = strlen(argv[1]);
468
469     if ((c == 'a') && (strncmp(argv[1], "addtag", length) == 0)) {
470         if (argc < 4) {
471             Tcl_AppendResult(interp, "wrong # args: should be \"",
472                     argv[0], " addtags tag searchCommand ?arg arg ...?\"",
473                     (char *) NULL);
474             goto error;
475         }
476         result = FindNodes(interp, treePtr, argc-3, argv+3, argv[2], argv[0],
477                 " addtag tag");
478     } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
479             && (length >= 2)) {
480         if (argc != 3) {
481             Tcl_AppendResult(interp, "wrong # args: should be \"",
482                     argv[0], " cget option\"",
483                     (char *) NULL);
484             goto error;
485         }
486         result = Ck_ConfigureValue(interp, treePtr->winPtr, configSpecs,
487                 (char *) treePtr, argv[2], 0);
488     } else if ((c == 'c') && (strncmp(argv[1], "children", length) == 0)
489             && (length >= 2)) {
490         Node *nodePtr = NULL;
491         TagSearch search;
492
493         if (argc > 3) {
494             Tcl_AppendResult(interp, "wrong # args: should be \"",
495                     argv[0], " children ?tagOrId?\"",
496                     (char *) NULL);
497             goto error;
498         }
499         if (argc > 2) {
500             nodePtr = StartTagSearch(treePtr, argv[2], &search);
501             if (nodePtr == NULL)
502                 goto error;
503         }
504         if (nodePtr == NULL)
505             nodePtr = treePtr->firstChild;
506         else
507             nodePtr = nodePtr->firstChild;
508         while (nodePtr != NULL) {
509             DoNode(interp, nodePtr, (Ck_Uid) NULL);
510             nodePtr = nodePtr->next;
511         }
512         result = TCL_OK;
513     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
514             && (length >= 2)) {
515         if (argc == 2) {
516             result = Ck_ConfigureInfo(interp, treePtr->winPtr, configSpecs,
517                     (char *) treePtr, (char *) NULL, 0);
518         } else if (argc == 3) {
519             result = Ck_ConfigureInfo(interp, treePtr->winPtr, configSpecs,
520                     (char *) treePtr, argv[2], 0);
521         } else {
522             result = ConfigureTree(interp, treePtr, argc-2, argv+2,
523                     CK_CONFIG_ARGV_ONLY);
524         }
525     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
526             && (length >= 2)) {
527         int i;
528
529         for (i = 2; i < argc; i++) {
530             for (;;) {
531                 Node *nodePtr;
532                 TagSearch search;
533
534                 nodePtr = StartTagSearch(treePtr, argv[i], &search);
535                 if (nodePtr == NULL)
536                     break;
537                 DeleteNode(treePtr, nodePtr);
538                 recompute++;
539             }
540         }
541         if (recompute)
542             redraw++;
543     } else if ((c == 'd') && (strncmp(argv[1], "dtag", length) == 0)
544             && (length >= 2)) {
545         Ck_Uid tag;
546         int i;
547         Node *nodePtr;
548         TagSearch search;
549
550         if ((argc != 3) && (argc != 4)) {
551             Tcl_AppendResult(interp, "wrong # args: should be \"",
552                     argv[0], " dtag tagOrId ?tagToDelete?\"",
553                     (char *) NULL);
554             goto error;
555         }
556         if (argc == 4) {
557             tag = Ck_GetUid(argv[3]);
558         } else {
559             tag = Ck_GetUid(argv[2]);
560         }
561         for (nodePtr = StartTagSearch(treePtr, argv[2], &search);
562                 nodePtr != NULL; nodePtr = NextNode(&search)) {
563             for (i = nodePtr->numTags-1; i >= 0; i--) {
564                 if (nodePtr->tagPtr[i] == tag) {
565                     nodePtr->tagPtr[i] = nodePtr->tagPtr[nodePtr->numTags-1];
566                     nodePtr->numTags--;
567                     if (tag == activeUid)
568                         redraw++;
569                     else if (tag == hideChildrenUid) {
570                         if (!(nodePtr->flags & SHOWCHILDREN)) {
571                             nodePtr->flags |= SHOWCHILDREN;
572                             recompute++;
573                             redraw++;
574                         }
575                     }
576                 }
577             }
578         }
579     } else if ((c == 'f') && (strncmp(argv[1], "find", length) == 0)
580             && (length >= 2)) {
581         if (argc < 3) {
582             Tcl_AppendResult(interp, "wrong # args: should be \"",
583                     argv[0], " find searchCommand ?arg arg ...?\"",
584                     (char *) NULL);
585             goto error;
586         }
587         result = FindNodes(interp, treePtr, argc - 2, argv + 2, (char *) NULL,
588                 argv[0], " find");
589     } else if ((c == 'g') && (strncmp(argv[1], "gettags", length) == 0)) {
590         Node *nodePtr;
591         TagSearch search;
592
593         if (argc != 3) {
594             Tcl_AppendResult(interp, "wrong # args: should be \"",
595                     argv[0], " gettags tagOrId\"", (char *) NULL);
596             goto error;
597         }
598         nodePtr = StartTagSearch(treePtr, argv[2], &search);
599         if (nodePtr != NULL) {
600             int i;
601
602             for (i = 0; i < nodePtr->numTags; i++) {
603                 Tcl_AppendElement(interp, (char *) nodePtr->tagPtr[i]);
604             }
605         }
606     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)) {
607         int optargc = 2, id;
608         Node *nodePtr = NULL, *new;
609         char *end;
610
611         if (argc < 3) {
612             Tcl_AppendResult(interp, "wrong # args: should be \"",
613                 argv[0], " insert ?id? ?option value ...?\"",
614                 (char *) NULL);
615             goto error;
616         }
617         id = strtoul(argv[2], &end, 0);
618         if (*end == 0) {
619             if (end != argv[2]) {
620                 Tcl_HashEntry *hPtr;
621
622                 hPtr = Tcl_FindHashEntry(&treePtr->nodeTable, (char *) id);
623                 if (hPtr == NULL) {
624                     Tcl_AppendResult(interp, "no node with id \"", argv[2],
625                         "\"", (char *) NULL);
626                     goto error;
627                 }
628                 nodePtr = (Node *) Tcl_GetHashValue(hPtr);
629             }
630             optargc = 3;
631         }
632
633         new = (Node *) ckalloc (sizeof (Node));
634         new->id = treePtr->idCount++;
635         new->level = nodePtr == NULL ? 0 : nodePtr->level + 1;
636         new->tree = treePtr;
637         new->parent = nodePtr;
638         new->next = NULL;
639         new->firstChild = new->lastChild = NULL;
640         new->tagPtr = new->staticTagSpace;
641         new->tagSpace = TAG_SPACE;
642         new->numTags = 0;
643         new->fg = new->bg = new->attr = -1;
644         new->text = NULL;
645         new->textWidth = 0;
646         new->flags = SHOWCHILDREN;
647
648         if (new->level * 2 > treePtr->leadingSpace) {
649             int *newString;
650
651             treePtr->leadingSpace = new->level * 8;
652             newString = (int *) ckalloc(treePtr->leadingSpace * sizeof (int));
653             if (treePtr->leadingString != NULL)
654                 ckfree((char *) treePtr->leadingString);
655             treePtr->leadingString = newString;
656         }
657
658         result = Ck_ConfigureWidget(interp, treePtr->winPtr,
659             nodeConfigSpecs, argc - optargc, &argv[optargc],
660             (char *) new, CK_CONFIG_ARGV_ONLY);
661
662         if (result == TCL_OK) {
663             Tcl_HashEntry *hPtr;
664             int newHash;
665             char buf[32];
666
667             hPtr = Tcl_CreateHashEntry(&treePtr->nodeTable,
668                 (char *) new->id, &newHash);
669             Tcl_SetHashValue(hPtr, (ClientData) new);
670             if (new->parent == NULL) {
671                 new->level = 0;
672                 if (treePtr->lastChild == NULL)
673                     treePtr->firstChild = new;
674                 else
675                     treePtr->lastChild->next = new;
676                 treePtr->lastChild = new;
677             } else {
678                 new->level = nodePtr->level + 1;
679                 if (nodePtr->lastChild == NULL)
680                     nodePtr->firstChild = new;
681                 else
682                     nodePtr->lastChild->next = new;
683                 nodePtr->lastChild = new;
684             }
685             recompute++;
686             redraw++;
687             sprintf(buf, "%d", new->id);
688             Tcl_AppendResult(interp, buf, (char *) NULL);
689         } else {
690             ckfree((char *) new);
691         }
692     } else if ((c == 'n') && (strncmp(argv[1], "nodecget", length) == 0)
693             && (length >= 6)) {
694         Node *nodePtr;
695         TagSearch search;
696
697         if (argc != 4) {
698             Tcl_AppendResult(interp, "wrong # args: should be \"",
699                     argv[0], " nodecget tagOrId option\"",
700                     (char *) NULL);
701             return TCL_ERROR;
702         }
703         nodePtr = StartTagSearch(treePtr, argv[2], &search);
704         if (nodePtr != NULL) {
705             result = Ck_ConfigureValue(treePtr->interp, treePtr->winPtr,
706                     nodeConfigSpecs, (char *) nodePtr,
707                     argv[3], 0);
708         }
709     } else if ((c == 'n') && (strncmp(argv[1], "nodeconfigure", length) == 0)
710             && (length >= 6)) {
711         Node *nodePtr;
712         TagSearch search;
713
714         if (argc < 3) {
715             Tcl_AppendResult(interp, "wrong # args: should be \"",
716                     argv[0], " nodeconfigure tagOrId ?option value ...?\"",
717                     (char *) NULL);
718             goto error;
719         }
720         for (nodePtr = StartTagSearch(treePtr, argv[2], &search);
721                 nodePtr != NULL; nodePtr = NextNode(&search)) {
722             if (argc == 3) {
723                 result = Ck_ConfigureInfo(treePtr->interp, treePtr->winPtr,
724                         nodeConfigSpecs, (char *) nodePtr,
725                         (char *) NULL, 0);
726             } else if (argc == 4) {
727                 result = Ck_ConfigureInfo(treePtr->interp, treePtr->winPtr,
728                         nodeConfigSpecs, (char *) nodePtr,
729                         argv[3], 0);
730             } else {
731                 result = Ck_ConfigureWidget(interp, treePtr->winPtr,
732                         nodeConfigSpecs, argc - 3, &argv[3],
733                         (char *) nodePtr, CK_CONFIG_ARGV_ONLY);
734                 redraw++;
735             }
736             if ((result != TCL_OK) || (argc < 5)) {
737                 break;
738             }
739         }
740     } else if ((c == 'p') && (strncmp(argv[1], "parent", length) == 0)
741             && (length >= 2)) {
742         Node *nodePtr;
743         TagSearch search;
744
745         if (argc != 3) {
746             Tcl_AppendResult(interp, "wrong # args: should be \"",
747                     argv[0], " parent tagOrId\"",
748                     (char *) NULL);
749             goto error;
750         }
751         nodePtr = StartTagSearch(treePtr, argv[2], &search);
752         if (nodePtr == NULL)
753             goto error;
754         if (nodePtr->parent != NULL)
755             DoNode(interp, nodePtr->parent, (Ck_Uid) NULL);
756         result = TCL_OK;
757     } else if ((c == 's') && (strncmp(argv[1], "select", length) == 0)
758             && (length >= 2)) {
759         Node *nodePtr;
760         TagSearch search;
761
762         if (argc != 4) {
763             Tcl_AppendResult(interp, "wrong # args: should be \"",
764                 argv[0], " select option tagOrId\"", (char *) NULL);
765             goto error;
766         }
767         nodePtr = StartTagSearch(treePtr, argv[3], &search);
768         if (nodePtr == NULL) {
769             Tcl_AppendResult(interp, "can't find a selectable node \"",
770                 argv[3], "\"", (char *) NULL);
771             goto error;
772         }
773         length = strlen(argv[2]);
774         c = argv[2][0];
775         if ((c == 'c') && (argv[2] != NULL) &&
776             (strncmp(argv[2], "clear", length) == 0)) {
777             do {
778                 nodePtr->flags &= ~SELECTED;
779                 nodePtr = NextNode(&search);
780                 redraw++;
781             } while (nodePtr != NULL);
782         } else if ((c == 'i') && (strncmp(argv[2], "includes", length) == 0)) {
783             interp->result = (nodePtr->flags & SELECTED) ? "1" : "0";
784         } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
785             do {
786                 nodePtr->flags |= SELECTED;
787                 nodePtr = NextNode(&search);
788                 redraw++;
789             } while (nodePtr != NULL);
790         } else {
791             Tcl_AppendResult(interp, "bad select option \"", argv[2],
792                     "\": must be clear, includes, or set", (char *) NULL);
793             goto error;
794         }
795     } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) {
796         int type, count;
797         double fraction;
798
799         if (argc == 2) {
800         } else {
801             type = Ck_GetScrollInfo(interp, argc, argv, &fraction, &count);
802             switch (type) {
803                 case CK_SCROLL_ERROR:
804                     goto error;
805                 case CK_SCROLL_MOVETO:
806                     break;
807                 case CK_SCROLL_PAGES:
808                     break;
809                 case CK_SCROLL_UNITS:
810                     break;
811             }
812         }
813     } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)) {
814         int type, count, index;
815         double fraction;
816
817         if (argc == 2) {
818             if (treePtr->visibleNodes == 0) {
819                 interp->result = "0 1";
820             } else {
821                 double fraction2;
822
823                 fraction = treePtr->topIndex / (double) treePtr->visibleNodes;
824                 fraction2 = (treePtr->topIndex + treePtr->winPtr->height) /
825                     (double) treePtr->visibleNodes;
826                 if (fraction2 > 1.0) {
827                     fraction2 = 1.0;
828                 }
829                 sprintf(interp->result, "%g %g", fraction, fraction2);
830             }
831         } else if (argc == 3) {
832             Node *nodePtr;
833             TagSearch search;
834
835             nodePtr = StartTagSearch(treePtr, argv[2], &search);
836             if (nodePtr == NULL) {
837                 Tcl_AppendResult(interp, "can't find a selectable node \"",
838                     argv[3], "\"", (char *) NULL);
839                 goto error;
840             }
841             if (GetNodeYCoord(treePtr, nodePtr, &index) == TCL_OK) {
842                 if (index < treePtr->topIndex ||
843                     index >= treePtr->topIndex + treePtr->winPtr->height)
844                     ChangeTreeView(treePtr,
845                         index - treePtr->winPtr->height / 2);
846             }
847         } else {
848             type = Ck_GetScrollInfo(interp, argc, argv, &fraction, &count);
849             switch (type) {
850                 case CK_SCROLL_ERROR:
851                     goto error;
852                 case CK_SCROLL_MOVETO:
853                     index = (int) (treePtr->visibleNodes * fraction + 0.5);
854                     break;
855                 case CK_SCROLL_PAGES:
856                     if (treePtr->visibleNodes > 2) {
857                         index = treePtr->topIndex
858                                 + count * (treePtr->winPtr->height - 2);
859                     } else {
860                         index = treePtr->topIndex + count;
861                     }
862                     break;
863                 case CK_SCROLL_UNITS:
864                     index = treePtr->topIndex + count;
865                     break;
866             }
867             ChangeTreeView(treePtr, index);
868         }
869     } else {
870         Tcl_AppendResult(interp, "bad option \"", argv[1],
871                 "\":  must be cget or configure",
872                 (char *) NULL);
873         goto error;
874     }
875     if (recompute)
876         RecomputeVisibleNodes(treePtr);
877     if (redraw)
878         TreeEventuallyRedraw(treePtr);
879
880     Ck_Release((ClientData) treePtr);
881     return result;
882
883 error:
884     Ck_Release((ClientData) treePtr);
885     return TCL_ERROR;
886 }
887 \f
888 /*
889  *----------------------------------------------------------------------
890  *
891  * DestroyTree --
892  *
893  *      This procedure is invoked to recycle all of the resources
894  *      associated with a tree widget.  It is invoked as a
895  *      when-idle handler in order to make sure that there is no
896  *      other use of the tree pending at the time of the deletion.
897  *
898  * Results:
899  *      None.
900  *
901  * Side effects:
902  *      Everything associated with the widget is freed up.
903  *
904  *----------------------------------------------------------------------
905  */
906
907 static void
908 DestroyTree(clientData)
909     ClientData clientData;      /* Info about tree widget. */
910 {
911     Tree *treePtr = (Tree *) clientData;
912     Tcl_HashEntry *hPtr;
913     Tcl_HashSearch search;
914     Node *nodePtr;
915
916     /*
917      * Free up all the stuff that requires special handling, then
918      * let Ck_FreeOptions handle all the standard option-related
919      * stuff.
920      */
921
922     if (treePtr->leadingString != NULL) {
923         ckfree((char *) treePtr->leadingString);
924         treePtr->leadingString = NULL;
925     }
926
927     hPtr = Tcl_FirstHashEntry(&treePtr->nodeTable, &search);
928     while (hPtr != NULL) {
929         nodePtr = (Node *) Tcl_GetHashValue(hPtr);
930         Ck_FreeOptions(nodeConfigSpecs, (char *) nodePtr, 0);
931         if (nodePtr->tagPtr != nodePtr->staticTagSpace)
932             ckfree((char *) nodePtr->tagPtr);
933         ckfree((char *) nodePtr);
934         hPtr = Tcl_NextHashEntry(&search);
935     }
936     Tcl_DeleteHashTable(&treePtr->nodeTable);
937
938     Ck_FreeOptions(configSpecs, (char *) treePtr, 0);
939     ckfree((char *) treePtr);
940 }
941 \f
942 /*
943  *----------------------------------------------------------------------
944  *
945  * ConfigureTree --
946  *
947  *      This procedure is called to process an argv/argc list, plus
948  *      the option database, in order to configure (or
949  *      reconfigure) a tree widget.
950  *
951  * Results:
952  *      The return value is a standard Tcl result.  If TCL_ERROR is
953  *      returned, then interp->result contains an error message.
954  *
955  * Side effects:
956  *      Configuration information, such as text string, colors, font,
957  *      etc. get set for treePtr;  old resources get freed, if there
958  *      were any.  The tree is redisplayed.
959  *
960  *----------------------------------------------------------------------
961  */
962
963 static int
964 ConfigureTree(interp, treePtr, argc, argv, flags)
965     Tcl_Interp *interp;         /* Used for error reporting. */
966     Tree *treePtr;              /* Information about widget;  may or may
967                                  * not already have values for some fields. */
968     int argc;                   /* Number of valid entries in argv. */
969     char **argv;                /* Arguments. */
970     int flags;                  /* Flags to pass to Ck_ConfigureWidget. */
971 {
972     int result, width, height;
973
974     result = Ck_ConfigureWidget(interp, treePtr->winPtr, configSpecs,
975             argc, argv, (char *) treePtr, flags);
976     if (result != TCL_OK)
977         return TCL_ERROR;
978     width = treePtr->width;
979     if (width <= 0)
980         width = 1;
981     height = treePtr->height;
982     if (height <= 0)
983         height = 1;
984     Ck_GeometryRequest(treePtr->winPtr, width, height);
985     TreeEventuallyRedraw(treePtr);
986     return TCL_OK;
987 }
988 \f
989 /*
990  *----------------------------------------------------------------------
991  *
992  * TreeEventuallyRedraw --
993  *
994  *      This procedure is called to dispatch a do-when-idle
995  *      handler for redrawing the tree.
996  *
997  * Results:
998  *      None.
999  *
1000  *----------------------------------------------------------------------
1001  */
1002
1003 static void
1004 TreeEventuallyRedraw(treePtr)
1005     Tree *treePtr;
1006 {
1007     if ((treePtr->winPtr->flags & CK_MAPPED)
1008         && !(treePtr->flags & REDRAW_PENDING)) {
1009         Tk_DoWhenIdle(DisplayTree, (ClientData) treePtr);
1010         treePtr->flags |= REDRAW_PENDING;
1011     }
1012 }
1013 \f
1014 /*
1015  *----------------------------------------------------------------------
1016  *
1017  * DeleteNode --
1018  *
1019  *      This procedure is called to delete a node and its children.
1020  *
1021  * Results:
1022  *      None.
1023  *
1024  *----------------------------------------------------------------------
1025  */
1026
1027 static void
1028 DeleteNode(treePtr, nodePtr)
1029     Tree *treePtr;
1030     Node *nodePtr;
1031 {
1032     Node *childPtr, *thisPtr, *prevPtr;
1033     Tcl_HashEntry *hPtr;
1034
1035     while ((childPtr = nodePtr->firstChild) != NULL)
1036         DeleteNode(treePtr, childPtr);
1037
1038     if (treePtr->topNode == nodePtr)
1039         treePtr->topNode = nodePtr->parent;
1040     if (treePtr->activeNode == nodePtr)
1041         treePtr->activeNode = NULL;
1042
1043     prevPtr = NULL;
1044     if (nodePtr->parent == NULL) {
1045         thisPtr = treePtr->firstChild;
1046     } else {
1047         thisPtr = nodePtr->parent->firstChild;
1048     }
1049     for (; thisPtr != NULL; prevPtr = thisPtr, thisPtr = thisPtr->next) {
1050         if (thisPtr == nodePtr) {
1051             if (prevPtr == NULL) {
1052                 if (nodePtr->parent == NULL)
1053                     treePtr->firstChild = nodePtr->next;
1054                 else
1055                     nodePtr->parent->firstChild = nodePtr->next;
1056             } else
1057                 prevPtr->next = nodePtr->next;
1058             if (nodePtr->next == NULL) {
1059                 if (nodePtr->parent == NULL)
1060                     treePtr->lastChild = prevPtr;
1061                 else
1062                     nodePtr->parent->lastChild = prevPtr;
1063             }
1064             break;
1065         }
1066     }
1067
1068     hPtr = Tcl_FindHashEntry(&treePtr->nodeTable, (char *) nodePtr->id);
1069     Tcl_DeleteHashEntry(hPtr);
1070     Ck_FreeOptions(nodeConfigSpecs, (char *) nodePtr, 0);
1071     if (nodePtr->tagPtr != nodePtr->staticTagSpace)
1072         ckfree((char *) nodePtr->tagPtr);
1073     ckfree((char *) nodePtr);
1074 }
1075 \f
1076
1077 static void
1078 DeleteActiveTag(treePtr)
1079     Tree *treePtr;
1080 {
1081     int i;
1082     Node *nodePtr = treePtr->activeNode;
1083
1084     if (nodePtr == NULL)
1085         return;
1086
1087     for (i = nodePtr->numTags-1; i >= 0; i--) {
1088         if (nodePtr->tagPtr[i] == activeUid) {
1089             nodePtr->tagPtr[i] = nodePtr->tagPtr[nodePtr->numTags-1];
1090             nodePtr->numTags--;
1091         }
1092     }
1093     treePtr->activeNode = NULL;
1094 }
1095
1096 \f
1097 /*
1098  *----------------------------------------------------------------------
1099  *
1100  * DisplayTree --
1101  *
1102  *      This procedure is invoked to display a tree widget.
1103  *
1104  * Results:
1105  *      None.
1106  *
1107  * Side effects:
1108  *      Commands are output to X to display the tree.
1109  *
1110  *----------------------------------------------------------------------
1111  */
1112
1113 static void
1114 DisplayTree(clientData)
1115     ClientData clientData;      /* Information about widget. */
1116 {
1117     Tree *treePtr = (Tree *) clientData;
1118     CkWindow *winPtr = treePtr->winPtr;
1119     Node *nodePtr, *nextPtr, *parentPtr;
1120     int i, x, y, mustRestore, rarrow;
1121     int ulcorner, urcorner, llcorner, lrcorner, lvline, lhline, ltee, ttee;
1122     WINDOW *window;
1123
1124     treePtr->flags &= ~REDRAW_PENDING;
1125     if ((treePtr->winPtr == NULL) || !(winPtr->flags & CK_MAPPED)) {
1126         return;
1127     }
1128     if (treePtr->flags & UPDATE_V_SCROLLBAR) {
1129         TreeUpdateVScrollbar(treePtr);
1130     }
1131     treePtr->flags &= ~(REDRAW_PENDING|UPDATE_H_SCROLLBAR|UPDATE_V_SCROLLBAR);
1132
1133     if (treePtr->firstChild == NULL) {
1134         Ck_ClearToBot(winPtr, 0, 0);
1135         Ck_EventuallyRefresh(winPtr);
1136         return;
1137     }
1138
1139     Ck_GetGChar(NULL, "rarrow", &rarrow);
1140     Ck_GetGChar(NULL, "ulcorner", &ulcorner);
1141     Ck_GetGChar(NULL, "urcorner", &urcorner);
1142     Ck_GetGChar(NULL, "llcorner", &llcorner);
1143     Ck_GetGChar(NULL, "lrcorner", &lrcorner);
1144     Ck_GetGChar(NULL, "vline", &lvline);
1145     Ck_GetGChar(NULL, "hline", &lhline);
1146     Ck_GetGChar(NULL, "ltee", &ltee);
1147     Ck_GetGChar(NULL, "ttee", &ttee);
1148
1149     Ck_SetWindowAttr(winPtr, treePtr->normalFg, treePtr->normalBg,
1150         treePtr->normalAttr);
1151
1152     nodePtr = treePtr->topNode;
1153
1154     i = nodePtr->level * 2 - 1;
1155     nextPtr = nodePtr->parent;
1156     while (i >= 0) {
1157         treePtr->leadingString[i] = ' ';
1158         parentPtr = nextPtr->parent;
1159         if (parentPtr != NULL) {
1160             if (parentPtr->lastChild == nextPtr)
1161                 treePtr->leadingString[i - 1] = ' ';
1162             else
1163                 treePtr->leadingString[i - 1] = lvline;
1164         } else if (treePtr->lastChild == nextPtr)
1165             treePtr->leadingString[i - 1] = ' ';
1166         else
1167             treePtr->leadingString[i - 1] = lvline;
1168         nextPtr = parentPtr;
1169         i -= 2;
1170     }
1171
1172     window = winPtr->window;
1173     y = 0;
1174     while (nodePtr != NULL && y < winPtr->height) {
1175         x = mustRestore = 0;
1176         wmove(window, y, x);
1177         if (nodePtr == treePtr->firstChild) {
1178             waddch(window, (nodePtr == treePtr->lastChild) ? lhline : ulcorner);
1179             x++;
1180         } else if (nodePtr == treePtr->lastChild) {
1181             waddch(window, llcorner);
1182             x++;
1183         } else if (nodePtr->level == 0) {
1184             waddch(window, ltee);
1185             x++;
1186         }
1187         for (i = 0; i < nodePtr->level * 2 && x < winPtr->width; i++) {
1188             waddch(window, treePtr->leadingString[i]);
1189             x++;
1190         }
1191         if (nodePtr->parent != NULL) {
1192            if (x < winPtr->width) {
1193                 if (nodePtr == nodePtr->parent->lastChild)
1194                     waddch(window, llcorner);
1195                 else
1196                     waddch(window, ltee);
1197                 x++;
1198             }
1199         }
1200         if (x < winPtr->width) {
1201             waddch(window, lhline);
1202             x++;
1203         }
1204         if (nodePtr->firstChild != NULL && (nodePtr->flags & SHOWCHILDREN)) {
1205             if (x < winPtr->width) {
1206                 waddch(window, ttee);
1207                 x++;
1208             }
1209             nextPtr = nodePtr->firstChild;
1210         } else {
1211             if (x < winPtr->width) {
1212                 waddch(window, (nodePtr->firstChild == NULL) ? lhline : rarrow);
1213                 x++;
1214             }
1215             nextPtr = nodePtr->next;
1216             if (nextPtr == NULL) {
1217                 parentPtr = nodePtr->parent;
1218                 while (nextPtr == NULL) {
1219                     if (parentPtr == NULL) {
1220                         break;
1221                     }
1222                     nextPtr = parentPtr->next;
1223                     if (nextPtr == NULL) {
1224                         parentPtr = parentPtr->parent;
1225                     }
1226                 }               
1227             }
1228         }
1229         if (x < winPtr->width) {
1230             waddch(window, ' ');
1231             x++;
1232         }
1233
1234         if (nodePtr == treePtr->activeNode && (treePtr->flags & GOT_FOCUS)) {
1235             Ck_SetWindowAttr(winPtr, treePtr->activeFg, treePtr->activeBg,
1236                 treePtr->activeAttr | ((nodePtr->flags & SELECTED) ? 
1237                 treePtr->selectAttr : 0));
1238             mustRestore = 1;
1239         } else if (nodePtr->flags & SELECTED) {
1240             Ck_SetWindowAttr(winPtr, treePtr->selectFg, treePtr->selectBg,
1241                 treePtr->selectAttr);
1242             mustRestore = 1;
1243         }
1244         CkDisplayChars(winPtr->mainPtr, window,
1245             nodePtr->text, strlen(nodePtr->text),
1246             x, y, 0,
1247             CK_NEWLINES_NOT_SPECIAL | CK_IGNORE_TABS);
1248         if (mustRestore)
1249             Ck_SetWindowAttr(winPtr, treePtr->normalFg, treePtr->normalBg,
1250                 treePtr->normalAttr);
1251         Ck_ClearToEol(winPtr, -1, -1);
1252
1253         i = nodePtr->level * 2;
1254         treePtr->leadingString[i] = nodePtr->next != NULL ? lvline : ' ';
1255         treePtr->leadingString[i + 1] = ' ';
1256
1257         nodePtr = nextPtr;
1258         y++;
1259     }
1260     if (y < winPtr->height)
1261         Ck_ClearToBot(winPtr, 0, y);
1262     Ck_EventuallyRefresh(winPtr);
1263 }
1264 \f
1265 /*
1266  *--------------------------------------------------------------
1267  *
1268  * TreeEventProc --
1269  *
1270  *      This procedure is invoked by the dispatcher for various
1271  *      events on trees.
1272  *
1273  * Results:
1274  *      None.
1275  *
1276  * Side effects:
1277  *      When the window gets deleted, internal structures get
1278  *      cleaned up.  When it gets exposed, it is redisplayed.
1279  *
1280  *--------------------------------------------------------------
1281  */
1282
1283 static void
1284 TreeEventProc(clientData, eventPtr)
1285     ClientData clientData;      /* Information about window. */
1286     CkEvent *eventPtr;          /* Information about event. */
1287 {
1288     Tree *treePtr = (Tree *) clientData;
1289
1290     if (eventPtr->type == CK_EV_EXPOSE) {
1291         TreeEventuallyRedraw(treePtr);
1292     } else if (eventPtr->type == CK_EV_DESTROY) {
1293         if (treePtr->winPtr != NULL) {
1294             treePtr->winPtr = NULL;
1295             Tcl_DeleteCommand(treePtr->interp,
1296                     Tcl_GetCommandName(treePtr->interp, treePtr->widgetCmd));
1297         }
1298         if (treePtr->flags & REDRAW_PENDING) {
1299             Tk_CancelIdleCall(DisplayTree, (ClientData) treePtr);
1300         }
1301         Ck_EventuallyFree((ClientData) treePtr, (Ck_FreeProc *) DestroyTree);
1302     } else if (eventPtr->type == CK_EV_FOCUSIN) {
1303         treePtr->flags |= GOT_FOCUS;
1304         TreeEventuallyRedraw(treePtr);
1305     } else if (eventPtr->type == CK_EV_FOCUSOUT) {
1306         treePtr->flags &= ~GOT_FOCUS;
1307         TreeEventuallyRedraw(treePtr);
1308     }
1309 }
1310 \f
1311 /*
1312  *----------------------------------------------------------------------
1313  *
1314  * TreeCmdDeletedProc --
1315  *
1316  *      This procedure is invoked when a widget command is deleted.  If
1317  *      the widget isn't already in the process of being destroyed,
1318  *      this command destroys it.
1319  *
1320  * Results:
1321  *      None.
1322  *
1323  * Side effects:
1324  *      The widget is destroyed.
1325  *
1326  *----------------------------------------------------------------------
1327  */
1328
1329 static void
1330 TreeCmdDeletedProc(clientData)
1331     ClientData clientData;      /* Pointer to widget record for widget. */
1332 {
1333     Tree *treePtr = (Tree *) clientData;
1334     CkWindow *winPtr = treePtr->winPtr;
1335
1336     /*
1337      * This procedure could be invoked either because the window was
1338      * destroyed and the command was then deleted (in which case winPtr
1339      * is NULL) or because the command was deleted, and then this procedure
1340      * destroys the widget.
1341      */
1342
1343     if (winPtr != NULL) {
1344         treePtr->winPtr = NULL;
1345         Ck_DestroyWindow(winPtr);
1346     }
1347 }
1348 \f
1349 /*
1350  *----------------------------------------------------------------------
1351  *
1352  * RecomputeVisibleNodes --
1353  *
1354  *      Display parameters are recomputed.
1355  *
1356  * Results:
1357  *      None.
1358  *
1359  *----------------------------------------------------------------------
1360  */
1361
1362 static void
1363 RecomputeVisibleNodes(treePtr)
1364     Tree *treePtr;
1365 {
1366     int count = 0, top = -1;
1367     Node *nodePtr, *nextPtr = NULL;
1368
1369     nodePtr = treePtr->firstChild;
1370     if (nodePtr == NULL)
1371         treePtr->topNode = NULL;
1372     while (nodePtr != NULL) {
1373         if (nodePtr->parent == NULL)
1374             nextPtr = nodePtr->next;
1375         if (nodePtr == treePtr->topNode)
1376             top = count;
1377         if (nodePtr->firstChild != NULL && (nodePtr->flags & SHOWCHILDREN)) {
1378             nodePtr = nodePtr->firstChild;
1379         } else if (nodePtr->next != NULL)
1380             nodePtr = nodePtr->next;
1381         else {
1382             while (nodePtr != NULL) {
1383                 nodePtr = nodePtr->parent;
1384                 if (nodePtr != NULL && nodePtr->next != NULL) {
1385                     nodePtr = nodePtr->next;
1386                     break;
1387                 }
1388             }
1389             if (nodePtr == NULL)
1390                 nodePtr = nextPtr;
1391         }
1392         count++;
1393     }
1394     if (top < 0) {
1395         treePtr->topNode = treePtr->firstChild;
1396         top = 0;
1397     }
1398     if (top != treePtr->topIndex || count != treePtr->visibleNodes)
1399         treePtr->flags |= UPDATE_V_SCROLLBAR;
1400     treePtr->topIndex = top;
1401     treePtr->visibleNodes = count;
1402 }    
1403 \f
1404 /*
1405  *----------------------------------------------------------------------
1406  *
1407  * ChangeTreeView --
1408  *
1409  *      Change the vertical view on a tree widget so that a given element
1410  *      is displayed at the top.
1411  *
1412  * Results:
1413  *      None.
1414  *
1415  * Side effects:
1416  *      What's displayed on the screen is changed.  If there is a
1417  *      scrollbar associated with this widget, then the scrollbar
1418  *      is instructed to change its display too.
1419  *
1420  *----------------------------------------------------------------------
1421  */
1422
1423 static void
1424 ChangeTreeView(treePtr, index)
1425     Tree *treePtr;                      /* Information about widget. */
1426     int index;                          /* Index of element in treePtr
1427                                          * that should now appear at the
1428                                          * top of the tree. */
1429 {
1430     int count;
1431     Node *nodePtr, *nextPtr = NULL;
1432
1433     if (index >= treePtr->visibleNodes - treePtr->winPtr->height)
1434         index = treePtr->visibleNodes - treePtr->winPtr->height;
1435     if (index < 0)
1436         index = 0;
1437     if (treePtr->topIndex != index) {
1438         if (index < treePtr->topIndex) {
1439             count = 0;
1440             nodePtr = treePtr->firstChild;
1441         } else {
1442             count = treePtr->topIndex;
1443             nodePtr = treePtr->topNode;
1444         }
1445         while (nodePtr != NULL) {
1446             if (nodePtr->parent == NULL)
1447                 nextPtr = nodePtr->next;
1448             if (count == index)
1449                 break;
1450             if (nodePtr->firstChild != NULL &&
1451                 (nodePtr->flags & SHOWCHILDREN)) {
1452                 nodePtr = nodePtr->firstChild;
1453             } else if (nodePtr->next != NULL)
1454                 nodePtr = nodePtr->next;
1455             else {
1456                 while (nodePtr != NULL) {
1457                     nodePtr = nodePtr->parent;
1458                     if (nodePtr != NULL && nodePtr->next != NULL) {
1459                         nodePtr = nodePtr->next;
1460                         break;
1461                     }
1462                 }
1463                 if (nodePtr == NULL)
1464                     nodePtr = nextPtr;
1465             }
1466             count++;
1467         }
1468         treePtr->topNode = nodePtr;
1469         treePtr->topIndex = count;
1470         treePtr->flags |= UPDATE_V_SCROLLBAR;
1471         TreeEventuallyRedraw(treePtr);
1472     }
1473 }
1474 \f
1475 /*
1476  *----------------------------------------------------------------------
1477  *
1478  * GetNodeYCoord --
1479  *
1480  *      Given node return the window Y coordinate corresponding to it.
1481  *
1482  *----------------------------------------------------------------------
1483  */
1484
1485 static int
1486 GetNodeYCoord(treePtr, thisPtr, yPtr)
1487     Tree *treePtr;                      /* Information about widget. */
1488     Node *thisPtr;
1489     int *yPtr;
1490 {
1491     int count;
1492     Node *nodePtr, *nextPtr = NULL;
1493
1494     count = 0;
1495     nodePtr = treePtr->firstChild;
1496     while (nodePtr != NULL) {
1497         if (thisPtr == nodePtr) {
1498             *yPtr = count;
1499             return TCL_OK;
1500         }
1501         if (nodePtr->parent == NULL)
1502             nextPtr = nodePtr->next;
1503         if (nodePtr->firstChild != NULL && (nodePtr->flags & SHOWCHILDREN)) {
1504             nodePtr = nodePtr->firstChild;
1505         } else if (nodePtr->next != NULL)
1506             nodePtr = nodePtr->next;
1507         else {
1508             while (nodePtr != NULL) {
1509                 nodePtr = nodePtr->parent;
1510                 if (nodePtr != NULL && nodePtr->next != NULL) {
1511                     nodePtr = nodePtr->next;
1512                     break;
1513                 }
1514             }
1515             if (nodePtr == NULL)
1516                 nodePtr = nextPtr;
1517         }
1518         count++;
1519     }
1520     return TCL_ERROR;
1521 }
1522 \f
1523 /*
1524  *--------------------------------------------------------------
1525  *
1526  * TreeTagsParseProc --
1527  *
1528  *      This procedure is invoked during option processing to handle
1529  *      "-tags" options for tree nodes.
1530  *
1531  * Results:
1532  *      A standard Tcl return value.
1533  *
1534  * Side effects:
1535  *      The tags for a given node get replaced by those indicated
1536  *      in the value argument.
1537  *
1538  *--------------------------------------------------------------
1539  */
1540
1541 static int
1542 TreeTagsParseProc(clientData, interp, winPtr, value, widgRec, offset)
1543     ClientData clientData;              /* Not used.*/
1544     Tcl_Interp *interp;                 /* Used for reporting errors. */
1545     CkWindow *winPtr;                   /* Window containing tree widget. */
1546     char *value;                        /* Value of option (list of tag
1547                                          * names). */
1548     char *widgRec;                      /* Pointer to record for item. */
1549     int offset;                         /* Offset into item (ignored). */
1550 {
1551     Node *nodePtr = (Node *) widgRec, *activeNode = NULL;
1552     int argc, i, hideChildren = 0, redraw = 0, recompute = 0;
1553     char **argv;
1554     Ck_Uid *newPtr;
1555
1556     /*
1557      * Break the value up into the individual tag names.
1558      */
1559
1560     if (Tcl_SplitList(interp, value, &argc, &argv) != TCL_OK) {
1561         return TCL_ERROR;
1562     }
1563
1564     /*
1565      * Check for special tags.
1566      */
1567     for (i = 0; i < nodePtr->numTags; i++) {
1568         if (nodePtr->tagPtr[i] == activeUid) {
1569             DeleteActiveTag(nodePtr->tree);
1570             redraw++;
1571         }
1572     }
1573
1574     /*
1575      * Make sure that there's enough space in the node to hold the
1576      * tag names.
1577      */
1578
1579     if (nodePtr->tagSpace < argc) {
1580         newPtr = (Ck_Uid *) ckalloc((unsigned) (argc * sizeof(Ck_Uid)));
1581         for (i = nodePtr->numTags-1; i >= 0; i--) {
1582             newPtr[i] = nodePtr->tagPtr[i];
1583         }
1584         if (nodePtr->tagPtr != nodePtr->staticTagSpace) {
1585             ckfree((char *) nodePtr->tagPtr);
1586         }
1587         nodePtr->tagPtr = newPtr;
1588         nodePtr->tagSpace = argc;
1589     }
1590     nodePtr->numTags = argc;
1591     for (i = 0; i < argc; i++) {
1592         nodePtr->tagPtr[i] = Ck_GetUid(argv[i]);
1593         if (nodePtr->tagPtr[i] == hideChildrenUid)
1594             hideChildren++;
1595         else if (nodePtr->tagPtr[i] == activeUid)
1596             activeNode = nodePtr;
1597     }
1598     ckfree((char *) argv);
1599     if (hideChildren && (nodePtr->flags & SHOWCHILDREN)) {
1600         nodePtr->flags &= ~SHOWCHILDREN;
1601         recompute++;
1602         redraw++;
1603     } else if (!hideChildren && !(nodePtr->flags & SHOWCHILDREN)) {
1604         nodePtr->flags |= SHOWCHILDREN;
1605         recompute++;
1606         redraw++;
1607     }
1608     if (activeNode != NULL) {
1609         DeleteActiveTag(nodePtr->tree);
1610         nodePtr->tree->activeNode = activeNode;
1611         redraw++;
1612     }
1613     if (recompute)
1614         RecomputeVisibleNodes(nodePtr->tree);
1615     if (redraw)
1616         TreeEventuallyRedraw(nodePtr->tree);
1617     return TCL_OK;
1618 }
1619 \f
1620 /*
1621  *--------------------------------------------------------------
1622  *
1623  * TreeTagsPrintProc --
1624  *
1625  *      This procedure is invoked by the Ck configuration code
1626  *      to produce a printable string for the "-tags" configuration
1627  *      option for tree nodes.
1628  *
1629  * Results:
1630  *      The return value is a string describing all the tags for
1631  *      the node referred to by "widgRec".  In addition, *freeProcPtr
1632  *      is filled in with the address of a procedure to call to free
1633  *      the result string when it's no longer needed (or NULL to
1634  *      indicate that the string doesn't need to be freed).
1635  *
1636  * Side effects:
1637  *      None.
1638  *
1639  *--------------------------------------------------------------
1640  */
1641
1642 static char *
1643 TreeTagsPrintProc(clientData, winPtr, widgRec, offset, freeProcPtr)
1644     ClientData clientData;              /* Ignored. */
1645     CkWindow *winPtr;                   /* Window containing tree widget. */
1646     char *widgRec;                      /* Pointer to record for item. */
1647     int offset;                         /* Ignored. */
1648     Tcl_FreeProc **freeProcPtr;         /* Pointer to variable to fill in with
1649                                          * information about how to reclaim
1650                                          * storage for return string. */
1651 {
1652     Node *nodePtr = (Node *) widgRec;
1653
1654     if (nodePtr->numTags == 0) {
1655         *freeProcPtr = (Tcl_FreeProc *) NULL;
1656         return "";
1657     }
1658     if (nodePtr->numTags == 1) {
1659         *freeProcPtr = (Tcl_FreeProc *) NULL;
1660         return (char *) nodePtr->tagPtr[0];
1661     }
1662     *freeProcPtr = (Tcl_FreeProc *) free;
1663     return Tcl_Merge(nodePtr->numTags, (char **) nodePtr->tagPtr);
1664 }
1665 \f
1666 /*
1667  *--------------------------------------------------------------
1668  *
1669  * StartTagSearch --
1670  *
1671  *      This procedure is called to initiate an enumeration of
1672  *      all nodes in a given tree that contain a given tag.
1673  *
1674  * Results:
1675  *      The return value is a pointer to the first node in
1676  *      treePtr that matches tag, or NULL if there is no
1677  *      such node.  The information at *searchPtr is initialized
1678  *      such that successive calls to NextNode will return
1679  *      successive nodes that match tag.
1680  *
1681  * Side effects:
1682  *      SearchPtr is linked into a list of searches in progress
1683  *      on treePtr, so that elements can safely be deleted
1684  *      while the search is in progress.  EndTagSearch must be
1685  *      called at the end of the search to unlink searchPtr from
1686  *      this list.
1687  *
1688  *--------------------------------------------------------------
1689  */
1690
1691 static Node *
1692 StartTagSearch(treePtr, tag, searchPtr)
1693     Tree *treePtr;                      /* Tree whose nodes are to be
1694                                          * searched. */
1695     char *tag;                          /* String giving tag value. */
1696     TagSearch *searchPtr;               /* Record describing tag search;
1697                                          * will be initialized here. */
1698 {
1699     int id;
1700     Tcl_HashEntry *hPtr;
1701     Node *nodePtr;
1702     Ck_Uid *tagPtr;
1703     Ck_Uid uid;
1704     int count;
1705
1706     /*
1707      * Initialize the search.
1708      */
1709
1710     nodePtr = NULL;
1711     searchPtr->treePtr = treePtr;
1712     searchPtr->searchOver = 0;
1713
1714     /*
1715      * Find the first matching node in one of several ways. If the tag
1716      * is a number then it selects the single node with the matching
1717      * identifier.
1718      */
1719
1720     if (isdigit((unsigned char) (*tag))) {
1721         char *end;
1722
1723         id = strtoul(tag, &end, 0);
1724         if (*end == 0) {
1725
1726             hPtr = Tcl_FindHashEntry(&treePtr->nodeTable, (char *) id);
1727             if (hPtr != NULL)
1728                 nodePtr = (Node *) Tcl_GetHashValue(hPtr);
1729             searchPtr->searchOver = 1;
1730             return nodePtr;
1731         }
1732     }
1733
1734     hPtr = Tcl_FirstHashEntry(&treePtr->nodeTable, &searchPtr->search);
1735     if (hPtr == NULL) {
1736         searchPtr->searchOver = 1;
1737         return nodePtr;
1738     }
1739     searchPtr->tag = uid = Ck_GetUid(tag);
1740     if (uid == allUid) {
1741
1742         /*
1743          * All nodes match.
1744          */
1745
1746         searchPtr->tag = NULL;
1747         return (Node *) Tcl_GetHashValue(hPtr);
1748     }
1749
1750     do {
1751         nodePtr = (Node *) Tcl_GetHashValue(hPtr);
1752         for (tagPtr = nodePtr->tagPtr, count = nodePtr->numTags;
1753              count > 0;
1754              tagPtr++, count--) {
1755             if (*tagPtr == uid) {
1756                 return nodePtr;
1757             }
1758         }
1759         hPtr = Tcl_NextHashEntry(&searchPtr->search);
1760     } while (hPtr != NULL);
1761
1762     searchPtr->searchOver = 1;
1763     return NULL;
1764 }
1765 \f
1766 /*
1767  *--------------------------------------------------------------
1768  *
1769  * NextNode --
1770  *
1771  *      This procedure returns successive nodes that match a given
1772  *      tag;  it should be called only after StartTagSearch has been
1773  *      used to begin a search.
1774  *
1775  * Results:
1776  *      The return value is a pointer to the next node that matches
1777  *      the tag specified to StartTagSearch, or NULL if no such
1778  *      node exists.  *SearchPtr is updated so that the next call
1779  *      to this procedure will return the next node.
1780  *
1781  * Side effects:
1782  *      None.
1783  *
1784  *--------------------------------------------------------------
1785  */
1786
1787 static Node *
1788 NextNode(searchPtr)
1789     TagSearch *searchPtr;               /* Record describing search in
1790                                          * progress. */
1791 {
1792     Node *nodePtr;
1793     Tcl_HashEntry *hPtr;
1794     int count;
1795     Ck_Uid uid;
1796     Ck_Uid *tagPtr;
1797
1798     if (searchPtr->searchOver)
1799         return NULL;
1800     
1801     hPtr = Tcl_NextHashEntry(&searchPtr->search);
1802     if (hPtr == NULL) {
1803         searchPtr->searchOver = 1;
1804         return NULL;
1805     }
1806
1807     /*
1808      * Handle special case of "all" search by returning next node.
1809      */
1810
1811     uid = searchPtr->tag;
1812     if (uid == NULL) {
1813         return (Node *) Tcl_GetHashValue(hPtr);
1814     }
1815
1816     /*
1817      * Look for a node with a particular tag.
1818      */
1819
1820     do {
1821         nodePtr = (Node *) Tcl_GetHashValue(hPtr);
1822         for (tagPtr = nodePtr->tagPtr, count = nodePtr->numTags;
1823              count > 0;
1824              tagPtr++, count--) {
1825             if (*tagPtr == uid) {
1826                 return nodePtr;
1827             }
1828         }
1829         hPtr = Tcl_NextHashEntry(&searchPtr->search);
1830     } while (hPtr != NULL);
1831
1832     searchPtr->searchOver = 1;
1833     return NULL;
1834 }
1835 \f
1836 /*
1837  *--------------------------------------------------------------
1838  *
1839  * DoNode --
1840  *
1841  *      This is a utility procedure called by FindNodes.  It
1842  *      either adds nodePtr's id to the result forming in interp,
1843  *      or it adds a new tag to nodePtr, depending on the value
1844  *      of tag.
1845  *
1846  * Results:
1847  *      None.
1848  *
1849  * Side effects:
1850  *      If tag is NULL then nodePtr's id is added as a list element
1851  *      to interp->result;  otherwise tag is added to nodePtr's
1852  *      list of tags.
1853  *
1854  *--------------------------------------------------------------
1855  */
1856
1857 static void
1858 DoNode(interp, nodePtr, tag)
1859     Tcl_Interp *interp;                 /* Interpreter in which to (possibly)
1860                                          * record node id. */
1861     Node *nodePtr;                      /* Node to (possibly) modify. */
1862     Ck_Uid tag;                         /* Tag to add to those already
1863                                          * present for node, or NULL. */
1864 {
1865     Ck_Uid *tagPtr;
1866     int count;
1867
1868     /*
1869      * Handle the "add-to-result" case and return, if appropriate.
1870      */
1871
1872     if (tag == NULL) {
1873         char msg[30];
1874
1875         sprintf(msg, "%d", nodePtr->id);
1876         Tcl_AppendElement(interp, msg);
1877         return;
1878     }
1879
1880     for (tagPtr = nodePtr->tagPtr, count = nodePtr->numTags;
1881             count > 0; tagPtr++, count--) {
1882         if (tag == *tagPtr)
1883             return;
1884     }
1885
1886     /*
1887      * Grow the tag space if there's no more room left in the current
1888      * block.
1889      */
1890
1891     if (nodePtr->tagSpace == nodePtr->numTags) {
1892         Ck_Uid *newTagPtr;
1893
1894         nodePtr->tagSpace += TAG_SPACE;
1895         newTagPtr = (Ck_Uid *) ckalloc((unsigned)
1896                 (nodePtr->tagSpace * sizeof (Ck_Uid)));
1897         memcpy(newTagPtr, nodePtr->tagPtr, nodePtr->numTags * sizeof (Ck_Uid));
1898         if (nodePtr->tagPtr != nodePtr->staticTagSpace) {
1899             ckfree((char *) nodePtr->tagPtr);
1900         }
1901         nodePtr->tagPtr = newTagPtr;
1902         tagPtr = &nodePtr->tagPtr[nodePtr->numTags];
1903     }
1904
1905     /*
1906      * Add in the new tag.
1907      */
1908
1909     *tagPtr = tag;
1910     nodePtr->numTags++;
1911
1912     if (tag == activeUid) {
1913         DeleteActiveTag(nodePtr->tree);
1914         nodePtr->tree->activeNode = nodePtr;
1915         TreeEventuallyRedraw(nodePtr->tree);
1916     } else if (tag == hideChildrenUid) {
1917         nodePtr->flags &= ~SHOWCHILDREN;
1918         RecomputeVisibleNodes(nodePtr->tree);
1919         TreeEventuallyRedraw(nodePtr->tree);
1920     }
1921 }
1922 \f
1923 /*
1924  *--------------------------------------------------------------
1925  *
1926  * FindNodes --
1927  *
1928  *      This procedure does all the work of implementing the
1929  *      "find" and "addtag" options of the tree widget command,
1930  *      which locate nodes that have certain features (location,
1931  *      tags).
1932  *
1933  * Results:
1934  *      A standard Tcl return value.  If newTag is NULL, then a
1935  *      list of ids from all the nodes that match argc/argv is
1936  *      returned in interp->result.  If newTag is NULL, then
1937  *      the normal interp->result is an empty string.  If an error
1938  *      occurs, then interp->result will hold an error message.
1939  *
1940  * Side effects:
1941  *      If newTag is non-NULL, then all the nodes that match the
1942  *      information in argc/argv have that tag added to their
1943  *      lists of tags.
1944  *
1945  *--------------------------------------------------------------
1946  */
1947
1948 static int
1949 FindNodes(interp, treePtr, argc, argv, newTag, cmdName, option)
1950     Tcl_Interp *interp;                 /* Interpreter for error reporting. */
1951     Tree *treePtr;                      /* Tree whose nodes are to be
1952                                          * searched. */
1953     int argc;                           /* Number of entries in argv.  Must be
1954                                          * greater than zero. */
1955     char **argv;                        /* Arguments that describe what items
1956                                          * to search for (see user doc on
1957                                          * "find" and "addtag" options). */
1958     char *newTag;                       /* If non-NULL, gives new tag to set
1959                                          * on all found items;  if NULL, then
1960                                          * ids of found items are returned
1961                                          * in interp->result. */
1962     char *cmdName;                      /* Name of original Tcl command, for
1963                                          * use in error messages. */
1964     char *option;                       /* For error messages:  gives option
1965                                          * from Tcl command and other stuff
1966                                          * up to what's in argc/argv. */
1967 {
1968     int c;
1969     size_t length;
1970     TagSearch search;
1971     Node *nodePtr;
1972     Ck_Uid uid;
1973
1974     if (newTag != NULL) {
1975         uid = Ck_GetUid(newTag);
1976     } else {
1977         uid = NULL;
1978     }
1979     c = argv[0][0];
1980     length = strlen(argv[0]);
1981     if ((c == 'a') && (strncmp(argv[0], "all", length) == 0)
1982             && (length >= 2)) {
1983         if (argc != 1) {
1984             Tcl_AppendResult(interp, "wrong # args:  must be \"",
1985                     cmdName, option, " all", (char *) NULL);
1986             return TCL_ERROR;
1987         }
1988         for (nodePtr = StartTagSearch(treePtr, "all", &search);
1989                 nodePtr != NULL; nodePtr = NextNode(&search)) {
1990             DoNode(interp, nodePtr, uid);
1991         }
1992     } else if ((c == 'n') && (strncmp(argv[0], "next", length) == 0) &&
1993         length > 2) {
1994
1995         if (argc != 2) {
1996             Tcl_AppendResult(interp, "wrong # args:  must be \"",
1997                     cmdName, option, " next tagOrId", (char *) NULL);
1998             return TCL_ERROR;
1999         }
2000         nodePtr = StartTagSearch(treePtr, argv[1], &search);
2001         if (nodePtr == NULL)
2002             nodePtr = treePtr->firstChild;
2003         if (nodePtr != NULL) {
2004             if (nodePtr->firstChild != NULL && (nodePtr->flags & SHOWCHILDREN))
2005                 nodePtr = nodePtr->firstChild;
2006             else if (nodePtr->next != NULL)
2007                 nodePtr = nodePtr->next;
2008             else {
2009                 while (nodePtr != NULL) {
2010                     nodePtr = nodePtr->parent;
2011                     if (nodePtr != NULL && nodePtr->next != NULL) {
2012                         nodePtr = nodePtr->next;
2013                         break;
2014                     }
2015                 }
2016             }
2017             if (nodePtr != NULL)
2018                 DoNode(interp, nodePtr, uid);
2019         }
2020     } else if ((c == 'n') && (strncmp(argv[0], "nearest", length) == 0) &&
2021         length > 2) {
2022         int x, y, count;
2023         Node *nextPtr = NULL;
2024
2025         if (argc != 3) {
2026             Tcl_AppendResult(interp, "wrong # args:  must be \"",
2027                     cmdName, option, " nearest x y", (char *) NULL);
2028             return TCL_ERROR;
2029         }
2030         if (Ck_GetCoord(interp, treePtr->winPtr, argv[1], &x) != TCL_OK ||
2031             Ck_GetCoord(interp, treePtr->winPtr, argv[2], &y) != TCL_OK)
2032             return TCL_ERROR;
2033         if (y >= treePtr->winPtr->height)
2034             y = treePtr->winPtr->height - 1;
2035
2036         count = 0;
2037         nodePtr = treePtr->topNode;
2038         while (nodePtr != NULL) {
2039             if (count == y)
2040                 break;
2041             if (nodePtr->parent == NULL)
2042                 nextPtr = nodePtr->next;
2043             if (nodePtr->firstChild != NULL && 
2044                 (nodePtr->flags & SHOWCHILDREN)) {
2045                 nodePtr = nodePtr->firstChild;
2046             } else if (nodePtr->next != NULL)
2047                 nodePtr = nodePtr->next;
2048             else {
2049                 while (nodePtr != NULL) {
2050                     nodePtr = nodePtr->parent;
2051                     if (nodePtr != NULL && nodePtr->next != NULL) {
2052                         nodePtr = nodePtr->next;
2053                         break;
2054                     }
2055                 }
2056                 if (nodePtr == NULL)
2057                     nodePtr = nextPtr;
2058             }
2059             count++;
2060         }
2061         if (nodePtr != NULL)
2062             sprintf(interp->result, "%d", nodePtr->id);
2063     } else if ((c == 'p') && (strncmp(argv[0], "prev", length) == 0)) {
2064         int done = 0;
2065         Node *parentPtr, *nextPtr;
2066
2067         if (argc != 2) {
2068             Tcl_AppendResult(interp, "wrong # args:  must be \"",
2069                     cmdName, option, " prev tagOrId", (char *) NULL);
2070             return TCL_ERROR;
2071         }
2072         nodePtr = StartTagSearch(treePtr, argv[1], &search);
2073         if (nodePtr == NULL)
2074             nodePtr = treePtr->firstChild;
2075         if (nodePtr != NULL) {
2076             parentPtr = nodePtr->parent;
2077             if (parentPtr != NULL) {
2078                 if (nodePtr == parentPtr->firstChild) {
2079                     nextPtr = parentPtr;
2080                     done = 1;
2081                 } else
2082                     nextPtr = parentPtr->firstChild;
2083             } else
2084                 parentPtr = nextPtr = treePtr->firstChild;
2085             if (!done) {
2086                 for (;nextPtr != NULL && nextPtr->next != nodePtr;
2087                      nextPtr = nextPtr->next) {
2088                     /* Empty loop body. */
2089                 }
2090                 if (nextPtr == NULL)
2091                     nextPtr = parentPtr->parent;
2092                 if (nextPtr == NULL)
2093                     nextPtr = treePtr->firstChild;
2094                 else {
2095                     while (nextPtr->lastChild != NULL &&
2096                            (nextPtr->flags & SHOWCHILDREN))
2097                         nextPtr = nextPtr->lastChild;
2098                 }
2099             }
2100             DoNode(interp, nextPtr, uid);
2101         }
2102     } else if ((c == 'w') && (strncmp(argv[0], "withtag", length) == 0)) {
2103         if (argc != 2) {
2104             Tcl_AppendResult(interp, "wrong # args:  must be \"",
2105                     cmdName, option, " withtag tagOrId", (char *) NULL);
2106             return TCL_ERROR;
2107         }
2108         for (nodePtr = StartTagSearch(treePtr, argv[1], &search);
2109                 nodePtr != NULL; nodePtr = NextNode(&search)) {
2110             DoNode(interp, nodePtr, uid);
2111         }
2112     } else  {
2113         Tcl_AppendResult(interp, "bad search command \"", argv[0],
2114                 "\": must be all, nearest, or withtag", (char *) NULL);
2115         return TCL_ERROR;
2116     }
2117     return TCL_OK;
2118 }
2119 \f
2120 /*
2121  *----------------------------------------------------------------------
2122  *
2123  * TreeUpdateVScrollbar --
2124  *
2125  *      This procedure is invoked whenever information has changed in
2126  *      a tree in a way that would invalidate a vertical scrollbar
2127  *      display.  If there is an associated scrollbar, then this command
2128  *      updates it by invoking a Tcl command.
2129  *
2130  * Results:
2131  *      None.
2132  *
2133  * Side effects:
2134  *      A Tcl command is invoked, and an additional command may be
2135  *      invoked to process errors in the command.
2136  *
2137  *----------------------------------------------------------------------
2138  */
2139
2140 static void
2141 TreeUpdateVScrollbar(treePtr)
2142     Tree *treePtr;              /* Information about widget. */
2143 {
2144     char string[100];
2145     double first, last;
2146     int result;
2147
2148     if (treePtr->yScrollCmd == NULL) {
2149         return;
2150     }
2151     if (treePtr->visibleNodes == 0) {
2152         first = 0.0;
2153         last = 1.0;
2154     } else {
2155         first = treePtr->topIndex / ((double) treePtr->visibleNodes);
2156         last = (treePtr->topIndex + treePtr->winPtr->height)
2157                 / ((double) treePtr->visibleNodes);
2158         if (last > 1.0) {
2159             last = 1.0;
2160         }
2161     }
2162     sprintf(string, " %g %g", first, last);
2163     result = Tcl_VarEval(treePtr->interp, treePtr->yScrollCmd, string,
2164             (char *) NULL);
2165     if (result != TCL_OK) {
2166         Tcl_AddErrorInfo(treePtr->interp,
2167                 "\n    (vertical scrolling command executed by tree)");
2168         Tk_BackgroundError(treePtr->interp);
2169     }
2170 }