1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 module dfl.treeview; 5 import core.sys.windows.windows; 6 import core.sys.windows.commctrl; 7 8 import dfl.exception; 9 import dfl.internal.dlib; 10 import dfl.control; 11 import dfl.application; 12 import dfl.base; 13 import dfl.event; 14 import dfl.drawing; 15 import dfl.collections; 16 import dfl.internal.utf; 17 18 version (DFL_NO_IMAGELIST) { 19 } else { 20 private import dfl.imagelist; 21 } 22 23 private extern (Windows) void _initTreeview(); 24 25 enum TreeViewAction : ubyte { 26 UNKNOWN, 27 COLLAPSE, 28 EXPAND, 29 BY_KEYBOARD, 30 BY_MOUSE, 31 } 32 33 class TreeViewCancelEventArgs : CancelEventArgs { 34 this(TreeNode node, bool cancel, TreeViewAction action) { 35 super(cancel); 36 37 _node = node; 38 _action = action; 39 } 40 41 final @property TreeViewAction action() { 42 return _action; 43 } 44 45 final @property TreeNode node() { 46 return _node; 47 } 48 49 private: 50 TreeNode _node; 51 TreeViewAction _action; 52 } 53 54 class TreeViewEventArgs : EventArgs { 55 this(TreeNode node, TreeViewAction action) { 56 _node = node; 57 _action = action; 58 } 59 60 this(TreeNode node) { 61 _node = node; 62 //_action = TreeViewAction.UNKNOWN; 63 } 64 65 final @property TreeViewAction action() { 66 return _action; 67 } 68 69 final @property TreeNode node() { 70 return _node; 71 } 72 73 private: 74 TreeNode _node; 75 TreeViewAction _action = TreeViewAction.UNKNOWN; 76 } 77 78 class NodeLabelEditEventArgs : EventArgs { 79 this(TreeNode node, Dstring label) { 80 _node = node; 81 _label = label; 82 } 83 84 this(TreeNode node) { 85 _node = node; 86 } 87 88 final @property TreeNode node() { 89 return _node; 90 } 91 92 final @property Dstring label() { 93 return _label; 94 } 95 96 final @property void cancelEdit(bool byes) { 97 _cancel = byes; 98 } 99 100 final @property bool cancelEdit() { 101 return _cancel; 102 } 103 104 private: 105 TreeNode _node; 106 Dstring _label; 107 bool _cancel = false; 108 } 109 110 class TreeNode : DObject { 111 this(Dstring labelText) { 112 this(); 113 114 ttext = labelText; 115 } 116 117 this(Dstring labelText, TreeNode[] children) { 118 this(); 119 120 ttext = labelText; 121 tchildren.addRange(children); 122 } 123 124 this() { 125 Application.ppin(cast(void*) this); 126 127 /* 128 bcolor = Color.empty; 129 fcolor = Color.empty; 130 */ 131 132 tchildren = new TreeNodeCollection(tview, this); 133 } 134 135 this(Object val) { // package 136 this(getObjectString(val)); 137 } 138 139 //final @property void backColor(Color c) { 140 //if(created) { 141 //COLORREF cref; 142 //if(Color.empty == c) { 143 //cref = CLR_NONE; 144 //} else { 145 //cref = c.toRgb(); 146 //} 147 ////SendMessageA(tview.hwnd, TVM_SETBKCOLOR, 0, cast(LPARAM)cref); 148 //prevwproc(LVM_SETBKCOLOR, 0, cast(LPARAM)cref); 149 //prevwproc(LVM_SETTEXTBKCOLOR, 0, cast(LPARAM)cref); 150 //} 151 //super.backColor = c; 152 //} 153 154 //final @property Color backColor() { 155 //if(Color.empty == backc) 156 //return defaultBackColor; 157 //return backc; 158 //} 159 160 /* 161 final @property void backColor(Color c) 162 { 163 bcolor = c; 164 } 165 166 final @property Color backColor() 167 { 168 return bcolor; 169 } 170 */ 171 172 final @property Rect bounds() { 173 Rect result; 174 175 if (created) { 176 RECT rect; 177 *(cast(HTREEITEM*)&rect) = hnode; 178 if (SendMessageA(tview.handle, TVM_GETITEMRECT, FALSE, cast(LPARAM)&rect)) { 179 result = Rect(&rect); 180 } 181 } 182 183 return result; 184 } 185 186 final @property TreeNode firstNode() { 187 if (tchildren.length) { 188 return tchildren._nodes[0]; 189 } 190 return null; 191 } 192 193 /* 194 final @property void foreColor(Color c) 195 { 196 fcolor = c; 197 } 198 199 final @property Color foreColor() 200 { 201 return fcolor; 202 } 203 */ 204 205 // Path from the root to this node. 206 final @property Dstring fullPath() { 207 if (!tparent) { 208 return ttext; 209 } 210 211 // Might want to manually loop through parents and preallocate the whole buffer. 212 assert(tview !is null); 213 dchar sep; 214 sep = tview.pathSeparator; 215 //return std.string.format("%s%s%s", tparent.fullPath, sep, ttext); 216 char[4] ssep; 217 int sseplen = 0; 218 foreach (char ch; (&sep)[0 .. 1]) { 219 ssep[sseplen++] = ch; 220 } 221 //return tparent.fullPath ~ ssep[0 .. sseplen] ~ ttext; 222 return tparent.fullPath ~ cast(Dstring) ssep[0 .. sseplen] ~ ttext; // Needed in D2. 223 } 224 225 final @property HTREEITEM handle() { 226 return hnode; 227 } 228 229 // Index of this node in the parent node. 230 final @property int index() { 231 int result = -1; 232 if (tparent) { 233 result = tparent.tchildren.indexOf(this); 234 assert(result != -1); 235 } 236 return result; 237 } 238 239 /* 240 final @property bool isEditing() 241 { 242 } 243 */ 244 245 final @property bool isExpanded() { 246 return isState(TVIS_EXPANDED); 247 } 248 249 final @property bool isSelected() { 250 return isState(TVIS_SELECTED); 251 } 252 253 /* 254 final @property bool isVisible() 255 { 256 } 257 */ 258 259 final @property TreeNode lastNode() { 260 if (tchildren.length) { 261 return tchildren._nodes[tchildren.length - 1]; 262 } 263 return null; 264 } 265 266 // Next sibling node. 267 final @property TreeNode nextNode() { 268 if (tparent) { 269 int i; 270 i = tparent.tchildren.indexOf(this); 271 assert(i != -1); 272 273 i++; 274 if (i != tparent.tchildren.length) { 275 return tparent.tchildren._nodes[i]; 276 } 277 } 278 return null; 279 } 280 281 /* 282 283 final @property void nodeFont(Font f) 284 { 285 tfont = f; 286 } 287 288 289 final @property Font nodeFont() 290 { 291 return tfont; 292 } 293 */ 294 295 final @property TreeNodeCollection nodes() { 296 return tchildren; 297 } 298 299 final @property TreeNode parent() { 300 return tparent; 301 } 302 303 // Previous sibling node. 304 final @property TreeNode prevNode() { 305 if (tparent) { 306 int i; 307 i = tparent.tchildren.indexOf(this); 308 assert(i != -1); 309 310 if (i) { 311 i--; 312 return tparent.tchildren._nodes[i]; 313 } 314 } 315 return null; 316 } 317 318 final @property void tag(Object o) { 319 ttag = o; 320 } 321 322 final @property Object tag() { 323 return ttag; 324 } 325 326 final @property void text(Dstring newText) { 327 ttext = newText; 328 329 if (created) { 330 TV_ITEMA item; 331 Message m; 332 333 item.mask = TVIF_HANDLE | TVIF_TEXT; 334 item.hItem = hnode; 335 /* 336 item.pszText = stringToStringz(ttext); 337 //item.cchTextMax = ttext.length; // ? 338 m = Message(tview.handle, TVM_SETITEMA, 0, cast(LPARAM)&item); 339 */ 340 if (dfl.internal.utf.useUnicode) { 341 item.pszText = cast(typeof(item.pszText)) dfl.internal.utf.toUnicodez(ttext); 342 m = Message(tview.handle, TVM_SETITEMW, 0, cast(LPARAM)&item); 343 } else { 344 item.pszText = cast(typeof(item.pszText)) dfl.internal.utf.unsafeAnsiz(ttext); 345 m = Message(tview.handle, TVM_SETITEMA, 0, cast(LPARAM)&item); 346 } 347 tview.prevWndProc(m); 348 } 349 } 350 351 final @property Dstring text() { 352 return ttext; 353 } 354 355 // Get the TreeView control this node belongs to. 356 final @property TreeView treeView() { 357 return tview; 358 } 359 360 final void beginEdit() { 361 if (created) { 362 SetFocus(tview.hwnd); // Needs to have focus. 363 HWND hwEdit; 364 hwEdit = cast(HWND) SendMessageA(tview.hwnd, TVM_EDITLABELA, 0, cast(LPARAM) hnode); 365 if (!hwEdit) { 366 goto err_edit; 367 } 368 } else { 369 err_edit: 370 throw new DflException("Unable to edit TreeNode"); 371 } 372 } 373 374 /* 375 376 final void endEdit(bool cancel) 377 { 378 // ? 379 } 380 */ 381 382 final void ensureVisible() { 383 if (created) { 384 SendMessageA(tview.hwnd, TVM_ENSUREVISIBLE, 0, cast(LPARAM) hnode); 385 } 386 } 387 388 final void collapse() { 389 if (created) { 390 SendMessageA(tview.hwnd, TVM_EXPAND, TVE_COLLAPSE, cast(LPARAM) hnode); 391 } 392 } 393 394 final void expand() { 395 if (created) { 396 SendMessageA(tview.hwnd, TVM_EXPAND, TVE_EXPAND, cast(LPARAM) hnode); 397 } 398 } 399 400 final void expandAll() { 401 if (created) { 402 SendMessageA(tview.hwnd, TVM_EXPAND, TVE_EXPAND, cast(LPARAM) hnode); 403 404 foreach (TreeNode node; tchildren._nodes) { 405 node.expandAll(); 406 } 407 } 408 } 409 410 static TreeNode fromHandle(TreeView tree, HTREEITEM handle) { 411 return tree.treeNodeFromHandle(handle); 412 } 413 414 final void remove() { 415 if (tparent) { 416 tparent.tchildren.remove(this); 417 } else if (tview) { // It's a top level node. 418 tview.tchildren.remove(this); 419 } 420 } 421 422 final void toggle() { 423 if (created) { 424 SendMessageA(tview.hwnd, TVM_EXPAND, TVE_TOGGLE, cast(LPARAM) hnode); 425 } 426 } 427 428 version (DFL_NO_IMAGELIST) { 429 } else { 430 final @property void imageIndex(int index) { 431 this._imgidx = index; 432 433 if (created) { 434 TV_ITEMA item; 435 Message m; 436 m = Message(tview.handle, TVM_SETITEMA, 0, cast(LPARAM)&item); 437 438 item.mask = TVIF_HANDLE | TVIF_IMAGE; 439 item.hItem = hnode; 440 item.iImage = _imgidx; 441 if (tview._selimgidx < 0) { 442 item.mask |= TVIF_SELECTEDIMAGE; 443 item.iSelectedImage = _imgidx; 444 } 445 tview.prevWndProc(m); 446 } 447 } 448 449 final @property int imageIndex() { 450 return _imgidx; 451 } 452 } 453 454 override Dstring toString() { 455 return ttext; 456 } 457 458 override Dequ opEquals(Object o) { 459 return 0 == stringICmp(ttext, getObjectString(o)); // ? 460 } 461 462 Dequ opEquals(TreeNode node) { 463 return 0 == stringICmp(ttext, node.ttext); 464 } 465 466 Dequ opEquals(Dstring val) { 467 return 0 == stringICmp(ttext, val); 468 } 469 470 override int opCmp(Object o) { 471 return stringICmp(ttext, getObjectString(o)); // ? 472 } 473 474 int opCmp(TreeNode node) { 475 return stringICmp(ttext, node.ttext); 476 } 477 478 int opCmp(Dstring val) { 479 return stringICmp(text, val); 480 } 481 482 private: 483 Dstring ttext; 484 TreeNode tparent; 485 TreeNodeCollection tchildren; 486 Object ttag; 487 HTREEITEM hnode; 488 TreeView tview; 489 version (DFL_NO_IMAGELIST) { 490 } else { 491 int _imgidx = -1; 492 } 493 /* 494 Color bcolor, fcolor; 495 Font tfont; 496 */ 497 498 package final @property bool created() { 499 if (tview && tview.created()) { 500 assert(hnode); 501 return true; 502 } 503 return false; 504 } 505 506 bool isState(UINT state) { 507 if (created) { 508 TV_ITEMA ti; 509 ti.mask = TVIF_HANDLE | TVIF_STATE; 510 ti.hItem = hnode; 511 ti.stateMask = state; 512 if (SendMessageA(tview.handle, TVM_GETITEMA, 0, cast(LPARAM)&ti)) { 513 if (ti.state & state) { 514 return true; 515 } 516 } 517 } 518 return false; 519 } 520 521 void _reset() { 522 hnode = null; 523 tview = null; 524 tparent = null; 525 } 526 } 527 528 class TreeNodeCollection { 529 void add(TreeNode node) { 530 //cprintf("Adding node %p '%.*s'\n", cast(void*)node, getObjectString(node)); 531 532 int i; 533 534 if (tview && tview.sorted()) { 535 // Insertion sort. 536 537 for (i = 0; i != _nodes.length; i++) { 538 if (node < _nodes[i]) { 539 break; 540 } 541 } 542 } else { 543 i = _nodes.length; 544 } 545 546 insert(i, node); 547 } 548 549 void add(Dstring text) { 550 return add(new TreeNode(text)); 551 } 552 553 void add(Object val) { 554 return add(new TreeNode(getObjectString(val))); // ? 555 } 556 557 void addRange(Object[] range) { 558 foreach (Object o; range) { 559 add(o); 560 } 561 } 562 563 void addRange(TreeNode[] range) { 564 foreach (TreeNode node; range) { 565 add(node); 566 } 567 } 568 569 void addRange(Dstring[] range) { 570 foreach (Dstring s; range) { 571 add(s); 572 } 573 } 574 575 // Like clear but doesn't bother removing stuff from the lists. 576 // Used when a parent is being removed and the children only 577 // need to be reset. 578 private void _reset() { 579 foreach (TreeNode node; _nodes) { 580 node._reset(); 581 } 582 } 583 584 // Clear node handles when the TreeView window is destroyed so 585 // that it can be reconstructed. 586 private void _resetHandles() { 587 foreach (TreeNode node; _nodes) { 588 node.tchildren._resetHandles(); 589 node.hnode = null; 590 } 591 } 592 593 private: 594 595 TreeView tview; // null if not assigned to a TreeView yet. 596 TreeNode tparent; // null if root. The parent of -_nodes-. 597 TreeNode[] _nodes; 598 599 void verifyNoParent(TreeNode node) { 600 if (node.tparent) { 601 throw new DflException("TreeNode already belongs to a TreeView"); 602 } 603 } 604 605 package this(TreeView treeView, TreeNode parentNode) { 606 tview = treeView; 607 tparent = parentNode; 608 } 609 610 package final void setTreeView(TreeView treeView) { 611 tview = treeView; 612 foreach (TreeNode node; _nodes) { 613 node.tchildren.setTreeView(treeView); 614 } 615 } 616 617 package final @property bool created() { 618 return tview && tview.created(); 619 } 620 621 package void populateInsertChildNode(ref Message m, ref TV_ITEMA dest, TreeNode node) { 622 with (dest) { 623 mask = /* TVIF_CHILDREN | */ TVIF_PARAM | TVIF_TEXT; 624 version (DFL_NO_IMAGELIST) { 625 } else { 626 mask |= TVIF_IMAGE | TVIF_SELECTEDIMAGE; 627 iImage = node._imgidx; 628 if (tview._selimgidx < 0) { 629 iSelectedImage = node._imgidx; 630 } else { 631 iSelectedImage = tview._selimgidx; 632 } 633 } 634 /* cChildren = I_CHILDRENCALLBACK; */ 635 lParam = cast(LPARAM) cast(void*) node; 636 /* 637 pszText = stringToStringz(node.text); 638 //cchTextMax = node.text.length; // ? 639 */ 640 if (dfl.internal.utf.useUnicode) { 641 pszText = cast(typeof(pszText)) dfl.internal.utf.toUnicodez(node.text); 642 m.hWnd = tview.handle; 643 m.msg = TVM_INSERTITEMW; 644 } else { 645 pszText = cast(typeof(pszText)) dfl.internal.utf.unsafeAnsiz(node.text); 646 m.hWnd = tview.handle; 647 m.msg = TVM_INSERTITEMA; 648 } 649 } 650 } 651 652 void doNodes() 653 in { 654 assert(created); 655 } 656 body { 657 TV_INSERTSTRUCTA tis; 658 Message m; 659 660 tis.hInsertAfter = TVI_LAST; 661 662 m.hWnd = tview.handle; 663 m.wParam = 0; 664 665 foreach (TreeNode node; _nodes) { 666 assert(!node.handle); 667 668 tis.hParent = tparent ? tparent.handle : TVI_ROOT; 669 populateInsertChildNode(m, tis.item, node); 670 671 m.lParam = cast(LPARAM)&tis; 672 tview.prevWndProc(m); 673 assert(m.result); 674 node.hnode = cast(HTREEITEM) m.result; 675 676 node.tchildren.doNodes(); 677 } 678 } 679 680 void _added(size_t idx, TreeNode val) { 681 verifyNoParent(val); 682 683 val.tparent = tparent; 684 val.tview = tview; 685 val.tchildren.setTreeView(tview); 686 687 if (created) { 688 TV_INSERTSTRUCTA tis; 689 690 if (idx <= 0) { 691 tis.hInsertAfter = TVI_FIRST; 692 } else if (idx >= cast(int) _nodes.length) { 693 tis.hInsertAfter = TVI_LAST; 694 } else { 695 tis.hInsertAfter = _nodes[idx - 1].handle; 696 } 697 698 tis.hParent = tparent ? tparent.handle : TVI_ROOT; 699 assert(tis.hInsertAfter); 700 701 Message m; 702 m.wParam = 0; 703 704 populateInsertChildNode(m, tis.item, val); 705 706 m.lParam = cast(LPARAM)&tis; 707 tview.prevWndProc(m); 708 assert(m.result); 709 val.hnode = cast(HTREEITEM) m.result; 710 711 val.tchildren.doNodes(); 712 713 if (tparent) { 714 tview.invalidate(tparent.bounds); 715 } 716 } 717 } 718 719 void _removing(size_t idx, TreeNode val) { 720 if (size_t.max == idx) { // Clearing all... 721 TreeNode[] nodes = _nodes; 722 _nodes = _nodes[0 .. 0]; // Not nice to dfl.collections, but OK. 723 if (created) { 724 Message m; 725 m.hWnd = tview.handle; 726 m.msg = TVM_DELETEITEM; 727 m.wParam = 0; 728 if (tparent) { 729 foreach (TreeNode node; nodes) { 730 assert(node.handle !is null); 731 m.lParam = cast(LPARAM) node.handle; 732 tview.prevWndProc(m); 733 734 node._reset(); 735 } 736 } else { 737 m.lParam = cast(LPARAM) TVI_ROOT; 738 tview.prevWndProc(m); 739 foreach (TreeNode node; nodes) { 740 node._reset(); 741 } 742 } 743 } 744 } else { 745 } 746 } 747 748 void _removed(size_t idx, TreeNode val) { 749 if (size_t.max == idx) { // Clear all. 750 } else { 751 if (created) { 752 assert(val.hnode); 753 Message m; 754 m = Message(tview.handle, TVM_DELETEITEM, 0, cast(LPARAM) val.hnode); 755 tview.prevWndProc(m); 756 } 757 758 // Clear children. 759 val._reset(); 760 } 761 } 762 763 public: 764 765 mixin ListWrapArray!(TreeNode, _nodes, _blankListCallback!(TreeNode), 766 _added, _removing, _removed, true, /*true*/ false, false) _wraparray; 767 } 768 769 class TreeView : ControlSuperClass { 770 this() { 771 _initTreeview(); 772 773 wstyle |= WS_TABSTOP | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_HASLINES; 774 wexstyle |= WS_EX_CLIENTEDGE; 775 ctrlStyle |= ControlStyles.SELECTABLE; 776 wclassStyle = treeviewClassStyle; 777 778 tchildren = new TreeNodeCollection(this, null); 779 } 780 781 /* 782 ~this() 783 { 784 if(tchildren) 785 tchildren._dtorReset(); 786 } 787 */ 788 789 static @property Color defaultBackColor() { 790 return SystemColors.window; 791 } 792 793 override @property Color backColor() { 794 if (Color.empty == backc) { 795 return defaultBackColor; 796 } 797 return backc; 798 } 799 800 override @property void backColor(Color b) { 801 super.backColor = b; 802 803 if (created) { 804 // For some reason the left edge isn't showing the new color. 805 // This causes the entire control to be redrawn with the new color. 806 // Sets the same font. 807 prevwproc(WM_SETFONT, this.font ? cast(WPARAM) this.font.handle : 0, MAKELPARAM(TRUE, 808 0)); 809 } 810 } 811 812 static @property Color defaultForeColor() { 813 return SystemColors.windowText; 814 } 815 816 override @property Color foreColor() { 817 if (Color.empty == forec) { 818 return defaultForeColor; 819 } 820 return forec; 821 } 822 823 alias foreColor = Control.foreColor; // Overload. 824 825 final @property void borderStyle(BorderStyle bs) { 826 final switch (bs) { 827 case BorderStyle.FIXED_3D: 828 _style(_style() & ~WS_BORDER); 829 _exStyle(_exStyle() | WS_EX_CLIENTEDGE); 830 break; 831 832 case BorderStyle.FIXED_SINGLE: 833 _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE); 834 _style(_style() | WS_BORDER); 835 break; 836 837 case BorderStyle.NONE: 838 _style(_style() & ~WS_BORDER); 839 _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE); 840 break; 841 } 842 843 if (created) { 844 redrawEntire(); 845 } 846 } 847 848 final @property BorderStyle borderStyle() { 849 if (_exStyle() & WS_EX_CLIENTEDGE) { 850 return BorderStyle.FIXED_3D; 851 } else if (_style() & WS_BORDER) { 852 return BorderStyle.FIXED_SINGLE; 853 } 854 return BorderStyle.NONE; 855 } 856 857 /* 858 859 final @property void checkBoxes(bool byes) 860 { 861 if(byes) 862 _style(_style() | TVS_CHECKBOXES); 863 else 864 _style(_style() & ~TVS_CHECKBOXES); 865 866 _crecreate(); 867 } 868 869 870 final @property bool checkBoxes() 871 { 872 return (_style() & TVS_CHECKBOXES) != 0; 873 } 874 */ 875 876 final @property void fullRowSelect(bool byes) { 877 if (byes) { 878 _style(_style() | TVS_FULLROWSELECT); 879 } else { 880 _style(_style() & ~TVS_FULLROWSELECT); 881 } 882 883 _crecreate(); // ? 884 } 885 886 final @property bool fullRowSelect() { 887 return (_style() & TVS_FULLROWSELECT) != 0; 888 } 889 890 final @property void hideSelection(bool byes) { 891 if (byes) { 892 _style(_style() & ~TVS_SHOWSELALWAYS); 893 } else { 894 _style(_style() | TVS_SHOWSELALWAYS); 895 } 896 } 897 898 final @property bool hideSelection() { 899 return (_style() & TVS_SHOWSELALWAYS) == 0; 900 } 901 902 deprecated alias hotTracking = hoverSelection; 903 904 final @property void hoverSelection(bool byes) { 905 if (byes) { 906 _style(_style() | TVS_TRACKSELECT); 907 } else { 908 _style(_style() & ~TVS_TRACKSELECT); 909 } 910 } 911 912 final @property bool hoverSelection() { 913 return (_style() & TVS_TRACKSELECT) != 0; 914 } 915 916 final @property void indent(int newIndent) { 917 if (newIndent < 0) { 918 newIndent = 0; 919 } else if (newIndent > 32_000) { 920 newIndent = 32_000; 921 } 922 923 ind = newIndent; 924 925 if (created) { 926 SendMessageA(hwnd, TVM_SETINDENT, ind, 0); 927 } 928 } 929 930 final @property int indent() { 931 if (created) { 932 ind = cast(int) SendMessageA(hwnd, TVM_GETINDENT, 0, 0); 933 } 934 return ind; 935 } 936 937 final @property void itemHeight(int h) { 938 if (h < 0) { 939 h = 0; 940 } 941 942 iheight = h; 943 944 if (created) { 945 SendMessageA(hwnd, TVM_SETITEMHEIGHT, iheight, 0); 946 } 947 } 948 949 final @property int itemHeight() { 950 if (created) { 951 iheight = cast(int) SendMessageA(hwnd, TVM_GETITEMHEIGHT, 0, 0); 952 } 953 return iheight; 954 } 955 956 final @property void labelEdit(bool byes) { 957 if (byes) { 958 _style(_style() | TVS_EDITLABELS); 959 } else { 960 _style(_style() & ~TVS_EDITLABELS); 961 } 962 } 963 964 final @property bool labelEdit() { 965 return (_style() & TVS_EDITLABELS) != 0; 966 } 967 968 final @property TreeNodeCollection nodes() { 969 return tchildren; 970 } 971 972 final @property void pathSeparator(dchar sep) { 973 pathsep = sep; 974 } 975 976 final @property dchar pathSeparator() { 977 return pathsep; 978 } 979 980 final @property void scrollable(bool byes) { 981 if (byes) { 982 _style(_style() & ~TVS_NOSCROLL); 983 } else { 984 _style(_style() | TVS_NOSCROLL); 985 } 986 987 if (created) { 988 redrawEntire(); 989 } 990 } 991 992 final @property bool scrollable() { 993 return (_style & TVS_NOSCROLL) == 0; 994 } 995 996 final @property void selectedNode(TreeNode node) { 997 if (created) { 998 if (node) { 999 SendMessageA(hwnd, TVM_SELECTITEM, TVGN_CARET, cast(LPARAM) node.handle); 1000 } else { 1001 // Should the selection be cleared if -node- is null? 1002 //SendMessageA(hwnd, TVM_SELECTITEM, TVGN_CARET, cast(LPARAM)null); 1003 } 1004 } 1005 } 1006 1007 final @property TreeNode selectedNode() { 1008 if (created) { 1009 HTREEITEM hnode; 1010 hnode = cast(HTREEITEM) SendMessageA(hwnd, TVM_GETNEXTITEM, TVGN_CARET, cast(LPARAM) 0); 1011 if (hnode) { 1012 return treeNodeFromHandle(hnode); 1013 } 1014 } 1015 return null; 1016 } 1017 1018 final @property void showLines(bool byes) { 1019 if (byes) { 1020 _style(_style() | TVS_HASLINES); 1021 } else { 1022 _style(_style() & ~TVS_HASLINES); 1023 } 1024 1025 _crecreate(); // ? 1026 } 1027 1028 final @property bool showLines() { 1029 return (_style() & TVS_HASLINES) != 0; 1030 } 1031 1032 final @property void showPlusMinus(bool byes) { 1033 if (byes) { 1034 _style(_style() | TVS_HASBUTTONS); 1035 } else { 1036 _style(_style() & ~TVS_HASBUTTONS); 1037 } 1038 1039 _crecreate(); // ? 1040 } 1041 1042 final @property bool showPlusMinus() { 1043 return (_style() & TVS_HASBUTTONS) != 0; 1044 } 1045 1046 // -showPlusMinus- should be false. 1047 final @property void singleExpand(bool byes) { 1048 if (byes) { 1049 _style(_style() | TVS_SINGLEEXPAND); 1050 } else { 1051 _style(_style() & ~TVS_SINGLEEXPAND); 1052 } 1053 1054 _crecreate(); // ? 1055 } 1056 1057 final @property bool singleExpand() { 1058 return (_style & TVS_SINGLEEXPAND) != 0; 1059 } 1060 1061 final @property void showRootLines(bool byes) { 1062 if (byes) { 1063 _style(_style() | TVS_LINESATROOT); 1064 } else { 1065 _style(_style() & ~TVS_LINESATROOT); 1066 } 1067 1068 _crecreate(); // ? 1069 } 1070 1071 final @property bool showRootLines() { 1072 return (_style() & TVS_LINESATROOT) != 0; 1073 } 1074 1075 final @property void sorted(bool byes) { 1076 _sort = byes; 1077 } 1078 1079 final @property bool sorted() { 1080 return _sort; 1081 } 1082 1083 // First visible node, based on the scrolled position. 1084 final @property TreeNode topNode() { 1085 if (created) { 1086 HTREEITEM hnode; 1087 hnode = cast(HTREEITEM) SendMessageA(hwnd, TVM_GETNEXTITEM, 1088 TVGN_FIRSTVISIBLE, cast(LPARAM) 0); 1089 if (hnode) { 1090 return treeNodeFromHandle(hnode); 1091 } 1092 } 1093 return null; 1094 } 1095 1096 // Number of visible nodes, including partially visible. 1097 final @property int visibleCount() { 1098 if (!created) { 1099 return 0; 1100 } 1101 return cast(int) SendMessageA(hwnd, TVM_GETVISIBLECOUNT, 0, 0); 1102 } 1103 1104 final void beginUpdate() { 1105 SendMessageA(handle, WM_SETREDRAW, false, 0); 1106 } 1107 1108 final void endUpdate() { 1109 SendMessageA(handle, WM_SETREDRAW, true, 0); 1110 invalidate(true); // Show updates. 1111 } 1112 1113 final void collapseAll() { 1114 if (created) { 1115 void collapsing(TreeNodeCollection tchildren) { 1116 foreach (TreeNode node; tchildren._nodes) { 1117 SendMessageA(hwnd, TVM_EXPAND, TVE_COLLAPSE, cast(LPARAM) node.hnode); 1118 collapsing(node.tchildren); 1119 } 1120 } 1121 1122 collapsing(tchildren); 1123 } 1124 } 1125 1126 final void expandAll() { 1127 if (created) { 1128 void expanding(TreeNodeCollection tchildren) { 1129 foreach (TreeNode node; tchildren._nodes) { 1130 SendMessageA(hwnd, TVM_EXPAND, TVE_EXPAND, cast(LPARAM) node.hnode); 1131 expanding(node.tchildren); 1132 } 1133 } 1134 1135 expanding(tchildren); 1136 } 1137 } 1138 1139 final TreeNode getNodeAt(int x, int y) { 1140 if (created) { 1141 TVHITTESTINFO thi; 1142 HTREEITEM hti; 1143 thi.pt.x = x; 1144 thi.pt.y = y; 1145 hti = cast(HTREEITEM) SendMessageA(hwnd, TVM_HITTEST, 0, cast(LPARAM)&thi); 1146 if (hti) { 1147 TreeNode result; 1148 result = treeNodeFromHandle(hti); 1149 if (result) { 1150 assert(result.tview is this); 1151 return result; 1152 } 1153 } 1154 } 1155 return null; 1156 } 1157 1158 final TreeNode getNodeAt(Point pt) { 1159 return getNodeAt(pt.x, pt.y); 1160 } 1161 1162 /* 1163 1164 // TODO: finish. 1165 final int getNodeCount(bool includeSubNodes) 1166 { 1167 int result; 1168 result = tchildren.length(); 1169 1170 if(includeSubNodes) 1171 { 1172 // ... 1173 } 1174 1175 return result; 1176 } 1177 */ 1178 1179 version (DFL_NO_IMAGELIST) { 1180 } else { 1181 1182 final @property void imageList(ImageList imglist) { 1183 if (isHandleCreated) { 1184 prevwproc(TVM_SETIMAGELIST, TVSIL_NORMAL, 1185 cast(LPARAM)(imglist ? imglist.handle : cast(HIMAGELIST) null)); 1186 } 1187 1188 _imglist = imglist; 1189 } 1190 1191 final @property ImageList imageList() { 1192 return _imglist; 1193 } 1194 1195 /* 1196 1197 // Default image index (if -1 use this). 1198 final @property void imageIndex(int index) 1199 { 1200 _defimgidx = index; 1201 } 1202 1203 1204 final @property int imageIndex() 1205 { 1206 return _defimgidx; 1207 } 1208 */ 1209 1210 final @property void selectedImageIndex(int index) { 1211 //assert(index >= 0); 1212 assert(index >= -1); 1213 _selimgidx = index; 1214 1215 if (isHandleCreated) { 1216 TreeNode curnode = selectedNode; 1217 _crecreate(); 1218 if (curnode) { 1219 curnode.ensureVisible(); 1220 } 1221 } 1222 } 1223 1224 final @property int selectedImageIndex() { 1225 return _selimgidx; 1226 } 1227 } 1228 1229 protected override @property Size defaultSize() { 1230 return Size(120, 100); 1231 } 1232 1233 /* 1234 override void createHandle() 1235 { 1236 if(isHandleCreated) 1237 return; 1238 1239 createClassHandle(TREEVIEW_CLASSNAME); 1240 1241 onHandleCreated(EventArgs.empty); 1242 } 1243 */ 1244 1245 protected override void createParams(ref CreateParams cp) { 1246 super.createParams(cp); 1247 1248 cp.className = TREEVIEW_CLASSNAME; 1249 } 1250 1251 protected override void onHandleCreated(EventArgs ea) { 1252 super.onHandleCreated(ea); 1253 1254 prevwproc(CCM_SETVERSION, 5, 0); // Fixes font size issue. 1255 1256 prevwproc(TVM_SETINDENT, ind, 0); 1257 1258 prevwproc(TVM_SETITEMHEIGHT, iheight, 0); 1259 1260 version (DFL_NO_IMAGELIST) { 1261 } else { 1262 if (_imglist) { 1263 prevwproc(TVM_SETIMAGELIST, TVSIL_NORMAL, cast(LPARAM) _imglist.handle); 1264 } 1265 } 1266 1267 tchildren.doNodes(); 1268 } 1269 1270 protected override void onHandleDestroyed(EventArgs ea) { 1271 tchildren._resetHandles(); 1272 1273 super.onHandleDestroyed(ea); 1274 } 1275 1276 protected override void wndProc(ref Message m) { 1277 // TODO: support these messages. 1278 switch (m.msg) { 1279 case TVM_INSERTITEMA: 1280 case TVM_INSERTITEMW: 1281 m.result = cast(LRESULT) 0; 1282 return; 1283 1284 case TVM_SETITEMA: 1285 case TVM_SETITEMW: 1286 m.result = cast(LRESULT)-1; 1287 return; 1288 1289 case TVM_DELETEITEM: 1290 m.result = FALSE; 1291 return; 1292 1293 case TVM_SETIMAGELIST: 1294 m.result = cast(LRESULT) 0; 1295 return; 1296 1297 default: 1298 } 1299 1300 super.wndProc(m); 1301 } 1302 1303 protected override void prevWndProc(ref Message msg) { 1304 //msg.result = CallWindowProcA(treeviewPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam); 1305 msg.result = dfl.internal.utf.callWindowProc(treeviewPrevWndProc, 1306 msg.hWnd, msg.msg, msg.wParam, msg.lParam); 1307 } 1308 1309 //TreeViewEventHandler afterCollapse; 1310 Event!(TreeView, TreeViewEventArgs) afterCollapse; 1311 //TreeViewEventHandler afterExpand; 1312 Event!(TreeView, TreeViewEventArgs) afterExpand; 1313 //TreeViewEventHandler afterSelect; 1314 Event!(TreeView, TreeViewEventArgs) afterSelect; 1315 //NodeLabelEditEventHandler afterLabelEdit; 1316 Event!(TreeView, NodeLabelEditEventArgs) afterLabelEdit; 1317 //TreeViewCancelEventHandler beforeCollapse; 1318 Event!(TreeView, TreeViewCancelEventArgs) beforeCollapse; 1319 //TreeViewCancelEventHandler beforeExpand; 1320 Event!(TreeView, TreeViewCancelEventArgs) beforeExpand; 1321 //TreeViewCancelEventHandler beforeSelect; 1322 Event!(TreeView, TreeViewCancelEventArgs) beforeSelect; 1323 //NodeLabelEditEventHandler beforeLabelEdit; 1324 Event!(TreeView, NodeLabelEditEventArgs) beforeLabelEdit; 1325 1326 protected void onAfterCollapse(TreeViewEventArgs ea) { 1327 afterCollapse(this, ea); 1328 } 1329 1330 protected void onAfterExpand(TreeViewEventArgs ea) { 1331 afterExpand(this, ea); 1332 } 1333 1334 protected void onAfterSelect(TreeViewEventArgs ea) { 1335 afterSelect(this, ea); 1336 } 1337 1338 protected void onAfterLabelEdit(NodeLabelEditEventArgs ea) { 1339 afterLabelEdit(this, ea); 1340 } 1341 1342 protected void onBeforeCollapse(TreeViewCancelEventArgs ea) { 1343 beforeCollapse(this, ea); 1344 } 1345 1346 protected void onBeforeExpand(TreeViewCancelEventArgs ea) { 1347 beforeExpand(this, ea); 1348 } 1349 1350 protected void onBeforeSelect(TreeViewCancelEventArgs ea) { 1351 beforeSelect(this, ea); 1352 } 1353 1354 protected void onBeforeLabelEdit(NodeLabelEditEventArgs ea) { 1355 beforeLabelEdit(this, ea); 1356 } 1357 1358 protected override void onReflectedMessage(ref Message m) { // package 1359 super.onReflectedMessage(m); 1360 1361 switch (m.msg) { 1362 case WM_NOTIFY: { 1363 NMHDR* nmh; 1364 NM_TREEVIEW* nmtv; 1365 TreeViewCancelEventArgs cea; 1366 1367 nmh = cast(NMHDR*) m.lParam; 1368 assert(nmh.hwndFrom == hwnd); 1369 1370 switch (nmh.code) { 1371 case NM_CUSTOMDRAW: { 1372 NMTVCUSTOMDRAW* tvcd; 1373 tvcd = cast(NMTVCUSTOMDRAW*) nmh; 1374 //if(tvcd.nmcd.dwDrawStage & CDDS_ITEM) 1375 { 1376 //if(tvcd.nmcd.uItemState & CDIS_SELECTED) 1377 if ((tvcd.nmcd.dwDrawStage & CDDS_ITEM) 1378 && (tvcd.nmcd.uItemState & CDIS_SELECTED)) { 1379 // Note: might not look good with custom colors. 1380 tvcd.clrText = SystemColors.highlightText.toRgb(); 1381 tvcd.clrTextBk = SystemColors.highlight.toRgb(); 1382 } else { 1383 //tvcd.clrText = foreColor.toRgb(); 1384 tvcd.clrText = foreColor.solidColor(backColor).toRgb(); 1385 tvcd.clrTextBk = backColor.toRgb(); 1386 } 1387 } 1388 m.result |= CDRF_NOTIFYITEMDRAW; // | CDRF_NOTIFYITEMERASE; 1389 1390 // This doesn't seem to be doing anything. 1391 Font fon; 1392 fon = this.font; 1393 if (fon) { 1394 SelectObject(tvcd.nmcd.hdc, fon.handle); 1395 m.result |= CDRF_NEWFONT; 1396 } 1397 } 1398 break; 1399 1400 /* 1401 case TVN_GETDISPINFOA: 1402 1403 break; 1404 */ 1405 1406 case TVN_SELCHANGINGW: 1407 goto sel_changing; 1408 1409 case TVN_SELCHANGINGA: 1410 if (dfl.internal.utf.useUnicode) { 1411 break; 1412 } 1413 sel_changing: 1414 1415 nmtv = cast(NM_TREEVIEW*) nmh; 1416 switch (nmtv.action) { 1417 case TVC_BYMOUSE: 1418 cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam, 1419 false, TreeViewAction.BY_MOUSE); 1420 onBeforeSelect(cea); 1421 m.result = cea.cancel; 1422 break; 1423 1424 case TVC_BYKEYBOARD: 1425 cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam, 1426 false, TreeViewAction.BY_KEYBOARD); 1427 onBeforeSelect(cea); 1428 m.result = cea.cancel; 1429 break; 1430 1431 //case TVC_UNKNOWN: 1432 default: 1433 cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam, 1434 false, TreeViewAction.UNKNOWN); 1435 onBeforeSelect(cea); 1436 m.result = cea.cancel; 1437 } 1438 break; 1439 1440 case TVN_SELCHANGEDW: 1441 goto sel_changed; 1442 1443 case TVN_SELCHANGEDA: 1444 if (dfl.internal.utf.useUnicode) { 1445 break; 1446 } 1447 sel_changed: 1448 1449 nmtv = cast(NM_TREEVIEW*) nmh; 1450 switch (nmtv.action) { 1451 case TVC_BYMOUSE: 1452 onAfterSelect(new TreeViewEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam, 1453 TreeViewAction.BY_MOUSE)); 1454 break; 1455 1456 case TVC_BYKEYBOARD: 1457 onAfterSelect(new TreeViewEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam, 1458 TreeViewAction.BY_KEYBOARD)); 1459 break; 1460 1461 //case TVC_UNKNOWN: 1462 default: 1463 onAfterSelect(new TreeViewEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam, 1464 TreeViewAction.UNKNOWN)); 1465 } 1466 break; 1467 1468 case TVN_ITEMEXPANDINGW: 1469 goto item_expanding; 1470 1471 case TVN_ITEMEXPANDINGA: 1472 if (dfl.internal.utf.useUnicode) { 1473 break; 1474 } 1475 item_expanding: 1476 1477 nmtv = cast(NM_TREEVIEW*) nmh; 1478 switch (nmtv.action) { 1479 case TVE_COLLAPSE: 1480 cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam, 1481 false, TreeViewAction.COLLAPSE); 1482 onBeforeCollapse(cea); 1483 m.result = cea.cancel; 1484 break; 1485 1486 case TVE_EXPAND: 1487 cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam, 1488 false, TreeViewAction.EXPAND); 1489 onBeforeExpand(cea); 1490 m.result = cea.cancel; 1491 break; 1492 1493 default: 1494 } 1495 break; 1496 1497 case TVN_ITEMEXPANDEDW: 1498 goto item_expanded; 1499 1500 case TVN_ITEMEXPANDEDA: 1501 if (dfl.internal.utf.useUnicode) { 1502 break; 1503 } 1504 item_expanded: 1505 1506 nmtv = cast(NM_TREEVIEW*) nmh; 1507 switch (nmtv.action) { 1508 case TVE_COLLAPSE: { 1509 scope TreeViewEventArgs tvea = new TreeViewEventArgs( 1510 cast(TreeNode) cast(void*) nmtv.itemNew.lParam, TreeViewAction.COLLAPSE); 1511 onAfterCollapse(tvea); 1512 } 1513 break; 1514 1515 case TVE_EXPAND: { 1516 scope TreeViewEventArgs tvea = new TreeViewEventArgs( 1517 cast(TreeNode) cast(void*) nmtv.itemNew.lParam, TreeViewAction.EXPAND); 1518 onAfterExpand(tvea); 1519 } 1520 break; 1521 1522 default: 1523 } 1524 break; 1525 1526 case TVN_BEGINLABELEDITW: 1527 goto begin_label_edit; 1528 1529 case TVN_BEGINLABELEDITA: 1530 if (dfl.internal.utf.useUnicode) { 1531 break; 1532 } 1533 begin_label_edit: { 1534 TV_DISPINFOA* nmdi; 1535 nmdi = cast(TV_DISPINFOA*) nmh; 1536 TreeNode node; 1537 node = cast(TreeNode) cast(void*) nmdi.item.lParam; 1538 scope NodeLabelEditEventArgs nleea = new NodeLabelEditEventArgs(node); 1539 onBeforeLabelEdit(nleea); 1540 m.result = nleea.cancelEdit; 1541 } 1542 break; 1543 1544 case TVN_ENDLABELEDITW: { 1545 Dstring label; 1546 TV_DISPINFOW* nmdi; 1547 nmdi = cast(TV_DISPINFOW*) nmh; 1548 if (nmdi.item.pszText) { 1549 TreeNode node; 1550 node = cast(TreeNode) cast(void*) nmdi.item.lParam; 1551 label = fromUnicodez(nmdi.item.pszText); 1552 scope NodeLabelEditEventArgs nleea = new NodeLabelEditEventArgs(node, 1553 label); 1554 onAfterLabelEdit(nleea); 1555 if (nleea.cancelEdit) { 1556 m.result = FALSE; 1557 } else { 1558 // TODO: check if correct implementation. 1559 // Update the node's cached text.. 1560 node.ttext = label; 1561 1562 m.result = TRUE; 1563 } 1564 } 1565 } 1566 break; 1567 1568 case TVN_ENDLABELEDITA: 1569 if (dfl.internal.utf.useUnicode) { 1570 break; 1571 } else { 1572 Dstring label; 1573 TV_DISPINFOA* nmdi; 1574 nmdi = cast(TV_DISPINFOA*) nmh; 1575 if (nmdi.item.pszText) { 1576 TreeNode node; 1577 node = cast(TreeNode) cast(void*) nmdi.item.lParam; 1578 label = fromAnsiz(nmdi.item.pszText); 1579 scope NodeLabelEditEventArgs nleea = new NodeLabelEditEventArgs(node, 1580 label); 1581 onAfterLabelEdit(nleea); 1582 if (nleea.cancelEdit) { 1583 m.result = FALSE; 1584 } else { 1585 // TODO: check if correct implementation. 1586 // Update the node's cached text.. 1587 node.ttext = label; 1588 1589 m.result = TRUE; 1590 } 1591 } 1592 break; 1593 } 1594 1595 default: 1596 } 1597 } 1598 break; 1599 1600 default: 1601 } 1602 } 1603 1604 private: 1605 TreeNodeCollection tchildren; 1606 int ind = 19; // Indent. 1607 dchar pathsep = '\\'; 1608 bool _sort = false; 1609 int iheight = 16; 1610 version (DFL_NO_IMAGELIST) { 1611 } else { 1612 ImageList _imglist; 1613 int _selimgidx = -1; //0; 1614 } 1615 1616 TreeNode treeNodeFromHandle(HTREEITEM hnode) { 1617 TV_ITEMA ti; 1618 ti.mask = TVIF_HANDLE | TVIF_PARAM; 1619 ti.hItem = hnode; 1620 if (SendMessageA(hwnd, TVM_GETITEMA, 0, cast(LPARAM)&ti)) { 1621 return cast(TreeNode) cast(void*) ti.lParam; 1622 } 1623 return null; 1624 } 1625 1626 package: 1627 final: 1628 LRESULT prevwproc(UINT msg, WPARAM wparam, LPARAM lparam) { 1629 //return CallWindowProcA(treeviewPrevWndProc, hwnd, msg, wparam, lparam); 1630 return dfl.internal.utf.callWindowProc(treeviewPrevWndProc, hwnd, msg, wparam, 1631 lparam); 1632 } 1633 }