1/*
2 * tkTreeItem.c --
3 *
4 *	This module implements items for treectrl widgets.
5 *
6 * Copyright (c) 2002-2009 Tim Baker
7 *
8 * RCS: @(#) $Id: tkTreeItem.c,v 1.113 2010/03/08 17:04:58 treectrl Exp $
9 */
10
11#include "tkTreeCtrl.h"
12
13typedef struct TreeItem_ TreeItem_;
14typedef struct Column Column;
15
16/*
17 * A data structure of the following type is kept for a single column in a
18 * single item.
19 */
20struct Column {
21    int cstate;		/* STATE_xxx flags manipulated with the
22			 * [item state forcolumn] command */
23    int span;		/* Number of tree-columns this column covers */
24    TreeStyle style;	/* Instance style. */
25    Column *next;	/* Column to the right of this one */
26};
27
28/*
29 * A data structure of the following type is kept for each item.
30 */
31struct TreeItem_ {
32    int id;		/* unique id */
33    int depth;		/* tree depth (-1 for the unique root item) */
34    int fixedHeight;	/* -height: desired height of this item (0 for
35			 * no-such-value) */
36    int numChildren;
37    int index;		/* "row" in flattened tree */
38    int indexVis;	/* visible "row" in flattened tree, -1 if hidden */
39    int state;		/* STATE_xxx flags */
40    TreeItem parent;
41    TreeItem firstChild;
42    TreeItem lastChild;
43    TreeItem prevSibling;
44    TreeItem nextSibling;
45    TreeItemDInfo dInfo; /* display info, or NULL */
46    TreeItemRInfo rInfo; /* range info, or NULL */
47    Column *columns;
48    int *spans;		/* 1 per tree-column. spans[N] is the column index of
49			 * the item-column displayed in column N. If this
50			 * item's columns all have a span of 1, this field
51			 * is NULL (unless it was previously allocated
52			 * because some spans were > 1). */
53    int spanAlloc;	/* Size of spans[]. */
54#define ITEM_FLAG_DELETED	0x0001 /* Item is being deleted */
55#define ITEM_FLAG_SPANS_SIMPLE	0x0002 /* All spans are 1 */
56#define ITEM_FLAG_SPANS_VALID	0x0004 /* Some spans are > 1, but we don't
57					* need to redo them. Also indicates
58					* we have an entry in
59					* TreeCtrl.itemSpansHash. */
60#define ITEM_FLAG_BUTTON	0x0008 /* -button true */
61#define ITEM_FLAG_BUTTON_AUTO	0x0010 /* -button auto */
62#define ITEM_FLAG_VISIBLE	0x0020 /* -visible */
63#define ITEM_FLAG_WRAP		0x0040 /* -wrap */
64    int flags;
65    TagInfo *tagInfo;	/* Tags. May be NULL. */
66};
67
68static CONST char *ItemUid = "Item", *ItemColumnUid = "ItemColumn";
69
70/*
71 * Macro to test whether an item is the unique root item
72 */
73#define IS_ROOT(i) ((i)->depth == -1)
74
75#define IS_ALL(i) ((i) == ITEM_ALL)
76
77#define IS_DELETED(i) (((i)->flags & ITEM_FLAG_DELETED) != 0)
78#define IS_VISIBLE(i) (((i)->flags & ITEM_FLAG_VISIBLE) != 0)
79#define IS_WRAP(i) (((i)->flags & ITEM_FLAG_WRAP) != 0)
80
81/*
82 * Flags returned by Tk_SetOptions() (see itemOptionSpecs below).
83 */
84#define ITEM_CONF_BUTTON		0x0001
85#define ITEM_CONF_SIZE			0x0002
86#define ITEM_CONF_VISIBLE		0x0004
87#define ITEM_CONF_WRAP			0x0008
88
89/*
90 * Information used for Item objv parsing.
91 */
92static Tk_OptionSpec itemOptionSpecs[] = {
93    {TK_OPTION_CUSTOM, "-button", (char *) NULL, (char *) NULL,
94     "0", -1, Tk_Offset(TreeItem_, flags),
95     0, (ClientData) NULL, ITEM_CONF_BUTTON},
96    {TK_OPTION_PIXELS, "-height", (char *) NULL, (char *) NULL,
97     (char *) NULL, -1, Tk_Offset(TreeItem_, fixedHeight),
98     TK_OPTION_NULL_OK, (ClientData) NULL, ITEM_CONF_SIZE},
99    {TK_OPTION_CUSTOM, "-tags", (char *) NULL, (char *) NULL,
100     (char *) NULL, -1, Tk_Offset(TreeItem_, tagInfo),
101     TK_OPTION_NULL_OK, (ClientData) &TreeCtrlCO_tagInfo, 0},
102    {TK_OPTION_CUSTOM, "-visible", (char *) NULL, (char *) NULL,
103     "1", -1, Tk_Offset(TreeItem_, flags),
104     0, (ClientData) NULL, ITEM_CONF_VISIBLE},
105    {TK_OPTION_CUSTOM, "-wrap", (char *) NULL, (char *) NULL,
106     "0", -1, Tk_Offset(TreeItem_, flags),
107     0, (ClientData) NULL, ITEM_CONF_WRAP},
108    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
109     (char *) NULL, 0, -1, 0, 0, 0}
110};
111
112/*
113 *----------------------------------------------------------------------
114 *
115 * Column_Alloc --
116 *
117 *	Allocate and initialize a new Column record.
118 *
119 * Results:
120 *	Pointer to allocated Column.
121 *
122 * Side effects:
123 *	Memory is allocated.
124 *
125 *----------------------------------------------------------------------
126 */
127
128static Column *
129Column_Alloc(
130    TreeCtrl *tree		/* Widget info. */
131    )
132{
133#ifdef ALLOC_HAX
134    Column *column = (Column *) TreeAlloc_Alloc(tree->allocData, ItemColumnUid,
135	    sizeof(Column));
136#else
137    Column *column = (Column *) ckalloc(sizeof(Column));
138#endif
139    memset(column, '\0', sizeof(Column));
140    column->span = 1;
141    return column;
142}
143
144/*
145 *----------------------------------------------------------------------
146 *
147 * TreeItemColumn_InvalidateSize --
148 *
149 *	Marks the needed height and width of the column as out-of-date.
150 *
151 * Results:
152 *	None.
153 *
154 * Side effects:
155 *	None.
156 *
157 *----------------------------------------------------------------------
158 */
159
160void
161TreeItemColumn_InvalidateSize(
162    TreeCtrl *tree,		/* Widget info. */
163    TreeItemColumn column_	/* Column token. */
164    )
165{
166}
167
168/*
169 *----------------------------------------------------------------------
170 *
171 * TreeItemColumn_NeededWidth --
172 *
173 *	Returns the requested width of a Column.
174 *
175 * Results:
176 *	If the Column has a style, the requested width of the style
177 *	is returned (a positive pixel value). Otherwise 0 is returned.
178 *
179 * Side effects:
180 *	None.
181 *
182 *----------------------------------------------------------------------
183 */
184
185int
186TreeItemColumn_NeededWidth(
187    TreeCtrl *tree,		/* Widget info. */
188    TreeItem item,		/* Item token. */
189    TreeItemColumn column_	/* Column token. */
190    )
191{
192    Column *self = (Column *) column_;
193
194    if (self->style != NULL)
195	return TreeStyle_NeededWidth(tree, self->style,
196		item->state | self->cstate);
197    return 0;
198}
199
200#ifdef EXPENSIVE_SPAN_WIDTH /* NOT USED */
201
202/*
203 * When a style spans 2 or more columns, all of the requested width goes
204 * to the first column in the span. Ideally the width needed by the style
205 * would be distributed over each column in the span. To be done properly,
206 * the visibility and fixed-width of each column needs to be considered.
207 *
208 * This routine does an okay job of calculating the requested width of a
209 * column in an item when spans are involved; however there is a lot of
210 * processing involved, and this routine is called for each column in
211 * a span separately (it would be better to calculate the width of all
212 * the columns in a span at the same time).
213 */
214
215int
216TreeItem_NeededWidthOfColumn(
217    TreeCtrl *tree,		/* Widget info. */
218    TreeItem item,		/* Item token. */
219    int _columnIndex		/* Column index. */
220    )
221{
222    TreeColumn treeColumn = tree->columns;
223    Column *column = item->columns;
224    int columnIndex = 0, itemColumnIndex = 0, span = 1;
225    int selfIndex = _columnIndex, fixedWidth = 0, width = 0;
226    int spanStart, spanEnd;
227    struct span {
228	Column *itemColumn;
229	int itemColumnIndex;
230	int fixedWidth;
231	int spanWidth;
232	int visible;
233    } staticSpans[STATIC_SIZE], *spans = staticSpans;
234
235    STATIC_ALLOC(spans, struct span, tree->columnCount);
236
237    while (treeColumn != NULL) {
238	spans[columnIndex].visible = TreeColumn_Visible(treeColumn);
239	if (--span == 0) {
240	    if (spans[columnIndex].visible)
241		span = column ? column->span : 1;
242	    else
243		span = 1;
244	    itemColumnIndex = columnIndex;
245	}
246	spans[columnIndex].itemColumn = column;
247	spans[columnIndex].itemColumnIndex = itemColumnIndex;
248	spans[columnIndex].spanWidth = 0;
249	spans[columnIndex].fixedWidth = TreeColumn_FixedWidth(treeColumn);
250	++columnIndex;
251	treeColumn = TreeColumn_Next(treeColumn);
252	if (column != NULL)
253	    column = column->next;
254    }
255
256    span = 0;
257    columnIndex = spanStart = spanEnd = spans[selfIndex].itemColumnIndex;
258    while (spans[columnIndex].itemColumnIndex == spanStart) {
259	if (spans[columnIndex].visible) {
260	    if (spans[columnIndex].fixedWidth >= 0) {
261		fixedWidth += spans[columnIndex].fixedWidth;
262		spans[columnIndex].spanWidth = spans[columnIndex].fixedWidth;
263	    } else
264		++span; /* number of visible columns in the span with no
265			 * fixed width */
266	}
267	spanEnd = columnIndex;
268	++columnIndex;
269	if (columnIndex == tree->columnCount)
270	    break;
271    }
272
273    column = spans[spanStart].itemColumn;
274    if (column != NULL && column->style != NULL) {
275	width = TreeStyle_NeededWidth(tree, column->style,
276		item->state | column->cstate);
277	if (width > fixedWidth && span > 0) {
278	    width -= fixedWidth;
279	    while (width > 0) {
280		int each = (width > span) ? width / span : 1;
281		columnIndex = spanStart;
282		while (columnIndex <= spanEnd) {
283		    if (spans[columnIndex].visible) {
284			if (spans[columnIndex].fixedWidth < 0) {
285			    spans[columnIndex].spanWidth += each;
286			    width -= each;
287			    if (width <= 0)
288				break;
289			}
290		    }
291		    ++columnIndex;
292		}
293	    }
294	};
295    }
296
297if (0)
298{
299    int i;
300    for (i = 0; i < tree->columnCount; i++) {
301	dbwin("%d,%d: itemColumn %p itemColumnIndex %d fixedWidth %d spanWidth %d\n",
302	    item->id, i, spans[i].itemColumn, spans[i].itemColumnIndex, spans[i].fixedWidth,
303	    spans[i].spanWidth);
304    }
305}
306    width = spans[selfIndex].spanWidth;
307    STATIC_FREE(spans, struct span, tree->columnCount);
308
309    return width;
310}
311
312#endif /* EXPENSIVE_SPAN_WIDTH */
313
314/*
315 *----------------------------------------------------------------------
316 *
317 * TreeItemColumn_GetStyle --
318 *
319 *	Returns the style assigned to a Column.
320 *
321 * Results:
322 *	Returns the style, or NULL.
323 *
324 * Side effects:
325 *	None.
326 *
327 *----------------------------------------------------------------------
328 */
329
330TreeStyle
331TreeItemColumn_GetStyle(
332    TreeCtrl *tree,		/* Widget info. */
333    TreeItemColumn column	/* Column token. */
334    )
335{
336    return ((Column *) column)->style;
337}
338
339/*
340 *----------------------------------------------------------------------
341 *
342 * TreeItemColumn_Index --
343 *
344 *	Return the 0-based index of a Column in an Item's linked list of
345 *	Columns.
346 *
347 * Results:
348 *	Integer index of the Column.
349 *
350 * Side effects:
351 *	Tcl_Panic() if the Column isn't found.
352 *
353 *----------------------------------------------------------------------
354 */
355
356int
357TreeItemColumn_Index(
358    TreeCtrl *tree,		/* Widget info. */
359    TreeItem item,		/* Item token. */
360    TreeItemColumn column_	/* Column token. */
361    )
362{
363    Column *column;
364    int i = 0;
365
366    column = item->columns;
367    while ((column != NULL) && (column != (Column *) column_)) {
368	i++;
369	column = column->next;
370    }
371    if (column == NULL)
372	panic("TreeItemColumn_Index: couldn't find the column\n");
373    return i;
374}
375
376/*
377 *----------------------------------------------------------------------
378 *
379 * TreeItemColumn_ForgetStyle --
380 *
381 *	Free the style assigned to a Column.
382 *
383 * Results:
384 *	Column has no style assigned anymore.
385 *
386 * Side effects:
387 *	Memory is freed.
388 *
389 *----------------------------------------------------------------------
390 */
391
392void
393TreeItemColumn_ForgetStyle(
394    TreeCtrl *tree,		/* Widget info. */
395    TreeItemColumn column_	/* Column token. */
396    )
397{
398    Column *self = (Column *) column_;
399
400    if (self->style != NULL) {
401	TreeStyle_FreeResources(tree, self->style);
402	self->style = NULL;
403    }
404}
405
406/*
407 *----------------------------------------------------------------------
408 *
409 * TreeItemColumn_GetNext --
410 *
411 *	Return the Column to the right of this one.
412 *
413 * Results:
414 *	The next Column in the linked list, or NULL.
415 *
416 * Side effects:
417 *	None.
418 *
419 *----------------------------------------------------------------------
420 */
421
422TreeItemColumn
423TreeItemColumn_GetNext(
424    TreeCtrl *tree,		/* Widget info. */
425    TreeItemColumn column	/* Column token. */
426    )
427{
428    return (TreeItemColumn) ((Column *) column)->next;
429}
430
431/*
432 *----------------------------------------------------------------------
433 *
434 * Column_FreeResources --
435 *
436 *	Free the style and memory associated with the given Column.
437 *
438 * Results:
439 *	The next Column in the linked list, or NULL.
440 *
441 * Side effects:
442 *	Memory is freed.
443 *
444 *----------------------------------------------------------------------
445 */
446
447static Column *
448Column_FreeResources(
449    TreeCtrl *tree,		/* Widget info. */
450    Column *self		/* Column to free. */
451    )
452{
453    Column *next = self->next;
454
455    if (self->style != NULL)
456	TreeStyle_FreeResources(tree, self->style);
457#ifdef ALLOC_HAX
458    TreeAlloc_Free(tree->allocData, ItemColumnUid, (char *) self, sizeof(Column));
459#else
460    WFREE(self, Column);
461#endif
462    return next;
463}
464
465/*
466 *----------------------------------------------------------------------
467 *
468 * Item_UpdateIndex --
469 *
470 *	Set the Item.depth, Item.index and Item.indexVis fields of the
471 *	given Item and all its descendants.
472 *
473 * Results:
474 *	None.
475 *
476 * Side effects:
477 *	The TreeCtrl.depth field may be updated to track the maximum
478 *	depth of all items.
479 *
480 *----------------------------------------------------------------------
481 */
482
483static void
484Item_UpdateIndex(TreeCtrl *tree,
485    TreeItem item,		/* Item to update. */
486    int *index,			/* New Item.index value for the item.
487				 * Value is incremented. */
488    int *indexVis		/* New Item.indexVis value for the item if
489				 * the item is ReallyVisible().
490				 * Value is incremented if the item is
491				 * ReallyVisible(). */
492    )
493{
494    TreeItem child, parent = item->parent;
495    int parentVis, parentOpen;
496
497    /* Also track max depth */
498    if (parent != NULL)
499	item->depth = parent->depth + 1;
500    else
501	item->depth = 0;
502    if (item->depth > tree->depth)
503	tree->depth = item->depth;
504
505    item->index = (*index)++;
506    item->indexVis = -1;
507    if (parent != NULL) {
508	parentOpen = (parent->state & STATE_OPEN) != 0;
509	parentVis = parent->indexVis != -1;
510	if (IS_ROOT(parent) && !tree->showRoot) {
511	    parentOpen = TRUE;
512	    parentVis = IS_VISIBLE(parent);
513	}
514	if (parentVis && parentOpen && IS_VISIBLE(item)) {
515	    item->indexVis = (*indexVis)++;
516	    if (IS_WRAP(item))
517		tree->itemWrapCount++;
518	}
519
520    }
521    child = item->firstChild;
522    while (child != NULL) {
523	Item_UpdateIndex(tree, child, index, indexVis);
524	child = child->nextSibling;
525    }
526}
527
528/*
529 *----------------------------------------------------------------------
530 *
531 * Tree_UpdateItemIndex --
532 *
533 *	Set the Item.depth, Item.index and Item.indexVis fields of the
534 *	every Item. Set TreeCtrl.depth to the maximum depth of all
535 *	Items. Set TreeCtrl.itemVisCount to the count of all visible
536 *	items.
537 *
538 *	Because this is slow we try not to do it until necessary.
539 *	The tree->updateIndex flags indicates when this is needed.
540 *
541 * Results:
542 *	None.
543 *
544 * Side effects:
545 *	None.
546 *
547 *----------------------------------------------------------------------
548 */
549
550void
551Tree_UpdateItemIndex(
552    TreeCtrl *tree		/* Widget info. */
553    )
554{
555    TreeItem item = tree->root;
556    int index = 1, indexVis = 0;
557
558    if (!tree->updateIndex)
559	return;
560
561    if (tree->debug.enable && tree->debug.data)
562	dbwin("Tree_UpdateItemIndex %s\n", Tk_PathName(tree->tkwin));
563
564    /* Also track max depth */
565    tree->depth = -1;
566
567    /* Count visible items with -wrap=true */
568    tree->itemWrapCount = 0;
569
570    item->index = 0;
571    item->indexVis = -1;
572    if (tree->showRoot && IS_VISIBLE(item)) {
573	item->indexVis = indexVis++;
574	if (IS_WRAP(item))
575	    tree->itemWrapCount++;
576    }
577    item = item->firstChild;
578    while (item != NULL) {
579	Item_UpdateIndex(tree, item, &index, &indexVis);
580	item = item->nextSibling;
581    }
582    tree->itemVisCount = indexVis;
583    tree->updateIndex = 0;
584}
585
586/*
587 *----------------------------------------------------------------------
588 *
589 * Item_Alloc --
590 *
591 *	Allocate an initialize a new Item record.
592 *
593 * Results:
594 *	Pointer to the allocated Item record.
595 *
596 * Side effects:
597 *	Memory is allocated.
598 *
599 *----------------------------------------------------------------------
600 */
601
602static TreeItem
603Item_Alloc(
604    TreeCtrl *tree		/* Widget info. */
605    )
606{
607#ifdef ALLOC_HAX
608    TreeItem item = (TreeItem) TreeAlloc_Alloc(tree->allocData, ItemUid, sizeof(TreeItem_));
609#else
610    TreeItem item = (TreeItem) ckalloc(sizeof(TreeItem_));
611#endif
612    memset(item, '\0', sizeof(TreeItem_));
613    if (Tk_InitOptions(tree->interp, (char *) item,
614		tree->itemOptionTable, tree->tkwin) != TCL_OK)
615	panic("Tk_InitOptions() failed in Item_Alloc()");
616    item->state =
617	STATE_OPEN |
618	STATE_ENABLED;
619    if (tree->gotFocus)
620	item->state |= STATE_FOCUS;
621    item->indexVis = -1;
622    /* In the typical case all spans are 1. */
623    item->flags |= ITEM_FLAG_SPANS_SIMPLE;
624    Tree_AddItem(tree, item);
625    return item;
626}
627
628/*
629 *----------------------------------------------------------------------
630 *
631 * Item_AllocRoot --
632 *
633 *	Allocate an initialize a new Item record for the root item.
634 *
635 * Results:
636 *	Pointer to the allocated Item record.
637 *
638 * Side effects:
639 *	Memory is allocated.
640 *
641 *----------------------------------------------------------------------
642 */
643
644static TreeItem
645Item_AllocRoot(
646    TreeCtrl *tree		/* Widget info. */
647    )
648{
649    TreeItem item;
650
651    item = Item_Alloc(tree);
652    item->depth = -1;
653    item->state |= STATE_ACTIVE;
654    return item;
655}
656
657/*
658 *----------------------------------------------------------------------
659 *
660 * TreeItem_GetFirstColumn --
661 *
662 *	Return the first Column record for an Item.
663 *
664 * Results:
665 *	Token for the column, or NULL.
666 *
667 * Side effects:
668 *	None.
669 *
670 *----------------------------------------------------------------------
671 */
672
673TreeItemColumn
674TreeItem_GetFirstColumn(
675    TreeCtrl *tree,		/* Widget info. */
676    TreeItem item		/* Item token. */
677    )
678{
679    return (TreeItemColumn) item->columns;
680}
681
682/*
683 *----------------------------------------------------------------------
684 *
685 * TreeItem_GetState --
686 *
687 *	Return the state flags for an Item.
688 *
689 * Results:
690 *	Bit mask of STATE_xxx flags.
691 *
692 * Side effects:
693 *	None.
694 *
695 *----------------------------------------------------------------------
696 */
697
698int
699TreeItem_GetState(
700    TreeCtrl *tree,		/* Widget info. */
701    TreeItem item		/* Item token. */
702    )
703{
704    return item->state;
705}
706
707/*
708 *----------------------------------------------------------------------
709 *
710 * Column_ChangeState --
711 *
712 *	Toggles zero or more STATE_xxx flags for a Column. If the
713 *	Column has a style assigned, its state may be changed.
714 *
715 * Results:
716 *	Bit mask of CS_LAYOUT and CS_DISPLAY flags, or zero if no
717 *	changes occurred.
718 *
719 * Side effects:
720 *	Display changes.
721 *
722 *----------------------------------------------------------------------
723 */
724
725static int
726Column_ChangeState(
727    TreeCtrl *tree,		/* Widget info. */
728    TreeItem item,		/* Item containing the column. */
729    Column *column,		/* Column to modify the state of. */
730    TreeColumn treeColumn,	/* Tree column. */
731    int stateOff,		/* STATE_xxx flags to turn off. */
732    int stateOn			/* STATE_xxx flags to turn on. */
733    )
734{
735    int cstate, state;
736    int sMask, iMask = 0;
737
738    cstate = column->cstate;
739    cstate &= ~stateOff;
740    cstate |= stateOn;
741
742    if (cstate == column->cstate)
743	return 0;
744
745    state = item->state | column->cstate;
746    state &= ~stateOff;
747    state |= stateOn;
748
749    if (column->style != NULL) {
750	sMask = TreeStyle_ChangeState(tree, column->style,
751		item->state | column->cstate, state);
752	if (sMask) {
753	    if (sMask & CS_LAYOUT)
754		Tree_InvalidateColumnWidth(tree, treeColumn);
755	    iMask |= sMask;
756	}
757
758	if (iMask & CS_LAYOUT) {
759	    TreeItem_InvalidateHeight(tree, item);
760	    TreeItemColumn_InvalidateSize(tree, (TreeItemColumn) column);
761	    Tree_FreeItemDInfo(tree, item, NULL);
762	    Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
763	} else if (iMask & CS_DISPLAY) {
764	    Tree_InvalidateItemDInfo(tree, treeColumn, item, NULL);
765	}
766    }
767
768    column->cstate = cstate;
769
770    return iMask;
771}
772
773/*
774 *----------------------------------------------------------------------
775 *
776 * TreeItem_ChangeState --
777 *
778 *	Toggles zero or more STATE_xxx flags for an Item. If the
779 *	Column has a style assigned, its state may be changed.
780 *
781 * Results:
782 *	Bit mask of CS_LAYOUT and CS_DISPLAY flags, or zero if no
783 *	changes occurred.
784 *
785 * Side effects:
786 *	Display changes.
787 *
788 *----------------------------------------------------------------------
789 */
790
791int
792TreeItem_ChangeState(
793    TreeCtrl *tree,		/* Widget info. */
794    TreeItem item,		/* Item token. */
795    int stateOff,		/* STATE_xxx flags to turn off. */
796    int stateOn			/* STATE_xxx flags to turn on. */
797    )
798{
799    Column *column;
800    TreeColumn treeColumn;
801    int columnIndex = 0, state, cstate;
802    int sMask, iMask = 0;
803
804    state = item->state;
805    state &= ~stateOff;
806    state |= stateOn;
807
808    if (state == item->state)
809	return 0;
810
811    treeColumn = tree->columns;
812    column = item->columns;
813    while (column != NULL) {
814	if (column->style != NULL) {
815	    cstate = item->state | column->cstate;
816	    cstate &= ~stateOff;
817	    cstate |= stateOn;
818	    sMask = TreeStyle_ChangeState(tree, column->style,
819		    item->state | column->cstate, cstate);
820	    if (sMask) {
821		if (sMask & CS_LAYOUT) {
822		    Tree_InvalidateColumnWidth(tree, treeColumn);
823		    TreeItemColumn_InvalidateSize(tree, (TreeItemColumn) column);
824		} else if (sMask & CS_DISPLAY) {
825		    Tree_InvalidateItemDInfo(tree, treeColumn, item, NULL);
826		}
827		iMask |= sMask;
828	    }
829	}
830	columnIndex++;
831	column = column->next;
832	treeColumn = TreeColumn_Next(treeColumn);
833    }
834
835    /* This item has a button */
836    if (TreeItem_HasButton(tree, item)) {
837
838	Tk_Image image1, image2;
839	Pixmap bitmap1, bitmap2;
840	/* NOTE: These next 2 lines must have 'static' to work around a
841	 * Microsoft compiler optimization bug. */
842	static int butOpen, butClosed;
843	static int themeOpen, themeClosed;
844	int w1, h1, w2, h2;
845	void *ptr1 = NULL, *ptr2 = NULL;
846
847	/*
848	 * Compare the image/bitmap/theme/xlib button for the old state
849	 * to the image/bitmap/theme/xlib button for the new state. Figure
850	 * out if the size or appearance has changed.
851	 */
852
853	/* image > bitmap > theme > draw */
854	image1 = PerStateImage_ForState(tree, &tree->buttonImage, item->state, NULL);
855	if (image1 != NULL) {
856	    Tk_SizeOfImage(image1, &w1, &h1);
857	    ptr1 = image1;
858	}
859	if (ptr1 == NULL) {
860	    bitmap1 = PerStateBitmap_ForState(tree, &tree->buttonBitmap, item->state, NULL);
861	    if (bitmap1 != None) {
862		Tk_SizeOfBitmap(tree->display, bitmap1, &w1, &h1);
863		ptr1 = (void *) bitmap1;
864	    }
865	}
866	if (ptr1 == NULL) {
867	    if (tree->useTheme &&
868		TreeTheme_GetButtonSize(tree, Tk_WindowId(tree->tkwin),
869		(item->state & STATE_OPEN) != 0, &w1, &h1) == TCL_OK) {
870		ptr1 = (item->state & STATE_OPEN) ? &themeOpen : &themeClosed;
871	    }
872	}
873	if (ptr1 == NULL) {
874	    w1 = h1 = tree->buttonSize;
875	    ptr1 = (item->state & STATE_OPEN) ? &butOpen : &butClosed;
876	}
877
878	/* image > bitmap > theme > draw */
879	image2 = PerStateImage_ForState(tree, &tree->buttonImage, state, NULL);
880	if (image2 != NULL) {
881	    Tk_SizeOfImage(image2, &w2, &h2);
882	    ptr2 = image2;
883	}
884	if (ptr2 == NULL) {
885	    bitmap2 = PerStateBitmap_ForState(tree, &tree->buttonBitmap, state, NULL);
886	    if (bitmap2 != None) {
887		Tk_SizeOfBitmap(tree->display, bitmap2, &w2, &h2);
888		ptr2 = (void *) bitmap2;
889	    }
890	}
891	if (ptr2 == NULL) {
892	    if (tree->useTheme &&
893		TreeTheme_GetButtonSize(tree, Tk_WindowId(tree->tkwin),
894		(state & STATE_OPEN) != 0, &w2, &h2) == TCL_OK) {
895		ptr2 = (state & STATE_OPEN) ? &themeOpen : &themeClosed;
896	    }
897	}
898	if (ptr2 == NULL) {
899	    w2 = h2 = tree->buttonSize;
900	    ptr2 = (state & STATE_OPEN) ? &butOpen : &butClosed;
901	}
902
903	if ((w1 != w2) || (h1 != h2)) {
904	    iMask |= CS_LAYOUT | CS_DISPLAY;
905	} else if (ptr1 != ptr2) {
906	    iMask |= CS_DISPLAY;
907	    if (tree->columnTree != NULL)
908		Tree_InvalidateItemDInfo(tree, tree->columnTree, item, NULL);
909	}
910    }
911
912    if (iMask & CS_LAYOUT) {
913	TreeItem_InvalidateHeight(tree, item);
914	Tree_FreeItemDInfo(tree, item, NULL);
915	Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
916    }
917
918    item->state = state;
919
920    return iMask;
921}
922
923/*
924 *----------------------------------------------------------------------
925 *
926 * TreeItem_UndefineState --
927 *
928 *	Clear a STATE_xxx flag in an Item and its Columns. This is
929 *	called when a user-defined state is undefined via the
930 *	[state undefine] widget command.
931 *
932 * Results:
933 *	None.
934 *
935 * Side effects:
936 *	None.
937 *
938 *----------------------------------------------------------------------
939 */
940
941void
942TreeItem_UndefineState(
943    TreeCtrl *tree,		/* Widget info. */
944    TreeItem item,		/* Item token. */
945    int state			/* STATE_xxx flag that is undefined. */
946    )
947{
948    Column *column = item->columns;
949
950    while (column != NULL) {
951	column->cstate &= ~state;
952	column = column->next;
953    }
954
955    item->state &= ~state;
956}
957
958/*
959 *----------------------------------------------------------------------
960 *
961 * TreeItem_HasButton --
962 *
963 *	Determine whether an item should have a button displayed next to
964 *	it. This considers the value of the item option -button as well
965 *	as the treectrl options -showbuttons, -showrootchildbuttons and
966 *	-showrootbutton.
967 *
968 * Results:
969 *	None.
970 *
971 * Side effects:
972 *	None.
973 *
974 *----------------------------------------------------------------------
975 */
976
977int
978TreeItem_HasButton(
979    TreeCtrl *tree,		/* Widget info. */
980    TreeItem item		/* Item token. */
981    )
982{
983    if (!tree->showButtons || (IS_ROOT(item) && !tree->showRootButton))
984	return 0;
985    if (item->parent == tree->root && !tree->showRootChildButtons)
986	return 0;
987    if (item->flags & ITEM_FLAG_BUTTON)
988	return 1;
989    if (item->flags & ITEM_FLAG_BUTTON_AUTO) {
990	TreeItem child = item->firstChild;
991	while (child != NULL) {
992	    if (IS_VISIBLE(child))
993		return 1;
994	    child = child->nextSibling;
995	}
996    }
997    return 0;
998}
999
1000/*
1001 *----------------------------------------------------------------------
1002 *
1003 * TreeItem_GetDepth --
1004 *
1005 *	Return the depth of an Item.
1006 *
1007 * Results:
1008 *	Integer >= -1. (-1 for the root)
1009 *
1010 * Side effects:
1011 *	None.
1012 *
1013 *----------------------------------------------------------------------
1014 */
1015
1016int
1017TreeItem_GetDepth(
1018    TreeCtrl *tree,		/* Widget info. */
1019    TreeItem item		/* Item token. */
1020    )
1021{
1022#if 0
1023    Tree_UpdateItemIndex(tree);
1024#endif
1025    return item->depth;
1026}
1027
1028/*
1029 *----------------------------------------------------------------------
1030 *
1031 * TreeItem_GetID --
1032 *
1033 *	Return the unique ID of an Item.
1034 *
1035 * Results:
1036 *	Integer >= 0. (0 for the root)
1037 *
1038 * Side effects:
1039 *	None.
1040 *
1041 *----------------------------------------------------------------------
1042 */
1043
1044int
1045TreeItem_GetID(
1046    TreeCtrl *tree,		/* Widget info. */
1047    TreeItem item		/* Item token. */
1048    )
1049{
1050    return item->id;
1051}
1052
1053/*
1054 *----------------------------------------------------------------------
1055 *
1056 * TreeItem_SetID --
1057 *
1058 *	Set the unique ID of an Item. This is called when the item
1059 *	is created.
1060 *
1061 * Results:
1062 *	The given ID.
1063 *
1064 * Side effects:
1065 *	None.
1066 *
1067 *----------------------------------------------------------------------
1068 */
1069
1070int
1071TreeItem_SetID(
1072    TreeCtrl *tree,		/* Widget info. */
1073    TreeItem item,		/* Item token. */
1074    int id			/* Unique ID for the item. */
1075    )
1076{
1077    return item->id = id;
1078}
1079
1080/*
1081 *----------------------------------------------------------------------
1082 *
1083 * TreeItem_GetEnabled --
1084 *
1085 *	Return whether an Item is enabled or not.
1086 *
1087 * Results:
1088 *	TRUE if the item is enabled, FALSE otherwise.
1089 *
1090 * Side effects:
1091 *	None.
1092 *
1093 *----------------------------------------------------------------------
1094 */
1095
1096int
1097TreeItem_GetEnabled(
1098    TreeCtrl *tree,		/* Widget info. */
1099    TreeItem item		/* Item token. */
1100    )
1101{
1102    return (item->state & STATE_ENABLED) != 0;
1103}
1104
1105/*
1106 *----------------------------------------------------------------------
1107 *
1108 * TreeItem_GetSelected --
1109 *
1110 *	Return whether an Item is selected or not.
1111 *
1112 * Results:
1113 *	TRUE if the item is part of the selection, FALSE otherwise.
1114 *
1115 * Side effects:
1116 *	None.
1117 *
1118 *----------------------------------------------------------------------
1119 */
1120
1121int
1122TreeItem_GetSelected(
1123    TreeCtrl *tree,		/* Widget info. */
1124    TreeItem item		/* Item token. */
1125    )
1126{
1127    return (item->state & STATE_SELECTED) != 0;
1128}
1129
1130/*
1131 *----------------------------------------------------------------------
1132 *
1133 * TreeItem_GetParent --
1134 *
1135 *	Return the parent of an Item.
1136 *
1137 * Results:
1138 *	Token for parent Item, or NULL.
1139 *
1140 * Side effects:
1141 *	None.
1142 *
1143 *----------------------------------------------------------------------
1144 */
1145
1146TreeItem
1147TreeItem_GetParent(
1148    TreeCtrl *tree,		/* Widget info. */
1149    TreeItem item		/* Item token. */
1150    )
1151{
1152    return item->parent;
1153}
1154
1155/*
1156 *----------------------------------------------------------------------
1157 *
1158 * TreeItem_GetWrap --
1159 *
1160 *	Return whether an Item -wrap is TRUE or FALSE.
1161 *
1162 * Results:
1163 *	TRUE if the item should wrap, FALSE otherwise.
1164 *
1165 * Side effects:
1166 *	None.
1167 *
1168 *----------------------------------------------------------------------
1169 */
1170
1171int
1172TreeItem_GetWrap(
1173    TreeCtrl *tree,		/* Widget info. */
1174    TreeItem item		/* Item token. */
1175    )
1176{
1177    return (item->flags & ITEM_FLAG_WRAP) != 0;
1178}
1179
1180/*
1181 *----------------------------------------------------------------------
1182 *
1183 * TreeItem_GetNextSibling --
1184 *
1185 *	Return the next sibling of an Item.
1186 *
1187 * Results:
1188 *	Token for next sibling Item, or NULL.
1189 *
1190 * Side effects:
1191 *	None.
1192 *
1193 *----------------------------------------------------------------------
1194 */
1195
1196TreeItem
1197TreeItem_GetNextSibling(
1198    TreeCtrl *tree,		/* Widget info. */
1199    TreeItem item		/* Item token. */
1200    )
1201{
1202    return item->nextSibling;
1203}
1204
1205/*
1206 *----------------------------------------------------------------------
1207 *
1208 * TreeItem_NextSiblingVisible --
1209 *
1210 *	Find a following sibling that is ReallyVisible().
1211 *
1212 * Results:
1213 *	Token for a sibling Item, or NULL.
1214 *
1215 * Side effects:
1216 *	None.
1217 *
1218 *----------------------------------------------------------------------
1219 */
1220
1221TreeItem
1222TreeItem_NextSiblingVisible(
1223    TreeCtrl *tree,		/* Widget info. */
1224    TreeItem item		/* Item token. */
1225    )
1226{
1227    item = TreeItem_GetNextSibling(tree, item);
1228    while (item != NULL) {
1229	if (TreeItem_ReallyVisible(tree, item))
1230	    return item;
1231	item = TreeItem_GetNextSibling(tree, item);
1232    }
1233    return NULL;
1234}
1235
1236/*
1237 *----------------------------------------------------------------------
1238 *
1239 * TreeItem_SetDInfo --
1240 *
1241 *	Store a display-info token in an Item. Called by the display
1242 *	code.
1243 *
1244 * Results:
1245 *	None.
1246 *
1247 * Side effects:
1248 *	None.
1249 *
1250 *----------------------------------------------------------------------
1251 */
1252
1253void
1254TreeItem_SetDInfo(
1255    TreeCtrl *tree,		/* Widget info. */
1256    TreeItem item,		/* Item token. */
1257    TreeItemDInfo dInfo		/* Display-info token. */
1258    )
1259{
1260    item->dInfo = dInfo;
1261}
1262
1263/*
1264 *----------------------------------------------------------------------
1265 *
1266 * TreeItem_GetDInfo --
1267 *
1268 *	Return the display-info token of an Item. Called by the display
1269 *	code.
1270 *
1271 * Results:
1272 *	The display-info token or NULL.
1273 *
1274 * Side effects:
1275 *	None.
1276 *
1277 *----------------------------------------------------------------------
1278 */
1279
1280TreeItemDInfo
1281TreeItem_GetDInfo(
1282    TreeCtrl *tree,		/* Widget info. */
1283    TreeItem item		/* Item token. */
1284    )
1285{
1286    return item->dInfo;
1287}
1288
1289/*
1290 *----------------------------------------------------------------------
1291 *
1292 * TreeItem_SetRInfo --
1293 *
1294 *	Store a range-info token in an Item. Called by the display
1295 *	code.
1296 *
1297 * Results:
1298 *	None.
1299 *
1300 * Side effects:
1301 *	None.
1302 *
1303 *----------------------------------------------------------------------
1304 */
1305
1306void
1307TreeItem_SetRInfo(
1308    TreeCtrl *tree,		/* Widget info. */
1309    TreeItem item,		/* Item token. */
1310    TreeItemRInfo rInfo		/* Range-info token */
1311    )
1312{
1313    item->rInfo = rInfo;
1314}
1315
1316/*
1317 *----------------------------------------------------------------------
1318 *
1319 * TreeItem_GetRInfo --
1320 *
1321 *	Return the range-info token of an Item. Called by the display
1322 *	code.
1323 *
1324 * Results:
1325 *	The range-info token or NULL.
1326 *
1327 * Side effects:
1328 *	None.
1329 *
1330 *----------------------------------------------------------------------
1331 */
1332
1333TreeItemRInfo
1334TreeItem_GetRInfo(
1335    TreeCtrl *tree,		/* Widget info. */
1336    TreeItem item		/* Item token. */
1337    )
1338{
1339    return item->rInfo;
1340}
1341
1342/*
1343 *----------------------------------------------------------------------
1344 *
1345 * TreeItem_Next --
1346 *
1347 *	Return the Item after the given one.
1348 *
1349 * Results:
1350 *	Result will be:
1351 *	  a) the first child, or
1352 *	  b) the next sibling, or
1353 *	  c) the next sibling of an ancestor, or
1354 *	  d) NULL if no item follows this one
1355 *
1356 * Side effects:
1357 *	None.
1358 *
1359 *----------------------------------------------------------------------
1360 */
1361
1362TreeItem
1363TreeItem_Next(
1364    TreeCtrl *tree,		/* Widget info. */
1365    TreeItem item		/* Item token. */
1366    )
1367{
1368    if (item->firstChild != NULL)
1369	return item->firstChild;
1370    if (item->nextSibling != NULL)
1371	return item->nextSibling;
1372    while (1) {
1373	item = item->parent;
1374	if (item == NULL)
1375	    break;
1376	if (item->nextSibling != NULL)
1377	    return item->nextSibling;
1378    }
1379    return NULL;
1380}
1381
1382/*
1383 *----------------------------------------------------------------------
1384 *
1385 * TreeItem_NextVisible --
1386 *
1387 *	Return the ReallyVisible() Item after the given one.
1388 *
1389 * Results:
1390 *	Result will be:
1391 *	  a) the first ReallyVisible() child, or
1392 *	  b) the next ReallyVisible() sibling, or
1393 *	  c) the next ReallyVisible() sibling of an ancestor, or
1394 *	  d) NULL if no ReallyVisible() item follows this one
1395 *
1396 * Side effects:
1397 *	None.
1398 *
1399 *----------------------------------------------------------------------
1400 */
1401
1402TreeItem
1403TreeItem_NextVisible(
1404    TreeCtrl *tree,		/* Widget info. */
1405    TreeItem item		/* Item token. */
1406    )
1407{
1408    item = TreeItem_Next(tree, item);
1409    while (item != NULL) {
1410	if (TreeItem_ReallyVisible(tree, item))
1411	    return item;
1412	item = TreeItem_Next(tree, item);
1413    }
1414    return NULL;
1415}
1416
1417/*
1418 *----------------------------------------------------------------------
1419 *
1420 * TreeItem_Prev --
1421 *
1422 *	Return the Item before the given one.
1423 *
1424 * Results:
1425 *	Result will be:
1426 *	  a) the last descendant of the previous sibling, or
1427 *	  b) the parent, or
1428 *	  c) NULL if the given Item is the root
1429 *
1430 * Side effects:
1431 *	None.
1432 *
1433 *----------------------------------------------------------------------
1434 */
1435
1436TreeItem
1437TreeItem_Prev(
1438    TreeCtrl *tree,		/* Widget info. */
1439    TreeItem item		/* Item token. */
1440    )
1441{
1442    TreeItem walk;
1443
1444    if (item->parent == NULL) /* root */
1445	return NULL;
1446    walk = item->parent;
1447    if (item->prevSibling) {
1448	walk = item->prevSibling;
1449	while (walk->lastChild != NULL)
1450	    walk = walk->lastChild;
1451    }
1452    return walk;
1453}
1454
1455/*
1456 *----------------------------------------------------------------------
1457 *
1458 * TreeItem_PrevVisible --
1459 *
1460 *	Return the ReallyVisible() Item before the given one.
1461 *
1462 * Results:
1463 *	Result will be:
1464 *	  a) the last descendant of the previous sibling, or
1465 *	  b) the parent, or
1466 *	  c) NULL if the given Item is the root
1467 *
1468 * Side effects:
1469 *	None.
1470 *
1471 *----------------------------------------------------------------------
1472 */
1473
1474TreeItem
1475TreeItem_PrevVisible(
1476    TreeCtrl *tree,		/* Widget info. */
1477    TreeItem item		/* Item token. */
1478    )
1479{
1480    item = TreeItem_Prev(tree, item);
1481    while (item != NULL) {
1482	if (TreeItem_ReallyVisible(tree, item))
1483	    return item;
1484	item = TreeItem_Prev(tree, item);
1485    }
1486    return NULL;
1487}
1488
1489/*
1490 *----------------------------------------------------------------------
1491 *
1492 * TreeItem_ToIndex --
1493 *
1494 *	Return Item.index and Item.indexVis.
1495 *
1496 * Results:
1497 *	The zero-based indexes of the Item.
1498 *
1499 * Side effects:
1500 *	Will recalculate the indexes of every item if needed.
1501 *
1502 *----------------------------------------------------------------------
1503 */
1504
1505void
1506TreeItem_ToIndex(
1507    TreeCtrl *tree,		/* Widget info. */
1508    TreeItem item,		/* Item token. */
1509    int *index,			/* Returned Item.index, may be NULL */
1510    int *indexVis		/* Returned Item.indexVis, may be NULL */
1511    )
1512{
1513    Tree_UpdateItemIndex(tree);
1514    if (index != NULL) (*index) = item->index;
1515    if (indexVis != NULL) (*indexVis) = item->indexVis;
1516}
1517
1518/*
1519 *----------------------------------------------------------------------
1520 *
1521 * ItemHasTag --
1522 *
1523 *	Checks whether an item has a certain tag.
1524 *
1525 * Results:
1526 *	Returns TRUE if the item has the given tag.
1527 *
1528 * Side effects:
1529 *	None.
1530 *
1531 *----------------------------------------------------------------------
1532 */
1533
1534static int
1535ItemHasTag(
1536    TreeItem item,		/* The item to test. */
1537    Tk_Uid tag			/* Tag to look for. */
1538    )
1539{
1540    TagInfo *tagInfo = item->tagInfo;
1541    Tk_Uid *tagPtr;
1542    int count;
1543
1544    if (tagInfo == NULL)
1545	return 0;
1546
1547    for (tagPtr = tagInfo->tagPtr, count = tagInfo->numTags;
1548	count > 0; tagPtr++, count--) {
1549	if (*tagPtr == tag) {
1550	    return 1;
1551	}
1552    }
1553    return 0;
1554}
1555
1556typedef struct Qualifiers {
1557    TreeCtrl *tree;
1558    int visible;		/* 1 if the item must be ReallyVisible(),
1559				   0 if the item must not be ReallyVisible(),
1560				   -1 for unspecified. */
1561    int states[3];		/* Item states that must be on or off. */
1562    TagExpr expr;		/* Tag expression. */
1563    int exprOK;			/* TRUE if expr is valid. */
1564    int depth;			/* >= 0 for depth, -1 for unspecified */
1565    Tk_Uid tag;			/* Tag (without operators) or NULL. */
1566} Qualifiers;
1567
1568/*
1569 *----------------------------------------------------------------------
1570 *
1571 * Qualifiers_Init --
1572 *
1573 *	Helper routine for TreeItem_FromObj.
1574 *
1575 * Results:
1576 *	None.
1577 *
1578 * Side effects:
1579 *	None.
1580 *
1581 *----------------------------------------------------------------------
1582 */
1583
1584static void
1585Qualifiers_Init(
1586    TreeCtrl *tree,		/* Widget info. */
1587    Qualifiers *q		/* Out: Initialized qualifiers. */
1588    )
1589{
1590    q->tree = tree;
1591    q->visible = -1;
1592    q->states[0] = q->states[1] = q->states[2] = 0;
1593    q->exprOK = FALSE;
1594    q->depth = -1;
1595    q->tag = NULL;
1596}
1597
1598/*
1599 *----------------------------------------------------------------------
1600 *
1601 * Qualifiers_Scan --
1602 *
1603 *	Helper routine for TreeItem_FromObj.
1604 *
1605 * Results:
1606 *	TCL_OK or TCL_ERROR.
1607 *
1608 * Side effects:
1609 *	None.
1610 *
1611 *----------------------------------------------------------------------
1612 */
1613
1614static int
1615Qualifiers_Scan(
1616    Qualifiers *q,		/* Must call Qualifiers_Init first,
1617				 * and Qualifiers_Free if result is TCL_OK. */
1618    int objc,			/* Number of arguments. */
1619    Tcl_Obj **objv,		/* Argument values. */
1620    int startIndex,		/* First objv[] index to look at. */
1621    int *argsUsed		/* Out: number of objv[] used. */
1622    )
1623{
1624    TreeCtrl *tree = q->tree;
1625    Tcl_Interp *interp = tree->interp;
1626    int qual, j = startIndex;
1627
1628    static CONST char *qualifiers[] = {
1629	"depth", "state", "tag", "visible", "!visible", NULL
1630    };
1631    enum qualEnum {
1632	QUAL_DEPTH, QUAL_STATE, QUAL_TAG, QUAL_VISIBLE, QUAL_NOT_VISIBLE
1633    };
1634    /* Number of arguments used by qualifiers[]. */
1635    static int qualArgs[] = {
1636	2, 2, 2, 1, 1
1637    };
1638
1639    *argsUsed = 0;
1640
1641    for (; j < objc; ) {
1642	if (Tcl_GetIndexFromObj(NULL, objv[j], qualifiers, NULL, 0,
1643		&qual) != TCL_OK)
1644	    break;
1645	if (objc - j < qualArgs[qual]) {
1646	    Tcl_AppendResult(interp, "missing arguments to \"",
1647		    Tcl_GetString(objv[j]), "\" qualifier", NULL);
1648	    goto errorExit;
1649	}
1650	switch ((enum qualEnum) qual) {
1651	    case QUAL_DEPTH: {
1652		if (Tcl_GetIntFromObj(interp, objv[j + 1], &q->depth) != TCL_OK)
1653		    goto errorExit;
1654		break;
1655	    }
1656	    case QUAL_STATE: {
1657		if (Tree_StateFromListObj(tree, objv[j + 1], q->states,
1658			SFO_NOT_TOGGLE) != TCL_OK)
1659		    goto errorExit;
1660		break;
1661	    }
1662	    case QUAL_TAG: {
1663		if (tree->itemTagExpr) {
1664		    if (q->exprOK)
1665			TagExpr_Free(&q->expr);
1666		    if (TagExpr_Init(tree, objv[j + 1], &q->expr) != TCL_OK)
1667			return TCL_ERROR;
1668		    q->exprOK = TRUE;
1669		} else {
1670		    q->tag = Tk_GetUid(Tcl_GetString(objv[j + 1]));
1671		}
1672		break;
1673	    }
1674	    case QUAL_VISIBLE: {
1675		q->visible = 1;
1676		break;
1677	    }
1678	    case QUAL_NOT_VISIBLE: {
1679		q->visible = 0;
1680		break;
1681	    }
1682	}
1683	*argsUsed += qualArgs[qual];
1684	j += qualArgs[qual];
1685    }
1686    return TCL_OK;
1687errorExit:
1688    if (q->exprOK)
1689	TagExpr_Free(&q->expr);
1690    return TCL_ERROR;
1691}
1692
1693/*
1694 *----------------------------------------------------------------------
1695 *
1696 * Qualifies --
1697 *
1698 *	Helper routine for TreeItem_FromObj.
1699 *
1700 * Results:
1701 *	Returns TRUE if the item meets the given criteria.
1702 *
1703 * Side effects:
1704 *	None.
1705 *
1706 *----------------------------------------------------------------------
1707 */
1708
1709static int
1710Qualifies(
1711    Qualifiers *q,		/* Qualifiers to check. */
1712    TreeItem item		/* The item to test. May be NULL. */
1713    )
1714{
1715    TreeCtrl *tree = q->tree;
1716
1717    /* Note: if the item is NULL it is a "match" because we have run
1718     * out of items to check. */
1719    if (item == NULL)
1720	return 1;
1721    if ((q->visible == 1) && !TreeItem_ReallyVisible(tree, item))
1722	return 0;
1723    else if ((q->visible == 0) && TreeItem_ReallyVisible(tree, (TreeItem) item))
1724	return 0;
1725    if (q->states[STATE_OP_OFF] & item->state)
1726	return 0;
1727    if ((q->states[STATE_OP_ON] & item->state) != q->states[STATE_OP_ON])
1728	return 0;
1729    if (q->exprOK && !TagExpr_Eval(&q->expr, item->tagInfo))
1730	return 0;
1731    if ((q->depth >= 0) && (item->depth + 1 != q->depth))
1732	return 0;
1733    if ((q->tag != NULL) && !ItemHasTag(item, q->tag))
1734	return 0;
1735    return 1;
1736}
1737
1738/*
1739 *----------------------------------------------------------------------
1740 *
1741 * Qualifiers_Free --
1742 *
1743 *	Helper routine for TreeItem_FromObj.
1744 *
1745 * Results:
1746 *	None.
1747 *
1748 * Side effects:
1749 *	None.
1750 *
1751 *----------------------------------------------------------------------
1752 */
1753
1754static void
1755Qualifiers_Free(
1756    Qualifiers *q		/* Out: Initialized qualifiers. */
1757    )
1758{
1759    if (q->exprOK)
1760	TagExpr_Free(&q->expr);
1761}
1762
1763/*
1764 *----------------------------------------------------------------------
1765 *
1766 * TreeItemList_FromObj --
1767 *
1768 *	Parse a Tcl_Obj item description to get a list of items.
1769 *
1770 *   -- returning a single item --
1771 *   "active MODIFIERS"
1772 *   "anchor MODIFIERS"
1773 *   "nearest x y MODIFIERS"
1774 *   "root MODIFIERS"
1775 *   "first QUALIFIERS MODIFIERS"
1776 *   "last QUALIFIERS MODIFIERS"
1777 *   "end QUALIFIERS MODIFIERS"
1778 *   "rnc row col MODIFIERS"
1779 *   "ID MODIFIERS"
1780 *   -- returning multiple items --
1781 *   all QUALIFIERS
1782 *   QUALIFIERS (like "all QUALIFIERS")
1783 *   list listOfItemDescs
1784 *   range QUALIFIERS
1785 *   tag tagExpr QUALIFIERS
1786 *   "TAG-EXPR QUALFIERS"
1787 *
1788 *   MODIFIERS:
1789 *   -- returning a single item --
1790 *   above
1791 *   below
1792 *   left
1793 *   right
1794 *   top
1795 *   bottom
1796 *   leftmost
1797 *   rightmost
1798 *   next QUALIFIERS
1799 *   prev QUALIFIERS
1800 *   parent
1801 *   firstchild QUALIFIERS
1802 *   lastchild QUALIFIERS
1803 *   child N|end?-offset? QUALIFIERS
1804 *   nextsibling QUALIFIERS
1805 *   prevsibling QUALIFIERS
1806 *   sibling N|end?-offset? QUALIFIERS
1807 *   -- returning multiple items --
1808 *   ancestors QUALIFIERS
1809 *   children QUALIFIERS
1810 *   descendants QUALIFIERS
1811 *
1812 *   QUALIFIERS:
1813 *   depth integer
1814 *   state stateList
1815 *   tag tagExpr
1816 *   visible
1817 *   !visible
1818 *
1819 *   Examples:
1820 *   $T item id "first visible firstchild"
1821 *   $T item id "first visible firstchild visible"
1822 *   $T item id "nearest x y nextsibling visible"
1823 *   $T item id "last visible state enabled"
1824 *
1825 * Results:
1826 *	TCL_OK or TCL_ERROR.
1827 *
1828 * Side effects:
1829 *	None.
1830 *
1831 *----------------------------------------------------------------------
1832 */
1833
1834int
1835TreeItemList_FromObj(
1836    TreeCtrl *tree,		/* Widget info. */
1837    Tcl_Obj *objPtr,		/* Object to parse to a list of items. */
1838    TreeItemList *items,	/* Uninitialized item list. Caller must free
1839				 * it with TreeItemList_Free unless the
1840				 * result of this function is TCL_ERROR. */
1841    int flags			/* IFO_xxx flags */
1842    )
1843{
1844    Tcl_Interp *interp = tree->interp;
1845    int i, objc, index, listIndex, id;
1846    Tcl_HashEntry *hPtr;
1847    Tcl_HashSearch search;
1848    Tcl_Obj **objv, *elemPtr;
1849    TreeItem item = NULL;
1850    Qualifiers q;
1851    int qualArgsTotal;
1852
1853    static CONST char *indexName[] = {
1854	"active", "all", "anchor", "end", "first", "last", "list",
1855	"nearest", "range", "rnc", "root", (char *) NULL
1856    };
1857    enum indexEnum {
1858	INDEX_ACTIVE, INDEX_ALL, INDEX_ANCHOR, INDEX_END, INDEX_FIRST,
1859	INDEX_LAST, INDEX_LIST, INDEX_NEAREST, INDEX_RANGE, INDEX_RNC,
1860	INDEX_ROOT
1861    };
1862    /* Number of arguments used by indexName[]. */
1863    static int indexArgs[] = {
1864	1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 1
1865    };
1866    /* Boolean: can indexName[] be followed by 1 or more qualifiers. */
1867    static int indexQual[] = {
1868	0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1
1869    };
1870
1871    static CONST char *modifiers[] = {
1872	"above", "ancestors", "below", "bottom", "child", "children",
1873	"descendants", "firstchild", "lastchild", "left", "leftmost", "next",
1874	"nextsibling", "parent", "prev", "prevsibling", "right", "rightmost",
1875	"sibling", "top", (char *) NULL
1876    };
1877    enum modEnum {
1878	TMOD_ABOVE, TMOD_ANCESTORS, TMOD_BELOW, TMOD_BOTTOM, TMOD_CHILD,
1879	TMOD_CHILDREN, TMOD_DESCENDANTS, TMOD_FIRSTCHILD, TMOD_LASTCHILD,
1880	TMOD_LEFT, TMOD_LEFTMOST, TMOD_NEXT, TMOD_NEXTSIBLING, TMOD_PARENT,
1881	TMOD_PREV, TMOD_PREVSIBLING, TMOD_RIGHT, TMOD_RIGHTMOST, TMOD_SIBLING,
1882	TMOD_TOP
1883    };
1884    /* Number of arguments used by modifiers[]. */
1885    static int modArgs[] = {
1886	1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1
1887    };
1888    /* Boolean: can modifiers[] be followed by 1 or more qualifiers. */
1889    static int modQual[] = {
1890	0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0
1891    };
1892
1893    TreeItemList_Init(tree, items, 0);
1894    Qualifiers_Init(tree, &q);
1895
1896    if (Tcl_ListObjGetElements(NULL, objPtr, &objc, &objv) != TCL_OK)
1897	goto baditem;
1898    if (objc == 0)
1899	goto baditem;
1900
1901    listIndex = 0;
1902    elemPtr = objv[listIndex];
1903    if (Tcl_GetIndexFromObj(NULL, elemPtr, indexName, NULL, 0, &index)
1904	    == TCL_OK) {
1905
1906	if (objc - listIndex < indexArgs[index]) {
1907	    Tcl_AppendResult(interp, "missing arguments to \"",
1908		    Tcl_GetString(elemPtr), "\" keyword", NULL);
1909	    goto errorExit;
1910	}
1911
1912	qualArgsTotal = 0;
1913	if (indexQual[index]) {
1914	    if (Qualifiers_Scan(&q, objc, objv, listIndex + indexArgs[index],
1915		    &qualArgsTotal) != TCL_OK) {
1916		goto errorExit;
1917	    }
1918	}
1919
1920	switch ((enum indexEnum) index) {
1921	    case INDEX_ACTIVE: {
1922		item = tree->activeItem;
1923		break;
1924	    }
1925	    case INDEX_ALL: {
1926		if (qualArgsTotal) {
1927		    hPtr = Tcl_FirstHashEntry(&tree->itemHash, &search);
1928		    while (hPtr != NULL) {
1929			item = (TreeItem) Tcl_GetHashValue(hPtr);
1930			if (Qualifies(&q, item)) {
1931			    TreeItemList_Append(items, (TreeItem) item);
1932			}
1933			hPtr = Tcl_NextHashEntry(&search);
1934		    }
1935		    item = NULL;
1936		} else if (flags & IFO_LIST_ALL) {
1937		    hPtr = Tcl_FirstHashEntry(&tree->itemHash, &search);
1938		    while (hPtr != NULL) {
1939			item = (TreeItem) Tcl_GetHashValue(hPtr);
1940			TreeItemList_Append(items, item);
1941			hPtr = Tcl_NextHashEntry(&search);
1942		    }
1943		    item = NULL;
1944		} else {
1945		    item = ITEM_ALL;
1946		}
1947		break;
1948	    }
1949	    case INDEX_ANCHOR: {
1950		item = tree->anchorItem;
1951		break;
1952	    }
1953	    case INDEX_FIRST: {
1954		item = tree->root;
1955		while (!Qualifies(&q, item))
1956		    item = TreeItem_Next(tree, item);
1957		break;
1958	    }
1959	    case INDEX_END:
1960	    case INDEX_LAST: {
1961		item = tree->root;
1962		while (item->lastChild) {
1963		    item = item->lastChild;
1964		}
1965		while (!Qualifies(&q, item))
1966		    item = TreeItem_Prev(tree, item);
1967		break;
1968	    }
1969	    case INDEX_LIST: {
1970		int listObjc;
1971		Tcl_Obj **listObjv;
1972		int count;
1973
1974		if (Tcl_ListObjGetElements(interp, objv[listIndex + 1],
1975			&listObjc, &listObjv) != TCL_OK) {
1976		    goto errorExit;
1977		}
1978		for (i = 0; i < listObjc; i++) {
1979		    TreeItemList item2s;
1980		    if (TreeItemList_FromObj(tree, listObjv[i], &item2s, flags)
1981			    != TCL_OK)
1982			goto errorExit;
1983		    TreeItemList_Concat(items, &item2s);
1984		    TreeItemList_Free(&item2s);
1985		}
1986		/* If any of the item descriptions in the list is "all", then
1987		 * clear the list of items and use "all". */
1988		count = TreeItemList_Count(items);
1989		for (i = 0; i < count; i++) {
1990		    TreeItem item2 = TreeItemList_Nth(items, i);
1991		    if (IS_ALL(item2))
1992			break;
1993		}
1994		if (i < count) {
1995		    TreeItemList_Free(items);
1996		    item = ITEM_ALL;
1997		} else
1998		    item = NULL;
1999		break;
2000	    }
2001	    case INDEX_NEAREST: {
2002		int x, y;
2003
2004		if (Tk_GetPixelsFromObj(interp, tree->tkwin,
2005			objv[listIndex + 1], &x) != TCL_OK) {
2006		    goto errorExit;
2007		}
2008		if (Tk_GetPixelsFromObj(interp, tree->tkwin,
2009			objv[listIndex + 2], &y) != TCL_OK) {
2010		    goto errorExit;
2011		}
2012		item = Tree_ItemUnderPoint(tree, &x, &y, TRUE);
2013		break;
2014	    }
2015	    case INDEX_RANGE: {
2016		TreeItem itemFirst, itemLast;
2017
2018		if (TreeItem_FromObj(tree, objv[listIndex + 1], &itemFirst,
2019			IFO_NOT_NULL) != TCL_OK)
2020		    goto errorExit;
2021		if (TreeItem_FromObj(tree, objv[listIndex + 2], &itemLast,
2022			IFO_NOT_NULL) != TCL_OK)
2023		    goto errorExit;
2024		if (TreeItem_FirstAndLast(tree, &itemFirst, &itemLast) == 0)
2025		    goto errorExit;
2026		while (1) {
2027		    if (Qualifies(&q, itemFirst)) {
2028			TreeItemList_Append(items, itemFirst);
2029		    }
2030		    if (itemFirst == itemLast)
2031			break;
2032		    itemFirst = TreeItem_Next(tree, itemFirst);
2033		}
2034		item = NULL;
2035		break;
2036	    }
2037	    case INDEX_RNC: {
2038		int row, col;
2039
2040		if (Tcl_GetIntFromObj(interp, objv[listIndex + 1], &row) != TCL_OK)
2041		    goto errorExit;
2042		if (Tcl_GetIntFromObj(interp, objv[listIndex + 2], &col) != TCL_OK)
2043		    goto errorExit;
2044		item = Tree_RNCToItem(tree, row, col);
2045		break;
2046	    }
2047	    case INDEX_ROOT: {
2048		item = tree->root;
2049		break;
2050	    }
2051	}
2052
2053	listIndex += indexArgs[index] + qualArgsTotal;
2054
2055    /* No indexName[] was found. */
2056    } else {
2057	int gotId = FALSE;
2058	TagExpr expr;
2059
2060	/* Try an itemPrefix + item ID. */
2061	if (tree->itemPrefixLen) {
2062	    char *end, *t = Tcl_GetString(elemPtr);
2063	    if (strncmp(t, tree->itemPrefix, tree->itemPrefixLen) == 0) {
2064		t += tree->itemPrefixLen;
2065		id = strtoul(t, &end, 10);
2066		if ((end != t) && (*end == '\0'))
2067		    gotId = TRUE;
2068	    }
2069
2070	/* Try an item ID. */
2071	} else if (Tcl_GetIntFromObj(NULL, elemPtr, &id) == TCL_OK) {
2072	    gotId = TRUE;
2073	}
2074	if (gotId) {
2075	    hPtr = Tcl_FindHashEntry(&tree->itemHash, (char *) id);
2076	    if (hPtr != NULL) {
2077		item = (TreeItem) Tcl_GetHashValue(hPtr);
2078	    } else {
2079		item = NULL;
2080	    }
2081	    listIndex++;
2082	    goto gotFirstPart;
2083	}
2084
2085	/* Try a list of qualifiers. This has the same effect as
2086	 * "all QUALIFIERS". */
2087	if (Qualifiers_Scan(&q, objc, objv, listIndex, &qualArgsTotal)
2088		!= TCL_OK) {
2089	    goto errorExit;
2090	}
2091	if (qualArgsTotal) {
2092	    hPtr = Tcl_FirstHashEntry(&tree->itemHash, &search);
2093	    while (hPtr != NULL) {
2094		item = (TreeItem) Tcl_GetHashValue(hPtr);
2095		if (Qualifies(&q, item)) {
2096		    TreeItemList_Append(items, item);
2097		}
2098		hPtr = Tcl_NextHashEntry(&search);
2099	    }
2100	    item = NULL;
2101	    listIndex += qualArgsTotal;
2102	    goto gotFirstPart;
2103	}
2104
2105	/* Try a tag or tag expression followed by qualifiers. */
2106	if (objc > 1) {
2107	    if (Qualifiers_Scan(&q, objc, objv, listIndex + 1,
2108		    &qualArgsTotal) != TCL_OK) {
2109		goto errorExit;
2110	    }
2111	}
2112	if (tree->itemTagExpr) {
2113	    if (TagExpr_Init(tree, elemPtr, &expr) != TCL_OK)
2114		goto errorExit;
2115	    hPtr = Tcl_FirstHashEntry(&tree->itemHash, &search);
2116	    while (hPtr != NULL) {
2117		item = (TreeItem) Tcl_GetHashValue(hPtr);
2118		if (TagExpr_Eval(&expr, item->tagInfo) && Qualifies(&q, item)) {
2119		    TreeItemList_Append(items, item);
2120		}
2121		hPtr = Tcl_NextHashEntry(&search);
2122	    }
2123	    TagExpr_Free(&expr);
2124	} else {
2125	    Tk_Uid tag = Tk_GetUid(Tcl_GetString(elemPtr));
2126	    hPtr = Tcl_FirstHashEntry(&tree->itemHash, &search);
2127	    while (hPtr != NULL) {
2128		item = (TreeItem) Tcl_GetHashValue(hPtr);
2129		if (ItemHasTag(item, tag) && Qualifies(&q, item)) {
2130		    TreeItemList_Append(items, item);
2131		}
2132		hPtr = Tcl_NextHashEntry(&search);
2133	    }
2134	}
2135	item = NULL;
2136	listIndex += 1 + qualArgsTotal;
2137    }
2138
2139gotFirstPart:
2140    /* If 1 item, use it and clear the list. */
2141    if (TreeItemList_Count(items) == 1) {
2142	item = TreeItemList_Nth(items, 0);
2143	items->count = 0;
2144    }
2145
2146    /* If "all" but only root exists, use it. */
2147    if (IS_ALL(item) && (tree->itemCount == 1) && !(flags & IFO_NOT_ROOT)) {
2148	item = tree->root;
2149    }
2150
2151    /* If > 1 item, no modifiers may follow. */
2152    if ((TreeItemList_Count(items) > 1) || IS_ALL(item)) {
2153	if (listIndex < objc) {
2154	    Tcl_AppendResult(interp, "unexpected arguments after \"",
2155		(char *) NULL);
2156	    for (i = 0; i < listIndex; i++) {
2157		Tcl_AppendResult(interp, Tcl_GetString(objv[i]), (char *) NULL);
2158		if (i != listIndex - 1)
2159		    Tcl_AppendResult(interp, " ", (char *) NULL);
2160	    }
2161	    Tcl_AppendResult(interp, "\"", (char *) NULL);
2162	    goto errorExit;
2163	}
2164    }
2165
2166    /* This means a valid specification was given, but there is no such item */
2167    if ((TreeItemList_Count(items) == 0) && (item == NULL)) {
2168	if (flags & IFO_NOT_NULL)
2169	    goto noitem;
2170	/* Empty list returned */
2171	goto goodExit;
2172    }
2173
2174    /* Process any modifiers following the item we matched above. */
2175    for (; listIndex < objc; /* nothing */) {
2176
2177	elemPtr = objv[listIndex];
2178	if (Tcl_GetIndexFromObj(interp, elemPtr, modifiers, "modifier", 0,
2179		    &index) != TCL_OK) {
2180	    goto errorExit;
2181	}
2182	if (objc - listIndex < modArgs[index]) {
2183	    Tcl_AppendResult(interp, "missing arguments to \"",
2184		    Tcl_GetString(elemPtr), "\" modifier", NULL);
2185	    goto errorExit;
2186	}
2187
2188	qualArgsTotal = 0;
2189	if (modQual[index]) {
2190	    Qualifiers_Free(&q);
2191	    Qualifiers_Init(tree, &q);
2192	    if (Qualifiers_Scan(&q, objc, objv, listIndex + modArgs[index],
2193		    &qualArgsTotal) != TCL_OK) {
2194		goto errorExit;
2195	    }
2196	}
2197
2198	switch ((enum modEnum) index) {
2199	    case TMOD_ABOVE: {
2200		item = Tree_ItemAbove(tree, item);
2201		break;
2202	    }
2203	    case TMOD_ANCESTORS: {
2204		item = item->parent;
2205		while (item != NULL) {
2206		    if (Qualifies(&q, item)) {
2207			TreeItemList_Append(items, item);
2208		    }
2209		    item = item->parent;
2210		}
2211		item = NULL;
2212		break;
2213	    }
2214	    case TMOD_BELOW: {
2215		item = Tree_ItemBelow(tree, item);
2216		break;
2217	    }
2218	    case TMOD_BOTTOM: {
2219		item = Tree_ItemBottom(tree, item);
2220		break;
2221	    }
2222	    case TMOD_CHILD: {
2223		int n, endRelative;
2224
2225		if (Tree_GetIntForIndex(tree, objv[listIndex + 1], &n,
2226			&endRelative) != TCL_OK) {
2227		    goto errorExit;
2228		}
2229		if (endRelative) {
2230		    item = item->lastChild;
2231		    while (item != NULL) {
2232			if (Qualifies(&q, item))
2233			    if (n-- <= 0)
2234				break;
2235			item = item->prevSibling;
2236		    }
2237		} else {
2238		    item = item->firstChild;
2239		    while (item != NULL) {
2240			if (Qualifies(&q, item))
2241			    if (n-- <= 0)
2242				break;
2243			item = item->nextSibling;
2244		    }
2245		}
2246		break;
2247	    }
2248	    case TMOD_CHILDREN: {
2249		item = item->firstChild;
2250		while (item != NULL) {
2251		    if (Qualifies(&q, item)) {
2252			TreeItemList_Append(items, item);
2253		    }
2254		    item = item->nextSibling;
2255		}
2256		item = NULL;
2257		break;
2258	    }
2259	    case TMOD_DESCENDANTS: {
2260		TreeItem last = item;
2261
2262		while (last->lastChild != NULL)
2263		    last = last->lastChild;
2264		item = item->firstChild;
2265		while (item != NULL) {
2266		    if (Qualifies(&q, item)) {
2267			TreeItemList_Append(items, item);
2268		    }
2269		    if (item == last)
2270			break;
2271		    item = TreeItem_Next(tree, item);
2272		}
2273		item = NULL;
2274		break;
2275	    }
2276	    case TMOD_FIRSTCHILD: {
2277		item = item->firstChild;
2278		while (!Qualifies(&q, item))
2279		    item = item->nextSibling;
2280		break;
2281	    }
2282	    case TMOD_LASTCHILD: {
2283		item = item->lastChild;
2284		while (!Qualifies(&q, item))
2285		    item = item->prevSibling;
2286		break;
2287	    }
2288	    case TMOD_LEFT: {
2289		item = Tree_ItemLeft(tree, item);
2290		break;
2291	    }
2292	    case TMOD_LEFTMOST: {
2293		item = Tree_ItemLeftMost(tree, item);
2294		break;
2295	    }
2296	    case TMOD_NEXT: {
2297		item = TreeItem_Next(tree, item);
2298		while (!Qualifies(&q, item))
2299		    item = TreeItem_Next(tree, item);
2300		break;
2301	    }
2302	    case TMOD_NEXTSIBLING: {
2303		item = item->nextSibling;
2304		while (!Qualifies(&q, item))
2305		    item = item->nextSibling;
2306		break;
2307	    }
2308	    case TMOD_PARENT: {
2309		item = item->parent;
2310		break;
2311	    }
2312	    case TMOD_PREV: {
2313		item = TreeItem_Prev(tree, item);
2314		while (!Qualifies(&q, item))
2315		    item = TreeItem_Prev(tree, item);
2316		break;
2317	    }
2318	    case TMOD_PREVSIBLING: {
2319		item = item->prevSibling;
2320		while (!Qualifies(&q, item))
2321		    item = item->prevSibling;
2322		break;
2323	    }
2324	    case TMOD_RIGHT: {
2325		item = Tree_ItemRight(tree, item);
2326		break;
2327	    }
2328	    case TMOD_RIGHTMOST: {
2329		item = Tree_ItemRightMost(tree, item);
2330		break;
2331	    }
2332	    case TMOD_SIBLING: {
2333		int n, endRelative;
2334
2335		if (Tree_GetIntForIndex(tree, objv[listIndex + 1], &n,
2336			&endRelative) != TCL_OK) {
2337		    goto errorExit;
2338		}
2339		item = item->parent;
2340		if (item == NULL)
2341		    break;
2342		if (endRelative) {
2343		    item = item->lastChild;
2344		    while (item != NULL) {
2345			if (Qualifies(&q, item))
2346			    if (n-- <= 0)
2347				break;
2348			item = item->prevSibling;
2349		    }
2350		} else {
2351		    item = item->firstChild;
2352		    while (item != NULL) {
2353			if (Qualifies(&q, item))
2354			    if (n-- <= 0)
2355				break;
2356			item = item->nextSibling;
2357		    }
2358		}
2359		break;
2360	    }
2361	    case TMOD_TOP: {
2362		item = Tree_ItemTop(tree, item);
2363		break;
2364	    }
2365	}
2366	if ((TreeItemList_Count(items) > 1) || IS_ALL(item)) {
2367	    int end = listIndex + modArgs[index] + qualArgsTotal;
2368	    if (end < objc) {
2369		Tcl_AppendResult(interp, "unexpected arguments after \"",
2370		    (char *) NULL);
2371		for (i = 0; i < end; i++) {
2372		    Tcl_AppendResult(interp, Tcl_GetString(objv[i]), (char *) NULL);
2373		    if (i != end - 1)
2374			Tcl_AppendResult(interp, " ", (char *) NULL);
2375		}
2376		Tcl_AppendResult(interp, "\"", (char *) NULL);
2377		goto errorExit;
2378	    }
2379	}
2380	if ((TreeItemList_Count(items) == 0) && (item == NULL)) {
2381	    if (flags & IFO_NOT_NULL)
2382		goto noitem;
2383	    /* Empty list returned. */
2384	    goto goodExit;
2385	}
2386	listIndex += modArgs[index] + qualArgsTotal;
2387    }
2388    if ((flags & IFO_NOT_MANY) && (IS_ALL(item) ||
2389	    (TreeItemList_Count(items) > 1))) {
2390	FormatResult(interp, "can't specify > 1 item for this command");
2391	goto errorExit;
2392    }
2393    if (TreeItemList_Count(items)) {
2394	if (flags & (IFO_NOT_ROOT | IFO_NOT_ORPHAN)) {
2395	    int i;
2396	    for (i = 0; i < TreeItemList_Count(items); i++) {
2397		item = TreeItemList_Nth(items, i);
2398		if (IS_ROOT(item) && (flags & IFO_NOT_ROOT))
2399		    goto notRoot;
2400		if ((item->parent == NULL) && (flags & IFO_NOT_ORPHAN))
2401		    goto notOrphan;
2402	    }
2403	}
2404    } else if (IS_ALL(item)) {
2405	TreeItemList_Append(items, ITEM_ALL);
2406    } else {
2407	if (IS_ROOT(item) && (flags & IFO_NOT_ROOT)) {
2408notRoot:
2409	    FormatResult(interp, "can't specify \"root\" for this command");
2410	    goto errorExit;
2411	}
2412	if ((item->parent == NULL) && (flags & IFO_NOT_ORPHAN)) {
2413notOrphan:
2414	    FormatResult(interp, "item \"%s\" has no parent",
2415		    Tcl_GetString(objPtr));
2416	    goto errorExit;
2417	}
2418	TreeItemList_Append(items, item);
2419    }
2420goodExit:
2421    Qualifiers_Free(&q);
2422    return TCL_OK;
2423
2424baditem:
2425    Tcl_AppendResult(interp, "bad item description \"", Tcl_GetString(objPtr),
2426	    "\"", NULL);
2427    goto errorExit;
2428
2429noitem:
2430    Tcl_AppendResult(interp, "item \"", Tcl_GetString(objPtr),
2431	    "\" doesn't exist", NULL);
2432
2433errorExit:
2434    Qualifiers_Free(&q);
2435    TreeItemList_Free(items);
2436    return TCL_ERROR;
2437}
2438
2439/*
2440 *----------------------------------------------------------------------
2441 *
2442 * TreeItem_FromObj --
2443 *
2444 *	Parse a Tcl_Obj item description to get a single item.
2445 *
2446 * Results:
2447 *	TCL_OK or TCL_ERROR.
2448 *
2449 * Side effects:
2450 *	None.
2451 *
2452 *----------------------------------------------------------------------
2453 */
2454
2455int
2456TreeItem_FromObj(
2457    TreeCtrl *tree,		/* Widget info. */
2458    Tcl_Obj *objPtr,		/* Object to parse to an item. */
2459    TreeItem *itemPtr,		/* Returned item. */
2460    int flags			/* IFO_xxx flags */
2461    )
2462{
2463    TreeItemList items;
2464
2465    if (TreeItemList_FromObj(tree, objPtr, &items, flags | IFO_NOT_MANY) != TCL_OK)
2466	return TCL_ERROR;
2467    /* May be NULL. */
2468    (*itemPtr) = TreeItemList_Nth(&items, 0);
2469    TreeItemList_Free(&items);
2470    return TCL_OK;
2471}
2472
2473/*
2474 *----------------------------------------------------------------------
2475 *
2476 * TreeItemForEach_Start --
2477 *
2478 *	Begin iterating over items. A command might accept two item
2479 *	descriptions for a range of items, or a single item description
2480 *	which may itself refer to multiple items. Either item
2481 *	description could be ITEM_ALL.
2482 *
2483 * Results:
2484 *	Returns the first item to iterate over. If an error occurs
2485 *	then ItemForEach.error is set to 1.
2486 *
2487 * Side effects:
2488 *	None.
2489 *
2490 *----------------------------------------------------------------------
2491 */
2492
2493TreeItem
2494TreeItemForEach_Start(
2495    TreeItemList *items,	/* List of items. */
2496    TreeItemList *item2s,	/* List of items or NULL. */
2497    ItemForEach *iter		/* Returned info, pass to
2498				   TreeItemForEach_Next. */
2499    )
2500{
2501    TreeCtrl *tree = items->tree;
2502    TreeItem item, item2 = NULL;
2503
2504    item = TreeItemList_Nth(items, 0);
2505    if (item2s)
2506	item2 = TreeItemList_Nth(item2s, 0);
2507
2508    iter->tree = tree;
2509    iter->all = FALSE;
2510    iter->error = 0;
2511    iter->items = NULL;
2512
2513    if (IS_ALL(item) || IS_ALL(item2)) {
2514	Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&tree->itemHash, &iter->search);
2515	iter->all = TRUE;
2516	return iter->item = (TreeItem) Tcl_GetHashValue(hPtr);
2517    }
2518
2519    if (item2 != NULL) {
2520	if (TreeItem_FirstAndLast(tree, &item, &item2) == 0) {
2521	    iter->error = 1;
2522	    return NULL;
2523	}
2524	iter->last = item2;
2525	return iter->item = item;
2526    }
2527
2528    iter->items = items;
2529    iter->index = 0;
2530    return iter->item = item;
2531}
2532
2533/*
2534 *----------------------------------------------------------------------
2535 *
2536 * TreeItemForEach_Next --
2537 *
2538 *	Returns the next item to iterate over. Keep calling this until
2539 *	the result is NULL.
2540 *
2541 * Results:
2542 *	Returns the next item to iterate over or NULL.
2543 *
2544 * Side effects:
2545 *	None.
2546 *
2547 *----------------------------------------------------------------------
2548 */
2549
2550TreeItem
2551TreeItemForEach_Next(
2552    ItemForEach *iter		/* Initialized by TreeItemForEach_Start. */
2553    )
2554{
2555    TreeCtrl *tree = iter->tree;
2556
2557    if (iter->all) {
2558	Tcl_HashEntry *hPtr = Tcl_NextHashEntry(&iter->search);
2559	if (hPtr == NULL)
2560	    return iter->item = NULL;
2561	return iter->item = (TreeItem) Tcl_GetHashValue(hPtr);
2562    }
2563
2564    if (iter->items != NULL) {
2565	if (iter->index >= TreeItemList_Count(iter->items))
2566	    return iter->item = NULL;
2567	return iter->item = TreeItemList_Nth(iter->items, ++iter->index);
2568    }
2569
2570    if (iter->item == iter->last)
2571	return iter->item = NULL;
2572    return iter->item = TreeItem_Next(tree, iter->item);
2573}
2574
2575/*
2576 *----------------------------------------------------------------------
2577 *
2578 * Item_ToggleOpen --
2579 *
2580 *	Inverts the STATE_OPEN flag of an Item.
2581 *
2582 * Results:
2583 *	Items may be displayed/undisplayed.
2584 *
2585 * Side effects:
2586 *	Display changes.
2587 *
2588 *----------------------------------------------------------------------
2589 */
2590
2591static void
2592Item_ToggleOpen(
2593    TreeCtrl *tree,		/* Widget info. */
2594    TreeItem item,		/* Item record. */
2595    int stateOff,		/* STATE_OPEN or 0 */
2596    int stateOn			/* STATE_OPEN or 0 */
2597    )
2598{
2599    int mask;
2600
2601    mask = TreeItem_ChangeState(tree, item, stateOff, stateOn);
2602
2603    if (IS_ROOT(item) && !tree->showRoot)
2604	return;
2605
2606#if 0
2607    /* Don't affect display if we weren't visible */
2608    if (!TreeItem_ReallyVisible(tree, item))
2609	return;
2610
2611    /* Invalidate display info for this item, so it is redrawn later. */
2612    Tree_InvalidateItemDInfo(tree, item, NULL);
2613#endif
2614
2615    if (item->numChildren > 0) {
2616	/* indexVis needs updating for all items after this one, if we
2617	 * have any visible children */
2618	tree->updateIndex = 1;
2619	Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
2620
2621	/* Hiding/showing children may change the width of any column */
2622	Tree_InvalidateColumnWidth(tree, NULL);
2623    }
2624
2625    /* If this item was previously onscreen, this call is repetitive. */
2626    Tree_EventuallyRedraw(tree);
2627}
2628
2629/*
2630 *----------------------------------------------------------------------
2631 *
2632 * TreeItem_OpenClose --
2633 *
2634 *	Inverts the STATE_OPEN flag of an Item.
2635 *
2636 * Results:
2637 *	Items may be displayed/undisplayed.
2638 *
2639 * Side effects:
2640 *	Display changes. <Expand> and <Collapse> events may be
2641 *	generated.
2642 *
2643 *----------------------------------------------------------------------
2644 */
2645
2646void
2647TreeItem_OpenClose(
2648    TreeCtrl *tree,		/* Widget info. */
2649    TreeItem item,		/* Item token. */
2650    int mode			/* -1: toggle
2651				 * 0: close
2652				 * 1: open */
2653    )
2654{
2655    int stateOff = 0, stateOn = 0;
2656
2657    /* When processing a list of items, any <Expand> or <Collapse> event
2658     * may result in items being deleted. */
2659    if (IS_DELETED(item)) return;
2660
2661    if (mode == -1) {
2662	if (item->state & STATE_OPEN)
2663	    stateOff = STATE_OPEN;
2664	else
2665	    stateOn = STATE_OPEN;
2666    } else if (!mode && (item->state & STATE_OPEN))
2667	stateOff = STATE_OPEN;
2668    else if (mode && !(item->state & STATE_OPEN))
2669	stateOn = STATE_OPEN;
2670
2671    if (stateOff != stateOn) {
2672	TreeNotify_OpenClose(tree, item, stateOn, TRUE);
2673	if (IS_DELETED(item)) return;
2674	Item_ToggleOpen(tree, item, stateOff, stateOn);
2675	TreeNotify_OpenClose(tree, item, stateOn, FALSE);
2676    }
2677}
2678
2679/*
2680 *----------------------------------------------------------------------
2681 *
2682 * TreeItem_Delete --
2683 *
2684 *	Recursively frees resources associated with an Item and its
2685 *	descendants.
2686 *
2687 * Results:
2688 *	Items are removed from their parent and freed.
2689 *
2690 * Side effects:
2691 *	Memory is freed. If the active item or selection-anchor item
2692 *	is deleted, the root becomes the active/anchor item.
2693 *	Display changes may occur.
2694 *
2695 *----------------------------------------------------------------------
2696 */
2697
2698void
2699TreeItem_Delete(
2700    TreeCtrl *tree,		/* Widget info. */
2701    TreeItem item		/* Item token. */
2702    )
2703{
2704    if (TreeItem_ReallyVisible(tree, item))
2705	Tree_InvalidateColumnWidth(tree, NULL);
2706
2707    while (item->numChildren > 0)
2708	TreeItem_Delete(tree, item->firstChild);
2709
2710    TreeItem_RemoveFromParent(tree, item);
2711    TreeDisplay_ItemDeleted(tree, item);
2712    Tree_RemoveItem(tree, item);
2713    TreeItem_FreeResources(tree, item);
2714    if (tree->activeItem == item) {
2715	tree->activeItem = tree->root;
2716	TreeItem_ChangeState(tree, tree->activeItem, 0, STATE_ACTIVE);
2717    }
2718    if (tree->anchorItem == item)
2719	tree->anchorItem = tree->root;
2720    if (tree->debug.enable && tree->debug.data)
2721	Tree_Debug(tree);
2722}
2723
2724/*
2725 *----------------------------------------------------------------------
2726 *
2727 * TreeItem_Deleted --
2728 *
2729 *	Return 1 if the given item is deleted.
2730 *
2731 * Results:
2732 *	None.
2733 *
2734 * Side effects:
2735 *	None.
2736 *
2737 *----------------------------------------------------------------------
2738 */
2739
2740int
2741TreeItem_Deleted(
2742    TreeCtrl *tree,		/* Widget info. */
2743    TreeItem item		/* Item token. */
2744    )
2745{
2746    return IS_DELETED(item);
2747}
2748
2749/*
2750 *----------------------------------------------------------------------
2751 *
2752 * TreeItem_FirstAndLast --
2753 *
2754 *	Determine the order of two items and swap them if needed.
2755 *
2756 * Results:
2757 *	If the items do not share a common ancestor, 0 is returned and
2758 *	an error message is left in the interpreter result.
2759 *	Otherwise the return value is the number of items in the
2760 *	range between first and last.
2761 *
2762 * Side effects:
2763 *	None.
2764 *
2765 *----------------------------------------------------------------------
2766 */
2767
2768int
2769TreeItem_FirstAndLast(
2770    TreeCtrl *tree,		/* Widget info. */
2771    TreeItem *first,		/* Item token. */
2772    TreeItem *last		/* Item token. */
2773    )
2774{
2775    int indexFirst, indexLast, index;
2776
2777    if (TreeItem_RootAncestor(tree, *first) !=
2778	    TreeItem_RootAncestor(tree, *last)) {
2779	FormatResult(tree->interp,
2780		"item %s%d and item %s%d don't share a common ancestor",
2781		tree->itemPrefix, TreeItem_GetID(tree, *first),
2782		tree->itemPrefix, TreeItem_GetID(tree, *last));
2783	return 0;
2784    }
2785    TreeItem_ToIndex(tree, *first, &indexFirst, NULL);
2786    TreeItem_ToIndex(tree, *last, &indexLast, NULL);
2787    if (indexFirst > indexLast) {
2788	TreeItem item = *first;
2789	*first = *last;
2790	*last = item;
2791
2792	index = indexFirst;
2793	indexFirst = indexLast;
2794	indexLast = index;
2795    }
2796    return indexLast - indexFirst + 1;
2797}
2798
2799/*
2800 *----------------------------------------------------------------------
2801 *
2802 * TreeItem_ListDescendants --
2803 *
2804 *	Appends descendants of an item to a list of items.
2805 *
2806 * Results:
2807 *	List of items may grow.
2808 *
2809 * Side effects:
2810 *	None.
2811 *
2812 *----------------------------------------------------------------------
2813 */
2814
2815void
2816TreeItem_ListDescendants(
2817    TreeCtrl *tree,		/* Widget info. */
2818    TreeItem item,		/* Item token. */
2819    TreeItemList *items		/* List of items to append descendants to. */
2820    )
2821{
2822    TreeItem last;
2823
2824    if (item->firstChild == NULL)
2825	return;
2826    last = item;
2827    while (last->lastChild != NULL)
2828	last = last->lastChild;
2829    item = item->firstChild;
2830    while (1) {
2831	TreeItemList_Append(items, item);
2832	if (item == last)
2833	    break;
2834	item = TreeItem_Next(tree, item);
2835    }
2836}
2837
2838
2839/*
2840 *----------------------------------------------------------------------
2841 *
2842 * TreeItem_UpdateDepth --
2843 *
2844 *	Recursively updates Item.depth of an Item and its
2845 *	descendants.
2846 *
2847 * Results:
2848 *	None.
2849 *
2850 * Side effects:
2851 *	None.
2852 *
2853 *----------------------------------------------------------------------
2854 */
2855
2856void
2857TreeItem_UpdateDepth(
2858    TreeCtrl *tree,		/* Widget info. */
2859    TreeItem item		/* Item token. */
2860    )
2861{
2862    TreeItem child;
2863
2864    if (IS_ROOT(item))
2865	return;
2866    if (item->parent != NULL)
2867	item->depth = item->parent->depth + 1;
2868    else
2869	item->depth = 0;
2870    child = item->firstChild;
2871    while (child != NULL) {
2872	TreeItem_UpdateDepth(tree, child);
2873	child = child->nextSibling;
2874    }
2875}
2876
2877/*
2878 *----------------------------------------------------------------------
2879 *
2880 * TreeItem_AddToParent --
2881 *
2882 *	Called *after* an Item is added as a child to another Item.
2883 *
2884 * Results:
2885 *	None.
2886 *
2887 * Side effects:
2888 *	Display changes.
2889 *
2890 *----------------------------------------------------------------------
2891 */
2892
2893void
2894TreeItem_AddToParent(
2895    TreeCtrl *tree,		/* Widget info. */
2896    TreeItem item		/* Item token. */
2897    )
2898{
2899    TreeItem last, parent = item->parent;
2900
2901    /* If this is the new last child, redraw the lines of the previous
2902     * sibling and all of its descendants so the line from the previous
2903     * sibling reaches this item */
2904    if ((item->prevSibling != NULL) &&
2905	    (item->nextSibling == NULL) &&
2906	    tree->showLines && (tree->columnTree != NULL)) {
2907	last = item->prevSibling;
2908	while (last->lastChild != NULL)
2909	    last = last->lastChild;
2910	Tree_InvalidateItemDInfo(tree, tree->columnTree,
2911		item->prevSibling, last);
2912    }
2913
2914    /* Redraw the parent if the parent has "-button auto". */
2915    if (IS_VISIBLE(item) && (parent->flags & ITEM_FLAG_BUTTON_AUTO) &&
2916	    tree->showButtons && (tree->columnTree != NULL)) {
2917	Tree_InvalidateItemDInfo(tree, tree->columnTree, parent,
2918		NULL);
2919    }
2920
2921    tree->updateIndex = 1;
2922    Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
2923
2924    /* Tree_UpdateItemIndex() also recalcs depth, but in one of my demos
2925     * I retrieve item depth during list creation. Since Tree_UpdateItemIndex()
2926     * is slow I will keep depth up-to-date here. */
2927    TreeItem_UpdateDepth(tree, item);
2928
2929    Tree_InvalidateColumnWidth(tree, NULL);
2930
2931    if (tree->debug.enable && tree->debug.data)
2932	Tree_Debug(tree);
2933}
2934
2935/*
2936 *----------------------------------------------------------------------
2937 *
2938 * RemoveFromParentAux --
2939 *
2940 *	Recursively update Item.depth, Item.index and Item.indexVis.
2941 *
2942 * Results:
2943 *	None.
2944 *
2945 * Side effects:
2946 *	Display changes.
2947 *
2948 *----------------------------------------------------------------------
2949 */
2950
2951static void
2952RemoveFromParentAux(
2953    TreeCtrl *tree,		/* Widget info. */
2954    TreeItem item,		/* Item being removed. */
2955    int *index			/* New value of Item.index. Is incremented. */
2956    )
2957{
2958    TreeItem child;
2959
2960    /* Invalidate display info. Don't free it because we may just be
2961     * moving the item to a new parent. FIXME: if it is being moved,
2962     * it might not actually need to be redrawn (just copied) */
2963    if (item->dInfo != NULL)
2964	Tree_InvalidateItemDInfo(tree, NULL, item, NULL);
2965
2966    if (item->parent != NULL)
2967	item->depth = item->parent->depth + 1;
2968    else
2969	item->depth = 0;
2970    item->index = (*index)++;
2971    item->indexVis = -1;
2972    child = item->firstChild;
2973    while (child != NULL) {
2974	RemoveFromParentAux(tree, child, index);
2975	child = child->nextSibling;
2976    }
2977}
2978
2979/*
2980 *----------------------------------------------------------------------
2981 *
2982 * TreeItem_RemoveFromParent --
2983 *
2984 *	Remove an Item from its parent (if any).
2985 *
2986 * Results:
2987 *	None.
2988 *
2989 * Side effects:
2990 *	Display changes.
2991 *
2992 *----------------------------------------------------------------------
2993 */
2994
2995void
2996TreeItem_RemoveFromParent(
2997    TreeCtrl *tree,		/* Widget info. */
2998    TreeItem item		/* Item token. */
2999    )
3000{
3001    TreeItem parent = item->parent;
3002    TreeItem last;
3003    int index = 0;
3004
3005    if (parent == NULL)
3006	return;
3007
3008    /* If this is the last child, redraw the lines of the previous
3009     * sibling and all of its descendants because the line from
3010     * the previous sibling to us is now gone */
3011    if ((item->prevSibling != NULL) &&
3012	    (item->nextSibling == NULL) &&
3013	    tree->showLines && (tree->columnTree != NULL)) {
3014	last = item->prevSibling;
3015	while (last->lastChild != NULL)
3016	    last = last->lastChild;
3017	Tree_InvalidateItemDInfo(tree, tree->columnTree,
3018		item->prevSibling, last);
3019    }
3020
3021    /* Redraw the parent if the parent has "-button auto". */
3022    if (IS_VISIBLE(item) && (parent->flags & ITEM_FLAG_BUTTON_AUTO) &&
3023	    tree->showButtons && (tree->columnTree != NULL)) {
3024	Tree_InvalidateItemDInfo(tree, tree->columnTree, parent,
3025		NULL);
3026    }
3027
3028    /*
3029     * Set a flag indicating that item indexes are out-of-date. This doesn't
3030     * cover the current item being removed.
3031     */
3032    tree->updateIndex = 1;
3033    Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
3034
3035    if (item->prevSibling)
3036	item->prevSibling->nextSibling = item->nextSibling;
3037    if (item->nextSibling)
3038	item->nextSibling->prevSibling = item->prevSibling;
3039    if (parent->firstChild == item) {
3040	parent->firstChild = item->nextSibling;
3041	if (!parent->firstChild)
3042	    parent->lastChild = NULL;
3043    }
3044    if (parent->lastChild == item)
3045	parent->lastChild = item->prevSibling;
3046    item->prevSibling = item->nextSibling = NULL;
3047    item->parent = NULL;
3048    parent->numChildren--;
3049
3050    /*
3051     * Update Item.depth, Item.index and Item.indexVis for the item and its
3052     * descendants. An up-to-date Item.index is needed for some operations that
3053     * use a range of items, such as [item delete].
3054     */
3055    RemoveFromParentAux(tree, item, &index);
3056}
3057
3058/*
3059 *----------------------------------------------------------------------
3060 *
3061 * TreeItem_RemoveColumns --
3062 *
3063 *	Free a range of Columns in an Item.
3064 *
3065 * Results:
3066 *	None.
3067 *
3068 * Side effects:
3069 *	Memory is deallocated.
3070 *
3071 *----------------------------------------------------------------------
3072 */
3073
3074void
3075TreeItem_RemoveColumns(
3076    TreeCtrl *tree,		/* Widget info. */
3077    TreeItem item,		/* Item token. */
3078    int first,			/* 0-based column index at start of
3079				 * the range. Must be <= last */
3080    int last			/* 0-based column index at end of
3081				 * the range. Must be >= first */
3082    )
3083{
3084    Column *column = item->columns;
3085    Column *prev = NULL, *next = NULL;
3086    int i = 0;
3087
3088    while (column != NULL) {
3089	next = column->next;
3090	if (i == first - 1)
3091	    prev = column;
3092	else if (i >= first)
3093	    Column_FreeResources(tree, column);
3094	if (i == last)
3095	    break;
3096	++i;
3097	column = next;
3098    }
3099    if (prev != NULL)
3100	prev->next = next;
3101    else if (first == 0)
3102	item->columns = next;
3103}
3104
3105/*
3106 *----------------------------------------------------------------------
3107 *
3108 * TreeItem_RemoveAllColumns --
3109 *
3110 *	Free all the Columns in an Item.
3111 *
3112 * Results:
3113 *	None.
3114 *
3115 * Side effects:
3116 *	Memory is deallocated.
3117 *
3118 *----------------------------------------------------------------------
3119 */
3120
3121void TreeItem_RemoveAllColumns(
3122    TreeCtrl *tree,		/* Widget info. */
3123    TreeItem item		/* Item token. */
3124    )
3125{
3126    Column *column = item->columns;
3127
3128    while (column != NULL) {
3129	Column *next = column->next;
3130	Column_FreeResources(tree, column);
3131	column = next;
3132    }
3133    item->columns = NULL;
3134}
3135
3136/*
3137 *----------------------------------------------------------------------
3138 *
3139 * Item_CreateColumn --
3140 *
3141 *	Allocate a Column record for an Item if it doesn't already
3142 *	exist.
3143 *
3144 * Results:
3145 *	Pointer to new or existing Column record.
3146 *
3147 * Side effects:
3148 *	Any column records preceding the desired one are allocated
3149 *	if they weren't already. Memory is allocated.
3150 *
3151 *----------------------------------------------------------------------
3152 */
3153
3154static Column *
3155Item_CreateColumn(
3156    TreeCtrl *tree,		/* Widget info. */
3157    TreeItem item,		/* Item to contain the column. */
3158    int columnIndex,		/* 0-based index of new column. */
3159    int *isNew			/* May be NULL. Set to TRUE if the
3160				 * column record was created. */
3161    )
3162{
3163    Column *column;
3164    int i;
3165
3166    if (isNew != NULL) (*isNew) = FALSE;
3167    column = item->columns;
3168    if (column == NULL) {
3169	column = Column_Alloc(tree);
3170	item->columns = column;
3171	if (isNew != NULL) (*isNew) = TRUE;
3172    }
3173    for (i = 0; i < columnIndex; i++) {
3174	if (column->next == NULL) {
3175	    column->next = Column_Alloc(tree);
3176	    if (isNew != NULL) (*isNew) = TRUE;
3177	}
3178	column = column->next;
3179    }
3180
3181    return column;
3182}
3183
3184/*
3185 *----------------------------------------------------------------------
3186 *
3187 * TreeItem_MoveColumn --
3188 *
3189 *	Rearranges an Item's list of Column records by moving one
3190 *	in front of another.
3191 *
3192 * Results:
3193 *	If the Column to be moved does not exist and the Column to place it
3194 *	in front of does not exist, then nothing happens. If the Column is
3195 *	to be moved past all currently allocated Columns, then new
3196 *	Column records are allocated.
3197 *
3198 * Side effects:
3199 *	Memory may be allocated.
3200 *
3201 *----------------------------------------------------------------------
3202 */
3203
3204void
3205TreeItem_MoveColumn(
3206    TreeCtrl *tree,		/* Widget info. */
3207    TreeItem item,		/* Item token. */
3208    int columnIndex,		/* 0-based index of the column to move. */
3209    int beforeIndex		/* 0-based index of the second column to move
3210				 * the first column to the left of. */
3211    )
3212{
3213    Column *before = NULL, *move = NULL;
3214    Column *prevM = NULL, *prevB = NULL;
3215    Column *last = NULL, *prev, *walk;
3216    int index = 0;
3217
3218    prev = NULL;
3219    walk = item->columns;
3220    while (walk != NULL) {
3221	if (index == columnIndex) {
3222	    prevM = prev;
3223	    move = walk;
3224	}
3225	if (index == beforeIndex) {
3226	    prevB = prev;
3227	    before = walk;
3228	}
3229	prev = walk;
3230	if (walk->next == NULL)
3231	    last = walk;
3232	index++;
3233	walk = walk->next;
3234    }
3235
3236    if (move == NULL && before == NULL)
3237	return;
3238    if (move == NULL)
3239	move = Column_Alloc(tree);
3240    else {
3241	if (before == NULL) {
3242	    prevB = Item_CreateColumn(tree, item, beforeIndex - 1, NULL);
3243	    last = prevB;
3244	}
3245	if (prevM == NULL)
3246	    item->columns = move->next;
3247	else
3248	    prevM->next = move->next;
3249    }
3250    if (before == NULL) {
3251	last->next = move;
3252	move->next = NULL;
3253    } else {
3254	if (prevB == NULL)
3255	    item->columns = move;
3256	else
3257	    prevB->next = move;
3258	move->next = before;
3259    }
3260}
3261
3262/*
3263 *----------------------------------------------------------------------
3264 *
3265 * TreeItem_FreeResources --
3266 *
3267 *	Free memory etc assocated with an Item.
3268 *
3269 * Results:
3270 *	None.
3271 *
3272 * Side effects:
3273 *	Memory is deallocated. Display changes.
3274 *
3275 *----------------------------------------------------------------------
3276 */
3277
3278void
3279TreeItem_FreeResources(
3280    TreeCtrl *tree,		/* Widget info. */
3281    TreeItem item		/* Item token. */
3282    )
3283{
3284    Column *column;
3285
3286    column = item->columns;
3287    while (column != NULL)
3288	column = Column_FreeResources(tree, column);
3289    if (item->dInfo != NULL)
3290	Tree_FreeItemDInfo(tree, item, NULL);
3291    if (item->rInfo != NULL)
3292	Tree_FreeItemRInfo(tree, item);
3293    if (item->spans != NULL)
3294	ckfree((char *) item->spans);
3295    Tk_FreeConfigOptions((char *) item, tree->itemOptionTable, tree->tkwin);
3296
3297    /* Add the item record to the "preserved" list. It will be freed later. */
3298    TreeItemList_Append(&tree->preserveItemList, item);
3299}
3300
3301/*
3302 *----------------------------------------------------------------------
3303 *
3304 * TreeItem_Release --
3305 *
3306 *	Finally free an item record when it is no longer needed.
3307 *
3308 * Results:
3309 *	None.
3310 *
3311 * Side effects:
3312 *	Memory is deallocated.
3313 *
3314 *----------------------------------------------------------------------
3315 */
3316
3317void
3318TreeItem_Release(
3319    TreeCtrl *tree,		/* Widget info. */
3320    TreeItem item		/* Item token. */
3321    )
3322{
3323#ifdef ALLOC_HAX
3324    TreeAlloc_Free(tree->allocData, ItemUid, (char *) item, sizeof(TreeItem_));
3325#else
3326    WFREE(item, TreeItem_);
3327#endif
3328}
3329
3330/*
3331 *----------------------------------------------------------------------
3332 *
3333 * Item_UseHeight --
3334 *
3335 *	Return the height used by styles in an Item.
3336 *
3337 * Results:
3338 *	Maximum height in pixels of the style in each visible Column.
3339 *
3340 * Side effects:
3341 *	None.
3342 *
3343 *----------------------------------------------------------------------
3344 */
3345
3346static int
3347Item_HeightOfStyles(
3348    TreeCtrl *tree,		/* Widget info. */
3349    TreeItem item		/* Item record. */
3350    )
3351{
3352    Column *column = item->columns;
3353    TreeColumn treeColumn = tree->columns;
3354    StyleDrawArgs drawArgs;
3355    int height = 0;
3356
3357    drawArgs.tree = tree;
3358
3359    while (column != NULL) {
3360	if (TreeColumn_Visible(treeColumn) && (column->style != NULL)) {
3361	    drawArgs.state = item->state | column->cstate;
3362	    drawArgs.style = column->style;
3363	    drawArgs.indent = (treeColumn == tree->columnTree) ?
3364		TreeItem_Indent(tree, item) : 0;
3365	    if ((TreeColumn_FixedWidth(treeColumn) != -1) ||
3366		    TreeColumn_Squeeze(treeColumn)) {
3367		drawArgs.width = TreeColumn_UseWidth(treeColumn);
3368	    } else
3369		drawArgs.width = -1;
3370	    height = MAX(height, TreeStyle_UseHeight(&drawArgs));
3371	}
3372	treeColumn = TreeColumn_Next(treeColumn);
3373	column = column->next;
3374    }
3375
3376    return height;
3377}
3378
3379/*
3380 *----------------------------------------------------------------------
3381 *
3382 * TreeItem_Height --
3383 *
3384 *	Return the height of an Item.
3385 *
3386 * Results:
3387 *	If the Item -height option is > 0, the result is the maximum
3388 *	of the button height (if a button is displayed) and the -height
3389 *	option.
3390 *	If the TreeCtrl -itemheight option is > 0, the result is the maximum
3391 *	of the button height (if a button is displayed) and the -itemheight
3392 *	option.
3393 *	Otherwise the result is the maximum of the button height (if a button
3394 *	is displayed) AND the TreeCtrl -minitemheight AND the height of
3395 *	the style in each visible Column.
3396 *
3397 * Side effects:
3398 *	None.
3399 *
3400 *----------------------------------------------------------------------
3401 */
3402
3403int TreeItem_Height(
3404    TreeCtrl *tree,		/* Widget info. */
3405    TreeItem item		/* Item token. */
3406    )
3407{
3408    int buttonHeight = 0;
3409    int useHeight;
3410
3411    if (!IS_VISIBLE(item) || (IS_ROOT(item) && !tree->showRoot))
3412	return 0;
3413
3414    /* Get requested height of the style in each column */
3415    useHeight = Item_HeightOfStyles(tree, item);
3416
3417    /* Can't have less height than our button */
3418    if (TreeItem_HasButton(tree, item)) {
3419	buttonHeight = Tree_ButtonHeight(tree, item->state);
3420    }
3421
3422    /* User specified a fixed height for this item */
3423    if (item->fixedHeight > 0)
3424	return MAX(item->fixedHeight, buttonHeight);
3425
3426    /* Fixed height of all items */
3427    if (tree->itemHeight > 0)
3428	return MAX(tree->itemHeight, buttonHeight);
3429
3430    /* Minimum height of all items */
3431    if (tree->minItemHeight > 0)
3432	useHeight = MAX(useHeight, tree->minItemHeight);
3433
3434    /* No fixed height specified */
3435    return MAX(useHeight, buttonHeight);
3436}
3437
3438/*
3439 *----------------------------------------------------------------------
3440 *
3441 * TreeItem_InvalidateHeight --
3442 *
3443 *	Marks Item.neededHeight out-of-date.
3444 *	NOTE: Item.neededHeight is unused.
3445 *
3446 * Results:
3447 *	None.
3448 *
3449 * Side effects:
3450 *	None.
3451 *
3452 *----------------------------------------------------------------------
3453 */
3454
3455void
3456TreeItem_InvalidateHeight(
3457    TreeCtrl *tree,		/* Widget info. */
3458    TreeItem item		/* Item token. */
3459    )
3460{
3461}
3462
3463/*
3464 *----------------------------------------------------------------------
3465 *
3466 * Item_FindColumn --
3467 *
3468 *	Return a Column record given a zero-based index.
3469 *
3470 * Results:
3471 *	The Column record or NULL.
3472 *
3473 * Side effects:
3474 *	None.
3475 *
3476 *----------------------------------------------------------------------
3477 */
3478
3479static Column *
3480Item_FindColumn(
3481    TreeCtrl *tree,		/* Widget info. */
3482    TreeItem item,		/* Item record. */
3483    int columnIndex		/* 0-based index of column to find. */
3484    )
3485{
3486    Column *column;
3487    int i = 0;
3488
3489    column = item->columns;
3490    if (!column)
3491	return NULL;
3492    while (column != NULL && i < columnIndex) {
3493	column = column->next;
3494	i++;
3495    }
3496    return column;
3497}
3498
3499/*
3500 *----------------------------------------------------------------------
3501 *
3502 * Item_FindColumnFromObj --
3503 *
3504 *	Return a Column record given a Tcl_Obj column description.
3505 *
3506 * Results:
3507 *	TCL_OK or TCL_ERROR.
3508 *
3509 * Side effects:
3510 *	None.
3511 *
3512 *----------------------------------------------------------------------
3513 */
3514
3515static int
3516Item_FindColumnFromObj(
3517    TreeCtrl *tree,		/* Widget info. */
3518    TreeItem item,		/* Item record. */
3519    Tcl_Obj *obj,		/* Column description. */
3520    Column **columnPtr,		/* Returned column, or NULL. */
3521    int *indexPtr		/* May be NULL. Returned 0-based index of
3522				 * the column. */
3523    )
3524{
3525    TreeColumn treeColumn;
3526    int columnIndex;
3527
3528    if (TreeColumn_FromObj(tree, obj, &treeColumn, CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK)
3529	return TCL_ERROR;
3530    columnIndex = TreeColumn_Index(treeColumn);
3531    (*columnPtr) = Item_FindColumn(tree, item, columnIndex);
3532    if (indexPtr != NULL)
3533	(*indexPtr) = columnIndex;
3534    return TCL_OK;
3535}
3536
3537/*
3538 *----------------------------------------------------------------------
3539 *
3540 * TreeItem_FindColumn --
3541 *
3542 *	Return an item-column token given a zero-based index.
3543 *
3544 * Results:
3545 *	The item-column token or NULL.
3546 *
3547 * Side effects:
3548 *	None.
3549 *
3550 *----------------------------------------------------------------------
3551 */
3552
3553TreeItemColumn
3554TreeItem_FindColumn(
3555    TreeCtrl *tree,		/* Widget info. */
3556    TreeItem item,		/* Item token. */
3557    int columnIndex		/* 0-based index of column to find. */
3558    )
3559{
3560    return (TreeItemColumn) Item_FindColumn(tree, item, columnIndex);
3561}
3562
3563/*
3564 *----------------------------------------------------------------------
3565 *
3566 * TreeItem_ColumnFromObj --
3567 *
3568 *	Return an item-column token given a Tcl_Obj column description.
3569 *
3570 * Results:
3571 *	TCL_OK or TCL_ERROR.
3572 *
3573 * Side effects:
3574 *	None.
3575 *
3576 *----------------------------------------------------------------------
3577 */
3578
3579int
3580TreeItem_ColumnFromObj(
3581    TreeCtrl *tree,		/* Widget info. */
3582    TreeItem item,		/* Item token. */
3583    Tcl_Obj *obj,		/* Column description. */
3584    TreeItemColumn *columnPtr,	/* Returned column, or NULL. */
3585    int *indexPtr		/* May be NULL. Returned 0-based index of
3586				 * the column. */
3587    )
3588{
3589    return Item_FindColumnFromObj(tree, item, obj, (Column **) columnPtr, indexPtr);
3590}
3591
3592#if 0
3593/*
3594 *----------------------------------------------------------------------
3595 *
3596 * Item_CreateColumnFromObj --
3597 *
3598 *	Return a Column record given a Tcl_Obj column description.
3599 *	The Column record is allocated if it doesn't exist.
3600 *
3601 * Results:
3602 *	TCL_OK or TCL_ERROR.
3603 *
3604 * Side effects:
3605 *	Memory may be allocated.
3606 *
3607 *----------------------------------------------------------------------
3608 */
3609
3610static int
3611Item_CreateColumnFromObj(
3612    TreeCtrl *tree,		/* Widget info. */
3613    TreeItem item,		/* Item record. */
3614    Tcl_Obj *obj,		/* Column description. */
3615    Column **column,		/* Returned column. */
3616    int *indexPtr,		/* May be NULL. Returned 0-based index of
3617				 * the column. */
3618    int *isNew			/* May be NULL. Set to TRUE if the
3619				 * column record was created. */
3620    )
3621{
3622    TreeColumn treeColumn;
3623    int columnIndex;
3624
3625    if (TreeColumn_FromObj(tree, obj, &treeColumn, CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK)
3626	return TCL_ERROR;
3627    columnIndex = TreeColumn_Index(treeColumn);
3628    (*column) = Item_CreateColumn(tree, item, columnIndex, isNew);
3629    if (indexPtr != NULL)
3630	(*indexPtr) = columnIndex;
3631    return TCL_OK;
3632}
3633#endif
3634
3635/*
3636 *----------------------------------------------------------------------
3637 *
3638 * TreeItem_Indent --
3639 *
3640 *	Return the amount of indentation for the given item. This is
3641 *	the width of the buttons/lines.
3642 *
3643 * Results:
3644 *	Pixel value >= 0.
3645 *
3646 * Side effects:
3647 *	None.
3648 *
3649 *----------------------------------------------------------------------
3650 */
3651
3652int TreeItem_Indent(
3653    TreeCtrl *tree,		/* Widget info. */
3654    TreeItem item		/* Item token. */
3655    )
3656{
3657    int depth;
3658
3659    if (IS_ROOT(item))
3660	return (tree->showRoot && tree->showButtons && tree->showRootButton)
3661	    ? tree->useIndent : 0;
3662
3663    Tree_UpdateItemIndex(tree);
3664
3665    depth = item->depth;
3666    if (tree->showRoot)
3667    {
3668	depth += 1;
3669	if (tree->showButtons && tree->showRootButton)
3670	    depth += 1;
3671    }
3672    else if (tree->showButtons && tree->showRootChildButtons)
3673	depth += 1;
3674    else if (tree->showLines && tree->showRootLines)
3675	depth += 1;
3676
3677    return tree->useIndent * depth;
3678}
3679
3680/*
3681 *----------------------------------------------------------------------
3682 *
3683 * ItemDrawBackground --
3684 *
3685 *	Draws part of the background area of an Item. The area is
3686 *	erased to the -itembackground color of the tree column or the
3687 *	TreeCtrl -background color. If the TreeCtrl -backgroundimage
3688 *	option is specified then that image is tiled over the given area.
3689 *
3690 * Results:
3691 *	None.
3692 *
3693 * Side effects:
3694 *	Stuff is drawn in a drawable.
3695 *
3696 *----------------------------------------------------------------------
3697 */
3698
3699static void
3700ItemDrawBackground(
3701    TreeCtrl *tree,		/* Widget info. */
3702    TreeColumn treeColumn,	/* Tree-column token. */
3703    TreeItem item,		/* Item record. */
3704    Column *column,		/* First column. */
3705    TreeDrawable drawable,	/* Where to draw. */
3706    int x, int y,		/* Area of the item to draw. */
3707    int width, int height,	/* ^ */
3708    int index			/* Used to select a color from the
3709				 * tree-column's -itembackground option. */
3710    )
3711{
3712    GC gc = None;
3713
3714    gc = TreeColumn_BackgroundGC(treeColumn, index);
3715    if (gc == None)
3716	gc = Tk_3DBorderGC(tree->tkwin, tree->border, TK_3D_FLAT_GC);
3717    /*
3718     * FIXME: If the background image is non-transparent, there is no
3719     * need to erase first.
3720     */
3721    XFillRectangle(tree->display, drawable.drawable, gc, x, y, width, height);
3722    if (tree->backgroundImage != NULL) {
3723	Tree_DrawTiledImage(tree, drawable.drawable, tree->backgroundImage, x, y,
3724		x + width, y + height,
3725		tree->drawableXOrigin, tree->drawableYOrigin);
3726    }
3727}
3728
3729/*
3730 *----------------------------------------------------------------------
3731 *
3732 * TreeItem_SpansInvalidate --
3733 *
3734 *	Invalidates the Item.spans field of one or all items.
3735 *
3736 * Results:
3737 *	The item(s) are removed from the TreeCtrl.itemSpansHash to
3738 *	indicate that the list of spans must be recalculated.
3739 *
3740 * Side effects:
3741 *	None.
3742 *
3743 *----------------------------------------------------------------------
3744 */
3745
3746void
3747TreeItem_SpansInvalidate(
3748    TreeCtrl *tree,		/* Widget info. */
3749    TreeItem item		/* Item token. NULL for all items. */
3750    )
3751{
3752    Tcl_HashEntry *hPtr;
3753    Tcl_HashSearch search;
3754    int count = 0;
3755
3756    if (item == NULL) {
3757	hPtr = Tcl_FirstHashEntry(&tree->itemSpansHash, &search);
3758	while (hPtr != NULL) {
3759	    item = (TreeItem) Tcl_GetHashKey(&tree->itemSpansHash, hPtr);
3760	    item->flags &= ~ITEM_FLAG_SPANS_VALID;
3761	    count++;
3762	    hPtr = Tcl_NextHashEntry(&search);
3763	}
3764	if (count) {
3765	    Tcl_DeleteHashTable(&tree->itemSpansHash);
3766	    Tcl_InitHashTable(&tree->itemSpansHash, TCL_ONE_WORD_KEYS);
3767	}
3768    } else if (item->flags & ITEM_FLAG_SPANS_VALID) {
3769	hPtr = Tcl_FindHashEntry(&tree->itemSpansHash, (char *) item);
3770	Tcl_DeleteHashEntry(hPtr);
3771	item->flags &= ~ITEM_FLAG_SPANS_VALID;
3772	count++;
3773    }
3774
3775    if (count && tree->debug.enable && tree->debug.span)
3776	dbwin("TreeItem_SpansInvalidate forgot %d items\n", count);
3777}
3778
3779/*
3780 *----------------------------------------------------------------------
3781 *
3782 * TreeItem_SpansRedo --
3783 *
3784 *	Updates the Item.spans field of an item.
3785 *
3786 * Results:
3787 *	Item.spans is resized if needed to (at least) the current number
3788 *	of tree columns. For tree column N, the index of the item
3789 *	column displayed there is written to spans[N].
3790 *
3791 *	The return value is 1 if every span is 1, otherwise 0.
3792 *
3793 * Side effects:
3794 *	Memory may be allocated.
3795 *
3796 *----------------------------------------------------------------------
3797 */
3798
3799int
3800TreeItem_SpansRedo(
3801    TreeCtrl *tree,		/* Widget info. */
3802    TreeItem item		/* Item token. */
3803    )
3804{
3805    TreeColumn treeColumn = tree->columns;
3806    Column *itemColumn = item->columns;
3807    int columnIndex = 0, spanner = 0, span = 1, simple = TRUE;
3808    int lock = TreeColumn_Lock(treeColumn);
3809
3810    if (tree->debug.enable && tree->debug.span)
3811	dbwin("TreeItem_SpansRedo item %d\n", item->id);
3812
3813    if (item->spans == NULL) {
3814	item->spans = (int *) ckalloc(sizeof(int) * tree->columnCount);
3815	item->spanAlloc = tree->columnCount;
3816    } else if (item->spanAlloc < tree->columnCount) {
3817	item->spans = (int *) ckrealloc((char *) item->spans,
3818		sizeof(int) * tree->columnCount);
3819	item->spanAlloc = tree->columnCount;
3820    }
3821
3822    while (treeColumn != NULL) {
3823	/* End current span if column lock changes. */
3824	if (TreeColumn_Lock(treeColumn) != lock) {
3825	    lock = TreeColumn_Lock(treeColumn);
3826	    span = 1;
3827	}
3828	if (--span == 0) {
3829	    if (TreeColumn_Visible(treeColumn))
3830		span = itemColumn ? itemColumn->span : 1;
3831	    else
3832		span = 1;
3833	    if (span > 1)
3834		simple = FALSE;
3835	    spanner = columnIndex;
3836	}
3837	item->spans[columnIndex] = spanner;
3838	columnIndex++;
3839	treeColumn = TreeColumn_Next(treeColumn);
3840	if (itemColumn != NULL)
3841	    itemColumn = itemColumn->next;
3842    }
3843
3844    return simple;
3845}
3846
3847/*
3848 *----------------------------------------------------------------------
3849 *
3850 * TreeItem_SpansRedoIfNeeded --
3851 *
3852 *	Updates the Item.spans field of an item if needed.
3853 *
3854 * Results:
3855 *	If all spans are known to be 1, nothing is done. If the list of
3856 *	spans is marked valid, nothing is done. Otherwise the list of
3857 *	spans is recalculated; if any span is > 1 the item is added
3858 *	to TreeCtrl.itemSpansHash.
3859 *
3860 * Side effects:
3861 *	Memory may be allocated.
3862 *
3863 *----------------------------------------------------------------------
3864 */
3865
3866void
3867TreeItem_SpansRedoIfNeeded(
3868    TreeCtrl *tree,
3869    TreeItem item
3870    )
3871{
3872    /* All the spans are 1. */
3873    if (item->flags & ITEM_FLAG_SPANS_SIMPLE)
3874	return;
3875
3876    /* Some spans > 1, but we calculated them already. */
3877    if (item->flags & ITEM_FLAG_SPANS_VALID)
3878	return;
3879
3880    if (TreeItem_SpansRedo(tree, item)) {
3881	/* Reverted to all spans=1. */
3882	item->flags |= ITEM_FLAG_SPANS_SIMPLE;
3883    } else {
3884	int isNew;
3885	Tcl_HashEntry *hPtr;
3886
3887	hPtr = Tcl_CreateHashEntry(&tree->itemSpansHash, (char *) item, &isNew);
3888	Tcl_SetHashValue(hPtr, (ClientData) item);
3889	item->flags |= ITEM_FLAG_SPANS_VALID;
3890    }
3891}
3892
3893/*
3894 *----------------------------------------------------------------------
3895 *
3896 * TreeItem_GetSpans --
3897 *
3898 *	Returns the spans[] array for an item.
3899 *
3900 * Results:
3901 *	If all spans are known to be 1, the result is NULL. Otherwise the
3902 *	list of spans is returned.
3903 *
3904 * Side effects:
3905 *	Memory may be allocated.
3906 *
3907 *----------------------------------------------------------------------
3908 */
3909
3910int *
3911TreeItem_GetSpans(
3912    TreeCtrl *tree,		/* Widget info. */
3913    TreeItem item		/* Item token. */
3914    )
3915{
3916    TreeItem_SpansRedoIfNeeded(tree, item);
3917    if (item->flags & ITEM_FLAG_SPANS_SIMPLE)
3918	return NULL;
3919    return item->spans;
3920}
3921
3922/*
3923 * The following structure holds information about which item column
3924 * is displayed at a given tree column.
3925 */
3926typedef struct SpanInfo {
3927    TreeColumn treeColumn;	/* Always non-null. */
3928    TreeItemColumn itemColumn;	/* May be null. */
3929    int span;			/* Number of tree-columns spanned. */
3930    int width;			/* Width of the span. */
3931} SpanInfo;
3932
3933/*
3934 *----------------------------------------------------------------------
3935 *
3936 * Item_GetSpans --
3937 *
3938 *	Fills an array of SpanInfo records, one per visible span.
3939 *
3940 * Results:
3941 *	The return value is the number of SpanInfo records written.
3942 *
3943 * Side effects:
3944 *	None.
3945 *
3946 *----------------------------------------------------------------------
3947 */
3948
3949static int
3950Item_GetSpans(
3951    TreeCtrl *tree,		/* Widget info. */
3952    TreeItem item,		/* Item token. */
3953    TreeColumn firstColumn,	/* Which columns. */
3954    SpanInfo spans[]		/* Returned span records. */
3955    )
3956{
3957    TreeColumn treeColumn = firstColumn;
3958    int columnIndex = TreeColumn_Index(firstColumn);
3959    Column *column = Item_FindColumn(tree, item, columnIndex);
3960    int spanCount = 0, span = 1;
3961    SpanInfo *spanPtr = NULL;
3962
3963    while (treeColumn != NULL) {
3964	if (TreeColumn_Lock(treeColumn) != TreeColumn_Lock(firstColumn))
3965	    break;
3966	if (--span == 0) {
3967	    if (TreeColumn_Visible(treeColumn)) {
3968		span = column ? column->span : 1;
3969		if (spanPtr == NULL)
3970		    spanPtr = spans;
3971		else
3972		    spanPtr++;
3973		spanPtr->treeColumn = treeColumn;
3974		spanPtr->itemColumn = (TreeItemColumn) column;
3975		spanPtr->span = 0;
3976		spanPtr->width = 0;
3977		spanCount++;
3978	    } else {
3979		span = 1;
3980		goto next;
3981	    }
3982	}
3983	spanPtr->span++;
3984	spanPtr->width += TreeColumn_UseWidth(treeColumn);
3985next:
3986	++columnIndex;
3987	treeColumn = TreeColumn_Next(treeColumn);
3988	if (column != NULL)
3989	    column = column->next;
3990    }
3991    return spanCount;
3992}
3993
3994typedef int (*TreeItemWalkSpansProc)(
3995    TreeCtrl *tree,
3996    TreeItem item,
3997    SpanInfo *spanPtr,
3998    StyleDrawArgs *drawArgs,
3999    ClientData clientData
4000    );
4001
4002/*
4003 *----------------------------------------------------------------------
4004 *
4005 * TreeItem_WalkSpans --
4006 *
4007 *	Iterates over the spans of an item and calls a callback routine
4008 *	for each span of non-zero width. This is used for drawing,
4009 *	hit-testing and other purposes.
4010 *
4011 * Results:
4012 *	None.
4013 *
4014 * Side effects:
4015 *	None.
4016 *
4017 *----------------------------------------------------------------------
4018 */
4019
4020static void
4021TreeItem_WalkSpans(
4022    TreeCtrl *tree,		/* Widget info. */
4023    TreeItem item,		/* Item token. */
4024    int lock,			/* Which columns. */
4025    int x, int y,		/* Drawable coordinates of the item. */
4026    int width, int height,	/* Total size of the item. */
4027    TreeItemWalkSpansProc proc,	/* Callback routine. */
4028    ClientData clientData	/* Data passed to callback routine. */
4029    )
4030{
4031    int columnWidth, totalWidth;
4032    Column *itemColumn;
4033    StyleDrawArgs drawArgs;
4034    TreeColumn treeColumn = tree->columnLockNone;
4035    int spanCount, spanIndex, columnCount = tree->columnCountVis;
4036    SpanInfo staticSpans[STATIC_SIZE], *spans = staticSpans;
4037    int area = TREE_AREA_CONTENT;
4038
4039    switch (lock) {
4040	case COLUMN_LOCK_LEFT:
4041	    treeColumn = tree->columnLockLeft;
4042	    columnCount = tree->columnCountVisLeft;
4043	    area = TREE_AREA_LEFT;
4044	    break;
4045	case COLUMN_LOCK_NONE:
4046	    break;
4047	case COLUMN_LOCK_RIGHT:
4048	    treeColumn = tree->columnLockRight;
4049	    columnCount = tree->columnCountVisRight;
4050	    area = TREE_AREA_RIGHT;
4051	    break;
4052    }
4053
4054    if (!Tree_AreaBbox(tree, area, &drawArgs.bounds[0], &drawArgs.bounds[1],
4055	    &drawArgs.bounds[2], &drawArgs.bounds[3])) {
4056	drawArgs.bounds[0] = drawArgs.bounds[1] =
4057	drawArgs.bounds[2] = drawArgs.bounds[3] = 0;
4058    }
4059
4060    STATIC_ALLOC(spans, SpanInfo, columnCount);
4061    spanCount = Item_GetSpans(tree, item, treeColumn, spans);
4062
4063    drawArgs.tree = tree;
4064    drawArgs.td.drawable = None;
4065
4066    totalWidth = 0;
4067    for (spanIndex = 0; spanIndex < spanCount; spanIndex++) {
4068	treeColumn = spans[spanIndex].treeColumn;
4069	itemColumn = (Column *) spans[spanIndex].itemColumn;
4070
4071	/* If this is the single visible column, use the provided width which
4072	 * may be different than the column's width. */
4073	if ((tree->columnCountVis == 1) && (treeColumn == tree->columnVis)) {
4074	    columnWidth = width;
4075
4076	/* More than one column is visible, or this is not the visible
4077	 * column. */
4078	} else {
4079	    columnWidth = spans[spanIndex].width;
4080	}
4081	if (columnWidth <= 0)
4082	    continue;
4083
4084	if (itemColumn != NULL) {
4085	    drawArgs.state = item->state | itemColumn->cstate;
4086	    drawArgs.style = itemColumn->style; /* may be NULL */
4087	} else {
4088	    drawArgs.state = item->state;
4089	    drawArgs.style = NULL;
4090	}
4091	if (treeColumn == tree->columnTree)
4092	    drawArgs.indent = TreeItem_Indent(tree, item);
4093	else
4094	    drawArgs.indent = 0;
4095	drawArgs.x = x + totalWidth;
4096	drawArgs.y = y;
4097	drawArgs.width = columnWidth;
4098	drawArgs.height = height;
4099	drawArgs.justify = TreeColumn_ItemJustify(treeColumn);
4100	if ((*proc)(tree, item, &spans[spanIndex], &drawArgs, clientData))
4101	    break;
4102
4103	totalWidth += columnWidth;
4104    }
4105
4106    STATIC_FREE(spans, SpanInfo, columnCount);
4107}
4108
4109/*
4110 *----------------------------------------------------------------------
4111 *
4112 * SpanWalkProc_Draw --
4113 *
4114 *	Callback routine to TreeItem_WalkSpans for TreeItem_Draw.
4115 *
4116 * Results:
4117 *	None.
4118 *
4119 * Side effects:
4120 *	Stuff is drawn in a drawable.
4121 *
4122 *----------------------------------------------------------------------
4123 */
4124
4125static int
4126SpanWalkProc_Draw(
4127    TreeCtrl *tree,
4128    TreeItem item,
4129    SpanInfo *spanPtr,
4130    StyleDrawArgs *drawArgs,
4131    ClientData clientData
4132    )
4133{
4134    TreeColumn treeColumn = spanPtr->treeColumn;
4135    Column *itemColumn = (Column *) spanPtr->itemColumn;
4136    int i, x;
4137    struct {
4138	TreeDrawable td;
4139	int minX;
4140	int maxX;
4141	int index;
4142    } *data = clientData;
4143
4144    /* Draw nothing if the entire span is out-of-bounds. */
4145    if ((drawArgs->x >= data->maxX) ||
4146	    (drawArgs->x + drawArgs->width <= data->minX))
4147	return 0;
4148
4149    drawArgs->td = data->td;
4150
4151    /* Draw background colors. */
4152    if (spanPtr->span == 1) {
4153	/* Important point: use drawArgs->width since an item's width may
4154	 * be totally different than tree->columnVis' width. */
4155	ItemDrawBackground(tree, treeColumn, item, itemColumn,
4156		drawArgs->td, drawArgs->x, drawArgs->y,
4157		drawArgs->width, drawArgs->height, data->index);
4158    } else {
4159	x = drawArgs->x;
4160	for (i = 0; i < spanPtr->span; i++) {
4161	    int columnWidth = TreeColumn_UseWidth(treeColumn);
4162	    if ((columnWidth > 0) && (x < data->maxX) &&
4163		    (x + columnWidth > data->minX)) {
4164		ItemDrawBackground(tree, treeColumn, item, itemColumn,
4165			drawArgs->td, x, drawArgs->y,
4166			columnWidth, drawArgs->height, data->index);
4167	    }
4168	    x += columnWidth;
4169	    treeColumn = TreeColumn_Next(treeColumn);
4170	}
4171    }
4172
4173    if (drawArgs->style != NULL) {
4174	StyleDrawArgs drawArgsCopy = *drawArgs;
4175	TreeStyle_Draw(&drawArgsCopy);
4176    }
4177
4178    if (spanPtr->treeColumn == tree->columnTree) {
4179	if (tree->showLines)
4180	    TreeItem_DrawLines(tree, item, drawArgs->x, drawArgs->y,
4181		    drawArgs->width, drawArgs->height, data->td);
4182	if (tree->showButtons)
4183	    TreeItem_DrawButton(tree, item, drawArgs->x, drawArgs->y,
4184		    drawArgs->width, drawArgs->height, data->td);
4185    }
4186
4187    /* Stop walking if we went past the right edge of the dirty area. */
4188    return drawArgs->x + drawArgs->width >= data->maxX;
4189}
4190
4191/*
4192 *----------------------------------------------------------------------
4193 *
4194 * TreeItem_Draw --
4195 *
4196 *	Draws part of an Item.
4197 *
4198 * Results:
4199 *	None.
4200 *
4201 * Side effects:
4202 *	Stuff is drawn in a drawable.
4203 *
4204 *----------------------------------------------------------------------
4205 */
4206
4207void
4208TreeItem_Draw(
4209    TreeCtrl *tree,		/* Widget info. */
4210    TreeItem item,		/* Item token. */
4211    int lock,			/* Which columns. */
4212    int x, int y,		/* Drawable coordinates of the item. */
4213    int width, int height,	/* Total size of the item. */
4214    TreeDrawable td,		/* Where to draw. */
4215    int minX, int maxX,		/* Left/right edge that needs to be drawn. */
4216    int index			/* Used to select a color from a
4217				 * tree-column's -itembackground option. */
4218    )
4219{
4220    struct {
4221	TreeDrawable td;
4222	int minX;
4223	int maxX;
4224	int index;
4225    } clientData;
4226
4227    clientData.td = td;
4228    clientData.minX = minX;
4229    clientData.maxX = maxX;
4230    clientData.index = index;
4231
4232    TreeItem_WalkSpans(tree, item, lock,
4233	    x, y, width, height,
4234	    SpanWalkProc_Draw, (ClientData) &clientData);
4235}
4236
4237/*
4238 *----------------------------------------------------------------------
4239 *
4240 * TreeItem_DrawLines --
4241 *
4242 *	Draws horizontal and vertical lines indicating parent-child
4243 *	relationship in an item.
4244 *
4245 * Results:
4246 *	None.
4247 *
4248 * Side effects:
4249 *	Stuff is drawn in a drawable.
4250 *
4251 *----------------------------------------------------------------------
4252 */
4253
4254void
4255TreeItem_DrawLines(
4256    TreeCtrl *tree,		/* Widget info. */
4257    TreeItem item,		/* Item token. */
4258    int x, int y,		/* Drawable coordinates of columnTree. */
4259    int width, int height,	/* Total size of columnTree. */
4260    TreeDrawable td		/* Where to draw. */
4261    )
4262{
4263    TreeItem parent, walk;
4264    int indent, left, lineLeft, lineTop;
4265    int hasPrev, hasNext;
4266    int i, vert = 0;
4267
4268    indent = TreeItem_Indent(tree, item);
4269
4270    /* Left edge of button/line area */
4271    left = x /* + tree->columnTreeLeft */ + indent - tree->useIndent;
4272
4273    /* Left edge of vertical line */
4274    lineLeft = left + (tree->useIndent - tree->lineThickness) / 2;
4275
4276    /* Top edge of horizontal line */
4277    lineTop = y + (height - tree->lineThickness) / 2;
4278
4279    /* NOTE: The next three checks do not call TreeItem_ReallyVisible()
4280     * since 'item' is ReallyVisible */
4281
4282    /* Check for ReallyVisible previous sibling */
4283    walk = item->prevSibling;
4284    while ((walk != NULL) && !IS_VISIBLE(walk))
4285	walk = walk->prevSibling;
4286    hasPrev = (walk != NULL);
4287
4288    /* Check for ReallyVisible parent */
4289    if ((item->parent != NULL) && (!IS_ROOT(item->parent) || tree->showRoot))
4290	hasPrev = TRUE;
4291
4292    /* Check for ReallyVisible next sibling */
4293    walk = item->nextSibling;
4294    while ((walk != NULL) && !IS_VISIBLE(walk))
4295	walk = walk->nextSibling;
4296    hasNext = (walk != NULL);
4297
4298    /* Option: Don't connect children of root item */
4299    if ((item->parent != NULL) && IS_ROOT(item->parent) && !tree->showRootLines)
4300	hasPrev = hasNext = FALSE;
4301
4302    /* Vertical line to parent and/or previous/next sibling */
4303    if (hasPrev || hasNext) {
4304	int top = y, bottom = y + height;
4305
4306	if (!hasPrev)
4307	    top = lineTop;
4308	if (!hasNext)
4309	    bottom = lineTop + tree->lineThickness;
4310
4311	if (tree->lineStyle == LINE_STYLE_DOT) {
4312	    for (i = 0; i < tree->lineThickness; i++)
4313		Tree_VDotLine(tree, td.drawable, tree->lineGC,
4314			lineLeft + i,
4315			top,
4316			bottom);
4317	} else
4318	    XFillRectangle(tree->display, td.drawable, tree->lineGC,
4319		    lineLeft,
4320		    top,
4321		    tree->lineThickness,
4322		    bottom - top);
4323
4324	/* Don't overlap horizontal line */
4325	vert = tree->lineThickness;
4326    }
4327
4328    /* Horizontal line to self */
4329    if (hasPrev || hasNext) {
4330	if (tree->lineStyle == LINE_STYLE_DOT) {
4331	    for (i = 0; i < tree->lineThickness; i++)
4332		Tree_HDotLine(tree, td.drawable, tree->lineGC,
4333			lineLeft + vert,
4334			lineTop + i,
4335			x /* + tree->columnTreeLeft */ + indent);
4336	} else
4337	    XFillRectangle(tree->display, td.drawable, tree->lineGC,
4338		    lineLeft + vert,
4339		    lineTop,
4340		    left + tree->useIndent - (lineLeft + vert),
4341		    tree->lineThickness);
4342    }
4343
4344    /* Vertical lines from ancestors to their next siblings */
4345    for (parent = item->parent;
4346	 parent != NULL;
4347	 parent = parent->parent) {
4348	lineLeft -= tree->useIndent;
4349
4350	/* Option: Don't connect children of root item */
4351	if ((parent->parent != NULL) && IS_ROOT(parent->parent) && !tree->showRootLines)
4352	    continue;
4353
4354	/* Check for ReallyVisible next sibling */
4355	item = parent->nextSibling;
4356	while ((item != NULL) && !IS_VISIBLE(item))
4357	    item = item->nextSibling;
4358
4359	if (item != NULL) {
4360	    if (tree->lineStyle == LINE_STYLE_DOT) {
4361		for (i = 0; i < tree->lineThickness; i++)
4362		    Tree_VDotLine(tree, td.drawable, tree->lineGC,
4363			    lineLeft + i,
4364			    y,
4365			    y + height);
4366	    } else
4367		XFillRectangle(tree->display, td.drawable, tree->lineGC,
4368			lineLeft,
4369			y,
4370			tree->lineThickness,
4371			height);
4372	}
4373    }
4374}
4375
4376/*
4377 *----------------------------------------------------------------------
4378 *
4379 * TreeItem_DrawButton --
4380 *
4381 *	Draws the button (if any) in an item.
4382 *
4383 * Results:
4384 *	None.
4385 *
4386 * Side effects:
4387 *	Stuff is drawn in a drawable.
4388 *
4389 *----------------------------------------------------------------------
4390 */
4391
4392void
4393TreeItem_DrawButton(
4394    TreeCtrl *tree,		/* Widget info. */
4395    TreeItem item,		/* Item token. */
4396    int x, int y,		/* Drawable coordinates of columnTree. */
4397    int width, int height,	/* Total size of columnTree. */
4398    TreeDrawable td		/* Where to draw. */
4399    )
4400{
4401    int indent, left, lineLeft, lineTop;
4402    int buttonLeft, buttonTop, w1;
4403    int macoffset = 0;
4404    Tk_Image image;
4405    Pixmap bitmap;
4406
4407    if (!TreeItem_HasButton(tree, item))
4408	return;
4409
4410#if defined(MAC_TK_CARBON)
4411    /* QuickDraw on Mac is offset by one pixel in both x and y. */
4412    macoffset = 1;
4413#endif
4414
4415    indent = TreeItem_Indent(tree, item);
4416
4417    /* Left edge of button/line area */
4418    left = x /* + tree->columnTreeLeft */ + indent - tree->useIndent;
4419
4420    image = PerStateImage_ForState(tree, &tree->buttonImage, item->state, NULL);
4421    if (image != NULL) {
4422	int imgW, imgH;
4423	Tk_SizeOfImage(image, &imgW, &imgH);
4424	Tree_RedrawImage(image, 0, 0, imgW, imgH, td,
4425	    left + (tree->useIndent - imgW) / 2,
4426	    y + (height - imgH) / 2);
4427	return;
4428    }
4429
4430    bitmap = PerStateBitmap_ForState(tree, &tree->buttonBitmap, item->state, NULL);
4431    if (bitmap != None) {
4432	int bmpW, bmpH;
4433	int bx, by;
4434	Tk_SizeOfBitmap(tree->display, bitmap, &bmpW, &bmpH);
4435	bx = left + (tree->useIndent - bmpW) / 2;
4436	by = y + (height - bmpH) / 2;
4437	Tree_DrawBitmap(tree, bitmap, td.drawable, NULL, NULL,
4438		0, 0, (unsigned int) bmpW, (unsigned int) bmpH,
4439		bx, by);
4440	return;
4441    }
4442
4443    if (tree->useTheme) {
4444	int bw, bh;
4445	if (TreeTheme_GetButtonSize(tree, td.drawable, item->state & STATE_OPEN,
4446	    &bw, &bh) == TCL_OK) {
4447	    if (TreeTheme_DrawButton(tree, td.drawable, item->state & STATE_OPEN,
4448		left + (tree->useIndent - bw) / 2, y + (height - bh) / 2,
4449		bw, bh) == TCL_OK) {
4450		return;
4451	    }
4452	}
4453    }
4454
4455    w1 = tree->buttonThickness / 2;
4456
4457    /* Left edge of vertical line */
4458    /* Make sure this matches TreeItem_DrawLines() */
4459    lineLeft = left + (tree->useIndent - tree->buttonThickness) / 2;
4460
4461    /* Top edge of horizontal line */
4462    /* Make sure this matches TreeItem_DrawLines() */
4463    lineTop = y + (height - tree->buttonThickness) / 2;
4464
4465    buttonLeft = left + (tree->useIndent - tree->buttonSize) / 2;
4466    buttonTop = y + (height - tree->buttonSize) / 2;
4467
4468    /* Erase button background */
4469    XFillRectangle(tree->display, td.drawable,
4470	    Tk_3DBorderGC(tree->tkwin, tree->border, TK_3D_FLAT_GC),
4471	    buttonLeft + tree->buttonThickness,
4472	    buttonTop + tree->buttonThickness,
4473	    tree->buttonSize - tree->buttonThickness,
4474	    tree->buttonSize - tree->buttonThickness);
4475
4476    /* Draw button outline */
4477    XDrawRectangle(tree->display, td.drawable, tree->buttonGC,
4478	    buttonLeft + w1,
4479	    buttonTop + w1,
4480	    tree->buttonSize - tree->buttonThickness + macoffset,
4481	    tree->buttonSize - tree->buttonThickness + macoffset);
4482
4483    /* Horizontal '-' */
4484    XFillRectangle(tree->display, td.drawable, tree->buttonGC,
4485	    buttonLeft + tree->buttonThickness * 2,
4486	    lineTop,
4487	    tree->buttonSize - tree->buttonThickness * 4,
4488	    tree->buttonThickness);
4489
4490    if (!(item->state & STATE_OPEN)) {
4491	/* Finish '+' */
4492	XFillRectangle(tree->display, td.drawable, tree->buttonGC,
4493		lineLeft,
4494		buttonTop + tree->buttonThickness * 2,
4495		tree->buttonThickness,
4496		tree->buttonSize - tree->buttonThickness * 4);
4497    }
4498}
4499
4500/*
4501 *----------------------------------------------------------------------
4502 *
4503 * SpanWalkProc_UpdateWindowPositions --
4504 *
4505 *	Callback routine to TreeItem_WalkSpans for
4506 *	TreeItem_UpdateWindowPositions.
4507 *
4508 * Results:
4509 *	None.
4510 *
4511 * Side effects:
4512 *	Windows in window elements may be resized/repositioned.
4513 *
4514 *----------------------------------------------------------------------
4515 */
4516
4517static int
4518SpanWalkProc_UpdateWindowPositions(
4519    TreeCtrl *tree,
4520    TreeItem item,
4521    SpanInfo *spanPtr,
4522    StyleDrawArgs *drawArgs,
4523    ClientData clientData
4524    )
4525{
4526    StyleDrawArgs drawArgsCopy;
4527    int requests;
4528
4529    if ((drawArgs->x >= drawArgs->bounds[2]) ||
4530	    (drawArgs->x + drawArgs->width <= drawArgs->bounds[0]) ||
4531	    (drawArgs->style == NULL))
4532	return 0;
4533
4534    TreeDisplay_GetReadyForTrouble(tree, &requests);
4535
4536    drawArgsCopy = *drawArgs;
4537    TreeStyle_UpdateWindowPositions(&drawArgsCopy);
4538
4539    if (TreeDisplay_WasThereTrouble(tree, requests))
4540	return 1;
4541
4542    /* Stop walking if we went past the right edge of the display area. */
4543    return drawArgs->x + drawArgs->width >= drawArgs->bounds[2];
4544}
4545
4546/*
4547 *----------------------------------------------------------------------
4548 *
4549 * TreeItem_UpdateWindowPositions --
4550 *
4551 *	Updates the geometry of any on-screen window elements. Called
4552 *	by the display code when an item was possibly scrolled.
4553 *
4554 * Results:
4555 *	None.
4556 *
4557 * Side effects:
4558 *	Windows in window elements may be resized/repositioned.
4559 *
4560 *----------------------------------------------------------------------
4561 */
4562
4563void
4564TreeItem_UpdateWindowPositions(
4565    TreeCtrl *tree,		/* Widget info. */
4566    TreeItem item,		/* Item token. */
4567    int lock,			/* Columns we care about. */
4568    int x, int y,		/* Window coordinates of the item. */
4569    int width, int height	/* Total size of the item. */
4570    )
4571{
4572    TreeItem_WalkSpans(tree, item, lock,
4573	    x, y, width, height,
4574	    SpanWalkProc_UpdateWindowPositions, (ClientData) NULL);
4575}
4576
4577/*
4578 *----------------------------------------------------------------------
4579 *
4580 * TreeItem_OnScreen --
4581 *
4582 *	Called by the display code when the item becomes visible
4583 *	(i.e., actually displayed) or hidden.
4584 *
4585 * Results:
4586 *	None.
4587 *
4588 * Side effects:
4589 *	Windows in window elements may be mapped/unmapped.
4590 *
4591 *----------------------------------------------------------------------
4592 */
4593
4594void
4595TreeItem_OnScreen(
4596    TreeCtrl *tree,		/* Widget info. */
4597    TreeItem item,		/* Item token. */
4598    int onScreen		/* TRUE if item is displayed. */
4599    )
4600{
4601#if 0
4602    Column *column = item->columns;
4603
4604    while (column != NULL) {
4605	if (column->style != NULL) {
4606	    TreeStyle_OnScreen(tree, column->style, onScreen);
4607	}
4608	column = column->next;
4609    }
4610#endif
4611}
4612
4613/*
4614 *----------------------------------------------------------------------
4615 *
4616 * TreeItem_ReallyVisible --
4617 *
4618 *	Return whether the given Item could be displayed.
4619 *
4620 * Results:
4621 *	TRUE if the item's -visible is TRUE, all of its ancestors'
4622 *	-visible options are TRUE, and all of its ancestors are open.
4623 *
4624 * Side effects:
4625 *	None.
4626 *
4627 *----------------------------------------------------------------------
4628 */
4629
4630int TreeItem_ReallyVisible(
4631    TreeCtrl *tree,		/* Widget info. */
4632    TreeItem item		/* Item token. */
4633    )
4634{
4635#if 0
4636    Tree_UpdateItemIndex(tree);
4637    return item->indexVis != -1;
4638#else
4639    TreeItem parent = item->parent;
4640
4641    if (!tree->updateIndex)
4642	return item->indexVis != -1;
4643
4644    if (!IS_VISIBLE(item))
4645	return 0;
4646    if (parent == NULL)
4647	return IS_ROOT(item) ? tree->showRoot : 0;
4648    if (IS_ROOT(parent)) {
4649	if (!IS_VISIBLE(parent))
4650	    return 0;
4651	if (!tree->showRoot)
4652	    return 1;
4653	if (!(parent->state & STATE_OPEN))
4654	    return 0;
4655    }
4656    if (!IS_VISIBLE(parent) || !(parent->state & STATE_OPEN))
4657	return 0;
4658    return TreeItem_ReallyVisible(tree, parent);
4659#endif
4660}
4661
4662/*
4663 *----------------------------------------------------------------------
4664 *
4665 * TreeItem_RootAncestor --
4666 *
4667 *	Return the toplevel ancestor of an Item.
4668 *
4669 * Results:
4670 *	Returns the root, or an orphan ancestor, or the given Item.
4671 *
4672 * Side effects:
4673 *	None.
4674 *
4675 *----------------------------------------------------------------------
4676 */
4677
4678TreeItem TreeItem_RootAncestor(
4679    TreeCtrl *tree,		/* Widget info. */
4680    TreeItem item		/* Item token. */
4681    )
4682{
4683    while (item->parent != NULL)
4684	item = item->parent;
4685    return item;
4686}
4687
4688/*
4689 *----------------------------------------------------------------------
4690 *
4691 * TreeItem_IsAncestor --
4692 *
4693 *	Determine if one Item is the ancestor of another.
4694 *
4695 * Results:
4696 *	TRUE if item1 is an ancestor of item2.
4697 *
4698 * Side effects:
4699 *	None.
4700 *
4701 *----------------------------------------------------------------------
4702 */
4703
4704int
4705TreeItem_IsAncestor(
4706    TreeCtrl *tree,		/* Widget info. */
4707    TreeItem item1,		/* Possible ancestor. */
4708    TreeItem item2		/* Item to check ancestry of. */
4709    )
4710{
4711    if (item1 == item2)
4712	return 0;
4713    while (item2 && item2 != item1)
4714	item2 = item2->parent;
4715    return item2 != NULL;
4716}
4717
4718/*
4719 *----------------------------------------------------------------------
4720 *
4721 * TreeItem_ToObj --
4722 *
4723 *	Convert an Item to a Tcl_Obj.
4724 *
4725 * Results:
4726 *	A new Tcl_Obj representing the Item.
4727 *
4728 * Side effects:
4729 *	Memory is allocated.
4730 *
4731 *----------------------------------------------------------------------
4732 */
4733
4734Tcl_Obj *TreeItem_ToObj(
4735    TreeCtrl *tree,		/* Widget info. */
4736    TreeItem item		/* Item token. */
4737    )
4738{
4739    if (tree->itemPrefixLen) {
4740	char buf[100 + TCL_INTEGER_SPACE];
4741	(void) sprintf(buf, "%s%d", tree->itemPrefix, item->id);
4742	return Tcl_NewStringObj(buf, -1);
4743    }
4744    return Tcl_NewIntObj(item->id);
4745}
4746
4747/*
4748 *----------------------------------------------------------------------
4749 *
4750 * Item_Configure --
4751 *
4752 *	This procedure is called to process an objc/objv list, plus
4753 *	the Tk option database, in order to configure (or reconfigure)
4754 *	an Item.
4755 *
4756 * Results:
4757 *	The return value is a standard Tcl result.  If TCL_ERROR is
4758 *	returned, then the interp's result contains an error message.
4759 *
4760 * Side effects:
4761 *	Configuration information gets set for item;  old resources get
4762 *	freed, if there were any.
4763 *
4764 *----------------------------------------------------------------------
4765 */
4766
4767static int Item_Configure(
4768    TreeCtrl *tree,		/* Widget info. */
4769    TreeItem item,		/* Item to configure. */
4770    int objc,			/* Number of arguments */
4771    Tcl_Obj *CONST objv[]	/* Array of arguments */
4772    )
4773{
4774    Tk_SavedOptions savedOptions;
4775    int error;
4776    Tcl_Obj *errorResult = NULL;
4777    int mask;
4778    int lastVisible = IS_VISIBLE(item);
4779    int lastWrap = IS_WRAP(item);
4780
4781    for (error = 0; error <= 1; error++) {
4782	if (error == 0) {
4783	    if (Tk_SetOptions(tree->interp, (char *) item, tree->itemOptionTable,
4784			objc, objv, tree->tkwin, &savedOptions, &mask) != TCL_OK) {
4785		mask = 0;
4786		continue;
4787	    }
4788
4789	    /* xxx */
4790
4791	    Tk_FreeSavedOptions(&savedOptions);
4792	    break;
4793	} else {
4794	    errorResult = Tcl_GetObjResult(tree->interp);
4795	    Tcl_IncrRefCount(errorResult);
4796	    Tk_RestoreSavedOptions(&savedOptions);
4797
4798	    /* xxx */
4799
4800	    Tcl_SetObjResult(tree->interp, errorResult);
4801	    Tcl_DecrRefCount(errorResult);
4802	    return TCL_ERROR;
4803	}
4804    }
4805
4806    if (mask & ITEM_CONF_SIZE) {
4807	Tree_FreeItemDInfo(tree, item, NULL);
4808	Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
4809    }
4810
4811    if (mask & ITEM_CONF_BUTTON) {
4812	if (tree->columnTree != NULL)
4813	    Tree_InvalidateItemDInfo(tree, tree->columnTree, item, NULL);
4814    }
4815
4816    if ((mask & ITEM_CONF_VISIBLE) && (IS_VISIBLE(item) != lastVisible)) {
4817
4818	/* Changing the visibility of an item can change the width of
4819	 * any column. This is due to column expansion (this item may
4820	 * be the widest item in the column) and spans > 1. */
4821	Tree_InvalidateColumnWidth(tree, NULL);
4822
4823	/* If this is the last child, redraw the lines of the previous
4824	 * sibling and all of its descendants because the line from
4825	 * the previous sibling to us is appearing/disappearing. */
4826	if ((item->prevSibling != NULL) &&
4827		(item->nextSibling == NULL) &&
4828		tree->showLines && (tree->columnTree != NULL)) {
4829	    TreeItem last = item->prevSibling;
4830	    while (last->lastChild != NULL)
4831		last = last->lastChild;
4832	    Tree_InvalidateItemDInfo(tree, tree->columnTree,
4833		    item->prevSibling,
4834		    last);
4835	}
4836
4837	/* Redraw the parent if the parent has "-button auto". */
4838	if ((item->parent != NULL) &&
4839		(item->parent->flags & ITEM_FLAG_BUTTON_AUTO) &&
4840		tree->showButtons && (tree->columnTree != NULL)) {
4841	    Tree_InvalidateItemDInfo(tree, tree->columnTree, item->parent,
4842		    NULL);
4843	}
4844
4845	tree->updateIndex = 1;
4846	Tree_DInfoChanged(tree, DINFO_REDO_RANGES | DINFO_REDO_SELECTION);
4847    }
4848
4849    if ((mask & ITEM_CONF_WRAP) && (IS_WRAP(item) != lastWrap)) {
4850	tree->updateIndex = 1;
4851	Tree_InvalidateColumnWidth(tree, NULL);
4852	Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
4853    }
4854
4855    return TCL_OK;
4856}
4857
4858/*
4859 *----------------------------------------------------------------------
4860 *
4861 * ItemCreateCmd --
4862 *
4863 *	This procedure is invoked to process the [item create] widget
4864 *	command.  See the user documentation for details on what
4865 *	it does.
4866 *
4867 * Results:
4868 *	A standard Tcl result.
4869 *
4870 * Side effects:
4871 *	See the user documentation.
4872 *
4873 *----------------------------------------------------------------------
4874 */
4875
4876static int
4877ItemCreateCmd(
4878    ClientData clientData,	/* Widget info. */
4879    Tcl_Interp *interp,		/* Current interpreter. */
4880    int objc,			/* Number of arguments. */
4881    Tcl_Obj *CONST objv[]	/* Argument values. */
4882    )
4883{
4884    TreeCtrl *tree = clientData;
4885    static CONST char *optionNames[] = { "-button", "-count", "-height",
4886	"-nextsibling", "-open", "-parent", "-prevsibling", "-returnid",
4887	"-tags", "-visible", "-wrap",
4888	(char *) NULL };
4889    enum { OPT_BUTTON, OPT_COUNT, OPT_HEIGHT, OPT_NEXTSIBLING,
4890	OPT_OPEN, OPT_PARENT, OPT_PREVSIBLING, OPT_RETURNID, OPT_TAGS,
4891	OPT_VISIBLE, OPT_WRAP };
4892    int index, i, count = 1, button = 0, returnId = 1, open = 1, visible = 1;
4893    int wrap = 0;
4894    int height = 0;
4895    TreeItem item, parent = NULL, prevSibling = NULL, nextSibling = NULL;
4896    TreeItem head = NULL, tail = NULL;
4897    Tcl_Obj *listObj = NULL, *tagsObj = NULL;
4898    TagInfo *tagInfo = NULL;
4899    TreeColumn treeColumn;
4900
4901    for (i = 3; i < objc; i += 2) {
4902	if (Tcl_GetIndexFromObj(interp, objv[i], optionNames, "option", 0,
4903		&index) != TCL_OK) {
4904	    return TCL_ERROR;
4905	}
4906	if (i + 1 == objc) {
4907	    FormatResult(interp, "missing value for \"%s\" option",
4908		    optionNames[index]);
4909	    return TCL_ERROR;
4910	}
4911	switch (index) {
4912	    case OPT_BUTTON: {
4913		int length;
4914		char *s = Tcl_GetStringFromObj(objv[i + 1], &length);
4915		if (s[0] == 'a' && strncmp(s, "auto", length) == 0) {
4916		    button = ITEM_FLAG_BUTTON_AUTO;
4917		} else {
4918		    if (Tcl_GetBooleanFromObj(interp, objv[i + 1], &button) != TCL_OK) {
4919			FormatResult(interp, "expected boolean or auto but got \"%s\"", s);
4920			return TCL_ERROR;
4921		    }
4922		    if (button) {
4923			button = ITEM_FLAG_BUTTON;
4924		    }
4925		}
4926		break;
4927	    }
4928	    case OPT_COUNT:
4929		if (Tcl_GetIntFromObj(interp, objv[i + 1], &count) != TCL_OK)
4930		    return TCL_ERROR;
4931		if (count <= 0) {
4932		    FormatResult(interp, "bad count \"%d\": must be > 0",
4933			    count);
4934		    return TCL_ERROR;
4935		}
4936		break;
4937	    case OPT_HEIGHT:
4938		if (Tk_GetPixelsFromObj(interp, tree->tkwin, objv[i + 1],
4939			&height) != TCL_OK)
4940		    return TCL_ERROR;
4941		if (height < 0) {
4942		    FormatResult(interp, "bad screen distance \"%s\": must be > 0",
4943			    Tcl_GetString(objv[i + 1]));
4944		    return TCL_ERROR;
4945		}
4946		break;
4947	    case OPT_NEXTSIBLING:
4948		if (TreeItem_FromObj(tree, objv[i + 1], &nextSibling,
4949			IFO_NOT_NULL | IFO_NOT_ROOT | IFO_NOT_ORPHAN) != TCL_OK) {
4950		    return TCL_ERROR;
4951		}
4952		parent = prevSibling = NULL;
4953		break;
4954	    case OPT_OPEN:
4955		if (Tcl_GetBooleanFromObj(interp, objv[i + 1], &open)
4956			!= TCL_OK) {
4957		    return TCL_ERROR;
4958		}
4959		break;
4960	    case OPT_PARENT:
4961		if (TreeItem_FromObj(tree, objv[i + 1], &parent, IFO_NOT_NULL) != TCL_OK) {
4962		    return TCL_ERROR;
4963		}
4964		prevSibling = nextSibling = NULL;
4965		break;
4966	    case OPT_PREVSIBLING:
4967		if (TreeItem_FromObj(tree, objv[i + 1], &prevSibling,
4968			IFO_NOT_NULL | IFO_NOT_ROOT | IFO_NOT_ORPHAN) != TCL_OK) {
4969		    return TCL_ERROR;
4970		}
4971		parent = nextSibling = NULL;
4972		break;
4973	    case OPT_RETURNID:
4974		if (Tcl_GetBooleanFromObj(interp, objv[i + 1], &returnId)
4975			!= TCL_OK) {
4976		    return TCL_ERROR;
4977		}
4978		break;
4979	    case OPT_TAGS:
4980		tagsObj = objv[i + 1];
4981		break;
4982	    case OPT_VISIBLE:
4983		if (Tcl_GetBooleanFromObj(interp, objv[i + 1], &visible)
4984			!= TCL_OK) {
4985		    return TCL_ERROR;
4986		}
4987		break;
4988	    case OPT_WRAP:
4989		if (Tcl_GetBooleanFromObj(interp, objv[i + 1], &wrap)
4990			!= TCL_OK) {
4991		    return TCL_ERROR;
4992		}
4993		break;
4994	}
4995    }
4996
4997    /* Do it here so I don't have to free it above if an error occurs. */
4998    if (tagsObj != NULL) {
4999	if (TagInfo_FromObj(tree, tagsObj, &tagInfo) != TCL_OK)
5000	    return TCL_ERROR;
5001    }
5002
5003    if (returnId)
5004	listObj = Tcl_NewListObj(0, NULL);
5005
5006    /* Don't allow non-deleted items to become children of a
5007    * deleted item. */
5008    if ((parent && IS_DELETED(parent)) ||
5009	(prevSibling && IS_DELETED(prevSibling->parent)) ||
5010	(nextSibling && IS_DELETED(nextSibling->parent)))
5011	parent = prevSibling = nextSibling = NULL;
5012
5013    for (i = 0; i < count; i++) {
5014	item = Item_Alloc(tree);
5015	item->flags &= ~(ITEM_FLAG_BUTTON | ITEM_FLAG_BUTTON_AUTO);
5016	item->flags |= button;
5017	if (visible) item->flags |= ITEM_FLAG_VISIBLE;
5018	else item->flags &= ~ITEM_FLAG_VISIBLE;
5019	if (open) item->state |= STATE_OPEN;
5020	else item->state &= ~STATE_OPEN;
5021	if (wrap) item->flags |= ITEM_FLAG_WRAP;
5022	else item->flags &= ~ITEM_FLAG_WRAP;
5023	item->fixedHeight = height;
5024
5025	/* Apply each column's -itemstyle option. */
5026	for (treeColumn = tree->columns; treeColumn != NULL;
5027		treeColumn = TreeColumn_Next(treeColumn)) {
5028	    TreeStyle style = TreeColumn_ItemStyle(treeColumn);
5029	    if (style != NULL) {
5030		Column *column = Item_CreateColumn(tree, item,
5031			TreeColumn_Index(treeColumn), NULL);
5032		column->style = TreeStyle_NewInstance(tree, style);
5033	    }
5034	}
5035#ifdef DEPRECATED
5036	/* Apply default styles */
5037	if (tree->defaultStyle.numStyles) {
5038	    int i, n = MIN(tree->columnCount, tree->defaultStyle.numStyles);
5039
5040	    for (i = 0; i < n; i++) {
5041		Column *column = Item_CreateColumn(tree, item, i, NULL);
5042		if (column->style != NULL)
5043		    continue;
5044		if (tree->defaultStyle.styles[i] != NULL) {
5045		    column->style = TreeStyle_NewInstance(tree,
5046			    tree->defaultStyle.styles[i]);
5047		}
5048	    }
5049	}
5050#endif /* DEPRECATED */
5051
5052	if (tagInfo != NULL) {
5053	    if (count == 1) {
5054		item->tagInfo = tagInfo;
5055		tagInfo = NULL;
5056	    } else {
5057		item->tagInfo = TagInfo_Copy(tree, tagInfo);
5058	    }
5059	}
5060
5061	/* Link the new items together as siblings */
5062	if (parent || prevSibling || nextSibling) {
5063	    if (head == NULL)
5064		head = item;
5065	    if (tail != NULL) {
5066		tail->nextSibling = item;
5067		item->prevSibling = tail;
5068	    }
5069	    tail = item;
5070	}
5071
5072	if (returnId)
5073	    Tcl_ListObjAppendElement(interp, listObj, TreeItem_ToObj(tree,
5074		    item));
5075    }
5076
5077    if (parent != NULL) {
5078	head->prevSibling = parent->lastChild;
5079	if (parent->lastChild != NULL)
5080	    parent->lastChild->nextSibling = head;
5081	else
5082	    parent->firstChild = head;
5083	parent->lastChild = tail;
5084    } else if (prevSibling != NULL) {
5085	parent = prevSibling->parent;
5086	if (prevSibling->nextSibling != NULL)
5087	    prevSibling->nextSibling->prevSibling = tail;
5088	else
5089	    parent->lastChild = tail;
5090	head->prevSibling = prevSibling;
5091	tail->nextSibling = prevSibling->nextSibling;
5092	prevSibling->nextSibling = head;
5093    } else if (nextSibling != NULL) {
5094	parent = nextSibling->parent;
5095	if (nextSibling->prevSibling != NULL)
5096	    nextSibling->prevSibling->nextSibling = head;
5097	else
5098	    parent->firstChild = head;
5099	head->prevSibling = nextSibling->prevSibling;
5100	tail->nextSibling = nextSibling;
5101	nextSibling->prevSibling = tail;
5102    }
5103
5104    if (parent != NULL) {
5105	for (item = head; item != NULL; item = item->nextSibling) {
5106	    item->parent = parent;
5107	    item->depth = parent->depth + 1;
5108	}
5109	parent->numChildren += count;
5110	TreeItem_AddToParent(tree, head);
5111    }
5112
5113    TagInfo_Free(tree, tagInfo);
5114
5115    if (returnId)
5116	Tcl_SetObjResult(interp, listObj);
5117
5118    return TCL_OK;
5119}
5120
5121/*
5122 *----------------------------------------------------------------------
5123 *
5124 * NoStyleMsg --
5125 *
5126 *	Utility to set the interpreter result with a message indicating
5127 *	a Column has no assigned style.
5128 *
5129 * Results:
5130 *	None.
5131 *
5132 * Side effects:
5133 *	Interpreter result is changed.
5134 *
5135 *----------------------------------------------------------------------
5136 */
5137
5138static void
5139NoStyleMsg(
5140    TreeCtrl *tree,		/* Widget info. */
5141    TreeItem item,		/* Item record. */
5142    int columnIndex		/* 0-based index of the column that
5143				 * has no style. */
5144    )
5145{
5146    FormatResult(tree->interp,
5147	    "item %s%d column %s%d has no style",
5148	    tree->itemPrefix, item->id,
5149	    tree->columnPrefix,
5150	    TreeColumn_GetID(Tree_FindColumn(tree, columnIndex)));
5151}
5152
5153/*
5154 *----------------------------------------------------------------------
5155 *
5156 * ItemElementCmd --
5157 *
5158 *	This procedure is invoked to process the [item element] widget
5159 *	command.  See the user documentation for details on what
5160 *	it does.
5161 *
5162 * Results:
5163 *	A standard Tcl result.
5164 *
5165 * Side effects:
5166 *	See the user documentation.
5167 *
5168 *----------------------------------------------------------------------
5169 */
5170
5171static int
5172ItemElementCmd(
5173    ClientData clientData,	/* Widget info. */
5174    Tcl_Interp *interp,		/* Current interpreter. */
5175    int objc,			/* Number of arguments. */
5176    Tcl_Obj *CONST objv[]	/* Argument values. */
5177    )
5178{
5179    TreeCtrl *tree = clientData;
5180    static CONST char *commandNames[] = {
5181#ifdef DEPRECATED
5182	"actual",
5183#endif
5184	"cget", "configure",
5185	"perstate", (char *) NULL };
5186    enum {
5187#ifdef DEPRECATED
5188    COMMAND_ACTUAL,
5189#endif
5190    COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_PERSTATE };
5191    int index;
5192    int columnIndex;
5193    Column *column;
5194    TreeItemList itemList;
5195    TreeItem item;
5196    int flags = IFO_NOT_NULL;
5197    int result = TCL_OK;
5198
5199    if (objc < 7) {
5200	Tcl_WrongNumArgs(interp, 3, objv, "command item column element ?arg ...?");
5201	return TCL_ERROR;
5202    }
5203
5204    if (Tcl_GetIndexFromObj(interp, objv[3], commandNames, "command", 0,
5205		&index) != TCL_OK)
5206	return TCL_ERROR;
5207
5208    /*
5209     * [configure] without an option-value pair can operate on a single item
5210     * only. [cget] and [perstate] only operate on a single item.
5211     */
5212    if ((index != COMMAND_CONFIGURE) || (objc < 9))
5213	flags |= IFO_NOT_MANY;
5214
5215    if (TreeItemList_FromObj(tree, objv[4], &itemList, flags) != TCL_OK)
5216	return TCL_ERROR;
5217    item = TreeItemList_Nth(&itemList, 0);
5218
5219    switch (index) {
5220	/* T item element perstate I C E option ?stateList? */
5221#ifdef DEPRECATED
5222	case COMMAND_ACTUAL:
5223#endif
5224	case COMMAND_PERSTATE: {
5225	    int state;
5226
5227	    if (objc < 8 || objc > 9) {
5228		Tcl_WrongNumArgs(tree->interp, 4, objv,
5229			"item column element option ?stateList?");
5230		result = TCL_ERROR;
5231		break;
5232	    }
5233	    if (Item_FindColumnFromObj(tree, item, objv[5], &column,
5234		    &columnIndex) != TCL_OK) {
5235		result = TCL_ERROR;
5236		break;
5237	    }
5238	    if ((column == NULL) || (column->style == NULL)) {
5239		NoStyleMsg(tree, item, columnIndex);
5240		result = TCL_ERROR;
5241		break;
5242	    }
5243	    state = item->state | column->cstate;
5244	    if (objc == 9) {
5245		int states[3];
5246
5247		if (Tree_StateFromListObj(tree, objv[8], states,
5248			    SFO_NOT_OFF | SFO_NOT_TOGGLE) != TCL_OK) {
5249		    result = TCL_ERROR;
5250		    break;
5251		}
5252		state = states[STATE_OP_ON];
5253	    }
5254	    result = TreeStyle_ElementActual(tree, column->style,
5255		    state, objv[6], objv[7]);
5256	    break;
5257	}
5258
5259	/* T item element cget I C E option */
5260	case COMMAND_CGET: {
5261	    if (objc != 8) {
5262		Tcl_WrongNumArgs(tree->interp, 4, objv,
5263			"item column element option");
5264		result = TCL_ERROR;
5265		break;
5266	    }
5267	    if (Item_FindColumnFromObj(tree, item, objv[5], &column,
5268		    &columnIndex) != TCL_OK) {
5269		result = TCL_ERROR;
5270		break;
5271	    }
5272	    if ((column == NULL) || (column->style == NULL)) {
5273		NoStyleMsg(tree, item, columnIndex);
5274		result = TCL_ERROR;
5275		break;
5276	    }
5277	    result = TreeStyle_ElementCget(tree, item,
5278		    (TreeItemColumn) column, column->style, objv[6], objv[7]);
5279	    break;
5280	}
5281
5282	/* T item element configure I C E ... */
5283	case COMMAND_CONFIGURE: {
5284	    struct columnObj {
5285		TreeColumnList columns;
5286		int isColumn;
5287		int numArgs;
5288	    } staticCO[STATIC_SIZE], *co = staticCO;
5289	    int i, index, indexElem, prevColumn;
5290	    ItemForEach iter;
5291
5292	    /* If no option-value pair is given, we can't specify more than
5293	     * one column. */
5294	    flags = CFO_NOT_NULL | CFO_NOT_TAIL;
5295	    if (objc < 9)
5296		flags |= CFO_NOT_MANY;
5297
5298	    STATIC_ALLOC(co, struct columnObj, objc);
5299	    for (i = 5; i < objc; i++) {
5300		co[i].isColumn = FALSE;
5301		co[i].numArgs = -1;
5302	    }
5303	    indexElem = 6;
5304
5305	    /* Get the first column(s) */
5306	    i = indexElem - 1;
5307	    if (TreeColumnList_FromObj(tree, objv[i], &co[i].columns,
5308		    flags) != TCL_OK) {
5309		result = TCL_ERROR;
5310		break;
5311	    }
5312	    co[i].isColumn = TRUE;
5313	    prevColumn = i;
5314
5315	    while (1) {
5316		int numArgs = 0;
5317		char breakChar = '\0';
5318
5319		/* Look for a + or , */
5320		for (index = indexElem + 1; index < objc; index++) {
5321		    if (numArgs % 2 == 0) {
5322			int length;
5323			char *s = Tcl_GetStringFromObj(objv[index], &length);
5324
5325			if ((length == 1) && ((s[0] == '+') || (s[0] == ','))) {
5326			    breakChar = s[0];
5327			    break;
5328			}
5329		    }
5330		    numArgs++;
5331		}
5332
5333		/* Require at least one option-value pair if more than one
5334		* element is specified. */
5335		if ((breakChar || indexElem != 6) && (numArgs < 2)) {
5336		    FormatResult(interp,
5337			"missing option-value pair after element \"%s\"",
5338			Tcl_GetString(objv[indexElem]));
5339		    result = TCL_ERROR;
5340		    goto doneCONF;
5341		}
5342
5343		co[indexElem].numArgs = numArgs;
5344
5345		if (!breakChar)
5346		    break;
5347
5348		if (index == objc - 1) {
5349		    FormatResult(interp, "missing %s after \"%c\"",
5350			(breakChar == '+') ? "element name" : "column",
5351			breakChar);
5352		    result = TCL_ERROR;
5353		    goto doneCONF;
5354		}
5355
5356		/* + indicates start of another element */
5357		if (breakChar == '+') {
5358		    indexElem = index + 1;
5359		}
5360
5361		/* , indicates start of another column */
5362		else if (breakChar == ',') {
5363		    co[prevColumn].numArgs = index - prevColumn;
5364
5365		    if (TreeColumnList_FromObj(tree, objv[index + 1],
5366			    &co[index + 1].columns, CFO_NOT_NULL |
5367			    CFO_NOT_TAIL) != TCL_OK) {
5368			result = TCL_ERROR;
5369			goto doneCONF;
5370		    }
5371		    co[index + 1].isColumn = TRUE;
5372		    prevColumn = index + 1;
5373
5374		    indexElem = index + 2;
5375		    if (indexElem == objc) {
5376			FormatResult(interp,
5377			    "missing element name after column \"%s\"",
5378			    Tcl_GetString(objv[index + 1]));
5379			result = TCL_ERROR;
5380			goto doneCONF;
5381		    }
5382		}
5383	    }
5384	    co[prevColumn].numArgs = index - prevColumn;
5385
5386	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
5387		/* T item element configure I C E option value \
5388		*     + E option value , C E option value */
5389		int iMask = 0;
5390
5391		/* co[index].numArgs is the number of arguments from the C
5392		 * to the next separator (but not including that separator). */
5393		for (index = 5; index < objc; index += co[index].numArgs + 1) {
5394		    ColumnForEach citer;
5395		    TreeColumn treeColumn;
5396
5397if (!co[index].isColumn) panic("isColumn == FALSE");
5398
5399		    COLUMN_FOR_EACH(treeColumn, &co[index].columns, NULL, &citer) {
5400			int columnIndex, cMask = 0;
5401
5402			columnIndex = TreeColumn_Index(treeColumn);
5403			column = Item_FindColumn(tree, item, columnIndex);
5404			if ((column == NULL) || (column->style == NULL)) {
5405			    NoStyleMsg(tree, item, columnIndex);
5406			    result = TCL_ERROR;
5407			    break;
5408			}
5409
5410			indexElem = index + 1;
5411
5412			/* Do each element in this column */
5413			while (1) {
5414			    int eMask, index2;
5415
5416if (co[indexElem].numArgs == -1) panic("indexElem=%d (%s) objc=%d numArgs == -1", indexElem, Tcl_GetString(objv[indexElem]), objc);
5417			    result = TreeStyle_ElementConfigure(tree, item,
5418				    (TreeItemColumn) column, column->style, objv[indexElem],
5419				    co[indexElem].numArgs, (Tcl_Obj **) objv + indexElem + 1, &eMask);
5420			    if (result != TCL_OK)
5421				break;
5422
5423			    cMask |= eMask;
5424
5425			    /* co[indexElem].numArgs is the number of
5426			     * option-value arguments after the element. */
5427			    index2 = indexElem + co[indexElem].numArgs;
5428			    if (index2 == objc - 1)
5429				break;
5430
5431			    /* Skip the '+' or ',' */
5432			    index2 += 2;
5433
5434			    if (co[index2].isColumn)
5435				break;
5436
5437			    indexElem = index2;
5438			}
5439
5440			if (cMask & CS_LAYOUT) {
5441			    TreeItemColumn_InvalidateSize(tree, (TreeItemColumn) column);
5442			    Tree_InvalidateColumnWidth(tree, treeColumn);
5443			} else if (cMask & CS_DISPLAY) {
5444			    Tree_InvalidateItemDInfo(tree, treeColumn, item, NULL);
5445			}
5446			iMask |= cMask;
5447			if (result != TCL_OK)
5448			    break;
5449		    }
5450		    if (result != TCL_OK)
5451			break;
5452		}
5453		if (iMask & CS_LAYOUT) {
5454		    TreeItem_InvalidateHeight(tree, item);
5455		    Tree_FreeItemDInfo(tree, item, NULL);
5456		    Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
5457		} else if (iMask & CS_DISPLAY) {
5458		}
5459		if (result != TCL_OK)
5460		    break;
5461	    }
5462doneCONF:
5463	    for (i = 5; i < objc; i++) {
5464		if (co[i].isColumn)
5465		    TreeColumnList_Free(&co[i].columns);
5466	    }
5467	    STATIC_FREE(co, struct columnObj, objc);
5468	    break;
5469	}
5470    }
5471
5472    TreeItemList_Free(&itemList);
5473    return result;
5474}
5475
5476/*
5477 *----------------------------------------------------------------------
5478 *
5479 * ItemStyleCmd --
5480 *
5481 *	This procedure is invoked to process the [item style] widget
5482 *	command.  See the user documentation for details on what
5483 *	it does.
5484 *
5485 * Results:
5486 *	A standard Tcl result.
5487 *
5488 * Side effects:
5489 *	See the user documentation.
5490 *
5491 *----------------------------------------------------------------------
5492 */
5493
5494static int
5495ItemStyleCmd(
5496    ClientData clientData,	/* Widget info. */
5497    Tcl_Interp *interp,		/* Current interpreter. */
5498    int objc,			/* Number of arguments. */
5499    Tcl_Obj *CONST objv[]	/* Argument values. */
5500    )
5501{
5502    TreeCtrl *tree = clientData;
5503    static CONST char *commandNames[] = { "elements", "map", "set", (char *) NULL };
5504    enum { COMMAND_ELEMENTS, COMMAND_MAP, COMMAND_SET };
5505    int index;
5506    TreeItemList itemList;
5507    TreeItem item;
5508    int flags = IFO_NOT_NULL;
5509    int result = TCL_OK;
5510
5511    if (objc < 5) {
5512	Tcl_WrongNumArgs(interp, 3, objv, "command item ?arg ...?");
5513	return TCL_ERROR;
5514    }
5515
5516    if (Tcl_GetIndexFromObj(interp, objv[3], commandNames, "command", 0,
5517		&index) != TCL_OK) {
5518	return TCL_ERROR;
5519    }
5520
5521    /* [style elements] only works on a single item.
5522     * [style set] only works on a single item without a column-style pair. */
5523    if ((index == COMMAND_ELEMENTS) || (index == COMMAND_SET && objc < 7))
5524	flags |= IFO_NOT_MANY;
5525    if (TreeItemList_FromObj(tree, objv[4], &itemList, flags) != TCL_OK) {
5526	return TCL_ERROR;
5527    }
5528    item = TreeItemList_Nth(&itemList, 0);
5529
5530    switch (index) {
5531	/* T item style elements I C */
5532	case COMMAND_ELEMENTS: {
5533	    Column *column;
5534	    int columnIndex;
5535
5536	    if (objc != 6) {
5537		Tcl_WrongNumArgs(interp, 4, objv, "item column");
5538		result = TCL_ERROR;
5539		break;
5540	    }
5541	    if (Item_FindColumnFromObj(tree, item, objv[5], &column,
5542		    &columnIndex) != TCL_OK) {
5543		result = TCL_ERROR;
5544		break;
5545	    }
5546	    if ((column == NULL) || (column->style == NULL)) {
5547		NoStyleMsg(tree, item, columnIndex);
5548		result = TCL_ERROR;
5549		break;
5550	    }
5551	    TreeStyle_ListElements(tree, column->style);
5552	    break;
5553	}
5554
5555	/* T item style map I C S map */
5556	case COMMAND_MAP: {
5557	    TreeStyle style;
5558	    TreeColumnList columns;
5559	    TreeColumn treeColumn;
5560	    Column *column;
5561	    int columnIndex;
5562	    int objcM;
5563	    Tcl_Obj **objvM;
5564	    ItemForEach iter;
5565	    ColumnForEach citer;
5566
5567	    if (objc != 8) {
5568		Tcl_WrongNumArgs(interp, 4, objv, "item column style map");
5569		return TCL_ERROR;
5570	    }
5571	    if (TreeColumnList_FromObj(tree, objv[5], &columns,
5572		    CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK) {
5573		result = TCL_ERROR;
5574		break;
5575	    }
5576	    if (TreeStyle_FromObj(tree, objv[6], &style) != TCL_OK) {
5577		result = TCL_ERROR;
5578		goto doneMAP;
5579	    }
5580	    if (Tcl_ListObjGetElements(interp, objv[7], &objcM, &objvM)
5581		    != TCL_OK) {
5582		result = TCL_ERROR;
5583		goto doneMAP;
5584	    }
5585	    if (objcM & 1) {
5586		FormatResult(interp, "list must contain even number of elements");
5587		result = TCL_ERROR;
5588		goto doneMAP;
5589	    }
5590	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
5591		COLUMN_FOR_EACH(treeColumn, &columns, NULL, &citer) {
5592		    columnIndex = TreeColumn_Index(treeColumn);
5593		    column = Item_CreateColumn(tree, item, columnIndex, NULL);
5594		    if (column->style != NULL) {
5595			if (TreeStyle_Remap(tree, column->style, style, objcM,
5596				objvM) != TCL_OK) {
5597			    result = TCL_ERROR;
5598			    break;
5599			}
5600		    } else {
5601			column->style = TreeStyle_NewInstance(tree, style);
5602		    }
5603		    TreeItemColumn_InvalidateSize(tree, (TreeItemColumn) column);
5604		    Tree_InvalidateColumnWidth(tree, treeColumn);
5605		}
5606		TreeItem_InvalidateHeight(tree, item);
5607		Tree_FreeItemDInfo(tree, item, NULL);
5608		if (result != TCL_OK)
5609		    break;
5610	    }
5611	    Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
5612doneMAP:
5613	    TreeColumnList_Free(&columns);
5614	    break;
5615	}
5616
5617	/* T item style set I ?C? ?S? ?C S ...?*/
5618	case COMMAND_SET: {
5619	    struct columnStyle {
5620		TreeColumnList columns;
5621		TreeStyle style;
5622	    };
5623	    struct columnStyle staticCS[STATIC_SIZE], *cs = staticCS;
5624	    TreeColumn treeColumn;
5625	    Column *column;
5626	    int i, count = 0, length, changed = FALSE, changedI;
5627	    ItemForEach iter;
5628	    ColumnForEach citer;
5629
5630	    if (objc < 5) {
5631		Tcl_WrongNumArgs(interp, 4, objv, "item ?column? ?style? ?column style ...?");
5632		return TCL_ERROR;
5633	    }
5634	    /* Return list of styles. */
5635	    if (objc == 5) {
5636		Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
5637		treeColumn = tree->columns;
5638		column = item->columns;
5639		while (treeColumn != NULL) {
5640		    if ((column != NULL) && (column->style != NULL))
5641			Tcl_ListObjAppendElement(interp, listObj,
5642				TreeStyle_ToObj(TreeStyle_GetMaster(
5643				tree, column->style)));
5644		    else
5645			Tcl_ListObjAppendElement(interp, listObj,
5646				Tcl_NewObj());
5647		    treeColumn = TreeColumn_Next(treeColumn);
5648		    if (column != NULL)
5649			column = column->next;
5650		}
5651		Tcl_SetObjResult(interp, listObj);
5652		break;
5653	    }
5654	    /* Return style in one column. */
5655	    if (objc == 6) {
5656		if (Item_FindColumnFromObj(tree, item, objv[5], &column, NULL) != TCL_OK)
5657		    return TCL_ERROR;
5658		if ((column != NULL) && (column->style != NULL))
5659		    Tcl_SetObjResult(interp, TreeStyle_ToObj(
5660			TreeStyle_GetMaster(tree, column->style)));
5661		break;
5662	    }
5663	    /* Get column/style pairs. */
5664	    STATIC_ALLOC(cs, struct columnStyle, objc / 2);
5665	    for (i = 5; i < objc; i += 2) {
5666		if (TreeColumnList_FromObj(tree, objv[i], &cs[count].columns,
5667			CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK) {
5668		    result = TCL_ERROR;
5669		    goto doneSET;
5670		}
5671		if (i + 1 == objc) {
5672		    FormatResult(interp, "missing style for column \"%s\"",
5673			Tcl_GetString(objv[i]));
5674		    result = TCL_ERROR;
5675		    goto doneSET;
5676		}
5677		(void) Tcl_GetStringFromObj(objv[i + 1], &length);
5678		if (length == 0) {
5679		    cs[count].style = NULL;
5680		} else {
5681		    if (TreeStyle_FromObj(tree, objv[i + 1], &cs[count].style)
5682			    != TCL_OK) {
5683			result = TCL_ERROR;
5684			goto doneSET;
5685		    }
5686		}
5687		count++;
5688	    }
5689	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
5690		changedI = FALSE;
5691		for (i = 0; i < count; i++) {
5692		    COLUMN_FOR_EACH(treeColumn, &cs[i].columns, NULL, &citer) {
5693			if (cs[i].style == NULL) {
5694			    column = Item_FindColumn(tree, item,
5695				    TreeColumn_Index(treeColumn));
5696			    if (column == NULL || column->style == NULL)
5697				continue;
5698			    TreeItemColumn_ForgetStyle(tree,
5699				    (TreeItemColumn) column);
5700			} else {
5701			    column = Item_CreateColumn(tree, item,
5702				    TreeColumn_Index(treeColumn), NULL);
5703			    if (column->style != NULL) {
5704				if (TreeStyle_GetMaster(tree, column->style)
5705					== cs[i].style)
5706				    continue;
5707				TreeItemColumn_ForgetStyle(tree,
5708					(TreeItemColumn) column);
5709			    }
5710			    column->style = TreeStyle_NewInstance(tree,
5711				    cs[i].style);
5712			}
5713			TreeItemColumn_InvalidateSize(tree,
5714				(TreeItemColumn) column);
5715			Tree_InvalidateColumnWidth(tree, treeColumn);
5716			changedI = TRUE;
5717		    }
5718		    if (changedI) {
5719			TreeItem_InvalidateHeight(tree, item);
5720			Tree_FreeItemDInfo(tree, item, NULL);
5721			changed = TRUE;
5722		    }
5723		}
5724	    }
5725	    if (changed)
5726		Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
5727doneSET:
5728	    for (i = 0; i < count; i++) {
5729		TreeColumnList_Free(&cs[i].columns);
5730	    }
5731	    STATIC_FREE(cs, struct columnStyle, objc / 2);
5732	    break;
5733	}
5734    }
5735
5736    TreeItemList_Free(&itemList);
5737    return result;
5738}
5739
5740/* Quicksort is not a "stable" sorting algorithm, but it can become a
5741 * stable sort by using the pre-sort order of two items as a tie-breaker
5742 * for items that would otherwise be considered equal. */
5743#define STABLE_SORT
5744
5745/* one per column per SortItem */
5746struct SortItem1
5747{
5748    long longValue;
5749    double doubleValue;
5750    char *string;
5751};
5752
5753/* one per Item */
5754struct SortItem
5755{
5756    TreeItem item;
5757    struct SortItem1 *item1;
5758    Tcl_Obj *obj; /* TreeItem_ToObj() */
5759#ifdef STABLE_SORT
5760    int index; /* The pre-sort order of the item */
5761#endif
5762};
5763
5764typedef struct SortData SortData;
5765
5766/* Used to process -element option */
5767struct SortElement
5768{
5769    TreeStyle style;
5770    TreeElement elem;
5771    int elemIndex;
5772};
5773
5774/* One per TreeColumn */
5775struct SortColumn
5776{
5777    int (*proc)(SortData *, struct SortItem *, struct SortItem *, int);
5778    int sortBy;
5779    int column;
5780    int order;
5781    Tcl_Obj *command;
5782    struct SortElement elems[20];
5783    int elemCount;
5784};
5785
5786/* Data for sort as a whole */
5787struct SortData
5788{
5789    TreeCtrl *tree;
5790    struct SortItem *items;
5791    struct SortItem1 *item1s; /* SortItem.item1 points in here */
5792#define MAX_SORT_COLUMNS 40
5793    struct SortColumn columns[MAX_SORT_COLUMNS];
5794    int columnCount; /* max number of columns to compare */
5795    int result;
5796};
5797
5798/* from Tcl 8.4.0 */
5799static int
5800DictionaryCompare(
5801    char *left,
5802    char *right
5803    )
5804{
5805    Tcl_UniChar uniLeft, uniRight, uniLeftLower, uniRightLower;
5806    int diff, zeros;
5807    int secondaryDiff = 0;
5808
5809    while (1) {
5810	if (isdigit(UCHAR(*right)) && isdigit(UCHAR(*left))) { /* INTL: digit */
5811	    /*
5812	     * There are decimal numbers embedded in the two
5813	     * strings.  Compare them as numbers, rather than
5814	     * strings.  If one number has more leading zeros than
5815	     * the other, the number with more leading zeros sorts
5816	     * later, but only as a secondary choice.
5817	     */
5818
5819	    zeros = 0;
5820	    while ((*right == '0') && (isdigit(UCHAR(right[1])))) {
5821		right++;
5822		zeros--;
5823	    }
5824	    while ((*left == '0') && (isdigit(UCHAR(left[1])))) {
5825		left++;
5826		zeros++;
5827	    }
5828	    if (secondaryDiff == 0) {
5829		secondaryDiff = zeros;
5830	    }
5831
5832	    /*
5833	     * The code below compares the numbers in the two
5834	     * strings without ever converting them to integers.  It
5835	     * does this by first comparing the lengths of the
5836	     * numbers and then comparing the digit values.
5837	     */
5838
5839	    diff = 0;
5840	    while (1) {
5841		if (diff == 0) {
5842		    diff = UCHAR(*left) - UCHAR(*right);
5843		}
5844		right++;
5845		left++;
5846		if (!isdigit(UCHAR(*right))) {	/* INTL: digit */
5847		    if (isdigit(UCHAR(*left))) {	/* INTL: digit */
5848			return 1;
5849		    } else {
5850			/*
5851			 * The two numbers have the same length. See
5852			 * if their values are different.
5853			 */
5854
5855			if (diff != 0) {
5856			    return diff;
5857			}
5858			break;
5859		    }
5860		} else if (!isdigit(UCHAR(*left))) {	/* INTL: digit */
5861		    return -1;
5862		}
5863	    }
5864	    continue;
5865	}
5866
5867	/*
5868	 * Convert character to Unicode for comparison purposes.  If either
5869	 * string is at the terminating null, do a byte-wise comparison and
5870	 * bail out immediately.
5871	 */
5872
5873	if ((*left != '\0') && (*right != '\0')) {
5874	    left += Tcl_UtfToUniChar(left, &uniLeft);
5875	    right += Tcl_UtfToUniChar(right, &uniRight);
5876	    /*
5877	     * Convert both chars to lower for the comparison, because
5878	     * dictionary sorts are case insensitve.  Covert to lower, not
5879	     * upper, so chars between Z and a will sort before A (where most
5880	     * other interesting punctuations occur)
5881	     */
5882	    uniLeftLower = Tcl_UniCharToLower(uniLeft);
5883	    uniRightLower = Tcl_UniCharToLower(uniRight);
5884	} else {
5885	    diff = UCHAR(*left) - UCHAR(*right);
5886	    break;
5887	}
5888
5889	diff = uniLeftLower - uniRightLower;
5890	if (diff) {
5891	    return diff;
5892	} else if (secondaryDiff == 0) {
5893	    if (Tcl_UniCharIsUpper(uniLeft) &&
5894		    Tcl_UniCharIsLower(uniRight)) {
5895		secondaryDiff = -1;
5896	    } else if (Tcl_UniCharIsUpper(uniRight) &&
5897		    Tcl_UniCharIsLower(uniLeft)) {
5898		secondaryDiff = 1;
5899	    }
5900	}
5901    }
5902    if (diff == 0) {
5903	diff = secondaryDiff;
5904    }
5905    return diff;
5906}
5907
5908static int
5909CompareAscii(
5910    SortData *sortData,
5911    struct SortItem *a,
5912    struct SortItem *b,
5913    int n			/* Column index. */
5914    )
5915{
5916    char *left  = a->item1[n].string;
5917    char *right = b->item1[n].string;
5918
5919    /* make sure to handle case where no string value has been set */
5920    if (left == NULL) {
5921	return ((right == NULL) ? 0 : (0 - UCHAR(*right)));
5922    } else if (right == NULL) {
5923	return UCHAR(*left);
5924    } else {
5925	return strcmp(left, right);
5926    }
5927}
5928
5929static int
5930CompareDict(
5931    SortData *sortData,
5932    struct SortItem *a,
5933    struct SortItem *b,
5934    int n			/* Column index. */
5935    )
5936{
5937    char *left  = a->item1[n].string;
5938    char *right = b->item1[n].string;
5939
5940    /* make sure to handle case where no string value has been set */
5941    if (left == NULL) {
5942	return ((right == NULL) ? 0 : (0 - UCHAR(*right)));
5943    } else if (right == NULL) {
5944	return UCHAR(*left);
5945    } else {
5946	return DictionaryCompare(left, right);
5947    }
5948}
5949
5950static int
5951CompareDouble(
5952    SortData *sortData,
5953    struct SortItem *a,
5954    struct SortItem *b,
5955    int n			/* Column index. */
5956    )
5957{
5958    return (a->item1[n].doubleValue < b->item1[n].doubleValue) ? -1 :
5959	((a->item1[n].doubleValue == b->item1[n].doubleValue) ? 0 : 1);
5960}
5961
5962static int
5963CompareLong(
5964    SortData *sortData,
5965    struct SortItem *a,
5966    struct SortItem *b,
5967    int n			/* Column index. */
5968    )
5969{
5970    return (a->item1[n].longValue < b->item1[n].longValue) ? -1 :
5971	((a->item1[n].longValue == b->item1[n].longValue) ? 0 : 1);
5972}
5973
5974static int
5975CompareCmd(
5976    SortData *sortData,
5977    struct SortItem *a,
5978    struct SortItem *b,
5979    int n			/* Column index. */
5980    )
5981{
5982    Tcl_Interp *interp = sortData->tree->interp;
5983    Tcl_Obj **objv, *paramObjv[2];
5984    int objc, v;
5985
5986    paramObjv[0] = a->obj;
5987    paramObjv[1] = b->obj;
5988
5989    Tcl_ListObjLength(interp, sortData->columns[n].command, &objc);
5990    Tcl_ListObjReplace(interp, sortData->columns[n].command, objc - 2,
5991	    2, 2, paramObjv);
5992    Tcl_ListObjGetElements(interp, sortData->columns[n].command,
5993	    &objc, &objv);
5994
5995    sortData->result = Tcl_EvalObjv(interp, objc, objv, 0);
5996
5997    if (sortData->result != TCL_OK) {
5998	Tcl_AddErrorInfo(interp, "\n    (evaluating item sort -command)");
5999	return 0;
6000    }
6001
6002    sortData->result = Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &v);
6003    if (sortData->result != TCL_OK) {
6004	Tcl_ResetResult(interp);
6005	Tcl_AppendToObj(Tcl_GetObjResult(interp),
6006		"-command returned non-numeric result", -1);
6007	return 0;
6008    }
6009
6010    return v;
6011}
6012
6013static int
6014CompareProc(
6015    SortData *sortData,
6016    struct SortItem *a,
6017    struct SortItem *b
6018    )
6019{
6020    int i, v;
6021
6022    if (a->item == b->item)
6023	return 0;
6024
6025    for (i = 0; i < sortData->columnCount; i++) {
6026	v = (*sortData->columns[i].proc)(sortData, a, b, i);
6027
6028	/* -command returned error */
6029	if (sortData->result != TCL_OK)
6030	    return 0;
6031
6032	if (v != 0) {
6033	    if (i && (sortData->columns[i].order != sortData->columns[0].order))
6034		v *= -1;
6035	    return v;
6036	}
6037    }
6038#ifdef STABLE_SORT
6039    return ((a->index < b->index) == sortData->columns[0].order) ? -1 : 1;
6040#else
6041    return 0;
6042#endif
6043}
6044
6045/* BEGIN custom quicksort() */
6046
6047static int
6048find_pivot(
6049    SortData *sortData,
6050    struct SortItem *left,
6051    struct SortItem *right,
6052    struct SortItem *pivot
6053    )
6054{
6055    struct SortItem *a, *b, *c, *p, *tmp;
6056    int v;
6057
6058    a = left;
6059    b = (left + (right - left) / 2);
6060    c = right;
6061
6062    /* Arrange a <= b <= c. */
6063    v = CompareProc(sortData, a, b);
6064    if (sortData->result != TCL_OK)
6065	return 0;
6066    if (v > 0) { tmp = a; a = b; b = tmp; }
6067
6068    v = CompareProc(sortData, a, c);
6069    if (sortData->result != TCL_OK)
6070	return 0;
6071    if (v > 0) { tmp = a; a = c; c = tmp; }
6072
6073    v = CompareProc(sortData, b, c);
6074    if (sortData->result != TCL_OK)
6075	return 0;
6076    if (v > 0) { tmp = b; b = c; c = tmp; }
6077
6078    /* if (a < b) pivot = b */
6079    v = CompareProc(sortData, a, b);
6080    if (sortData->result != TCL_OK)
6081	return 0;
6082    if (v < 0) {
6083	(*pivot) = *b;
6084	return 1;
6085    }
6086
6087    /* if (b < c) pivot = c */
6088    v = CompareProc(sortData, b, c);
6089    if (sortData->result != TCL_OK)
6090	return 0;
6091    if (v < 0) {
6092	(*pivot) = *c;
6093	return 1;
6094    }
6095
6096    for (p = left + 1; p <= right; p++) {
6097	int v = CompareProc(sortData, p, left);
6098	if (sortData->result != TCL_OK)
6099	    return 0;
6100	if (v != 0) {
6101	    (*pivot) = (v < 0) ? *left : *p;
6102	    return 1;
6103	}
6104    }
6105    return 0;
6106}
6107
6108/* If the user provides a -command which does not properly compare two
6109 * elements, quicksort may go into an infinite loop or access illegal memory.
6110 * This #define indicates parts of the code which are not part of a normal
6111 * quicksort, but are present to detect the aforementioned bugs. */
6112#define BUGGY_COMMAND
6113
6114static struct SortItem *
6115partition(
6116    SortData *sortData,
6117    struct SortItem *left,
6118    struct SortItem *right,
6119    struct SortItem *pivot
6120    )
6121{
6122    int v;
6123#ifdef BUGGY_COMMAND
6124    struct SortItem *min = left, *max = right;
6125#endif
6126
6127    while (left <= right) {
6128	/*
6129	  while (*left < *pivot)
6130	    ++left;
6131	*/
6132	while (1) {
6133	    v = CompareProc(sortData, left, pivot);
6134	    if (sortData->result != TCL_OK)
6135		return NULL;
6136	    if (v >= 0)
6137		break;
6138#ifdef BUGGY_COMMAND
6139	    /* If -command always returns < 0, 'left' becomes invalid */
6140	    if (left == max)
6141		goto buggy;
6142#endif
6143	    left++;
6144	}
6145	/*
6146	  while (*right >= *pivot)
6147	    --right;
6148	*/
6149	while (1) {
6150	    v = CompareProc(sortData, right, pivot);
6151	    if (sortData->result != TCL_OK)
6152		return NULL;
6153	    if (v < 0)
6154		break;
6155#ifdef BUGGY_COMMAND
6156	    /* If -command always returns >= 0, 'right' becomes invalid */
6157	    if (right == min)
6158		goto buggy;
6159#endif
6160	    right--;
6161	}
6162	if (left < right) {
6163	    struct SortItem tmp = *left;
6164	    *left = *right;
6165	    *right = tmp;
6166	    left++;
6167	    right--;
6168	}
6169    }
6170    return left;
6171#ifdef BUGGY_COMMAND
6172    buggy:
6173    FormatResult(sortData->tree->interp, "buggy item sort -command detected");
6174    sortData->result = TCL_ERROR;
6175    return NULL;
6176#endif
6177}
6178
6179static void
6180quicksort(
6181    SortData *sortData,
6182    struct SortItem *left,
6183    struct SortItem *right
6184    )
6185{
6186    struct SortItem *p, pivot;
6187
6188    if (sortData->result != TCL_OK)
6189	return;
6190
6191    if (left == right)
6192	return;
6193
6194    /* FIXME: switch to insertion sort or similar when the number of
6195     * elements is small. */
6196
6197    if (find_pivot(sortData, left, right, &pivot) == 1) {
6198	p = partition(sortData, left, right, &pivot);
6199	quicksort(sortData, left, p - 1);
6200	quicksort(sortData, p, right);
6201    }
6202}
6203
6204/* END custom quicksort() */
6205
6206/*
6207 *----------------------------------------------------------------------
6208 *
6209 * ItemSortCmd --
6210 *
6211 *	This procedure is invoked to process the [item sort] widget
6212 *	command.  See the user documentation for details on what
6213 *	it does.
6214 *
6215 * Results:
6216 *	A standard Tcl result.
6217 *
6218 * Side effects:
6219 *	See the user documentation.
6220 *
6221 *----------------------------------------------------------------------
6222 */
6223
6224static int
6225ItemSortCmd(
6226    ClientData clientData,	/* Widget info. */
6227    Tcl_Interp *interp,		/* Current interpreter. */
6228    int objc,			/* Number of arguments. */
6229    Tcl_Obj *CONST objv[]	/* Argument values. */
6230    )
6231{
6232    TreeCtrl *tree = clientData;
6233    TreeItem item, first, last, walk, lastChild;
6234    Column *column;
6235    int i, j, count, elemIndex, index, indexF = 0, indexL = 0;
6236    int sawColumn = FALSE, sawCmd = FALSE;
6237    static int (*sortProc[5])(SortData *, struct SortItem *, struct SortItem *, int) =
6238	{ CompareAscii, CompareDict, CompareDouble, CompareLong, CompareCmd };
6239    SortData sortData;
6240    TreeColumn treeColumn;
6241    struct SortElement *elemPtr;
6242    int notReally = FALSE;
6243    int result = TCL_OK;
6244
6245    if (objc < 4) {
6246	Tcl_WrongNumArgs(interp, 3, objv, "item ?option ...?");
6247	return TCL_ERROR;
6248    }
6249
6250    if (TreeItem_FromObj(tree, objv[3], &item, IFO_NOT_NULL) != TCL_OK)
6251	return TCL_ERROR;
6252
6253    /* If the item has no children, then nothing is done and no error
6254     * is generated. */
6255    if (item->numChildren < 1)
6256	return TCL_OK;
6257
6258    /* Defaults: sort ascii strings in column 0 only */
6259    sortData.tree = tree;
6260    sortData.columnCount = 1;
6261    sortData.columns[0].column = 0;
6262    sortData.columns[0].sortBy = SORT_ASCII;
6263    sortData.columns[0].order = 1;
6264    sortData.columns[0].elemCount = 0;
6265    sortData.result = TCL_OK;
6266
6267    first = item->firstChild;
6268    last = item->lastChild;
6269
6270    for (i = 4; i < objc; ) {
6271	static CONST char *optionName[] = { "-ascii", "-column", "-command",
6272					    "-decreasing", "-dictionary", "-element", "-first", "-increasing",
6273					    "-integer", "-last", "-notreally", "-real", NULL };
6274	int numArgs[] = { 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 1, 1 };
6275	enum { OPT_ASCII, OPT_COLUMN, OPT_COMMAND, OPT_DECREASING, OPT_DICT,
6276	       OPT_ELEMENT, OPT_FIRST, OPT_INCREASING, OPT_INTEGER, OPT_LAST,
6277	       OPT_NOT_REALLY, OPT_REAL };
6278
6279	if (Tcl_GetIndexFromObj(interp, objv[i], optionName, "option", 0,
6280		    &index) != TCL_OK)
6281	    return TCL_ERROR;
6282	if (objc - i < numArgs[index]) {
6283	    FormatResult(interp, "missing value for \"%s\" option",
6284		    optionName[index]);
6285	    return TCL_ERROR;
6286	}
6287	switch (index) {
6288	    case OPT_ASCII:
6289		sortData.columns[sortData.columnCount - 1].sortBy = SORT_ASCII;
6290		break;
6291	    case OPT_COLUMN:
6292		if (TreeColumn_FromObj(tree, objv[i + 1], &treeColumn,
6293			    CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK)
6294		    return TCL_ERROR;
6295		/* The first -column we see is the first column we compare */
6296		if (sawColumn) {
6297		    if (sortData.columnCount + 1 > MAX_SORT_COLUMNS) {
6298			FormatResult(interp,
6299				"can't compare more than %d columns",
6300				MAX_SORT_COLUMNS);
6301			return TCL_ERROR;
6302		    }
6303		    sortData.columnCount++;
6304		    /* Defaults for this column */
6305		    sortData.columns[sortData.columnCount - 1].sortBy = SORT_ASCII;
6306		    sortData.columns[sortData.columnCount - 1].order = 1;
6307		    sortData.columns[sortData.columnCount - 1].elemCount = 0;
6308		}
6309		sortData.columns[sortData.columnCount - 1].column = TreeColumn_Index(treeColumn);
6310		sawColumn = TRUE;
6311		break;
6312	    case OPT_COMMAND:
6313		sortData.columns[sortData.columnCount - 1].command = objv[i + 1];
6314		sortData.columns[sortData.columnCount - 1].sortBy = SORT_COMMAND;
6315		sawCmd = TRUE;
6316		break;
6317	    case OPT_DECREASING:
6318		sortData.columns[sortData.columnCount - 1].order = 0;
6319		break;
6320	    case OPT_DICT:
6321		sortData.columns[sortData.columnCount - 1].sortBy = SORT_DICT;
6322		break;
6323	    case OPT_ELEMENT: {
6324		int listObjc;
6325		Tcl_Obj **listObjv;
6326
6327		if (Tcl_ListObjGetElements(interp, objv[i + 1], &listObjc,
6328			    &listObjv) != TCL_OK)
6329		    return TCL_ERROR;
6330		elemPtr = sortData.columns[sortData.columnCount - 1].elems;
6331		sortData.columns[sortData.columnCount - 1].elemCount = 0;
6332		if (listObjc == 0) {
6333		} else if (listObjc == 1) {
6334		    if (TreeElement_FromObj(tree, listObjv[0], &elemPtr->elem)
6335			    != TCL_OK) {
6336			Tcl_AddErrorInfo(interp,
6337				"\n    (processing -element option)");
6338			return TCL_ERROR;
6339		    }
6340		    if (!TreeElement_IsType(tree, elemPtr->elem, "text")) {
6341			FormatResult(interp,
6342				"element %s is not of type \"text\"",
6343				Tcl_GetString(listObjv[0]));
6344			Tcl_AddErrorInfo(interp,
6345				"\n    (processing -element option)");
6346			return TCL_ERROR;
6347		    }
6348		    elemPtr->style = NULL;
6349		    elemPtr->elemIndex = -1;
6350		    sortData.columns[sortData.columnCount - 1].elemCount++;
6351		} else {
6352		    if (listObjc & 1) {
6353			FormatResult(interp,
6354				"list must have even number of elements");
6355			Tcl_AddErrorInfo(interp,
6356				"\n    (processing -element option)");
6357			return TCL_ERROR;
6358		    }
6359		    for (j = 0; j < listObjc; j += 2) {
6360			if ((TreeStyle_FromObj(tree, listObjv[j],
6361				     &elemPtr->style) != TCL_OK) ||
6362				(TreeElement_FromObj(tree, listObjv[j + 1],
6363					&elemPtr->elem) != TCL_OK) ||
6364				(TreeStyle_FindElement(tree, elemPtr->style,
6365					elemPtr->elem, &elemPtr->elemIndex) != TCL_OK)) {
6366			    Tcl_AddErrorInfo(interp,
6367				    "\n    (processing -element option)");
6368			    return TCL_ERROR;
6369			}
6370			if (!TreeElement_IsType(tree, elemPtr->elem, "text")) {
6371			    FormatResult(interp,
6372				    "element %s is not of type \"text\"",
6373				    Tcl_GetString(listObjv[j + 1]));
6374			    Tcl_AddErrorInfo(interp,
6375				    "\n    (processing -element option)");
6376			    return TCL_ERROR;
6377			}
6378			sortData.columns[sortData.columnCount - 1].elemCount++;
6379			elemPtr++;
6380		    }
6381		}
6382		break;
6383	    }
6384	    case OPT_FIRST:
6385		if (TreeItem_FromObj(tree, objv[i + 1], &first, IFO_NOT_NULL) != TCL_OK)
6386		    return TCL_ERROR;
6387		if (first->parent != item) {
6388		    FormatResult(interp,
6389			    "item %s%d is not a child of item %s%d",
6390			    tree->itemPrefix, first->id, tree->itemPrefix, item->id);
6391		    return TCL_ERROR;
6392		}
6393		break;
6394	    case OPT_INCREASING:
6395		sortData.columns[sortData.columnCount - 1].order = 1;
6396		break;
6397	    case OPT_INTEGER:
6398		sortData.columns[sortData.columnCount - 1].sortBy = SORT_LONG;
6399		break;
6400	    case OPT_LAST:
6401		if (TreeItem_FromObj(tree, objv[i + 1], &last, IFO_NOT_NULL) != TCL_OK)
6402		    return TCL_ERROR;
6403		if (last->parent != item) {
6404		    FormatResult(interp,
6405			    "item %s%d is not a child of item %s%d",
6406			    tree->itemPrefix, last->id, tree->itemPrefix, item->id);
6407		    return TCL_ERROR;
6408		}
6409		break;
6410	    case OPT_NOT_REALLY:
6411		notReally = TRUE;
6412		break;
6413	    case OPT_REAL:
6414		sortData.columns[sortData.columnCount - 1].sortBy = SORT_DOUBLE;
6415		break;
6416	}
6417	i += numArgs[index];
6418    }
6419
6420    /* If there are no columns, we cannot perform a sort unless -command
6421     * is specified. */
6422    if ((tree->columnCount < 1) && (sortData.columns[0].sortBy != SORT_COMMAND)) {
6423	FormatResult(interp, "there are no columns");
6424	return TCL_ERROR;
6425    }
6426
6427    /* If there is only one item to sort, then return early. */
6428    if (first == last) {
6429	if (notReally)
6430	    Tcl_SetObjResult(interp, TreeItem_ToObj(tree, first));
6431	return TCL_OK;
6432    }
6433
6434    for (i = 0; i < sortData.columnCount; i++) {
6435
6436	/* Initialize the sort procedure for this column. */
6437	sortData.columns[i].proc = sortProc[sortData.columns[i].sortBy];
6438
6439	/* Append two dummy args to the -command argument. These two dummy
6440	 * args are replaced by the 2 item ids being compared. See
6441	 * CompareCmd(). */
6442	if (sortData.columns[i].sortBy == SORT_COMMAND) {
6443	    Tcl_Obj *obj = Tcl_DuplicateObj(sortData.columns[i].command);
6444	    Tcl_Obj *obj2 = Tcl_NewObj();
6445	    Tcl_IncrRefCount(obj);
6446	    if (Tcl_ListObjAppendElement(interp, obj, obj2) != TCL_OK) {
6447		Tcl_DecrRefCount(obj);
6448		Tcl_IncrRefCount(obj2);
6449		Tcl_DecrRefCount(obj2);
6450
6451		for (j = 0; j < i; j++) {
6452		    if (sortData.columns[j].sortBy == SORT_COMMAND) {
6453			Tcl_DecrRefCount(sortData.columns[j].command);
6454		    }
6455		}
6456
6457		return TCL_ERROR;
6458	    }
6459	    (void) Tcl_ListObjAppendElement(interp, obj, obj2);
6460	    sortData.columns[i].command = obj;
6461	}
6462    }
6463
6464    index = 0;
6465    walk = item->firstChild;
6466    while (walk != NULL) {
6467	if (walk == first)
6468	    indexF = index;
6469	if (walk == last)
6470	    indexL = index;
6471	index++;
6472	walk = walk->nextSibling;
6473    }
6474    if (indexF > indexL) {
6475	walk = last;
6476	last = first;
6477	first = walk;
6478
6479	index = indexL;
6480	indexL = indexF;
6481	indexF = index;
6482    }
6483    count = indexL - indexF + 1;
6484
6485    sortData.item1s = (struct SortItem1 *) ckalloc(sizeof(struct SortItem1) * count * sortData.columnCount);
6486    sortData.items = (struct SortItem *) ckalloc(sizeof(struct SortItem) * count);
6487    for (i = 0; i < count; i++) {
6488	sortData.items[i].item1 = sortData.item1s + i * sortData.columnCount;
6489	sortData.items[i].obj = NULL;
6490    }
6491
6492    index = 0;
6493    walk = first;
6494    while (walk != last->nextSibling) {
6495	struct SortItem *sortItem = &sortData.items[index];
6496
6497	sortItem->item = walk;
6498#ifdef STABLE_SORT
6499	sortItem->index = index;
6500#endif
6501	if (sawCmd) {
6502	    Tcl_Obj *obj = TreeItem_ToObj(tree, walk);
6503	    Tcl_IncrRefCount(obj);
6504	    sortData.items[index].obj = obj;
6505	}
6506	for (i = 0; i < sortData.columnCount; i++) {
6507	    struct SortItem1 *sortItem1 = sortItem->item1 + i;
6508
6509	    if (sortData.columns[i].sortBy == SORT_COMMAND)
6510		continue;
6511
6512	    column = Item_FindColumn(tree, walk, sortData.columns[i].column);
6513	    if ((column == NULL) || (column->style == NULL)) {
6514		NoStyleMsg(tree, walk, sortData.columns[i].column);
6515		result = TCL_ERROR;
6516		goto done;
6517	    }
6518
6519	    /* -element was empty. Find the first text element in the style */
6520	    if (sortData.columns[i].elemCount == 0)
6521		elemIndex = -1;
6522
6523	    /* -element was element name. Find the element in the style */
6524	    else if ((sortData.columns[i].elemCount == 1) &&
6525		    (sortData.columns[i].elems[0].style == NULL)) {
6526		if (TreeStyle_FindElement(tree, column->style,
6527			    sortData.columns[i].elems[0].elem, &elemIndex) != TCL_OK) {
6528		    result = TCL_ERROR;
6529		    goto done;
6530		}
6531	    }
6532
6533	    /* -element was style/element pair list */
6534	    else {
6535		TreeStyle masterStyle = TreeStyle_GetMaster(tree, column->style);
6536
6537		/* If the item style does not match any in the -element list,
6538		 * we will use the first text element in the item style. */
6539		elemIndex = -1;
6540
6541		/* Match a style from the -element list. Look in reverse order
6542		 * to handle duplicates. */
6543		for (j = sortData.columns[i].elemCount - 1; j >= 0; j--) {
6544		    if (sortData.columns[i].elems[j].style == masterStyle) {
6545			elemIndex = sortData.columns[i].elems[j].elemIndex;
6546			break;
6547		    }
6548		}
6549	    }
6550	    if (TreeStyle_GetSortData(tree, column->style, elemIndex,
6551			sortData.columns[i].sortBy,
6552			&sortItem1->longValue,
6553			&sortItem1->doubleValue,
6554			&sortItem1->string) != TCL_OK) {
6555		char msg[128];
6556		sprintf(msg, "\n    (preparing to sort item %s%d column %s%d)",
6557			tree->itemPrefix, walk->id,
6558			tree->columnPrefix, TreeColumn_GetID(
6559			Tree_FindColumn(tree, sortData.columns[i].column)));
6560		Tcl_AddErrorInfo(interp, msg);
6561		result = TCL_ERROR;
6562		goto done;
6563	    }
6564	}
6565	index++;
6566	walk = walk->nextSibling;
6567    }
6568
6569    quicksort(&sortData, sortData.items, sortData.items + count - 1);
6570
6571    if (sortData.result != TCL_OK) {
6572	result = sortData.result;
6573	goto done;
6574    }
6575
6576    if (sawCmd)
6577	Tcl_ResetResult(interp);
6578
6579    if (notReally) {
6580	Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
6581	Tcl_Obj *itemObj;
6582
6583	/* Smallest to largest */
6584	if (sortData.columns[0].order == 1) {
6585	    for (i = 0; i < count; i++) {
6586		itemObj = sortData.items[i].obj;
6587		if (itemObj == NULL)
6588		    itemObj = TreeItem_ToObj(tree,
6589			    sortData.items[i].item);
6590		Tcl_ListObjAppendElement(interp, listObj, itemObj);
6591	    }
6592	}
6593
6594	/* Largest to smallest */
6595	else {
6596	    for (i = count - 1; i >= 0; i--) {
6597		itemObj = sortData.items[i].obj;
6598		if (itemObj == NULL)
6599		    itemObj = TreeItem_ToObj(tree,
6600			    sortData.items[i].item);
6601		Tcl_ListObjAppendElement(interp, listObj, itemObj);
6602	    }
6603	}
6604
6605	Tcl_SetObjResult(interp, listObj);
6606	goto done;
6607    }
6608    first = first->prevSibling;
6609    last = last->nextSibling;
6610
6611    /* Smallest to largest */
6612    if (sortData.columns[0].order == 1) {
6613	for (i = 0; i < count - 1; i++) {
6614	    sortData.items[i].item->nextSibling = sortData.items[i + 1].item;
6615	    sortData.items[i + 1].item->prevSibling = sortData.items[i].item;
6616	}
6617	indexF = 0;
6618	indexL = count - 1;
6619    }
6620
6621    /* Largest to smallest */
6622    else {
6623	for (i = count - 1; i > 0; i--) {
6624	    sortData.items[i].item->nextSibling = sortData.items[i - 1].item;
6625	    sortData.items[i - 1].item->prevSibling = sortData.items[i].item;
6626	}
6627	indexF = count - 1;
6628	indexL = 0;
6629    }
6630
6631    lastChild = item->lastChild;
6632
6633    sortData.items[indexF].item->prevSibling = first;
6634    if (first)
6635	first->nextSibling = sortData.items[indexF].item;
6636    else
6637	item->firstChild = sortData.items[indexF].item;
6638
6639    sortData.items[indexL].item->nextSibling = last;
6640    if (last)
6641	last->prevSibling = sortData.items[indexL].item;
6642    else
6643	item->lastChild = sortData.items[indexL].item;
6644
6645    /* Redraw the lines of the old/new lastchild */
6646    if ((item->lastChild != lastChild) && tree->showLines && (tree->columnTree != NULL)) {
6647	if (lastChild->dInfo != NULL)
6648	    Tree_InvalidateItemDInfo(tree, tree->columnTree,
6649		    lastChild,
6650		    NULL);
6651	if (item->lastChild->dInfo != NULL)
6652	    Tree_InvalidateItemDInfo(tree, tree->columnTree,
6653		    item->lastChild,
6654		    NULL);
6655    }
6656
6657    tree->updateIndex = 1;
6658    Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
6659
6660    done:
6661    for (i = 0; i < count; i++) {
6662	if (sortData.items[i].obj != NULL) {
6663	    Tcl_DecrRefCount(sortData.items[i].obj);
6664	}
6665    }
6666    for (i = 0; i < sortData.columnCount; i++) {
6667	if (sortData.columns[i].sortBy == SORT_COMMAND) {
6668	    Tcl_DecrRefCount(sortData.columns[i].command);
6669	}
6670    }
6671    ckfree((char *) sortData.item1s);
6672    ckfree((char *) sortData.items);
6673
6674    if (tree->debug.enable && tree->debug.data) {
6675	Tree_Debug(tree);
6676    }
6677
6678    return result;
6679}
6680
6681/*
6682 *----------------------------------------------------------------------
6683 *
6684 * TreeItemList_Sort --
6685 *
6686 *	Sorts a list of items.
6687 *
6688 * Results:
6689 *	None.
6690 *
6691 * Side effects:
6692 *	None.
6693 *
6694 *----------------------------------------------------------------------
6695 */
6696
6697static int
6698TILSCompare(
6699    CONST VOID *first_,
6700    CONST VOID *second_
6701    )
6702{
6703    TreeItem first = *(TreeItem *) first_;
6704    TreeItem second = *(TreeItem *) second_;
6705
6706    return first->index - second->index;
6707}
6708
6709void
6710TreeItemList_Sort(
6711    TreeItemList *items
6712    )
6713{
6714    Tree_UpdateItemIndex(items->tree);
6715
6716    /* TkTable uses this, but mentions possible lack of thread-safety. */
6717    qsort((VOID *) TreeItemList_Items(items),
6718	    (size_t) TreeItemList_Count(items),
6719	    sizeof(TreeItem),
6720	    TILSCompare);
6721}
6722
6723/*
6724 *----------------------------------------------------------------------
6725 *
6726 * ItemStateCmd --
6727 *
6728 *	This procedure is invoked to process the [item state] widget
6729 *	command.  See the user documentation for details on what
6730 *	it does.
6731 *
6732 * Results:
6733 *	A standard Tcl result.
6734 *
6735 * Side effects:
6736 *	See the user documentation.
6737 *
6738 *----------------------------------------------------------------------
6739 */
6740
6741static int
6742ItemStateCmd(
6743    ClientData clientData,	/* Widget info. */
6744    Tcl_Interp *interp,		/* Current interpreter. */
6745    int objc,			/* Number of arguments. */
6746    Tcl_Obj *CONST objv[]	/* Argument values. */
6747    )
6748{
6749    TreeCtrl *tree = clientData;
6750    static CONST char *commandNames[] = {
6751	"forcolumn", "get", "set", (char *) NULL
6752    };
6753    enum {
6754	COMMAND_FORCOLUMN, COMMAND_GET, COMMAND_SET
6755    };
6756    int index;
6757    TreeItem item;
6758
6759    if (objc < 5) {
6760	Tcl_WrongNumArgs(interp, 3, objv, "command item ?arg ...?");
6761	return TCL_ERROR;
6762    }
6763
6764    if (Tcl_GetIndexFromObj(interp, objv[3], commandNames, "command", 0,
6765		&index) != TCL_OK)
6766	return TCL_ERROR;
6767
6768    switch (index) {
6769	/* T item state forcolumn I C ?stateList? */
6770	case COMMAND_FORCOLUMN: {
6771	    TreeItemList itemList;
6772	    TreeColumnList columns;
6773	    TreeColumn treeColumn;
6774	    Tcl_Obj *listObj;
6775	    Column *column;
6776	    int columnIndex;
6777	    int i, states[3], stateOn, stateOff;
6778	    ItemForEach iter;
6779	    ColumnForEach citer;
6780	    int flags = IFO_NOT_NULL;
6781	    int result = TCL_OK;
6782
6783	    if (objc < 6 || objc > 7) {
6784		Tcl_WrongNumArgs(interp, 4, objv, "item column ?stateList?");
6785		return TCL_ERROR;
6786	    }
6787	    /* Without a stateList only one item is accepted. */
6788	    if (objc == 6)
6789		flags |= IFO_NOT_MANY;
6790	    if (TreeItemList_FromObj(tree, objv[4], &itemList, flags)
6791		    != TCL_OK)
6792		return TCL_ERROR;
6793	    TreeColumnList_Init(tree, &columns, 0);
6794	    if (objc == 6) {
6795		item = TreeItemList_Nth(&itemList, 0);
6796		if (Item_FindColumnFromObj(tree, item, objv[5], &column,
6797			    &columnIndex) != TCL_OK) {
6798		    result = TCL_ERROR;
6799		    goto doneFORC;
6800		}
6801		if ((column == NULL) || !column->cstate)
6802		    goto doneFORC;
6803		listObj = Tcl_NewListObj(0, NULL);
6804		for (i = 0; i < 32; i++) {
6805		    if (tree->stateNames[i] == NULL)
6806			continue;
6807		    if (column->cstate & (1L << i)) {
6808			Tcl_ListObjAppendElement(interp, listObj,
6809				Tcl_NewStringObj(tree->stateNames[i], -1));
6810		    }
6811		}
6812		Tcl_SetObjResult(interp, listObj);
6813		goto doneFORC;
6814	    }
6815	    if (TreeColumnList_FromObj(tree, objv[5], &columns,
6816		    CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK) {
6817		result = TCL_ERROR;
6818		goto doneFORC;
6819	    }
6820	    if (Tree_StateFromListObj(tree, objv[6], states, SFO_NOT_STATIC) != TCL_OK) {
6821		result = TCL_ERROR;
6822		goto doneFORC;
6823	    }
6824	    if ((states[0] | states[1] | states[2]) == 0)
6825		goto doneFORC;
6826	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
6827		COLUMN_FOR_EACH(treeColumn, &columns, NULL, &citer) {
6828		    columnIndex = TreeColumn_Index(treeColumn);
6829		    column = Item_CreateColumn(tree, item, columnIndex, NULL);
6830		    stateOn = states[STATE_OP_ON];
6831		    stateOff = states[STATE_OP_OFF];
6832		    stateOn |= ~column->cstate & states[STATE_OP_TOGGLE];
6833		    stateOff |= column->cstate & states[STATE_OP_TOGGLE];
6834		    Column_ChangeState(tree, item, column, treeColumn,
6835			    stateOff, stateOn);
6836		}
6837	    }
6838doneFORC:
6839	    TreeColumnList_Free(&columns);
6840	    TreeItemList_Free(&itemList);
6841	    return result;
6842	}
6843
6844	/* T item state get I ?state? */
6845	case COMMAND_GET: {
6846	    Tcl_Obj *listObj;
6847	    int i, states[3];
6848
6849	    if (objc > 6) {
6850		Tcl_WrongNumArgs(interp, 5, objv, "?state?");
6851		return TCL_ERROR;
6852	    }
6853	    if (TreeItem_FromObj(tree, objv[4], &item, IFO_NOT_NULL) != TCL_OK)
6854		return TCL_ERROR;
6855	    if (objc == 6) {
6856		states[STATE_OP_ON] = 0;
6857		if (Tree_StateFromObj(tree, objv[5], states, NULL,
6858			    SFO_NOT_OFF | SFO_NOT_TOGGLE) != TCL_OK)
6859		    return TCL_ERROR;
6860		Tcl_SetObjResult(interp,
6861			Tcl_NewBooleanObj((item->state & states[STATE_OP_ON]) != 0));
6862		break;
6863	    }
6864	    listObj = Tcl_NewListObj(0, NULL);
6865	    for (i = 0; i < 32; i++) {
6866		if (tree->stateNames[i] == NULL)
6867		    continue;
6868		if (item->state & (1L << i)) {
6869		    Tcl_ListObjAppendElement(interp, listObj,
6870			    Tcl_NewStringObj(tree->stateNames[i], -1));
6871		}
6872	    }
6873	    Tcl_SetObjResult(interp, listObj);
6874	    break;
6875	}
6876
6877	/* T item state set I ?I? {state ...} */
6878	case COMMAND_SET: {
6879	    TreeItemList itemList, item2List;
6880	    int states[3], stateOn, stateOff;
6881	    ItemForEach iter;
6882	    int result = TCL_OK;
6883
6884	    if (objc < 6 || objc > 7) {
6885		Tcl_WrongNumArgs(interp, 5, objv, "?last? stateList");
6886		return TCL_ERROR;
6887	    }
6888	    if (TreeItemList_FromObj(tree, objv[4], &itemList, IFO_NOT_NULL) != TCL_OK)
6889		return TCL_ERROR;
6890	    if (objc == 6) {
6891		TreeItemList_Init(tree, &item2List, 0);
6892	    }
6893	    if (objc == 7) {
6894		if (TreeItemList_FromObj(tree, objv[5], &item2List, IFO_NOT_NULL) != TCL_OK) {
6895		    result =  TCL_ERROR;
6896		    goto doneSET;
6897		}
6898	    }
6899	    if (Tree_StateFromListObj(tree, objv[objc - 1], states,
6900			SFO_NOT_STATIC) != TCL_OK) {
6901		result =  TCL_ERROR;
6902		goto doneSET;
6903	    }
6904	    if ((states[0] | states[1] | states[2]) == 0)
6905		goto doneSET;
6906	    ITEM_FOR_EACH(item, &itemList, &item2List, &iter) {
6907		stateOn = states[STATE_OP_ON];
6908		stateOff = states[STATE_OP_OFF];
6909		stateOn |= ~item->state & states[STATE_OP_TOGGLE];
6910		stateOff |= item->state & states[STATE_OP_TOGGLE];
6911		TreeItem_ChangeState(tree, item, stateOff, stateOn);
6912	    }
6913	    if (iter.error)
6914		result = TCL_ERROR;
6915doneSET:
6916	    TreeItemList_Free(&itemList);
6917	    TreeItemList_Free(&item2List);
6918	    return result;
6919	}
6920    }
6921
6922    return TCL_OK;
6923}
6924
6925/*
6926 *----------------------------------------------------------------------
6927 *
6928 * ItemTagCmd --
6929 *
6930 *	This procedure is invoked to process the [item tag] widget
6931 *	command.  See the user documentation for details on what
6932 *	it does.
6933 *
6934 * Results:
6935 *	A standard Tcl result.
6936 *
6937 * Side effects:
6938 *	See the user documentation.
6939 *
6940 *----------------------------------------------------------------------
6941 */
6942
6943static int
6944ItemTagCmd(
6945    ClientData clientData,	/* Widget info. */
6946    Tcl_Interp *interp,		/* Current interpreter. */
6947    int objc,			/* Number of arguments. */
6948    Tcl_Obj *CONST objv[]	/* Argument values. */
6949    )
6950{
6951    TreeCtrl *tree = clientData;
6952    static CONST char *commandNames[] = {
6953	"add", "expr", "names", "remove", (char *) NULL
6954    };
6955    enum {
6956	COMMAND_ADD, COMMAND_EXPR, COMMAND_NAMES, COMMAND_REMOVE
6957    };
6958    int index;
6959    ItemForEach iter;
6960    TreeItemList items;
6961    TreeItem item;
6962    int result = TCL_OK;
6963
6964    if (objc < 4) {
6965	Tcl_WrongNumArgs(interp, 3, objv, "command ?arg arg ...?");
6966	return TCL_ERROR;
6967    }
6968
6969    if (Tcl_GetIndexFromObj(interp, objv[3], commandNames, "command", 0,
6970	&index) != TCL_OK) {
6971	return TCL_ERROR;
6972    }
6973
6974    switch (index) {
6975	/* T item tag add I tagList */
6976	case COMMAND_ADD: {
6977	    int i, numTags;
6978	    Tcl_Obj **listObjv;
6979	    Tk_Uid staticTags[STATIC_SIZE], *tags = staticTags;
6980
6981	    if (objc != 6) {
6982		Tcl_WrongNumArgs(interp, 4, objv, "item tagList");
6983		return TCL_ERROR;
6984	    }
6985	    if (TreeItemList_FromObj(tree, objv[4], &items, IFO_NOT_NULL) != TCL_OK) {
6986		return TCL_ERROR;
6987	    }
6988	    if (Tcl_ListObjGetElements(interp, objv[5], &numTags, &listObjv) != TCL_OK) {
6989		result = TCL_ERROR;
6990		break;
6991	    }
6992	    STATIC_ALLOC(tags, Tk_Uid, numTags);
6993	    for (i = 0; i < numTags; i++) {
6994		tags[i] = Tk_GetUid(Tcl_GetString(listObjv[i]));
6995	    }
6996	    ITEM_FOR_EACH(item, &items, NULL, &iter) {
6997		item->tagInfo = TagInfo_Add(tree, item->tagInfo, tags, numTags);
6998	    }
6999	    STATIC_FREE(tags, Tk_Uid, numTags);
7000	    break;
7001	}
7002
7003	/* T item tag expr I tagExpr */
7004	case COMMAND_EXPR: {
7005	    TagExpr expr;
7006	    int ok = TRUE;
7007
7008	    if (objc != 6) {
7009		Tcl_WrongNumArgs(interp, 4, objv, "item tagExpr");
7010		return TCL_ERROR;
7011	    }
7012	    if (TreeItemList_FromObj(tree, objv[4], &items, IFO_NOT_NULL) != TCL_OK) {
7013		return TCL_ERROR;
7014	    }
7015	    if (TagExpr_Init(tree, objv[5], &expr) != TCL_OK) {
7016		result = TCL_ERROR;
7017		break;
7018	    }
7019	    ITEM_FOR_EACH(item, &items, NULL, &iter) {
7020		if (!TagExpr_Eval(&expr, item->tagInfo)) {
7021		    ok = FALSE;
7022		    break;
7023		}
7024	    }
7025	    TagExpr_Free(&expr);
7026	    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(ok));
7027	    break;
7028	}
7029
7030	/* T item tag names I */
7031	case COMMAND_NAMES: {
7032	    Tcl_Obj *listObj;
7033	    Tk_Uid *tags = NULL;
7034	    int i, tagSpace, numTags = 0;
7035
7036	    if (objc != 5) {
7037		Tcl_WrongNumArgs(interp, 4, objv, "item");
7038		return TCL_ERROR;
7039	    }
7040	    if (TreeItemList_FromObj(tree, objv[4], &items, IFO_NOT_NULL) != TCL_OK) {
7041		return TCL_ERROR;
7042	    }
7043	    ITEM_FOR_EACH(item, &items, NULL, &iter) {
7044		tags = TagInfo_Names(tree, item->tagInfo, tags, &numTags, &tagSpace);
7045	    }
7046	    if (numTags) {
7047		listObj = Tcl_NewListObj(0, NULL);
7048		for (i = 0; i < numTags; i++) {
7049		    Tcl_ListObjAppendElement(NULL, listObj,
7050			    Tcl_NewStringObj((char *) tags[i], -1));
7051		}
7052		Tcl_SetObjResult(interp, listObj);
7053		ckfree((char *) tags);
7054	    }
7055	    break;
7056	}
7057
7058	/* T item tag remove I tagList */
7059	case COMMAND_REMOVE: {
7060	    int i, numTags;
7061	    Tcl_Obj **listObjv;
7062	    Tk_Uid staticTags[STATIC_SIZE], *tags = staticTags;
7063
7064	    if (objc != 6) {
7065		Tcl_WrongNumArgs(interp, 4, objv, "item tagList");
7066		return TCL_ERROR;
7067	    }
7068	    if (TreeItemList_FromObj(tree, objv[4], &items, IFO_NOT_NULL) != TCL_OK) {
7069		return TCL_ERROR;
7070	    }
7071	    if (Tcl_ListObjGetElements(interp, objv[5], &numTags, &listObjv) != TCL_OK) {
7072		result = TCL_ERROR;
7073		break;
7074	    }
7075	    STATIC_ALLOC(tags, Tk_Uid, numTags);
7076	    for (i = 0; i < numTags; i++) {
7077		tags[i] = Tk_GetUid(Tcl_GetString(listObjv[i]));
7078	    }
7079	    ITEM_FOR_EACH(item, &items, NULL, &iter) {
7080		item->tagInfo = TagInfo_Remove(tree, item->tagInfo, tags, numTags);
7081	    }
7082	    STATIC_FREE(tags, Tk_Uid, numTags);
7083	    break;
7084	}
7085    }
7086
7087    TreeItemList_Free(&items);
7088    return result;
7089}
7090
7091#ifdef SELECTION_VISIBLE
7092
7093/*
7094 *----------------------------------------------------------------------
7095 *
7096 * Tree_DeselectHidden --
7097 *
7098 *	Removes any selected items which are no longer ReallyVisible()
7099 *	from the selection.
7100 *
7101 * Results:
7102 *	None.
7103 *
7104 * Side effects:
7105 *	<Selection> event may be generated.
7106 *
7107 *----------------------------------------------------------------------
7108 */
7109
7110/*
7111 * FIXME: optimize all calls to this routine.
7112 * Optionally call Tree_DInfoChanged(tree, DINFO_REDO_SELECTION) instead.
7113 */
7114void
7115Tree_DeselectHidden(
7116    TreeCtrl *tree		/* Widget info. */
7117    )
7118{
7119    TreeItemList items;
7120    Tcl_HashEntry *hPtr;
7121    Tcl_HashSearch search;
7122    TreeItem item;
7123    int i;
7124
7125    if (tree->selectCount < 1)
7126	return;
7127
7128    /* This call is slow for large lists. */
7129    Tree_UpdateItemIndex(tree);
7130
7131    TreeItemList_Init(tree, &items, tree->selectCount);
7132
7133    hPtr = Tcl_FirstHashEntry(&tree->selection, &search);
7134    while (hPtr != NULL) {
7135	item = (TreeItem) Tcl_GetHashKey(&tree->selection, hPtr);
7136	if (!TreeItem_ReallyVisible(tree, item))
7137	    TreeItemList_Append(&items, item);
7138	hPtr = Tcl_NextHashEntry(&search);
7139    }
7140    for (i = 0; i < TreeItemList_Count(&items); i++)
7141	Tree_RemoveFromSelection(tree, TreeItemList_Nth(&items, i));
7142    if (TreeItemList_Count(&items)) {
7143	TreeNotify_Selection(tree, NULL, &items);
7144    }
7145    TreeItemList_Free(&items);
7146}
7147
7148#endif /* SELECTION_VISIBLE */
7149
7150/*
7151 *----------------------------------------------------------------------
7152 *
7153 * TreeItemCmd --
7154 *
7155 *	This procedure is invoked to process the [item] widget
7156 *	command.  See the user documentation for details on what
7157 *	it does.
7158 *
7159 * Results:
7160 *	A standard Tcl result.
7161 *
7162 * Side effects:
7163 *	See the user documentation.
7164 *
7165 *----------------------------------------------------------------------
7166 */
7167
7168int
7169TreeItemCmd(
7170    ClientData clientData,	/* Widget info. */
7171    Tcl_Interp *interp,		/* Current interpreter. */
7172    int objc,			/* Number of arguments. */
7173    Tcl_Obj *CONST objv[]	/* Argument values. */
7174    )
7175{
7176    TreeCtrl *tree = clientData;
7177    enum {
7178	COMMAND_ANCESTORS,
7179	COMMAND_BBOX,
7180	COMMAND_CGET,
7181	COMMAND_CHILDREN,
7182	COMMAND_COLLAPSE,
7183	COMMAND_COMPARE,
7184#ifdef DEPRECATED
7185	COMMAND_COMPLEX,
7186#endif
7187	COMMAND_CONFIGURE,
7188	COMMAND_COUNT,
7189	COMMAND_CREATE,
7190	COMMAND_DELETE,
7191	COMMAND_DESCENDANTS,
7192	COMMAND_DUMP,
7193	COMMAND_ELEMENT,
7194	COMMAND_ENABLED,
7195	COMMAND_EXPAND,
7196	COMMAND_FIRSTCHILD,
7197	COMMAND_ID,
7198	COMMAND_IMAGE,
7199	COMMAND_ISANCESTOR,
7200	COMMAND_ISOPEN,
7201	COMMAND_LASTCHILD,
7202	COMMAND_NEXTSIBLING,
7203	COMMAND_NUMCHILDREN,
7204	COMMAND_ORDER,
7205	COMMAND_PARENT,
7206	COMMAND_PREVSIBLING,
7207	COMMAND_RANGE,
7208	COMMAND_REMOVE,
7209	COMMAND_RNC,
7210	COMMAND_SORT,
7211	COMMAND_SPAN,
7212	COMMAND_STATE,
7213	COMMAND_STYLE,
7214	COMMAND_TAG,
7215	COMMAND_TEXT,
7216	COMMAND_TOGGLE
7217    };
7218
7219/* AF_xxx must not conflict with IFO_xxx. */
7220#define AF_NOT_ANCESTOR	0x00010000 /* item can't be ancestor of other item */
7221#define AF_NOT_EQUAL	0x00020000 /* second item can't be same as first */
7222#define AF_SAMEROOT	0x00040000 /* both items must be descendants of a
7223				    * common ancestor */
7224#define AF_NOT_ITEM	0x00080000 /* arg is not an Item */
7225#define AF_NOT_DELETED	0x00100000 /* item can't be deleted */
7226
7227    struct {
7228	char *cmdName;
7229	int minArgs;
7230	int maxArgs;
7231	int flags;	/* AF_xxx | IFO_xxx for 1st arg. */
7232	int flags2;	/* AF_xxx | IFO_xxx for 2nd arg. */
7233	int flags3;	/* AF_xxx | IFO_xxx for 3rd arg. */
7234	char *argString;
7235	Tcl_ObjCmdProc *proc;
7236    } argInfo[] = {
7237	{ "ancestors", 1, 1, IFO_NOT_MANY | IFO_NOT_NULL, 0, 0, "item", NULL },
7238	{ "bbox", 1, 3, IFO_NOT_MANY | IFO_NOT_NULL, AF_NOT_ITEM, AF_NOT_ITEM,
7239		"item ?column? ?element?", NULL },
7240	{ "cget", 2, 2, IFO_NOT_MANY | IFO_NOT_NULL, AF_NOT_ITEM, 0,
7241		"item option", NULL },
7242	{ "children", 1, 1, IFO_NOT_MANY | IFO_NOT_NULL, 0, 0,
7243		"item", NULL },
7244	{ "collapse", 1, 2, IFO_NOT_NULL, AF_NOT_ITEM, 0,
7245		"item ?-recurse?", NULL},
7246	{ "compare", 3, 3, IFO_NOT_MANY | IFO_NOT_NULL, AF_NOT_ITEM,
7247		IFO_NOT_MANY | IFO_NOT_NULL | AF_SAMEROOT, "item1 op item2",
7248		NULL },
7249#ifdef DEPRECATED
7250	{ "complex", 2, 100000, IFO_NOT_MANY | IFO_NOT_NULL, AF_NOT_ITEM,
7251		AF_NOT_ITEM, "item list ...", NULL },
7252#endif
7253	{ "configure", 1, 100000, IFO_NOT_NULL, AF_NOT_ITEM, AF_NOT_ITEM,
7254		"item ?option? ?value? ?option value ...?", NULL },
7255	{ "count", 0, 1, 0, 0, 0, "?itemDesc?" , NULL},
7256	{ "create", 0, 0, 0, 0, 0, NULL, ItemCreateCmd },
7257	{ "delete", 1, 2, IFO_NOT_NULL, IFO_NOT_NULL | AF_SAMEROOT, 0,
7258		"first ?last?", NULL },
7259	{ "descendants", 1, 1, IFO_NOT_MANY | IFO_NOT_NULL, 0, 0, "item",
7260		NULL },
7261	{ "dump", 1, 1, IFO_NOT_MANY | IFO_NOT_NULL, 0, 0, "item", NULL },
7262	{ "element", 0, 0, 0, 0, 0, NULL, ItemElementCmd },
7263	{ "enabled", 1, 2, IFO_NOT_NULL, AF_NOT_ITEM, 0, "item ?boolean?",
7264		NULL },
7265	{ "expand", 1, 2, IFO_NOT_NULL, AF_NOT_ITEM, 0, "item ?-recurse?",
7266		NULL},
7267	{ "firstchild", 1, 2, IFO_NOT_MANY | IFO_NOT_NULL | AF_NOT_DELETED,
7268		IFO_NOT_MANY | IFO_NOT_NULL | IFO_NOT_ROOT | AF_NOT_ANCESTOR |
7269		AF_NOT_EQUAL | AF_NOT_DELETED, 0, "item ?newFirstChild?",
7270		NULL },
7271	{ "id", 1, 1, 0, 0, 0, "item", NULL },
7272	{ "image", 1, 100000, IFO_NOT_NULL, AF_NOT_ITEM, AF_NOT_ITEM,
7273		"item ?column? ?image? ?column image ...?", NULL },
7274	{ "isancestor", 2, 2, IFO_NOT_MANY | IFO_NOT_NULL, IFO_NOT_MANY |
7275		IFO_NOT_NULL, 0, "item item2", NULL },
7276	{ "isopen", 1, 1, IFO_NOT_MANY | IFO_NOT_NULL, 0, 0, "item", NULL },
7277	{ "lastchild", 1, 2, IFO_NOT_MANY | IFO_NOT_NULL | AF_NOT_DELETED,
7278		IFO_NOT_MANY | IFO_NOT_NULL | IFO_NOT_ROOT | AF_NOT_ANCESTOR |
7279		AF_NOT_EQUAL | AF_NOT_DELETED, 0, "item ?newLastChild?", NULL },
7280	{ "nextsibling", 1, 2, IFO_NOT_MANY | IFO_NOT_NULL | IFO_NOT_ROOT |
7281		IFO_NOT_ORPHAN, IFO_NOT_MANY | IFO_NOT_NULL | IFO_NOT_ROOT |
7282		AF_NOT_ANCESTOR | AF_NOT_EQUAL, 0, "item ?newNextSibling?",
7283		NULL },
7284	{ "numchildren", 1, 1, IFO_NOT_MANY | IFO_NOT_NULL, 0, 0, "item",
7285		NULL },
7286	{ "order", 1, 2, IFO_NOT_MANY | IFO_NOT_NULL, AF_NOT_ITEM, 0,
7287		"item ?-visible?", NULL },
7288	{ "parent", 1, 1, IFO_NOT_MANY | IFO_NOT_NULL, 0, 0, "item", NULL },
7289	{ "prevsibling", 1, 2, IFO_NOT_MANY | IFO_NOT_NULL | IFO_NOT_ROOT |
7290		IFO_NOT_ORPHAN, IFO_NOT_MANY | IFO_NOT_NULL | IFO_NOT_ROOT |
7291		AF_NOT_ANCESTOR | AF_NOT_EQUAL, 0, "item ?newPrevSibling?",
7292		NULL },
7293	{ "range", 2, 2, IFO_NOT_MANY | IFO_NOT_NULL, IFO_NOT_MANY |
7294		IFO_NOT_NULL | AF_SAMEROOT, 0, "first last", NULL },
7295	{ "remove", 1, 1, IFO_NOT_NULL | IFO_NOT_ROOT, 0, 0, "item", NULL },
7296	{ "rnc", 1, 1, IFO_NOT_MANY | IFO_NOT_NULL, 0, 0, "item", NULL },
7297	{ "sort", 0, 0, 0, 0, 0, NULL, ItemSortCmd },
7298	{ "span", 1, 100000, IFO_NOT_NULL, AF_NOT_ITEM, AF_NOT_ITEM,
7299		"item ?column? ?span? ?column span ...?", NULL },
7300	{ "state", 0, 0, 0, 0, 0, NULL, ItemStateCmd },
7301	{ "style", 0, 0, 0, 0, 0, NULL, ItemStyleCmd },
7302	{ "tag", 0, 0, 0, 0, 0, NULL, ItemTagCmd },
7303	{ "text", 1, 100000, IFO_NOT_NULL, AF_NOT_ITEM, AF_NOT_ITEM,
7304		"item ?column? ?text? ?column text ...?", NULL },
7305	{ "toggle", 1, 2, IFO_NOT_NULL, AF_NOT_ITEM, 0, "item ?-recurse?",
7306		NULL},
7307	{ NULL }
7308    };
7309    int index;
7310    int numArgs = objc - 3;
7311    TreeItemList itemList, item2List;
7312    TreeItem item = NULL, item2 = NULL, child;
7313    ItemForEach iter;
7314    int result = TCL_OK;
7315
7316    if (objc < 3) {
7317	Tcl_WrongNumArgs(interp, 2, objv, "command ?arg arg ...?");
7318	return TCL_ERROR;
7319    }
7320
7321    if (Tcl_GetIndexFromObjStruct(interp, objv[2], argInfo, sizeof(argInfo[0]),
7322	    "command", 0, &index) != TCL_OK) {
7323	return TCL_ERROR;
7324    }
7325
7326    if (argInfo[index].proc != NULL)
7327	return argInfo[index].proc(clientData, interp, objc, objv);
7328
7329    if ((numArgs < argInfo[index].minArgs) ||
7330	    (numArgs > argInfo[index].maxArgs)) {
7331	Tcl_WrongNumArgs(interp, 3, objv, argInfo[index].argString);
7332	return TCL_ERROR;
7333    }
7334
7335    TreeItemList_Init(tree, &itemList, 0);
7336    TreeItemList_Init(tree, &item2List, 0);
7337
7338    if ((numArgs >= 1) && !(argInfo[index].flags & AF_NOT_ITEM)) {
7339	if (TreeItemList_FromObj(tree, objv[3], &itemList,
7340		    argInfo[index].flags & 0xFFFF) != TCL_OK) {
7341	    goto errorExit;
7342	}
7343	item = TreeItemList_Nth(&itemList, 0); /* May be NULL. */
7344	if ((argInfo[index].flags & AF_NOT_DELETED) && IS_DELETED(item)) {
7345	    FormatResult(interp, "item %s%d is being deleted",
7346		    tree->itemPrefix, item->id);
7347	    goto errorExit;
7348	}
7349    }
7350    if (((numArgs >= 2) && !(argInfo[index].flags2 & AF_NOT_ITEM)) ||
7351	    ((numArgs >= 3) && !(argInfo[index].flags3 & AF_NOT_ITEM))) {
7352	int flags, obji;
7353	if (argInfo[index].flags2 & AF_NOT_ITEM) {
7354	    obji = 5;
7355	    flags = argInfo[index].flags3;
7356	} else {
7357	    obji = 4;
7358	    flags = argInfo[index].flags2;
7359	}
7360	if (TreeItemList_FromObj(tree, objv[obji], &item2List,
7361		flags & 0xFFFF) != TCL_OK) {
7362	    goto errorExit;
7363	}
7364	ITEM_FOR_EACH(item2, &item2List, NULL, &iter) {
7365	    if ((flags & AF_NOT_DELETED) && IS_DELETED(item2)) {
7366		FormatResult(interp, "item %s%d is being deleted",
7367			tree->itemPrefix, item2->id);
7368		goto errorExit;
7369	    }
7370	    if ((flags & AF_NOT_EQUAL) && (item == item2)) {
7371		FormatResult(interp, "item %s%d same as second item", tree->itemPrefix,
7372			item->id);
7373		goto errorExit;
7374	    }
7375	    if ((argInfo[index].flags & AF_NOT_ANCESTOR) &&
7376		    TreeItem_IsAncestor(tree, item, item2)) {
7377		FormatResult(interp, "item %s%d is ancestor of item %s%d",
7378			tree->itemPrefix, item->id, tree->itemPrefix, item2->id);
7379		goto errorExit;
7380	    }
7381	    if ((flags & AF_NOT_ANCESTOR) &&
7382		    TreeItem_IsAncestor(tree, item2, item)) {
7383		FormatResult(interp, "item %s%d is ancestor of item %s%d",
7384			tree->itemPrefix, item2->id, tree->itemPrefix, item->id);
7385		goto errorExit;
7386	    }
7387	    if ((flags & AF_SAMEROOT) &&
7388		    TreeItem_RootAncestor(tree, item) !=
7389		    TreeItem_RootAncestor(tree, item2)) {
7390		FormatResult(interp,
7391			"item %s%d and item %s%d don't share a common ancestor",
7392			tree->itemPrefix, item->id, tree->itemPrefix, item2->id);
7393		goto errorExit;
7394	    }
7395	}
7396	item2 = TreeItemList_Nth(&item2List, 0); /* May be NULL. */
7397    }
7398
7399    switch (index) {
7400	case COMMAND_ANCESTORS: {
7401	    Tcl_Obj *listObj;
7402	    TreeItem parent = item->parent;
7403
7404	    if (parent == NULL)
7405		break; /* empty list */
7406	    listObj = Tcl_NewListObj(0, NULL);
7407	    while (parent != NULL) {
7408		Tcl_ListObjAppendElement(interp, listObj,
7409			TreeItem_ToObj(tree, parent));
7410		parent = parent->parent;
7411	    }
7412	    Tcl_SetObjResult(interp, listObj);
7413	    break;
7414	}
7415	/* T item bbox I ?C? ?E? */
7416	case COMMAND_BBOX: {
7417	    int x, y, w, h;
7418	    int count;
7419	    TreeColumn treeColumn;
7420	    TreeRectangle rect;
7421
7422	    if (objc == 4) {
7423		if (Tree_ItemBbox(tree, item, COLUMN_LOCK_NONE, &x, &y, &w, &h) < 0)
7424		    break;
7425	    } else {
7426		if (TreeColumn_FromObj(tree, objv[4], &treeColumn,
7427			CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK)
7428		    goto errorExit;
7429
7430		/* Bounds of a column. */
7431		if (objc == 5) {
7432		    objc = 0;
7433		    objv = NULL;
7434
7435		/* Single element in a column. */
7436		} else {
7437		    objc -= 5;
7438		    objv += 5;
7439		}
7440
7441		count = TreeItem_GetRects(tree, item, treeColumn,
7442			objc, objv, &rect);
7443		if (count == 0)
7444		    break;
7445		if (count == -1)
7446		    goto errorExit;
7447		x = rect.x;
7448		y = rect.y;
7449		w = rect.width;
7450		h = rect.height;
7451	    }
7452	    FormatResult(interp, "%d %d %d %d",
7453		    x - tree->xOrigin,
7454		    y - tree->yOrigin,
7455		    x - tree->xOrigin + w,
7456		    y - tree->yOrigin + h);
7457	    break;
7458	}
7459	case COMMAND_CGET: {
7460	    Tcl_Obj *resultObjPtr;
7461
7462	    resultObjPtr = Tk_GetOptionValue(interp, (char *) item,
7463		    tree->itemOptionTable, objv[4], tree->tkwin);
7464	    if (resultObjPtr == NULL)
7465		goto errorExit;
7466	    Tcl_SetObjResult(interp, resultObjPtr);
7467	    break;
7468	}
7469	case COMMAND_CHILDREN: {
7470	    if (item->numChildren != 0) {
7471		Tcl_Obj *listObj;
7472
7473		listObj = Tcl_NewListObj(0, NULL);
7474		child = item->firstChild;
7475		while (child != NULL) {
7476		    Tcl_ListObjAppendElement(interp, listObj,
7477			    TreeItem_ToObj(tree, child));
7478		    child = child->nextSibling;
7479		}
7480		Tcl_SetObjResult(interp, listObj);
7481	    }
7482	    break;
7483	}
7484	case COMMAND_COLLAPSE:
7485	case COMMAND_EXPAND:
7486	case COMMAND_TOGGLE: {
7487	    int recurse = 0;
7488	    int mode = 0; /* lint */
7489	    int i, count;
7490	    TreeItemList items;
7491
7492	    if (numArgs == 2) {
7493		int len;
7494		char *s = Tcl_GetStringFromObj(objv[4], &len);
7495		if (strncmp(s, "-recurse", len)) {
7496		    FormatResult(interp, "bad option \"%s\": must be -recurse",
7497			    s);
7498		    goto errorExit;
7499		}
7500		recurse = 1;
7501	    }
7502	    switch (index) {
7503		case COMMAND_COLLAPSE:
7504		    mode = 0;
7505		    break;
7506		case COMMAND_EXPAND:
7507		    mode = 1;
7508		    break;
7509		case COMMAND_TOGGLE:
7510		    mode = -1;
7511		    break;
7512	    }
7513	    TreeItemList_Init(tree, &items, 0);
7514	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
7515		TreeItemList_Append(&items, item);
7516		if (!iter.all && recurse) {
7517		    TreeItem_ListDescendants(tree, item, &items);
7518		}
7519	    }
7520	    count = TreeItemList_Count(&items);
7521	    for (i = 0; i < count; i++) {
7522		item = TreeItemList_Nth(&items, i);
7523		TreeItem_OpenClose(tree, item, mode);
7524	    }
7525	    TreeItemList_Free(&items);
7526#ifdef SELECTION_VISIBLE
7527	    Tree_DeselectHidden(tree);
7528#endif
7529	    break;
7530	}
7531	/* T item compare I op I */
7532	case COMMAND_COMPARE: {
7533	    static CONST char *opName[] = { "<", "<=", "==", ">=", ">", "!=", NULL };
7534	    int op, compare = 0, index1, index2;
7535
7536	    if (Tcl_GetIndexFromObj(interp, objv[4], opName, "comparison operator", 0,
7537		    &op) != TCL_OK)
7538		goto errorExit;
7539	    TreeItem_ToIndex(tree, item, &index1, NULL);
7540	    TreeItem_ToIndex(tree, item2, &index2, NULL);
7541	    switch (op) {
7542		case 0: compare = index1 < index2; break;
7543		case 1: compare = index1 <= index2; break;
7544		case 2: compare = index1 == index2; break;
7545		case 3: compare = index1 >= index2; break;
7546		case 4: compare = index1 > index2; break;
7547		case 5: compare = index1 != index2; break;
7548	    }
7549	    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(compare));
7550	    break;
7551	}
7552
7553#ifdef DEPRECATED
7554	case COMMAND_COMPLEX: {
7555	    int i, j, columnIndex;
7556	    int objc1, objc2;
7557	    Tcl_Obj **objv1, **objv2;
7558	    TreeColumn treeColumn = tree->columns;
7559	    Column *column;
7560	    int eMask, cMask, iMask = 0;
7561
7562	    if (objc <= 4)
7563		break;
7564	    columnIndex = 0;
7565	    for (i = 4; i < objc; i++, columnIndex++,
7566		    treeColumn = TreeColumn_Next(treeColumn)) {
7567		if (treeColumn == NULL) {
7568		    FormatResult(interp, "column #%d doesn't exist",
7569			    columnIndex);
7570		    result = TCL_ERROR;
7571		    goto doneComplex;
7572		}
7573		column = Item_FindColumn(tree, item, columnIndex);
7574		if (column == NULL) {
7575		    FormatResult(interp, "item %s%d doesn't have column %s%d",
7576			    tree->itemPrefix, item->id,
7577			    tree->columnPrefix, TreeColumn_GetID(treeColumn));
7578		    result = TCL_ERROR;
7579		    goto doneComplex;
7580		}
7581		/* List of element-configs per column */
7582		if (Tcl_ListObjGetElements(interp, objv[i],
7583			    &objc1, &objv1) != TCL_OK) {
7584		    result = TCL_ERROR;
7585		    goto doneComplex;
7586		}
7587		if (objc1 == 0)
7588		    continue;
7589		if (column->style == NULL) {
7590		    FormatResult(interp, "item %s%d column %s%d has no style",
7591			    tree->itemPrefix, item->id,
7592			    tree->columnPrefix, TreeColumn_GetID(treeColumn));
7593		    result = TCL_ERROR;
7594		    goto doneComplex;
7595		}
7596		cMask = 0;
7597		for (j = 0; j < objc1; j++) {
7598		    /* elem option value... */
7599		    if (Tcl_ListObjGetElements(interp, objv1[j],
7600				&objc2, &objv2) != TCL_OK) {
7601			result = TCL_ERROR;
7602			goto doneComplex;
7603		    }
7604		    if (objc2 < 3) {
7605			FormatResult(interp,
7606				"wrong # args: should be \"element option value ...\"");
7607			result = TCL_ERROR;
7608			goto doneComplex;
7609		    }
7610		    if (TreeStyle_ElementConfigure(tree, item,
7611				(TreeItemColumn) column, column->style,
7612				objv2[0], objc2 - 1, objv2 + 1, &eMask) != TCL_OK) {
7613			result = TCL_ERROR;
7614			goto doneComplex;
7615		    }
7616		    cMask |= eMask;
7617		    iMask |= eMask;
7618		}
7619		if (cMask & CS_LAYOUT)
7620		    TreeItemColumn_InvalidateSize(tree, (TreeItemColumn) column);
7621	    }
7622	    doneComplex:
7623	    if (iMask & CS_DISPLAY)
7624		Tree_InvalidateItemDInfo(tree, NULL, item, NULL);
7625	    if (iMask & CS_LAYOUT) {
7626		Tree_InvalidateColumnWidth(tree, NULL);
7627		TreeItem_InvalidateHeight(tree, item);
7628		Tree_FreeItemDInfo(tree, item, NULL);
7629		Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
7630	    }
7631	    break;
7632	}
7633#endif /* DEPRECATED*/
7634
7635	/* T item configure I ?option? ?value? ?option value ...? */
7636	case COMMAND_CONFIGURE: {
7637	    if (objc <= 5) {
7638		Tcl_Obj *resultObjPtr;
7639
7640		if (IS_ALL(item) || (TreeItemList_Count(&itemList) > 1)) {
7641		    FormatResult(interp, "can't specify > 1 item for this command");
7642		    goto errorExit;
7643		}
7644		resultObjPtr = Tk_GetOptionInfo(interp, (char *) item,
7645			tree->itemOptionTable,
7646			(objc == 4) ? (Tcl_Obj *) NULL : objv[4],
7647			tree->tkwin);
7648		if (resultObjPtr == NULL)
7649		    goto errorExit;
7650		Tcl_SetObjResult(interp, resultObjPtr);
7651		break;
7652	    }
7653	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
7654		result = Item_Configure(tree, item, objc - 4, objv + 4);
7655		if (result != TCL_OK)
7656		    break;
7657	    }
7658	    break;
7659	}
7660	case COMMAND_COUNT: {
7661	    int count = tree->itemCount;
7662
7663	    if (objc == 4) {
7664		count = 0;
7665		ITEM_FOR_EACH(item, &itemList, &item2List, &iter) {
7666		    count++;
7667		}
7668	    }
7669	    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
7670	    break;
7671	}
7672	case COMMAND_DELETE: {
7673	    TreeItemList deleted, selected;
7674	    int i, count;
7675
7676	    /* The root is never deleted */
7677	    if (tree->itemCount == 1)
7678		break;
7679
7680	    if (objc == 5) {
7681		if (item == NULL || item2 == NULL) {
7682		    FormatResult(interp, "invalid range \"%s\" to \"%s\"",
7683			    Tcl_GetString(objv[3]), Tcl_GetString(objv[4]));
7684		    goto errorExit;
7685		}
7686	    }
7687
7688	    /*
7689	     * ITEM_FLAG_DELETED prevents us from adding the same item
7690	     * twice to the 'deleted' list. It also prevents nested
7691	     * calls to this command (through binding scripts) deleting
7692	     * the same item twice.
7693	     */
7694
7695	    TreeItemList_Init(tree, &deleted, tree->itemCount - 1);
7696	    TreeItemList_Init(tree, &selected, tree->selectCount);
7697
7698	    ITEM_FOR_EACH(item, &itemList, &item2List, &iter) {
7699		if (IS_ROOT(item))
7700		    continue;
7701		if (IS_DELETED(item))
7702		    continue;
7703		item->flags |= ITEM_FLAG_DELETED;
7704		TreeItemList_Append(&deleted, item);
7705		if (TreeItem_GetSelected(tree, item))
7706		    TreeItemList_Append(&selected, item);
7707		if (iter.all)
7708		    continue;
7709		/* Check every descendant. */
7710		if (item->firstChild == NULL)
7711		    continue;
7712		item2 = item;
7713		while (item2->lastChild != NULL)
7714		    item2 = item2->lastChild;
7715		item = item->firstChild;
7716		while (1) {
7717		    if (IS_DELETED(item)) {
7718			/* Skip all descendants (they are already flagged). */
7719			while (item->lastChild != NULL)
7720			    item = item->lastChild;
7721		    } else {
7722			item->flags |= ITEM_FLAG_DELETED;
7723			TreeItemList_Append(&deleted, item);
7724			if (TreeItem_GetSelected(tree, item))
7725			    TreeItemList_Append(&selected, item);
7726		    }
7727		    if (item == item2)
7728			break;
7729		    item = TreeItem_Next(tree, item);
7730		}
7731	    }
7732
7733	    count = TreeItemList_Count(&selected);
7734	    if (count) {
7735		for (i = 0; i < count; i++) {
7736		    item = TreeItemList_Nth(&selected, i);
7737		    Tree_RemoveFromSelection(tree, item);
7738		}
7739		/* Generate <Selection> event for selected items being deleted. */
7740		TreeNotify_Selection(tree, NULL, &selected);
7741	    }
7742
7743	    count = TreeItemList_Count(&deleted);
7744	    if (count) {
7745		/* Generate <ItemDelete> event for items being deleted. */
7746		TreeNotify_ItemDeleted(tree, &deleted);
7747
7748		/* Remove every item from its parent. Needed because items
7749		 * are deleted recursively. */
7750		for (i = 0; i < count; i++) {
7751		    item = TreeItemList_Nth(&deleted, i);
7752		    TreeItem_RemoveFromParent(tree, item);
7753		}
7754
7755		/* Delete the items. The item record will be freed when no
7756		 * longer in use; however, the item cannot be referred to
7757		 * by commands from this point on. */
7758		for (i = 0; i < count; i++) {
7759		    item = TreeItemList_Nth(&deleted, i);
7760		    TreeItem_Delete(tree, item);
7761		}
7762	    }
7763
7764	    TreeItemList_Free(&selected);
7765	    TreeItemList_Free(&deleted);
7766	    break;
7767	}
7768	case COMMAND_DESCENDANTS: {
7769	    Tcl_Obj *listObj;
7770
7771	    if (item->firstChild == NULL)
7772		break;
7773	    item2 = item;
7774	    while (item2->lastChild != NULL)
7775		item2 = item2->lastChild;
7776	    item = item->firstChild;
7777	    listObj = Tcl_NewListObj(0, NULL);
7778	    while (1) {
7779		Tcl_ListObjAppendElement(interp, listObj,
7780			TreeItem_ToObj(tree, item));
7781		if (item == item2)
7782		    break;
7783		item = TreeItem_Next(tree, item);
7784	    }
7785	    Tcl_SetObjResult(interp, listObj);
7786	    break;
7787	}
7788	case COMMAND_DUMP: {
7789	    Tree_UpdateItemIndex(tree);
7790	    FormatResult(interp, "index %d indexVis %d",
7791		    item->index, item->indexVis);
7792	    break;
7793	}
7794	/* T item enabled I ?boolean? */
7795	case COMMAND_ENABLED: {
7796	    int enabled;
7797	    TreeItemList newD;
7798	    int stateOff, stateOn;
7799
7800	    if (objc == 4) {
7801		if (IS_ALL(item) || (TreeItemList_Count(&itemList) > 1)) {
7802		    FormatResult(interp, "can't specify > 1 item for this command");
7803		    goto errorExit;
7804		}
7805		Tcl_SetObjResult(interp,
7806			Tcl_NewBooleanObj(item->state & STATE_ENABLED));
7807		break;
7808	    }
7809	    if (Tcl_GetBooleanFromObj(interp, objv[4], &enabled) != TCL_OK)
7810		goto errorExit;
7811	    stateOff = enabled ? 0 : STATE_ENABLED;
7812	    stateOn = enabled ? STATE_ENABLED : 0;
7813	    TreeItemList_Init(tree, &newD, tree->selectCount);
7814	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
7815		if (enabled != TreeItem_GetEnabled(tree, item)) {
7816		    TreeItem_ChangeState(tree, item, stateOff, stateOn);
7817		    /* Disabled items cannot be selected. */
7818		    if (!enabled && TreeItem_GetSelected(tree, item)) {
7819			Tree_RemoveFromSelection(tree, item);
7820			TreeItemList_Append(&newD, item);
7821		    }
7822		}
7823	    }
7824	    if (TreeItemList_Count(&newD))
7825		TreeNotify_Selection(tree, NULL, &newD);
7826	    TreeItemList_Free(&newD);
7827	    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(enabled));
7828	    break;
7829	}
7830	case COMMAND_FIRSTCHILD: {
7831	    if (item2 != NULL && item2 != item->firstChild) {
7832		TreeItem_RemoveFromParent(tree, item2);
7833		item2->nextSibling = item->firstChild;
7834		if (item->firstChild != NULL)
7835		    item->firstChild->prevSibling = item2;
7836		else
7837		    item->lastChild = item2;
7838		item->firstChild = item2;
7839		item2->parent = item;
7840		item->numChildren++;
7841		TreeItem_AddToParent(tree, item2);
7842#ifdef SELECTION_VISIBLE
7843		Tree_DeselectHidden(tree);
7844#endif
7845	    }
7846	    if (item->firstChild != NULL)
7847		Tcl_SetObjResult(interp, TreeItem_ToObj(tree, item->firstChild));
7848	    break;
7849	}
7850	/* T item id I */
7851	case COMMAND_ID: {
7852	    Tcl_Obj *listObj;
7853
7854	    listObj = Tcl_NewListObj(0, NULL);
7855	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
7856		Tcl_ListObjAppendElement(interp, listObj,
7857			TreeItem_ToObj(tree, item));
7858	    }
7859	    Tcl_SetObjResult(interp, listObj);
7860	    break;
7861	}
7862	case COMMAND_ISANCESTOR: {
7863	    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(
7864				 TreeItem_IsAncestor(tree, item, item2)));
7865	    break;
7866	}
7867	case COMMAND_ISOPEN: {
7868	    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(item->state & STATE_OPEN));
7869	    break;
7870	}
7871	case COMMAND_LASTCHILD: {
7872	    /* Don't allow non-deleted items to become children of a
7873	     * deleted item. */
7874	    if (item2 != NULL && item2 != item->lastChild) {
7875		TreeItem_RemoveFromParent(tree, item2);
7876		item2->prevSibling = item->lastChild;
7877		if (item->lastChild != NULL)
7878		    item->lastChild->nextSibling = item2;
7879		else
7880		    item->firstChild = item2;
7881		item->lastChild = item2;
7882		item2->parent = item;
7883		item->numChildren++;
7884		TreeItem_AddToParent(tree, item2);
7885#ifdef SELECTION_VISIBLE
7886		Tree_DeselectHidden(tree);
7887#endif
7888	    }
7889	    if (item->lastChild != NULL)
7890		Tcl_SetObjResult(interp, TreeItem_ToObj(tree, item->lastChild));
7891	    break;
7892	}
7893	case COMMAND_NEXTSIBLING: {
7894	    if (item2 != NULL && item2 != item->nextSibling) {
7895		TreeItem_RemoveFromParent(tree, item2);
7896		item2->prevSibling = item;
7897		if (item->nextSibling != NULL) {
7898		    item->nextSibling->prevSibling = item2;
7899		    item2->nextSibling = item->nextSibling;
7900		} else
7901		    item->parent->lastChild = item2;
7902		item->nextSibling = item2;
7903		item2->parent = item->parent;
7904		item->parent->numChildren++;
7905		TreeItem_AddToParent(tree, item2);
7906#ifdef SELECTION_VISIBLE
7907		Tree_DeselectHidden(tree);
7908#endif
7909	    }
7910	    if (item->nextSibling != NULL)
7911		Tcl_SetObjResult(interp, TreeItem_ToObj(tree, item->nextSibling));
7912	    break;
7913	}
7914	case COMMAND_NUMCHILDREN: {
7915	    Tcl_SetObjResult(interp, Tcl_NewIntObj(item->numChildren));
7916	    break;
7917	}
7918	/* T item order I ?-visible? */
7919	case COMMAND_ORDER: {
7920	    int visible = FALSE;
7921	    if (objc == 5) {
7922		int len;
7923		char *s = Tcl_GetStringFromObj(objv[4], &len);
7924		if ((s[0] == '-') && (strncmp(s, "-visible", len) == 0))
7925		    visible = TRUE;
7926		else {
7927		    FormatResult(interp, "bad switch \"%s\": must be -visible",
7928			s);
7929		    goto errorExit;
7930		}
7931	    }
7932	    Tree_UpdateItemIndex(tree);
7933	    Tcl_SetObjResult(interp,
7934		    Tcl_NewIntObj(visible ? item->indexVis : item->index));
7935	    break;
7936	}
7937	/* T item range I I */
7938	case COMMAND_RANGE: {
7939	    TreeItem itemFirst = item;
7940	    TreeItem itemLast = item2;
7941	    Tcl_Obj *listObj;
7942
7943	    if (itemFirst == itemLast) {
7944		Tcl_SetObjResult(interp, TreeItem_ToObj(tree, itemFirst));
7945		break;
7946	    }
7947	    (void) TreeItem_FirstAndLast(tree, &itemFirst, &itemLast);
7948	    listObj = Tcl_NewListObj(0, NULL);
7949	    while (itemFirst != NULL) {
7950		Tcl_ListObjAppendElement(interp, listObj,
7951			TreeItem_ToObj(tree, itemFirst));
7952		if (itemFirst == itemLast)
7953		    break;
7954		itemFirst = TreeItem_Next(tree, itemFirst);
7955	    }
7956	    Tcl_SetObjResult(interp, listObj);
7957	    break;
7958	}
7959	case COMMAND_PARENT: {
7960	    if (item->parent != NULL)
7961		Tcl_SetObjResult(interp, TreeItem_ToObj(tree, item->parent));
7962	    break;
7963	}
7964	case COMMAND_PREVSIBLING: {
7965	    if (item2 != NULL && item2 != item->prevSibling) {
7966		TreeItem_RemoveFromParent(tree, item2);
7967		item2->nextSibling = item;
7968		if (item->prevSibling != NULL) {
7969		    item->prevSibling->nextSibling = item2;
7970		    item2->prevSibling = item->prevSibling;
7971		} else
7972		    item->parent->firstChild = item2;
7973		item->prevSibling = item2;
7974		item2->parent = item->parent;
7975		item->parent->numChildren++;
7976		TreeItem_AddToParent(tree, item2);
7977#ifdef SELECTION_VISIBLE
7978		Tree_DeselectHidden(tree);
7979#endif
7980	    }
7981	    if (item->prevSibling != NULL)
7982		Tcl_SetObjResult(interp, TreeItem_ToObj(tree, item->prevSibling));
7983	    break;
7984	}
7985	case COMMAND_REMOVE: {
7986	    int removed = FALSE;
7987
7988	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
7989		if (item->parent != NULL) {
7990		    TreeItem_RemoveFromParent(tree, item);
7991		    Tree_FreeItemDInfo(tree, item, NULL);
7992		    removed = TRUE;
7993		}
7994	    }
7995	    if (!removed)
7996		break;
7997	    if (tree->debug.enable && tree->debug.data)
7998		Tree_Debug(tree);
7999	    Tree_InvalidateColumnWidth(tree, NULL);
8000#ifdef SELECTION_VISIBLE
8001	    Tree_DeselectHidden(tree);
8002#endif
8003	    break;
8004	}
8005	case COMMAND_RNC: {
8006	    int row,col;
8007
8008	    if (Tree_ItemToRNC(tree, item, &row, &col) == TCL_OK)
8009		FormatResult(interp, "%d %d", row, col);
8010	    break;
8011	}
8012
8013	/* T item span I ?C? ?span? ?C span ...? */
8014	case COMMAND_SPAN: {
8015	    TreeColumn treeColumn = tree->columns;
8016	    Column *column;
8017	    Tcl_Obj *listObj;
8018	    struct columnSpan {
8019		TreeColumnList columns;
8020		int span;
8021	    } staticCS[STATIC_SIZE], *cs = staticCS;
8022	    int i, count = 0, span, changed = FALSE;
8023	    ColumnForEach citer;
8024
8025	    if ((objc < 6) && (IS_ALL(item) ||
8026		    (TreeItemList_Count(&itemList) > 1))) {
8027		FormatResult(interp, "can't specify > 1 item for this command");
8028		goto errorExit;
8029	    }
8030	    if (objc == 4) {
8031		listObj = Tcl_NewListObj(0, NULL);
8032		column = item->columns;
8033		while (treeColumn != NULL) {
8034		    Tcl_ListObjAppendElement(interp, listObj,
8035			    Tcl_NewIntObj(column ? column->span : 1));
8036		    treeColumn = TreeColumn_Next(treeColumn);
8037		    if (column != NULL)
8038			column = column->next;
8039		}
8040		Tcl_SetObjResult(interp, listObj);
8041		break;
8042	    }
8043	    if (objc == 5) {
8044		if (Item_FindColumnFromObj(tree, item, objv[4], &column, NULL) != TCL_OK) {
8045		    goto errorExit;
8046		}
8047		Tcl_SetObjResult(interp, Tcl_NewIntObj(column ? column->span : 1));
8048		break;
8049	    }
8050	    if ((objc - 4) & 1) {
8051		FormatResult(interp, "missing argument after column \"%s\"",
8052			Tcl_GetString(objv[objc - 1]));
8053		goto errorExit;
8054	    }
8055	    /* Gather column/span pairs. */
8056	    STATIC_ALLOC(cs, struct columnSpan, objc / 2);
8057	    for (i = 4; i < objc; i += 2) {
8058		if (TreeColumnList_FromObj(tree, objv[i], &cs[count].columns,
8059			CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK) {
8060		    result = TCL_ERROR;
8061		    goto doneSPAN;
8062		}
8063		if (Tcl_GetIntFromObj(interp, objv[i + 1], &span) != TCL_OK) {
8064		    result = TCL_ERROR;
8065		    goto doneSPAN;
8066		}
8067		if (span <= 0) {
8068		    FormatResult(interp, "bad span \"%d\": must be > 0", span);
8069		    result = TCL_ERROR;
8070		    goto doneSPAN;
8071		}
8072		cs[count].span = span;
8073		count++;
8074	    }
8075	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
8076		int changedI = FALSE;
8077		for (i = 0; i < count; i++) {
8078		    COLUMN_FOR_EACH(treeColumn, &cs[i].columns, NULL, &citer) {
8079			column = Item_CreateColumn(tree, item,
8080				TreeColumn_Index(treeColumn), NULL);
8081			if (column->span != cs[i].span) {
8082			    if (cs[i].span > 1) {
8083				item->flags &= ~ITEM_FLAG_SPANS_SIMPLE;
8084			    }
8085			    TreeItem_SpansInvalidate(tree, item);
8086			    column->span = cs[i].span;
8087			    TreeItemColumn_InvalidateSize(tree,
8088				(TreeItemColumn) column);
8089			    changedI = TRUE;
8090			    Tree_InvalidateColumnWidth(tree, treeColumn);
8091			}
8092		    }
8093		}
8094		if (changedI) {
8095		    TreeItem_InvalidateHeight(tree, item);
8096		    Tree_FreeItemDInfo(tree, item, NULL);
8097		    changed = TRUE;
8098		}
8099	    }
8100	    if (changed)
8101		Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
8102doneSPAN:
8103	    for (i = 0; i < count; i++) {
8104		TreeColumnList_Free(&cs[i].columns);
8105	    }
8106	    STATIC_FREE(cs, struct columnSpan, objc / 2);
8107	    break;
8108	}
8109
8110	/* T item image I ?C? ?image? ?C image ...? */
8111	case COMMAND_IMAGE:
8112	/* T item text I ?C? ?text? ?C text ...? */
8113	case COMMAND_TEXT: {
8114	    TreeColumn treeColumn = tree->columns;
8115	    Column *column;
8116	    Tcl_Obj *objPtr;
8117	    int isImage = (index == COMMAND_IMAGE);
8118	    struct columnObj {
8119		TreeColumnList columns;
8120		Tcl_Obj *obj;
8121	    } staticCO[STATIC_SIZE], *co = staticCO;
8122	    int i, count = 0, changed = FALSE, columnIndex;
8123	    ColumnForEach citer;
8124
8125	    if ((objc < 6) && (IS_ALL(item) ||
8126		    (TreeItemList_Count(&itemList) > 1))) {
8127		FormatResult(interp, "can't specify > 1 item for this command");
8128		goto errorExit;
8129	    }
8130	    if (objc == 4) {
8131		Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
8132		column = item->columns;
8133		while (treeColumn != NULL) {
8134		    if ((column != NULL) && (column->style != NULL))
8135			objPtr = isImage ?
8136			    TreeStyle_GetImage(tree, column->style) :
8137			    TreeStyle_GetText(tree, column->style);
8138		    else
8139			objPtr = NULL;
8140		    if (objPtr == NULL)
8141			objPtr = Tcl_NewObj();
8142		    Tcl_ListObjAppendElement(interp, listObj, objPtr);
8143		    treeColumn = TreeColumn_Next(treeColumn);
8144		    if (column != NULL)
8145			column = column->next;
8146		}
8147		Tcl_SetObjResult(interp, listObj);
8148		break;
8149	    }
8150	    if (objc == 5) {
8151		if (Item_FindColumnFromObj(tree, item, objv[4], &column, NULL) != TCL_OK) {
8152		    goto errorExit;
8153		}
8154		if ((column != NULL) && (column->style != NULL)) {
8155		    objPtr = isImage ?
8156			TreeStyle_GetImage(tree, column->style) :
8157			TreeStyle_GetText(tree, column->style);
8158		    if (objPtr != NULL)
8159			Tcl_SetObjResult(interp, objPtr);
8160		}
8161		break;
8162	    }
8163	    if ((objc - 4) & 1) {
8164		FormatResult(interp, "missing argument after column \"%s\"",
8165			Tcl_GetString(objv[objc - 1]));
8166		goto errorExit;
8167	    }
8168	    /* Gather column/obj pairs. */
8169	    STATIC_ALLOC(co, struct columnObj, objc / 2);
8170	    for (i = 4; i < objc; i += 2) {
8171		if (TreeColumnList_FromObj(tree, objv[i], &co[count].columns,
8172			CFO_NOT_NULL | CFO_NOT_TAIL) != TCL_OK) {
8173		    result = TCL_ERROR;
8174		    goto doneTEXT;
8175		}
8176		co[count].obj = objv[i + 1];
8177		count++;
8178	    }
8179	    ITEM_FOR_EACH(item, &itemList, NULL, &iter) {
8180		int changedI = FALSE;
8181		for (i = 0; i < count; i++) {
8182		    COLUMN_FOR_EACH(treeColumn, &co[i].columns, NULL, &citer) {
8183			columnIndex = TreeColumn_Index(treeColumn);
8184			column = Item_FindColumn(tree, item, columnIndex);
8185			if ((column == NULL) || (column->style == NULL)) {
8186			    NoStyleMsg(tree, item, columnIndex);
8187			    result = TCL_ERROR;
8188			    goto doneTEXT;
8189			}
8190			result = isImage ?
8191			    TreeStyle_SetImage(tree, item,
8192				(TreeItemColumn) column, column->style, co[i].obj) :
8193			    TreeStyle_SetText(tree, item,
8194				(TreeItemColumn) column, column->style, co[i].obj);
8195			if (result != TCL_OK)
8196			    goto doneTEXT;
8197			TreeItemColumn_InvalidateSize(tree, (TreeItemColumn) column);
8198			Tree_InvalidateColumnWidth(tree, treeColumn);
8199			changedI = TRUE;
8200		    }
8201		}
8202		if (changedI) {
8203		    TreeItem_InvalidateHeight(tree, item);
8204		    Tree_FreeItemDInfo(tree, item, NULL);
8205		    changed = TRUE;
8206		}
8207	    }
8208	    if (changed)
8209		Tree_DInfoChanged(tree, DINFO_REDO_RANGES);
8210doneTEXT:
8211	    for (i = 0; i < count; i++) {
8212		TreeColumnList_Free(&co[i].columns);
8213	    }
8214	    STATIC_FREE(co, struct columnObj, objc / 2);
8215	    break;
8216	}
8217    }
8218
8219    TreeItemList_Free(&itemList);
8220    TreeItemList_Free(&item2List);
8221    return result;
8222
8223errorExit:
8224    TreeItemList_Free(&itemList);
8225    TreeItemList_Free(&item2List);
8226    return TCL_ERROR;
8227}
8228
8229/*
8230 *----------------------------------------------------------------------
8231 *
8232 * TreeItem_Debug --
8233 *
8234 *	Perform some sanity checks on an Item and its descendants.
8235 *
8236 * Results:
8237 *	TCL_OK or TCL_ERROR.
8238 *
8239 * Side effects:
8240 *	None.
8241 *
8242 *----------------------------------------------------------------------
8243 */
8244
8245int TreeItem_Debug(
8246    TreeCtrl *tree,		/* Widget info. */
8247    TreeItem item		/* Item token. */
8248    )
8249{
8250    TreeItem child;
8251    Tcl_Interp *interp = tree->interp;
8252    int count;
8253
8254    if (item->parent == item) {
8255	FormatResult(interp,
8256		"parent of %d is itself", item->id);
8257	return TCL_ERROR;
8258    }
8259
8260    if (item->parent == NULL) {
8261	if (item->prevSibling != NULL) {
8262	    FormatResult(interp,
8263		    "parent of %d is nil, prevSibling is not nil",
8264		    item->id);
8265	    return TCL_ERROR;
8266	}
8267	if (item->nextSibling != NULL) {
8268	    FormatResult(interp,
8269		    "parent of %d is nil, nextSibling is not nil",
8270		    item->id);
8271	    return TCL_ERROR;
8272	}
8273    }
8274
8275    if (item->prevSibling != NULL) {
8276	if (item->prevSibling == item) {
8277	    FormatResult(interp,
8278		    "prevSibling of %d is itself",
8279		    item->id);
8280	    return TCL_ERROR;
8281	}
8282	if (item->prevSibling->nextSibling != item) {
8283	    FormatResult(interp,
8284		    "item%d.prevSibling.nextSibling is not it",
8285		    item->id);
8286	    return TCL_ERROR;
8287	}
8288    }
8289
8290    if (item->nextSibling != NULL) {
8291	if (item->nextSibling == item) {
8292	    FormatResult(interp,
8293		    "nextSibling of %d is itself",
8294		    item->id);
8295	    return TCL_ERROR;
8296	}
8297	if (item->nextSibling->prevSibling != item) {
8298	    FormatResult(interp,
8299		    "item%d.nextSibling->prevSibling is not it",
8300		    item->id);
8301	    return TCL_ERROR;
8302	}
8303    }
8304
8305    if (item->numChildren < 0) {
8306	FormatResult(interp,
8307		"numChildren of %d is %d",
8308		item->id, item->numChildren);
8309	return TCL_ERROR;
8310    }
8311
8312    if (item->numChildren == 0) {
8313	if (item->firstChild != NULL) {
8314	    FormatResult(interp,
8315		    "item%d.numChildren is zero, firstChild is not nil",
8316		    item->id);
8317	    return TCL_ERROR;
8318	}
8319	if (item->lastChild != NULL) {
8320	    FormatResult(interp,
8321		    "item%d.numChildren is zero, lastChild is not nil",
8322		    item->id);
8323	    return TCL_ERROR;
8324	}
8325    }
8326
8327    if (item->numChildren > 0) {
8328	if (item->firstChild == NULL) {
8329	    FormatResult(interp,
8330		    "item%d.firstChild is nil",
8331		    item->id);
8332	    return TCL_ERROR;
8333	}
8334	if (item->firstChild == item) {
8335	    FormatResult(interp,
8336		    "item%d.firstChild is itself",
8337		    item->id);
8338	    return TCL_ERROR;
8339	}
8340	if (item->firstChild->parent != item) {
8341	    FormatResult(interp,
8342		    "item%d.firstChild.parent is not it",
8343		    item->id);
8344	    return TCL_ERROR;
8345	}
8346	if (item->firstChild->prevSibling != NULL) {
8347	    FormatResult(interp,
8348		    "item%d.firstChild.prevSibling is not nil",
8349		    item->id);
8350	    return TCL_ERROR;
8351	}
8352
8353	if (item->lastChild == NULL) {
8354	    FormatResult(interp,
8355		    "item%d.lastChild is nil",
8356		    item->id);
8357	    return TCL_ERROR;
8358	}
8359	if (item->lastChild == item) {
8360	    FormatResult(interp,
8361		    "item%d.lastChild is itself",
8362		    item->id);
8363	    return TCL_ERROR;
8364	}
8365	if (item->lastChild->parent != item) {
8366	    FormatResult(interp,
8367		    "item%d.lastChild.parent is not it",
8368		    item->id);
8369	    return TCL_ERROR;
8370	}
8371	if (item->lastChild->nextSibling != NULL) {
8372	    FormatResult(interp,
8373		    "item%d.lastChild.nextSibling is not nil",
8374		    item->id);
8375	    return TCL_ERROR;
8376	}
8377
8378	/* Count number of children */
8379	count = 0;
8380	child = item->firstChild;
8381	while (child != NULL) {
8382	    count++;
8383	    child = child->nextSibling;
8384	}
8385	if (count != item->numChildren) {
8386	    FormatResult(interp,
8387		    "item%d.numChildren is %d, but counted %d",
8388		    item->id, item->numChildren, count);
8389	    return TCL_ERROR;
8390	}
8391
8392	/* Debug each child recursively */
8393	child = item->firstChild;
8394	while (child != NULL) {
8395	    if (child->parent != item) {
8396		FormatResult(interp,
8397			"child->parent of %d is not it",
8398			item->id);
8399		return TCL_ERROR;
8400	    }
8401	    if (TreeItem_Debug(tree, child) != TCL_OK)
8402		return TCL_ERROR;
8403	    child = child->nextSibling;
8404	}
8405    }
8406    return TCL_OK;
8407}
8408
8409/*
8410 *----------------------------------------------------------------------
8411 *
8412 * SpanWalkProc_Identify --
8413 *
8414 *	Callback routine to TreeItem_WalkSpans for TreeItem_Identify.
8415 *
8416 * Results:
8417 *	None.
8418 *
8419 * Side effects:
8420 *	None.
8421 *
8422 *----------------------------------------------------------------------
8423 */
8424
8425static int
8426SpanWalkProc_Identify(
8427    TreeCtrl *tree,
8428    TreeItem item,
8429    SpanInfo *spanPtr,
8430    StyleDrawArgs *drawArgs,
8431    ClientData clientData
8432    )
8433{
8434    struct {
8435	int x;
8436	int y;
8437	char *buf;
8438    } *data = clientData;
8439
8440    if ((data->x < drawArgs->x + drawArgs->indent) ||
8441	    (data->x >= drawArgs->x + drawArgs->width))
8442	return 0;
8443
8444    sprintf(data->buf + strlen(data->buf), " column %s%d",
8445	    tree->columnPrefix, TreeColumn_GetID(spanPtr->treeColumn));
8446
8447    if (drawArgs->style != NULL) {
8448	char *elem = TreeStyle_Identify(drawArgs, data->x, data->y);
8449	if (elem != NULL)
8450	    sprintf(data->buf + strlen(data->buf), " elem %s", elem);
8451    }
8452    return 1; /* stop */
8453}
8454
8455/*
8456 *----------------------------------------------------------------------
8457 *
8458 * TreeItem_Identify --
8459 *
8460 *	Determine which column and element the given point is in.
8461 *	This is used by the [identify] widget command.
8462 *
8463 * Results:
8464 *	If the Item is not ReallyVisible() or no columns are visible
8465 *	then buf[] is untouched. Otherwise the given string may be
8466 *	appended with "column C" followed by "elem E" although both
8467 *	are optional.
8468 *
8469 * Side effects:
8470 *	None.
8471 *
8472 *----------------------------------------------------------------------
8473 */
8474
8475void
8476TreeItem_Identify(
8477    TreeCtrl *tree,		/* Widget info. */
8478    TreeItem item,		/* Item token. */
8479    int lock,			/* Columns to hit-test. */
8480    int x, int y,		/* Item coords to hit-test with. */
8481    char *buf			/* NULL-terminated string which may be
8482				 * appended. */
8483    )
8484{
8485    int left, top, width, height;
8486    struct {
8487	int x;
8488	int y;
8489	char *buf;
8490    } clientData;
8491
8492    if (Tree_ItemBbox(tree, item, lock,
8493	    &left, &top, &width, &height) < 0)
8494	return;
8495
8496    /* Tree_ItemBbox returns canvas coords. x/y are item coords. */
8497    clientData.x = x;
8498    clientData.y = y;
8499    clientData.buf = buf;
8500
8501    TreeItem_WalkSpans(tree, item, lock,
8502	    0, 0, width, height,
8503	    SpanWalkProc_Identify, (ClientData) &clientData);
8504}
8505
8506/*
8507 *----------------------------------------------------------------------
8508 *
8509 * SpanWalkProc_Identify2 --
8510 *
8511 *	Callback routine to TreeItem_WalkSpans for TreeItem_Identify2.
8512 *
8513 * Results:
8514 *	None.
8515 *
8516 * Side effects:
8517 *	None.
8518 *
8519 *----------------------------------------------------------------------
8520 */
8521
8522static int
8523SpanWalkProc_Identify2(
8524    TreeCtrl *tree,
8525    TreeItem item,
8526    SpanInfo *spanPtr,
8527    StyleDrawArgs *drawArgs,
8528    ClientData clientData
8529    )
8530{
8531    Tcl_Obj *subListObj;
8532    struct {
8533	int x1; int y1;
8534	int x2; int y2;
8535	Tcl_Obj *listObj;
8536    } *data = clientData;
8537
8538    if ((data->x2 < drawArgs->x + drawArgs->indent) ||
8539	    (data->x1 >= drawArgs->x + drawArgs->width))
8540	return 0;
8541
8542    subListObj = Tcl_NewListObj(0, NULL);
8543    Tcl_ListObjAppendElement(tree->interp, subListObj,
8544	    TreeColumn_ToObj(tree, spanPtr->treeColumn));
8545    if (drawArgs->style != NULL) {
8546	StyleDrawArgs drawArgsCopy = *drawArgs;
8547	TreeStyle_Identify2(&drawArgsCopy, data->x1, data->y1,
8548		data->x2, data->y2,
8549		subListObj);
8550    }
8551    Tcl_ListObjAppendElement(tree->interp, data->listObj, subListObj);
8552    return drawArgs->x + drawArgs->width >= data->x2;
8553}
8554
8555/*
8556 *----------------------------------------------------------------------
8557 *
8558 * TreeItem_Identify2 --
8559 *
8560 *	Determine which columns and elements intersect the given
8561 *	area. This is used by the [marquee identify] widget command.
8562 *
8563 * Results:
8564 *	If the Item is not ReallyVisible() or no columns are visible
8565 *	then listObj is untouched. Otherwise the list is appended
8566 *	with C {E ...} C {E...}.
8567 *
8568 * Side effects:
8569 *	None.
8570 *
8571 *----------------------------------------------------------------------
8572 */
8573
8574void
8575TreeItem_Identify2(
8576    TreeCtrl *tree,		/* Widget info. */
8577    TreeItem item,		/* Item token. */
8578    int x1, int y1,		/* Top-left of area to hit-test. */
8579    int x2, int y2,		/* Bottom-right of area to hit-test. */
8580    Tcl_Obj *listObj		/* Initialized list object. */
8581    )
8582{
8583    int left, top, width, height;
8584    struct {
8585	int x1; int y1;
8586	int x2; int y2;
8587	Tcl_Obj *listObj;
8588    } clientData;
8589
8590    if (Tree_ItemBbox(tree, item, COLUMN_LOCK_NONE,
8591	    &left, &top, &width, &height) < 0)
8592	return;
8593
8594    /* Tree_ItemBbox returns canvas coords. x1 etc are canvas coords. */
8595    clientData.x1 = x1;
8596    clientData.y1 = y1;
8597    clientData.x2 = x2;
8598    clientData.y2 = y2;
8599    clientData.listObj = listObj;
8600
8601    TreeItem_WalkSpans(tree, item, COLUMN_LOCK_NONE,
8602	    left, top, width, height,
8603	    SpanWalkProc_Identify2, (ClientData) &clientData);
8604}
8605
8606/*
8607 *----------------------------------------------------------------------
8608 *
8609 * SpanWalkProc_GetRects --
8610 *
8611 *	Callback routine to TreeItem_WalkSpans for TreeItem_GetRects.
8612 *
8613 * Results:
8614 *	None.
8615 *
8616 * Side effects:
8617 *	None.
8618 *
8619 *----------------------------------------------------------------------
8620 */
8621
8622static int
8623SpanWalkProc_GetRects(
8624    TreeCtrl *tree,
8625    TreeItem item,
8626    SpanInfo *spanPtr,
8627    StyleDrawArgs *drawArgs,
8628    ClientData clientData
8629    )
8630{
8631    int objc;
8632    Tcl_Obj *CONST *objv;
8633    struct {
8634	TreeColumn treeColumn;
8635	int count;
8636	Tcl_Obj *CONST *objv;
8637	TreeRectangle *rects;
8638	int result;
8639   } *data = clientData;
8640
8641    if (spanPtr->treeColumn != data->treeColumn)
8642	return 0;
8643
8644    /* Bounds of span. */
8645    if (data->count == 0) {
8646	/* Not sure why I add 'indent' but for compatibility I will leave
8647	 * it in. */
8648	data->rects[0].x = drawArgs->x + drawArgs->indent;
8649	data->rects[0].y = drawArgs->y;
8650	data->rects[0].width = drawArgs->width - drawArgs->indent;
8651	data->rects[0].height = drawArgs->height;
8652	data->result = 1; /* # of rects */
8653	return 1; /* stop */
8654    }
8655
8656    if (drawArgs->style == NULL) {
8657	NoStyleMsg(tree, item, TreeColumn_Index(spanPtr->treeColumn));
8658	data->result = -1; /* error */
8659	return 1; /* stop */
8660    }
8661
8662    if (data->count == -1) {
8663	/* All the elements. */
8664	objc = 0;
8665	objv = NULL;
8666    } else {
8667	objc = data->count;
8668	objv = data->objv;
8669    }
8670
8671    data->result = TreeStyle_GetElemRects(drawArgs, objc, objv, data->rects);
8672    return 1; /* stop */
8673}
8674
8675/*
8676 *----------------------------------------------------------------------
8677 *
8678 * TreeItem_GetRects --
8679 *
8680 *	Returns zero or more bounding boxes for an item-column or
8681 *	element(s) in an item-column.
8682 *
8683 * Results:
8684 *	If the item is not visible, or has zero height/width, or the given
8685 *	column is obscurred by a preceding span, or the column has zero
8686 *	width or is not visible, the return value is zero.
8687 *
8688 *	If count==0, the bounding box of the span is returned and the
8689 *	return value is 1 (for a single rect).
8690 *
8691 *	If count!=0, and the item-column does not have a style assigned, an
8692 *	error is left in the interpreter result and the return value is -1.
8693 *
8694 *	If count==-1, the bounding box of each element in the style is
8695 *	returned and the return value is the number of elements in the style.
8696 *
8697 *	If count>0, the bounding box of each element specified by the objv[]
8698 *	array is returned and the return value is equal to count. If any of
8699 *	the objv[] elements in not in the style, an error is left in the
8700 *	interpreter result and the return value is -1.
8701 *
8702 * Side effects:
8703 *	None.
8704 *
8705 *----------------------------------------------------------------------
8706 */
8707
8708int
8709TreeItem_GetRects(
8710    TreeCtrl *tree,		/* Widget info. */
8711    TreeItem item,		/* Item token. */
8712    TreeColumn treeColumn,	/* The column to get rects for. */
8713    int count,			/* -1 means get rects for all elements.
8714				 * 0 means get bounds of the span.
8715				 * 1+ means objv[] contains names of elements
8716				 *  to get rects for. */
8717    Tcl_Obj *CONST objv[],	/* Array of element names or NULL. */
8718    TreeRectangle rects[]	/* Out: returned bounding boxes. */
8719    )
8720{
8721    int left, top, width, height;
8722    int lock = TreeColumn_Lock(treeColumn);
8723    struct {
8724	TreeColumn treeColumn;
8725	int count;
8726	Tcl_Obj *CONST *objv;
8727	TreeRectangle *rects;
8728	int result;
8729    } clientData;
8730
8731    if (Tree_ItemBbox(tree, item, lock,
8732	    &left, &top, &width, &height) < 0)
8733	return 0;
8734
8735    clientData.treeColumn = treeColumn;
8736    clientData.count = count;
8737    clientData.objv = objv;
8738    clientData.rects = rects;
8739    clientData.result = 0; /* -1 error, 0 no rects, 1+ success */
8740
8741    TreeItem_WalkSpans(tree, item, lock,
8742	    left, top, width, height,
8743	    SpanWalkProc_GetRects, (ClientData) &clientData);
8744
8745    return clientData.result;
8746}
8747
8748/*
8749 *----------------------------------------------------------------------
8750 *
8751 * TreeItem_Init --
8752 *
8753 *	Perform item-related initialization when a new TreeCtrl is
8754 *	created.
8755 *
8756 * Results:
8757 *	TCL_OK.
8758 *
8759 * Side effects:
8760 *	Memory is allocated.
8761 *
8762 *----------------------------------------------------------------------
8763 */
8764
8765int
8766TreeItem_Init(
8767    TreeCtrl *tree		/* Widget info. */
8768    )
8769{
8770    ItemButtonCO_Init(itemOptionSpecs, "-button", ITEM_FLAG_BUTTON,
8771	    ITEM_FLAG_BUTTON_AUTO);
8772    BooleanFlagCO_Init(itemOptionSpecs, "-visible", ITEM_FLAG_VISIBLE);
8773    BooleanFlagCO_Init(itemOptionSpecs, "-wrap", ITEM_FLAG_WRAP);
8774
8775    tree->itemOptionTable = Tk_CreateOptionTable(tree->interp, itemOptionSpecs);
8776
8777    tree->root = Item_AllocRoot(tree);
8778    tree->activeItem = tree->root; /* always non-null */
8779    tree->anchorItem = tree->root; /* always non-null */
8780
8781    return TCL_OK;
8782}
8783