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