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