1/* $Id$
2 * Copyright (c) 2004, Joe English
3 *
4 * ttk::treeview widget implementation.
5 */
6
7#include <string.h>
8#include <stdio.h>
9#include <tk.h>
10#include "ttkTheme.h"
11#include "ttkWidget.h"
12
13#define DEF_TREE_ROWS		"10"
14#define DEF_COLWIDTH		"200"
15#define DEF_MINWIDTH		"20"
16
17static const int DEFAULT_ROWHEIGHT 	= 20;
18static const int DEFAULT_INDENT 	= 20;
19static const int HALO   		= 4;	/* separator */
20
21#define TTK_STATE_OPEN TTK_STATE_USER1
22#define TTK_STATE_LEAF TTK_STATE_USER2
23
24#define STATE_CHANGED	 	(0x100)	/* item state option changed */
25
26/*------------------------------------------------------------------------
27 * +++ Tree items.
28 *
29 * INVARIANTS:
30 * 	item->children	==> item->children->parent == item
31 *	item->next	==> item->next->parent == item->parent
32 * 	item->next 	==> item->next->prev == item
33 * 	item->prev 	==> item->prev->next == item
34 */
35
36typedef struct TreeItemRec TreeItem;
37struct TreeItemRec {
38    Tcl_HashEntry *entryPtr;	/* Back-pointer to hash table entry */
39    TreeItem	*parent;	/* Parent item */
40    TreeItem	*children;	/* Linked list of child items */
41    TreeItem	*next;		/* Next sibling */
42    TreeItem	*prev;		/* Previous sibling */
43
44    /*
45     * Options and instance data:
46     */
47    Ttk_State 	state;
48    Tcl_Obj	*textObj;
49    Tcl_Obj	*imageObj;
50    Tcl_Obj	*valuesObj;
51    Tcl_Obj	*openObj;
52    Tcl_Obj	*tagsObj;
53
54    /*
55     * Derived resources:
56     */
57    Ttk_TagSet	tagset;
58    Ttk_ImageSpec *imagespec;
59};
60
61#define ITEM_OPTION_TAGS_CHANGED	0x100
62#define ITEM_OPTION_IMAGE_CHANGED	0x200
63
64static Tk_OptionSpec ItemOptionSpecs[] = {
65    {TK_OPTION_STRING, "-text", "text", "Text",
66	"", Tk_Offset(TreeItem,textObj), -1,
67	0,0,0 },
68    {TK_OPTION_STRING, "-image", "image", "Image",
69	NULL, Tk_Offset(TreeItem,imageObj), -1,
70	TK_OPTION_NULL_OK,0,ITEM_OPTION_IMAGE_CHANGED },
71    {TK_OPTION_STRING, "-values", "values", "Values",
72	NULL, Tk_Offset(TreeItem,valuesObj), -1,
73	TK_OPTION_NULL_OK,0,0 },
74    {TK_OPTION_BOOLEAN, "-open", "open", "Open",
75	"0", Tk_Offset(TreeItem,openObj), -1,
76	0,0,0 },
77    {TK_OPTION_STRING, "-tags", "tags", "Tags",
78	NULL, Tk_Offset(TreeItem,tagsObj), -1,
79	TK_OPTION_NULL_OK,0,ITEM_OPTION_TAGS_CHANGED },
80
81    {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0}
82};
83
84/* + NewItem --
85 * 	Allocate a new, uninitialized, unlinked item
86 */
87static TreeItem *NewItem(void)
88{
89    TreeItem *item = (TreeItem*)ckalloc(sizeof(*item));
90
91    item->entryPtr = 0;
92    item->parent = item->children = item->next = item->prev = NULL;
93
94    item->state = 0ul;
95    item->textObj = NULL;
96    item->imageObj = NULL;
97    item->valuesObj = NULL;
98    item->openObj = NULL;
99    item->tagsObj = NULL;
100
101    item->tagset = NULL;
102    item->imagespec = NULL;
103
104    return item;
105}
106
107/* + FreeItem --
108 * 	Destroy an item
109 */
110static void FreeItem(TreeItem *item)
111{
112    if (item->textObj) { Tcl_DecrRefCount(item->textObj); }
113    if (item->imageObj) { Tcl_DecrRefCount(item->imageObj); }
114    if (item->valuesObj) { Tcl_DecrRefCount(item->valuesObj); }
115    if (item->openObj) { Tcl_DecrRefCount(item->openObj); }
116    if (item->tagsObj) { Tcl_DecrRefCount(item->tagsObj); }
117
118    if (item->tagset)	{ Ttk_FreeTagSet(item->tagset); }
119    if (item->imagespec) { TtkFreeImageSpec(item->imagespec); }
120
121    ckfree((ClientData)item);
122}
123
124static void FreeItemCB(void *clientData) { FreeItem(clientData); }
125
126/* + DetachItem --
127 * 	Unlink an item from the tree.
128 */
129static void DetachItem(TreeItem *item)
130{
131    if (item->parent && item->parent->children == item)
132	item->parent->children = item->next;
133    if (item->prev)
134	item->prev->next = item->next;
135    if (item->next)
136	item->next->prev = item->prev;
137    item->next = item->prev = item->parent = NULL;
138}
139
140/* + InsertItem --
141 * 	Insert an item into the tree after the specified item.
142 *
143 * Preconditions:
144 * 	+ item is currently detached
145 * 	+ prev != NULL ==> prev->parent == parent.
146 */
147static void InsertItem(TreeItem *parent, TreeItem *prev, TreeItem *item)
148{
149    item->parent = parent;
150    item->prev = prev;
151    if (prev) {
152	item->next = prev->next;
153	prev->next = item;
154    } else {
155	item->next = parent->children;
156	parent->children = item;
157    }
158    if (item->next) {
159	item->next->prev = item;
160    }
161}
162
163/* + NextPreorder --
164 * 	Return the next item in preorder traversal order.
165 */
166
167static TreeItem *NextPreorder(TreeItem *item)
168{
169    if (item->children)
170	return item->children;
171    while (!item->next) {
172	item = item->parent;
173	if (!item)
174	    return 0;
175    }
176    return item->next;
177}
178
179/*------------------------------------------------------------------------
180 * +++ Display items and tag options.
181 */
182
183typedef struct {
184    Tcl_Obj *textObj;		/* taken from item / data cell */
185    Tcl_Obj *imageObj;		/* taken from item */
186    Tcl_Obj *anchorObj;		/* from column <<NOTE-ANCHOR>> */
187    Tcl_Obj *backgroundObj;	/* remainder from tag */
188    Tcl_Obj *foregroundObj;
189    Tcl_Obj *fontObj;
190} DisplayItem;
191
192static Tk_OptionSpec TagOptionSpecs[] = {
193    {TK_OPTION_STRING, "-text", "text", "Text",
194	NULL, Tk_Offset(DisplayItem,textObj), -1,
195	TK_OPTION_NULL_OK,0,0 },
196    {TK_OPTION_STRING, "-image", "image", "Image",
197	NULL, Tk_Offset(DisplayItem,imageObj), -1,
198	TK_OPTION_NULL_OK,0,0 },
199    {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
200	NULL, Tk_Offset(DisplayItem,anchorObj), -1,
201	TK_OPTION_NULL_OK, 0, GEOMETRY_CHANGED},	/* <<NOTE-ANCHOR>> */
202    {TK_OPTION_COLOR, "-background", "windowColor", "WindowColor",
203	NULL, Tk_Offset(DisplayItem,backgroundObj), -1,
204	TK_OPTION_NULL_OK,0,0 },
205    {TK_OPTION_COLOR, "-foreground", "textColor", "TextColor",
206	NULL, Tk_Offset(DisplayItem,foregroundObj), -1,
207	TK_OPTION_NULL_OK,0,0 },
208    {TK_OPTION_FONT, "-font", "font", "Font",
209	NULL, Tk_Offset(DisplayItem,fontObj), -1,
210	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
211
212    {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0}
213};
214
215/*------------------------------------------------------------------------
216 * +++ Columns.
217 *
218 * There are separate option tables associated with the column record:
219 * ColumnOptionSpecs is for configuring the column,
220 * and HeadingOptionSpecs is for drawing headings.
221 */
222typedef struct {
223    int 	width;		/* Column width, in pixels */
224    int 	minWidth;	/* Minimum column width, in pixels */
225    int 	stretch;	/* Should column stretch while resizing? */
226    Tcl_Obj	*idObj;		/* Column identifier, from -columns option */
227
228    Tcl_Obj	*anchorObj;	/* -anchor for cell data <<NOTE-ANCHOR>> */
229
230    /* Column heading data:
231     */
232    Tcl_Obj 	*headingObj;		/* Heading label */
233    Tcl_Obj	*headingImageObj;	/* Heading image */
234    Tcl_Obj 	*headingAnchorObj;	/* -anchor for heading label */
235    Tcl_Obj	*headingCommandObj;	/* Command to execute */
236    Tcl_Obj 	*headingStateObj;	/* @@@ testing ... */
237    Ttk_State	headingState;		/* ... */
238
239    /* Temporary storage for cell data
240     */
241    Tcl_Obj 	*data;
242} TreeColumn;
243
244static void InitColumn(TreeColumn *column)
245{
246    column->width = 200;
247    column->minWidth = 20;
248    column->stretch = 1;
249    column->idObj = 0;
250    column->anchorObj = 0;
251
252    column->headingState = 0;
253    column->headingObj = 0;
254    column->headingImageObj = 0;
255    column->headingAnchorObj = 0;
256    column->headingStateObj = 0;
257    column->headingCommandObj = 0;
258
259    column->data = 0;
260}
261
262static void FreeColumn(TreeColumn *column)
263{
264    if (column->idObj) { Tcl_DecrRefCount(column->idObj); }
265    if (column->anchorObj) { Tcl_DecrRefCount(column->anchorObj); }
266
267    if (column->headingObj) { Tcl_DecrRefCount(column->headingObj); }
268    if (column->headingImageObj) { Tcl_DecrRefCount(column->headingImageObj); }
269    if (column->headingAnchorObj) { Tcl_DecrRefCount(column->headingAnchorObj); }
270    if (column->headingStateObj) { Tcl_DecrRefCount(column->headingStateObj); }
271    if (column->headingCommandObj) { Tcl_DecrRefCount(column->headingCommandObj); }
272
273    /* Don't touch column->data, it's scratch storage */
274}
275
276static Tk_OptionSpec ColumnOptionSpecs[] = {
277    {TK_OPTION_INT, "-width", "width", "Width",
278	DEF_COLWIDTH, -1, Tk_Offset(TreeColumn,width),
279	0,0,GEOMETRY_CHANGED },
280    {TK_OPTION_INT, "-minwidth", "minWidth", "MinWidth",
281	DEF_MINWIDTH, -1, Tk_Offset(TreeColumn,minWidth),
282	0,0,0 },
283    {TK_OPTION_BOOLEAN, "-stretch", "stretch", "Stretch",
284	"1", -1, Tk_Offset(TreeColumn,stretch),
285	0,0,0 },
286    {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
287	"w", Tk_Offset(TreeColumn,anchorObj), -1,	/* <<NOTE-ANCHOR>> */
288	0,0,0 },
289    {TK_OPTION_STRING, "-id", "id", "ID",
290	NULL, Tk_Offset(TreeColumn,idObj), -1,
291	TK_OPTION_NULL_OK,0,READONLY_OPTION },
292    {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0}
293};
294
295static Tk_OptionSpec HeadingOptionSpecs[] = {
296    {TK_OPTION_STRING, "-text", "text", "Text",
297	"", Tk_Offset(TreeColumn,headingObj), -1,
298	0,0,0 },
299    {TK_OPTION_STRING, "-image", "image", "Image",
300	"", Tk_Offset(TreeColumn,headingImageObj), -1,
301	0,0,0 },
302    {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
303	"center", Tk_Offset(TreeColumn,headingAnchorObj), -1,
304	0,0,0 },
305    {TK_OPTION_STRING, "-command", "", "",
306	"", Tk_Offset(TreeColumn,headingCommandObj), -1,
307	TK_OPTION_NULL_OK,0,0 },
308    {TK_OPTION_STRING, "state", "", "",
309	"", Tk_Offset(TreeColumn,headingStateObj), -1,
310	0,0,STATE_CHANGED },
311    {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0}
312};
313
314/*------------------------------------------------------------------------
315 * +++ -show option:
316 * TODO: Implement SHOW_BRANCHES.
317 */
318
319#define SHOW_TREE 	(0x1) 	/* Show tree column? */
320#define SHOW_HEADINGS	(0x2)	/* Show heading row? */
321
322#define DEFAULT_SHOW	"tree headings"
323
324static const char *showStrings[] = {
325    "tree", "headings", NULL
326};
327
328static int GetEnumSetFromObj(
329    Tcl_Interp *interp,
330    Tcl_Obj *objPtr,
331    const char *table[],
332    unsigned *resultPtr)
333{
334    unsigned result = 0;
335    int i, objc;
336    Tcl_Obj **objv;
337
338    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK)
339	return TCL_ERROR;
340
341    for (i = 0; i < objc; ++i) {
342	int index;
343	if (TCL_OK != Tcl_GetIndexFromObj(
344		    interp, objv[i], table, "value", TCL_EXACT, &index))
345	{
346	    return TCL_ERROR;
347	}
348	result |= (1 << index);
349    }
350
351    *resultPtr = result;
352    return TCL_OK;
353}
354
355/*------------------------------------------------------------------------
356 * +++ Treeview widget record.
357 *
358 * Dependencies:
359 * 	columns, columnNames: -columns
360 * 	displayColumns:	-columns, -displaycolumns
361 * 	headingHeight: [layout]
362 * 	rowHeight, indent: style
363 */
364typedef struct {
365    /* Resources acquired at initialization-time:
366     */
367    Tk_OptionTable itemOptionTable;
368    Tk_OptionTable columnOptionTable;
369    Tk_OptionTable headingOptionTable;
370    Tk_OptionTable tagOptionTable;
371    Tk_BindingTable bindingTable;
372    Ttk_TagTable tagTable;
373
374    /* Acquired in GetLayout hook:
375     */
376    Ttk_Layout itemLayout;
377    Ttk_Layout cellLayout;
378    Ttk_Layout headingLayout;
379    Ttk_Layout rowLayout;
380
381    int headingHeight;		/* Space for headings */
382    int rowHeight;		/* Height of each item */
383    int indent;			/* #pixels horizontal offset for child items */
384
385    /* Tree data:
386     */
387    Tcl_HashTable items;	/* Map: item name -> item */
388    int serial;			/* Next item # for autogenerated names */
389    TreeItem *root;		/* Root item */
390
391    TreeColumn column0;		/* Column options for display column #0 */
392    TreeColumn *columns;	/* Array of column options for data columns */
393
394    TreeItem *focus;		/* Current focus item */
395    TreeItem *endPtr;		/* See EndPosition() */
396
397    /* Widget options:
398     */
399    Tcl_Obj *columnsObj;	/* List of symbolic column names */
400    Tcl_Obj *displayColumnsObj;	/* List of columns to display */
401
402    Tcl_Obj *heightObj;		/* height (rows) */
403    Tcl_Obj *paddingObj;	/* internal padding */
404
405    Tcl_Obj *showObj;		/* -show list */
406    Tcl_Obj *selectModeObj;	/* -selectmode option */
407
408    Scrollable xscroll;
409    ScrollHandle xscrollHandle;
410    Scrollable yscroll;
411    ScrollHandle yscrollHandle;
412
413    /* Derived resources:
414     */
415    Tcl_HashTable columnNames;	/* Map: column name -> column table entry */
416    int nColumns; 		/* #columns */
417    unsigned showFlags;		/* bitmask of subparts to display */
418
419    TreeColumn **displayColumns; /* List of columns for display (incl tree) */
420    int nDisplayColumns;	/* #display columns */
421    Ttk_Box headingArea;	/* Display area for column headings */
422    Ttk_Box treeArea;   	/* Display area for tree */
423    int slack;			/* Slack space (see Resizing section) */
424
425} TreePart;
426
427typedef struct {
428    WidgetCore core;
429    TreePart tree;
430} Treeview;
431
432#define USER_MASK 		0x0100
433#define COLUMNS_CHANGED 	(USER_MASK)
434#define DCOLUMNS_CHANGED	(USER_MASK<<1)
435#define SCROLLCMD_CHANGED	(USER_MASK<<2)
436#define SHOW_CHANGED 		(USER_MASK<<3)
437
438static const char *SelectModeStrings[] = { "none", "browse", "extended", NULL };
439
440static Tk_OptionSpec TreeviewOptionSpecs[] = {
441    WIDGET_TAKES_FOCUS,
442
443    {TK_OPTION_STRING, "-columns", "columns", "Columns",
444	"", Tk_Offset(Treeview,tree.columnsObj), -1,
445	0,0,COLUMNS_CHANGED | GEOMETRY_CHANGED /*| READONLY_OPTION*/ },
446    {TK_OPTION_STRING, "-displaycolumns","displayColumns","DisplayColumns",
447	"#all", Tk_Offset(Treeview,tree.displayColumnsObj), -1,
448	0,0,DCOLUMNS_CHANGED | GEOMETRY_CHANGED },
449    {TK_OPTION_STRING, "-show", "show", "Show",
450	DEFAULT_SHOW, Tk_Offset(Treeview,tree.showObj), -1,
451	0,0,SHOW_CHANGED | GEOMETRY_CHANGED },
452
453    {TK_OPTION_STRING_TABLE, "-selectmode", "selectMode", "SelectMode",
454	"extended", Tk_Offset(Treeview,tree.selectModeObj), -1,
455	0,(ClientData)SelectModeStrings,0 },
456
457    {TK_OPTION_PIXELS, "-height", "height", "Height",
458	DEF_TREE_ROWS, Tk_Offset(Treeview,tree.heightObj), -1,
459	0,0,GEOMETRY_CHANGED},
460    {TK_OPTION_STRING, "-padding", "padding", "Pad",
461	NULL, Tk_Offset(Treeview,tree.paddingObj), -1,
462	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
463
464    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
465	NULL, -1, Tk_Offset(Treeview, tree.xscroll.scrollCmd),
466	TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED},
467    {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
468	NULL, -1, Tk_Offset(Treeview, tree.yscroll.scrollCmd),
469	TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED},
470
471    WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
472};
473
474/*------------------------------------------------------------------------
475 * +++ Utilities.
476 */
477typedef void (*HashEntryIterator)(void *hashValue);
478
479static void foreachHashEntry(Tcl_HashTable *ht, HashEntryIterator func)
480{
481    Tcl_HashSearch search;
482    Tcl_HashEntry *entryPtr = Tcl_FirstHashEntry(ht, &search);
483    while (entryPtr != NULL) {
484	func(Tcl_GetHashValue(entryPtr));
485	entryPtr = Tcl_NextHashEntry(&search);
486    }
487}
488
489/* + unshare(objPtr) --
490 * 	Ensure that a Tcl_Obj * has refcount 1 -- either return objPtr
491 * 	itself,	or a duplicated copy.
492 */
493static Tcl_Obj *unshare(Tcl_Obj *objPtr)
494{
495    if (Tcl_IsShared(objPtr)) {
496	Tcl_Obj *newObj = Tcl_DuplicateObj(objPtr);
497	Tcl_DecrRefCount(objPtr);
498	Tcl_IncrRefCount(newObj);
499	return newObj;
500    }
501    return objPtr;
502}
503
504/* DisplayLayout --
505 * 	Rebind, place, and draw a layout + object combination.
506 */
507static void DisplayLayout(
508    Ttk_Layout layout, void *recordPtr, Ttk_State state, Ttk_Box b, Drawable d)
509{
510    Ttk_RebindSublayout(layout, recordPtr);
511    Ttk_PlaceLayout(layout, state, b);
512    Ttk_DrawLayout(layout, state, d);
513}
514
515/* + GetColumn --
516 * 	Look up column by name or number.
517 * 	Returns: pointer to column table entry, NULL if not found.
518 * 	Leaves an error message in interp->result on error.
519 */
520static TreeColumn *GetColumn(
521    Tcl_Interp *interp, Treeview *tv, Tcl_Obj *columnIDObj)
522{
523    Tcl_HashEntry *entryPtr;
524    int columnIndex;
525
526    /* Check for named column:
527     */
528    entryPtr = Tcl_FindHashEntry(
529	    &tv->tree.columnNames, Tcl_GetString(columnIDObj));
530    if (entryPtr) {
531	return Tcl_GetHashValue(entryPtr);
532    }
533
534    /* Check for number:
535     */
536    if (Tcl_GetIntFromObj(NULL, columnIDObj, &columnIndex) == TCL_OK) {
537	if (columnIndex < 0 || columnIndex >= tv->tree.nColumns) {
538	    Tcl_ResetResult(interp);
539	    Tcl_AppendResult(interp,
540		    "Column index ",
541		    Tcl_GetString(columnIDObj),
542		    " out of bounds",
543		    NULL);
544	    return NULL;
545	}
546
547	return tv->tree.columns + columnIndex;
548    }
549    Tcl_ResetResult(interp);
550    Tcl_AppendResult(interp,
551	"Invalid column index ", Tcl_GetString(columnIDObj),
552	NULL);
553    return NULL;
554}
555
556/* + FindColumn --
557 * 	Look up column by name, number, or display index.
558 */
559static TreeColumn *FindColumn(
560    Tcl_Interp *interp, Treeview *tv, Tcl_Obj *columnIDObj)
561{
562    int colno;
563
564    if (sscanf(Tcl_GetString(columnIDObj), "#%d", &colno) == 1)
565    {	/* Display column specification, #n */
566	if (colno >= 0 && colno < tv->tree.nDisplayColumns) {
567	    return tv->tree.displayColumns[colno];
568	}
569	/* else */
570	Tcl_ResetResult(interp);
571	Tcl_AppendResult(interp,
572	    "Column ", Tcl_GetString(columnIDObj), " out of range",
573	    NULL);
574	return NULL;
575    }
576
577    return GetColumn(interp, tv, columnIDObj);
578}
579
580/* + FindItem --
581 * 	Locates the item with the specified identifier in the tree.
582 * 	If there is no such item, leaves an error message in interp.
583 */
584static TreeItem *FindItem(
585    Tcl_Interp *interp, Treeview *tv, Tcl_Obj *itemNameObj)
586{
587    const char *itemName = Tcl_GetString(itemNameObj);
588    Tcl_HashEntry *entryPtr =  Tcl_FindHashEntry(&tv->tree.items, itemName);
589
590    if (!entryPtr) {
591	Tcl_ResetResult(interp);
592	Tcl_AppendResult(interp, "Item ", itemName, " not found", NULL);
593	return 0;
594    }
595    return Tcl_GetHashValue(entryPtr);
596}
597
598/* + GetItemListFromObj --
599 * 	Parse a Tcl_Obj * as a list of items.
600 * 	Returns a NULL-terminated array of items; result must
601 * 	be ckfree()d. On error, returns NULL and leaves an error
602 * 	message in interp.
603 */
604
605static TreeItem **GetItemListFromObj(
606    Tcl_Interp *interp, Treeview *tv, Tcl_Obj *objPtr)
607{
608    TreeItem **items;
609    Tcl_Obj **elements;
610    int i, nElements;
611
612    if (Tcl_ListObjGetElements(interp,objPtr,&nElements,&elements) != TCL_OK) {
613	return NULL;
614    }
615
616    items = (TreeItem**)ckalloc((nElements + 1)*sizeof(TreeItem*));
617    for (i = 0; i < nElements; ++i) {
618	items[i] = FindItem(interp, tv, elements[i]);
619	if (!items[i]) {
620	    ckfree((ClientData)items);
621	    return NULL;
622	}
623    }
624    items[i] = NULL;
625    return items;
626}
627
628/* + ItemName --
629 * 	Returns the item's ID.
630 */
631static const char *ItemName(Treeview *tv, TreeItem *item)
632{
633    return Tcl_GetHashKey(&tv->tree.items, item->entryPtr);
634}
635
636/* + ItemID --
637 * 	Returns a fresh Tcl_Obj * (refcount 0) holding the
638 * 	item identifier of the specified item.
639 */
640static Tcl_Obj *ItemID(Treeview *tv, TreeItem *item)
641{
642    return Tcl_NewStringObj(ItemName(tv, item), -1);
643}
644
645/*------------------------------------------------------------------------
646 * +++ Column configuration.
647 */
648
649/* + TreeviewFreeColumns --
650 * 	Free column data.
651 */
652static void TreeviewFreeColumns(Treeview *tv)
653{
654    int i;
655
656    Tcl_DeleteHashTable(&tv->tree.columnNames);
657    Tcl_InitHashTable(&tv->tree.columnNames, TCL_STRING_KEYS);
658
659    if (tv->tree.columns) {
660	for (i = 0; i < tv->tree.nColumns; ++i)
661	    FreeColumn(tv->tree.columns + i);
662	ckfree((ClientData)tv->tree.columns);
663	tv->tree.columns = 0;
664    }
665}
666
667/* + TreeviewInitColumns --
668 *	Initialize column data when -columns changes.
669 *	Returns: TCL_OK or TCL_ERROR;
670 */
671static int TreeviewInitColumns(Tcl_Interp *interp, Treeview *tv)
672{
673    Tcl_Obj **columns;
674    int i, ncols;
675
676    if (Tcl_ListObjGetElements(
677	    interp, tv->tree.columnsObj, &ncols, &columns) != TCL_OK)
678    {
679	return TCL_ERROR;
680    }
681
682    /*
683     * Free old values:
684     */
685    TreeviewFreeColumns(tv);
686
687    /*
688     * Initialize columns array and columnNames hash table:
689     */
690    tv->tree.nColumns = ncols;
691    tv->tree.columns =
692	(TreeColumn*)ckalloc(tv->tree.nColumns * sizeof(TreeColumn));
693
694    for (i = 0; i < ncols; ++i) {
695	int isNew;
696	Tcl_Obj *columnName = Tcl_DuplicateObj(columns[i]);
697
698	Tcl_HashEntry *entryPtr = Tcl_CreateHashEntry(
699	    &tv->tree.columnNames, Tcl_GetString(columnName), &isNew);
700	Tcl_SetHashValue(entryPtr, tv->tree.columns + i);
701
702	InitColumn(tv->tree.columns + i);
703	Tk_InitOptions(
704	    interp, (ClientData)(tv->tree.columns + i),
705	    tv->tree.columnOptionTable, tv->core.tkwin);
706	Tk_InitOptions(
707	    interp, (ClientData)(tv->tree.columns + i),
708	    tv->tree.headingOptionTable, tv->core.tkwin);
709	Tcl_IncrRefCount(columnName);
710	tv->tree.columns[i].idObj = columnName;
711    }
712
713    return TCL_OK;
714}
715
716/* + TreeviewInitDisplayColumns --
717 * 	Initializes the 'displayColumns' array.
718 *
719 * 	Note that displayColumns[0] is always the tree column,
720 * 	even when SHOW_TREE is not set.
721 *
722 * @@@ TODO: disallow duplicated columns
723 */
724static int TreeviewInitDisplayColumns(Tcl_Interp *interp, Treeview *tv)
725{
726    Tcl_Obj **dcolumns;
727    int index, ndcols;
728    TreeColumn **displayColumns = 0;
729
730    if (Tcl_ListObjGetElements(interp,
731	    tv->tree.displayColumnsObj, &ndcols, &dcolumns) != TCL_OK) {
732	return TCL_ERROR;
733    }
734
735    if (!strcmp(Tcl_GetString(tv->tree.displayColumnsObj), "#all")) {
736	ndcols = tv->tree.nColumns;
737	displayColumns = (TreeColumn**)ckalloc((ndcols+1)*sizeof(TreeColumn*));
738	for (index = 0; index < ndcols; ++index) {
739	    displayColumns[index+1] = tv->tree.columns + index;
740	}
741    } else {
742	displayColumns = (TreeColumn**)ckalloc((ndcols+1)*sizeof(TreeColumn*));
743	for (index = 0; index < ndcols; ++index) {
744	    displayColumns[index+1] = GetColumn(interp, tv, dcolumns[index]);
745	    if (!displayColumns[index+1]) {
746		ckfree((ClientData)displayColumns);
747		return TCL_ERROR;
748	    }
749	}
750    }
751    displayColumns[0] = &tv->tree.column0;
752
753    if (tv->tree.displayColumns)
754	ckfree((ClientData)tv->tree.displayColumns);
755    tv->tree.displayColumns = displayColumns;
756    tv->tree.nDisplayColumns = ndcols + 1;
757
758    return TCL_OK;
759}
760
761/*------------------------------------------------------------------------
762 * +++ Resizing.
763 * 	slack invariant: TreeWidth(tree) + slack = treeArea.width
764 */
765
766#define FirstColumn(tv)  ((tv->tree.showFlags&SHOW_TREE) ? 0 : 1)
767
768/* + TreeWidth --
769 * 	Compute the requested tree width from the sum of visible column widths.
770 */
771static int TreeWidth(Treeview *tv)
772{
773    int i = FirstColumn(tv);
774    int width = 0;
775
776    while (i < tv->tree.nDisplayColumns) {
777	width += tv->tree.displayColumns[i++]->width;
778    }
779    return width;
780}
781
782/* + RecomputeSlack --
783 */
784static void RecomputeSlack(Treeview *tv)
785{
786    tv->tree.slack = tv->tree.treeArea.width - TreeWidth(tv);
787}
788
789/* + PickupSlack/DepositSlack --
790 * 	When resizing columns, distribute extra space to 'slack' first,
791 * 	and only adjust column widths if 'slack' goes to zero.
792 * 	That is, don't bother changing column widths if the tree
793 * 	is already scrolled or short.
794 */
795static int PickupSlack(Treeview *tv, int extra)
796{
797    int newSlack = tv->tree.slack + extra;
798
799    if (   (newSlack < 0 && 0 <= tv->tree.slack)
800	|| (newSlack > 0 && 0 >= tv->tree.slack))
801    {
802	tv->tree.slack = 0;
803	return newSlack;
804    } else {
805	tv->tree.slack = newSlack;
806	return 0;
807    }
808}
809
810static void DepositSlack(Treeview *tv, int extra)
811{
812    tv->tree.slack += extra;
813}
814
815/* + Stretch --
816 * 	Adjust width of column by N pixels, down to minimum width.
817 * 	Returns: #pixels actually moved.
818 */
819static int Stretch(TreeColumn *c, int n)
820{
821    int newWidth = n + c->width;
822    if (newWidth < c->minWidth) {
823	n = c->minWidth - c->width;
824	c->width = c->minWidth;
825    } else {
826	c->width = newWidth;
827    }
828    return n;
829}
830
831/* + ShoveLeft --
832 * 	Adjust width of (stretchable) columns to the left by N pixels.
833 * 	Returns: leftover slack.
834 */
835static int ShoveLeft(Treeview *tv, int i, int n)
836{
837    int first = FirstColumn(tv);
838    while (n != 0 && i >= first) {
839	TreeColumn *c = tv->tree.displayColumns[i];
840	if (c->stretch) {
841	    n -= Stretch(c, n);
842	}
843	--i;
844    }
845    return n;
846}
847
848/* + ShoveRight --
849 * 	Adjust width of (stretchable) columns to the right by N pixels.
850 * 	Returns: leftover slack.
851 */
852static int ShoveRight(Treeview *tv, int i, int n)
853{
854    while (n != 0 && i < tv->tree.nDisplayColumns) {
855	TreeColumn *c = tv->tree.displayColumns[i];
856	if (c->stretch) {
857	    n -= Stretch(c, n);
858	}
859	++i;
860    }
861    return n;
862}
863
864/* + DistributeWidth --
865 * 	Distribute n pixels evenly across all stretchable display columns.
866 * 	Returns: leftover slack.
867 * Notes:
868 * 	The "((++w % m) < r)" term is there so that the remainder r = n % m
869 * 	is distributed round-robin.
870 */
871static int DistributeWidth(Treeview *tv, int n)
872{
873    int w = TreeWidth(tv);
874    int m = 0;
875    int i, d, r;
876
877    for (i = FirstColumn(tv); i < tv->tree.nDisplayColumns; ++i) {
878	if (tv->tree.displayColumns[i]->stretch) {
879	    ++m;
880	}
881    }
882    if (m == 0) {
883	return n;
884    }
885
886    d = n / m;
887    r = n % m;
888    if (r < 0) { r += m; --d; }
889
890    for (i = FirstColumn(tv); i < tv->tree.nDisplayColumns; ++i) {
891	TreeColumn *c = tv->tree.displayColumns[i];
892	if (c->stretch) {
893	    n -= Stretch(c, d + ((++w % m) < r));
894	}
895    }
896    return n;
897}
898
899/* + ResizeColumns --
900 * 	Recompute column widths based on available width.
901 * 	Pick up slack first;
902 * 	Distribute the remainder evenly across stretchable columns;
903 * 	If any is still left over due to minwidth constraints, shove left.
904 */
905static void ResizeColumns(Treeview *tv, int newWidth)
906{
907    int delta = newWidth - (TreeWidth(tv) + tv->tree.slack);
908    DepositSlack(tv,
909	ShoveLeft(tv, tv->tree.nDisplayColumns - 1,
910	    DistributeWidth(tv, PickupSlack(tv, delta))));
911}
912
913/* + DragColumn --
914 * 	Move the separator to the right of specified column,
915 * 	adjusting other column widths as necessary.
916 */
917static void DragColumn(Treeview *tv, int i, int delta)
918{
919    TreeColumn *c = tv->tree.displayColumns[i];
920    int dl = delta - ShoveLeft(tv, i-1, delta - Stretch(c, delta));
921    int dr = ShoveRight(tv, i+1, PickupSlack(tv, -dl));
922    DepositSlack(tv, dr);
923}
924
925/*------------------------------------------------------------------------
926 * +++ Event handlers.
927 */
928
929static TreeItem *IdentifyItem(Treeview *tv, int y); /*forward*/
930
931static const unsigned int TreeviewBindEventMask =
932      KeyPressMask|KeyReleaseMask
933    | ButtonPressMask|ButtonReleaseMask
934    | PointerMotionMask|ButtonMotionMask
935    | VirtualEventMask
936    ;
937
938static void TreeviewBindEventProc(void *clientData, XEvent *event)
939{
940    Treeview *tv = clientData;
941    TreeItem *item = NULL;
942    Ttk_TagSet tagset;
943
944    /*
945     * Figure out where to deliver the event.
946     */
947    switch (event->type)
948    {
949	case KeyPress:
950	case KeyRelease:
951	case VirtualEvent:
952	    item = tv->tree.focus;
953	    break;
954	case ButtonPress:
955	case ButtonRelease:
956	    item = IdentifyItem(tv, event->xbutton.y);
957	    break;
958	case MotionNotify:
959	    item = IdentifyItem(tv, event->xmotion.y);
960	    break;
961	default:
962	    break;
963    }
964
965    if (!item) {
966	return;
967    }
968
969    /* ASSERT: Ttk_GetTagSetFromObj succeeds.
970     * NB: must use a local copy of the tagset,
971     * in case a binding script stomps on -tags.
972     */
973    tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, item->tagsObj);
974
975    /*
976     * Fire binding:
977     */
978    Tcl_Preserve(clientData);
979    Tk_BindEvent(tv->tree.bindingTable, event, tv->core.tkwin,
980	    tagset->nTags, (void **)tagset->tags);
981    Tcl_Release(clientData);
982
983    Ttk_FreeTagSet(tagset);
984}
985
986/*------------------------------------------------------------------------
987 * +++ Initialization and cleanup.
988 */
989
990static void TreeviewInitialize(Tcl_Interp *interp, void *recordPtr)
991{
992    Treeview *tv = recordPtr;
993    int unused;
994
995    tv->tree.itemOptionTable =
996	Tk_CreateOptionTable(interp, ItemOptionSpecs);
997    tv->tree.columnOptionTable =
998	Tk_CreateOptionTable(interp, ColumnOptionSpecs);
999    tv->tree.headingOptionTable =
1000	Tk_CreateOptionTable(interp, HeadingOptionSpecs);
1001    tv->tree.tagOptionTable =
1002	Tk_CreateOptionTable(interp, TagOptionSpecs);
1003
1004    tv->tree.tagTable = Ttk_CreateTagTable(
1005	interp, tv->core.tkwin, TagOptionSpecs, sizeof(DisplayItem));
1006    tv->tree.bindingTable = Tk_CreateBindingTable(interp);
1007    Tk_CreateEventHandler(tv->core.tkwin,
1008	TreeviewBindEventMask, TreeviewBindEventProc, tv);
1009
1010    tv->tree.itemLayout
1011	= tv->tree.cellLayout
1012	= tv->tree.headingLayout
1013	= tv->tree.rowLayout
1014	= 0;
1015    tv->tree.headingHeight = tv->tree.rowHeight = DEFAULT_ROWHEIGHT;
1016    tv->tree.indent = DEFAULT_INDENT;
1017
1018    Tcl_InitHashTable(&tv->tree.columnNames, TCL_STRING_KEYS);
1019    tv->tree.nColumns = tv->tree.nDisplayColumns = 0;
1020    tv->tree.columns = NULL;
1021    tv->tree.displayColumns = NULL;
1022    tv->tree.showFlags = ~0;
1023
1024    InitColumn(&tv->tree.column0);
1025    Tk_InitOptions(
1026	interp, (ClientData)(&tv->tree.column0),
1027	tv->tree.columnOptionTable, tv->core.tkwin);
1028    Tk_InitOptions(
1029	interp, (ClientData)(&tv->tree.column0),
1030	tv->tree.headingOptionTable, tv->core.tkwin);
1031
1032    Tcl_InitHashTable(&tv->tree.items, TCL_STRING_KEYS);
1033    tv->tree.serial = 0;
1034
1035    tv->tree.focus = tv->tree.endPtr = 0;
1036
1037    /* Create root item "":
1038     */
1039    tv->tree.root = NewItem();
1040    Tk_InitOptions(interp, (ClientData)tv->tree.root,
1041	tv->tree.itemOptionTable, tv->core.tkwin);
1042    tv->tree.root->tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, NULL);
1043    tv->tree.root->entryPtr = Tcl_CreateHashEntry(&tv->tree.items, "", &unused);
1044    Tcl_SetHashValue(tv->tree.root->entryPtr, tv->tree.root);
1045
1046    /* Scroll handles:
1047     */
1048    tv->tree.xscrollHandle = TtkCreateScrollHandle(&tv->core,&tv->tree.xscroll);
1049    tv->tree.yscrollHandle = TtkCreateScrollHandle(&tv->core,&tv->tree.yscroll);
1050
1051    /* Size parameters:
1052     */
1053    tv->tree.treeArea = tv->tree.headingArea = Ttk_MakeBox(0,0,0,0);
1054    tv->tree.slack = 0;
1055}
1056
1057static void TreeviewCleanup(void *recordPtr)
1058{
1059    Treeview *tv = recordPtr;
1060
1061    Tk_DeleteEventHandler(tv->core.tkwin,
1062	    TreeviewBindEventMask,  TreeviewBindEventProc, tv);
1063    Tk_DeleteBindingTable(tv->tree.bindingTable);
1064    Ttk_DeleteTagTable(tv->tree.tagTable);
1065
1066    if (tv->tree.itemLayout) Ttk_FreeLayout(tv->tree.itemLayout);
1067    if (tv->tree.cellLayout) Ttk_FreeLayout(tv->tree.cellLayout);
1068    if (tv->tree.headingLayout) Ttk_FreeLayout(tv->tree.headingLayout);
1069    if (tv->tree.rowLayout) Ttk_FreeLayout(tv->tree.rowLayout);
1070
1071    TreeviewFreeColumns(tv);
1072
1073    if (tv->tree.displayColumns)
1074	Tcl_Free((ClientData)tv->tree.displayColumns);
1075
1076    foreachHashEntry(&tv->tree.items, FreeItemCB);
1077    Tcl_DeleteHashTable(&tv->tree.items);
1078
1079    TtkFreeScrollHandle(tv->tree.xscrollHandle);
1080    TtkFreeScrollHandle(tv->tree.yscrollHandle);
1081}
1082
1083/* + TreeviewConfigure --
1084 * 	Configuration widget hook.
1085 *
1086 * 	BUG: If user sets -columns and -displaycolumns, but -displaycolumns
1087 * 	has an error, the widget is left in an inconsistent state.
1088 */
1089static int
1090TreeviewConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
1091{
1092    Treeview *tv = recordPtr;
1093    unsigned showFlags = tv->tree.showFlags;
1094
1095    if (mask & COLUMNS_CHANGED) {
1096	if (TreeviewInitColumns(interp, tv) != TCL_OK)
1097	    return TCL_ERROR;
1098	mask |= DCOLUMNS_CHANGED;
1099    }
1100    if (mask & DCOLUMNS_CHANGED) {
1101	if (TreeviewInitDisplayColumns(interp, tv) != TCL_OK)
1102	    return TCL_ERROR;
1103    }
1104    if (mask & SCROLLCMD_CHANGED) {
1105	TtkScrollbarUpdateRequired(tv->tree.xscrollHandle);
1106	TtkScrollbarUpdateRequired(tv->tree.yscrollHandle);
1107    }
1108    if (  (mask & SHOW_CHANGED)
1109	&& GetEnumSetFromObj(
1110		    interp,tv->tree.showObj,showStrings,&showFlags) != TCL_OK)
1111    {
1112	return TCL_ERROR;
1113    }
1114
1115    if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) {
1116	return TCL_ERROR;
1117    }
1118
1119    tv->tree.showFlags = showFlags;
1120
1121    if (mask & (SHOW_CHANGED | DCOLUMNS_CHANGED)) {
1122	RecomputeSlack(tv);
1123    }
1124    return TCL_OK;
1125}
1126
1127/* + ConfigureItem --
1128 * 	Set item options.
1129 */
1130static int ConfigureItem(
1131    Tcl_Interp *interp, Treeview *tv, TreeItem *item,
1132    int objc, Tcl_Obj *const objv[])
1133{
1134    Tk_SavedOptions savedOptions;
1135    int mask;
1136    Ttk_ImageSpec *newImageSpec = NULL;
1137    Ttk_TagSet newTagSet = NULL;
1138
1139    if (Tk_SetOptions(interp, (ClientData)item, tv->tree.itemOptionTable,
1140		objc, objv, tv->core.tkwin, &savedOptions, &mask)
1141		!= TCL_OK)
1142    {
1143	return TCL_ERROR;
1144    }
1145
1146    /* Make sure that -values is a valid list:
1147     */
1148    if (item->valuesObj) {
1149	int unused;
1150	if (Tcl_ListObjLength(interp, item->valuesObj, &unused) != TCL_OK)
1151	    goto error;
1152    }
1153
1154    /* Check -image.
1155     */
1156    if ((mask & ITEM_OPTION_IMAGE_CHANGED) && item->imageObj) {
1157	newImageSpec = TtkGetImageSpec(interp, tv->core.tkwin, item->imageObj);
1158	if (!newImageSpec) {
1159	    goto error;
1160	}
1161    }
1162
1163    /* Check -tags.
1164     * Side effect: may create new tags.
1165     */
1166    if (mask & ITEM_OPTION_TAGS_CHANGED) {
1167	newTagSet = Ttk_GetTagSetFromObj(
1168		interp, tv->tree.tagTable, item->tagsObj);
1169	if (!newTagSet) {
1170	    goto error;
1171	}
1172    }
1173
1174    /* Keep TTK_STATE_OPEN flag in sync with item->openObj.
1175     * We use both a state flag and a Tcl_Obj* resource so elements
1176     * can access the value in either way.
1177     */
1178    if (item->openObj) {
1179	int isOpen;
1180	if (Tcl_GetBooleanFromObj(interp, item->openObj, &isOpen) != TCL_OK)
1181	    goto error;
1182	if (isOpen)
1183	    item->state |= TTK_STATE_OPEN;
1184	else
1185	    item->state &= ~TTK_STATE_OPEN;
1186    }
1187
1188    /* All OK.
1189     */
1190    Tk_FreeSavedOptions(&savedOptions);
1191    if (mask & ITEM_OPTION_TAGS_CHANGED) {
1192	if (item->tagset) { Ttk_FreeTagSet(item->tagset); }
1193	item->tagset = newTagSet;
1194    }
1195    if (mask & ITEM_OPTION_IMAGE_CHANGED) {
1196	if (item->imagespec) { TtkFreeImageSpec(item->imagespec); }
1197	item->imagespec = newImageSpec;
1198    }
1199    TtkRedisplayWidget(&tv->core);
1200    return TCL_OK;
1201
1202error:
1203    Tk_RestoreSavedOptions(&savedOptions);
1204    if (newTagSet) { Ttk_FreeTagSet(newTagSet); }
1205    if (newImageSpec) { TtkFreeImageSpec(newImageSpec); }
1206    return TCL_ERROR;
1207}
1208
1209/* + ConfigureColumn --
1210 * 	Set column options.
1211 */
1212static int ConfigureColumn(
1213    Tcl_Interp *interp, Treeview *tv, TreeColumn *column,
1214    int objc, Tcl_Obj *const objv[])
1215{
1216    Tk_SavedOptions savedOptions;
1217    int mask;
1218
1219    if (Tk_SetOptions(interp, (ClientData)column,
1220	    tv->tree.columnOptionTable, objc, objv, tv->core.tkwin,
1221	    &savedOptions,&mask) != TCL_OK)
1222    {
1223	return TCL_ERROR;
1224    }
1225
1226    if (mask & READONLY_OPTION) {
1227	Tcl_ResetResult(interp);
1228	Tcl_AppendResult(interp, "Attempt to change read-only option", NULL);
1229	goto error;
1230    }
1231
1232    /* Propagate column width changes to overall widget request width,
1233     * but only if the widget is currently unmapped, in order to prevent
1234     * geometry jumping during interactive column resize.
1235     */
1236    if (mask & GEOMETRY_CHANGED) {
1237	if (!Tk_IsMapped(tv->core.tkwin)) {
1238	    TtkResizeWidget(&tv->core);
1239	}
1240	RecomputeSlack(tv);
1241    }
1242    TtkRedisplayWidget(&tv->core);
1243
1244    /* ASSERT: SLACKINVARIANT */
1245
1246    Tk_FreeSavedOptions(&savedOptions);
1247    return TCL_OK;
1248
1249error:
1250    Tk_RestoreSavedOptions(&savedOptions);
1251    return TCL_ERROR;
1252}
1253
1254/* + ConfigureHeading --
1255 * 	Set heading options.
1256 */
1257static int ConfigureHeading(
1258    Tcl_Interp *interp, Treeview *tv, TreeColumn *column,
1259    int objc, Tcl_Obj *const objv[])
1260{
1261    Tk_SavedOptions savedOptions;
1262    int mask;
1263
1264    if (Tk_SetOptions(interp, (ClientData)column,
1265	    tv->tree.headingOptionTable, objc, objv, tv->core.tkwin,
1266	    &savedOptions,&mask) != TCL_OK)
1267    {
1268	return TCL_ERROR;
1269    }
1270
1271    /* @@@ testing ... */
1272    if ((mask & STATE_CHANGED) && column->headingStateObj) {
1273	Ttk_StateSpec stateSpec;
1274	if (Ttk_GetStateSpecFromObj(
1275		interp, column->headingStateObj, &stateSpec) != TCL_OK)
1276	{
1277	    goto error;
1278	}
1279	column->headingState = Ttk_ModifyState(column->headingState,&stateSpec);
1280	Tcl_DecrRefCount(column->headingStateObj);
1281	column->headingStateObj = Ttk_NewStateSpecObj(column->headingState,0);
1282	Tcl_IncrRefCount(column->headingStateObj);
1283    }
1284
1285    TtkRedisplayWidget(&tv->core);
1286    Tk_FreeSavedOptions(&savedOptions);
1287    return TCL_OK;
1288
1289error:
1290    Tk_RestoreSavedOptions(&savedOptions);
1291    return TCL_ERROR;
1292}
1293
1294/*------------------------------------------------------------------------
1295 * +++ Geometry routines.
1296 */
1297
1298/* + CountRows --
1299 * 	Returns the number of viewable rows rooted at item
1300 */
1301static int CountRows(TreeItem *item)
1302{
1303    int rows = 1;
1304
1305    if (item->state & TTK_STATE_OPEN) {
1306	TreeItem *child = item->children;
1307	while (child) {
1308	    rows += CountRows(child);
1309	    child = child->next;
1310	}
1311    }
1312    return rows;
1313}
1314
1315/* + IdentifyRow --
1316 * 	Recursive search for item at specified y position.
1317 * 	Main work routine for IdentifyItem()
1318 */
1319static TreeItem *IdentifyRow(
1320    Treeview *tv,	/* Widget record */
1321    TreeItem *item, 	/* Where to start search */
1322    int *ypos,		/* Scan position */
1323    int y)		/* Target y coordinate */
1324{
1325    while (item) {
1326	int next_ypos = *ypos + tv->tree.rowHeight;
1327	if (*ypos <= y && y <= next_ypos) {
1328	    return item;
1329	}
1330	*ypos = next_ypos;
1331	if (item->state & TTK_STATE_OPEN) {
1332	    TreeItem *subitem = IdentifyRow(tv, item->children, ypos, y);
1333	    if (subitem) {
1334		return subitem;
1335	    }
1336	}
1337	item = item->next;
1338    }
1339    return 0;
1340}
1341
1342/* + IdentifyItem --
1343 * 	Locate the item at the specified y position, if any.
1344 */
1345static TreeItem *IdentifyItem(Treeview *tv, int y)
1346{
1347    int rowHeight = tv->tree.rowHeight;
1348    int ypos = tv->tree.treeArea.y - rowHeight * tv->tree.yscroll.first;
1349    return IdentifyRow(tv, tv->tree.root->children, &ypos, y);
1350}
1351
1352/* + IdentifyDisplayColumn --
1353 * 	Returns the display column number at the specified x position,
1354 * 	or -1 if x is outside any columns.
1355 */
1356static int IdentifyDisplayColumn(Treeview *tv, int x, int *x1)
1357{
1358    int colno = FirstColumn(tv);
1359    int xpos = tv->tree.treeArea.x - tv->tree.xscroll.first;
1360
1361    while (colno < tv->tree.nDisplayColumns) {
1362	TreeColumn *column = tv->tree.displayColumns[colno];
1363	int next_xpos = xpos + column->width;
1364	if (xpos <= x && x <= next_xpos + HALO) {
1365	    *x1 = next_xpos;
1366	    return colno;
1367	}
1368	++colno;
1369	xpos = next_xpos;
1370    }
1371
1372    return -1;
1373}
1374
1375/* + RowNumber --
1376 * 	Calculate which row the specified item appears on;
1377 * 	returns -1 if the item is not viewable.
1378 * 	Xref: DrawForest, IdentifyItem.
1379 */
1380static int RowNumber(Treeview *tv, TreeItem *item)
1381{
1382    TreeItem *p = tv->tree.root->children;
1383    int n = 0;
1384
1385    while (p) {
1386	if (p == item)
1387	    return n;
1388
1389	++n;
1390
1391	/* Find next viewable item in preorder traversal order
1392	 */
1393	if (p->children && (p->state & TTK_STATE_OPEN)) {
1394	    p = p->children;
1395	} else {
1396	    while (!p->next && p && p->parent)
1397		p = p->parent;
1398	    if (p)
1399		p = p->next;
1400	}
1401    }
1402
1403    return -1;
1404}
1405
1406/* + ItemDepth -- return the depth of a tree item.
1407 * 	The depth of an item is equal to the number of proper ancestors,
1408 * 	not counting the root node.
1409 */
1410static int ItemDepth(TreeItem *item)
1411{
1412    int depth = 0;
1413    while (item->parent) {
1414	++depth;
1415	item = item->parent;
1416    }
1417    return depth-1;
1418}
1419
1420/* + ItemRow --
1421 * 	Returns row number of specified item relative to root,
1422 * 	-1 if item is not viewable.
1423 */
1424static int ItemRow(Treeview *tv, TreeItem *p)
1425{
1426    TreeItem *root = tv->tree.root;
1427    int rowNumber = 0;
1428
1429    for (;;) {
1430	if (p->prev) {
1431	    p = p->prev;
1432	    rowNumber += CountRows(p);
1433	} else {
1434	    p = p->parent;
1435	    if (!(p && (p->state & TTK_STATE_OPEN))) {
1436		/* detached or closed ancestor */
1437		return -1;
1438	    }
1439	    if (p == root) {
1440		return rowNumber;
1441	    }
1442	    ++rowNumber;
1443	}
1444    }
1445}
1446
1447/* + BoundingBox --
1448 * 	Compute the parcel of the specified column of the specified item,
1449 *	(or the entire item if column is NULL)
1450 *	Returns: 0 if item or column is not viewable, 1 otherwise.
1451 */
1452static int BoundingBox(
1453    Treeview *tv,		/* treeview widget */
1454    TreeItem *item,		/* desired item */
1455    TreeColumn *column,		/* desired column */
1456    Ttk_Box *bbox_rtn)		/* bounding box of item */
1457{
1458    int row = ItemRow(tv, item);
1459    Ttk_Box bbox = tv->tree.treeArea;
1460
1461    if (row < tv->tree.yscroll.first || row > tv->tree.yscroll.last) {
1462	/* not viewable, or off-screen */
1463	return 0;
1464    }
1465
1466    bbox.y += (row - tv->tree.yscroll.first) * tv->tree.rowHeight;
1467    bbox.height = tv->tree.rowHeight;
1468
1469    bbox.x -= tv->tree.xscroll.first;
1470    bbox.width = TreeWidth(tv);
1471
1472    if (column) {
1473	int xpos = 0, i = FirstColumn(tv);
1474	while (i < tv->tree.nDisplayColumns) {
1475	    if (tv->tree.displayColumns[i] == column) {
1476		break;
1477	    }
1478	    xpos += tv->tree.displayColumns[i]->width;
1479	    ++i;
1480	}
1481	if (i == tv->tree.nDisplayColumns) { /* specified column unviewable */
1482	    return 0;
1483	}
1484	bbox.x += xpos;
1485	bbox.width = column->width;
1486
1487	/* Account for indentation in tree column:
1488	 */
1489	if (column == &tv->tree.column0) {
1490	    int indent = tv->tree.indent * ItemDepth(item);
1491	    bbox.x += indent;
1492	    bbox.width -= indent;
1493	}
1494    }
1495    *bbox_rtn = bbox;
1496    return 1;
1497}
1498
1499/* + IdentifyRegion --
1500 */
1501
1502typedef enum {
1503    REGION_NOTHING = 0,
1504    REGION_HEADING,
1505    REGION_SEPARATOR,
1506    REGION_TREE,
1507    REGION_CELL
1508} TreeRegion;
1509
1510static const char *regionStrings[] = {
1511    "nothing", "heading", "separator", "tree", "cell", 0
1512};
1513
1514static TreeRegion IdentifyRegion(Treeview *tv, int x, int y)
1515{
1516    int x1 = 0, colno;
1517
1518    colno = IdentifyDisplayColumn(tv, x, &x1);
1519    if (Ttk_BoxContains(tv->tree.headingArea, x, y)) {
1520	if (colno < 0) {
1521	    return REGION_NOTHING;
1522	} else if (-HALO <= x1 - x  && x1 - x <= HALO) {
1523	    return REGION_SEPARATOR;
1524	} else {
1525	    return REGION_HEADING;
1526	}
1527    } else if (Ttk_BoxContains(tv->tree.treeArea, x, y)) {
1528	TreeItem *item = IdentifyItem(tv, y);
1529	if (item && colno > 0) {
1530	    return REGION_CELL;
1531	} else if (item) {
1532	    return REGION_TREE;
1533	}
1534    }
1535    return REGION_NOTHING;
1536}
1537
1538/*------------------------------------------------------------------------
1539 * +++ Display routines.
1540 */
1541
1542/* + GetSublayout --
1543 * 	Utility routine; acquires a sublayout for items, cells, etc.
1544 */
1545static Ttk_Layout GetSublayout(
1546    Tcl_Interp *interp,
1547    Ttk_Theme themePtr,
1548    Ttk_Layout parentLayout,
1549    const char *layoutName,
1550    Tk_OptionTable optionTable,
1551    Ttk_Layout *layoutPtr)
1552{
1553    Ttk_Layout newLayout = Ttk_CreateSublayout(
1554	    interp, themePtr, parentLayout, layoutName, optionTable);
1555
1556    if (newLayout) {
1557	if (*layoutPtr)
1558	    Ttk_FreeLayout(*layoutPtr);
1559	*layoutPtr = newLayout;
1560    }
1561    return newLayout;
1562}
1563
1564/* + TreeviewGetLayout --
1565 * 	GetLayout() widget hook.
1566 */
1567static Ttk_Layout TreeviewGetLayout(
1568    Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr)
1569{
1570    Treeview *tv = recordPtr;
1571    Ttk_Layout treeLayout = TtkWidgetGetLayout(interp, themePtr, recordPtr);
1572    Tcl_Obj *objPtr;
1573    int unused;
1574
1575    if (!(
1576	treeLayout
1577     && GetSublayout(interp, themePtr, treeLayout, ".Item",
1578	    tv->tree.tagOptionTable, &tv->tree.itemLayout)
1579     && GetSublayout(interp, themePtr, treeLayout, ".Cell",
1580	    tv->tree.tagOptionTable, &tv->tree.cellLayout)
1581     && GetSublayout(interp, themePtr, treeLayout, ".Heading",
1582	    tv->tree.headingOptionTable, &tv->tree.headingLayout)
1583     && GetSublayout(interp, themePtr, treeLayout, ".Row",
1584	    tv->tree.tagOptionTable, &tv->tree.rowLayout)
1585    )) {
1586	return 0;
1587    }
1588
1589    /* Compute heading height.
1590     */
1591    Ttk_RebindSublayout(tv->tree.headingLayout, &tv->tree.column0);
1592    Ttk_LayoutSize(tv->tree.headingLayout, 0, &unused, &tv->tree.headingHeight);
1593
1594    /* Get item height, indent from style:
1595     * @@@ TODO: sanity-check.
1596     */
1597    tv->tree.rowHeight = DEFAULT_ROWHEIGHT;
1598    tv->tree.indent = DEFAULT_INDENT;
1599    if ((objPtr = Ttk_QueryOption(treeLayout, "-rowheight", 0))) {
1600	(void)Tcl_GetIntFromObj(NULL, objPtr, &tv->tree.rowHeight);
1601    }
1602    if ((objPtr = Ttk_QueryOption(treeLayout, "-indent", 0))) {
1603	(void)Tcl_GetIntFromObj(NULL, objPtr, &tv->tree.indent);
1604    }
1605
1606    return treeLayout;
1607}
1608
1609/* + TreeviewDoLayout --
1610 * 	DoLayout() widget hook.  Computes widget layout.
1611 *
1612 * Side effects:
1613 * 	Computes headingArea and treeArea.
1614 * 	Computes subtree height.
1615 * 	Invokes scroll callbacks.
1616 */
1617static void TreeviewDoLayout(void *clientData)
1618{
1619    Treeview *tv = clientData;
1620    int visibleRows;
1621
1622    /* ASSERT: SLACKINVARIANT */
1623
1624    Ttk_PlaceLayout(tv->core.layout,tv->core.state,Ttk_WinBox(tv->core.tkwin));
1625    tv->tree.treeArea = Ttk_ClientRegion(tv->core.layout, "treearea");
1626
1627    ResizeColumns(tv, tv->tree.treeArea.width);
1628    /* ASSERT: SLACKINVARIANT */
1629
1630    TtkScrolled(tv->tree.xscrollHandle,
1631	    tv->tree.xscroll.first,
1632	    tv->tree.xscroll.first + tv->tree.treeArea.width,
1633	    TreeWidth(tv));
1634
1635    if (tv->tree.showFlags & SHOW_HEADINGS) {
1636	tv->tree.headingArea = Ttk_PackBox(
1637	    &tv->tree.treeArea, 1, tv->tree.headingHeight, TTK_SIDE_TOP);
1638    } else {
1639	tv->tree.headingArea = Ttk_MakeBox(0,0,0,0);
1640    }
1641
1642    visibleRows = tv->tree.treeArea.height / tv->tree.rowHeight;
1643    tv->tree.root->state |= TTK_STATE_OPEN;
1644    TtkScrolled(tv->tree.yscrollHandle,
1645	    tv->tree.yscroll.first,
1646	    tv->tree.yscroll.first + visibleRows,
1647	    CountRows(tv->tree.root) - 1);
1648}
1649
1650/* + TreeviewSize --
1651 * 	SizeProc() widget hook.  Size is determined by
1652 * 	-height option and column widths.
1653 */
1654static int TreeviewSize(void *clientData, int *widthPtr, int *heightPtr)
1655{
1656    Treeview *tv = clientData;
1657    int nRows, padHeight, padWidth;
1658
1659    Ttk_LayoutSize(tv->core.layout, tv->core.state, &padWidth, &padHeight);
1660    Tcl_GetIntFromObj(NULL, tv->tree.heightObj, &nRows);
1661
1662    *widthPtr = padWidth + TreeWidth(tv);
1663    *heightPtr = padHeight + tv->tree.rowHeight * nRows;
1664
1665    if (tv->tree.showFlags & SHOW_HEADINGS) {
1666	*heightPtr += tv->tree.headingHeight;
1667    }
1668
1669    return 1;
1670}
1671
1672/* + ItemState --
1673 * 	Returns the state of the specified item, based
1674 * 	on widget state, item state, and other information.
1675 */
1676static Ttk_State ItemState(Treeview *tv, TreeItem *item)
1677{
1678    Ttk_State state = tv->core.state | item->state;
1679    if (!item->children)
1680	state |= TTK_STATE_LEAF;
1681    if (item != tv->tree.focus)
1682	state &= ~TTK_STATE_FOCUS;
1683    return state;
1684}
1685
1686/* + DrawHeadings --
1687 *	Draw tree headings.
1688 */
1689static void DrawHeadings(Treeview *tv, Drawable d)
1690{
1691    const int x0 = tv->tree.headingArea.x - tv->tree.xscroll.first;
1692    const int y0 = tv->tree.headingArea.y;
1693    const int h0 = tv->tree.headingArea.height;
1694    int i = FirstColumn(tv);
1695    int x = 0;
1696
1697    while (i < tv->tree.nDisplayColumns) {
1698	TreeColumn *column = tv->tree.displayColumns[i];
1699	Ttk_Box parcel = Ttk_MakeBox(x0+x, y0, column->width, h0);
1700	DisplayLayout(tv->tree.headingLayout,
1701	    column, column->headingState, parcel, d);
1702	x += column->width;
1703	++i;
1704    }
1705}
1706
1707/* + PrepareItem --
1708 * 	Fill in a displayItem record.
1709 */
1710static void PrepareItem(
1711    Treeview *tv, TreeItem *item, DisplayItem *displayItem)
1712{
1713    Ttk_Style style = Ttk_LayoutStyle(tv->core.layout);
1714    Ttk_State state = ItemState(tv, item);
1715
1716    Ttk_TagSetValues(tv->tree.tagTable, item->tagset, displayItem);
1717    Ttk_TagSetApplyStyle(tv->tree.tagTable, style, state, displayItem);
1718}
1719
1720/* + DrawCells --
1721 *	Draw data cells for specified item.
1722 */
1723static void DrawCells(
1724    Treeview *tv, TreeItem *item, DisplayItem *displayItem,
1725    Drawable d, int x, int y)
1726{
1727    Ttk_Layout layout = tv->tree.cellLayout;
1728    Ttk_State state = ItemState(tv, item);
1729    Ttk_Padding cellPadding = {4, 0, 4, 0};
1730    int rowHeight = tv->tree.rowHeight;
1731    int nValues = 0;
1732    Tcl_Obj **values = 0;
1733    int i;
1734
1735    if (!item->valuesObj) {
1736	return;
1737    }
1738
1739    Tcl_ListObjGetElements(NULL, item->valuesObj, &nValues, &values);
1740    for (i = 0; i < tv->tree.nColumns; ++i) {
1741	tv->tree.columns[i].data = (i < nValues) ? values[i] : 0;
1742    }
1743
1744    for (i = 1; i < tv->tree.nDisplayColumns; ++i) {
1745	TreeColumn *column = tv->tree.displayColumns[i];
1746	Ttk_Box parcel = Ttk_PadBox(
1747	    Ttk_MakeBox(x, y, column->width, rowHeight), cellPadding);
1748
1749	displayItem->textObj = column->data;
1750	displayItem->anchorObj = column->anchorObj;	/* <<NOTE-ANCHOR>> */
1751
1752	DisplayLayout(layout, displayItem, state, parcel, d);
1753	x += column->width;
1754    }
1755}
1756
1757/* + DrawItem --
1758 * 	Draw an item (row background, tree label, and cells).
1759 */
1760static void DrawItem(
1761    Treeview *tv, TreeItem *item, Drawable d, int depth, int row)
1762{
1763    Ttk_State state = ItemState(tv, item);
1764    DisplayItem displayItem;
1765    int rowHeight = tv->tree.rowHeight;
1766    int x = tv->tree.treeArea.x - tv->tree.xscroll.first;
1767    int y = tv->tree.treeArea.y + rowHeight * (row - tv->tree.yscroll.first);
1768
1769    if (row % 2) state |= TTK_STATE_ALTERNATE;
1770
1771    PrepareItem(tv, item, &displayItem);
1772
1773    /* Draw row background:
1774     */
1775    {
1776	Ttk_Box rowBox = Ttk_MakeBox(x, y, TreeWidth(tv), rowHeight);
1777	DisplayLayout(tv->tree.rowLayout, &displayItem, state, rowBox, d);
1778    }
1779
1780    /* Draw tree label:
1781     */
1782    if (tv->tree.showFlags & SHOW_TREE) {
1783	int indent = depth * tv->tree.indent;
1784	int colwidth = tv->tree.column0.width;
1785	Ttk_Box parcel = Ttk_MakeBox(
1786		x+indent, y, colwidth-indent, rowHeight);
1787	if (item->textObj) { displayItem.textObj = item->textObj; }
1788	if (item->imageObj) { displayItem.imageObj = item->imageObj; }
1789	/* ??? displayItem.anchorObj = 0; <<NOTE-ANCHOR>> */
1790	DisplayLayout(tv->tree.itemLayout, &displayItem, state, parcel, d);
1791	x += colwidth;
1792    }
1793
1794    /* Draw data cells:
1795     */
1796    DrawCells(tv, item, &displayItem, d, x, y);
1797}
1798
1799/* + DrawSubtree --
1800 * 	Draw an item and all of its (viewable) descendants.
1801 *
1802 * Returns:
1803 * 	Row number of the last item drawn.
1804 */
1805
1806static int DrawForest(	/* forward */
1807    Treeview *tv, TreeItem *item, Drawable d, int depth, int row);
1808
1809static int DrawSubtree(
1810    Treeview *tv, TreeItem *item, Drawable d, int depth, int row)
1811{
1812    if (row >= tv->tree.yscroll.first) {
1813	DrawItem(tv, item, d, depth, row);
1814    }
1815
1816    if (item->state & TTK_STATE_OPEN) {
1817	return DrawForest(tv, item->children, d, depth + 1, row + 1);
1818    } else {
1819	return row + 1;
1820    }
1821}
1822
1823/* + DrawForest --
1824 * 	Draw a sequence of items and their visible descendants.
1825 *
1826 * Returns:
1827 * 	Row number of the last item drawn.
1828 */
1829static int DrawForest(
1830    Treeview *tv, TreeItem *item, Drawable d, int depth, int row)
1831{
1832    while (item && row <= tv->tree.yscroll.last) {
1833        row = DrawSubtree(tv, item, d, depth, row);
1834	item = item->next;
1835    }
1836    return row;
1837}
1838
1839/* + TreeviewDisplay --
1840 * 	Display() widget hook.  Draw the widget contents.
1841 */
1842static void TreeviewDisplay(void *clientData, Drawable d)
1843{
1844    Treeview *tv = clientData;
1845
1846    Ttk_DrawLayout(tv->core.layout, tv->core.state, d);
1847    if (tv->tree.showFlags & SHOW_HEADINGS) {
1848	DrawHeadings(tv, d);
1849    }
1850    DrawForest(tv, tv->tree.root->children, d, 0,0);
1851}
1852
1853/*------------------------------------------------------------------------
1854 * +++ Utilities for widget commands
1855 */
1856
1857/* + InsertPosition --
1858 * 	Locate the previous sibling for [$tree insert].
1859 *
1860 * 	Returns a pointer to the item just before the specified index,
1861 * 	or 0 if the item is to be inserted at the beginning.
1862 */
1863static TreeItem *InsertPosition(TreeItem *parent, int index)
1864{
1865    TreeItem *prev = 0, *next = parent->children;
1866
1867    while (next != 0 && index > 0) {
1868	--index;
1869	prev = next;
1870	next = prev->next;
1871    }
1872
1873    return prev;
1874}
1875
1876/* + EndPosition --
1877 * 	Locate the last child of the specified node.
1878 *
1879 * 	To avoid quadratic-time behavior in the common cases
1880 * 	where the treeview is populated in breadth-first or
1881 * 	depth-first order using [$tv insert $parent end ...],
1882 * 	we cache the result from the last call to EndPosition()
1883 * 	and start the search from there on a cache hit.
1884 *
1885 */
1886static TreeItem *EndPosition(Treeview *tv, TreeItem *parent)
1887{
1888    TreeItem *endPtr = tv->tree.endPtr;
1889
1890    while (endPtr && endPtr->parent != parent) {
1891	endPtr = endPtr->parent;
1892    }
1893    if (!endPtr) {
1894	endPtr = parent->children;
1895    }
1896
1897    if (endPtr) {
1898	while (endPtr->next) {
1899	    endPtr = endPtr->next;
1900	}
1901	tv->tree.endPtr = endPtr;
1902    }
1903
1904    return endPtr;
1905}
1906
1907/* + AncestryCheck --
1908 * 	Verify that specified item is not an ancestor of the specified parent;
1909 * 	returns 1 if OK, 0 and leaves an error message in interp otherwise.
1910 */
1911static int AncestryCheck(
1912    Tcl_Interp *interp, Treeview *tv, TreeItem *item, TreeItem *parent)
1913{
1914    TreeItem *p = parent;
1915    while (p) {
1916	if (p == item) {
1917	    Tcl_ResetResult(interp);
1918	    Tcl_AppendResult(interp,
1919		    "Cannot insert ", ItemName(tv, item),
1920		    " as a descendant of ", ItemName(tv, parent),
1921		    NULL);
1922	    return 0;
1923	}
1924	p = p->parent;
1925    }
1926    return 1;
1927}
1928
1929/* + DeleteItems --
1930 * 	Remove an item and all of its descendants from the hash table
1931 * 	and detach them from the tree; returns a linked list (chained
1932 * 	along the ->next pointer) of deleted items.
1933 */
1934static TreeItem *DeleteItems(TreeItem *item, TreeItem *delq)
1935{
1936    if (item->entryPtr) {
1937	DetachItem(item);
1938	while (item->children) {
1939	    delq = DeleteItems(item->children, delq);
1940	}
1941	Tcl_DeleteHashEntry(item->entryPtr);
1942	item->entryPtr = 0;
1943	item->next = delq;
1944	delq = item;
1945    } /* else -- item has already been unlinked */
1946    return delq;
1947}
1948
1949/*------------------------------------------------------------------------
1950 * +++ Widget commands -- item inquiry.
1951 */
1952
1953/* + $tv children $item ?newchildren? --
1954 * 	Return the list of children associated with $item
1955 */
1956static int TreeviewChildrenCommand(
1957    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1958{
1959    Treeview *tv = recordPtr;
1960    TreeItem *item;
1961    Tcl_Obj *result;
1962
1963    if (objc < 3 || objc > 4) {
1964	Tcl_WrongNumArgs(interp, 2, objv, "item ?newchildren?");
1965	return TCL_ERROR;
1966    }
1967    item = FindItem(interp, tv, objv[2]);
1968    if (!item) {
1969	return TCL_ERROR;
1970    }
1971
1972    if (objc == 3) {
1973	result = Tcl_NewListObj(0,0);
1974	for (item = item->children; item; item = item->next) {
1975	    Tcl_ListObjAppendElement(interp, result, ItemID(tv, item));
1976	}
1977	Tcl_SetObjResult(interp, result);
1978    } else {
1979	TreeItem **newChildren = GetItemListFromObj(interp, tv, objv[3]);
1980	TreeItem *child;
1981	int i;
1982
1983	if (!newChildren)
1984	    return TCL_ERROR;
1985
1986	/* Sanity-check:
1987	 */
1988	for (i=0; newChildren[i]; ++i) {
1989	    if (!AncestryCheck(interp, tv, newChildren[i], item)) {
1990		ckfree((ClientData)newChildren);
1991		return TCL_ERROR;
1992	    }
1993	}
1994
1995	/* Detach old children:
1996	 */
1997	child = item->children;
1998	while (child) {
1999	    TreeItem *next = child->next;
2000	    DetachItem(child);
2001	    child = next;
2002	}
2003
2004	/* Detach new children from their current locations:
2005	 */
2006	for (i=0; newChildren[i]; ++i) {
2007	    DetachItem(newChildren[i]);
2008	}
2009
2010	/* Reinsert new children:
2011	 * Note: it is not an error for an item to be listed more than once,
2012	 * though it probably should be...
2013	 */
2014	child = 0;
2015	for (i=0; newChildren[i]; ++i) {
2016	    if (newChildren[i]->parent) {
2017		/* This is a duplicate element which has already been
2018		 * inserted.  Ignore it.
2019		 */
2020		continue;
2021	    }
2022	    InsertItem(item, child, newChildren[i]);
2023	    child = newChildren[i];
2024	}
2025
2026	ckfree((ClientData)newChildren);
2027	TtkRedisplayWidget(&tv->core);
2028    }
2029
2030    return TCL_OK;
2031}
2032
2033/* + $tv parent $item --
2034 * 	Return the item ID of $item's parent.
2035 */
2036static int TreeviewParentCommand(
2037    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2038{
2039    Treeview *tv = recordPtr;
2040    TreeItem *item;
2041
2042    if (objc != 3) {
2043	Tcl_WrongNumArgs(interp, 2, objv, "item");
2044	return TCL_ERROR;
2045    }
2046    item = FindItem(interp, tv, objv[2]);
2047    if (!item) {
2048	return TCL_ERROR;
2049    }
2050
2051    if (item->parent) {
2052	Tcl_SetObjResult(interp, ItemID(tv, item->parent));
2053    } else {
2054	/* This is the root item.  @@@ Return an error? */
2055	Tcl_ResetResult(interp);
2056    }
2057
2058    return TCL_OK;
2059}
2060
2061/* + $tv next $item
2062 * 	Return the ID of $item's next sibling.
2063 */
2064static int TreeviewNextCommand(
2065    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2066{
2067    Treeview *tv = recordPtr;
2068    TreeItem *item;
2069
2070    if (objc != 3) {
2071	Tcl_WrongNumArgs(interp, 2, objv, "item");
2072	return TCL_ERROR;
2073    }
2074    item = FindItem(interp, tv, objv[2]);
2075    if (!item) {
2076	return TCL_ERROR;
2077    }
2078
2079    if (item->next) {
2080	Tcl_SetObjResult(interp, ItemID(tv, item->next));
2081    } /* else -- leave interp-result empty */
2082
2083    return TCL_OK;
2084}
2085
2086/* + $tv prev $item
2087 * 	Return the ID of $item's previous sibling.
2088 */
2089static int TreeviewPrevCommand(
2090    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2091{
2092    Treeview *tv = recordPtr;
2093    TreeItem *item;
2094
2095    if (objc != 3) {
2096	Tcl_WrongNumArgs(interp, 2, objv, "item");
2097	return TCL_ERROR;
2098    }
2099    item = FindItem(interp, tv, objv[2]);
2100    if (!item) {
2101	return TCL_ERROR;
2102    }
2103
2104    if (item->prev) {
2105	Tcl_SetObjResult(interp, ItemID(tv, item->prev));
2106    } /* else -- leave interp-result empty */
2107
2108    return TCL_OK;
2109}
2110
2111/* + $tv index $item --
2112 * 	Return the index of $item within its parent.
2113 */
2114static int TreeviewIndexCommand(
2115    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2116{
2117    Treeview *tv = recordPtr;
2118    TreeItem *item;
2119    int index = 0;
2120
2121    if (objc != 3) {
2122	Tcl_WrongNumArgs(interp, 2, objv, "item");
2123	return TCL_ERROR;
2124    }
2125    item = FindItem(interp, tv, objv[2]);
2126    if (!item) {
2127	return TCL_ERROR;
2128    }
2129
2130    while (item->prev) {
2131	++index;
2132	item = item->prev;
2133    }
2134
2135    Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
2136    return TCL_OK;
2137}
2138
2139/* + $tv exists $itemid --
2140 * 	Test if the specified item id is present in the tree.
2141 */
2142static int TreeviewExistsCommand(
2143    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2144{
2145    Treeview *tv = recordPtr;
2146    Tcl_HashEntry *entryPtr;
2147
2148    if (objc != 3) {
2149	Tcl_WrongNumArgs(interp, 2, objv, "itemid");
2150	return TCL_ERROR;
2151    }
2152
2153    entryPtr = Tcl_FindHashEntry(&tv->tree.items, Tcl_GetString(objv[2]));
2154    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(entryPtr != 0));
2155    return TCL_OK;
2156}
2157
2158/* + $tv bbox $itemid ?$column? --
2159 * 	Return bounding box [x y width height] of specified item.
2160 */
2161static int TreeviewBBoxCommand(
2162    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2163{
2164    Treeview *tv = recordPtr;
2165    TreeItem *item = 0;
2166    TreeColumn *column = 0;
2167    Ttk_Box bbox;
2168
2169    if (objc < 3 || objc > 4) {
2170	Tcl_WrongNumArgs(interp, 2, objv, "itemid ?column");
2171	return TCL_ERROR;
2172    }
2173
2174    item = FindItem(interp, tv, objv[2]);
2175    if (!item) {
2176	return TCL_ERROR;
2177    }
2178    if (objc >=4 && (column = FindColumn(interp,tv,objv[3])) == NULL) {
2179	return TCL_ERROR;
2180    }
2181
2182    if (BoundingBox(tv, item, column, &bbox)) {
2183	Tcl_SetObjResult(interp, Ttk_NewBoxObj(bbox));
2184    }
2185
2186    return TCL_OK;
2187}
2188
2189/* + $tv identify $x $y -- (obsolescent)
2190 * 	Implements the old, horrible, 2-argument form of [$tv identify].
2191 *
2192 * Returns: one of
2193 * 	heading #n
2194 * 	cell itemid #n
2195 * 	item itemid element
2196 * 	row itemid
2197 */
2198static int TreeviewHorribleIdentify(
2199    Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], Treeview *tv)
2200{
2201    const char *what = "nothing", *detail = NULL;
2202    TreeItem *item = 0;
2203    Tcl_Obj *result;
2204    int dColumnNumber;
2205    char dcolbuf[16];
2206    int x, y, x1;
2207
2208    /* ASSERT: objc == 4 */
2209
2210    if (   Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK
2211	|| Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK
2212    ) {
2213	return TCL_ERROR;
2214    }
2215
2216    dColumnNumber = IdentifyDisplayColumn(tv, x, &x1);
2217    if (dColumnNumber < 0) {
2218	goto done;
2219    }
2220    sprintf(dcolbuf, "#%d", dColumnNumber);
2221
2222    if (Ttk_BoxContains(tv->tree.headingArea,x,y)) {
2223	if (-HALO <= x1 - x  && x1 - x <= HALO) {
2224	    what = "separator";
2225	} else {
2226	    what = "heading";
2227	}
2228	detail = dcolbuf;
2229    } else if (Ttk_BoxContains(tv->tree.treeArea,x,y)) {
2230	item = IdentifyItem(tv, y);
2231	if (item && dColumnNumber > 0) {
2232	    what = "cell";
2233	    detail = dcolbuf;
2234	} else if (item) {
2235	    Ttk_Layout layout = tv->tree.itemLayout;
2236	    Ttk_Box itemBox;
2237	    DisplayItem displayItem;
2238	    Ttk_Element element;
2239
2240	    BoundingBox(tv, item, NULL, &itemBox);
2241	    PrepareItem(tv, item, &displayItem); /*@@@ FIX: -text, etc*/
2242	    Ttk_RebindSublayout(layout, &displayItem);
2243	    Ttk_PlaceLayout(layout, ItemState(tv,item), itemBox);
2244	    element = Ttk_IdentifyElement(layout, x, y);
2245
2246	    if (element) {
2247		what = "item";
2248		detail = Ttk_ElementName(element);
2249	    } else {
2250		what = "row";
2251	    }
2252	}
2253    }
2254
2255done:
2256    result = Tcl_NewListObj(0,0);
2257    Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(what, -1));
2258    if (item)
2259	Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item));
2260    if (detail)
2261	Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(detail, -1));
2262
2263    Tcl_SetObjResult(interp, result);
2264    return TCL_OK;
2265}
2266
2267/* + $tv identify $component $x $y --
2268 * 	Identify the component at position x,y.
2269 */
2270
2271static int TreeviewIdentifyCommand(
2272    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2273{
2274    static const char *submethodStrings[] =
2275	 { "region", "item", "column", "row", "element", NULL };
2276    enum { I_REGION, I_ITEM, I_COLUMN, I_ROW, I_ELEMENT };
2277
2278    Treeview *tv = recordPtr;
2279    int submethod;
2280    int x, y;
2281
2282    TreeRegion region;
2283    Ttk_Box bbox;
2284    TreeItem *item;
2285    TreeColumn *column = 0;
2286    int colno, x1;
2287
2288    if (objc == 4) {	/* Old form */
2289	return TreeviewHorribleIdentify(interp, objc, objv, tv);
2290    } else if (objc != 5) {
2291	Tcl_WrongNumArgs(interp, 2, objv, "command x y");
2292	return TCL_ERROR;
2293    }
2294
2295    if (   Tcl_GetIndexFromObj(interp, objv[2],
2296		submethodStrings, "command", TCL_EXACT, &submethod) != TCL_OK
2297        || Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK
2298	|| Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK
2299    ) {
2300	return TCL_ERROR;
2301    }
2302
2303    region = IdentifyRegion(tv, x, y);
2304    item = IdentifyItem(tv, y);
2305    colno = IdentifyDisplayColumn(tv, x, &x1);
2306    column = (colno >= 0) ?  tv->tree.displayColumns[colno] : NULL;
2307
2308    switch (submethod)
2309    {
2310	case I_REGION :
2311	    Tcl_SetObjResult(interp,Tcl_NewStringObj(regionStrings[region],-1));
2312	    break;
2313
2314	case I_ITEM :
2315	case I_ROW :
2316	    if (item) {
2317		Tcl_SetObjResult(interp, ItemID(tv, item));
2318	    }
2319	    break;
2320
2321	case I_COLUMN :
2322	    if (colno >= 0) {
2323		char dcolbuf[16];
2324		sprintf(dcolbuf, "#%d", colno);
2325		Tcl_SetObjResult(interp, Tcl_NewStringObj(dcolbuf, -1));
2326	    }
2327	    break;
2328
2329	case I_ELEMENT :
2330	{
2331	    Ttk_Layout layout = 0;
2332	    DisplayItem displayItem;
2333	    Ttk_Element element;
2334
2335	    switch (region) {
2336		case REGION_NOTHING:
2337		    layout = tv->core.layout;
2338		    return TCL_OK; /* @@@ NYI */
2339		case REGION_HEADING:
2340		case REGION_SEPARATOR:
2341		    layout = tv->tree.headingLayout;
2342		    return TCL_OK; /* @@@ NYI */
2343		case REGION_TREE:
2344		    layout = tv->tree.itemLayout;
2345		    break;
2346		case REGION_CELL:
2347		    layout = tv->tree.cellLayout;
2348		    break;
2349	    }
2350
2351	    if (!BoundingBox(tv, item, column, &bbox)) {
2352		return TCL_OK;
2353	    }
2354
2355	    PrepareItem(tv, item, &displayItem); /*@@@ FIX: fill in -text,etc */
2356	    Ttk_RebindSublayout(layout, &displayItem);
2357	    Ttk_PlaceLayout(layout, ItemState(tv,item), bbox);
2358	    element = Ttk_IdentifyElement(layout, x, y);
2359
2360	    if (element) {
2361		const char *elementName = Ttk_ElementName(element);
2362		Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1));
2363	    }
2364	    break;
2365	}
2366    }
2367    return TCL_OK;
2368}
2369
2370/*------------------------------------------------------------------------
2371 * +++ Widget commands -- item and column configuration.
2372 */
2373
2374/* + $tv item $item ?options ....?
2375 * 	Query or configure item options.
2376 */
2377static int TreeviewItemCommand(
2378    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2379{
2380    Treeview *tv = recordPtr;
2381    TreeItem *item;
2382
2383    if (objc < 3) {
2384	Tcl_WrongNumArgs(interp, 2, objv, "item ?option ?value??...");
2385	return TCL_ERROR;
2386    }
2387    if (!(item = FindItem(interp, tv, objv[2]))) {
2388	return TCL_ERROR;
2389    }
2390
2391    if (objc == 3) {
2392	return TtkEnumerateOptions(interp, item, ItemOptionSpecs,
2393	    tv->tree.itemOptionTable,  tv->core.tkwin);
2394    } else if (objc == 4) {
2395	return TtkGetOptionValue(interp, item, objv[3],
2396	    tv->tree.itemOptionTable, tv->core.tkwin);
2397    } else {
2398	return ConfigureItem(interp, tv, item, objc-3, objv+3);
2399    }
2400}
2401
2402/* + $tv column column ?options ....?
2403 * 	Column data accessor
2404 */
2405static int TreeviewColumnCommand(
2406    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2407{
2408    Treeview *tv = recordPtr;
2409    TreeColumn *column;
2410
2411    if (objc < 3) {
2412	Tcl_WrongNumArgs(interp, 2, objv, "column -option value...");
2413	return TCL_ERROR;
2414    }
2415    if (!(column = FindColumn(interp, tv, objv[2]))) {
2416	return TCL_ERROR;
2417    }
2418
2419    if (objc == 3) {
2420	return TtkEnumerateOptions(interp, column, ColumnOptionSpecs,
2421	    tv->tree.columnOptionTable, tv->core.tkwin);
2422    } else if (objc == 4) {
2423	return TtkGetOptionValue(interp, column, objv[3],
2424	    tv->tree.columnOptionTable, tv->core.tkwin);
2425    } else {
2426	return ConfigureColumn(interp, tv, column, objc-3, objv+3);
2427    }
2428}
2429
2430/* + $tv heading column ?options ....?
2431 * 	Heading data accessor
2432 */
2433static int TreeviewHeadingCommand(
2434    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2435{
2436    Treeview *tv = recordPtr;
2437    Tk_OptionTable optionTable = tv->tree.headingOptionTable;
2438    Tk_Window tkwin = tv->core.tkwin;
2439    TreeColumn *column;
2440
2441    if (objc < 3) {
2442	Tcl_WrongNumArgs(interp, 2, objv, "column -option value...");
2443	return TCL_ERROR;
2444    }
2445    if (!(column = FindColumn(interp, tv, objv[2]))) {
2446	return TCL_ERROR;
2447    }
2448
2449    if (objc == 3) {
2450	return TtkEnumerateOptions(
2451	    interp, column, HeadingOptionSpecs, optionTable, tkwin);
2452    } else if (objc == 4) {
2453	return TtkGetOptionValue(
2454	    interp, column, objv[3], optionTable, tkwin);
2455    } else {
2456	return ConfigureHeading(interp, tv, column, objc-3,objv+3);
2457    }
2458}
2459
2460/* + $tv set $item ?$column ?value??
2461 * 	Query or configure cell values
2462 */
2463static int TreeviewSetCommand(
2464    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2465{
2466    Treeview *tv = recordPtr;
2467    TreeItem *item;
2468    TreeColumn *column;
2469    int columnNumber;
2470
2471    if (objc < 3 || objc > 5) {
2472	Tcl_WrongNumArgs(interp, 2, objv, "item ?column ?value??");
2473	return TCL_ERROR;
2474    }
2475    if (!(item = FindItem(interp, tv, objv[2])))
2476	return TCL_ERROR;
2477
2478    /* Make sure -values exists:
2479     */
2480    if (!item->valuesObj) {
2481	item->valuesObj = Tcl_NewListObj(0,0);
2482	Tcl_IncrRefCount(item->valuesObj);
2483    }
2484
2485    if (objc == 3) {
2486	/* Return dictionary:
2487	 */
2488	Tcl_Obj *result = Tcl_NewListObj(0,0);
2489	Tcl_Obj *value;
2490	for (columnNumber=0; columnNumber<tv->tree.nColumns; ++columnNumber) {
2491	    Tcl_ListObjIndex(interp, item->valuesObj, columnNumber, &value);
2492	    if (value) {
2493		Tcl_ListObjAppendElement(interp, result,
2494			tv->tree.columns[columnNumber].idObj);
2495		Tcl_ListObjAppendElement(interp, result, value);
2496	    }
2497	}
2498	Tcl_SetObjResult(interp, result);
2499	return TCL_OK;
2500    }
2501
2502    /* else -- get or set column
2503     */
2504    if (!(column = FindColumn(interp, tv, objv[3])))
2505	return TCL_ERROR;
2506
2507    if (column == &tv->tree.column0) {
2508	/* @@@ Maybe set -text here instead? */
2509	Tcl_AppendResult(interp, "Display column #0 cannot be set", NULL);
2510	return TCL_ERROR;
2511    }
2512
2513    /* Note: we don't do any error checking in the list operations,
2514     * since item->valuesObj is guaranteed to be a list.
2515     */
2516    columnNumber = column - tv->tree.columns;
2517
2518    if (objc == 4) {	/* get column */
2519	Tcl_Obj *result = 0;
2520	Tcl_ListObjIndex(interp, item->valuesObj, columnNumber, &result);
2521	if (!result) {
2522	    result = Tcl_NewStringObj("",0);
2523	}
2524	Tcl_SetObjResult(interp, result);
2525	return TCL_OK;
2526    } else {		/* set column */
2527	int length;
2528
2529	item->valuesObj = unshare(item->valuesObj);
2530
2531	/* Make sure -values is fully populated:
2532	 */
2533	Tcl_ListObjLength(interp, item->valuesObj, &length);
2534	while (length < tv->tree.nColumns) {
2535	    Tcl_Obj *empty = Tcl_NewStringObj("",0);
2536	    Tcl_ListObjAppendElement(interp, item->valuesObj, empty);
2537	    ++length;
2538	}
2539
2540	/* Set value:
2541	 */
2542	Tcl_ListObjReplace(interp,item->valuesObj,columnNumber,1,1,objv+4);
2543	TtkRedisplayWidget(&tv->core);
2544	return TCL_OK;
2545    }
2546}
2547
2548/*------------------------------------------------------------------------
2549 * +++ Widget commands -- tree modification.
2550 */
2551
2552/* + $tv insert $parent $index ?-id id? ?-option value ...?
2553 * 	Insert a new item.
2554 */
2555static int TreeviewInsertCommand(
2556    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2557{
2558    Treeview *tv = recordPtr;
2559    TreeItem *parent, *sibling, *newItem;
2560    Tcl_HashEntry *entryPtr;
2561    int isNew;
2562
2563    if (objc < 4) {
2564	Tcl_WrongNumArgs(interp, 2, objv, "parent index ?-id id? -options...");
2565	return TCL_ERROR;
2566    }
2567
2568    /* Get parent node:
2569     */
2570    if ((parent = FindItem(interp, tv, objv[2])) == NULL) {
2571	return TCL_ERROR;
2572    }
2573
2574    /* Locate previous sibling based on $index:
2575     */
2576    if (!strcmp(Tcl_GetString(objv[3]), "end")) {
2577	sibling = EndPosition(tv, parent);
2578    } else {
2579	int index;
2580	if (Tcl_GetIntFromObj(interp, objv[3], &index) != TCL_OK)
2581	    return TCL_ERROR;
2582	sibling = InsertPosition(parent, index);
2583    }
2584
2585    /* Get node name:
2586     *     If -id supplied and does not already exist, use that;
2587     *     Otherwise autogenerate new one.
2588     */
2589    objc -= 4; objv += 4;
2590    if (objc >= 2 && !strcmp("-id", Tcl_GetString(objv[0]))) {
2591	const char *itemName = Tcl_GetString(objv[1]);
2592	entryPtr = Tcl_CreateHashEntry(&tv->tree.items, itemName, &isNew);
2593	if (!isNew) {
2594	    Tcl_AppendResult(interp, "Item ",itemName," already exists",NULL);
2595	    return TCL_ERROR;
2596	}
2597	objc -= 2; objv += 2;
2598    } else {
2599	char idbuf[16];
2600	do {
2601	    ++tv->tree.serial;
2602	    sprintf(idbuf, "I%03X", tv->tree.serial);
2603	    entryPtr = Tcl_CreateHashEntry(&tv->tree.items, idbuf, &isNew);
2604	} while (!isNew);
2605    }
2606
2607    /* Create and configure new item:
2608     */
2609    newItem = NewItem();
2610    Tk_InitOptions(
2611	interp, (ClientData)newItem, tv->tree.itemOptionTable, tv->core.tkwin);
2612    newItem->tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, NULL);
2613    if (ConfigureItem(interp, tv, newItem, objc, objv) != TCL_OK) {
2614    	Tcl_DeleteHashEntry(entryPtr);
2615	FreeItem(newItem);
2616	return TCL_ERROR;
2617    }
2618
2619    /* Store in hash table, link into tree:
2620     */
2621    Tcl_SetHashValue(entryPtr, newItem);
2622    newItem->entryPtr = entryPtr;
2623    InsertItem(parent, sibling, newItem);
2624    TtkRedisplayWidget(&tv->core);
2625
2626    Tcl_SetObjResult(interp, ItemID(tv, newItem));
2627    return TCL_OK;
2628}
2629
2630/* + $tv detach $item --
2631 * 	Unlink $item from the tree.
2632 */
2633static int TreeviewDetachCommand(
2634    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2635{
2636    Treeview *tv = recordPtr;
2637    TreeItem **items;
2638    int i;
2639
2640    if (objc != 3) {
2641	Tcl_WrongNumArgs(interp, 2, objv, "item");
2642	return TCL_ERROR;
2643    }
2644    if (!(items = GetItemListFromObj(interp, tv, objv[2]))) {
2645	return TCL_ERROR;
2646    }
2647
2648    /* Sanity-check */
2649    for (i = 0; items[i]; ++i) {
2650	if (items[i] == tv->tree.root) {
2651	    Tcl_AppendResult(interp, "Cannot detach root item", NULL);
2652	    ckfree((ClientData)items);
2653	    return TCL_ERROR;
2654	}
2655    }
2656
2657    for (i = 0; items[i]; ++i) {
2658	DetachItem(items[i]);
2659    }
2660
2661    TtkRedisplayWidget(&tv->core);
2662    ckfree((ClientData)items);
2663    return TCL_OK;
2664}
2665
2666/* + $tv delete $items --
2667 * 	Delete each item in $items.
2668 *
2669 * 	Do this in two passes:
2670 * 	First detach the item and all its descendants and remove them
2671 * 	from the hash table.  Free the items themselves in a second pass.
2672 *
2673 * 	It's done this way because an item may appear more than once
2674 *	in the list of items to delete (either directly or as a descendant
2675 *	of a previously deleted item.)
2676 */
2677
2678static int TreeviewDeleteCommand(
2679    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2680{
2681    Treeview *tv = recordPtr;
2682    TreeItem **items, *delq;
2683    int i;
2684
2685    if (objc != 3) {
2686	Tcl_WrongNumArgs(interp, 2, objv, "items");
2687	return TCL_ERROR;
2688    }
2689
2690    if (!(items = GetItemListFromObj(interp, tv, objv[2]))) {
2691	return TCL_ERROR;
2692    }
2693
2694    /* Sanity-check:
2695     */
2696    for (i=0; items[i]; ++i) {
2697	if (items[i] == tv->tree.root) {
2698	    ckfree((ClientData)items);
2699	    Tcl_AppendResult(interp, "Cannot delete root item", NULL);
2700	    return TCL_ERROR;
2701	}
2702    }
2703
2704    /* Remove items from hash table.
2705     */
2706    delq = 0;
2707    for (i=0; items[i]; ++i) {
2708	delq = DeleteItems(items[i], delq);
2709    }
2710
2711    /* Free items:
2712     */
2713    while (delq) {
2714	TreeItem *next = delq->next;
2715	if (tv->tree.focus == delq)
2716	    tv->tree.focus = 0;
2717	if (tv->tree.endPtr == delq)
2718	    tv->tree.endPtr = 0;
2719	FreeItem(delq);
2720	delq = next;
2721    }
2722
2723    ckfree((ClientData)items);
2724    TtkRedisplayWidget(&tv->core);
2725    return TCL_OK;
2726}
2727
2728/* + $tv move $item $parent $index
2729 * 	Move $item to the specified $index in $parent's child list.
2730 */
2731static int TreeviewMoveCommand(
2732    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2733{
2734    Treeview *tv = recordPtr;
2735    TreeItem *item, *parent;
2736    TreeItem *sibling;
2737
2738    if (objc != 5) {
2739	Tcl_WrongNumArgs(interp, 2, objv, "item parent index");
2740	return TCL_ERROR;
2741    }
2742    if (   (item = FindItem(interp, tv, objv[2])) == 0
2743	|| (parent = FindItem(interp, tv, objv[3])) == 0)
2744    {
2745	return TCL_ERROR;
2746    }
2747
2748    /* Locate previous sibling based on $index:
2749     */
2750    if (!strcmp(Tcl_GetString(objv[4]), "end")) {
2751	sibling = EndPosition(tv, parent);
2752    } else {
2753	TreeItem *p;
2754	int index;
2755
2756	if (Tcl_GetIntFromObj(interp, objv[4], &index) != TCL_OK) {
2757	    return TCL_ERROR;
2758	}
2759
2760	sibling = 0;
2761	for (p = parent->children; p != NULL && index > 0; p = p->next) {
2762	    if (p != item) {
2763		--index;
2764	    } /* else -- moving node forward, count index+1 nodes  */
2765	    sibling = p;
2766	}
2767    }
2768
2769    /* Check ancestry:
2770     */
2771    if (!AncestryCheck(interp, tv, item, parent)) {
2772	return TCL_ERROR;
2773    }
2774
2775    /* Moving an item after itself is a no-op:
2776     */
2777    if (item == sibling) {
2778	return TCL_OK;
2779    }
2780
2781    /* Move item:
2782     */
2783    DetachItem(item);
2784    InsertItem(parent, sibling, item);
2785
2786    TtkRedisplayWidget(&tv->core);
2787    return TCL_OK;
2788}
2789
2790/*------------------------------------------------------------------------
2791 * +++ Widget commands -- scrolling
2792 */
2793
2794static int TreeviewXViewCommand(
2795    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2796{
2797    Treeview *tv = recordPtr;
2798    return TtkScrollviewCommand(interp, objc, objv, tv->tree.xscrollHandle);
2799}
2800
2801static int TreeviewYViewCommand(
2802    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2803{
2804    Treeview *tv = recordPtr;
2805    return TtkScrollviewCommand(interp, objc, objv, tv->tree.yscrollHandle);
2806}
2807
2808/* $tree see $item --
2809 * 	Ensure that $item is visible.
2810 */
2811static int TreeviewSeeCommand(
2812    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2813{
2814    Treeview *tv = recordPtr;
2815    TreeItem *item, *parent;
2816    int rowNumber;
2817
2818    if (objc != 3) {
2819	Tcl_WrongNumArgs(interp, 2, objv, "item");
2820	return TCL_ERROR;
2821    }
2822    if (!(item = FindItem(interp, tv, objv[2]))) {
2823	return TCL_ERROR;
2824    }
2825
2826    /* Make sure all ancestors are open:
2827     */
2828    for (parent = item->parent; parent; parent = parent->parent) {
2829	if (!(parent->state & TTK_STATE_OPEN)) {
2830	    parent->openObj = unshare(parent->openObj);
2831	    Tcl_SetBooleanObj(parent->openObj, 1);
2832	    parent->state |= TTK_STATE_OPEN;
2833	    TtkRedisplayWidget(&tv->core);
2834	}
2835    }
2836
2837    /* Make sure item is visible:
2838     * @@@ DOUBLE-CHECK THIS:
2839     */
2840    rowNumber = RowNumber(tv, item);
2841    if (rowNumber < tv->tree.yscroll.first) {
2842	TtkScrollTo(tv->tree.yscrollHandle, rowNumber);
2843    } else if (rowNumber >= tv->tree.yscroll.last) {
2844	TtkScrollTo(tv->tree.yscrollHandle,
2845	    tv->tree.yscroll.first + (1+rowNumber - tv->tree.yscroll.last));
2846    }
2847
2848    return TCL_OK;
2849}
2850
2851/*------------------------------------------------------------------------
2852 * +++ Widget commands -- interactive column resize
2853 */
2854
2855/* + $tree drag $column $newX --
2856 * 	Set right edge of display column $column to x position $X
2857 */
2858static int TreeviewDragCommand(
2859    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2860{
2861    Treeview *tv = recordPtr;
2862    int left = tv->tree.treeArea.x - tv->tree.xscroll.first;
2863    int i = FirstColumn(tv);
2864    TreeColumn *column;
2865    int newx;
2866
2867    if (objc != 4) {
2868	Tcl_WrongNumArgs(interp, 2, objv, "column xposition");
2869	return TCL_ERROR;
2870    }
2871
2872    if (  (column = FindColumn(interp, tv, objv[2])) == 0
2873        || Tcl_GetIntFromObj(interp, objv[3], &newx) != TCL_OK)
2874    {
2875	return TCL_ERROR;
2876    }
2877
2878    for (;i < tv->tree.nDisplayColumns; ++i) {
2879	TreeColumn *c = tv->tree.displayColumns[i];
2880	int right = left + c->width;
2881	if (c == column) {
2882	    DragColumn(tv, i, newx - right);
2883	    /* ASSERT: SLACKINVARIANT */
2884	    TtkRedisplayWidget(&tv->core);
2885	    return TCL_OK;
2886	}
2887	left = right;
2888    }
2889
2890    Tcl_ResetResult(interp);
2891    Tcl_AppendResult(interp,
2892	"column ", Tcl_GetString(objv[2]), " is not displayed",
2893	NULL);
2894    return TCL_ERROR;
2895}
2896
2897/*------------------------------------------------------------------------
2898 * +++ Widget commands -- focus and selection
2899 */
2900
2901/* + $tree focus ?item?
2902 */
2903static int TreeviewFocusCommand(
2904    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2905{
2906    Treeview *tv = recordPtr;
2907
2908    if (objc == 2) {
2909	if (tv->tree.focus) {
2910	    Tcl_SetObjResult(interp, ItemID(tv, tv->tree.focus));
2911	}
2912	return TCL_OK;
2913    } else if (objc == 3) {
2914	TreeItem *newFocus = FindItem(interp, tv, objv[2]);
2915	if (!newFocus)
2916	    return TCL_ERROR;
2917	tv->tree.focus = newFocus;
2918	TtkRedisplayWidget(&tv->core);
2919	return TCL_OK;
2920    } else {
2921	Tcl_WrongNumArgs(interp, 2, objv, "?newFocus?");
2922	return TCL_ERROR;
2923    }
2924}
2925
2926/* + $tree selection ?add|remove|set|toggle $items?
2927 */
2928static int TreeviewSelectionCommand(
2929    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
2930{
2931    enum {
2932	SELECTION_SET, SELECTION_ADD, SELECTION_REMOVE, SELECTION_TOGGLE
2933    };
2934    static const char *selopStrings[] = {
2935	"set", "add", "remove", "toggle", NULL
2936    };
2937
2938    Treeview *tv = recordPtr;
2939    int selop, i;
2940    TreeItem *item, **items;
2941
2942    if (objc == 2) {
2943	Tcl_Obj *result = Tcl_NewListObj(0,0);
2944	for (item = tv->tree.root->children; item; item=NextPreorder(item)) {
2945	    if (item->state & TTK_STATE_SELECTED)
2946		Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item));
2947	}
2948	Tcl_SetObjResult(interp, result);
2949	return TCL_OK;
2950    }
2951
2952    if (objc != 4) {
2953    	Tcl_WrongNumArgs(interp, 2, objv, "?add|remove|set|toggle items?");
2954	return TCL_ERROR;
2955    }
2956
2957    if (Tcl_GetIndexFromObj(interp, objv[2], selopStrings,
2958	    "selection operation", 0, &selop) != TCL_OK)
2959    {
2960	return TCL_ERROR;
2961    }
2962
2963    items = GetItemListFromObj(interp, tv, objv[3]);
2964    if (!items) {
2965	return TCL_ERROR;
2966    }
2967
2968    switch (selop)
2969    {
2970	case SELECTION_SET:
2971	    for (item=tv->tree.root; item; item=NextPreorder(item)) {
2972		item->state &= ~TTK_STATE_SELECTED;
2973	    }
2974	    /*FALLTHRU*/
2975	case SELECTION_ADD:
2976	    for (i=0; items[i]; ++i) {
2977		items[i]->state |= TTK_STATE_SELECTED;
2978	    }
2979	    break;
2980	case SELECTION_REMOVE:
2981	    for (i=0; items[i]; ++i) {
2982		items[i]->state &= ~TTK_STATE_SELECTED;
2983	    }
2984	    break;
2985	case SELECTION_TOGGLE:
2986	    for (i=0; items[i]; ++i) {
2987		items[i]->state ^= TTK_STATE_SELECTED;
2988	    }
2989	    break;
2990    }
2991
2992    ckfree((ClientData)items);
2993    TtkSendVirtualEvent(tv->core.tkwin, "TreeviewSelect");
2994    TtkRedisplayWidget(&tv->core);
2995
2996    return TCL_OK;
2997}
2998
2999/*------------------------------------------------------------------------
3000 * +++ Widget commands -- tags and bindings.
3001 */
3002
3003/* + $tv tag bind $tag ?$sequence ?$script??
3004 */
3005static int TreeviewTagBindCommand(
3006    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
3007{
3008    Treeview *tv = recordPtr;
3009    Ttk_TagTable tagTable = tv->tree.tagTable;
3010    Tk_BindingTable bindingTable = tv->tree.bindingTable;
3011    Ttk_Tag tag;
3012
3013    if (objc < 4 || objc > 6) {
3014    	Tcl_WrongNumArgs(interp, 3, objv, "tagName ?sequence? ?script?");
3015	return TCL_ERROR;
3016    }
3017
3018    tag = Ttk_GetTagFromObj(tagTable, objv[3]);
3019    if (!tag) { return TCL_ERROR; }
3020
3021    if (objc == 4) {		/* $tv tag bind $tag */
3022	Tk_GetAllBindings(interp, bindingTable, tag);
3023    } else if (objc == 5) { 	/* $tv tag bind $tag $sequence */
3024	/* TODO: distinguish "no such binding" (OK) from "bad pattern" (ERROR)
3025	 */
3026	const char *script = Tk_GetBinding(interp,
3027		bindingTable, tag, Tcl_GetString(objv[4]));
3028	if (script != NULL) {
3029	    Tcl_SetObjResult(interp, Tcl_NewStringObj(script,-1));
3030	}
3031    } else if (objc == 6) {	/* $tv tag bind $tag $sequence $script */
3032	const char *sequence = Tcl_GetString(objv[4]);
3033	const char *script = Tcl_GetString(objv[5]);
3034
3035	if (!*script) { /* Delete existing binding */
3036	    Tk_DeleteBinding(interp, bindingTable, tag, sequence);
3037	} else {
3038	    unsigned long mask = Tk_CreateBinding(interp,
3039		    bindingTable, tag, sequence, script, 0);
3040
3041	    /* Test mask to make sure event is supported:
3042	     */
3043	    if (mask & (~TreeviewBindEventMask)) {
3044		Tk_DeleteBinding(interp, bindingTable, tag, sequence);
3045		Tcl_ResetResult(interp);
3046		Tcl_AppendResult(interp, "unsupported event ", sequence,
3047		    "\nonly key, button, motion, and virtual events supported",
3048		    NULL);
3049		return TCL_ERROR;
3050	    }
3051	}
3052    }
3053    return TCL_OK;
3054}
3055
3056/* + $tv tag configure $tag ?-option ?value -option value...??
3057 */
3058static int TreeviewTagConfigureCommand(
3059    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
3060{
3061    Treeview *tv = recordPtr;
3062    Ttk_TagTable tagTable = tv->tree.tagTable;
3063    Ttk_Tag tag;
3064
3065    if (objc < 4) {
3066    	Tcl_WrongNumArgs(interp, 3, objv, "tagName ?-option ?value ...??");
3067	return TCL_ERROR;
3068    }
3069
3070    tag = Ttk_GetTagFromObj(tagTable, objv[3]);
3071
3072    if (objc == 4) {
3073	return Ttk_EnumerateTagOptions(interp, tagTable, tag);
3074    } else if (objc == 5) {
3075	Tcl_Obj *result = Ttk_TagOptionValue(interp, tagTable, tag, objv[4]);
3076	if (result) {
3077	    Tcl_SetObjResult(interp, result);
3078	    return TCL_OK;
3079	} /* else */
3080	return TCL_ERROR;
3081    }
3082    /* else */
3083    TtkRedisplayWidget(&tv->core);
3084    return Ttk_ConfigureTag(interp, tagTable, tag, objc - 4, objv + 4);
3085}
3086
3087/* + $tv tag has $tag ?$item?
3088 */
3089static int TreeviewTagHasCommand(
3090    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
3091{
3092    Treeview *tv = recordPtr;
3093
3094    if (objc == 4) {	/* Return list of all items with tag */
3095	Ttk_Tag tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]);
3096	TreeItem *item = tv->tree.root;
3097	Tcl_Obj *result = Tcl_NewListObj(0,0);
3098
3099	while (item) {
3100	    if (Ttk_TagSetContains(item->tagset, tag)) {
3101		Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item));
3102	    }
3103	    item = NextPreorder(item);
3104	}
3105
3106	Tcl_SetObjResult(interp, result);
3107	return TCL_OK;
3108    } else if (objc == 5) {	/* Test if item has specified tag */
3109	Ttk_Tag tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]);
3110	TreeItem *item = FindItem(interp, tv, objv[4]);
3111	if (!item) {
3112	    return TCL_ERROR;
3113	}
3114	Tcl_SetObjResult(interp,
3115	    Tcl_NewBooleanObj(Ttk_TagSetContains(item->tagset, tag)));
3116	return TCL_OK;
3117    } else {
3118    	Tcl_WrongNumArgs(interp, 3, objv, "tagName ?item?");
3119	return TCL_ERROR;
3120    }
3121}
3122
3123/* + $tv tag names $tag
3124 */
3125static int TreeviewTagNamesCommand(
3126    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
3127{
3128    Treeview *tv = recordPtr;
3129
3130    if (objc != 3) {
3131	Tcl_WrongNumArgs(interp, 3, objv, "");
3132	return TCL_ERROR;
3133    }
3134
3135    return Ttk_EnumerateTags(interp, tv->tree.tagTable);
3136}
3137
3138/* + $tv tag add $tag $items
3139 */
3140static void AddTag(TreeItem *item, Ttk_Tag tag)
3141{
3142    if (Ttk_TagSetAdd(item->tagset, tag)) {
3143	Tcl_DecrRefCount(item->tagsObj);
3144	item->tagsObj = Ttk_NewTagSetObj(item->tagset);
3145	Tcl_IncrRefCount(item->tagsObj);
3146    }
3147}
3148
3149static int TreeviewTagAddCommand(
3150    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
3151{
3152    Treeview *tv = recordPtr;
3153    Ttk_Tag tag;
3154    TreeItem **items;
3155    int i;
3156
3157    if (objc != 5) {
3158	Tcl_WrongNumArgs(interp, 3, objv, "tagName items");
3159	return TCL_ERROR;
3160    }
3161
3162    tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]);
3163    items = GetItemListFromObj(interp, tv, objv[4]);
3164
3165    if (!items) {
3166	return TCL_ERROR;
3167    }
3168
3169    for (i=0; items[i]; ++i) {
3170	AddTag(items[i], tag);
3171    }
3172
3173    return TCL_OK;
3174}
3175
3176/* + $tv tag remove $tag $items
3177 */
3178static void RemoveTag(TreeItem *item, Ttk_Tag tag)
3179{
3180    if (Ttk_TagSetRemove(item->tagset, tag)) {
3181	Tcl_DecrRefCount(item->tagsObj);
3182	item->tagsObj = Ttk_NewTagSetObj(item->tagset);
3183	Tcl_IncrRefCount(item->tagsObj);
3184    }
3185}
3186
3187static int TreeviewTagRemoveCommand(
3188    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
3189{
3190    Treeview *tv = recordPtr;
3191    Ttk_Tag tag;
3192
3193    if (objc < 4) {
3194	Tcl_WrongNumArgs(interp, 3, objv, "tagName items");
3195	return TCL_ERROR;
3196    }
3197
3198    tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]);
3199
3200    if (objc == 5) {
3201	TreeItem **items = GetItemListFromObj(interp, tv, objv[4]);
3202	int i;
3203
3204	if (!items) {
3205	    return TCL_ERROR;
3206	}
3207	for (i=0; items[i]; ++i) {
3208	    RemoveTag(items[i], tag);
3209	}
3210    } else if (objc == 4) {
3211	TreeItem *item = tv->tree.root;
3212	while (item) {
3213	    RemoveTag(item, tag);
3214	    item=NextPreorder(item);
3215	}
3216    }
3217    return TCL_OK;
3218}
3219
3220static const Ttk_Ensemble TreeviewTagCommands[] = {
3221    { "add",		TreeviewTagAddCommand,0 },
3222    { "bind",		TreeviewTagBindCommand,0 },
3223    { "configure",	TreeviewTagConfigureCommand,0 },
3224    { "has",		TreeviewTagHasCommand,0 },
3225    { "names",		TreeviewTagNamesCommand,0 },
3226    { "remove",		TreeviewTagRemoveCommand,0 },
3227    { 0,0,0 }
3228};
3229
3230/*------------------------------------------------------------------------
3231 * +++ Widget commands record.
3232 */
3233static const Ttk_Ensemble TreeviewCommands[] = {
3234    { "bbox",  		TreeviewBBoxCommand,0 },
3235    { "children",	TreeviewChildrenCommand,0 },
3236    { "cget",		TtkWidgetCgetCommand,0 },
3237    { "column", 	TreeviewColumnCommand,0 },
3238    { "configure",	TtkWidgetConfigureCommand,0 },
3239    { "delete", 	TreeviewDeleteCommand,0 },
3240    { "detach", 	TreeviewDetachCommand,0 },
3241    { "drag",   	TreeviewDragCommand,0 },
3242    { "exists", 	TreeviewExistsCommand,0 },
3243    { "focus", 		TreeviewFocusCommand,0 },
3244    { "heading", 	TreeviewHeadingCommand,0 },
3245    { "identify",  	TreeviewIdentifyCommand,0 },
3246    { "index",  	TreeviewIndexCommand,0 },
3247    { "instate",	TtkWidgetInstateCommand,0 },
3248    { "insert", 	TreeviewInsertCommand,0 },
3249    { "item", 		TreeviewItemCommand,0 },
3250    { "move", 		TreeviewMoveCommand,0 },
3251    { "next", 		TreeviewNextCommand,0 },
3252    { "parent", 	TreeviewParentCommand,0 },
3253    { "prev", 		TreeviewPrevCommand,0 },
3254    { "see", 		TreeviewSeeCommand,0 },
3255    { "selection" ,	TreeviewSelectionCommand,0 },
3256    { "set",  		TreeviewSetCommand,0 },
3257    { "state",  	TtkWidgetStateCommand,0 },
3258    { "tag",    	0,TreeviewTagCommands },
3259    { "xview",  	TreeviewXViewCommand,0 },
3260    { "yview",  	TreeviewYViewCommand,0 },
3261    { 0,0,0 }
3262};
3263
3264/*------------------------------------------------------------------------
3265 * +++ Widget definition.
3266 */
3267
3268static WidgetSpec TreeviewWidgetSpec = {
3269    "Treeview",			/* className */
3270    sizeof(Treeview),   	/* recordSize */
3271    TreeviewOptionSpecs,	/* optionSpecs */
3272    TreeviewCommands,   	/* subcommands */
3273    TreeviewInitialize,   	/* initializeProc */
3274    TreeviewCleanup,		/* cleanupProc */
3275    TreeviewConfigure,    	/* configureProc */
3276    TtkNullPostConfigure,  	/* postConfigureProc */
3277    TreeviewGetLayout, 		/* getLayoutProc */
3278    TreeviewSize, 		/* sizeProc */
3279    TreeviewDoLayout,		/* layoutProc */
3280    TreeviewDisplay		/* displayProc */
3281};
3282
3283/*------------------------------------------------------------------------
3284 * +++ Layout specifications.
3285 */
3286
3287TTK_BEGIN_LAYOUT_TABLE(LayoutTable)
3288
3289TTK_LAYOUT("Treeview",
3290    TTK_GROUP("Treeview.field", TTK_FILL_BOTH|TTK_BORDER,
3291	TTK_GROUP("Treeview.padding", TTK_FILL_BOTH,
3292	    TTK_NODE("Treeview.treearea", TTK_FILL_BOTH))))
3293
3294TTK_LAYOUT("Item",
3295    TTK_GROUP("Treeitem.padding", TTK_FILL_BOTH,
3296	TTK_NODE("Treeitem.indicator", TTK_PACK_LEFT)
3297	TTK_NODE("Treeitem.image", TTK_PACK_LEFT)
3298	TTK_GROUP("Treeitem.focus", TTK_PACK_LEFT,
3299	    TTK_NODE("Treeitem.text", TTK_PACK_LEFT))))
3300
3301TTK_LAYOUT("Cell",
3302    TTK_GROUP("Treedata.padding", TTK_FILL_BOTH,
3303	TTK_NODE("Treeitem.text", TTK_FILL_BOTH)))
3304
3305TTK_LAYOUT("Heading",
3306    TTK_NODE("Treeheading.cell", TTK_FILL_BOTH)
3307    TTK_GROUP("Treeheading.border", TTK_FILL_BOTH,
3308	TTK_GROUP("Treeheading.padding", TTK_FILL_BOTH,
3309	    TTK_NODE("Treeheading.image", TTK_PACK_RIGHT)
3310	    TTK_NODE("Treeheading.text", TTK_FILL_X))))
3311
3312TTK_LAYOUT("Row",
3313    TTK_NODE("Treeitem.row", TTK_FILL_BOTH))
3314
3315TTK_END_LAYOUT_TABLE
3316
3317/*------------------------------------------------------------------------
3318 * +++ Tree indicator element.
3319 */
3320
3321typedef struct {
3322    Tcl_Obj *colorObj;
3323    Tcl_Obj *sizeObj;
3324    Tcl_Obj *marginsObj;
3325} TreeitemIndicator;
3326
3327static Ttk_ElementOptionSpec TreeitemIndicatorOptions[] = {
3328    { "-foreground", TK_OPTION_COLOR,
3329	Tk_Offset(TreeitemIndicator,colorObj), DEFAULT_FOREGROUND },
3330    { "-indicatorsize", TK_OPTION_PIXELS,
3331	Tk_Offset(TreeitemIndicator,sizeObj), "12" },
3332    { "-indicatormargins", TK_OPTION_STRING,
3333	Tk_Offset(TreeitemIndicator,marginsObj), "2 2 4 2" },
3334    { NULL, 0, 0, NULL }
3335};
3336
3337static void TreeitemIndicatorSize(
3338    void *clientData, void *elementRecord, Tk_Window tkwin,
3339    int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
3340{
3341    TreeitemIndicator *indicator = elementRecord;
3342    Ttk_Padding margins;
3343    int size = 0;
3344
3345    Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginsObj, &margins);
3346    Tk_GetPixelsFromObj(NULL, tkwin, indicator->sizeObj, &size);
3347
3348    *widthPtr = size + Ttk_PaddingWidth(margins);
3349    *heightPtr = size + Ttk_PaddingHeight(margins);
3350}
3351
3352static void TreeitemIndicatorDraw(
3353    void *clientData, void *elementRecord, Tk_Window tkwin,
3354    Drawable d, Ttk_Box b, Ttk_State state)
3355{
3356    TreeitemIndicator *indicator = elementRecord;
3357    ArrowDirection direction =
3358	(state & TTK_STATE_OPEN) ? ARROW_DOWN : ARROW_RIGHT;
3359    Ttk_Padding margins;
3360    XColor *borderColor = Tk_GetColorFromObj(tkwin, indicator->colorObj);
3361    XGCValues gcvalues; GC gc; unsigned mask;
3362
3363    if (state & TTK_STATE_LEAF) /* don't draw anything */
3364	return;
3365
3366    Ttk_GetPaddingFromObj(NULL,tkwin,indicator->marginsObj,&margins);
3367    b = Ttk_PadBox(b, margins);
3368
3369    gcvalues.foreground = borderColor->pixel;
3370    gcvalues.line_width = 1;
3371    mask = GCForeground | GCLineWidth;
3372    gc = Tk_GetGC(tkwin, mask, &gcvalues);
3373
3374    TtkDrawArrow(Tk_Display(tkwin), d, gc, b, direction);
3375
3376    Tk_FreeGC(Tk_Display(tkwin), gc);
3377}
3378
3379static Ttk_ElementSpec TreeitemIndicatorElementSpec = {
3380    TK_STYLE_VERSION_2,
3381    sizeof(TreeitemIndicator),
3382    TreeitemIndicatorOptions,
3383    TreeitemIndicatorSize,
3384    TreeitemIndicatorDraw
3385};
3386
3387/*------------------------------------------------------------------------
3388 * +++ Row element.
3389 */
3390
3391typedef struct {
3392    Tcl_Obj *backgroundObj;
3393    Tcl_Obj *rowNumberObj;
3394} RowElement;
3395
3396static Ttk_ElementOptionSpec RowElementOptions[] = {
3397    { "-background", TK_OPTION_COLOR,
3398	Tk_Offset(RowElement,backgroundObj), DEFAULT_BACKGROUND },
3399    { "-rownumber", TK_OPTION_INT,
3400	Tk_Offset(RowElement,rowNumberObj), "0" },
3401    { NULL, 0, 0, NULL }
3402};
3403
3404static void RowElementDraw(
3405    void *clientData, void *elementRecord, Tk_Window tkwin,
3406    Drawable d, Ttk_Box b, Ttk_State state)
3407{
3408    RowElement *row = elementRecord;
3409    XColor *color = Tk_GetColorFromObj(tkwin, row->backgroundObj);
3410    GC gc = Tk_GCForColor(color, d);
3411    XFillRectangle(Tk_Display(tkwin), d, gc,
3412	    b.x, b.y, b.width, b.height);
3413}
3414
3415static Ttk_ElementSpec RowElementSpec = {
3416    TK_STYLE_VERSION_2,
3417    sizeof(RowElement),
3418    RowElementOptions,
3419    TtkNullElementSize,
3420    RowElementDraw
3421};
3422
3423/*------------------------------------------------------------------------
3424 * +++ Initialisation.
3425 */
3426
3427MODULE_SCOPE
3428void TtkTreeview_Init(Tcl_Interp *interp)
3429{
3430    Ttk_Theme theme = Ttk_GetDefaultTheme(interp);
3431
3432    RegisterWidget(interp, "ttk::treeview", &TreeviewWidgetSpec);
3433
3434    Ttk_RegisterElement(interp, theme, "Treeitem.indicator",
3435	    &TreeitemIndicatorElementSpec, 0);
3436    Ttk_RegisterElement(interp, theme, "Treeitem.row", &RowElementSpec, 0);
3437    Ttk_RegisterElement(interp, theme, "Treeheading.cell", &RowElementSpec, 0);
3438    Ttk_RegisterElement(interp, theme, "treearea", &ttkNullElementSpec, 0);
3439
3440    Ttk_RegisterLayouts(theme, LayoutTable);
3441}
3442
3443/*EOF*/
3444