/*
* Copyright (c) 2004, Joe English
*
* ttk::treeview widget implementation.
*/
#include "tkInt.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 const Tk_OptionSpec ItemOptionSpecs[] = {
{TK_OPTION_STRING, "-text", "text", "Text",
"", offsetof(TreeItem,textObj), TCL_INDEX_NONE,
0,0,0 },
{TK_OPTION_STRING, "-image", "image", "Image",
NULL, offsetof(TreeItem,imageObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,ITEM_OPTION_IMAGE_CHANGED },
{TK_OPTION_STRING, "-values", "values", "Values",
NULL, offsetof(TreeItem,valuesObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,0 },
{TK_OPTION_BOOLEAN, "-open", "open", "Open",
"0", offsetof(TreeItem,openObj), TCL_INDEX_NONE,
0,0,0 },
{TK_OPTION_STRING, "-tags", "tags", "Tags",
NULL, offsetof(TreeItem,tagsObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,ITEM_OPTION_TAGS_CHANGED },
{TK_OPTION_END, 0,0,0, NULL, TCL_INDEX_NONE,TCL_INDEX_NONE, 0,0,0}
};
/* Forward declaration */
static void RemoveTag(TreeItem *, Ttk_Tag);
/* + 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(item);
}
static void FreeItemCB(void *clientData) { FreeItem((TreeItem *)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 const Tk_OptionSpec TagOptionSpecs[] = {
{TK_OPTION_STRING, "-text", "text", "Text",
NULL, offsetof(DisplayItem,textObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,0 },
{TK_OPTION_STRING, "-image", "image", "Image",
NULL, offsetof(DisplayItem,imageObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,0 },
{TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
NULL, offsetof(DisplayItem,anchorObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK, 0, GEOMETRY_CHANGED}, /* <<NOTE-ANCHOR>> */
{TK_OPTION_COLOR, "-background", "windowColor", "WindowColor",
NULL, offsetof(DisplayItem,backgroundObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,0 },
{TK_OPTION_COLOR, "-foreground", "textColor", "TextColor",
NULL, offsetof(DisplayItem,foregroundObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,0 },
{TK_OPTION_FONT, "-font", "font", "Font",
NULL, offsetof(DisplayItem,fontObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
{TK_OPTION_END, 0,0,0, NULL, TCL_INDEX_NONE,TCL_INDEX_NONE, 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 const Tk_OptionSpec ColumnOptionSpecs[] = {
{TK_OPTION_INT, "-width", "width", "Width",
DEF_COLWIDTH, TCL_INDEX_NONE, offsetof(TreeColumn,width),
0,0,GEOMETRY_CHANGED },
{TK_OPTION_INT, "-minwidth", "minWidth", "MinWidth",
DEF_MINWIDTH, TCL_INDEX_NONE, offsetof(TreeColumn,minWidth),
0,0,0 },
{TK_OPTION_BOOLEAN, "-stretch", "stretch", "Stretch",
"1", TCL_INDEX_NONE, offsetof(TreeColumn,stretch),
0,0,GEOMETRY_CHANGED },
{TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
"w", offsetof(TreeColumn,anchorObj), TCL_INDEX_NONE, /* <<NOTE-ANCHOR>> */
0,0,0 },
{TK_OPTION_STRING, "-id", "id", "ID",
NULL, offsetof(TreeColumn,idObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,READONLY_OPTION },
{TK_OPTION_END, 0,0,0, NULL, TCL_INDEX_NONE,TCL_INDEX_NONE, 0,0,0}
};
static const Tk_OptionSpec HeadingOptionSpecs[] = {
{TK_OPTION_STRING, "-text", "text", "Text",
"", offsetof(TreeColumn,headingObj), TCL_INDEX_NONE,
0,0,0 },
{TK_OPTION_STRING, "-image", "image", "Image",
"", offsetof(TreeColumn,headingImageObj), TCL_INDEX_NONE,
0,0,0 },
{TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
"center", offsetof(TreeColumn,headingAnchorObj), TCL_INDEX_NONE,
0,0,0 },
{TK_OPTION_STRING, "-command", "", "",
"", offsetof(TreeColumn,headingCommandObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,0 },
{TK_OPTION_STRING, "state", "", "",
"", offsetof(TreeColumn,headingStateObj), TCL_INDEX_NONE,
0,0,STATE_CHANGED },
{TK_OPTION_END, 0,0,0, NULL, TCL_INDEX_NONE,TCL_INDEX_NONE, 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 *const showStrings[] = {
"tree", "headings", NULL
};
static int GetEnumSetFromObj(
Tcl_Interp *interp,
Tcl_Obj *objPtr,
const char *const 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_GetIndexFromObjStruct(interp, objv[i], table,
sizeof(char *), "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 *const SelectModeStrings[] = { "none", "browse", "extended", NULL };
static const Tk_OptionSpec TreeviewOptionSpecs[] = {
{TK_OPTION_STRING, "-columns", "columns", "Columns",
"", offsetof(Treeview,tree.columnsObj), TCL_INDEX_NONE,
0,0,COLUMNS_CHANGED | GEOMETRY_CHANGED /*| READONLY_OPTION*/ },
{TK_OPTION_STRING, "-displaycolumns","displayColumns","DisplayColumns",
"#all", offsetof(Treeview,tree.displayColumnsObj), TCL_INDEX_NONE,
0,0,DCOLUMNS_CHANGED | GEOMETRY_CHANGED },
{TK_OPTION_STRING, "-show", "show", "Show",
DEFAULT_SHOW, offsetof(Treeview,tree.showObj), TCL_INDEX_NONE,
0,0,SHOW_CHANGED | GEOMETRY_CHANGED },
{TK_OPTION_STRING_TABLE, "-selectmode", "selectMode", "SelectMode",
"extended", offsetof(Treeview,tree.selectModeObj), TCL_INDEX_NONE,
0,(ClientData)SelectModeStrings,0 },
{TK_OPTION_PIXELS, "-height", "height", "Height",
DEF_TREE_ROWS, offsetof(Treeview,tree.heightObj), TCL_INDEX_NONE,
0,0,GEOMETRY_CHANGED},
{TK_OPTION_STRING, "-padding", "padding", "Pad",
NULL, offsetof(Treeview,tree.paddingObj), TCL_INDEX_NONE,
TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
{TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
NULL, TCL_INDEX_NONE, offsetof(Treeview, tree.xscroll.scrollCmd),
TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED},
{TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
NULL, TCL_INDEX_NONE, offsetof(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 (TreeColumn *)Tcl_GetHashValue(entryPtr);
}
/* Check for number:
*/
if (Tcl_GetIntFromObj(NULL, columnIDObj, &columnIndex) == TCL_OK) {
if (columnIndex < 0 || columnIndex >= tv->tree.nColumns) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"Column index %s out of bounds",
Tcl_GetString(columnIDObj)));
Tcl_SetErrorCode(interp, "TTK", "TREE", "COLBOUND", NULL);
return NULL;
}
return tv->tree.columns + columnIndex;
}
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"Invalid column index %s", Tcl_GetString(columnIDObj)));
Tcl_SetErrorCode(interp, "TTK", "TREE", "COLUMN", 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_SetObjResult(interp, Tcl_ObjPrintf(
"Column %s out of range", Tcl_GetString(columnIDObj)));
Tcl_SetErrorCode(interp, "TTK", "TREE", "COLUMN", 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_SetObjResult(interp, Tcl_ObjPrintf(
"Item %s not found", itemName));
Tcl_SetErrorCode(interp, "TTK", "TREE", "ITEM", NULL);
return 0;
}
return (TreeItem *)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(items);
return NULL;
}
}
items[i] = NULL;
return items;
}
/* + ItemName --
* Returns the item's ID.
*/
static const char *ItemName(Treeview *tv, TreeItem *item)
{
return (const char *)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(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(displayColumns);
return TCL_ERROR;
}
}
}
displayColumns[0] = &tv->tree.column0;
if (tv->tree.displayColumns)
ckfree(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 long TreeviewBindEventMask =
KeyPressMask|KeyReleaseMask
| ButtonPressMask|ButtonReleaseMask
| PointerMotionMask|ButtonMotionMask
| VirtualEventMask
;
static void TreeviewBindEventProc(void *clientData, XEvent *event)
{
Treeview *tv = (Treeview *)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 = (Treeview *)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 = (Treeview *)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)
ckfree((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 = (Treeview *)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, 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, column,
tv->tree.columnOptionTable, objc, objv, tv->core.tkwin,
&savedOptions,&mask) != TCL_OK)
{
return TCL_ERROR;
}
if (mask & READONLY_OPTION) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"Attempt to change read-only option", -1));
Tcl_SetErrorCode(interp, "TTK", "TREE", "READONLY", 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);
} else {
RecomputeSlack(tv);
ResizeColumns(tv, TreeWidth(tv));
}
}
TtkRedisplayWidget(&tv->core);
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, 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 *const 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 = (Treeview *)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 = (Treeview *)clientData;
int visibleRows;
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);
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 = (Treeview *)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 = (Treeview *)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_SetObjResult(interp, Tcl_ObjPrintf(
"Cannot insert %s as descendant of %s",
ItemName(tv, item), ItemName(tv, parent)));
Tcl_SetErrorCode(interp, "TTK", "TREE", "ANCESTRY", 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 = (Treeview *)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(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(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 = (Treeview *)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 = (Treeview *)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 = (Treeview *)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 = (Treeview *)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 = (Treeview *)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_NewWideIntObj(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 = (Treeview *)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;
(void)objc;
/* 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);
if (item->textObj) { displayItem.textObj = item->textObj; }
if (item->imageObj) { displayItem.imageObj = item->imageObj; }
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 *const submethodStrings[] =
{ "region", "item", "column", "row", "element", NULL };
enum { I_REGION, I_ITEM, I_COLUMN, I_ROW, I_ELEMENT };
Treeview *tv = (Treeview *)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_GetIndexFromObjStruct(interp, objv[2], submethodStrings,
sizeof(char *), "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) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf("#%d", colno));
}
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);
if (item->textObj) { displayItem.textObj = item->textObj; }
if (item->imageObj) { displayItem.imageObj = item->imageObj; }
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 = (Treeview *)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 = (Treeview *)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 = (Treeview *)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 = (Treeview *)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(NULL, result,
tv->tree.columns[columnNumber].idObj);
Tcl_ListObjAppendElement(NULL, 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_SetObjResult(interp, Tcl_NewStringObj(
"Display column #0 cannot be set", -1));
Tcl_SetErrorCode(interp, "TTK", "TREE", "COLUMN_0", 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 = (Treeview *)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_SetObjResult(interp, Tcl_ObjPrintf(
"Item %s already exists", itemName));
Tcl_SetErrorCode(interp, "TTK", "TREE", "ITEM_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 = (Treeview *)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_SetObjResult(interp, Tcl_NewStringObj(
"Cannot detach root item", -1));
Tcl_SetErrorCode(interp, "TTK", "TREE", "ROOT", NULL);
ckfree(items);
return TCL_ERROR;
}
}
for (i = 0; items[i]; ++i) {
DetachItem(items[i]);
}
TtkRedisplayWidget(&tv->core);
ckfree(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 = (Treeview *)recordPtr;
TreeItem **items, *delq;
int i, selItemDeleted = 0;
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(items);
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"Cannot delete root item", -1));
Tcl_SetErrorCode(interp, "TTK", "TREE", "ROOT", NULL);
return TCL_ERROR;
}
}
/* Remove items from hash table.
*/
delq = 0;
for (i=0; items[i]; ++i) {
if (items[i]->state & TTK_STATE_SELECTED) {
selItemDeleted = 1;
}
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(items);
if (selItemDeleted) {
TtkSendVirtualEvent(tv->core.tkwin, "TreeviewSelect");
}
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 = (Treeview *)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 = (Treeview *)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 = (Treeview *)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 = (Treeview *)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);
}
}
/* Make sure item is visible:
*/
rowNumber = RowNumber(tv, item);
if (rowNumber < tv->tree.yscroll.first) {
TtkScrollTo(tv->tree.yscrollHandle, rowNumber, 1);
} else if (rowNumber >= tv->tree.yscroll.last) {
TtkScrollTo(tv->tree.yscrollHandle,
tv->tree.yscroll.first + (1+rowNumber - tv->tree.yscroll.last), 1);
}
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 = (Treeview *)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);
TtkRedisplayWidget(&tv->core);
return TCL_OK;
}
left = right;
}
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"column %s is not displayed", Tcl_GetString(objv[2])));
Tcl_SetErrorCode(interp, "TTK", "TREE", "COLUMN_INVISIBLE", NULL);
return TCL_ERROR;
}
static int TreeviewDropCommand(
void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
Treeview *tv = (Treeview *)recordPtr;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "drop");
return TCL_ERROR;
}
ResizeColumns(tv, TreeWidth(tv));
TtkRedisplayWidget(&tv->core);
return TCL_OK;
}
/*------------------------------------------------------------------------
* +++ Widget commands -- focus and selection
*/
/* + $tree focus ?item?
*/
static int TreeviewFocusCommand(
void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
Treeview *tv = (Treeview *)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 *const selopStrings[] = {
"set", "add", "remove", "toggle", NULL
};
Treeview *tv = (Treeview *)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_GetIndexFromObjStruct(interp, objv[2], selopStrings,
sizeof(char *), "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(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 = (Treeview *)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_SetObjResult(interp, Tcl_ObjPrintf(
"unsupported event %s\nonly key, button, motion, and"
" virtual events supported", sequence));
Tcl_SetErrorCode(interp, "TTK", "TREE", "BIND_EVENTS", 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 = (Treeview *)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 delete $tag
*/
static int TreeviewTagDeleteCommand(
void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
Treeview *tv = (Treeview *)recordPtr;
Ttk_TagTable tagTable = tv->tree.tagTable;
TreeItem *item = tv->tree.root;
Ttk_Tag tag;
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "tagName");
return TCL_ERROR;
}
tag = Ttk_GetTagFromObj(tagTable, objv[3]);
/* remove the tag from all items */
while (item) {
RemoveTag(item, tag);
item = NextPreorder(item);
}
/* then remove the tag from the tag table */
Ttk_DeleteTagFromTable(tagTable, tag);
TtkRedisplayWidget(&tv->core);
return TCL_OK;
}
/* + $tv tag has $tag ?$item?
*/
static int TreeviewTagHasCommand(
void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
Treeview *tv = (Treeview *)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_NewWideIntObj(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 = (Treeview *)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 = (Treeview *)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 = (Treeview *)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 },
{ "delete", TreeviewTagDeleteCommand,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 },
{ "drop", TreeviewDropCommand,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 const 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 const Ttk_ElementOptionSpec TreeitemIndicatorOptions[] = {
{ "-foreground", TK_OPTION_COLOR,
offsetof(TreeitemIndicator,colorObj), DEFAULT_FOREGROUND },
{ "-indicatorsize", TK_OPTION_PIXELS,
offsetof(TreeitemIndicator,sizeObj), "12" },
{ "-indicatormargins", TK_OPTION_STRING,
offsetof(TreeitemIndicator,marginsObj), "2 2 4 2" },
{ NULL, TK_OPTION_BOOLEAN, 0, NULL }
};
static void TreeitemIndicatorSize(
void *dummy, void *elementRecord, Tk_Window tkwin,
int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
{
TreeitemIndicator *indicator = (TreeitemIndicator *)elementRecord;
Ttk_Padding margins;
int size = 0;
(void)dummy;
(void)paddingPtr;
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 *dummy, void *elementRecord, Tk_Window tkwin,
Drawable d, Ttk_Box b, Ttk_State state)
{
TreeitemIndicator *indicator = (TreeitemIndicator *)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;
(void)dummy;
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 const Ttk_ElementSpec TreeitemIndicatorElementSpec = {
TK_STYLE_VERSION_2,
sizeof(TreeitemIndicator),
TreeitemIndicatorOptions,
TreeitemIndicatorSize,
TreeitemIndicatorDraw
};
/*------------------------------------------------------------------------
* +++ Row element.
*/
typedef struct {
Tcl_Obj *backgroundObj;
Tcl_Obj *rowNumberObj;
} RowElement;
static const Ttk_ElementOptionSpec RowElementOptions[] = {
{ "-background", TK_OPTION_COLOR,
offsetof(RowElement,backgroundObj), DEFAULT_BACKGROUND },
{ "-rownumber", TK_OPTION_INT,
offsetof(RowElement,rowNumberObj), "0" },
{ NULL, TK_OPTION_BOOLEAN, 0, NULL }
};
static void RowElementDraw(
void *dummy, void *elementRecord, Tk_Window tkwin,
Drawable d, Ttk_Box b, Ttk_State state)
{
RowElement *row = (RowElement *)elementRecord;
XColor *color = Tk_GetColorFromObj(tkwin, row->backgroundObj);
GC gc = Tk_GCForColor(color, d);
(void)dummy;
(void)state;
XFillRectangle(Tk_Display(tkwin), d, gc,
b.x, b.y, b.width, b.height);
}
static const 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*/