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 /// Support foreach 272 int opApply(int delegate(ref TreeItem) callback) { 273 return _children.opApply(callback); 274 } 275 276 /// returns true if item has at least one child 277 @property bool hasChildren() { return childCount > 0; } 278 279 /// returns number of children of this widget 280 @property int childCount() { return _children.count; } 281 /// returns child by index 282 TreeItem child(int index) { return _children.get(index); } 283 /// adds child, returns added item 284 TreeItem addChild(TreeItem item, int index = -1) { 285 TreeItem res = _children.insert(item, index).parent(this).level(_level + 1); 286 root.onUpdate(res); 287 return res; 288 } 289 /// removes child, returns removed item 290 TreeItem removeChild(int index) { 291 if (index < 0 || index >= _children.count) 292 return null; 293 TreeItem res = _children.remove(index); 294 TreeItem newSelection = null; 295 if (res !is null) { 296 res.parent = null; 297 if (root && root.selectedItem is res) { 298 if (index < _children.count) 299 newSelection = _children[index]; 300 else if (index > 0) 301 newSelection = _children[index - 1]; 302 else 303 newSelection = this; 304 } 305 } 306 root.selectItem(newSelection); 307 root.onUpdate(this); 308 return res; 309 } 310 /// removes child by reference, returns removed item 311 TreeItem removeChild(TreeItem child) { 312 TreeItem res = null; 313 int index = _children.indexOf(child); 314 return removeChild(index); 315 } 316 /// removes child by ID, returns removed item 317 TreeItem removeChild(string ID) { 318 TreeItem res = null; 319 int index = _children.indexOf(ID); 320 return removeChild(index); 321 } 322 /// returns index of widget in child list, -1 if passed widget is not a child of this widget 323 int childIndex(TreeItem item) { return _children.indexOf(item); } 324 /// notify listeners 325 protected void onUpdate(TreeItem item) { 326 if (root) 327 root.onUpdate(item); 328 } 329 protected void toggleExpand(TreeItem item) { 330 root.toggleExpand(item); 331 } 332 protected void selectItem(TreeItem item) { 333 root.selectItem(item); 334 } 335 protected void activateItem(TreeItem item) { 336 root.activateItem(item); 337 } 338 339 protected TreeItem nextVisible(TreeItem item, ref bool found) { 340 if (this is item) 341 found = true; 342 else if (found && isVisible) 343 return this; 344 for (int i = 0; i < childCount; i++) { 345 TreeItem res = child(i).nextVisible(item, found); 346 if (res) 347 return res; 348 } 349 return null; 350 } 351 352 protected TreeItem prevVisible(TreeItem item, ref TreeItem prevFoundVisible) { 353 if (this is item) 354 return prevFoundVisible; 355 else if (isVisible) 356 prevFoundVisible = this; 357 for (int i = 0; i < childCount; i++) { 358 TreeItem res = child(i).prevVisible(item, prevFoundVisible); 359 if (res) 360 return res; 361 } 362 return null; 363 } 364 365 /// returns item by id, null if not found 366 TreeItem findItemById(string id) { 367 if (_id.equal(id)) 368 return this; 369 for (int i = 0; i < childCount; i++) { 370 TreeItem res = child(i).findItemById(id); 371 if (res) 372 return res; 373 } 374 return null; 375 } 376 } 377 378 interface OnTreeContentChangeListener { 379 void onTreeContentChange(TreeItems source); 380 } 381 382 interface OnTreeStateChangeListener { 383 void onTreeStateChange(TreeItems source); 384 } 385 386 interface OnTreeExpandedStateListener { 387 void onTreeExpandedStateChange(TreeItems source, TreeItem item); 388 } 389 390 interface OnTreeSelectionChangeListener { 391 void onTreeItemSelected(TreeItems source, TreeItem selectedItem, bool activated); 392 } 393 394 class TreeItems : TreeItem { 395 // signal handler OnTreeContentChangeListener 396 Listener!OnTreeContentChangeListener contentListener; 397 Listener!OnTreeStateChangeListener stateListener; 398 Listener!OnTreeSelectionChangeListener selectionListener; 399 Listener!OnTreeExpandedStateListener expandListener; 400 401 protected bool _noCollapseForSingleTopLevelItem; 402 @property bool noCollapseForSingleTopLevelItem() { return _noCollapseForSingleTopLevelItem; } 403 @property TreeItems noCollapseForSingleTopLevelItem(bool flg) { _noCollapseForSingleTopLevelItem = flg; return this; } 404 405 protected TreeItem _selectedItem; 406 protected TreeItem _defaultItem; 407 408 this() { 409 super("tree"); 410 } 411 412 /// returns true if this item is root item 413 override @property bool isRoot() { 414 return true; 415 } 416 417 /// notify listeners 418 override protected void onUpdate(TreeItem item) { 419 if (contentListener.assigned) 420 contentListener(this); 421 } 422 423 bool canCollapse(TreeItem item) { 424 if (!_noCollapseForSingleTopLevelItem) 425 return true; 426 if (!hasChildren) 427 return false; 428 if (_children.count == 1 && _children[0] is item) 429 return false; 430 return true; 431 } 432 433 bool canCollapseTopLevel() { 434 if (!_noCollapseForSingleTopLevelItem) 435 return true; 436 if (!hasChildren) 437 return false; 438 if (_children.count == 1) 439 return false; 440 return true; 441 } 442 443 override void toggleExpand(TreeItem item) { 444 bool expandChanged = false; 445 if (item.expanded) { 446 if (item.canCollapse()) { 447 item.collapse(); 448 expandChanged = true; 449 } 450 } else { 451 item.expand(); 452 expandChanged = true; 453 } 454 if (stateListener.assigned) 455 stateListener(this); 456 if (expandChanged && expandListener.assigned) 457 expandListener(this, item); 458 } 459 460 override void selectItem(TreeItem item) { 461 if (_selectedItem is item) 462 return; 463 _selectedItem = item; 464 if (stateListener.assigned) 465 stateListener(this); 466 if (selectionListener.assigned) 467 selectionListener(this, _selectedItem, false); 468 } 469 470 void setDefaultItem(TreeItem item) { 471 _defaultItem = item; 472 if (stateListener.assigned) 473 stateListener(this); 474 } 475 476 override void activateItem(TreeItem item) { 477 if (!(_selectedItem is item)) { 478 _selectedItem = item; 479 if (stateListener.assigned) 480 stateListener(this); 481 } 482 if (selectionListener.assigned) 483 selectionListener(this, _selectedItem, true); 484 } 485 486 @property override TreeItem selectedItem() { 487 return _selectedItem; 488 } 489 490 @property override TreeItem defaultItem() { 491 return _defaultItem; 492 } 493 494 void selectNext() { 495 if (!hasChildren) 496 return; 497 if (!_selectedItem) 498 selectItem(child(0)); 499 bool found = false; 500 TreeItem next = nextVisible(_selectedItem, found); 501 if (next) 502 selectItem(next); 503 } 504 505 void selectPrevious() { 506 if (!hasChildren) 507 return; 508 TreeItem found = null; 509 TreeItem prev = prevVisible(_selectedItem, found); 510 if (prev) 511 selectItem(prev); 512 } 513 } 514 515 /// grid control action codes 516 enum TreeActions : int { 517 /// no action 518 None = 0, 519 /// move selection up 520 Up = 2000, 521 /// move selection down 522 Down, 523 /// move selection left 524 Left, 525 /// move selection right 526 Right, 527 528 /// scroll up, w/o changing selection 529 ScrollUp, 530 /// scroll down, w/o changing selection 531 ScrollDown, 532 /// scroll left, w/o changing selection 533 ScrollLeft, 534 /// scroll right, w/o changing selection 535 ScrollRight, 536 537 /// scroll top w/o changing selection 538 ScrollTop, 539 /// scroll bottom, w/o changing selection 540 ScrollBottom, 541 542 /// scroll up, w/o changing selection 543 ScrollPageUp, 544 /// scroll down, w/o changing selection 545 ScrollPageDown, 546 /// scroll left, w/o changing selection 547 ScrollPageLeft, 548 /// scroll right, w/o changing selection 549 ScrollPageRight, 550 551 /// move cursor one page up 552 PageUp, 553 /// move cursor one page up with selection 554 SelectPageUp, 555 /// move cursor one page down 556 PageDown, 557 /// move cursor one page down with selection 558 SelectPageDown, 559 /// move cursor to the beginning of page 560 PageBegin, 561 /// move cursor to the beginning of page with selection 562 SelectPageBegin, 563 /// move cursor to the end of page 564 PageEnd, 565 /// move cursor to the end of page with selection 566 SelectPageEnd, 567 /// move cursor to the beginning of line 568 LineBegin, 569 /// move cursor to the beginning of line with selection 570 SelectLineBegin, 571 /// move cursor to the end of line 572 LineEnd, 573 /// move cursor to the end of line with selection 574 SelectLineEnd, 575 /// move cursor to the beginning of document 576 DocumentBegin, 577 /// move cursor to the beginning of document with selection 578 SelectDocumentBegin, 579 /// move cursor to the end of document 580 DocumentEnd, 581 /// move cursor to the end of document with selection 582 SelectDocumentEnd, 583 } 584 585 586 const int DOUBLE_CLICK_TIME_MS = 250; 587 588 interface OnTreePopupMenuListener { 589 MenuItem onTreeItemPopupMenu(TreeItems source, TreeItem selectedItem); 590 } 591 592 /// Item widget for displaying in trees 593 class TreeItemWidget : HorizontalLayout { 594 TreeItem _item; 595 TextWidget _tab; 596 ImageWidget _expander; 597 ImageWidget _icon; 598 TextWidget _label; 599 HorizontalLayout _body; 600 long lastClickTime; 601 602 Listener!OnTreePopupMenuListener popupMenuListener; 603 604 @property TreeItem item() { return _item; } 605 606 607 this(TreeItem item) { 608 super(item.id); 609 styleId = STYLE_TREE_ITEM; 610 611 clickable = true; 612 focusable = true; 613 trackHover = true; 614 615 _item = item; 616 _tab = new TextWidget("tab"); 617 //dchar[] tabText; 618 //dchar[] singleTab = [' ', ' ', ' ', ' ']; 619 //for (int i = 1; i < _item.level; i++) 620 // tabText ~= singleTab; 621 //_tab.text = cast(dstring)tabText; 622 int level = _item.level - 1; 623 if (!_item.root.canCollapseTopLevel()) 624 level--; 625 if (level < 0) 626 level = 0; 627 int w = level * style.font.size * 3 / 4; 628 _tab.minWidth = w; 629 _tab.maxWidth = w; 630 if (_item.canCollapse()) { 631 _expander = new ImageWidget("expander", _item.hasChildren && _item.expanded ? "arrow_right_down_black" : "arrow_right_hollow"); 632 _expander.styleId = STYLE_TREE_ITEM_EXPAND_ICON; 633 _expander.clickable = true; 634 _expander.trackHover = true; 635 _expander.visibility = _item.hasChildren ? Visibility.Visible : Visibility.Invisible; 636 //_expander.setState(State.Parent); 637 638 _expander.click = delegate(Widget source) { 639 _item.selectItem(_item); 640 _item.toggleExpand(_item); 641 return true; 642 }; 643 } 644 click = delegate(Widget source) { 645 long ts = currentTimeMillis(); 646 _item.selectItem(_item); 647 if (ts - lastClickTime < DOUBLE_CLICK_TIME_MS) { 648 if (_item.hasChildren) { 649 _item.toggleExpand(_item); 650 } else { 651 _item.activateItem(_item); 652 } 653 } 654 lastClickTime = ts; 655 return true; 656 }; 657 _body = new HorizontalLayout("item_body"); 658 _body.styleId = STYLE_TREE_ITEM_BODY; 659 _body.setState(State.Parent); 660 if (_item.iconRes.length > 0) { 661 _icon = new ImageWidget("icon", _item.iconRes); 662 _icon.styleId = STYLE_TREE_ITEM_ICON; 663 _icon.setState(State.Parent); 664 _icon.padding(Rect(0, 0, BACKEND_GUI ? 5 : 0, 0)); 665 _body.addChild(_icon); 666 } 667 _label = new TextWidget("label", _item.text); 668 _label.styleId = STYLE_TREE_ITEM_LABEL; 669 _label.setState(State.Parent); 670 _body.addChild(_label); 671 // append children 672 addChild(_tab); 673 if (_expander) 674 addChild(_expander); 675 addChild(_body); 676 } 677 678 override bool onKeyEvent(KeyEvent event) { 679 if (keyEvent.assigned && keyEvent(this, event)) 680 return true; // processed by external handler 681 if (!focused || !visible) 682 return false; 683 if (event.action != KeyAction.KeyDown) 684 return false; 685 int action = 0; 686 switch (event.keyCode) with(KeyCode) { 687 case SPACE: 688 case RETURN: 689 if (_item.hasChildren) 690 _item.toggleExpand(_item); 691 else 692 _item.activateItem(_item); 693 return true; 694 default: 695 break; 696 } 697 return false; 698 } 699 700 /// process mouse event; return true if event is processed by widget. 701 override bool onMouseEvent(MouseEvent event) { 702 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Right) { 703 if (popupMenuListener.assigned) { 704 MenuItem menu = popupMenuListener(_item.root, _item); 705 if (menu) { 706 PopupMenu popupMenu = new PopupMenu(menu); 707 PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, event.x, event.y); 708 popup.flags = PopupFlags.CloseOnClickOutside; 709 return true; 710 } 711 } 712 } 713 return super.onMouseEvent(event); 714 } 715 716 void updateWidget() { 717 if (_expander) { 718 _expander.drawable = _item.expanded ? "arrow_right_down_black" : "arrow_right_hollow"; 719 } 720 if (_item.isVisible) 721 visibility = Visibility.Visible; 722 else 723 visibility = Visibility.Gone; 724 if (_item.isSelected) 725 setState(State.Selected); 726 else 727 resetState(State.Selected); 728 if (_item.isDefault) 729 setState(State.Default); 730 else 731 resetState(State.Default); 732 } 733 } 734 735 736 737 /// Abstract tree widget 738 class TreeWidgetBase : ScrollWidget, OnTreeContentChangeListener, OnTreeStateChangeListener, OnTreeSelectionChangeListener, OnTreeExpandedStateListener, OnKeyHandler { 739 740 protected TreeItems _tree; 741 742 @property ref TreeItems items() { return _tree; } 743 744 Signal!OnTreeSelectionChangeListener selectionChange; 745 Signal!OnTreeExpandedStateListener expandedChange; 746 /// allows to provide individual popup menu for items 747 Listener!OnTreePopupMenuListener popupMenu; 748 749 protected bool _needUpdateWidgets; 750 protected bool _needUpdateWidgetStates; 751 752 protected bool _noCollapseForSingleTopLevelItem; 753 @property bool noCollapseForSingleTopLevelItem() { 754 return _noCollapseForSingleTopLevelItem; 755 } 756 @property TreeWidgetBase noCollapseForSingleTopLevelItem(bool flg) { 757 _noCollapseForSingleTopLevelItem = flg; 758 if (_tree) 759 _tree.noCollapseForSingleTopLevelItem = flg; 760 return this; 761 } 762 763 protected MenuItem onTreeItemPopupMenu(TreeItems source, TreeItem selectedItem) { 764 if (popupMenu) 765 return popupMenu(source, selectedItem); 766 return null; 767 } 768 769 /// empty parameter list constructor - for usage by factory 770 this() { 771 this(null); 772 } 773 /// create with ID parameter 774 this(string ID, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) { 775 super(ID, hscrollbarMode, vscrollbarMode); 776 contentWidget = new VerticalLayout("TREE_CONTENT"); 777 _tree = new TreeItems(); 778 _tree.contentListener = this; 779 _tree.stateListener = this; 780 _tree.selectionListener = this; 781 _tree.expandListener = this; 782 783 _needUpdateWidgets = true; 784 _needUpdateWidgetStates = true; 785 acceleratorMap.add( [ 786 new Action(TreeActions.Up, KeyCode.UP, 0), 787 new Action(TreeActions.Down, KeyCode.DOWN, 0), 788 new Action(TreeActions.ScrollLeft, KeyCode.LEFT, 0), 789 new Action(TreeActions.ScrollRight, KeyCode.RIGHT, 0), 790 //new Action(TreeActions.LineBegin, KeyCode.HOME, 0), 791 //new Action(TreeActions.LineEnd, KeyCode.END, 0), 792 new Action(TreeActions.PageUp, KeyCode.PAGEUP, 0), 793 new Action(TreeActions.PageDown, KeyCode.PAGEDOWN, 0), 794 //new Action(TreeActions.PageBegin, KeyCode.PAGEUP, KeyFlag.Control), 795 //new Action(TreeActions.PageEnd, KeyCode.PAGEDOWN, KeyFlag.Control), 796 new Action(TreeActions.ScrollTop, KeyCode.HOME, KeyFlag.Control), 797 new Action(TreeActions.ScrollBottom, KeyCode.END, KeyFlag.Control), 798 new Action(TreeActions.ScrollPageUp, KeyCode.PAGEUP, KeyFlag.Control), 799 new Action(TreeActions.ScrollPageDown, KeyCode.PAGEDOWN, KeyFlag.Control), 800 new Action(TreeActions.ScrollUp, KeyCode.UP, KeyFlag.Control), 801 new Action(TreeActions.ScrollDown, KeyCode.DOWN, KeyFlag.Control), 802 new Action(TreeActions.ScrollLeft, KeyCode.LEFT, KeyFlag.Control), 803 new Action(TreeActions.ScrollRight, KeyCode.RIGHT, KeyFlag.Control), 804 ]); 805 } 806 807 ~this() { 808 if (_tree) { 809 destroy(_tree); 810 _tree = null; 811 } 812 } 813 814 /** Override to use custom tree item widgets. */ 815 protected Widget createItemWidget(TreeItem item) { 816 TreeItemWidget res = new TreeItemWidget(item); 817 res.keyEvent = this; 818 res.popupMenuListener = &onTreeItemPopupMenu; 819 return res; 820 } 821 822 /// returns item by id, null if not found 823 TreeItem findItemById(string id) { 824 return _tree.findItemById(id); 825 } 826 827 override bool onKey(Widget source, KeyEvent event) { 828 if (event.action == KeyAction.KeyDown) { 829 Action action = findKeyAction(event.keyCode, event.flags); // & (KeyFlag.Shift | KeyFlag.Alt | KeyFlag.Control) 830 if (action !is null) { 831 return handleAction(action); 832 } 833 } 834 return false; 835 } 836 837 protected void addWidgets(TreeItem item) { 838 if (item.level > 0) 839 _contentWidget.addChild(createItemWidget(item)); 840 for (int i = 0; i < item.childCount; i++) 841 addWidgets(item.child(i)); 842 } 843 844 protected void updateWidgets() { 845 _contentWidget.removeAllChildren(); 846 addWidgets(_tree); 847 _needUpdateWidgets = false; 848 } 849 850 void clearAllItems() { 851 items.clear(); 852 updateWidgets(); 853 requestLayout(); 854 } 855 856 protected void updateWidgetStates() { 857 for (int i = 0; i < _contentWidget.childCount; i++) { 858 TreeItemWidget child = cast(TreeItemWidget)_contentWidget.child(i); 859 if (child) 860 child.updateWidget(); 861 } 862 _needUpdateWidgetStates = false; 863 } 864 865 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 866 override void layout(Rect rc) { 867 if (visibility == Visibility.Gone) { 868 return; 869 } 870 if (_needUpdateWidgets) 871 updateWidgets(); 872 if (_needUpdateWidgetStates) 873 updateWidgetStates(); 874 super.layout(rc); 875 } 876 877 override Point minimumVisibleContentSize() { 878 return Point(100.pointsToPixels, 100.pointsToPixels); 879 } 880 881 /// calculate full content size in pixels 882 override Point fullContentSize() { 883 if (_needUpdateWidgets) 884 updateWidgets(); 885 if (_needUpdateWidgetStates) 886 updateWidgetStates(); 887 return super.fullContentSize(); 888 //_contentWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED); 889 //return Point(_contentWidget.measuredWidth,_contentWidget.measuredHeight); 890 } 891 892 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 893 override void measure(int parentWidth, int parentHeight) { 894 if (visibility == Visibility.Gone) { 895 return; 896 } 897 if (_needUpdateWidgets) 898 updateWidgets(); 899 if (_needUpdateWidgetStates) 900 updateWidgetStates(); 901 super.measure(parentWidth, parentHeight); 902 } 903 904 /// listener 905 override void onTreeContentChange(TreeItems source) { 906 _needUpdateWidgets = true; 907 requestLayout(); 908 //updateScrollBars(); 909 } 910 911 override void onTreeStateChange(TreeItems source) { 912 _needUpdateWidgetStates = true; 913 requestLayout(); 914 //updateScrollBars(); 915 } 916 917 override void onTreeExpandedStateChange(TreeItems source, TreeItem item) { 918 if (expandedChange.assigned) 919 expandedChange(source, item); 920 layout(pos); 921 //requestLayout(); 922 //updateScrollBars(); 923 } 924 925 TreeItemWidget findItemWidget(TreeItem item) { 926 for (int i = 0; i < _contentWidget.childCount; i++) { 927 TreeItemWidget child = cast(TreeItemWidget) _contentWidget.child(i); 928 if (child && child.item is item) 929 return child; 930 } 931 return null; 932 } 933 934 override void onTreeItemSelected(TreeItems source, TreeItem selectedItem, bool activated) { 935 TreeItemWidget selected = findItemWidget(selectedItem); 936 if (selected && selected.visibility == Visibility.Visible) { 937 selected.setFocus(); 938 makeWidgetVisible(selected, false, true); 939 } 940 if (selectionChange.assigned) 941 selectionChange(source, selectedItem, activated); 942 } 943 944 void makeItemVisible(TreeItem item) { 945 TreeItemWidget widget = findItemWidget(item); 946 if (widget && widget.visibility == Visibility.Visible) { 947 makeWidgetVisible(widget, false, true); 948 } 949 } 950 951 void clearSelection() { 952 _tree.selectItem(null); 953 } 954 955 void selectItem(TreeItem item, bool makeVisible = true) { 956 if (!item) { 957 clearSelection(); 958 return; 959 } 960 _tree.selectItem(item); 961 if (makeVisible) 962 makeItemVisible(item); 963 } 964 965 void selectItem(string itemId, bool makeVisible = true) { 966 TreeItem item = findItemById(itemId); 967 selectItem(item, makeVisible); 968 } 969 970 override protected bool handleAction(const Action a) { 971 Log.d("tree.handleAction ", a.id); 972 switch (a.id) with(TreeActions) 973 { 974 case ScrollLeft: 975 if (_hscrollbar) 976 _hscrollbar.sendScrollEvent(ScrollAction.LineUp); 977 break; 978 case ScrollRight: 979 if (_hscrollbar) 980 _hscrollbar.sendScrollEvent(ScrollAction.LineDown); 981 break; 982 case ScrollUp: 983 if (_vscrollbar) 984 _vscrollbar.sendScrollEvent(ScrollAction.LineUp); 985 break; 986 case ScrollPageUp: 987 if (_vscrollbar) 988 _vscrollbar.sendScrollEvent(ScrollAction.PageUp); 989 break; 990 case ScrollDown: 991 if (_vscrollbar) 992 _vscrollbar.sendScrollEvent(ScrollAction.LineDown); 993 break; 994 case ScrollPageDown: 995 if (_vscrollbar) 996 _vscrollbar.sendScrollEvent(ScrollAction.PageDown); 997 break; 998 case Up: 999 _tree.selectPrevious(); 1000 break; 1001 case Down: 1002 _tree.selectNext(); 1003 break; 1004 case PageUp: 1005 // TODO: implement page up 1006 _tree.selectPrevious(); 1007 break; 1008 case PageDown: 1009 // TODO: implement page down 1010 _tree.selectPrevious(); 1011 break; 1012 default: 1013 return super.handleAction(a); 1014 } 1015 return true; 1016 } 1017 1018 override void invalidate() 1019 { 1020 super.invalidate(); 1021 updateWidgets(); 1022 } 1023 } 1024 1025 /// Tree widget with items which can have icons and labels 1026 class TreeWidget : TreeWidgetBase { 1027 /// empty parameter list constructor - for usage by factory 1028 this() { 1029 this(null); 1030 } 1031 /// create with ID parameter 1032 this(string ID, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) { 1033 super(ID, hscrollbarMode, vscrollbarMode); 1034 } 1035 }