diff options
Diffstat (limited to 'generic/ttk/ttkTreeview.c')
-rw-r--r-- | generic/ttk/ttkTreeview.c | 3447 |
1 files changed, 3447 insertions, 0 deletions
diff --git a/generic/ttk/ttkTreeview.c b/generic/ttk/ttkTreeview.c new file mode 100644 index 0000000..f0a3003 --- /dev/null +++ b/generic/ttk/ttkTreeview.c @@ -0,0 +1,3447 @@ +/* + * Copyright (c) 2004, Joe English + * + * ttk::treeview widget implementation. + */ + +#include <string.h> +#include <stdio.h> +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +#define DEF_TREE_ROWS "10" +#define DEF_COLWIDTH "200" +#define DEF_MINWIDTH "20" + +static const int DEFAULT_ROWHEIGHT = 20; +static const int DEFAULT_INDENT = 20; +static const int HALO = 4; /* separator */ + +#define TTK_STATE_OPEN TTK_STATE_USER1 +#define TTK_STATE_LEAF TTK_STATE_USER2 + +#define STATE_CHANGED (0x100) /* item state option changed */ + +/*------------------------------------------------------------------------ + * +++ Tree items. + * + * INVARIANTS: + * item->children ==> item->children->parent == item + * item->next ==> item->next->parent == item->parent + * item->next ==> item->next->prev == item + * item->prev ==> item->prev->next == item + */ + +typedef struct TreeItemRec TreeItem; +struct TreeItemRec { + Tcl_HashEntry *entryPtr; /* Back-pointer to hash table entry */ + TreeItem *parent; /* Parent item */ + TreeItem *children; /* Linked list of child items */ + TreeItem *next; /* Next sibling */ + TreeItem *prev; /* Previous sibling */ + + /* + * Options and instance data: + */ + Ttk_State state; + Tcl_Obj *textObj; + Tcl_Obj *imageObj; + Tcl_Obj *valuesObj; + Tcl_Obj *openObj; + Tcl_Obj *tagsObj; + + /* + * Derived resources: + */ + Ttk_TagSet tagset; + Ttk_ImageSpec *imagespec; +}; + +#define ITEM_OPTION_TAGS_CHANGED 0x100 +#define ITEM_OPTION_IMAGE_CHANGED 0x200 + +static Tk_OptionSpec ItemOptionSpecs[] = { + {TK_OPTION_STRING, "-text", "text", "Text", + "", Tk_Offset(TreeItem,textObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-image", "image", "Image", + NULL, Tk_Offset(TreeItem,imageObj), -1, + TK_OPTION_NULL_OK,0,ITEM_OPTION_IMAGE_CHANGED }, + {TK_OPTION_STRING, "-values", "values", "Values", + NULL, Tk_Offset(TreeItem,valuesObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_BOOLEAN, "-open", "open", "Open", + "0", Tk_Offset(TreeItem,openObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-tags", "tags", "Tags", + NULL, Tk_Offset(TreeItem,tagsObj), -1, + TK_OPTION_NULL_OK,0,ITEM_OPTION_TAGS_CHANGED }, + + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +/* + NewItem -- + * Allocate a new, uninitialized, unlinked item + */ +static TreeItem *NewItem(void) +{ + TreeItem *item = (TreeItem*)ckalloc(sizeof(*item)); + + item->entryPtr = 0; + item->parent = item->children = item->next = item->prev = NULL; + + item->state = 0ul; + item->textObj = NULL; + item->imageObj = NULL; + item->valuesObj = NULL; + item->openObj = NULL; + item->tagsObj = NULL; + + item->tagset = NULL; + item->imagespec = NULL; + + return item; +} + +/* + FreeItem -- + * Destroy an item + */ +static void FreeItem(TreeItem *item) +{ + if (item->textObj) { Tcl_DecrRefCount(item->textObj); } + if (item->imageObj) { Tcl_DecrRefCount(item->imageObj); } + if (item->valuesObj) { Tcl_DecrRefCount(item->valuesObj); } + if (item->openObj) { Tcl_DecrRefCount(item->openObj); } + if (item->tagsObj) { Tcl_DecrRefCount(item->tagsObj); } + + if (item->tagset) { Ttk_FreeTagSet(item->tagset); } + if (item->imagespec) { TtkFreeImageSpec(item->imagespec); } + + ckfree((ClientData)item); +} + +static void FreeItemCB(void *clientData) { FreeItem(clientData); } + +/* + DetachItem -- + * Unlink an item from the tree. + */ +static void DetachItem(TreeItem *item) +{ + if (item->parent && item->parent->children == item) + item->parent->children = item->next; + if (item->prev) + item->prev->next = item->next; + if (item->next) + item->next->prev = item->prev; + item->next = item->prev = item->parent = NULL; +} + +/* + InsertItem -- + * Insert an item into the tree after the specified item. + * + * Preconditions: + * + item is currently detached + * + prev != NULL ==> prev->parent == parent. + */ +static void InsertItem(TreeItem *parent, TreeItem *prev, TreeItem *item) +{ + item->parent = parent; + item->prev = prev; + if (prev) { + item->next = prev->next; + prev->next = item; + } else { + item->next = parent->children; + parent->children = item; + } + if (item->next) { + item->next->prev = item; + } +} + +/* + NextPreorder -- + * Return the next item in preorder traversal order. + */ + +static TreeItem *NextPreorder(TreeItem *item) +{ + if (item->children) + return item->children; + while (!item->next) { + item = item->parent; + if (!item) + return 0; + } + return item->next; +} + +/*------------------------------------------------------------------------ + * +++ Display items and tag options. + */ + +typedef struct { + Tcl_Obj *textObj; /* taken from item / data cell */ + Tcl_Obj *imageObj; /* taken from item */ + Tcl_Obj *anchorObj; /* from column <<NOTE-ANCHOR>> */ + Tcl_Obj *backgroundObj; /* remainder from tag */ + Tcl_Obj *foregroundObj; + Tcl_Obj *fontObj; +} DisplayItem; + +static Tk_OptionSpec TagOptionSpecs[] = { + {TK_OPTION_STRING, "-text", "text", "Text", + NULL, Tk_Offset(DisplayItem,textObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_STRING, "-image", "image", "Image", + NULL, Tk_Offset(DisplayItem,imageObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", + NULL, Tk_Offset(DisplayItem,anchorObj), -1, + TK_OPTION_NULL_OK, 0, GEOMETRY_CHANGED}, /* <<NOTE-ANCHOR>> */ + {TK_OPTION_COLOR, "-background", "windowColor", "WindowColor", + NULL, Tk_Offset(DisplayItem,backgroundObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_COLOR, "-foreground", "textColor", "TextColor", + NULL, Tk_Offset(DisplayItem,foregroundObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_FONT, "-font", "font", "Font", + NULL, Tk_Offset(DisplayItem,fontObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +/*------------------------------------------------------------------------ + * +++ Columns. + * + * There are separate option tables associated with the column record: + * ColumnOptionSpecs is for configuring the column, + * and HeadingOptionSpecs is for drawing headings. + */ +typedef struct { + int width; /* Column width, in pixels */ + int minWidth; /* Minimum column width, in pixels */ + int stretch; /* Should column stretch while resizing? */ + Tcl_Obj *idObj; /* Column identifier, from -columns option */ + + Tcl_Obj *anchorObj; /* -anchor for cell data <<NOTE-ANCHOR>> */ + + /* Column heading data: + */ + Tcl_Obj *headingObj; /* Heading label */ + Tcl_Obj *headingImageObj; /* Heading image */ + Tcl_Obj *headingAnchorObj; /* -anchor for heading label */ + Tcl_Obj *headingCommandObj; /* Command to execute */ + Tcl_Obj *headingStateObj; /* @@@ testing ... */ + Ttk_State headingState; /* ... */ + + /* Temporary storage for cell data + */ + Tcl_Obj *data; +} TreeColumn; + +static void InitColumn(TreeColumn *column) +{ + column->width = 200; + column->minWidth = 20; + column->stretch = 1; + column->idObj = 0; + column->anchorObj = 0; + + column->headingState = 0; + column->headingObj = 0; + column->headingImageObj = 0; + column->headingAnchorObj = 0; + column->headingStateObj = 0; + column->headingCommandObj = 0; + + column->data = 0; +} + +static void FreeColumn(TreeColumn *column) +{ + if (column->idObj) { Tcl_DecrRefCount(column->idObj); } + if (column->anchorObj) { Tcl_DecrRefCount(column->anchorObj); } + + if (column->headingObj) { Tcl_DecrRefCount(column->headingObj); } + if (column->headingImageObj) { Tcl_DecrRefCount(column->headingImageObj); } + if (column->headingAnchorObj) { Tcl_DecrRefCount(column->headingAnchorObj); } + if (column->headingStateObj) { Tcl_DecrRefCount(column->headingStateObj); } + if (column->headingCommandObj) { Tcl_DecrRefCount(column->headingCommandObj); } + + /* Don't touch column->data, it's scratch storage */ +} + +static Tk_OptionSpec ColumnOptionSpecs[] = { + {TK_OPTION_INT, "-width", "width", "Width", + DEF_COLWIDTH, -1, Tk_Offset(TreeColumn,width), + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-minwidth", "minWidth", "MinWidth", + DEF_MINWIDTH, -1, Tk_Offset(TreeColumn,minWidth), + 0,0,0 }, + {TK_OPTION_BOOLEAN, "-stretch", "stretch", "Stretch", + "1", -1, Tk_Offset(TreeColumn,stretch), + 0,0,0 }, + {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", + "w", Tk_Offset(TreeColumn,anchorObj), -1, /* <<NOTE-ANCHOR>> */ + 0,0,0 }, + {TK_OPTION_STRING, "-id", "id", "ID", + NULL, Tk_Offset(TreeColumn,idObj), -1, + TK_OPTION_NULL_OK,0,READONLY_OPTION }, + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +static Tk_OptionSpec HeadingOptionSpecs[] = { + {TK_OPTION_STRING, "-text", "text", "Text", + "", Tk_Offset(TreeColumn,headingObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-image", "image", "Image", + "", Tk_Offset(TreeColumn,headingImageObj), -1, + 0,0,0 }, + {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", + "center", Tk_Offset(TreeColumn,headingAnchorObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-command", "", "", + "", Tk_Offset(TreeColumn,headingCommandObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_STRING, "state", "", "", + "", Tk_Offset(TreeColumn,headingStateObj), -1, + 0,0,STATE_CHANGED }, + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +/*------------------------------------------------------------------------ + * +++ -show option: + * TODO: Implement SHOW_BRANCHES. + */ + +#define SHOW_TREE (0x1) /* Show tree column? */ +#define SHOW_HEADINGS (0x2) /* Show heading row? */ + +#define DEFAULT_SHOW "tree headings" + +static const char *showStrings[] = { + "tree", "headings", NULL +}; + +static int GetEnumSetFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + const char *table[], + unsigned *resultPtr) +{ + unsigned result = 0; + int i, objc; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) + return TCL_ERROR; + + for (i = 0; i < objc; ++i) { + int index; + if (TCL_OK != Tcl_GetIndexFromObj( + interp, objv[i], table, "value", TCL_EXACT, &index)) + { + return TCL_ERROR; + } + result |= (1 << index); + } + + *resultPtr = result; + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Treeview widget record. + * + * Dependencies: + * columns, columnNames: -columns + * displayColumns: -columns, -displaycolumns + * headingHeight: [layout] + * rowHeight, indent: style + */ +typedef struct { + /* Resources acquired at initialization-time: + */ + Tk_OptionTable itemOptionTable; + Tk_OptionTable columnOptionTable; + Tk_OptionTable headingOptionTable; + Tk_OptionTable tagOptionTable; + Tk_BindingTable bindingTable; + Ttk_TagTable tagTable; + + /* Acquired in GetLayout hook: + */ + Ttk_Layout itemLayout; + Ttk_Layout cellLayout; + Ttk_Layout headingLayout; + Ttk_Layout rowLayout; + + int headingHeight; /* Space for headings */ + int rowHeight; /* Height of each item */ + int indent; /* #pixels horizontal offset for child items */ + + /* Tree data: + */ + Tcl_HashTable items; /* Map: item name -> item */ + int serial; /* Next item # for autogenerated names */ + TreeItem *root; /* Root item */ + + TreeColumn column0; /* Column options for display column #0 */ + TreeColumn *columns; /* Array of column options for data columns */ + + TreeItem *focus; /* Current focus item */ + TreeItem *endPtr; /* See EndPosition() */ + + /* Widget options: + */ + Tcl_Obj *columnsObj; /* List of symbolic column names */ + Tcl_Obj *displayColumnsObj; /* List of columns to display */ + + Tcl_Obj *heightObj; /* height (rows) */ + Tcl_Obj *paddingObj; /* internal padding */ + + Tcl_Obj *showObj; /* -show list */ + Tcl_Obj *selectModeObj; /* -selectmode option */ + + Scrollable xscroll; + ScrollHandle xscrollHandle; + Scrollable yscroll; + ScrollHandle yscrollHandle; + + /* Derived resources: + */ + Tcl_HashTable columnNames; /* Map: column name -> column table entry */ + int nColumns; /* #columns */ + unsigned showFlags; /* bitmask of subparts to display */ + + TreeColumn **displayColumns; /* List of columns for display (incl tree) */ + int nDisplayColumns; /* #display columns */ + Ttk_Box headingArea; /* Display area for column headings */ + Ttk_Box treeArea; /* Display area for tree */ + int slack; /* Slack space (see Resizing section) */ + +} TreePart; + +typedef struct { + WidgetCore core; + TreePart tree; +} Treeview; + +#define USER_MASK 0x0100 +#define COLUMNS_CHANGED (USER_MASK) +#define DCOLUMNS_CHANGED (USER_MASK<<1) +#define SCROLLCMD_CHANGED (USER_MASK<<2) +#define SHOW_CHANGED (USER_MASK<<3) + +static const char *SelectModeStrings[] = { "none", "browse", "extended", NULL }; + +static Tk_OptionSpec TreeviewOptionSpecs[] = { + {TK_OPTION_STRING, "-columns", "columns", "Columns", + "", Tk_Offset(Treeview,tree.columnsObj), -1, + 0,0,COLUMNS_CHANGED | GEOMETRY_CHANGED /*| READONLY_OPTION*/ }, + {TK_OPTION_STRING, "-displaycolumns","displayColumns","DisplayColumns", + "#all", Tk_Offset(Treeview,tree.displayColumnsObj), -1, + 0,0,DCOLUMNS_CHANGED | GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-show", "show", "Show", + DEFAULT_SHOW, Tk_Offset(Treeview,tree.showObj), -1, + 0,0,SHOW_CHANGED | GEOMETRY_CHANGED }, + + {TK_OPTION_STRING_TABLE, "-selectmode", "selectMode", "SelectMode", + "extended", Tk_Offset(Treeview,tree.selectModeObj), -1, + 0,(ClientData)SelectModeStrings,0 }, + + {TK_OPTION_PIXELS, "-height", "height", "Height", + DEF_TREE_ROWS, Tk_Offset(Treeview,tree.heightObj), -1, + 0,0,GEOMETRY_CHANGED}, + {TK_OPTION_STRING, "-padding", "padding", "Pad", + NULL, Tk_Offset(Treeview,tree.paddingObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + + {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", + NULL, -1, Tk_Offset(Treeview, tree.xscroll.scrollCmd), + TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED}, + {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", + NULL, -1, Tk_Offset(Treeview, tree.yscroll.scrollCmd), + TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/*------------------------------------------------------------------------ + * +++ Utilities. + */ +typedef void (*HashEntryIterator)(void *hashValue); + +static void foreachHashEntry(Tcl_HashTable *ht, HashEntryIterator func) +{ + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr = Tcl_FirstHashEntry(ht, &search); + while (entryPtr != NULL) { + func(Tcl_GetHashValue(entryPtr)); + entryPtr = Tcl_NextHashEntry(&search); + } +} + +/* + unshareObj(objPtr) -- + * Ensure that a Tcl_Obj * has refcount 1 -- either return objPtr + * itself, or a duplicated copy. + */ +static Tcl_Obj *unshareObj(Tcl_Obj *objPtr) +{ + if (Tcl_IsShared(objPtr)) { + Tcl_Obj *newObj = Tcl_DuplicateObj(objPtr); + Tcl_DecrRefCount(objPtr); + Tcl_IncrRefCount(newObj); + return newObj; + } + return objPtr; +} + +/* DisplayLayout -- + * Rebind, place, and draw a layout + object combination. + */ +static void DisplayLayout( + Ttk_Layout layout, void *recordPtr, Ttk_State state, Ttk_Box b, Drawable d) +{ + Ttk_RebindSublayout(layout, recordPtr); + Ttk_PlaceLayout(layout, state, b); + Ttk_DrawLayout(layout, state, d); +} + +/* + GetColumn -- + * Look up column by name or number. + * Returns: pointer to column table entry, NULL if not found. + * Leaves an error message in interp->result on error. + */ +static TreeColumn *GetColumn( + Tcl_Interp *interp, Treeview *tv, Tcl_Obj *columnIDObj) +{ + Tcl_HashEntry *entryPtr; + int columnIndex; + + /* Check for named column: + */ + entryPtr = Tcl_FindHashEntry( + &tv->tree.columnNames, Tcl_GetString(columnIDObj)); + if (entryPtr) { + return Tcl_GetHashValue(entryPtr); + } + + /* Check for number: + */ + if (Tcl_GetIntFromObj(NULL, columnIDObj, &columnIndex) == TCL_OK) { + if (columnIndex < 0 || columnIndex >= tv->tree.nColumns) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Column index ", + Tcl_GetString(columnIDObj), + " out of bounds", + NULL); + return NULL; + } + + return tv->tree.columns + columnIndex; + } + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Invalid column index ", Tcl_GetString(columnIDObj), + NULL); + return NULL; +} + +/* + FindColumn -- + * Look up column by name, number, or display index. + */ +static TreeColumn *FindColumn( + Tcl_Interp *interp, Treeview *tv, Tcl_Obj *columnIDObj) +{ + int colno; + + if (sscanf(Tcl_GetString(columnIDObj), "#%d", &colno) == 1) + { /* Display column specification, #n */ + if (colno >= 0 && colno < tv->tree.nDisplayColumns) { + return tv->tree.displayColumns[colno]; + } + /* else */ + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Column ", Tcl_GetString(columnIDObj), " out of range", + NULL); + return NULL; + } + + return GetColumn(interp, tv, columnIDObj); +} + +/* + FindItem -- + * Locates the item with the specified identifier in the tree. + * If there is no such item, leaves an error message in interp. + */ +static TreeItem *FindItem( + Tcl_Interp *interp, Treeview *tv, Tcl_Obj *itemNameObj) +{ + const char *itemName = Tcl_GetString(itemNameObj); + Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&tv->tree.items, itemName); + + if (!entryPtr) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Item ", itemName, " not found", NULL); + return 0; + } + return Tcl_GetHashValue(entryPtr); +} + +/* + GetItemListFromObj -- + * Parse a Tcl_Obj * as a list of items. + * Returns a NULL-terminated array of items; result must + * be ckfree()d. On error, returns NULL and leaves an error + * message in interp. + */ + +static TreeItem **GetItemListFromObj( + Tcl_Interp *interp, Treeview *tv, Tcl_Obj *objPtr) +{ + TreeItem **items; + Tcl_Obj **elements; + int i, nElements; + + if (Tcl_ListObjGetElements(interp,objPtr,&nElements,&elements) != TCL_OK) { + return NULL; + } + + items = (TreeItem**)ckalloc((nElements + 1)*sizeof(TreeItem*)); + for (i = 0; i < nElements; ++i) { + items[i] = FindItem(interp, tv, elements[i]); + if (!items[i]) { + ckfree((ClientData)items); + return NULL; + } + } + items[i] = NULL; + return items; +} + +/* + ItemName -- + * Returns the item's ID. + */ +static const char *ItemName(Treeview *tv, TreeItem *item) +{ + return Tcl_GetHashKey(&tv->tree.items, item->entryPtr); +} + +/* + ItemID -- + * Returns a fresh Tcl_Obj * (refcount 0) holding the + * item identifier of the specified item. + */ +static Tcl_Obj *ItemID(Treeview *tv, TreeItem *item) +{ + return Tcl_NewStringObj(ItemName(tv, item), -1); +} + +/*------------------------------------------------------------------------ + * +++ Column configuration. + */ + +/* + TreeviewFreeColumns -- + * Free column data. + */ +static void TreeviewFreeColumns(Treeview *tv) +{ + int i; + + Tcl_DeleteHashTable(&tv->tree.columnNames); + Tcl_InitHashTable(&tv->tree.columnNames, TCL_STRING_KEYS); + + if (tv->tree.columns) { + for (i = 0; i < tv->tree.nColumns; ++i) + FreeColumn(tv->tree.columns + i); + ckfree((ClientData)tv->tree.columns); + tv->tree.columns = 0; + } +} + +/* + TreeviewInitColumns -- + * Initialize column data when -columns changes. + * Returns: TCL_OK or TCL_ERROR; + */ +static int TreeviewInitColumns(Tcl_Interp *interp, Treeview *tv) +{ + Tcl_Obj **columns; + int i, ncols; + + if (Tcl_ListObjGetElements( + interp, tv->tree.columnsObj, &ncols, &columns) != TCL_OK) + { + return TCL_ERROR; + } + + /* + * Free old values: + */ + TreeviewFreeColumns(tv); + + /* + * Initialize columns array and columnNames hash table: + */ + tv->tree.nColumns = ncols; + tv->tree.columns = + (TreeColumn*)ckalloc(tv->tree.nColumns * sizeof(TreeColumn)); + + for (i = 0; i < ncols; ++i) { + int isNew; + Tcl_Obj *columnName = Tcl_DuplicateObj(columns[i]); + + Tcl_HashEntry *entryPtr = Tcl_CreateHashEntry( + &tv->tree.columnNames, Tcl_GetString(columnName), &isNew); + Tcl_SetHashValue(entryPtr, tv->tree.columns + i); + + InitColumn(tv->tree.columns + i); + Tk_InitOptions( + interp, (ClientData)(tv->tree.columns + i), + tv->tree.columnOptionTable, tv->core.tkwin); + Tk_InitOptions( + interp, (ClientData)(tv->tree.columns + i), + tv->tree.headingOptionTable, tv->core.tkwin); + Tcl_IncrRefCount(columnName); + tv->tree.columns[i].idObj = columnName; + } + + return TCL_OK; +} + +/* + TreeviewInitDisplayColumns -- + * Initializes the 'displayColumns' array. + * + * Note that displayColumns[0] is always the tree column, + * even when SHOW_TREE is not set. + * + * @@@ TODO: disallow duplicated columns + */ +static int TreeviewInitDisplayColumns(Tcl_Interp *interp, Treeview *tv) +{ + Tcl_Obj **dcolumns; + int index, ndcols; + TreeColumn **displayColumns = 0; + + if (Tcl_ListObjGetElements(interp, + tv->tree.displayColumnsObj, &ndcols, &dcolumns) != TCL_OK) { + return TCL_ERROR; + } + + if (!strcmp(Tcl_GetString(tv->tree.displayColumnsObj), "#all")) { + ndcols = tv->tree.nColumns; + displayColumns = (TreeColumn**)ckalloc((ndcols+1)*sizeof(TreeColumn*)); + for (index = 0; index < ndcols; ++index) { + displayColumns[index+1] = tv->tree.columns + index; + } + } else { + displayColumns = (TreeColumn**)ckalloc((ndcols+1)*sizeof(TreeColumn*)); + for (index = 0; index < ndcols; ++index) { + displayColumns[index+1] = GetColumn(interp, tv, dcolumns[index]); + if (!displayColumns[index+1]) { + ckfree((ClientData)displayColumns); + return TCL_ERROR; + } + } + } + displayColumns[0] = &tv->tree.column0; + + if (tv->tree.displayColumns) + ckfree((ClientData)tv->tree.displayColumns); + tv->tree.displayColumns = displayColumns; + tv->tree.nDisplayColumns = ndcols + 1; + + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Resizing. + * slack invariant: TreeWidth(tree) + slack = treeArea.width + */ + +#define FirstColumn(tv) ((tv->tree.showFlags&SHOW_TREE) ? 0 : 1) + +/* + TreeWidth -- + * Compute the requested tree width from the sum of visible column widths. + */ +static int TreeWidth(Treeview *tv) +{ + int i = FirstColumn(tv); + int width = 0; + + while (i < tv->tree.nDisplayColumns) { + width += tv->tree.displayColumns[i++]->width; + } + return width; +} + +/* + RecomputeSlack -- + */ +static void RecomputeSlack(Treeview *tv) +{ + tv->tree.slack = tv->tree.treeArea.width - TreeWidth(tv); +} + +/* + PickupSlack/DepositSlack -- + * When resizing columns, distribute extra space to 'slack' first, + * and only adjust column widths if 'slack' goes to zero. + * That is, don't bother changing column widths if the tree + * is already scrolled or short. + */ +static int PickupSlack(Treeview *tv, int extra) +{ + int newSlack = tv->tree.slack + extra; + + if ( (newSlack < 0 && 0 <= tv->tree.slack) + || (newSlack > 0 && 0 >= tv->tree.slack)) + { + tv->tree.slack = 0; + return newSlack; + } else { + tv->tree.slack = newSlack; + return 0; + } +} + +static void DepositSlack(Treeview *tv, int extra) +{ + tv->tree.slack += extra; +} + +/* + Stretch -- + * Adjust width of column by N pixels, down to minimum width. + * Returns: #pixels actually moved. + */ +static int Stretch(TreeColumn *c, int n) +{ + int newWidth = n + c->width; + if (newWidth < c->minWidth) { + n = c->minWidth - c->width; + c->width = c->minWidth; + } else { + c->width = newWidth; + } + return n; +} + +/* + ShoveLeft -- + * Adjust width of (stretchable) columns to the left by N pixels. + * Returns: leftover slack. + */ +static int ShoveLeft(Treeview *tv, int i, int n) +{ + int first = FirstColumn(tv); + while (n != 0 && i >= first) { + TreeColumn *c = tv->tree.displayColumns[i]; + if (c->stretch) { + n -= Stretch(c, n); + } + --i; + } + return n; +} + +/* + ShoveRight -- + * Adjust width of (stretchable) columns to the right by N pixels. + * Returns: leftover slack. + */ +static int ShoveRight(Treeview *tv, int i, int n) +{ + while (n != 0 && i < tv->tree.nDisplayColumns) { + TreeColumn *c = tv->tree.displayColumns[i]; + if (c->stretch) { + n -= Stretch(c, n); + } + ++i; + } + return n; +} + +/* + DistributeWidth -- + * Distribute n pixels evenly across all stretchable display columns. + * Returns: leftover slack. + * Notes: + * The "((++w % m) < r)" term is there so that the remainder r = n % m + * is distributed round-robin. + */ +static int DistributeWidth(Treeview *tv, int n) +{ + int w = TreeWidth(tv); + int m = 0; + int i, d, r; + + for (i = FirstColumn(tv); i < tv->tree.nDisplayColumns; ++i) { + if (tv->tree.displayColumns[i]->stretch) { + ++m; + } + } + if (m == 0) { + return n; + } + + d = n / m; + r = n % m; + if (r < 0) { r += m; --d; } + + for (i = FirstColumn(tv); i < tv->tree.nDisplayColumns; ++i) { + TreeColumn *c = tv->tree.displayColumns[i]; + if (c->stretch) { + n -= Stretch(c, d + ((++w % m) < r)); + } + } + return n; +} + +/* + ResizeColumns -- + * Recompute column widths based on available width. + * Pick up slack first; + * Distribute the remainder evenly across stretchable columns; + * If any is still left over due to minwidth constraints, shove left. + */ +static void ResizeColumns(Treeview *tv, int newWidth) +{ + int delta = newWidth - (TreeWidth(tv) + tv->tree.slack); + DepositSlack(tv, + ShoveLeft(tv, tv->tree.nDisplayColumns - 1, + DistributeWidth(tv, PickupSlack(tv, delta)))); +} + +/* + DragColumn -- + * Move the separator to the right of specified column, + * adjusting other column widths as necessary. + */ +static void DragColumn(Treeview *tv, int i, int delta) +{ + TreeColumn *c = tv->tree.displayColumns[i]; + int dl = delta - ShoveLeft(tv, i-1, delta - Stretch(c, delta)); + int dr = ShoveRight(tv, i+1, PickupSlack(tv, -dl)); + DepositSlack(tv, dr); +} + +/*------------------------------------------------------------------------ + * +++ Event handlers. + */ + +static TreeItem *IdentifyItem(Treeview *tv, int y); /*forward*/ + +static const unsigned int TreeviewBindEventMask = + KeyPressMask|KeyReleaseMask + | ButtonPressMask|ButtonReleaseMask + | PointerMotionMask|ButtonMotionMask + | VirtualEventMask + ; + +static void TreeviewBindEventProc(void *clientData, XEvent *event) +{ + Treeview *tv = clientData; + TreeItem *item = NULL; + Ttk_TagSet tagset; + + /* + * Figure out where to deliver the event. + */ + switch (event->type) + { + case KeyPress: + case KeyRelease: + case VirtualEvent: + item = tv->tree.focus; + break; + case ButtonPress: + case ButtonRelease: + item = IdentifyItem(tv, event->xbutton.y); + break; + case MotionNotify: + item = IdentifyItem(tv, event->xmotion.y); + break; + default: + break; + } + + if (!item) { + return; + } + + /* ASSERT: Ttk_GetTagSetFromObj succeeds. + * NB: must use a local copy of the tagset, + * in case a binding script stomps on -tags. + */ + tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, item->tagsObj); + + /* + * Fire binding: + */ + Tcl_Preserve(clientData); + Tk_BindEvent(tv->tree.bindingTable, event, tv->core.tkwin, + tagset->nTags, (void **)tagset->tags); + Tcl_Release(clientData); + + Ttk_FreeTagSet(tagset); +} + +/*------------------------------------------------------------------------ + * +++ Initialization and cleanup. + */ + +static void TreeviewInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Treeview *tv = recordPtr; + int unused; + + tv->tree.itemOptionTable = + Tk_CreateOptionTable(interp, ItemOptionSpecs); + tv->tree.columnOptionTable = + Tk_CreateOptionTable(interp, ColumnOptionSpecs); + tv->tree.headingOptionTable = + Tk_CreateOptionTable(interp, HeadingOptionSpecs); + tv->tree.tagOptionTable = + Tk_CreateOptionTable(interp, TagOptionSpecs); + + tv->tree.tagTable = Ttk_CreateTagTable( + interp, tv->core.tkwin, TagOptionSpecs, sizeof(DisplayItem)); + tv->tree.bindingTable = Tk_CreateBindingTable(interp); + Tk_CreateEventHandler(tv->core.tkwin, + TreeviewBindEventMask, TreeviewBindEventProc, tv); + + tv->tree.itemLayout + = tv->tree.cellLayout + = tv->tree.headingLayout + = tv->tree.rowLayout + = 0; + tv->tree.headingHeight = tv->tree.rowHeight = DEFAULT_ROWHEIGHT; + tv->tree.indent = DEFAULT_INDENT; + + Tcl_InitHashTable(&tv->tree.columnNames, TCL_STRING_KEYS); + tv->tree.nColumns = tv->tree.nDisplayColumns = 0; + tv->tree.columns = NULL; + tv->tree.displayColumns = NULL; + tv->tree.showFlags = ~0; + + InitColumn(&tv->tree.column0); + Tk_InitOptions( + interp, (ClientData)(&tv->tree.column0), + tv->tree.columnOptionTable, tv->core.tkwin); + Tk_InitOptions( + interp, (ClientData)(&tv->tree.column0), + tv->tree.headingOptionTable, tv->core.tkwin); + + Tcl_InitHashTable(&tv->tree.items, TCL_STRING_KEYS); + tv->tree.serial = 0; + + tv->tree.focus = tv->tree.endPtr = 0; + + /* Create root item "": + */ + tv->tree.root = NewItem(); + Tk_InitOptions(interp, (ClientData)tv->tree.root, + tv->tree.itemOptionTable, tv->core.tkwin); + tv->tree.root->tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, NULL); + tv->tree.root->entryPtr = Tcl_CreateHashEntry(&tv->tree.items, "", &unused); + Tcl_SetHashValue(tv->tree.root->entryPtr, tv->tree.root); + + /* Scroll handles: + */ + tv->tree.xscrollHandle = TtkCreateScrollHandle(&tv->core,&tv->tree.xscroll); + tv->tree.yscrollHandle = TtkCreateScrollHandle(&tv->core,&tv->tree.yscroll); + + /* Size parameters: + */ + tv->tree.treeArea = tv->tree.headingArea = Ttk_MakeBox(0,0,0,0); + tv->tree.slack = 0; +} + +static void TreeviewCleanup(void *recordPtr) +{ + Treeview *tv = recordPtr; + + Tk_DeleteEventHandler(tv->core.tkwin, + TreeviewBindEventMask, TreeviewBindEventProc, tv); + Tk_DeleteBindingTable(tv->tree.bindingTable); + Ttk_DeleteTagTable(tv->tree.tagTable); + + if (tv->tree.itemLayout) Ttk_FreeLayout(tv->tree.itemLayout); + if (tv->tree.cellLayout) Ttk_FreeLayout(tv->tree.cellLayout); + if (tv->tree.headingLayout) Ttk_FreeLayout(tv->tree.headingLayout); + if (tv->tree.rowLayout) Ttk_FreeLayout(tv->tree.rowLayout); + + TreeviewFreeColumns(tv); + + if (tv->tree.displayColumns) + Tcl_Free((ClientData)tv->tree.displayColumns); + + foreachHashEntry(&tv->tree.items, FreeItemCB); + Tcl_DeleteHashTable(&tv->tree.items); + + TtkFreeScrollHandle(tv->tree.xscrollHandle); + TtkFreeScrollHandle(tv->tree.yscrollHandle); +} + +/* + TreeviewConfigure -- + * Configuration widget hook. + * + * BUG: If user sets -columns and -displaycolumns, but -displaycolumns + * has an error, the widget is left in an inconsistent state. + */ +static int +TreeviewConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Treeview *tv = recordPtr; + unsigned showFlags = tv->tree.showFlags; + + if (mask & COLUMNS_CHANGED) { + if (TreeviewInitColumns(interp, tv) != TCL_OK) + return TCL_ERROR; + mask |= DCOLUMNS_CHANGED; + } + if (mask & DCOLUMNS_CHANGED) { + if (TreeviewInitDisplayColumns(interp, tv) != TCL_OK) + return TCL_ERROR; + } + if (mask & SCROLLCMD_CHANGED) { + TtkScrollbarUpdateRequired(tv->tree.xscrollHandle); + TtkScrollbarUpdateRequired(tv->tree.yscrollHandle); + } + if ( (mask & SHOW_CHANGED) + && GetEnumSetFromObj( + interp,tv->tree.showObj,showStrings,&showFlags) != TCL_OK) + { + return TCL_ERROR; + } + + if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) { + return TCL_ERROR; + } + + tv->tree.showFlags = showFlags; + + if (mask & (SHOW_CHANGED | DCOLUMNS_CHANGED)) { + RecomputeSlack(tv); + } + return TCL_OK; +} + +/* + ConfigureItem -- + * Set item options. + */ +static int ConfigureItem( + Tcl_Interp *interp, Treeview *tv, TreeItem *item, + int objc, Tcl_Obj *const objv[]) +{ + Tk_SavedOptions savedOptions; + int mask; + Ttk_ImageSpec *newImageSpec = NULL; + Ttk_TagSet newTagSet = NULL; + + if (Tk_SetOptions(interp, (ClientData)item, tv->tree.itemOptionTable, + objc, objv, tv->core.tkwin, &savedOptions, &mask) + != TCL_OK) + { + return TCL_ERROR; + } + + /* Make sure that -values is a valid list: + */ + if (item->valuesObj) { + int unused; + if (Tcl_ListObjLength(interp, item->valuesObj, &unused) != TCL_OK) + goto error; + } + + /* Check -image. + */ + if ((mask & ITEM_OPTION_IMAGE_CHANGED) && item->imageObj) { + newImageSpec = TtkGetImageSpec(interp, tv->core.tkwin, item->imageObj); + if (!newImageSpec) { + goto error; + } + } + + /* Check -tags. + * Side effect: may create new tags. + */ + if (mask & ITEM_OPTION_TAGS_CHANGED) { + newTagSet = Ttk_GetTagSetFromObj( + interp, tv->tree.tagTable, item->tagsObj); + if (!newTagSet) { + goto error; + } + } + + /* Keep TTK_STATE_OPEN flag in sync with item->openObj. + * We use both a state flag and a Tcl_Obj* resource so elements + * can access the value in either way. + */ + if (item->openObj) { + int isOpen; + if (Tcl_GetBooleanFromObj(interp, item->openObj, &isOpen) != TCL_OK) + goto error; + if (isOpen) + item->state |= TTK_STATE_OPEN; + else + item->state &= ~TTK_STATE_OPEN; + } + + /* All OK. + */ + Tk_FreeSavedOptions(&savedOptions); + if (mask & ITEM_OPTION_TAGS_CHANGED) { + if (item->tagset) { Ttk_FreeTagSet(item->tagset); } + item->tagset = newTagSet; + } + if (mask & ITEM_OPTION_IMAGE_CHANGED) { + if (item->imagespec) { TtkFreeImageSpec(item->imagespec); } + item->imagespec = newImageSpec; + } + TtkRedisplayWidget(&tv->core); + return TCL_OK; + +error: + Tk_RestoreSavedOptions(&savedOptions); + if (newTagSet) { Ttk_FreeTagSet(newTagSet); } + if (newImageSpec) { TtkFreeImageSpec(newImageSpec); } + return TCL_ERROR; +} + +/* + ConfigureColumn -- + * Set column options. + */ +static int ConfigureColumn( + Tcl_Interp *interp, Treeview *tv, TreeColumn *column, + int objc, Tcl_Obj *const objv[]) +{ + Tk_SavedOptions savedOptions; + int mask; + + if (Tk_SetOptions(interp, (ClientData)column, + tv->tree.columnOptionTable, objc, objv, tv->core.tkwin, + &savedOptions,&mask) != TCL_OK) + { + return TCL_ERROR; + } + + if (mask & READONLY_OPTION) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Attempt to change read-only option", NULL); + goto error; + } + + /* Propagate column width changes to overall widget request width, + * but only if the widget is currently unmapped, in order to prevent + * geometry jumping during interactive column resize. + */ + if (mask & GEOMETRY_CHANGED) { + if (!Tk_IsMapped(tv->core.tkwin)) { + TtkResizeWidget(&tv->core); + } + RecomputeSlack(tv); + } + TtkRedisplayWidget(&tv->core); + + /* ASSERT: SLACKINVARIANT */ + + Tk_FreeSavedOptions(&savedOptions); + return TCL_OK; + +error: + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; +} + +/* + ConfigureHeading -- + * Set heading options. + */ +static int ConfigureHeading( + Tcl_Interp *interp, Treeview *tv, TreeColumn *column, + int objc, Tcl_Obj *const objv[]) +{ + Tk_SavedOptions savedOptions; + int mask; + + if (Tk_SetOptions(interp, (ClientData)column, + tv->tree.headingOptionTable, objc, objv, tv->core.tkwin, + &savedOptions,&mask) != TCL_OK) + { + return TCL_ERROR; + } + + /* @@@ testing ... */ + if ((mask & STATE_CHANGED) && column->headingStateObj) { + Ttk_StateSpec stateSpec; + if (Ttk_GetStateSpecFromObj( + interp, column->headingStateObj, &stateSpec) != TCL_OK) + { + goto error; + } + column->headingState = Ttk_ModifyState(column->headingState,&stateSpec); + Tcl_DecrRefCount(column->headingStateObj); + column->headingStateObj = Ttk_NewStateSpecObj(column->headingState,0); + Tcl_IncrRefCount(column->headingStateObj); + } + + TtkRedisplayWidget(&tv->core); + Tk_FreeSavedOptions(&savedOptions); + return TCL_OK; + +error: + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; +} + +/*------------------------------------------------------------------------ + * +++ Geometry routines. + */ + +/* + CountRows -- + * Returns the number of viewable rows rooted at item + */ +static int CountRows(TreeItem *item) +{ + int rows = 1; + + if (item->state & TTK_STATE_OPEN) { + TreeItem *child = item->children; + while (child) { + rows += CountRows(child); + child = child->next; + } + } + return rows; +} + +/* + IdentifyRow -- + * Recursive search for item at specified y position. + * Main work routine for IdentifyItem() + */ +static TreeItem *IdentifyRow( + Treeview *tv, /* Widget record */ + TreeItem *item, /* Where to start search */ + int *ypos, /* Scan position */ + int y) /* Target y coordinate */ +{ + while (item) { + int next_ypos = *ypos + tv->tree.rowHeight; + if (*ypos <= y && y <= next_ypos) { + return item; + } + *ypos = next_ypos; + if (item->state & TTK_STATE_OPEN) { + TreeItem *subitem = IdentifyRow(tv, item->children, ypos, y); + if (subitem) { + return subitem; + } + } + item = item->next; + } + return 0; +} + +/* + IdentifyItem -- + * Locate the item at the specified y position, if any. + */ +static TreeItem *IdentifyItem(Treeview *tv, int y) +{ + int rowHeight = tv->tree.rowHeight; + int ypos = tv->tree.treeArea.y - rowHeight * tv->tree.yscroll.first; + return IdentifyRow(tv, tv->tree.root->children, &ypos, y); +} + +/* + IdentifyDisplayColumn -- + * Returns the display column number at the specified x position, + * or -1 if x is outside any columns. + */ +static int IdentifyDisplayColumn(Treeview *tv, int x, int *x1) +{ + int colno = FirstColumn(tv); + int xpos = tv->tree.treeArea.x - tv->tree.xscroll.first; + + while (colno < tv->tree.nDisplayColumns) { + TreeColumn *column = tv->tree.displayColumns[colno]; + int next_xpos = xpos + column->width; + if (xpos <= x && x <= next_xpos + HALO) { + *x1 = next_xpos; + return colno; + } + ++colno; + xpos = next_xpos; + } + + return -1; +} + +/* + RowNumber -- + * Calculate which row the specified item appears on; + * returns -1 if the item is not viewable. + * Xref: DrawForest, IdentifyItem. + */ +static int RowNumber(Treeview *tv, TreeItem *item) +{ + TreeItem *p = tv->tree.root->children; + int n = 0; + + while (p) { + if (p == item) + return n; + + ++n; + + /* Find next viewable item in preorder traversal order + */ + if (p->children && (p->state & TTK_STATE_OPEN)) { + p = p->children; + } else { + while (!p->next && p && p->parent) + p = p->parent; + if (p) + p = p->next; + } + } + + return -1; +} + +/* + ItemDepth -- return the depth of a tree item. + * The depth of an item is equal to the number of proper ancestors, + * not counting the root node. + */ +static int ItemDepth(TreeItem *item) +{ + int depth = 0; + while (item->parent) { + ++depth; + item = item->parent; + } + return depth-1; +} + +/* + ItemRow -- + * Returns row number of specified item relative to root, + * -1 if item is not viewable. + */ +static int ItemRow(Treeview *tv, TreeItem *p) +{ + TreeItem *root = tv->tree.root; + int rowNumber = 0; + + for (;;) { + if (p->prev) { + p = p->prev; + rowNumber += CountRows(p); + } else { + p = p->parent; + if (!(p && (p->state & TTK_STATE_OPEN))) { + /* detached or closed ancestor */ + return -1; + } + if (p == root) { + return rowNumber; + } + ++rowNumber; + } + } +} + +/* + BoundingBox -- + * Compute the parcel of the specified column of the specified item, + * (or the entire item if column is NULL) + * Returns: 0 if item or column is not viewable, 1 otherwise. + */ +static int BoundingBox( + Treeview *tv, /* treeview widget */ + TreeItem *item, /* desired item */ + TreeColumn *column, /* desired column */ + Ttk_Box *bbox_rtn) /* bounding box of item */ +{ + int row = ItemRow(tv, item); + Ttk_Box bbox = tv->tree.treeArea; + + if (row < tv->tree.yscroll.first || row > tv->tree.yscroll.last) { + /* not viewable, or off-screen */ + return 0; + } + + bbox.y += (row - tv->tree.yscroll.first) * tv->tree.rowHeight; + bbox.height = tv->tree.rowHeight; + + bbox.x -= tv->tree.xscroll.first; + bbox.width = TreeWidth(tv); + + if (column) { + int xpos = 0, i = FirstColumn(tv); + while (i < tv->tree.nDisplayColumns) { + if (tv->tree.displayColumns[i] == column) { + break; + } + xpos += tv->tree.displayColumns[i]->width; + ++i; + } + if (i == tv->tree.nDisplayColumns) { /* specified column unviewable */ + return 0; + } + bbox.x += xpos; + bbox.width = column->width; + + /* Account for indentation in tree column: + */ + if (column == &tv->tree.column0) { + int indent = tv->tree.indent * ItemDepth(item); + bbox.x += indent; + bbox.width -= indent; + } + } + *bbox_rtn = bbox; + return 1; +} + +/* + IdentifyRegion -- + */ + +typedef enum { + REGION_NOTHING = 0, + REGION_HEADING, + REGION_SEPARATOR, + REGION_TREE, + REGION_CELL +} TreeRegion; + +static const char *regionStrings[] = { + "nothing", "heading", "separator", "tree", "cell", 0 +}; + +static TreeRegion IdentifyRegion(Treeview *tv, int x, int y) +{ + int x1 = 0, colno; + + colno = IdentifyDisplayColumn(tv, x, &x1); + if (Ttk_BoxContains(tv->tree.headingArea, x, y)) { + if (colno < 0) { + return REGION_NOTHING; + } else if (-HALO <= x1 - x && x1 - x <= HALO) { + return REGION_SEPARATOR; + } else { + return REGION_HEADING; + } + } else if (Ttk_BoxContains(tv->tree.treeArea, x, y)) { + TreeItem *item = IdentifyItem(tv, y); + if (item && colno > 0) { + return REGION_CELL; + } else if (item) { + return REGION_TREE; + } + } + return REGION_NOTHING; +} + +/*------------------------------------------------------------------------ + * +++ Display routines. + */ + +/* + GetSublayout -- + * Utility routine; acquires a sublayout for items, cells, etc. + */ +static Ttk_Layout GetSublayout( + Tcl_Interp *interp, + Ttk_Theme themePtr, + Ttk_Layout parentLayout, + const char *layoutName, + Tk_OptionTable optionTable, + Ttk_Layout *layoutPtr) +{ + Ttk_Layout newLayout = Ttk_CreateSublayout( + interp, themePtr, parentLayout, layoutName, optionTable); + + if (newLayout) { + if (*layoutPtr) + Ttk_FreeLayout(*layoutPtr); + *layoutPtr = newLayout; + } + return newLayout; +} + +/* + TreeviewGetLayout -- + * GetLayout() widget hook. + */ +static Ttk_Layout TreeviewGetLayout( + Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr) +{ + Treeview *tv = recordPtr; + Ttk_Layout treeLayout = TtkWidgetGetLayout(interp, themePtr, recordPtr); + Tcl_Obj *objPtr; + int unused; + + if (!( + treeLayout + && GetSublayout(interp, themePtr, treeLayout, ".Item", + tv->tree.tagOptionTable, &tv->tree.itemLayout) + && GetSublayout(interp, themePtr, treeLayout, ".Cell", + tv->tree.tagOptionTable, &tv->tree.cellLayout) + && GetSublayout(interp, themePtr, treeLayout, ".Heading", + tv->tree.headingOptionTable, &tv->tree.headingLayout) + && GetSublayout(interp, themePtr, treeLayout, ".Row", + tv->tree.tagOptionTable, &tv->tree.rowLayout) + )) { + return 0; + } + + /* Compute heading height. + */ + Ttk_RebindSublayout(tv->tree.headingLayout, &tv->tree.column0); + Ttk_LayoutSize(tv->tree.headingLayout, 0, &unused, &tv->tree.headingHeight); + + /* Get item height, indent from style: + * @@@ TODO: sanity-check. + */ + tv->tree.rowHeight = DEFAULT_ROWHEIGHT; + tv->tree.indent = DEFAULT_INDENT; + if ((objPtr = Ttk_QueryOption(treeLayout, "-rowheight", 0))) { + (void)Tcl_GetIntFromObj(NULL, objPtr, &tv->tree.rowHeight); + } + if ((objPtr = Ttk_QueryOption(treeLayout, "-indent", 0))) { + (void)Tcl_GetIntFromObj(NULL, objPtr, &tv->tree.indent); + } + + return treeLayout; +} + +/* + TreeviewDoLayout -- + * DoLayout() widget hook. Computes widget layout. + * + * Side effects: + * Computes headingArea and treeArea. + * Computes subtree height. + * Invokes scroll callbacks. + */ +static void TreeviewDoLayout(void *clientData) +{ + Treeview *tv = clientData; + int visibleRows; + + /* ASSERT: SLACKINVARIANT */ + + Ttk_PlaceLayout(tv->core.layout,tv->core.state,Ttk_WinBox(tv->core.tkwin)); + tv->tree.treeArea = Ttk_ClientRegion(tv->core.layout, "treearea"); + + ResizeColumns(tv, tv->tree.treeArea.width); + /* ASSERT: SLACKINVARIANT */ + + TtkScrolled(tv->tree.xscrollHandle, + tv->tree.xscroll.first, + tv->tree.xscroll.first + tv->tree.treeArea.width, + TreeWidth(tv)); + + if (tv->tree.showFlags & SHOW_HEADINGS) { + tv->tree.headingArea = Ttk_PackBox( + &tv->tree.treeArea, 1, tv->tree.headingHeight, TTK_SIDE_TOP); + } else { + tv->tree.headingArea = Ttk_MakeBox(0,0,0,0); + } + + visibleRows = tv->tree.treeArea.height / tv->tree.rowHeight; + tv->tree.root->state |= TTK_STATE_OPEN; + TtkScrolled(tv->tree.yscrollHandle, + tv->tree.yscroll.first, + tv->tree.yscroll.first + visibleRows, + CountRows(tv->tree.root) - 1); +} + +/* + TreeviewSize -- + * SizeProc() widget hook. Size is determined by + * -height option and column widths. + */ +static int TreeviewSize(void *clientData, int *widthPtr, int *heightPtr) +{ + Treeview *tv = clientData; + int nRows, padHeight, padWidth; + + Ttk_LayoutSize(tv->core.layout, tv->core.state, &padWidth, &padHeight); + Tcl_GetIntFromObj(NULL, tv->tree.heightObj, &nRows); + + *widthPtr = padWidth + TreeWidth(tv); + *heightPtr = padHeight + tv->tree.rowHeight * nRows; + + if (tv->tree.showFlags & SHOW_HEADINGS) { + *heightPtr += tv->tree.headingHeight; + } + + return 1; +} + +/* + ItemState -- + * Returns the state of the specified item, based + * on widget state, item state, and other information. + */ +static Ttk_State ItemState(Treeview *tv, TreeItem *item) +{ + Ttk_State state = tv->core.state | item->state; + if (!item->children) + state |= TTK_STATE_LEAF; + if (item != tv->tree.focus) + state &= ~TTK_STATE_FOCUS; + return state; +} + +/* + DrawHeadings -- + * Draw tree headings. + */ +static void DrawHeadings(Treeview *tv, Drawable d) +{ + const int x0 = tv->tree.headingArea.x - tv->tree.xscroll.first; + const int y0 = tv->tree.headingArea.y; + const int h0 = tv->tree.headingArea.height; + int i = FirstColumn(tv); + int x = 0; + + while (i < tv->tree.nDisplayColumns) { + TreeColumn *column = tv->tree.displayColumns[i]; + Ttk_Box parcel = Ttk_MakeBox(x0+x, y0, column->width, h0); + DisplayLayout(tv->tree.headingLayout, + column, column->headingState, parcel, d); + x += column->width; + ++i; + } +} + +/* + PrepareItem -- + * Fill in a displayItem record. + */ +static void PrepareItem( + Treeview *tv, TreeItem *item, DisplayItem *displayItem) +{ + Ttk_Style style = Ttk_LayoutStyle(tv->core.layout); + Ttk_State state = ItemState(tv, item); + + Ttk_TagSetValues(tv->tree.tagTable, item->tagset, displayItem); + Ttk_TagSetApplyStyle(tv->tree.tagTable, style, state, displayItem); +} + +/* + DrawCells -- + * Draw data cells for specified item. + */ +static void DrawCells( + Treeview *tv, TreeItem *item, DisplayItem *displayItem, + Drawable d, int x, int y) +{ + Ttk_Layout layout = tv->tree.cellLayout; + Ttk_State state = ItemState(tv, item); + Ttk_Padding cellPadding = {4, 0, 4, 0}; + int rowHeight = tv->tree.rowHeight; + int nValues = 0; + Tcl_Obj **values = 0; + int i; + + if (!item->valuesObj) { + return; + } + + Tcl_ListObjGetElements(NULL, item->valuesObj, &nValues, &values); + for (i = 0; i < tv->tree.nColumns; ++i) { + tv->tree.columns[i].data = (i < nValues) ? values[i] : 0; + } + + for (i = 1; i < tv->tree.nDisplayColumns; ++i) { + TreeColumn *column = tv->tree.displayColumns[i]; + Ttk_Box parcel = Ttk_PadBox( + Ttk_MakeBox(x, y, column->width, rowHeight), cellPadding); + + displayItem->textObj = column->data; + displayItem->anchorObj = column->anchorObj; /* <<NOTE-ANCHOR>> */ + + DisplayLayout(layout, displayItem, state, parcel, d); + x += column->width; + } +} + +/* + DrawItem -- + * Draw an item (row background, tree label, and cells). + */ +static void DrawItem( + Treeview *tv, TreeItem *item, Drawable d, int depth, int row) +{ + Ttk_State state = ItemState(tv, item); + DisplayItem displayItem; + int rowHeight = tv->tree.rowHeight; + int x = tv->tree.treeArea.x - tv->tree.xscroll.first; + int y = tv->tree.treeArea.y + rowHeight * (row - tv->tree.yscroll.first); + + if (row % 2) state |= TTK_STATE_ALTERNATE; + + PrepareItem(tv, item, &displayItem); + + /* Draw row background: + */ + { + Ttk_Box rowBox = Ttk_MakeBox(x, y, TreeWidth(tv), rowHeight); + DisplayLayout(tv->tree.rowLayout, &displayItem, state, rowBox, d); + } + + /* Draw tree label: + */ + if (tv->tree.showFlags & SHOW_TREE) { + int indent = depth * tv->tree.indent; + int colwidth = tv->tree.column0.width; + Ttk_Box parcel = Ttk_MakeBox( + x+indent, y, colwidth-indent, rowHeight); + if (item->textObj) { displayItem.textObj = item->textObj; } + if (item->imageObj) { displayItem.imageObj = item->imageObj; } + /* ??? displayItem.anchorObj = 0; <<NOTE-ANCHOR>> */ + DisplayLayout(tv->tree.itemLayout, &displayItem, state, parcel, d); + x += colwidth; + } + + /* Draw data cells: + */ + DrawCells(tv, item, &displayItem, d, x, y); +} + +/* + DrawSubtree -- + * Draw an item and all of its (viewable) descendants. + * + * Returns: + * Row number of the last item drawn. + */ + +static int DrawForest( /* forward */ + Treeview *tv, TreeItem *item, Drawable d, int depth, int row); + +static int DrawSubtree( + Treeview *tv, TreeItem *item, Drawable d, int depth, int row) +{ + if (row >= tv->tree.yscroll.first) { + DrawItem(tv, item, d, depth, row); + } + + if (item->state & TTK_STATE_OPEN) { + return DrawForest(tv, item->children, d, depth + 1, row + 1); + } else { + return row + 1; + } +} + +/* + DrawForest -- + * Draw a sequence of items and their visible descendants. + * + * Returns: + * Row number of the last item drawn. + */ +static int DrawForest( + Treeview *tv, TreeItem *item, Drawable d, int depth, int row) +{ + while (item && row <= tv->tree.yscroll.last) { + row = DrawSubtree(tv, item, d, depth, row); + item = item->next; + } + return row; +} + +/* + TreeviewDisplay -- + * Display() widget hook. Draw the widget contents. + */ +static void TreeviewDisplay(void *clientData, Drawable d) +{ + Treeview *tv = clientData; + + Ttk_DrawLayout(tv->core.layout, tv->core.state, d); + if (tv->tree.showFlags & SHOW_HEADINGS) { + DrawHeadings(tv, d); + } + DrawForest(tv, tv->tree.root->children, d, 0,0); +} + +/*------------------------------------------------------------------------ + * +++ Utilities for widget commands + */ + +/* + InsertPosition -- + * Locate the previous sibling for [$tree insert]. + * + * Returns a pointer to the item just before the specified index, + * or 0 if the item is to be inserted at the beginning. + */ +static TreeItem *InsertPosition(TreeItem *parent, int index) +{ + TreeItem *prev = 0, *next = parent->children; + + while (next != 0 && index > 0) { + --index; + prev = next; + next = prev->next; + } + + return prev; +} + +/* + EndPosition -- + * Locate the last child of the specified node. + * + * To avoid quadratic-time behavior in the common cases + * where the treeview is populated in breadth-first or + * depth-first order using [$tv insert $parent end ...], + * we cache the result from the last call to EndPosition() + * and start the search from there on a cache hit. + * + */ +static TreeItem *EndPosition(Treeview *tv, TreeItem *parent) +{ + TreeItem *endPtr = tv->tree.endPtr; + + while (endPtr && endPtr->parent != parent) { + endPtr = endPtr->parent; + } + if (!endPtr) { + endPtr = parent->children; + } + + if (endPtr) { + while (endPtr->next) { + endPtr = endPtr->next; + } + tv->tree.endPtr = endPtr; + } + + return endPtr; +} + +/* + AncestryCheck -- + * Verify that specified item is not an ancestor of the specified parent; + * returns 1 if OK, 0 and leaves an error message in interp otherwise. + */ +static int AncestryCheck( + Tcl_Interp *interp, Treeview *tv, TreeItem *item, TreeItem *parent) +{ + TreeItem *p = parent; + while (p) { + if (p == item) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Cannot insert ", ItemName(tv, item), + " as a descendant of ", ItemName(tv, parent), + NULL); + return 0; + } + p = p->parent; + } + return 1; +} + +/* + DeleteItems -- + * Remove an item and all of its descendants from the hash table + * and detach them from the tree; returns a linked list (chained + * along the ->next pointer) of deleted items. + */ +static TreeItem *DeleteItems(TreeItem *item, TreeItem *delq) +{ + if (item->entryPtr) { + DetachItem(item); + while (item->children) { + delq = DeleteItems(item->children, delq); + } + Tcl_DeleteHashEntry(item->entryPtr); + item->entryPtr = 0; + item->next = delq; + delq = item; + } /* else -- item has already been unlinked */ + return delq; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- item inquiry. + */ + +/* + $tv children $item ?newchildren? -- + * Return the list of children associated with $item + */ +static int TreeviewChildrenCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + Tcl_Obj *result; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "item ?newchildren?"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + if (objc == 3) { + result = Tcl_NewListObj(0,0); + for (item = item->children; item; item = item->next) { + Tcl_ListObjAppendElement(interp, result, ItemID(tv, item)); + } + Tcl_SetObjResult(interp, result); + } else { + TreeItem **newChildren = GetItemListFromObj(interp, tv, objv[3]); + TreeItem *child; + int i; + + if (!newChildren) + return TCL_ERROR; + + /* Sanity-check: + */ + for (i=0; newChildren[i]; ++i) { + if (!AncestryCheck(interp, tv, newChildren[i], item)) { + ckfree((ClientData)newChildren); + return TCL_ERROR; + } + } + + /* Detach old children: + */ + child = item->children; + while (child) { + TreeItem *next = child->next; + DetachItem(child); + child = next; + } + + /* Detach new children from their current locations: + */ + for (i=0; newChildren[i]; ++i) { + DetachItem(newChildren[i]); + } + + /* Reinsert new children: + * Note: it is not an error for an item to be listed more than once, + * though it probably should be... + */ + child = 0; + for (i=0; newChildren[i]; ++i) { + if (newChildren[i]->parent) { + /* This is a duplicate element which has already been + * inserted. Ignore it. + */ + continue; + } + InsertItem(item, child, newChildren[i]); + child = newChildren[i]; + } + + ckfree((ClientData)newChildren); + TtkRedisplayWidget(&tv->core); + } + + return TCL_OK; +} + +/* + $tv parent $item -- + * Return the item ID of $item's parent. + */ +static int TreeviewParentCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + if (item->parent) { + Tcl_SetObjResult(interp, ItemID(tv, item->parent)); + } else { + /* This is the root item. @@@ Return an error? */ + Tcl_ResetResult(interp); + } + + return TCL_OK; +} + +/* + $tv next $item + * Return the ID of $item's next sibling. + */ +static int TreeviewNextCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + if (item->next) { + Tcl_SetObjResult(interp, ItemID(tv, item->next)); + } /* else -- leave interp-result empty */ + + return TCL_OK; +} + +/* + $tv prev $item + * Return the ID of $item's previous sibling. + */ +static int TreeviewPrevCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + if (item->prev) { + Tcl_SetObjResult(interp, ItemID(tv, item->prev)); + } /* else -- leave interp-result empty */ + + return TCL_OK; +} + +/* + $tv index $item -- + * Return the index of $item within its parent. + */ +static int TreeviewIndexCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + int index = 0; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + while (item->prev) { + ++index; + item = item->prev; + } + + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); + return TCL_OK; +} + +/* + $tv exists $itemid -- + * Test if the specified item id is present in the tree. + */ +static int TreeviewExistsCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Tcl_HashEntry *entryPtr; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "itemid"); + return TCL_ERROR; + } + + entryPtr = Tcl_FindHashEntry(&tv->tree.items, Tcl_GetString(objv[2])); + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(entryPtr != 0)); + return TCL_OK; +} + +/* + $tv bbox $itemid ?$column? -- + * Return bounding box [x y width height] of specified item. + */ +static int TreeviewBBoxCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item = 0; + TreeColumn *column = 0; + Ttk_Box bbox; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "itemid ?column"); + return TCL_ERROR; + } + + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + if (objc >=4 && (column = FindColumn(interp,tv,objv[3])) == NULL) { + return TCL_ERROR; + } + + if (BoundingBox(tv, item, column, &bbox)) { + Tcl_SetObjResult(interp, Ttk_NewBoxObj(bbox)); + } + + return TCL_OK; +} + +/* + $tv identify $x $y -- (obsolescent) + * Implements the old, horrible, 2-argument form of [$tv identify]. + * + * Returns: one of + * heading #n + * cell itemid #n + * item itemid element + * row itemid + */ +static int TreeviewHorribleIdentify( + Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], Treeview *tv) +{ + const char *what = "nothing", *detail = NULL; + TreeItem *item = 0; + Tcl_Obj *result; + int dColumnNumber; + char dcolbuf[16]; + int x, y, x1; + + /* ASSERT: objc == 4 */ + + if ( Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK + ) { + return TCL_ERROR; + } + + dColumnNumber = IdentifyDisplayColumn(tv, x, &x1); + if (dColumnNumber < 0) { + goto done; + } + sprintf(dcolbuf, "#%d", dColumnNumber); + + if (Ttk_BoxContains(tv->tree.headingArea,x,y)) { + if (-HALO <= x1 - x && x1 - x <= HALO) { + what = "separator"; + } else { + what = "heading"; + } + detail = dcolbuf; + } else if (Ttk_BoxContains(tv->tree.treeArea,x,y)) { + item = IdentifyItem(tv, y); + if (item && dColumnNumber > 0) { + what = "cell"; + detail = dcolbuf; + } else if (item) { + Ttk_Layout layout = tv->tree.itemLayout; + Ttk_Box itemBox; + DisplayItem displayItem; + Ttk_Element element; + + BoundingBox(tv, item, NULL, &itemBox); + PrepareItem(tv, item, &displayItem); /*@@@ FIX: -text, etc*/ + Ttk_RebindSublayout(layout, &displayItem); + Ttk_PlaceLayout(layout, ItemState(tv,item), itemBox); + element = Ttk_IdentifyElement(layout, x, y); + + if (element) { + what = "item"; + detail = Ttk_ElementName(element); + } else { + what = "row"; + } + } + } + +done: + result = Tcl_NewListObj(0,0); + Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(what, -1)); + if (item) + Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item)); + if (detail) + Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(detail, -1)); + + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +/* + $tv identify $component $x $y -- + * Identify the component at position x,y. + */ + +static int TreeviewIdentifyCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + static const char *submethodStrings[] = + { "region", "item", "column", "row", "element", NULL }; + enum { I_REGION, I_ITEM, I_COLUMN, I_ROW, I_ELEMENT }; + + Treeview *tv = recordPtr; + int submethod; + int x, y; + + TreeRegion region; + Ttk_Box bbox; + TreeItem *item; + TreeColumn *column = 0; + int colno, x1; + + if (objc == 4) { /* Old form */ + return TreeviewHorribleIdentify(interp, objc, objv, tv); + } else if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "command x y"); + return TCL_ERROR; + } + + if ( Tcl_GetIndexFromObj(interp, objv[2], + submethodStrings, "command", TCL_EXACT, &submethod) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK + ) { + return TCL_ERROR; + } + + region = IdentifyRegion(tv, x, y); + item = IdentifyItem(tv, y); + colno = IdentifyDisplayColumn(tv, x, &x1); + column = (colno >= 0) ? tv->tree.displayColumns[colno] : NULL; + + switch (submethod) + { + case I_REGION : + Tcl_SetObjResult(interp,Tcl_NewStringObj(regionStrings[region],-1)); + break; + + case I_ITEM : + case I_ROW : + if (item) { + Tcl_SetObjResult(interp, ItemID(tv, item)); + } + break; + + case I_COLUMN : + if (colno >= 0) { + char dcolbuf[16]; + sprintf(dcolbuf, "#%d", colno); + Tcl_SetObjResult(interp, Tcl_NewStringObj(dcolbuf, -1)); + } + break; + + case I_ELEMENT : + { + Ttk_Layout layout = 0; + DisplayItem displayItem; + Ttk_Element element; + + switch (region) { + case REGION_NOTHING: + layout = tv->core.layout; + return TCL_OK; /* @@@ NYI */ + case REGION_HEADING: + case REGION_SEPARATOR: + layout = tv->tree.headingLayout; + return TCL_OK; /* @@@ NYI */ + case REGION_TREE: + layout = tv->tree.itemLayout; + break; + case REGION_CELL: + layout = tv->tree.cellLayout; + break; + } + + if (!BoundingBox(tv, item, column, &bbox)) { + return TCL_OK; + } + + PrepareItem(tv, item, &displayItem); /*@@@ FIX: fill in -text,etc */ + Ttk_RebindSublayout(layout, &displayItem); + Ttk_PlaceLayout(layout, ItemState(tv,item), bbox); + element = Ttk_IdentifyElement(layout, x, y); + + if (element) { + const char *elementName = Ttk_ElementName(element); + Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1)); + } + break; + } + } + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- item and column configuration. + */ + +/* + $tv item $item ?options ....? + * Query or configure item options. + */ +static int TreeviewItemCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item ?option ?value??..."); + return TCL_ERROR; + } + if (!(item = FindItem(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + if (objc == 3) { + return TtkEnumerateOptions(interp, item, ItemOptionSpecs, + tv->tree.itemOptionTable, tv->core.tkwin); + } else if (objc == 4) { + return TtkGetOptionValue(interp, item, objv[3], + tv->tree.itemOptionTable, tv->core.tkwin); + } else { + return ConfigureItem(interp, tv, item, objc-3, objv+3); + } +} + +/* + $tv column column ?options ....? + * Column data accessor + */ +static int TreeviewColumnCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeColumn *column; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "column -option value..."); + return TCL_ERROR; + } + if (!(column = FindColumn(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + if (objc == 3) { + return TtkEnumerateOptions(interp, column, ColumnOptionSpecs, + tv->tree.columnOptionTable, tv->core.tkwin); + } else if (objc == 4) { + return TtkGetOptionValue(interp, column, objv[3], + tv->tree.columnOptionTable, tv->core.tkwin); + } else { + return ConfigureColumn(interp, tv, column, objc-3, objv+3); + } +} + +/* + $tv heading column ?options ....? + * Heading data accessor + */ +static int TreeviewHeadingCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Tk_OptionTable optionTable = tv->tree.headingOptionTable; + Tk_Window tkwin = tv->core.tkwin; + TreeColumn *column; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "column -option value..."); + return TCL_ERROR; + } + if (!(column = FindColumn(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + if (objc == 3) { + return TtkEnumerateOptions( + interp, column, HeadingOptionSpecs, optionTable, tkwin); + } else if (objc == 4) { + return TtkGetOptionValue( + interp, column, objv[3], optionTable, tkwin); + } else { + return ConfigureHeading(interp, tv, column, objc-3,objv+3); + } +} + +/* + $tv set $item ?$column ?value?? + * Query or configure cell values + */ +static int TreeviewSetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + TreeColumn *column; + int columnNumber; + + if (objc < 3 || objc > 5) { + Tcl_WrongNumArgs(interp, 2, objv, "item ?column ?value??"); + return TCL_ERROR; + } + if (!(item = FindItem(interp, tv, objv[2]))) + return TCL_ERROR; + + /* Make sure -values exists: + */ + if (!item->valuesObj) { + item->valuesObj = Tcl_NewListObj(0,0); + Tcl_IncrRefCount(item->valuesObj); + } + + if (objc == 3) { + /* Return dictionary: + */ + Tcl_Obj *result = Tcl_NewListObj(0,0); + Tcl_Obj *value; + for (columnNumber=0; columnNumber<tv->tree.nColumns; ++columnNumber) { + Tcl_ListObjIndex(interp, item->valuesObj, columnNumber, &value); + if (value) { + Tcl_ListObjAppendElement(interp, result, + tv->tree.columns[columnNumber].idObj); + Tcl_ListObjAppendElement(interp, result, value); + } + } + Tcl_SetObjResult(interp, result); + return TCL_OK; + } + + /* else -- get or set column + */ + if (!(column = FindColumn(interp, tv, objv[3]))) + return TCL_ERROR; + + if (column == &tv->tree.column0) { + /* @@@ Maybe set -text here instead? */ + Tcl_AppendResult(interp, "Display column #0 cannot be set", NULL); + return TCL_ERROR; + } + + /* Note: we don't do any error checking in the list operations, + * since item->valuesObj is guaranteed to be a list. + */ + columnNumber = column - tv->tree.columns; + + if (objc == 4) { /* get column */ + Tcl_Obj *result = 0; + Tcl_ListObjIndex(interp, item->valuesObj, columnNumber, &result); + if (!result) { + result = Tcl_NewStringObj("",0); + } + Tcl_SetObjResult(interp, result); + return TCL_OK; + } else { /* set column */ + int length; + + item->valuesObj = unshareObj(item->valuesObj); + + /* Make sure -values is fully populated: + */ + Tcl_ListObjLength(interp, item->valuesObj, &length); + while (length < tv->tree.nColumns) { + Tcl_Obj *empty = Tcl_NewStringObj("",0); + Tcl_ListObjAppendElement(interp, item->valuesObj, empty); + ++length; + } + + /* Set value: + */ + Tcl_ListObjReplace(interp,item->valuesObj,columnNumber,1,1,objv+4); + TtkRedisplayWidget(&tv->core); + return TCL_OK; + } +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- tree modification. + */ + +/* + $tv insert $parent $index ?-id id? ?-option value ...? + * Insert a new item. + */ +static int TreeviewInsertCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *parent, *sibling, *newItem; + Tcl_HashEntry *entryPtr; + int isNew; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, "parent index ?-id id? -options..."); + return TCL_ERROR; + } + + /* Get parent node: + */ + if ((parent = FindItem(interp, tv, objv[2])) == NULL) { + return TCL_ERROR; + } + + /* Locate previous sibling based on $index: + */ + if (!strcmp(Tcl_GetString(objv[3]), "end")) { + sibling = EndPosition(tv, parent); + } else { + int index; + if (Tcl_GetIntFromObj(interp, objv[3], &index) != TCL_OK) + return TCL_ERROR; + sibling = InsertPosition(parent, index); + } + + /* Get node name: + * If -id supplied and does not already exist, use that; + * Otherwise autogenerate new one. + */ + objc -= 4; objv += 4; + if (objc >= 2 && !strcmp("-id", Tcl_GetString(objv[0]))) { + const char *itemName = Tcl_GetString(objv[1]); + entryPtr = Tcl_CreateHashEntry(&tv->tree.items, itemName, &isNew); + if (!isNew) { + Tcl_AppendResult(interp, "Item ",itemName," already exists",NULL); + return TCL_ERROR; + } + objc -= 2; objv += 2; + } else { + char idbuf[16]; + do { + ++tv->tree.serial; + sprintf(idbuf, "I%03X", tv->tree.serial); + entryPtr = Tcl_CreateHashEntry(&tv->tree.items, idbuf, &isNew); + } while (!isNew); + } + + /* Create and configure new item: + */ + newItem = NewItem(); + Tk_InitOptions( + interp, (ClientData)newItem, tv->tree.itemOptionTable, tv->core.tkwin); + newItem->tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, NULL); + if (ConfigureItem(interp, tv, newItem, objc, objv) != TCL_OK) { + Tcl_DeleteHashEntry(entryPtr); + FreeItem(newItem); + return TCL_ERROR; + } + + /* Store in hash table, link into tree: + */ + Tcl_SetHashValue(entryPtr, newItem); + newItem->entryPtr = entryPtr; + InsertItem(parent, sibling, newItem); + TtkRedisplayWidget(&tv->core); + + Tcl_SetObjResult(interp, ItemID(tv, newItem)); + return TCL_OK; +} + +/* + $tv detach $item -- + * Unlink $item from the tree. + */ +static int TreeviewDetachCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem **items; + int i; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + if (!(items = GetItemListFromObj(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + /* Sanity-check */ + for (i = 0; items[i]; ++i) { + if (items[i] == tv->tree.root) { + Tcl_AppendResult(interp, "Cannot detach root item", NULL); + ckfree((ClientData)items); + return TCL_ERROR; + } + } + + for (i = 0; items[i]; ++i) { + DetachItem(items[i]); + } + + TtkRedisplayWidget(&tv->core); + ckfree((ClientData)items); + return TCL_OK; +} + +/* + $tv delete $items -- + * Delete each item in $items. + * + * Do this in two passes: + * First detach the item and all its descendants and remove them + * from the hash table. Free the items themselves in a second pass. + * + * It's done this way because an item may appear more than once + * in the list of items to delete (either directly or as a descendant + * of a previously deleted item.) + */ + +static int TreeviewDeleteCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem **items, *delq; + int i; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "items"); + return TCL_ERROR; + } + + if (!(items = GetItemListFromObj(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + /* Sanity-check: + */ + for (i=0; items[i]; ++i) { + if (items[i] == tv->tree.root) { + ckfree((ClientData)items); + Tcl_AppendResult(interp, "Cannot delete root item", NULL); + return TCL_ERROR; + } + } + + /* Remove items from hash table. + */ + delq = 0; + for (i=0; items[i]; ++i) { + delq = DeleteItems(items[i], delq); + } + + /* Free items: + */ + while (delq) { + TreeItem *next = delq->next; + if (tv->tree.focus == delq) + tv->tree.focus = 0; + if (tv->tree.endPtr == delq) + tv->tree.endPtr = 0; + FreeItem(delq); + delq = next; + } + + ckfree((ClientData)items); + TtkRedisplayWidget(&tv->core); + return TCL_OK; +} + +/* + $tv move $item $parent $index + * Move $item to the specified $index in $parent's child list. + */ +static int TreeviewMoveCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item, *parent; + TreeItem *sibling; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "item parent index"); + return TCL_ERROR; + } + if ( (item = FindItem(interp, tv, objv[2])) == 0 + || (parent = FindItem(interp, tv, objv[3])) == 0) + { + return TCL_ERROR; + } + + /* Locate previous sibling based on $index: + */ + if (!strcmp(Tcl_GetString(objv[4]), "end")) { + sibling = EndPosition(tv, parent); + } else { + TreeItem *p; + int index; + + if (Tcl_GetIntFromObj(interp, objv[4], &index) != TCL_OK) { + return TCL_ERROR; + } + + sibling = 0; + for (p = parent->children; p != NULL && index > 0; p = p->next) { + if (p != item) { + --index; + } /* else -- moving node forward, count index+1 nodes */ + sibling = p; + } + } + + /* Check ancestry: + */ + if (!AncestryCheck(interp, tv, item, parent)) { + return TCL_ERROR; + } + + /* Moving an item after itself is a no-op: + */ + if (item == sibling) { + return TCL_OK; + } + + /* Move item: + */ + DetachItem(item); + InsertItem(parent, sibling, item); + + TtkRedisplayWidget(&tv->core); + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- scrolling + */ + +static int TreeviewXViewCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + return TtkScrollviewCommand(interp, objc, objv, tv->tree.xscrollHandle); +} + +static int TreeviewYViewCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + return TtkScrollviewCommand(interp, objc, objv, tv->tree.yscrollHandle); +} + +/* $tree see $item -- + * Ensure that $item is visible. + */ +static int TreeviewSeeCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item, *parent; + int rowNumber; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + if (!(item = FindItem(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + /* Make sure all ancestors are open: + */ + for (parent = item->parent; parent; parent = parent->parent) { + if (!(parent->state & TTK_STATE_OPEN)) { + parent->openObj = unshareObj(parent->openObj); + Tcl_SetBooleanObj(parent->openObj, 1); + parent->state |= TTK_STATE_OPEN; + TtkRedisplayWidget(&tv->core); + } + } + tv->tree.yscroll.total = CountRows(tv->tree.root) - 1; + + /* Make sure item is visible: + */ + rowNumber = RowNumber(tv, item); + if (rowNumber < tv->tree.yscroll.first) { + TtkScrollTo(tv->tree.yscrollHandle, rowNumber); + } else if (rowNumber >= tv->tree.yscroll.last) { + TtkScrollTo(tv->tree.yscrollHandle, + tv->tree.yscroll.first + (1+rowNumber - tv->tree.yscroll.last)); + } + + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- interactive column resize + */ + +/* + $tree drag $column $newX -- + * Set right edge of display column $column to x position $X + */ +static int TreeviewDragCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + int left = tv->tree.treeArea.x - tv->tree.xscroll.first; + int i = FirstColumn(tv); + TreeColumn *column; + int newx; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "column xposition"); + return TCL_ERROR; + } + + if ( (column = FindColumn(interp, tv, objv[2])) == 0 + || Tcl_GetIntFromObj(interp, objv[3], &newx) != TCL_OK) + { + return TCL_ERROR; + } + + for (;i < tv->tree.nDisplayColumns; ++i) { + TreeColumn *c = tv->tree.displayColumns[i]; + int right = left + c->width; + if (c == column) { + DragColumn(tv, i, newx - right); + /* ASSERT: SLACKINVARIANT */ + TtkRedisplayWidget(&tv->core); + return TCL_OK; + } + left = right; + } + + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "column ", Tcl_GetString(objv[2]), " is not displayed", + NULL); + return TCL_ERROR; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- focus and selection + */ + +/* + $tree focus ?item? + */ +static int TreeviewFocusCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + + if (objc == 2) { + if (tv->tree.focus) { + Tcl_SetObjResult(interp, ItemID(tv, tv->tree.focus)); + } + return TCL_OK; + } else if (objc == 3) { + TreeItem *newFocus = FindItem(interp, tv, objv[2]); + if (!newFocus) + return TCL_ERROR; + tv->tree.focus = newFocus; + TtkRedisplayWidget(&tv->core); + return TCL_OK; + } else { + Tcl_WrongNumArgs(interp, 2, objv, "?newFocus?"); + return TCL_ERROR; + } +} + +/* + $tree selection ?add|remove|set|toggle $items? + */ +static int TreeviewSelectionCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + enum { + SELECTION_SET, SELECTION_ADD, SELECTION_REMOVE, SELECTION_TOGGLE + }; + static const char *selopStrings[] = { + "set", "add", "remove", "toggle", NULL + }; + + Treeview *tv = recordPtr; + int selop, i; + TreeItem *item, **items; + + if (objc == 2) { + Tcl_Obj *result = Tcl_NewListObj(0,0); + for (item = tv->tree.root->children; item; item=NextPreorder(item)) { + if (item->state & TTK_STATE_SELECTED) + Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item)); + } + Tcl_SetObjResult(interp, result); + return TCL_OK; + } + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "?add|remove|set|toggle items?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[2], selopStrings, + "selection operation", 0, &selop) != TCL_OK) + { + return TCL_ERROR; + } + + items = GetItemListFromObj(interp, tv, objv[3]); + if (!items) { + return TCL_ERROR; + } + + switch (selop) + { + case SELECTION_SET: + for (item=tv->tree.root; item; item=NextPreorder(item)) { + item->state &= ~TTK_STATE_SELECTED; + } + /*FALLTHRU*/ + case SELECTION_ADD: + for (i=0; items[i]; ++i) { + items[i]->state |= TTK_STATE_SELECTED; + } + break; + case SELECTION_REMOVE: + for (i=0; items[i]; ++i) { + items[i]->state &= ~TTK_STATE_SELECTED; + } + break; + case SELECTION_TOGGLE: + for (i=0; items[i]; ++i) { + items[i]->state ^= TTK_STATE_SELECTED; + } + break; + } + + ckfree((ClientData)items); + TtkSendVirtualEvent(tv->core.tkwin, "TreeviewSelect"); + TtkRedisplayWidget(&tv->core); + + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- tags and bindings. + */ + +/* + $tv tag bind $tag ?$sequence ?$script?? + */ +static int TreeviewTagBindCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Ttk_TagTable tagTable = tv->tree.tagTable; + Tk_BindingTable bindingTable = tv->tree.bindingTable; + Ttk_Tag tag; + + if (objc < 4 || objc > 6) { + Tcl_WrongNumArgs(interp, 3, objv, "tagName ?sequence? ?script?"); + return TCL_ERROR; + } + + tag = Ttk_GetTagFromObj(tagTable, objv[3]); + if (!tag) { return TCL_ERROR; } + + if (objc == 4) { /* $tv tag bind $tag */ + Tk_GetAllBindings(interp, bindingTable, tag); + } else if (objc == 5) { /* $tv tag bind $tag $sequence */ + /* TODO: distinguish "no such binding" (OK) from "bad pattern" (ERROR) + */ + const char *script = Tk_GetBinding(interp, + bindingTable, tag, Tcl_GetString(objv[4])); + if (script != NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(script,-1)); + } + } else if (objc == 6) { /* $tv tag bind $tag $sequence $script */ + const char *sequence = Tcl_GetString(objv[4]); + const char *script = Tcl_GetString(objv[5]); + + if (!*script) { /* Delete existing binding */ + Tk_DeleteBinding(interp, bindingTable, tag, sequence); + } else { + unsigned long mask = Tk_CreateBinding(interp, + bindingTable, tag, sequence, script, 0); + + /* Test mask to make sure event is supported: + */ + if (mask & (~TreeviewBindEventMask)) { + Tk_DeleteBinding(interp, bindingTable, tag, sequence); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "unsupported event ", sequence, + "\nonly key, button, motion, and virtual events supported", + NULL); + return TCL_ERROR; + } + } + } + return TCL_OK; +} + +/* + $tv tag configure $tag ?-option ?value -option value...?? + */ +static int TreeviewTagConfigureCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Ttk_TagTable tagTable = tv->tree.tagTable; + Ttk_Tag tag; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 3, objv, "tagName ?-option ?value ...??"); + return TCL_ERROR; + } + + tag = Ttk_GetTagFromObj(tagTable, objv[3]); + + if (objc == 4) { + return Ttk_EnumerateTagOptions(interp, tagTable, tag); + } else if (objc == 5) { + Tcl_Obj *result = Ttk_TagOptionValue(interp, tagTable, tag, objv[4]); + if (result) { + Tcl_SetObjResult(interp, result); + return TCL_OK; + } /* else */ + return TCL_ERROR; + } + /* else */ + TtkRedisplayWidget(&tv->core); + return Ttk_ConfigureTag(interp, tagTable, tag, objc - 4, objv + 4); +} + +/* + $tv tag has $tag ?$item? + */ +static int TreeviewTagHasCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + + if (objc == 4) { /* Return list of all items with tag */ + Ttk_Tag tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]); + TreeItem *item = tv->tree.root; + Tcl_Obj *result = Tcl_NewListObj(0,0); + + while (item) { + if (Ttk_TagSetContains(item->tagset, tag)) { + Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item)); + } + item = NextPreorder(item); + } + + Tcl_SetObjResult(interp, result); + return TCL_OK; + } else if (objc == 5) { /* Test if item has specified tag */ + Ttk_Tag tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]); + TreeItem *item = FindItem(interp, tv, objv[4]); + if (!item) { + return TCL_ERROR; + } + Tcl_SetObjResult(interp, + Tcl_NewBooleanObj(Ttk_TagSetContains(item->tagset, tag))); + return TCL_OK; + } else { + Tcl_WrongNumArgs(interp, 3, objv, "tagName ?item?"); + return TCL_ERROR; + } +} + +/* + $tv tag names $tag + */ +static int TreeviewTagNamesCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, ""); + return TCL_ERROR; + } + + return Ttk_EnumerateTags(interp, tv->tree.tagTable); +} + +/* + $tv tag add $tag $items + */ +static void AddTag(TreeItem *item, Ttk_Tag tag) +{ + if (Ttk_TagSetAdd(item->tagset, tag)) { + if (item->tagsObj) Tcl_DecrRefCount(item->tagsObj); + item->tagsObj = Ttk_NewTagSetObj(item->tagset); + Tcl_IncrRefCount(item->tagsObj); + } +} + +static int TreeviewTagAddCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Ttk_Tag tag; + TreeItem **items; + int i; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "tagName items"); + return TCL_ERROR; + } + + tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]); + items = GetItemListFromObj(interp, tv, objv[4]); + + if (!items) { + return TCL_ERROR; + } + + for (i=0; items[i]; ++i) { + AddTag(items[i], tag); + } + + TtkRedisplayWidget(&tv->core); + + return TCL_OK; +} + +/* + $tv tag remove $tag ?$items? + */ +static void RemoveTag(TreeItem *item, Ttk_Tag tag) +{ + if (Ttk_TagSetRemove(item->tagset, tag)) { + if (item->tagsObj) Tcl_DecrRefCount(item->tagsObj); + item->tagsObj = Ttk_NewTagSetObj(item->tagset); + Tcl_IncrRefCount(item->tagsObj); + } +} + +static int TreeviewTagRemoveCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Ttk_Tag tag; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 3, objv, "tagName items"); + return TCL_ERROR; + } + + tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]); + + if (objc == 5) { + TreeItem **items = GetItemListFromObj(interp, tv, objv[4]); + int i; + + if (!items) { + return TCL_ERROR; + } + for (i=0; items[i]; ++i) { + RemoveTag(items[i], tag); + } + } else if (objc == 4) { + TreeItem *item = tv->tree.root; + while (item) { + RemoveTag(item, tag); + item=NextPreorder(item); + } + } + + TtkRedisplayWidget(&tv->core); + + return TCL_OK; +} + +static const Ttk_Ensemble TreeviewTagCommands[] = { + { "add", TreeviewTagAddCommand,0 }, + { "bind", TreeviewTagBindCommand,0 }, + { "configure", TreeviewTagConfigureCommand,0 }, + { "has", TreeviewTagHasCommand,0 }, + { "names", TreeviewTagNamesCommand,0 }, + { "remove", TreeviewTagRemoveCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget commands record. + */ +static const Ttk_Ensemble TreeviewCommands[] = { + { "bbox", TreeviewBBoxCommand,0 }, + { "children", TreeviewChildrenCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "column", TreeviewColumnCommand,0 }, + { "configure", TtkWidgetConfigureCommand,0 }, + { "delete", TreeviewDeleteCommand,0 }, + { "detach", TreeviewDetachCommand,0 }, + { "drag", TreeviewDragCommand,0 }, + { "exists", TreeviewExistsCommand,0 }, + { "focus", TreeviewFocusCommand,0 }, + { "heading", TreeviewHeadingCommand,0 }, + { "identify", TreeviewIdentifyCommand,0 }, + { "index", TreeviewIndexCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "insert", TreeviewInsertCommand,0 }, + { "item", TreeviewItemCommand,0 }, + { "move", TreeviewMoveCommand,0 }, + { "next", TreeviewNextCommand,0 }, + { "parent", TreeviewParentCommand,0 }, + { "prev", TreeviewPrevCommand,0 }, + { "see", TreeviewSeeCommand,0 }, + { "selection" , TreeviewSelectionCommand,0 }, + { "set", TreeviewSetCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "tag", 0,TreeviewTagCommands }, + { "xview", TreeviewXViewCommand,0 }, + { "yview", TreeviewYViewCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget definition. + */ + +static WidgetSpec TreeviewWidgetSpec = { + "Treeview", /* className */ + sizeof(Treeview), /* recordSize */ + TreeviewOptionSpecs, /* optionSpecs */ + TreeviewCommands, /* subcommands */ + TreeviewInitialize, /* initializeProc */ + TreeviewCleanup, /* cleanupProc */ + TreeviewConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + TreeviewGetLayout, /* getLayoutProc */ + TreeviewSize, /* sizeProc */ + TreeviewDoLayout, /* layoutProc */ + TreeviewDisplay /* displayProc */ +}; + +/*------------------------------------------------------------------------ + * +++ Layout specifications. + */ + +TTK_BEGIN_LAYOUT_TABLE(LayoutTable) + +TTK_LAYOUT("Treeview", + TTK_GROUP("Treeview.field", TTK_FILL_BOTH|TTK_BORDER, + TTK_GROUP("Treeview.padding", TTK_FILL_BOTH, + TTK_NODE("Treeview.treearea", TTK_FILL_BOTH)))) + +TTK_LAYOUT("Item", + TTK_GROUP("Treeitem.padding", TTK_FILL_BOTH, + TTK_NODE("Treeitem.indicator", TTK_PACK_LEFT) + TTK_NODE("Treeitem.image", TTK_PACK_LEFT) + TTK_GROUP("Treeitem.focus", TTK_PACK_LEFT, + TTK_NODE("Treeitem.text", TTK_PACK_LEFT)))) + +TTK_LAYOUT("Cell", + TTK_GROUP("Treedata.padding", TTK_FILL_BOTH, + TTK_NODE("Treeitem.text", TTK_FILL_BOTH))) + +TTK_LAYOUT("Heading", + TTK_NODE("Treeheading.cell", TTK_FILL_BOTH) + TTK_GROUP("Treeheading.border", TTK_FILL_BOTH, + TTK_GROUP("Treeheading.padding", TTK_FILL_BOTH, + TTK_NODE("Treeheading.image", TTK_PACK_RIGHT) + TTK_NODE("Treeheading.text", TTK_FILL_X)))) + +TTK_LAYOUT("Row", + TTK_NODE("Treeitem.row", TTK_FILL_BOTH)) + +TTK_END_LAYOUT_TABLE + +/*------------------------------------------------------------------------ + * +++ Tree indicator element. + */ + +typedef struct { + Tcl_Obj *colorObj; + Tcl_Obj *sizeObj; + Tcl_Obj *marginsObj; +} TreeitemIndicator; + +static Ttk_ElementOptionSpec TreeitemIndicatorOptions[] = { + { "-foreground", TK_OPTION_COLOR, + Tk_Offset(TreeitemIndicator,colorObj), DEFAULT_FOREGROUND }, + { "-indicatorsize", TK_OPTION_PIXELS, + Tk_Offset(TreeitemIndicator,sizeObj), "12" }, + { "-indicatormargins", TK_OPTION_STRING, + Tk_Offset(TreeitemIndicator,marginsObj), "2 2 4 2" }, + { NULL, 0, 0, NULL } +}; + +static void TreeitemIndicatorSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TreeitemIndicator *indicator = elementRecord; + Ttk_Padding margins; + int size = 0; + + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginsObj, &margins); + Tk_GetPixelsFromObj(NULL, tkwin, indicator->sizeObj, &size); + + *widthPtr = size + Ttk_PaddingWidth(margins); + *heightPtr = size + Ttk_PaddingHeight(margins); +} + +static void TreeitemIndicatorDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + TreeitemIndicator *indicator = elementRecord; + ArrowDirection direction = + (state & TTK_STATE_OPEN) ? ARROW_DOWN : ARROW_RIGHT; + Ttk_Padding margins; + XColor *borderColor = Tk_GetColorFromObj(tkwin, indicator->colorObj); + XGCValues gcvalues; GC gc; unsigned mask; + + if (state & TTK_STATE_LEAF) /* don't draw anything */ + return; + + Ttk_GetPaddingFromObj(NULL,tkwin,indicator->marginsObj,&margins); + b = Ttk_PadBox(b, margins); + + gcvalues.foreground = borderColor->pixel; + gcvalues.line_width = 1; + mask = GCForeground | GCLineWidth; + gc = Tk_GetGC(tkwin, mask, &gcvalues); + + TtkDrawArrow(Tk_Display(tkwin), d, gc, b, direction); + + Tk_FreeGC(Tk_Display(tkwin), gc); +} + +static Ttk_ElementSpec TreeitemIndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TreeitemIndicator), + TreeitemIndicatorOptions, + TreeitemIndicatorSize, + TreeitemIndicatorDraw +}; + +/*------------------------------------------------------------------------ + * +++ Row element. + */ + +typedef struct { + Tcl_Obj *backgroundObj; + Tcl_Obj *rowNumberObj; +} RowElement; + +static Ttk_ElementOptionSpec RowElementOptions[] = { + { "-background", TK_OPTION_COLOR, + Tk_Offset(RowElement,backgroundObj), DEFAULT_BACKGROUND }, + { "-rownumber", TK_OPTION_INT, + Tk_Offset(RowElement,rowNumberObj), "0" }, + { NULL, 0, 0, NULL } +}; + +static void RowElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + RowElement *row = elementRecord; + XColor *color = Tk_GetColorFromObj(tkwin, row->backgroundObj); + GC gc = Tk_GCForColor(color, d); + XFillRectangle(Tk_Display(tkwin), d, gc, + b.x, b.y, b.width, b.height); +} + +static Ttk_ElementSpec RowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(RowElement), + RowElementOptions, + TtkNullElementSize, + RowElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Initialisation. + */ + +MODULE_SCOPE +void TtkTreeview_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + RegisterWidget(interp, "ttk::treeview", &TreeviewWidgetSpec); + + Ttk_RegisterElement(interp, theme, "Treeitem.indicator", + &TreeitemIndicatorElementSpec, 0); + Ttk_RegisterElement(interp, theme, "Treeitem.row", &RowElementSpec, 0); + Ttk_RegisterElement(interp, theme, "Treeheading.cell", &RowElementSpec, 0); + Ttk_RegisterElement(interp, theme, "treearea", &ttkNullElementSpec, 0); + + Ttk_RegisterLayouts(theme, LayoutTable); +} + +/*EOF*/ |