1 // Written in the D programming language. 2 3 /** 4 5 This module contains tree widgets implementation 6 7 8 TreeWidgetBase - abstract tree widget 9 10 TreeWidget - Tree widget with items which can have icons and labels 11 12 13 Synopsis: 14 15 ---- 16 import dlangui.widgets.tree; 17 18 // tree view example 19 TreeWidget tree = new TreeWidget("TREE1"); 20 tree.layoutWidth(WRAP_CONTENT).layoutHeight(FILL_PARENT); 21 TreeItem tree1 = tree.items.newChild("group1", "Group 1"d, "document-open"); 22 tree1.newChild("g1_1", "Group 1 item 1"d); 23 tree1.newChild("g1_2", "Group 1 item 2"d); 24 tree1.newChild("g1_3", "Group 1 item 3"d); 25 TreeItem tree2 = tree.items.newChild("group2", "Group 2"d, "document-save"); 26 tree2.newChild("g2_1", "Group 2 item 1"d, "edit-copy"); 27 tree2.newChild("g2_2", "Group 2 item 2"d, "edit-cut"); 28 tree2.newChild("g2_3", "Group 2 item 3"d, "edit-paste"); 29 tree2.newChild("g2_4", "Group 2 item 4"d); 30 TreeItem tree3 = tree.items.newChild("group3", "Group 3"d); 31 tree3.newChild("g3_1", "Group 3 item 1"d); 32 tree3.newChild("g3_2", "Group 3 item 2"d); 33 TreeItem tree32 = tree3.newChild("g3_3", "Group 3 item 3"d); 34 tree3.newChild("g3_4", "Group 3 item 4"d); 35 tree32.newChild("group3_2_1", "Group 3 item 2 subitem 1"d); 36 tree32.newChild("group3_2_2", "Group 3 item 2 subitem 2"d); 37 tree32.newChild("group3_2_3", "Group 3 item 2 subitem 3"d); 38 tree32.newChild("group3_2_4", "Group 3 item 2 subitem 4"d); 39 tree32.newChild("group3_2_5", "Group 3 item 2 subitem 5"d); 40 tree3.newChild("g3_5", "Group 3 item 5"d); 41 tree3.newChild("g3_6", "Group 3 item 6"d); 42 43 LinearLayout treeLayout = new HorizontalLayout("TREE"); 44 LinearLayout treeControlledPanel = new VerticalLayout(); 45 treeLayout.layoutWidth = FILL_PARENT; 46 treeControlledPanel.layoutWidth = FILL_PARENT; 47 treeControlledPanel.layoutHeight = FILL_PARENT; 48 TextWidget treeItemLabel = new TextWidget("TREE_ITEM_DESC"); 49 treeItemLabel.layoutWidth = FILL_PARENT; 50 treeItemLabel.layoutHeight = FILL_PARENT; 51 treeItemLabel.alignment = Align.Center; 52 treeItemLabel.text = "Sample text"d; 53 treeControlledPanel.addChild(treeItemLabel); 54 treeLayout.addChild(tree); 55 treeLayout.addChild(new ResizerWidget()); 56 treeLayout.addChild(treeControlledPanel); 57 58 tree.selectionListener = delegate(TreeItems source, TreeItem selectedItem, bool activated) { 59 dstring label = "Selected item: "d ~ toUTF32(selectedItem.id) ~ (activated ? " selected + activated"d : " selected"d); 60 treeItemLabel.text = label; 61 }; 62 63 tree.items.selectItem(tree.items.child(0)); 64 ---- 65 66 Copyright: Vadim Lopatin, 2014 67 License: Boost License 1.0 68 Authors: Vadim Lopatin, coolreader.org@gmail.com 69 */ 70 module dlangui.widgets.tree; 71 72 import dlangui.widgets.widget; 73 import dlangui.widgets.controls; 74 import dlangui.widgets.scroll; 75 import dlangui.widgets.menu; 76 import dlangui.widgets.popup; 77 import dlangui.widgets.layouts; 78 import std.conv; 79 import std.algorithm; 80 81 // tree widget item data container 82 class TreeItem { 83 protected TreeItem _parent; 84 protected string _id; 85 protected string _iconRes; 86 protected int _level; 87 protected UIString _text; 88 protected ObjectList!TreeItem _children; 89 protected bool _expanded; 90 91 this(string id) { 92 _id = id; 93 _expanded = true; 94 } 95 this(string id, dstring label, string iconRes = null) { 96 _id = id; 97 _expanded = true; 98 _iconRes = iconRes; 99 _text.value = label; 100 } 101 this(string id, UIString label, string iconRes = null) { 102 _id = id; 103 _expanded = true; 104 _iconRes = iconRes; 105 _text = label; 106 } 107 this(string id, string labelRes, string iconRes = null) { 108 _id = id; 109 _expanded = true; 110 _iconRes = iconRes; 111 _text.id = labelRes; 112 } 113 /// create and add new child item 114 TreeItem newChild(string id, dstring label, string iconRes = null) { 115 TreeItem res = new TreeItem(id, label, iconRes); 116 addChild(res); 117 return res; 118 } 119 120 /// returns true if item supports collapse 121 @property bool canCollapse() { 122 if (auto r = root) { 123 return r.canCollapse(this); 124 } 125 return true; 126 } 127 128 /// returns topmost item 129 @property TreeItems root() { 130 TreeItem p = this; 131 while (p._parent) 132 p = p._parent; 133 return cast(TreeItems)p; 134 } 135 136 /// returns true if this item is root item 137 @property bool isRoot() { 138 return false; 139 } 140 141 void clear() { 142 foreach(c; _children) { 143 c.parent = null; 144 if(c is root.selectedItem) 145 root.selectItem(null); 146 } 147 _children.clear(); 148 root.onUpdate(this); 149 } 150 151 @property TreeItem parent() { return _parent; } 152 @property protected TreeItem parent(TreeItem p) { _parent = p; return this; } 153 @property string id() { return _id; } 154 @property TreeItem id(string id) { _id = id; return this; } 155 @property string iconRes() { return _iconRes; } 156 @property TreeItem iconRes(string res) { _iconRes = res; return this; } 157 @property int level() { return _level; } 158 @property protected TreeItem level(int level) { 159 _level = level; 160 for (int i = 0; i < childCount; i++) 161 child(i).level = _level + 1; 162 return this; 163 } 164 @property bool expanded() { return _expanded; } 165 @property protected TreeItem expanded(bool expanded) { _expanded = expanded; return this; } 166 /** Returns true if this item and all parents are expanded. */ 167 bool isFullyExpanded() { 168 if (!_expanded) 169 return false; 170 if (!_parent) 171 return true; 172 return _parent.isFullyExpanded(); 173 } 174 /** Returns true if all parents are expanded. */ 175 bool isVisible() { 176 if (_parent) 177 return _parent.isFullyExpanded(); 178 return false; 179 } 180 void expand() { 181 _expanded = true; 182 if (_parent) 183 _parent.expand(); 184 } 185 void collapse() { 186 _expanded = false; 187 } 188 /// expand this node and all children 189 void expandAll() { 190 foreach(c; _children) { 191 if (!c._expanded && c.canCollapse) //? 192 c.expandAll(); 193 } 194 if (!expanded) 195 toggleExpand(this); 196 } 197 /// expand this node and all children 198 void collapseAll() { 199 foreach(c; _children) { 200 if (c._expanded && c.canCollapse) 201 c.collapseAll(); 202 } 203 if (expanded) 204 toggleExpand(this); 205 } 206 207 @property TreeItem selectedItem() { 208 return root.selectedItem(); 209 } 210 211 @property TreeItem defaultItem() { 212 return root.defaultItem(); 213 } 214 215 @property bool isSelected() { 216 return (selectedItem is this); 217 } 218 219 @property bool isDefault() { 220 return (defaultItem is this); 221 } 222 223 /// get widget text 224 @property dstring text() { return _text; } 225 /// set text to show 226 @property TreeItem text(dstring s) { 227 _text = s; 228 return this; 229 } 230 /// set text to show 231 @property TreeItem text(UIString s) { 232 _text = s; 233 return this; 234 } 235 /// set text resource ID to show 236 @property TreeItem textResource(string s) { 237 _text = s; 238 return this; 239 } 240 241 bool compareId(string id) { 242 return _id !is null && _id.equal(id); 243 } 244 245 @property TreeItem topParent() { 246 if (!_parent) 247 return this; 248 return _parent.topParent; 249 } 250 251 252 protected int _intParam; 253 @property int intParam() { 254 return _intParam; 255 } 256 @property TreeItem intParam(int value) { 257 _intParam = value; 258 return this; 259 } 260 261 protected Object _objectParam; 262 @property Object objectParam() { 263 return _objectParam; 264 } 265 266 @property TreeItem objectParam(Object value) { 267 _objectParam = value; 268 return this; 269 } 270 271 272 /// returns true if item has at least one child 273 @property bool hasChildren() { return childCount > 0; } 274 275 /// returns number of children of this widget 276 @property int childCount() { return _children.count; } 277 /// returns child by index 278 TreeItem child(int index) { return _children.get(index); } 279 /// adds child, returns added item 280 TreeItem addChild(TreeItem item, int index = -1) { 281 TreeItem res = _children.insert(item, index).parent(this).level(_level + 1); 282 root.onUpdate(res); 283 return res; 284 } 285 /// removes child, returns removed item 286 TreeItem removeChild(int index) { 287 if (index < 0 || index >= _children.count) 288 return null; 289 TreeItem res = _children.remove(index); 290 TreeItem newSelection = null; 291 if (res !is null) { 292 res.parent = null; 293 if (root && root.selectedItem is res) { 294 if (index < _children.count) 295 newSelection = _children[index]; 296 else if (index > 0) 297 newSelection = _children[index - 1]; 298 else 299 newSelection = this; 300 } 301 } 302 root.selectItem(newSelection); 303 root.onUpdate(this); 304 return res; 305 } 306 /// removes child by reference, returns removed item 307 TreeItem removeChild(TreeItem child) { 308 TreeItem res = null; 309 int index = _children.indexOf(child); 310 return removeChild(index); 311 } 312 /// removes child by ID, returns removed item 313 TreeItem removeChild(string ID) { 314 TreeItem res = null; 315 int index = _children.indexOf(ID); 316 return removeChild(index); 317 } 318 /// returns index of widget in child list, -1 if passed widget is not a child of this widget 319 int childIndex(TreeItem item) { return _children.indexOf(item); } 320 /// notify listeners 321 protected void onUpdate(TreeItem item) { 322 if (root) 323 root.onUpdate(item); 324 } 325 protected void toggleExpand(TreeItem item) { 326 root.toggleExpand(item); 327 } 328 protected void selectItem(TreeItem item) { 329 root.selectItem(item); 330 } 331 protected void activateItem(TreeItem item) { 332 root.activateItem(item); 333 } 334 335 protected TreeItem nextVisible(TreeItem item, ref bool found) { 336 if (this is item) 337 found = true; 338 else if (found && isVisible) 339 return this; 340 for (int i = 0; i < childCount; i++) { 341 TreeItem res = child(i).nextVisible(item, found); 342 if (res) 343 return res; 344 } 345 return null; 346 } 347 348 protected TreeItem prevVisible(TreeItem item, ref TreeItem prevFoundVisible) { 349 if (this is item) 350 return prevFoundVisible; 351 else if (isVisible) 352 prevFoundVisible = this; 353 for (int i = 0; i < childCount; i++) { 354 TreeItem res = child(i).prevVisible(item, prevFoundVisible); 355 if (res) 356 return res; 357 } 358 return null; 359 } 360 361 /// returns item by id, null if not found 362 TreeItem findItemById(string id) { 363 if (_id.equal(id)) 364 return this; 365 for (int i = 0; i < childCount; i++) { 366 TreeItem res = child(i).findItemById(id); 367 if (res) 368 return res; 369 } 370 return null; 371 } 372 } 373 374 interface OnTreeContentChangeListener { 375 void onTreeContentChange(TreeItems source); 376 } 377 378 interface OnTreeStateChangeListener { 379 void onTreeStateChange(TreeItems source); 380 } 381 382 interface OnTreeExpandedStateListener { 383 void onTreeExpandedStateChange(TreeItems source, TreeItem item); 384 } 385 386 interface OnTreeSelectionChangeListener { 387 void onTreeItemSelected(TreeItems source, TreeItem selectedItem, bool activated); 388 } 389 390 class TreeItems : TreeItem { 391 // signal handler OnTreeContentChangeListener 392 Listener!OnTreeContentChangeListener contentListener; 393 Listener!OnTreeStateChangeListener stateListener; 394 Listener!OnTreeSelectionChangeListener selectionListener; 395 Listener!OnTreeExpandedStateListener expandListener; 396 397 protected bool _noCollapseForSingleTopLevelItem; 398 @property bool noCollapseForSingleTopLevelItem() { return _noCollapseForSingleTopLevelItem; } 399 @property TreeItems noCollapseForSingleTopLevelItem(bool flg) { _noCollapseForSingleTopLevelItem = flg; return this; } 400 401 protected TreeItem _selectedItem; 402 protected TreeItem _defaultItem; 403 404 this() { 405 super("tree"); 406 } 407 408 /// returns true if this item is root item 409 override @property bool isRoot() { 410 return true; 411 } 412 413 /// notify listeners 414 override protected void onUpdate(TreeItem item) { 415 if (contentListener.assigned) 416 contentListener(this); 417 } 418 419 bool canCollapse(TreeItem item) { 420 if (!_noCollapseForSingleTopLevelItem) 421 return true; 422 if (!hasChildren) 423 return false; 424 if (_children.count == 1 && _children[0] is item) 425 return false; 426 return true; 427 } 428 429 bool canCollapseTopLevel() { 430 if (!_noCollapseForSingleTopLevelItem) 431 return true; 432 if (!hasChildren) 433 return false; 434 if (_children.count == 1) 435 return false; 436 return true; 437 } 438 439 override void toggleExpand(TreeItem item) { 440 bool expandChanged = false; 441 if (item.expanded) { 442 if (item.canCollapse()) { 443 item.collapse(); 444 expandChanged = true; 445 } 446 } else { 447 item.expand(); 448 expandChanged = true; 449 } 450 if (stateListener.assigned) 451 stateListener(this); 452 if (expandChanged && expandListener.assigned) 453 expandListener(this, item); 454 } 455 456 override void selectItem(TreeItem item) { 457 if (_selectedItem is item) 458 return; 459 _selectedItem = item; 460 if (stateListener.assigned) 461 stateListener(this); 462 if (selectionListener.assigned) 463 selectionListener(this, _selectedItem, false); 464 } 465 466 void setDefaultItem(TreeItem item) { 467 _defaultItem = item; 468 if (stateListener.assigned) 469 stateListener(this); 470 } 471 472 override void activateItem(TreeItem item) { 473 if (!(_selectedItem is item)) { 474 _selectedItem = item; 475 if (stateListener.assigned) 476 stateListener(this); 477 } 478 if (selectionListener.assigned) 479 selectionListener(this, _selectedItem, true); 480 } 481 482 @property override TreeItem selectedItem() { 483 return _selectedItem; 484 } 485 486 @property override TreeItem defaultItem() { 487 return _defaultItem; 488 } 489 490 void selectNext() { 491 if (!hasChildren) 492 return; 493 if (!_selectedItem) 494 selectItem(child(0)); 495 bool found = false; 496 TreeItem next = nextVisible(_selectedItem, found); 497 if (next) 498 selectItem(next); 499 } 500 501 void selectPrevious() { 502 if (!hasChildren) 503 return; 504 TreeItem found = null; 505 TreeItem prev = prevVisible(_selectedItem, found); 506 if (prev) 507 selectItem(prev); 508 } 509 } 510 511 /// grid control action codes 512 enum TreeActions : int { 513 /// no action 514 None = 0, 515 /// move selection up 516 Up = 2000, 517 /// move selection down 518 Down, 519 /// move selection left 520 Left, 521 /// move selection right 522 Right, 523 524 /// scroll up, w/o changing selection 525 ScrollUp, 526 /// scroll down, w/o changing selection 527 ScrollDown, 528 /// scroll left, w/o changing selection 529 ScrollLeft, 530 /// scroll right, w/o changing selection 531 ScrollRight, 532 533 /// scroll top w/o changing selection 534 ScrollTop, 535 /// scroll bottom, w/o changing selection 536 ScrollBottom, 537 538 /// scroll up, w/o changing selection 539 ScrollPageUp, 540 /// scroll down, w/o changing selection 541 ScrollPageDown, 542 /// scroll left, w/o changing selection 543 ScrollPageLeft, 544 /// scroll right, w/o changing selection 545 ScrollPageRight, 546 547 /// move cursor one page up 548 PageUp, 549 /// move cursor one page up with selection 550 SelectPageUp, 551 /// move cursor one page down 552 PageDown, 553 /// move cursor one page down with selection 554 SelectPageDown, 555 /// move cursor to the beginning of page 556 PageBegin, 557 /// move cursor to the beginning of page with selection 558 SelectPageBegin, 559 /// move cursor to the end of page 560 PageEnd, 561 /// move cursor to the end of page with selection 562 SelectPageEnd, 563 /// move cursor to the beginning of line 564 LineBegin, 565 /// move cursor to the beginning of line with selection 566 SelectLineBegin, 567 /// move cursor to the end of line 568 LineEnd, 569 /// move cursor to the end of line with selection 570 SelectLineEnd, 571 /// move cursor to the beginning of document 572 DocumentBegin, 573 /// move cursor to the beginning of document with selection 574 SelectDocumentBegin, 575 /// move cursor to the end of document 576 DocumentEnd, 577 /// move cursor to the end of document with selection 578 SelectDocumentEnd, 579 } 580 581 582 const int DOUBLE_CLICK_TIME_MS = 250; 583 584 interface OnTreePopupMenuListener { 585 MenuItem onTreeItemPopupMenu(TreeItems source, TreeItem selectedItem); 586 } 587 588 /// Item widget for displaying in trees 589 class TreeItemWidget : HorizontalLayout { 590 TreeItem _item; 591 TextWidget _tab; 592 ImageWidget _expander; 593 ImageWidget _icon; 594 TextWidget _label; 595 HorizontalLayout _body; 596 long lastClickTime; 597 598 Listener!OnTreePopupMenuListener popupMenuListener; 599 600 @property TreeItem item() { return _item; } 601 602 603 this(TreeItem item) { 604 super(item.id); 605 styleId = STYLE_TREE_ITEM; 606 607 clickable = true; 608 focusable = true; 609 trackHover = true; 610 611 _item = item; 612 _tab = new TextWidget("tab"); 613 //dchar[] tabText; 614 //dchar[] singleTab = [' ', ' ', ' ', ' ']; 615 //for (int i = 1; i < _item.level; i++) 616 // tabText ~= singleTab; 617 //_tab.text = cast(dstring)tabText; 618 int level = _item.level - 1; 619 if (!_item.root.canCollapseTopLevel()) 620 level--; 621 if (level < 0) 622 level = 0; 623 int w = level * style.font.size * 3 / 4; 624 _tab.minWidth = w; 625 _tab.maxWidth = w; 626 if (_item.canCollapse()) { 627 _expander = new ImageWidget("expander", _item.hasChildren && _item.expanded ? "arrow_right_down_black" : "arrow_right_hollow"); 628 _expander.styleId = STYLE_TREE_ITEM_EXPAND_ICON; 629 _expander.clickable = true; 630 _expander.trackHover = true; 631 _expander.visibility = _item.hasChildren ? Visibility.Visible : Visibility.Invisible; 632 //_expander.setState(State.Parent); 633 634 _expander.click = delegate(Widget source) { 635 _item.selectItem(_item); 636 _item.toggleExpand(_item); 637 return true; 638 }; 639 } 640 click = delegate(Widget source) { 641 long ts = currentTimeMillis(); 642 _item.selectItem(_item); 643 if (ts - lastClickTime < DOUBLE_CLICK_TIME_MS) { 644 if (_item.hasChildren) { 645 _item.toggleExpand(_item); 646 } else { 647 _item.activateItem(_item); 648 } 649 } 650 lastClickTime = ts; 651 return true; 652 }; 653 _body = new HorizontalLayout("item_body"); 654 _body.styleId = STYLE_TREE_ITEM_BODY; 655 _body.setState(State.Parent); 656 if (_item.iconRes.length > 0) { 657 _icon = new ImageWidget("icon", _item.iconRes); 658 _icon.styleId = STYLE_TREE_ITEM_ICON; 659 _icon.setState(State.Parent); 660 _icon.padding(Rect(0, 0, BACKEND_GUI ? 5 : 0, 0)); 661 _body.addChild(_icon); 662 } 663 _label = new TextWidget("label", _item.text); 664 _label.styleId = STYLE_TREE_ITEM_LABEL; 665 _label.setState(State.Parent); 666 _body.addChild(_label); 667 // append children 668 addChild(_tab); 669 if (_expander) 670 addChild(_expander); 671 addChild(_body); 672 } 673 674 override bool onKeyEvent(KeyEvent event) { 675 if (keyEvent.assigned && keyEvent(this, event)) 676 return true; // processed by external handler 677 if (!focused || !visible) 678 return false; 679 if (event.action != KeyAction.KeyDown) 680 return false; 681 int action = 0; 682 switch (event.keyCode) with(KeyCode) { 683 case SPACE: 684 case RETURN: 685 if (_item.hasChildren) 686 _item.toggleExpand(_item); 687 else 688 _item.activateItem(_item); 689 return true; 690 default: 691 break; 692 } 693 return false; 694 } 695 696 /// process mouse event; return true if event is processed by widget. 697 override bool onMouseEvent(MouseEvent event) { 698 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Right) { 699 if (popupMenuListener.assigned) { 700 MenuItem menu = popupMenuListener(_item.root, _item); 701 if (menu) { 702 PopupMenu popupMenu = new PopupMenu(menu); 703 PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, event.x, event.y); 704 popup.flags = PopupFlags.CloseOnClickOutside; 705 return true; 706 } 707 } 708 } 709 return super.onMouseEvent(event); 710 } 711 712 void updateWidget() { 713 if (_expander) { 714 _expander.drawable = _item.expanded ? "arrow_right_down_black" : "arrow_right_hollow"; 715 } 716 if (_item.isVisible) 717 visibility = Visibility.Visible; 718 else 719 visibility = Visibility.Gone; 720 if (_item.isSelected) 721 setState(State.Selected); 722 else 723 resetState(State.Selected); 724 if (_item.isDefault) 725 setState(State.Default); 726 else 727 resetState(State.Default); 728 } 729 } 730 731 732 733 /// Abstract tree widget 734 class TreeWidgetBase : ScrollWidget, OnTreeContentChangeListener, OnTreeStateChangeListener, OnTreeSelectionChangeListener, OnTreeExpandedStateListener, OnKeyHandler { 735 736 protected TreeItems _tree; 737 738 @property ref TreeItems items() { return _tree; } 739 740 Signal!OnTreeSelectionChangeListener selectionChange; 741 Signal!OnTreeExpandedStateListener expandedChange; 742 /// allows to provide individual popup menu for items 743 Listener!OnTreePopupMenuListener popupMenu; 744 745 protected bool _needUpdateWidgets; 746 protected bool _needUpdateWidgetStates; 747 748 protected bool _noCollapseForSingleTopLevelItem; 749 @property bool noCollapseForSingleTopLevelItem() { 750 return _noCollapseForSingleTopLevelItem; 751 } 752 @property TreeWidgetBase noCollapseForSingleTopLevelItem(bool flg) { 753 _noCollapseForSingleTopLevelItem = flg; 754 if (_tree) 755 _tree.noCollapseForSingleTopLevelItem = flg; 756 return this; 757 } 758 759 protected MenuItem onTreeItemPopupMenu(TreeItems source, TreeItem selectedItem) { 760 if (popupMenu) 761 return popupMenu(source, selectedItem); 762 return null; 763 } 764 765 /// empty parameter list constructor - for usage by factory 766 this() { 767 this(null); 768 } 769 /// create with ID parameter 770 this(string ID, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) { 771 super(ID, hscrollbarMode, vscrollbarMode); 772 contentWidget = new VerticalLayout("TREE_CONTENT"); 773 _tree = new TreeItems(); 774 _tree.contentListener = this; 775 _tree.stateListener = this; 776 _tree.selectionListener = this; 777 _tree.expandListener = this; 778 779 _needUpdateWidgets = true; 780 _needUpdateWidgetStates = true; 781 acceleratorMap.add( [ 782 new Action(TreeActions.Up, KeyCode.UP, 0), 783 new Action(TreeActions.Down, KeyCode.DOWN, 0), 784 new Action(TreeActions.ScrollLeft, KeyCode.LEFT, 0), 785 new Action(TreeActions.ScrollRight, KeyCode.RIGHT, 0), 786 //new Action(TreeActions.LineBegin, KeyCode.HOME, 0), 787 //new Action(TreeActions.LineEnd, KeyCode.END, 0), 788 new Action(TreeActions.PageUp, KeyCode.PAGEUP, 0), 789 new Action(TreeActions.PageDown, KeyCode.PAGEDOWN, 0), 790 //new Action(TreeActions.PageBegin, KeyCode.PAGEUP, KeyFlag.Control), 791 //new Action(TreeActions.PageEnd, KeyCode.PAGEDOWN, KeyFlag.Control), 792 new Action(TreeActions.ScrollTop, KeyCode.HOME, KeyFlag.Control), 793 new Action(TreeActions.ScrollBottom, KeyCode.END, KeyFlag.Control), 794 new Action(TreeActions.ScrollPageUp, KeyCode.PAGEUP, KeyFlag.Control), 795 new Action(TreeActions.ScrollPageDown, KeyCode.PAGEDOWN, KeyFlag.Control), 796 new Action(TreeActions.ScrollUp, KeyCode.UP, KeyFlag.Control), 797 new Action(TreeActions.ScrollDown, KeyCode.DOWN, KeyFlag.Control), 798 new Action(TreeActions.ScrollLeft, KeyCode.LEFT, KeyFlag.Control), 799 new Action(TreeActions.ScrollRight, KeyCode.RIGHT, KeyFlag.Control), 800 ]); 801 } 802 803 ~this() { 804 if (_tree) { 805 destroy(_tree); 806 _tree = null; 807 } 808 } 809 810 /** Override to use custom tree item widgets. */ 811 protected Widget createItemWidget(TreeItem item) { 812 TreeItemWidget res = new TreeItemWidget(item); 813 res.keyEvent = this; 814 res.popupMenuListener = &onTreeItemPopupMenu; 815 return res; 816 } 817 818 /// returns item by id, null if not found 819 TreeItem findItemById(string id) { 820 return _tree.findItemById(id); 821 } 822 823 override bool onKey(Widget source, KeyEvent event) { 824 if (event.action == KeyAction.KeyDown) { 825 Action action = findKeyAction(event.keyCode, event.flags); // & (KeyFlag.Shift | KeyFlag.Alt | KeyFlag.Control) 826 if (action !is null) { 827 return handleAction(action); 828 } 829 } 830 return false; 831 } 832 833 protected void addWidgets(TreeItem item) { 834 if (item.level > 0) 835 _contentWidget.addChild(createItemWidget(item)); 836 for (int i = 0; i < item.childCount; i++) 837 addWidgets(item.child(i)); 838 } 839 840 protected void updateWidgets() { 841 _contentWidget.removeAllChildren(); 842 addWidgets(_tree); 843 _needUpdateWidgets = false; 844 } 845 846 void clearAllItems() { 847 items.clear(); 848 updateWidgets(); 849 requestLayout(); 850 } 851 852 protected void updateWidgetStates() { 853 for (int i = 0; i < _contentWidget.childCount; i++) { 854 TreeItemWidget child = cast(TreeItemWidget)_contentWidget.child(i); 855 if (child) 856 child.updateWidget(); 857 } 858 _needUpdateWidgetStates = false; 859 } 860 861 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 862 override void layout(Rect rc) { 863 if (visibility == Visibility.Gone) { 864 return; 865 } 866 if (_needUpdateWidgets) 867 updateWidgets(); 868 if (_needUpdateWidgetStates) 869 updateWidgetStates(); 870 super.layout(rc); 871 } 872 873 override Point minimumVisibleContentSize() { 874 return Point(100.pointsToPixels, 100.pointsToPixels); 875 } 876 877 /// calculate full content size in pixels 878 override Point fullContentSize() { 879 if (_needUpdateWidgets) 880 updateWidgets(); 881 if (_needUpdateWidgetStates) 882 updateWidgetStates(); 883 return super.fullContentSize(); 884 //_contentWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED); 885 //return Point(_contentWidget.measuredWidth,_contentWidget.measuredHeight); 886 } 887 888 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 889 override void measure(int parentWidth, int parentHeight) { 890 if (visibility == Visibility.Gone) { 891 return; 892 } 893 if (_needUpdateWidgets) 894 updateWidgets(); 895 if (_needUpdateWidgetStates) 896 updateWidgetStates(); 897 super.measure(parentWidth, parentHeight); 898 } 899 900 /// listener 901 override void onTreeContentChange(TreeItems source) { 902 _needUpdateWidgets = true; 903 requestLayout(); 904 //updateScrollBars(); 905 } 906 907 override void onTreeStateChange(TreeItems source) { 908 _needUpdateWidgetStates = true; 909 requestLayout(); 910 //updateScrollBars(); 911 } 912 913 override void onTreeExpandedStateChange(TreeItems source, TreeItem item) { 914 if (expandedChange.assigned) 915 expandedChange(source, item); 916 layout(pos); 917 //requestLayout(); 918 //updateScrollBars(); 919 } 920 921 TreeItemWidget findItemWidget(TreeItem item) { 922 for (int i = 0; i < _contentWidget.childCount; i++) { 923 TreeItemWidget child = cast(TreeItemWidget) _contentWidget.child(i); 924 if (child && child.item is item) 925 return child; 926 } 927 return null; 928 } 929 930 override void onTreeItemSelected(TreeItems source, TreeItem selectedItem, bool activated) { 931 TreeItemWidget selected = findItemWidget(selectedItem); 932 if (selected && selected.visibility == Visibility.Visible) { 933 selected.setFocus(); 934 makeWidgetVisible(selected, false, true); 935 } 936 if (selectionChange.assigned) 937 selectionChange(source, selectedItem, activated); 938 } 939 940 void makeItemVisible(TreeItem item) { 941 TreeItemWidget widget = findItemWidget(item); 942 if (widget && widget.visibility == Visibility.Visible) { 943 makeWidgetVisible(widget, false, true); 944 } 945 } 946 947 void clearSelection() { 948 _tree.selectItem(null); 949 } 950 951 void selectItem(TreeItem item, bool makeVisible = true) { 952 if (!item) { 953 clearSelection(); 954 return; 955 } 956 _tree.selectItem(item); 957 if (makeVisible) 958 makeItemVisible(item); 959 } 960 961 void selectItem(string itemId, bool makeVisible = true) { 962 TreeItem item = findItemById(itemId); 963 selectItem(item, makeVisible); 964 } 965 966 override protected bool handleAction(const Action a) { 967 Log.d("tree.handleAction ", a.id); 968 switch (a.id) with(TreeActions) 969 { 970 case ScrollLeft: 971 if (_hscrollbar) 972 _hscrollbar.sendScrollEvent(ScrollAction.LineUp); 973 break; 974 case ScrollRight: 975 if (_hscrollbar) 976 _hscrollbar.sendScrollEvent(ScrollAction.LineDown); 977 break; 978 case ScrollUp: 979 if (_vscrollbar) 980 _vscrollbar.sendScrollEvent(ScrollAction.LineUp); 981 break; 982 case ScrollPageUp: 983 if (_vscrollbar) 984 _vscrollbar.sendScrollEvent(ScrollAction.PageUp); 985 break; 986 case ScrollDown: 987 if (_vscrollbar) 988 _vscrollbar.sendScrollEvent(ScrollAction.LineDown); 989 break; 990 case ScrollPageDown: 991 if (_vscrollbar) 992 _vscrollbar.sendScrollEvent(ScrollAction.PageDown); 993 break; 994 case Up: 995 _tree.selectPrevious(); 996 break; 997 case Down: 998 _tree.selectNext(); 999 break; 1000 case PageUp: 1001 // TODO: implement page up 1002 _tree.selectPrevious(); 1003 break; 1004 case PageDown: 1005 // TODO: implement page down 1006 _tree.selectPrevious(); 1007 break; 1008 default: 1009 return super.handleAction(a); 1010 } 1011 return true; 1012 } 1013 } 1014 1015 /// Tree widget with items which can have icons and labels 1016 class TreeWidget : TreeWidgetBase { 1017 /// empty parameter list constructor - for usage by factory 1018 this() { 1019 this(null); 1020 } 1021 /// create with ID parameter 1022 this(string ID, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) { 1023 super(ID, hscrollbarMode, vscrollbarMode); 1024 } 1025 }