1 /******************************************************************************* 2 * Copyright (c) 2000, 2019 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Tom Schindl <tom.schindl@bestsolution.at> - bug 153993, bug 167323, bug 175192 14 * Lasse Knudsen, bug 205700 15 * Micah Hainline, bug 210448 16 * Michael Schneider, bug 210747 17 * Bruce Sutton, bug 221768 18 * Matthew Hall, bug 221988 19 * Julien Desgats, bug 203950 20 * Alexander Fedorov <alexander.fedorov@arsysop.ru> - Bug 548314 21 *******************************************************************************/ 22 23 package org.eclipse.jface.viewers; 24 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.LinkedList; 28 import java.util.List; 29 30 import org.eclipse.core.runtime.Assert; 31 import org.eclipse.core.runtime.IStatus; 32 import org.eclipse.core.runtime.ListenerList; 33 import org.eclipse.core.runtime.Status; 34 import org.eclipse.jface.internal.InternalPolicy; 35 import org.eclipse.jface.util.Policy; 36 import org.eclipse.jface.util.SafeRunnable; 37 import org.eclipse.swt.SWT; 38 import org.eclipse.swt.custom.BusyIndicator; 39 import org.eclipse.swt.events.SelectionEvent; 40 import org.eclipse.swt.events.SelectionListener; 41 import org.eclipse.swt.events.TreeEvent; 42 import org.eclipse.swt.events.TreeListener; 43 import org.eclipse.swt.graphics.Point; 44 import org.eclipse.swt.widgets.Control; 45 import org.eclipse.swt.widgets.Item; 46 import org.eclipse.swt.widgets.Widget; 47 48 /** 49 * Abstract base implementation for tree-structure-oriented viewers (trees and 50 * table trees). 51 * <p> 52 * Nodes in the tree can be in either an expanded or a collapsed state, 53 * depending on whether the children on a node are visible. This class 54 * introduces public methods for controlling the expanding and collapsing of 55 * nodes. 56 * </p> 57 * <p> 58 * As of 3.2, AbstractTreeViewer supports multiple equal elements (each with a 59 * different parent chain) in the tree. This support requires that clients 60 * enable the element map by calling <code>setUseHashlookup(true)</code>. 61 * </p> 62 * <p> 63 * Content providers for abstract tree viewers must implement one of the 64 * interfaces <code>ITreeContentProvider</code> or (as of 3.2, to support 65 * multiple equal elements) <code>ITreePathContentProvider</code>. 66 * </p> 67 * <p> 68 * <strong> This class is not intended to be subclassed outside of the JFace 69 * viewers framework.</strong> 70 * </p> 71 * 72 * @see TreeViewer 73 */ 74 public abstract class AbstractTreeViewer extends ColumnViewer { 75 76 /** 77 * Constant indicating that all levels of the tree should be expanded or 78 * collapsed. 79 * 80 * @see #expandToLevel(int) 81 * @see #collapseToLevel(Object, int) 82 */ 83 public static final int ALL_LEVELS = -1; 84 85 /** 86 * List of registered tree listeners (element type: 87 * <code>TreeListener</code>). 88 */ 89 private ListenerList<ITreeViewerListener> treeListeners = new ListenerList<>(); 90 91 /** 92 * The level to which the tree is automatically expanded each time the 93 * viewer's input is changed (that is, by <code>setInput</code>). A value 94 * of 0 means that auto-expand is off. 95 * 96 * @see #setAutoExpandLevel 97 */ 98 private int expandToLevel = 0; 99 100 /** 101 * Indicates if filters should be checked to determine expandability of 102 * a tree node. 103 */ 104 private boolean isExpandableCheckFilters = false; 105 106 /** 107 * Safe runnable used to update an item. 108 */ 109 class UpdateItemSafeRunnable extends SafeRunnable { 110 private Object element; 111 112 private Item item; 113 UpdateItemSafeRunnable(Item item, Object element)114 UpdateItemSafeRunnable(Item item, Object element) { 115 this.item = item; 116 this.element = element; 117 } 118 119 @Override run()120 public void run() { 121 doUpdateItem(item, element); 122 } 123 124 } 125 126 /** 127 * Creates an abstract tree viewer. The viewer has no input, no content 128 * provider, a default label provider, no sorter, no filters, and has 129 * auto-expand turned off. 130 */ AbstractTreeViewer()131 protected AbstractTreeViewer() { 132 // do nothing 133 } 134 135 /** 136 * Adds the given child elements to this viewer as children of the given 137 * parent element. If this viewer does not have a sorter, the elements are 138 * added at the end of the parent's list of children in the order given; 139 * otherwise, the elements are inserted at the appropriate positions. 140 * <p> 141 * This method should be called (by the content provider) when elements have 142 * been added to the model, in order to cause the viewer to accurately 143 * reflect the model. This method only affects the viewer, not the model. 144 * </p> 145 * 146 * @param parentElementOrTreePath 147 * the parent element 148 * @param childElements 149 * the child elements to add 150 */ add(Object parentElementOrTreePath, Object... childElements)151 public void add(Object parentElementOrTreePath, Object... childElements) { 152 Assert.isNotNull(parentElementOrTreePath); 153 assertElementsNotNull(childElements); 154 if (checkBusy()) 155 return; 156 Widget[] widgets = internalFindItems(parentElementOrTreePath); 157 // If parent hasn't been realized yet, just ignore the add. 158 if (widgets.length == 0) { 159 return; 160 } 161 162 for (Widget widget : widgets) { 163 internalAdd(widget, parentElementOrTreePath, childElements); 164 } 165 } 166 167 /** 168 * Find the items for the given element of tree path 169 * 170 * @param parentElementOrTreePath 171 * the element or tree path 172 * @return the items for that element 173 * 174 * @since 3.3 175 */ internalFindItems(Object parentElementOrTreePath)176 final protected Widget[] internalFindItems(Object parentElementOrTreePath) { 177 Widget[] widgets; 178 if (parentElementOrTreePath instanceof TreePath) { 179 TreePath path = (TreePath) parentElementOrTreePath; 180 Widget w = internalFindItem(path); 181 if (w == null) { 182 widgets = new Widget[] {}; 183 } else { 184 widgets = new Widget[] { w }; 185 } 186 } else { 187 widgets = findItems(parentElementOrTreePath); 188 } 189 return widgets; 190 } 191 192 /** 193 * Return the item at the given path or <code>null</code> 194 * 195 * @param path 196 * the path 197 * @return {@link Widget} the item at that path 198 */ internalFindItem(TreePath path)199 private Widget internalFindItem(TreePath path) { 200 Widget[] widgets = findItems(path.getLastSegment()); 201 for (Widget widget : widgets) { 202 if (widget instanceof Item) { 203 Item item = (Item) widget; 204 TreePath p = getTreePathFromItem(item); 205 if (p.equals(path)) { 206 return widget; 207 } 208 } 209 } 210 return null; 211 } 212 213 /** 214 * Adds the given child elements to this viewer as children of the given 215 * parent element. 216 * <p> 217 * EXPERIMENTAL. Not to be used except by JDT. This method was added to 218 * support JDT's explorations into grouping by working sets. This method 219 * cannot be removed without breaking binary backwards compatibility, but 220 * should not be called by clients. 221 * </p> 222 * 223 * @param widget 224 * the widget for the parent element 225 * @param parentElementOrTreePath 226 * the parent element 227 * @param childElements 228 * the child elements to add 229 * @since 3.1 230 */ internalAdd(Widget widget, Object parentElementOrTreePath, Object[] childElements)231 protected void internalAdd(Widget widget, Object parentElementOrTreePath, 232 Object[] childElements) { 233 Object parent; 234 TreePath path; 235 if (parentElementOrTreePath instanceof TreePath) { 236 path = (TreePath) parentElementOrTreePath; 237 parent = path.getLastSegment(); 238 } else { 239 parent = parentElementOrTreePath; 240 path = null; 241 } 242 243 // optimization! 244 // if the widget is not expanded we just invalidate the subtree 245 if (widget instanceof Item) { 246 Item ti = (Item) widget; 247 if (!getExpanded(ti)) { 248 boolean needDummy = isExpandable(ti, path, parent); 249 boolean haveDummy = false; 250 // remove all children 251 Item[] items = getItems(ti); 252 for (Item item : items) { 253 if (item.getData() != null) { 254 disassociate(item); 255 item.dispose(); 256 } else { 257 if (needDummy && !haveDummy) { 258 haveDummy = true; 259 } else { 260 item.dispose(); 261 } 262 } 263 } 264 // append a dummy if necessary 265 if (needDummy && !haveDummy) { 266 newItem(ti, SWT.NULL, -1); 267 } 268 return; 269 } 270 } 271 272 if (childElements.length > 0) { 273 // TODO: Add filtering back? 274 Object[] filtered = filter(parentElementOrTreePath, childElements); 275 ViewerComparator comparator = getComparator(); 276 if (comparator != null) { 277 if (comparator instanceof TreePathViewerSorter) { 278 TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator; 279 if (path == null) { 280 path = internalGetSorterParentPath(widget, comparator); 281 } 282 tpvs.sort(this, path, filtered); 283 } else { 284 comparator.sort(this, filtered); 285 } 286 } 287 createAddedElements(widget, filtered); 288 if (InternalPolicy.DEBUG_LOG_EQUAL_VIEWER_ELEMENTS) { 289 Item[] children = getChildren(widget); 290 Object[] elements = new Object[children.length]; 291 for (int i = 0; i < children.length; i++) { 292 elements[i] = children[i].getData(); 293 } 294 assertElementsNotNull(parent, elements); 295 } 296 } 297 } 298 299 /** 300 * Filter the children elements. 301 * 302 * @param parentElementOrTreePath 303 * the parent element or path 304 * @param elements 305 * the child elements 306 * @return the filter list of children 307 */ filter(Object parentElementOrTreePath, Object[] elements)308 private Object[] filter(Object parentElementOrTreePath, Object[] elements) { 309 ViewerFilter[] filters = getFilters(); 310 if (filters != null) { 311 List<Object> filtered = new ArrayList<>(elements.length); 312 for (Object element : elements) { 313 boolean add = true; 314 for (ViewerFilter filter : filters) { 315 add = filter.select(this, parentElementOrTreePath, 316 element); 317 if (!add) { 318 break; 319 } 320 } 321 if (add) { 322 filtered.add(element); 323 } 324 } 325 return filtered.toArray(); 326 } 327 return elements; 328 } 329 330 /** 331 * Create the new elements in the parent widget. If the child already exists 332 * do nothing. 333 * 334 * @param widget 335 * @param elements 336 * Sorted list of elements to add. 337 */ createAddedElements(Widget widget, Object[] elements)338 private void createAddedElements(Widget widget, Object[] elements) { 339 340 if (elements.length == 1) { 341 if (equals(elements[0], widget.getData())) { 342 return; 343 } 344 } 345 346 ViewerComparator comparator = getComparator(); 347 TreePath parentPath = internalGetSorterParentPath(widget, comparator); 348 Item[] items = getChildren(widget); 349 350 // Optimize for the empty case 351 if (items.length == 0) { 352 for (Object element : elements) { 353 createTreeItem(widget, element, -1); 354 } 355 return; 356 } 357 358 // Optimize for no comparator 359 if (comparator == null) { 360 for (Object element : elements) { 361 if (itemExists(items, element)) { 362 internalRefresh(element); 363 } else { 364 createTreeItem(widget, element, -1); 365 } 366 } 367 return; 368 } 369 // As the items are sorted already we optimize for a 370 // start position. This is the insertion position relative to the 371 // original item array. 372 int indexInItems = 0; 373 374 // Count of elements we have added. See bug 205700 for why this is needed. 375 int newItems = 0; 376 377 elementloop: for (Object element : elements) { 378 // update the index relative to the original item array 379 indexInItems = insertionPosition(items, comparator, 380 indexInItems, element, parentPath); 381 if (indexInItems == items.length) { 382 createTreeItem(widget, element, -1); 383 newItems++; 384 } else { 385 // Search for an item for the element. The comparator might 386 // regard elements as equal when they are not. 387 388 // Use a separate index variable to search within the existing 389 // elements that compare equally, see 390 // TreeViewerTestBug205700.testAddEquallySortedElements. 391 int insertionIndexInItems = indexInItems; 392 while( insertionIndexInItems < items.length 393 && internalCompare(comparator, parentPath, element, 394 items[insertionIndexInItems].getData()) == 0) { 395 // As we cannot assume the sorter is consistent with 396 // equals() - therefore we can 397 // just check against the item prior to this index (if 398 // any) 399 if (items[insertionIndexInItems].getData().equals(element)) { 400 // Found the item for the element. 401 // Refresh the element in case it has new children. 402 internalRefresh(element); 403 // Do not create a new item - continue with the next element. 404 continue elementloop; 405 } 406 insertionIndexInItems++; 407 } 408 // Did we get to the end? 409 if (insertionIndexInItems == items.length) { 410 createTreeItem(widget, element, -1); 411 newItems++; 412 } else { 413 // InsertionIndexInItems is the index in the original array. We 414 // need to correct by the number of new items we have 415 // created. See bug 205700. 416 createTreeItem(widget, element, insertionIndexInItems + newItems); 417 newItems++; 418 } 419 } 420 } 421 } 422 423 /** 424 * See if element is the data of one of the elements in items. 425 * 426 * @param items 427 * @param element 428 * @return <code>true</code> if the element matches. 429 */ itemExists(Item[] items, Object element)430 private boolean itemExists(Item[] items, Object element) { 431 if (usingElementMap()) { 432 Widget[] existingItems = findItems(element); 433 // optimization for two common cases 434 if (existingItems.length == 0) { 435 return false; 436 } else if (existingItems.length == 1) { 437 if (items.length > 0 && existingItems[0] instanceof Item) { 438 Item existingItem = (Item) existingItems[0]; 439 return getParentItem(existingItem) == getParentItem(items[0]); 440 } 441 } 442 } 443 for (Item item : items) { 444 if (item.getData().equals(element)) { 445 return true; 446 } 447 } 448 return false; 449 } 450 451 /** 452 * Returns the index where the item should be inserted. It uses sorter to 453 * determine the correct position, if sorter is not assigned, returns the 454 * index of the element after the last. 455 * 456 * @param items 457 * the items to search 458 * @param comparator 459 * The comparator to use. 460 * @param lastInsertion 461 * the start index to start search for position from this allows 462 * optimizing search for multiple elements that are sorted 463 * themselves. 464 * @param element 465 * element to find position for. 466 * @param parentPath 467 * the tree path for the element's parent or <code>null</code> 468 * if the element is a root element or the sorter is not a 469 * {@link TreePathViewerSorter} 470 * @return the index to use when inserting the element. 471 * 472 */ 473 insertionPosition(Item[] items, ViewerComparator comparator, int lastInsertion, Object element, TreePath parentPath)474 private int insertionPosition(Item[] items, ViewerComparator comparator, 475 int lastInsertion, Object element, TreePath parentPath) { 476 477 int size = items.length; 478 if (comparator == null) { 479 return size; 480 } 481 int min = lastInsertion, max = size - 1; 482 483 while (min <= max) { 484 int mid = (min + max) / 2; 485 Object data = items[mid].getData(); 486 int compare = internalCompare(comparator, parentPath, data, element); 487 if (compare == 0) { 488 return mid;// Return if we already match 489 } 490 if (compare < 0) { 491 min = mid + 1; 492 } else { 493 max = mid - 1; 494 } 495 } 496 return min; 497 498 } 499 500 /** 501 * Returns the index where the item should be inserted. It uses sorter to 502 * determine the correct position, if sorter is not assigned, returns the 503 * index of the element after the last. 504 * 505 * @param parent 506 * The parent widget 507 * @param sorter 508 * The sorter to use. 509 * @param startIndex 510 * the start index to start search for position from this allows 511 * optimizing search for multiple elements that are sorted 512 * themselves. 513 * @param element 514 * element to find position for. 515 * @param currentSize 516 * the current size of the collection 517 * @return the index to use when inserting the element. 518 * 519 */ 520 521 /** 522 * Returns the index where the item should be inserted. 523 * 524 * @param parent 525 * The parent widget the element will be inserted into. 526 * @param element 527 * The element to insert. 528 * @return the index of the element 529 */ indexForElement(Widget parent, Object element)530 protected int indexForElement(Widget parent, Object element) { 531 ViewerComparator comparator = getComparator(); 532 TreePath parentPath = internalGetSorterParentPath(parent, comparator); 533 534 Item[] items = getChildren(parent); 535 int count = items.length; 536 537 if (comparator == null) { 538 return count; 539 } 540 int min = 0, max = count - 1; 541 542 while (min <= max) { 543 int mid = (min + max) / 2; 544 Object data = items[mid].getData(); 545 int compare = internalCompare(comparator, parentPath, data, element); 546 if (compare == 0) { 547 // find first item > element 548 while (compare == 0) { 549 ++mid; 550 if (mid >= count) { 551 break; 552 } 553 data = items[mid].getData(); 554 compare = internalCompare(comparator, parentPath, data, 555 element); 556 } 557 return mid; 558 } 559 if (compare < 0) { 560 min = mid + 1; 561 } else { 562 max = mid - 1; 563 } 564 } 565 return min; 566 } 567 568 /** 569 * Return the tree path that should be used as the parent path for the given 570 * widget and sorter. A <code>null</code> is returned if either the sorter 571 * is not a {@link TreePathViewerSorter} or if the parent widget is not an 572 * {@link Item} (i.e. is the root of the tree). 573 * 574 * @param parent 575 * the parent widget 576 * @param comparator 577 * the sorter 578 * @return the tree path that should be used as the parent path for the 579 * given widget and sorter 580 */ internalGetSorterParentPath(Widget parent, ViewerComparator comparator)581 private TreePath internalGetSorterParentPath(Widget parent, 582 ViewerComparator comparator) { 583 TreePath path; 584 if (comparator instanceof TreePathViewerSorter 585 && parent instanceof Item) { 586 Item item = (Item) parent; 587 path = getTreePathFromItem(item); 588 } else { 589 path = null; 590 } 591 return path; 592 } 593 594 /** 595 * Compare the two elements using the given sorter. If the sorter is a 596 * {@link TreePathViewerSorter}, the provided tree path will be used. If 597 * the tree path is null and the sorter is a tree path sorter, then the 598 * elements are root elements 599 * 600 * @param comparator 601 * the sorter 602 * @param parentPath 603 * the path of the elements' parent 604 * @param e1 605 * the first element 606 * @param e2 607 * the second element 608 * @return the result of comparing the two elements 609 */ internalCompare(ViewerComparator comparator, TreePath parentPath, Object e1, Object e2)610 private int internalCompare(ViewerComparator comparator, 611 TreePath parentPath, Object e1, Object e2) { 612 if (comparator instanceof TreePathViewerSorter) { 613 TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator; 614 return tpvs.compare(this, parentPath, e1, e2); 615 } 616 return comparator.compare(this, e1, e2); 617 } 618 619 @Override getSortedChildren(Object parentElementOrTreePath)620 protected Object[] getSortedChildren(Object parentElementOrTreePath) { 621 Object[] result = getFilteredChildren(parentElementOrTreePath); 622 ViewerComparator comparator = getComparator(); 623 if (parentElementOrTreePath != null 624 && comparator instanceof TreePathViewerSorter) { 625 TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator; 626 627 // be sure we're not modifying the original array from the model 628 result = result.clone(); 629 630 TreePath path = null; 631 if (parentElementOrTreePath instanceof TreePath) { 632 path = (TreePath) parentElementOrTreePath; 633 } else { 634 Object parent = parentElementOrTreePath; 635 Widget w = internalGetWidgetToSelect(parent); 636 if (w != null) { 637 path = internalGetSorterParentPath(w, comparator); 638 } 639 } 640 tpvs.sort(this, path, result); 641 } else if (comparator != null) { 642 // be sure we're not modifying the original array from the model 643 result = result.clone(); 644 comparator.sort(this, result); 645 } 646 return result; 647 } 648 649 /** 650 * Adds the given child element to this viewer as a child of the given 651 * parent element. If this viewer does not have a sorter, the element is 652 * added at the end of the parent's list of children; otherwise, the element 653 * is inserted at the appropriate position. 654 * <p> 655 * This method should be called (by the content provider) when a single 656 * element has been added to the model, in order to cause the viewer to 657 * accurately reflect the model. This method only affects the viewer, not 658 * the model. Note that there is another method for efficiently processing 659 * the simultaneous addition of multiple elements. 660 * </p> 661 * 662 * @param parentElementOrTreePath 663 * the parent element or path 664 * @param childElement 665 * the child element 666 */ add(Object parentElementOrTreePath, Object childElement)667 public void add(Object parentElementOrTreePath, Object childElement) { 668 add(parentElementOrTreePath, new Object[] { childElement }); 669 } 670 671 /** 672 * Adds the given SWT selection listener to the given SWT control. 673 * 674 * @param control 675 * the SWT control 676 * @param listener 677 * the SWT selection listener 678 * @deprecated 679 */ 680 @Deprecated addSelectionListener(Control control, SelectionListener listener)681 protected void addSelectionListener(Control control, 682 SelectionListener listener) { 683 // do nothing 684 } 685 686 /** 687 * Adds a listener for expand and collapse events in this viewer. Has no 688 * effect if an identical listener is already registered. 689 * 690 * @param listener 691 * a tree viewer listener 692 */ addTreeListener(ITreeViewerListener listener)693 public void addTreeListener(ITreeViewerListener listener) { 694 treeListeners.add(listener); 695 } 696 697 /** 698 * Adds the given SWT tree listener to the given SWT control. 699 * 700 * @param control 701 * the SWT control 702 * @param listener 703 * the SWT tree listener 704 */ addTreeListener(Control control, TreeListener listener)705 protected abstract void addTreeListener(Control control, 706 TreeListener listener); 707 708 @Override associate(Object element, Item item)709 protected void associate(Object element, Item item) { 710 Object data = item.getData(); 711 if (data != null && data != element && equals(data, element)) { 712 // workaround for PR 1FV62BT 713 // assumption: elements are equal but not identical 714 // -> remove from map but don't touch children 715 unmapElement(data, item); 716 item.setData(element); 717 mapElement(element, item); 718 } else { 719 // recursively disassociate all 720 super.associate(element, item); 721 } 722 } 723 724 /** 725 * Collapses all nodes of the viewer's tree, starting with the root. This method 726 * is equivalent to <code>collapseToLevel(ALL_LEVELS)</code>. 727 */ collapseAll()728 public void collapseAll() { 729 Object root = getRoot(); 730 if (root != null) { 731 collapseToLevel(root, ALL_LEVELS); 732 } 733 } 734 735 /** 736 * Collapses the subtree rooted at the given element or tree path to the given 737 * level. 738 * <p> 739 * Note that the default implementation of this method does turn redraw off via 740 * this operation via a call to <code>setRedraw</code> 741 * </p> 742 * 743 * @param elementOrTreePath the element or tree path 744 * @param level non-negative level, or <code>ALL_LEVELS</code> to 745 * collapse all levels of the tree 746 */ collapseToLevel(Object elementOrTreePath, int level)747 public void collapseToLevel(Object elementOrTreePath, int level) { 748 Assert.isNotNull(elementOrTreePath); 749 Control control = getControl(); 750 try { 751 control.setRedraw(false); 752 Widget w = internalGetWidgetToSelect(elementOrTreePath); 753 if (w != null) { 754 internalCollapseToLevel(w, level); 755 } 756 } finally { 757 control.setRedraw(true); 758 } 759 } 760 761 /** 762 * Creates all children for the given widget. 763 * <p> 764 * The default implementation of this framework method assumes that 765 * <code>widget.getData()</code> returns the element corresponding to the 766 * node. Note: the node is not visually expanded! You may have to call 767 * <code>parent.setExpanded(true)</code>. 768 * </p> 769 * 770 * @param widget 771 * the widget 772 */ createChildren(final Widget widget)773 protected void createChildren(final Widget widget) { 774 createChildren(widget, true); 775 } 776 777 /** 778 * Creates all children for the given widget. 779 * <p> 780 * The default implementation of this framework method assumes that 781 * <code>widget.getData()</code> returns the element corresponding to the 782 * node. Note: the node is not visually expanded! You may have to call 783 * <code>parent.setExpanded(true)</code>. 784 * </p> 785 * 786 * @param widget 787 * the widget 788 * @param materialize 789 * true if children are expected to be fully materialized 790 */ createChildren(final Widget widget, boolean materialize)791 void createChildren(final Widget widget, boolean materialize) { 792 boolean oldBusy = isBusy(); 793 setBusy(true); 794 try { 795 final Item[] items = getChildren(widget); 796 if (items != null && items.length > 0) { 797 Object data = items[0].getData(); 798 if (data != null) { 799 return; // children already there! 800 } 801 } 802 803 // fix for PR 1FW89L7: 804 // don't complain and remove all "dummies" ... 805 if (items != null) { 806 for (Item item : items) { 807 if (item.getData() != null) { 808 disassociate(item); 809 Assert.isTrue(item.getData() == null, "Second or later child is non -null");//$NON-NLS-1$ 810 811 } 812 item.dispose(); 813 } 814 } 815 Object d = widget.getData(); 816 if (d != null) { 817 Object parentElement = d; 818 Object[] children; 819 if (isTreePathContentProvider() && widget instanceof Item) { 820 TreePath path = getTreePathFromItem((Item) widget); 821 children = getSortedChildren(path); 822 } else { 823 children = getSortedChildren(parentElement); 824 } 825 for (Object element : children) { 826 createTreeItem(widget, element, -1); 827 } 828 } 829 } finally { 830 setBusy(oldBusy); 831 } 832 } 833 834 /** 835 * Creates a single item for the given parent and synchronizes it with the 836 * given element. 837 * 838 * @param parent 839 * the parent widget 840 * @param element 841 * the element 842 * @param index 843 * if non-negative, indicates the position to insert the item 844 * into its parent 845 */ createTreeItem(Widget parent, Object element, int index)846 protected void createTreeItem(Widget parent, Object element, int index) { 847 Item item = newItem(parent, SWT.NULL, index); 848 updateItem(item, element); 849 updatePlus(item, element); 850 } 851 852 /** 853 * The <code>AbstractTreeViewer</code> implementation of this method also 854 * recurses over children of the corresponding element. 855 */ 856 @Override disassociate(Item item)857 protected void disassociate(Item item) { 858 super.disassociate(item); 859 // recursively unmapping the items is only required when 860 // the hash map is used. In the other case disposing 861 // an item will recursively dispose its children. 862 if (usingElementMap()) { 863 disassociateChildren(item); 864 } 865 } 866 867 /** 868 * Disassociates the children of the given SWT item from their corresponding 869 * elements. 870 * 871 * @param item 872 * the widget 873 */ disassociateChildren(Item item)874 private void disassociateChildren(Item item) { 875 Item[] items = getChildren(item); 876 for (Item child : items) { 877 if (child.getData() != null) { 878 disassociate(child); 879 } 880 } 881 } 882 883 @Override doFindInputItem(Object element)884 protected Widget doFindInputItem(Object element) { 885 // compare with root 886 Object root = getRoot(); 887 if (root == null) { 888 return null; 889 } 890 891 if (equals(root, element)) { 892 return getControl(); 893 } 894 return null; 895 } 896 897 @Override doFindItem(Object element)898 protected Widget doFindItem(Object element) { 899 // compare with root 900 Object root = getRoot(); 901 if (root == null) { 902 return null; 903 } 904 905 Item[] items = getChildren(getControl()); 906 if (items != null) { 907 for (Item item : items) { 908 Widget o = internalFindItem(item, element); 909 if (o != null) { 910 return o; 911 } 912 } 913 } 914 return null; 915 } 916 917 /** 918 * Copies the attributes of the given element into the given SWT item. 919 * 920 * @param item 921 * the SWT item 922 * @param element 923 * the element 924 */ doUpdateItem(final Item item, Object element)925 protected void doUpdateItem(final Item item, Object element) { 926 if (item.isDisposed()) { 927 unmapElement(element, item); 928 return; 929 } 930 931 int columnCount = doGetColumnCount(); 932 if (columnCount == 0)// If no columns are created then fake one 933 columnCount = 1; 934 935 ViewerRow viewerRowFromItem = getViewerRowFromItem(item); 936 937 boolean isVirtual = (getControl().getStyle() & SWT.VIRTUAL) != 0; 938 939 // If the control is virtual, we cannot use the cached viewer row object. See bug 188663. 940 if (isVirtual) { 941 viewerRowFromItem = (ViewerRow) viewerRowFromItem.clone(); 942 } 943 944 for (int column = 0; column < columnCount; column++) { 945 ViewerColumn columnViewer = getViewerColumn(column); 946 ViewerCell cellToUpdate = updateCell(viewerRowFromItem, column, 947 element); 948 949 // If the control is virtual, we cannot use the cached cell object. See bug 188663. 950 if (isVirtual) { 951 cellToUpdate = new ViewerCell(cellToUpdate.getViewerRow(), cellToUpdate.getColumnIndex(), element); 952 } 953 954 columnViewer.refresh(cellToUpdate); 955 956 // clear cell (see bug 201280) 957 updateCell(null, 0, null); 958 959 // As it is possible for user code to run the event 960 // loop check here. 961 if (item.isDisposed()) { 962 unmapElement(element, item); 963 return; 964 } 965 966 } 967 } 968 969 /** 970 * Returns <code>true</code> if the given list and array of items refer to 971 * the same model elements. Order is unimportant. 972 * <p> 973 * This method is not intended to be overridden by subclasses. 974 * </p> 975 * 976 * @param items 977 * the list of items 978 * @param current 979 * the array of items 980 * @return <code>true</code> if the refer to the same elements, 981 * <code>false</code> otherwise 982 * 983 * @since 3.1 in TreeViewer, moved to AbstractTreeViewer in 3.3 984 */ isSameSelection(List<Item> items, Item[] current)985 protected boolean isSameSelection(List<Item> items, Item[] current) { 986 // If they are not the same size then they are not equivalent 987 int n = items.size(); 988 if (n != current.length) { 989 return false; 990 } 991 992 CustomHashtable itemSet = newHashtable(n * 2 + 1); 993 for (Item item : items) { 994 Object element = item.getData(); 995 itemSet.put(element, element); 996 } 997 998 // Go through the items of the current collection 999 // If there is a mismatch return false 1000 for (Item c : current) { 1001 if (c.getData() == null || !itemSet.containsKey(c.getData())) { 1002 return false; 1003 } 1004 } 1005 1006 return true; 1007 } 1008 1009 1010 1011 @Override doUpdateItem(Widget widget, Object element, boolean fullMap)1012 protected void doUpdateItem(Widget widget, Object element, boolean fullMap) { 1013 boolean oldBusy = isBusy(); 1014 setBusy(true); 1015 try { 1016 if (widget instanceof Item) { 1017 Item item = (Item) widget; 1018 1019 // ensure that back pointer is correct 1020 if (fullMap) { 1021 associate(element, item); 1022 } else { 1023 Object data = item.getData(); 1024 if (data != null) { 1025 unmapElement(data, item); 1026 } 1027 item.setData(element); 1028 mapElement(element, item); 1029 } 1030 1031 // update icon and label 1032 SafeRunnable.run(new UpdateItemSafeRunnable(item, element)); 1033 } 1034 } finally { 1035 setBusy(oldBusy); 1036 } 1037 } 1038 1039 /** 1040 * Expands all nodes of the viewer's tree, starting with the root. This 1041 * method is equivalent to <code>expandToLevel(ALL_LEVELS)</code>. 1042 */ expandAll()1043 public void expandAll() { 1044 expandToLevel(ALL_LEVELS, true); 1045 } 1046 1047 /** 1048 * Expands all nodes of the viewer's tree, starting with the root. This method 1049 * is equivalent to <code>expandToLevel(ALL_LEVELS)</code>. 1050 * 1051 * @param disableRedraw 1052 * <code>true</code> when drawing operations should be disabled 1053 * during expansion. 1054 * @since 3.14 1055 */ expandAll(boolean disableRedraw)1056 public void expandAll(boolean disableRedraw) { 1057 expandToLevel(ALL_LEVELS, disableRedraw); 1058 } 1059 1060 /** 1061 * Expands the root of the viewer's tree to the given level. 1062 * 1063 * @param level 1064 * non-negative level, or <code>ALL_LEVELS</code> to expand all 1065 * levels of the tree 1066 */ expandToLevel(int level)1067 public void expandToLevel(int level) { 1068 expandToLevel(level, true); 1069 } 1070 1071 /** 1072 * Expands the root of the viewer's tree to the given level. 1073 * 1074 * @param level non-negative level, or <code>ALL_LEVELS</code> to expand 1075 * all levels of the tree 1076 * @param disableRedraw <code>true</code> when drawing operations should be 1077 * disabled during expansion. <code>true</code> when 1078 * drawing operations should be enabled during expansion. 1079 * Prefer using true as this results in a faster UI 1080 * @since 3.14 1081 */ expandToLevel(int level, boolean disableRedraw)1082 public void expandToLevel(int level, boolean disableRedraw) { 1083 BusyIndicator.showWhile(getControl().getDisplay(), () -> { 1084 expandToLevel(getRoot(), level, disableRedraw); 1085 }); 1086 } 1087 1088 /** 1089 * Expands all ancestors of the given element or tree path so that the given 1090 * element becomes visible in this viewer's tree control, and then expands 1091 * the subtree rooted at the given element to the given level. 1092 * 1093 * @param elementOrTreePath 1094 * the element 1095 * @param level 1096 * non-negative level, or <code>ALL_LEVELS</code> to expand all 1097 * levels of the tree 1098 */ expandToLevel(Object elementOrTreePath, int level)1099 public void expandToLevel(Object elementOrTreePath, int level) { 1100 expandToLevel(elementOrTreePath, level, true); 1101 } 1102 1103 /** 1104 * Expands all ancestors of the given element or tree path so that the given 1105 * element becomes visible in this viewer's tree control, and then expands the 1106 * subtree rooted at the given element to the given level. 1107 * 1108 * @param elementOrTreePath the element 1109 * @param level non-negative level, or <code>ALL_LEVELS</code> to 1110 * expand all levels of the tree 1111 * @param disableRedraw <code>true</code> when drawing operations should be 1112 * disabled during expansion. <code>false</code> when 1113 * drawing operations should be enabled during 1114 * expansion. Prefer true as this results in a faster 1115 * UI. 1116 * @since 3.14 1117 */ expandToLevel(Object elementOrTreePath, int level, boolean disableRedraw)1118 public void expandToLevel(Object elementOrTreePath, int level, boolean disableRedraw) { 1119 if (checkBusy()) 1120 return; 1121 Control control = getControl(); 1122 try { 1123 if (disableRedraw) { 1124 control.setRedraw(false); 1125 } 1126 Widget w = internalExpand(elementOrTreePath, true); 1127 if (w != null) { 1128 internalExpandToLevel(w, level); 1129 } 1130 } finally { 1131 if (disableRedraw) { 1132 control.setRedraw(true); 1133 } 1134 } 1135 } 1136 1137 /** 1138 * Fires a tree collapsed event. Only listeners registered at the time this 1139 * method is called are notified. 1140 * 1141 * @param event 1142 * the tree expansion event 1143 * @see ITreeViewerListener#treeCollapsed 1144 */ fireTreeCollapsed(final TreeExpansionEvent event)1145 protected void fireTreeCollapsed(final TreeExpansionEvent event) { 1146 boolean oldBusy = isBusy(); 1147 setBusy(true); 1148 try { 1149 for (ITreeViewerListener l : treeListeners) { 1150 SafeRunnable.run(new SafeRunnable() { 1151 @Override 1152 public void run() { 1153 l.treeCollapsed(event); 1154 } 1155 }); 1156 } 1157 } finally { 1158 setBusy(oldBusy); 1159 } 1160 } 1161 1162 /** 1163 * Fires a tree expanded event. Only listeners registered at the time this 1164 * method is called are notified. 1165 * 1166 * @param event 1167 * the tree expansion event 1168 * @see ITreeViewerListener#treeExpanded 1169 */ fireTreeExpanded(final TreeExpansionEvent event)1170 protected void fireTreeExpanded(final TreeExpansionEvent event) { 1171 boolean oldBusy = isBusy(); 1172 setBusy(true); 1173 try { 1174 for (ITreeViewerListener l : treeListeners) { 1175 SafeRunnable.run(new SafeRunnable() { 1176 @Override 1177 public void run() { 1178 l.treeExpanded(event); 1179 } 1180 }); 1181 } 1182 } finally { 1183 setBusy(oldBusy); 1184 } 1185 } 1186 1187 /** 1188 * Returns the auto-expand level. 1189 * 1190 * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of 1191 * the tree are expanded automatically 1192 * @see #setAutoExpandLevel 1193 */ getAutoExpandLevel()1194 public int getAutoExpandLevel() { 1195 return expandToLevel; 1196 } 1197 1198 /** 1199 * Returns the SWT child items for the given SWT widget. 1200 * 1201 * @param widget 1202 * the widget 1203 * @return the child items 1204 */ getChildren(Widget widget)1205 protected abstract Item[] getChildren(Widget widget); 1206 1207 /** 1208 * Get the child for the widget at index. Note that the default 1209 * implementation is not very efficient and should be overridden if this 1210 * class is implemented. 1211 * 1212 * @param widget 1213 * the widget to check 1214 * @param index 1215 * the index of the widget 1216 * @return Item or <code>null</code> if widget is not a type that can 1217 * contain items. 1218 * 1219 * @throws ArrayIndexOutOfBoundsException 1220 * if the index is not valid. 1221 * @since 3.1 1222 */ getChild(Widget widget, int index)1223 protected Item getChild(Widget widget, int index) { 1224 return getChildren(widget)[index]; 1225 } 1226 1227 /** 1228 * Returns whether the given SWT item is expanded or collapsed. 1229 * 1230 * @param item 1231 * the item 1232 * @return <code>true</code> if the item is considered expanded and 1233 * <code>false</code> if collapsed 1234 */ getExpanded(Item item)1235 protected abstract boolean getExpanded(Item item); 1236 1237 /** 1238 * Returns a list of elements corresponding to expanded nodes in this 1239 * viewer's tree, including currently hidden ones that are marked as 1240 * expanded but are under a collapsed ancestor. 1241 * <p> 1242 * This method is typically used when preserving the interesting state of a 1243 * viewer; <code>setExpandedElements</code> is used during the restore. 1244 * </p> 1245 * 1246 * @return the array of expanded elements 1247 * @see #setExpandedElements 1248 */ getExpandedElements()1249 public Object[] getExpandedElements() { 1250 ArrayList<Item> items = new ArrayList<>(); 1251 internalCollectExpandedItems(items, getControl()); 1252 ArrayList<Object> result = new ArrayList<>(items.size()); 1253 for (Item item : items) { 1254 Object data = item.getData(); 1255 if (data != null) { 1256 result.add(data); 1257 } 1258 } 1259 return result.toArray(); 1260 } 1261 1262 /** 1263 * Returns whether the node corresponding to the given element or tree path 1264 * is expanded or collapsed. 1265 * 1266 * @param elementOrTreePath 1267 * the element 1268 * @return <code>true</code> if the node is expanded, and 1269 * <code>false</code> if collapsed 1270 */ getExpandedState(Object elementOrTreePath)1271 public boolean getExpandedState(Object elementOrTreePath) { 1272 Assert.isNotNull(elementOrTreePath); 1273 Widget item = internalGetWidgetToSelect(elementOrTreePath); 1274 if (item instanceof Item) { 1275 return getExpanded((Item) item); 1276 } 1277 return false; 1278 } 1279 1280 /** 1281 * Returns the number of child items of the given SWT control. 1282 * 1283 * @param control 1284 * the control 1285 * @return the number of children 1286 */ getItemCount(Control control)1287 protected abstract int getItemCount(Control control); 1288 1289 /** 1290 * Returns the number of child items of the given SWT item. 1291 * 1292 * @param item 1293 * the item 1294 * @return the number of children 1295 */ getItemCount(Item item)1296 protected abstract int getItemCount(Item item); 1297 1298 /** 1299 * Returns the child items of the given SWT item. 1300 * 1301 * @param item 1302 * the item 1303 * @return the child items 1304 */ getItems(Item item)1305 protected abstract Item[] getItems(Item item); 1306 1307 /** 1308 * Returns the item after the given item in the tree, or <code>null</code> 1309 * if there is no next item. 1310 * 1311 * @param item 1312 * the item 1313 * @param includeChildren 1314 * <code>true</code> if the children are considered in 1315 * determining which item is next, and <code>false</code> if 1316 * subtrees are ignored 1317 * @return the next item, or <code>null</code> if none 1318 */ getNextItem(Item item, boolean includeChildren)1319 protected Item getNextItem(Item item, boolean includeChildren) { 1320 if (item == null) { 1321 return null; 1322 } 1323 if (includeChildren && getExpanded(item)) { 1324 Item[] children = getItems(item); 1325 if (children != null && children.length > 0) { 1326 return children[0]; 1327 } 1328 } 1329 1330 // next item is either next sibling or next sibling of first 1331 // parent that has a next sibling. 1332 Item parent = getParentItem(item); 1333 if (parent == null) { 1334 return null; 1335 } 1336 Item[] siblings = getItems(parent); 1337 if (siblings != null) { 1338 if (siblings.length <= 1) { 1339 return getNextItem(parent, false); 1340 } 1341 1342 for (int i = 0; i < siblings.length; i++) { 1343 if (siblings[i] == item && i < (siblings.length - 1)) { 1344 return siblings[i + 1]; 1345 } 1346 } 1347 } 1348 return getNextItem(parent, false); 1349 } 1350 1351 /** 1352 * Returns the parent item of the given item in the tree, or 1353 * <code>null</code> if there is no parent item. 1354 * 1355 * @param item 1356 * the item 1357 * @return the parent item, or <code>null</code> if none 1358 */ getParentItem(Item item)1359 protected abstract Item getParentItem(Item item); 1360 1361 /** 1362 * Returns the item before the given item in the tree, or <code>null</code> 1363 * if there is no previous item. 1364 * 1365 * @param item 1366 * the item 1367 * @return the previous item, or <code>null</code> if none 1368 */ getPreviousItem(Item item)1369 protected Item getPreviousItem(Item item) { 1370 // previous item is either right-most visible descendent of previous 1371 // sibling or parent 1372 Item parent = getParentItem(item); 1373 if (parent == null) { 1374 return null; 1375 } 1376 Item[] siblings = getItems(parent); 1377 if (siblings.length == 0 || siblings[0] == item) { 1378 return parent; 1379 } 1380 Item previous = siblings[0]; 1381 for (int i = 1; i < siblings.length; i++) { 1382 if (siblings[i] == item) { 1383 return rightMostVisibleDescendent(previous); 1384 } 1385 previous = siblings[i]; 1386 } 1387 return null; 1388 } 1389 1390 @Override getRawChildren(Object parentElementOrTreePath)1391 protected Object[] getRawChildren(Object parentElementOrTreePath) { 1392 boolean oldBusy = isBusy(); 1393 setBusy(true); 1394 try { 1395 Object parent; 1396 TreePath path; 1397 if (parentElementOrTreePath instanceof TreePath) { 1398 path = (TreePath) parentElementOrTreePath; 1399 parent = path.getLastSegment(); 1400 } else { 1401 parent = parentElementOrTreePath; 1402 path = null; 1403 } 1404 if (parent != null) { 1405 if (equals(parent, getRoot())) { 1406 return super.getRawChildren(parent); 1407 } 1408 IContentProvider cp = getContentProvider(); 1409 if (cp instanceof ITreePathContentProvider) { 1410 ITreePathContentProvider tpcp = (ITreePathContentProvider) cp; 1411 if (path == null) { 1412 // A path was not provided so try and find one 1413 Widget w = findItem(parent); 1414 if (w instanceof Item) { 1415 Item item = (Item) w; 1416 path = getTreePathFromItem(item); 1417 } 1418 if (path == null) { 1419 path = new TreePath(new Object[] { parent }); 1420 } 1421 } 1422 Object[] result = tpcp.getChildren(path); 1423 if (result != null) { 1424 assertElementsNotNull(parent, result); 1425 return result; 1426 } 1427 } else if (cp instanceof ITreeContentProvider) { 1428 ITreeContentProvider tcp = (ITreeContentProvider) cp; 1429 Object[] result = tcp.getChildren(parent); 1430 if (result != null) { 1431 assertElementsNotNull(parent, result); 1432 return result; 1433 } 1434 } 1435 } 1436 return new Object[0]; 1437 } finally { 1438 setBusy(oldBusy); 1439 } 1440 } 1441 1442 /** 1443 * Asserts that the given array of elements is itself non- <code>null</code> 1444 * and contains no <code>null</code> elements. 1445 * 1446 * @param parent 1447 * the parent element 1448 * @param elements 1449 * the array to check 1450 * 1451 * @see #assertElementsNotNull(Object[]) 1452 */ assertElementsNotNull(Object parent, Object[] elements)1453 private void assertElementsNotNull(Object parent, Object[] elements) { 1454 Assert.isNotNull(elements); 1455 for (Object element : elements) { 1456 Assert.isNotNull(element); 1457 } 1458 1459 if (InternalPolicy.DEBUG_LOG_EQUAL_VIEWER_ELEMENTS 1460 && elements.length > 1) { 1461 CustomHashtable elementSet = newHashtable(elements.length * 2); 1462 for (Object element : elements) { 1463 Object old = elementSet.put(element, element); 1464 if (old != null) { 1465 String message = "Sibling elements in viewer must not be equal:\n " //$NON-NLS-1$ 1466 + old + ",\n " + element + ",\n parent: " + parent; //$NON-NLS-1$ //$NON-NLS-2$ 1467 Policy.getLog().log( 1468 new Status(IStatus.WARNING, Policy.JFACE, message, 1469 new RuntimeException())); 1470 return; 1471 } 1472 } 1473 } 1474 } 1475 1476 /** 1477 * Returns all selected items for the given SWT control. 1478 * 1479 * @param control 1480 * the control 1481 * @return the list of selected items 1482 */ getSelection(Control control)1483 protected abstract Item[] getSelection(Control control); 1484 1485 @SuppressWarnings("rawtypes") 1486 @Override getSelectionFromWidget()1487 protected List getSelectionFromWidget() { 1488 Widget[] items = getSelection(getControl()); 1489 List<Object> list = new ArrayList<>(items.length); 1490 for (Widget item : items) { 1491 Object e = item.getData(); 1492 if (e != null) { 1493 list.add(e); 1494 } 1495 } 1496 return list; 1497 } 1498 1499 /* 1500 * Overridden in AbstractTreeViewer to fix bug 108102 (code copied from 1501 * StructuredViewer to avoid introducing new API) 1502 */ 1503 @Override handleDoubleSelect(SelectionEvent event)1504 protected void handleDoubleSelect(SelectionEvent event) { 1505 // handle case where an earlier selection listener disposed the control. 1506 Control control = getControl(); 1507 if (control != null && !control.isDisposed()) { 1508 // If the double-clicked element can be obtained from the event, use 1509 // it 1510 // otherwise get it from the control. Some controls like List do 1511 // not have the notion of item. 1512 // For details, see bug 90161 [Navigator] DefaultSelecting folders 1513 // shouldn't always expand first one 1514 ISelection selection; 1515 if (event.item != null && event.item.getData() != null) { 1516 1517 // changes to fix bug 108102 follow 1518 TreePath treePath = getTreePathFromItem((Item) event.item); 1519 selection = new TreeSelection(treePath); 1520 // end of changes 1521 1522 } else { 1523 selection = getSelection(); 1524 updateSelection(selection); 1525 } 1526 fireDoubleClick(new DoubleClickEvent(this, selection)); 1527 } 1528 } 1529 1530 /** 1531 * Handles a tree collapse event from the SWT widget. 1532 * 1533 * @param event 1534 * the SWT tree event 1535 */ handleTreeCollapse(TreeEvent event)1536 protected void handleTreeCollapse(TreeEvent event) { 1537 if (event.item.getData() != null) { 1538 fireTreeCollapsed(new TreeExpansionEvent(this, event.item.getData())); 1539 } 1540 } 1541 1542 /** 1543 * Handles a tree expand event from the SWT widget. 1544 * 1545 * @param event 1546 * the SWT tree event 1547 */ handleTreeExpand(TreeEvent event)1548 protected void handleTreeExpand(TreeEvent event) { 1549 createChildren(event.item); 1550 if (event.item.getData() != null) { 1551 fireTreeExpanded(new TreeExpansionEvent(this, event.item.getData())); 1552 } 1553 } 1554 1555 @Override hookControl(Control control)1556 protected void hookControl(Control control) { 1557 super.hookControl(control); 1558 addTreeListener(control, new TreeListener() { 1559 @Override 1560 public void treeExpanded(TreeEvent event) { 1561 handleTreeExpand(event); 1562 } 1563 1564 @Override 1565 public void treeCollapsed(TreeEvent event) { 1566 handleTreeCollapse(event); 1567 } 1568 }); 1569 } 1570 1571 @Override inputChanged(Object input, Object oldInput)1572 protected void inputChanged(Object input, Object oldInput) { 1573 preservingSelection(() -> { 1574 Control tree = getControl(); 1575 tree.setRedraw(false); 1576 try { 1577 removeAll(tree); 1578 tree.setData(getRoot()); 1579 internalInitializeTree(tree); 1580 } finally { 1581 tree.setRedraw(true); 1582 } 1583 }); 1584 } 1585 1586 /** 1587 * Initializes the tree with root items, expanding to the appropriate 1588 * level if necessary. 1589 * 1590 * @param tree the tree control 1591 * @since 3.3 1592 */ internalInitializeTree(Control tree)1593 protected void internalInitializeTree(Control tree) { 1594 createChildren(tree); 1595 internalExpandToLevel(tree, expandToLevel); 1596 } 1597 1598 /** 1599 * Recursively collapses the subtree rooted at the given widget to the given 1600 * level. 1601 * 1602 * @param widget the widget 1603 * @param level non-negative level, or <code>ALL_LEVELS</code> to collapse all 1604 * levels of the tree 1605 */ internalCollapseToLevel(Widget widget, int level)1606 protected void internalCollapseToLevel(Widget widget, int level) { 1607 if (level == ALL_LEVELS || level > 0) { 1608 1609 if (widget instanceof Item) { 1610 Item item = (Item) widget; 1611 setExpanded(item, false); 1612 Object element = item.getData(); 1613 if (element != null && level == ALL_LEVELS) { 1614 if (optionallyPruneChildren(item, element)) { 1615 return; 1616 } 1617 } 1618 } 1619 1620 if (level == ALL_LEVELS || level > 1) { 1621 Item[] children = getChildren(widget); 1622 if (children != null) { 1623 int nextLevel = (level == ALL_LEVELS ? ALL_LEVELS 1624 : level - 1); 1625 for (Item element : children) { 1626 internalCollapseToLevel(element, nextLevel); 1627 } 1628 } 1629 } 1630 } 1631 } 1632 1633 /** 1634 * Recursively collects all expanded items from the given widget. 1635 * 1636 * @param result 1637 * a list (element type: <code>Item</code>) into which to 1638 * collect the elements 1639 * @param widget 1640 * the widget 1641 */ internalCollectExpandedItems(List<Item> result, Widget widget)1642 private void internalCollectExpandedItems(List<Item> result, Widget widget) { 1643 Item[] items = getChildren(widget); 1644 for (Item item : items) { 1645 // Disregard dummy nodes (see bug 287765) 1646 if (item.getData() != null) { 1647 if (getExpanded(item)) { 1648 result.add(item); 1649 } 1650 internalCollectExpandedItems(result, item); 1651 } 1652 } 1653 } 1654 1655 /** 1656 * Tries to create a path of tree items for the given element or tree path. 1657 * This method recursively walks up towards the root of the tree and in the 1658 * case of an element (rather than a tree path) assumes that 1659 * <code>getParent</code> returns the correct parent of an element. 1660 * 1661 * @param elementOrPath 1662 * the element 1663 * @param expand 1664 * <code>true</code> if all nodes on the path should be 1665 * expanded, and <code>false</code> otherwise 1666 * @return Widget 1667 */ internalExpand(Object elementOrPath, boolean expand)1668 protected Widget internalExpand(Object elementOrPath, boolean expand) { 1669 1670 if (elementOrPath == null) { 1671 return null; 1672 } 1673 1674 Widget w = internalGetWidgetToSelect(elementOrPath); 1675 if (w == null) { 1676 if (equals(elementOrPath, getRoot())) { // stop at root 1677 return null; 1678 } 1679 // my parent has to create me 1680 Object parent = getParentElement(elementOrPath); 1681 if (parent != null) { 1682 Widget pw = internalExpand(parent, false); 1683 if (pw != null) { 1684 // let my parent create me 1685 createChildren(pw); 1686 Object element = internalToElement(elementOrPath); 1687 w = internalFindChild(pw, element); 1688 } 1689 } 1690 } 1691 if (expand && w instanceof Item) { 1692 // expand parent items top-down 1693 Item item = getParentItem((Item) w); 1694 LinkedList<Item> toExpandList = new LinkedList<>(); 1695 while (item != null) { 1696 if (!getExpanded(item)) { 1697 toExpandList.addFirst(item); 1698 } 1699 item = getParentItem(item); 1700 } 1701 for (Item toExpand : toExpandList) { 1702 setExpanded(toExpand, true); 1703 } 1704 } 1705 return w; 1706 } 1707 1708 /** 1709 * If the argument is a tree path, returns its last segment, otherwise 1710 * return the argument 1711 * 1712 * @param elementOrPath 1713 * an element or a tree path 1714 * @return the element, or the last segment of the tree path 1715 */ internalToElement(Object elementOrPath)1716 private Object internalToElement(Object elementOrPath) { 1717 if (elementOrPath instanceof TreePath) { 1718 return ((TreePath) elementOrPath).getLastSegment(); 1719 } 1720 return elementOrPath; 1721 } 1722 1723 /** 1724 * This method takes a tree path or an element. If the argument is not a tree 1725 * path, returns the parent of the given element or <code>null</code> if the 1726 * parent is not known. If the argument is a tree path with more than one 1727 * segment, returns its parent tree path, otherwise returns <code>null</code>. 1728 * 1729 * @param elementOrTreePath the element or path to find parent for 1730 * @return the parent element, or parent path, or <code>null</code> 1731 * 1732 * @since 3.2 1733 */ getParentElement(Object elementOrTreePath)1734 protected Object getParentElement(Object elementOrTreePath) { 1735 if (elementOrTreePath instanceof TreePath) { 1736 TreePath treePath = (TreePath) elementOrTreePath; 1737 return (treePath).getParentPath(); 1738 } 1739 IContentProvider cp = getContentProvider(); 1740 if (cp instanceof ITreePathContentProvider) { 1741 ITreePathContentProvider tpcp = (ITreePathContentProvider) cp; 1742 TreePath[] paths = tpcp.getParents(elementOrTreePath); 1743 if (paths.length > 0) { 1744 if (paths[0].getSegmentCount() == 0) { 1745 return getRoot(); 1746 } 1747 return paths[0].getLastSegment(); 1748 } 1749 } 1750 if (cp instanceof ITreeContentProvider) { 1751 ITreeContentProvider tcp = (ITreeContentProvider) cp; 1752 return tcp.getParent(elementOrTreePath); 1753 } 1754 return null; 1755 } 1756 1757 /** 1758 * Returns the widget to be selected for the given element or tree path. 1759 * 1760 * @param elementOrTreePath 1761 * the element or tree path to select 1762 * @return the widget to be selected, or <code>null</code> if not found 1763 * 1764 * @since 3.1 1765 */ internalGetWidgetToSelect(Object elementOrTreePath)1766 protected Widget internalGetWidgetToSelect(Object elementOrTreePath) { 1767 if (elementOrTreePath instanceof TreePath) { 1768 TreePath treePath = (TreePath) elementOrTreePath; 1769 if (treePath.getSegmentCount() == 0) { 1770 return getControl(); 1771 } 1772 Widget[] candidates = findItems(treePath.getLastSegment()); 1773 for (Widget candidate : candidates) { 1774 if (!(candidate instanceof Item)) { 1775 continue; 1776 } 1777 if (treePath.equals(getTreePathFromItem((Item) candidate), getComparer())) { 1778 return candidate; 1779 } 1780 } 1781 return null; 1782 } 1783 return findItem(elementOrTreePath); 1784 } 1785 1786 /** 1787 * Recursively expands the subtree rooted at the given widget to the given 1788 * level. 1789 * <p> 1790 * Note that the default implementation of this method does not call 1791 * <code>setRedraw</code>. 1792 * </p> 1793 * 1794 * @param widget the widget 1795 * @param level non-negative level, or <code>ALL_LEVELS</code> to collapse all 1796 * levels of the tree 1797 */ internalExpandToLevel(Widget widget, int level)1798 protected void internalExpandToLevel(Widget widget, int level) { 1799 if (level == ALL_LEVELS || level > 0) { 1800 if (widget instanceof Item && widget.getData() != null 1801 && !isExpandable((Item) widget, null, widget.getData())) { 1802 return; 1803 } 1804 createChildren(widget, false); 1805 if (widget instanceof Item) { 1806 setExpanded((Item) widget, true); 1807 } 1808 if (level == ALL_LEVELS || level > 1) { 1809 Item[] children = getChildren(widget); 1810 if (children != null) { 1811 int newLevel = (level == ALL_LEVELS ? ALL_LEVELS 1812 : level - 1); 1813 for (Item element : children) { 1814 internalExpandToLevel(element, newLevel); 1815 } 1816 } 1817 } 1818 } 1819 } 1820 1821 /** 1822 * Non-recursively tries to find the given element as a child of the given 1823 * parent (item or tree). 1824 * 1825 * @param parent 1826 * the parent item 1827 * @param element 1828 * the element 1829 * @return Widget 1830 */ internalFindChild(Widget parent, Object element)1831 private Widget internalFindChild(Widget parent, Object element) { 1832 Item[] items = getChildren(parent); 1833 for (Item item : items) { 1834 Object data = item.getData(); 1835 if (data != null && equals(data, element)) { 1836 return item; 1837 } 1838 } 1839 return null; 1840 } 1841 1842 /** 1843 * Recursively tries to find the given element. 1844 * 1845 * @param parent 1846 * the parent item 1847 * @param element 1848 * the element 1849 * @return Widget 1850 */ internalFindItem(Item parent, Object element)1851 private Widget internalFindItem(Item parent, Object element) { 1852 1853 // compare with node 1854 Object data = parent.getData(); 1855 if (data != null) { 1856 if (equals(data, element)) { 1857 return parent; 1858 } 1859 } 1860 // recurse over children 1861 Item[] items = getChildren(parent); 1862 for (Item item : items) { 1863 Widget o = internalFindItem(item, element); 1864 if (o != null) { 1865 return o; 1866 } 1867 } 1868 return null; 1869 } 1870 1871 @Override internalRefresh(Object element)1872 protected void internalRefresh(Object element) { 1873 internalRefresh(element, true); 1874 } 1875 1876 @Override internalRefresh(Object element, boolean updateLabels)1877 protected void internalRefresh(Object element, boolean updateLabels) { 1878 // If element is null, do a full refresh. 1879 if (element == null) { 1880 internalRefresh(getControl(), getRoot(), true, updateLabels); 1881 return; 1882 } 1883 Widget[] items = findItems(element); 1884 if (items.length != 0) { 1885 for (Widget item : items) { 1886 // pick up structure changes too 1887 internalRefresh(item, element, true, updateLabels); 1888 } 1889 } 1890 } 1891 1892 /** 1893 * Refreshes the tree starting at the given widget. 1894 * <p> 1895 * EXPERIMENTAL. Not to be used except by JDT. This method was added to 1896 * support JDT's explorations into grouping by working sets. This method 1897 * cannot be removed without breaking binary backwards compatibility, but 1898 * should not be called by clients. 1899 * </p> 1900 * 1901 * @param widget 1902 * the widget 1903 * @param element 1904 * the element 1905 * @param doStruct 1906 * <code>true</code> if structural changes are to be picked up, 1907 * and <code>false</code> if only label provider changes are of 1908 * interest 1909 * @param updateLabels 1910 * <code>true</code> to update labels for existing elements, 1911 * <code>false</code> to only update labels as needed, assuming 1912 * that labels for existing elements are unchanged. 1913 * @since 3.1 1914 */ internalRefresh(Widget widget, Object element, boolean doStruct, boolean updateLabels)1915 protected void internalRefresh(Widget widget, Object element, 1916 boolean doStruct, boolean updateLabels) { 1917 1918 if (widget instanceof Item) { 1919 if (doStruct) { 1920 updatePlus((Item) widget, element); 1921 } 1922 if (updateLabels || !equals(element, widget.getData())) { 1923 doUpdateItem(widget, element, true); 1924 } else { 1925 associate(element, (Item) widget); 1926 } 1927 } 1928 1929 if (doStruct) { 1930 internalRefreshStruct(widget, element, updateLabels); 1931 } else { 1932 Item[] children = getChildren(widget); 1933 if (children != null) { 1934 for (Item item : children) { 1935 Object data = item.getData(); 1936 if (data != null) { 1937 internalRefresh(item, data, doStruct, updateLabels); 1938 } 1939 } 1940 } 1941 } 1942 } 1943 1944 /** 1945 * Update the structure and recurse. Items are updated in updateChildren, as 1946 * needed. 1947 * 1948 * @param widget 1949 * @param element 1950 * @param updateLabels 1951 */ internalRefreshStruct(Widget widget, Object element, boolean updateLabels)1952 /* package */void internalRefreshStruct(Widget widget, Object element, 1953 boolean updateLabels) { 1954 updateChildren(widget, element, null, updateLabels); 1955 Item[] children = getChildren(widget); 1956 if (children != null) { 1957 for (Item item : children) { 1958 Object data = item.getData(); 1959 if (data != null) { 1960 internalRefreshStruct(item, data, updateLabels); 1961 } 1962 } 1963 } 1964 } 1965 1966 /** 1967 * Removes the given elements from this viewer. 1968 * <p> 1969 * EXPERIMENTAL. Not to be used except by JDT. This method was added to 1970 * support JDT's explorations into grouping by working sets. This method 1971 * cannot be removed without breaking binary backwards compatibility, but 1972 * should not be called by clients. 1973 * </p> 1974 * 1975 * @param elementsOrPaths 1976 * the elements or element paths to remove 1977 * @since 3.1 1978 */ internalRemove(Object[] elementsOrPaths)1979 protected void internalRemove(Object[] elementsOrPaths) { 1980 Object input = getInput(); 1981 for (Object element : elementsOrPaths) { 1982 if (equals(element, input)) { 1983 setInput(null); 1984 return; 1985 } 1986 Widget[] childItems = internalFindItems(element); 1987 if (childItems.length > 0) { 1988 for (Widget childItem : childItems) { 1989 if (childItem instanceof Item) { 1990 disassociate((Item) childItem); 1991 childItem.dispose(); 1992 } 1993 } 1994 } else { 1995 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=210747 1996 Object parent = getParentElement(element); 1997 if (parent != null 1998 && !equals(parent, getRoot()) 1999 && !(parent instanceof TreePath && ((TreePath) parent) 2000 .getSegmentCount() == 0)) { 2001 Widget[] parentItems = internalFindItems(parent); 2002 for (Widget parentItem : parentItems) { 2003 if (parentItem instanceof Item) { 2004 updatePlus((Item) parentItem, parent); 2005 } 2006 } 2007 } 2008 } 2009 } 2010 } 2011 2012 /** 2013 * Removes the given elements from this viewer, whenever those elements 2014 * appear as children of the given parent. 2015 * 2016 * @param parent the parent element 2017 * @param elements 2018 * the elements to remove 2019 * @since 3.1 2020 */ internalRemove(Object parent, Object[] elements)2021 protected void internalRemove(Object parent, Object[] elements) { 2022 2023 CustomHashtable toRemove = new CustomHashtable(getComparer()); 2024 for (Object element : elements) { 2025 toRemove.put(element, element); 2026 } 2027 2028 // Find each place the parent appears in the tree 2029 Widget[] parentItemArray = findItems(parent); 2030 for (Widget parentItem : parentItemArray) { 2031 // May happen if parent element is a descendent of of a previously 2032 // removed element 2033 if (parentItem.isDisposed()) 2034 continue; 2035 2036 // Iterate over the child items and remove each one 2037 Item[] children = getChildren(parentItem); 2038 2039 if (children.length == 1 && children[0].getData() == null && 2040 parentItem instanceof Item) { // dummy node 2041 // Remove plus if parent element has no children 2042 updatePlus((Item) parentItem, parent); 2043 } else { 2044 for (Item child : children) { 2045 Object data = child.getData(); 2046 if (data != null && toRemove.containsKey(data)) { 2047 disassociate(child); 2048 child.dispose(); 2049 } 2050 } 2051 } 2052 } 2053 } 2054 2055 /** 2056 * Sets the expanded state of all items to correspond to the given set of 2057 * expanded elements. 2058 * 2059 * @param expandedElements 2060 * the set (element type: <code>Object</code>) of elements 2061 * which are expanded 2062 * @param widget 2063 * the widget 2064 */ internalSetExpanded(CustomHashtable expandedElements, Widget widget)2065 private void internalSetExpanded(CustomHashtable expandedElements, 2066 Widget widget) { 2067 Item[] items = getChildren(widget); 2068 for (Item item : items) { 2069 Object data = item.getData(); 2070 if (data != null) { 2071 // remove the element to avoid an infinite loop 2072 // if the same element appears on a child item 2073 boolean expanded = expandedElements.remove(data) != null; 2074 if (expanded != getExpanded(item)) { 2075 if (expanded) { 2076 createChildren(item); 2077 } 2078 setExpanded(item, expanded); 2079 } 2080 } 2081 if (expandedElements.size() > 0) { 2082 internalSetExpanded(expandedElements, item); 2083 } 2084 } 2085 } 2086 2087 /** 2088 * Sets the expanded state of all items to correspond to the given set of 2089 * expanded tree paths. 2090 * 2091 * @param expandedTreePaths 2092 * the set (element type: <code>TreePath</code>) of elements 2093 * which are expanded 2094 * @param widget 2095 * the widget 2096 */ internalSetExpandedTreePaths( CustomHashtable expandedTreePaths, Widget widget, TreePath currentPath)2097 private void internalSetExpandedTreePaths( 2098 CustomHashtable expandedTreePaths, Widget widget, 2099 TreePath currentPath) { 2100 Item[] items = getChildren(widget); 2101 for (Item item : items) { 2102 Object data = item.getData(); 2103 TreePath childPath = data == null ? null : currentPath 2104 .createChildPath(data); 2105 if (data != null && childPath != null) { 2106 // remove the element to avoid an infinite loop 2107 // if the same element appears on a child item 2108 boolean expanded = expandedTreePaths.remove(childPath) != null; 2109 if (expanded != getExpanded(item)) { 2110 if (expanded) { 2111 createChildren(item); 2112 } 2113 setExpanded(item, expanded); 2114 } 2115 } 2116 internalSetExpandedTreePaths(expandedTreePaths, item, childPath); 2117 } 2118 } 2119 2120 /** 2121 * Return whether the tree node representing the given element or path can 2122 * be expanded. Clients should query expandability by path if the viewer's 2123 * content provider is an {@link ITreePathContentProvider}. 2124 * <p> 2125 * The default implementation of this framework method calls 2126 * <code>hasChildren</code> on this viewer's content provider. It may be 2127 * overridden if necessary. 2128 * </p> 2129 * @see #setExpandPreCheckFilters(boolean) 2130 * @param elementOrTreePath 2131 * the element or path 2132 * @return <code>true</code> if the tree node representing the given 2133 * element can be expanded, or <code>false</code> if not 2134 */ isExpandable(Object elementOrTreePath)2135 public boolean isExpandable(Object elementOrTreePath) { 2136 Object element; 2137 TreePath path; 2138 if (elementOrTreePath instanceof TreePath) { 2139 path = (TreePath) elementOrTreePath; 2140 element = path.getLastSegment(); 2141 } else { 2142 element = elementOrTreePath; 2143 path = null; 2144 } 2145 IContentProvider cp = getContentProvider(); 2146 if (cp instanceof ITreePathContentProvider) { 2147 ITreePathContentProvider tpcp = (ITreePathContentProvider) cp; 2148 if (path == null) { 2149 // A path was not provided so try and find one 2150 Widget w = findItem(element); 2151 if (w instanceof Item) { 2152 Item item = (Item) w; 2153 path = getTreePathFromItem(item); 2154 } 2155 if (path == null) { 2156 path = new TreePath(new Object[] { element }); 2157 } 2158 } 2159 boolean hasChildren = tpcp.hasChildren(path); 2160 if (hasChildren && isExpandableCheckFilters && hasFilters()) { 2161 return getFilteredChildren(path).length > 0; 2162 } 2163 return hasChildren; 2164 } 2165 if (cp instanceof ITreeContentProvider) { 2166 ITreeContentProvider tcp = (ITreeContentProvider) cp; 2167 boolean hasChildren = tcp.hasChildren(element); 2168 if (hasChildren && isExpandableCheckFilters && hasFilters()) { 2169 return getFilteredChildren(element).length > 0; 2170 } 2171 return hasChildren; 2172 } 2173 return false; 2174 } 2175 2176 /** 2177 * Return whether the given element is expandable. 2178 * 2179 * @param item 2180 * the tree item for the element 2181 * @param parentPath 2182 * the parent path if it is known or <code>null</code> if it 2183 * needs to be determines 2184 * @param element 2185 * the element 2186 * @return whether the given element is expandable 2187 */ isExpandable(Item item, TreePath parentPath, Object element)2188 private boolean isExpandable(Item item, TreePath parentPath, Object element) { 2189 Object elementOrTreePath = element; 2190 if (isTreePathContentProvider()) { 2191 if (parentPath != null) { 2192 elementOrTreePath = parentPath.createChildPath(element); 2193 } else { 2194 elementOrTreePath = getTreePathFromItem(item); 2195 } 2196 } 2197 return isExpandable(elementOrTreePath); 2198 } 2199 2200 @Override labelProviderChanged()2201 protected void labelProviderChanged() { 2202 // we have to walk the (visible) tree and update every item 2203 Control tree = getControl(); 2204 tree.setRedraw(false); 2205 // don't pick up structure changes, but do force label updates 2206 internalRefresh(tree, getRoot(), false, true); 2207 tree.setRedraw(true); 2208 } 2209 2210 /** 2211 * Creates a new item. 2212 * 2213 * @param parent 2214 * the parent widget 2215 * @param style 2216 * SWT style bits 2217 * @param index 2218 * if non-negative, indicates the position to insert the item 2219 * into its parent 2220 * @return the newly-created item 2221 */ newItem(Widget parent, int style, int index)2222 protected abstract Item newItem(Widget parent, int style, int index); 2223 2224 /** 2225 * Removes the given elements from this viewer. The selection is updated if 2226 * required. 2227 * <p> 2228 * This method should be called (by the content provider) when elements have 2229 * been removed from the model, in order to cause the viewer to accurately 2230 * reflect the model. This method only affects the viewer, not the model. 2231 * </p> 2232 * 2233 * @param elementsOrTreePaths the elements to remove 2234 */ remove(final Object... elementsOrTreePaths)2235 public void remove(final Object... elementsOrTreePaths) { 2236 assertElementsNotNull(elementsOrTreePaths); 2237 if (elementsOrTreePaths.length == 0) { 2238 return; 2239 } 2240 if (checkBusy()) 2241 return; 2242 preservingSelection(() -> internalRemove(elementsOrTreePaths)); 2243 } 2244 2245 /** 2246 * Removes the given elements from this viewer whenever they appear as 2247 * children of the given parent element. If the given elements also appear 2248 * as children of some other parent, the other parent will remain unchanged. 2249 * The selection is updated if required. 2250 * <p> 2251 * This method should be called (by the content provider) when elements have 2252 * been removed from the model, in order to cause the viewer to accurately 2253 * reflect the model. This method only affects the viewer, not the model. 2254 * </p> 2255 * 2256 * @param parent 2257 * the parent of the elements to remove 2258 * @param elements 2259 * the elements to remove 2260 * 2261 * @since 3.2 2262 */ remove(final Object parent, final Object... elements)2263 public void remove(final Object parent, final Object... elements) { 2264 assertElementsNotNull(elements); 2265 if (elements.length == 0) { 2266 return; 2267 } 2268 if (checkBusy()) 2269 return; 2270 preservingSelection(() -> internalRemove(parent, elements)); 2271 } 2272 2273 /** 2274 * Removes the given element from the viewer. The selection is updated if 2275 * necessary. 2276 * <p> 2277 * This method should be called (by the content provider) when a single 2278 * element has been removed from the model, in order to cause the viewer to 2279 * accurately reflect the model. This method only affects the viewer, not 2280 * the model. Note that there is another method for efficiently processing 2281 * the simultaneous removal of multiple elements. 2282 * </p> 2283 * 2284 * @param elementsOrTreePaths 2285 * the element 2286 */ remove(Object elementsOrTreePaths)2287 public void remove(Object elementsOrTreePaths) { 2288 remove(new Object[] { elementsOrTreePaths }); 2289 } 2290 2291 /** 2292 * Removes all items from the given control. 2293 * 2294 * @param control 2295 * the control 2296 */ removeAll(Control control)2297 protected abstract void removeAll(Control control); 2298 2299 /** 2300 * Removes a listener for expand and collapse events in this viewer. Has no 2301 * effect if an identical listener is not registered. 2302 * 2303 * @param listener 2304 * a tree viewer listener 2305 */ removeTreeListener(ITreeViewerListener listener)2306 public void removeTreeListener(ITreeViewerListener listener) { 2307 treeListeners.remove(listener); 2308 } 2309 2310 /** 2311 * This implementation of reveal() reveals the given element or tree path. 2312 */ 2313 @Override reveal(Object elementOrTreePath)2314 public void reveal(Object elementOrTreePath) { 2315 Assert.isNotNull(elementOrTreePath); 2316 Widget w = internalExpand(elementOrTreePath, true); 2317 if (w instanceof Item) { 2318 showItem((Item) w); 2319 } 2320 } 2321 2322 /** 2323 * Returns the rightmost visible descendent of the given item. Returns the 2324 * item itself if it has no children. 2325 * 2326 * @param item 2327 * the item to compute the descendent of 2328 * @return the rightmost visible descendent or the item itself if it has no 2329 * children 2330 */ rightMostVisibleDescendent(Item item)2331 private Item rightMostVisibleDescendent(Item item) { 2332 Item[] children = getItems(item); 2333 if (getExpanded(item) && children != null && children.length > 0) { 2334 return rightMostVisibleDescendent(children[children.length - 1]); 2335 } 2336 return item; 2337 } 2338 2339 @Override scrollDown(int x, int y)2340 public Item scrollDown(int x, int y) { 2341 Item current = getItem(x, y); 2342 if (current != null) { 2343 Item next = getNextItem(current, true); 2344 showItem(next == null ? current : next); 2345 return next; 2346 } 2347 return null; 2348 } 2349 2350 @Override scrollUp(int x, int y)2351 public Item scrollUp(int x, int y) { 2352 Item current = getItem(x, y); 2353 if (current != null) { 2354 Item previous = getPreviousItem(current); 2355 showItem(previous == null ? current : previous); 2356 return previous; 2357 } 2358 return null; 2359 } 2360 2361 /** 2362 * Sets the auto-expand level to be used when the input of the viewer is set 2363 * using {@link #setInput(Object)}. The value 0 means that there is no 2364 * auto-expand; 1 means that the invisible root element is expanded (since 2365 * most concrete subclasses do not show the root element, there is usually 2366 * no practical difference between using the values 0 and 1); 2 means that 2367 * top-level elements are expanded, but not their children; 3 means that 2368 * top-level elements are expanded, and their children, but not 2369 * grandchildren; and so on. 2370 * <p> 2371 * The value <code>ALL_LEVELS</code> means that all subtrees should be 2372 * expanded. 2373 * </p> 2374 * <p> 2375 * Note that in previous releases, the Javadoc for this method had an off-by 2376 * one error. See bug 177669 for details. 2377 * </p> 2378 * 2379 * @param level 2380 * non-negative level, or <code>ALL_LEVELS</code> to expand all 2381 * levels of the tree 2382 */ setAutoExpandLevel(int level)2383 public void setAutoExpandLevel(int level) { 2384 expandToLevel = level; 2385 } 2386 2387 /** 2388 * Sets the content provider used by this <code>AbstractTreeViewer</code>. 2389 * <p> 2390 * Content providers for abstract tree viewers must implement either 2391 * {@link ITreeContentProvider} or {@link ITreePathContentProvider}. 2392 */ 2393 @Override setContentProvider(IContentProvider provider)2394 public void setContentProvider(IContentProvider provider) { 2395 // the actual check is in assertContentProviderType 2396 super.setContentProvider(provider); 2397 } 2398 2399 @Override assertContentProviderType(IContentProvider provider)2400 protected void assertContentProviderType(IContentProvider provider) { 2401 Assert.isTrue(provider instanceof ITreeContentProvider 2402 || provider instanceof ITreePathContentProvider, 2403 "Instances of AbstractTreeViewer must have a content provider " //$NON-NLS-1$ 2404 + "of type ITreeContentProvider or ITreePathContentProvider"); //$NON-NLS-1$ 2405 } 2406 2407 /** 2408 * Sets the expand state of the given item. 2409 * 2410 * @param item 2411 * the item 2412 * @param expand 2413 * the expand state of the item 2414 */ setExpanded(Item item, boolean expand)2415 protected abstract void setExpanded(Item item, boolean expand); 2416 2417 /** 2418 * Sets which nodes are expanded in this viewer's tree. The given list 2419 * contains the elements that are to be expanded; all other nodes are to be 2420 * collapsed. 2421 * <p> 2422 * This method is typically used when restoring the interesting state of a 2423 * viewer captured by an earlier call to <code>getExpandedElements</code>. 2424 * </p> 2425 * 2426 * @param elements 2427 * the array of expanded elements 2428 * @see #getExpandedElements 2429 */ setExpandedElements(Object... elements)2430 public void setExpandedElements(Object... elements) { 2431 assertElementsNotNull(elements); 2432 if (checkBusy()) { 2433 return; 2434 } 2435 CustomHashtable expandedElements = newHashtable(elements.length * 2 + 1); 2436 for (Object element : elements) { 2437 // Ensure item exists for element. This will materialize items for 2438 // each element and their parents, if possible. This is important 2439 // to support expanding of inner tree nodes without necessarily 2440 // expanding their parents. 2441 internalExpand(element, false); 2442 expandedElements.put(element, element); 2443 } 2444 // this will traverse all existing items, and create children for 2445 // elements that need to be expanded. If the tree contains multiple 2446 // equal elements, and those are in the set of elements to be expanded, 2447 // only the first item found for each element will be expanded. 2448 internalSetExpanded(expandedElements, getControl()); 2449 } 2450 2451 /** 2452 * Sets which nodes are expanded in this viewer's tree. The given list 2453 * contains the tree paths that are to be expanded; all other nodes are to 2454 * be collapsed. 2455 * <p> 2456 * This method is typically used when restoring the interesting state of a 2457 * viewer captured by an earlier call to <code>getExpandedTreePaths</code>. 2458 * </p> 2459 * 2460 * @param treePaths 2461 * the array of expanded tree paths 2462 * @see #getExpandedTreePaths() 2463 * 2464 * @since 3.2 2465 */ setExpandedTreePaths(TreePath... treePaths)2466 public void setExpandedTreePaths(TreePath... treePaths) { 2467 assertElementsNotNull((Object[]) treePaths); 2468 if (checkBusy()) 2469 return; 2470 final IElementComparer comparer = getComparer(); 2471 IElementComparer treePathComparer = new IElementComparer() { 2472 2473 @Override 2474 public boolean equals(Object a, Object b) { 2475 return ((TreePath) a).equals(((TreePath) b), comparer); 2476 } 2477 2478 2479 @Override 2480 public int hashCode(Object element) { 2481 return ((TreePath) element).hashCode(comparer); 2482 } 2483 }; 2484 CustomHashtable expandedTreePaths = new CustomHashtable( 2485 treePaths.length * 2 + 1, treePathComparer); 2486 for (TreePath treePath : treePaths) { 2487 // Ensure item exists for element. This will materialize items for 2488 // each element and their parents, if possible. This is important 2489 // to support expanding of inner tree nodes without necessarily 2490 // expanding their parents. 2491 internalExpand(treePath, false); 2492 expandedTreePaths.put(treePath, treePath); 2493 } 2494 // this will traverse all existing items, and create children for 2495 // elements that need to be expanded. If the tree contains multiple 2496 // equal elements, and those are in the set of elements to be expanded, 2497 // only the first item found for each element will be expanded. 2498 internalSetExpandedTreePaths(expandedTreePaths, getControl(), 2499 new TreePath(new Object[0])); 2500 } 2501 2502 /** 2503 * Sets whether the node corresponding to the given element or tree path is 2504 * expanded or collapsed. 2505 * 2506 * @param elementOrTreePath 2507 * the element 2508 * @param expanded 2509 * <code>true</code> if the node is expanded, and 2510 * <code>false</code> if collapsed 2511 */ setExpandedState(Object elementOrTreePath, boolean expanded)2512 public void setExpandedState(Object elementOrTreePath, boolean expanded) { 2513 Assert.isNotNull(elementOrTreePath); 2514 if (checkBusy()) 2515 return; 2516 Widget item = internalExpand(elementOrTreePath, false); 2517 if (item instanceof Item) { 2518 if (expanded) { 2519 createChildren(item); 2520 } 2521 setExpanded((Item) item, expanded); 2522 } 2523 } 2524 2525 /** 2526 * Sets the selection to the given list of items. 2527 * 2528 * @param items 2529 * list of items (element type: 2530 * <code>org.eclipse.swt.widgets.Item</code>) 2531 */ setSelection(List<Item> items)2532 protected abstract void setSelection(List<Item> items); 2533 2534 /** 2535 * This implementation of setSelectionToWidget accepts a list of elements or 2536 * a list of tree paths. 2537 */ 2538 @Override setSelectionToWidget(@uppressWarningsR) List v, boolean reveal)2539 protected void setSelectionToWidget(@SuppressWarnings("rawtypes") List v, boolean reveal) { 2540 if (v == null) { 2541 setSelection(new ArrayList<>(0)); 2542 return; 2543 } 2544 int size = v.size(); 2545 List<Item> newSelection = new ArrayList<>(size); 2546 for (int i = 0; i < size; ++i) { 2547 Object elementOrTreePath = v.get(i); 2548 // Use internalExpand since item may not yet be created. See 2549 // 1G6B1AR. 2550 Widget w = internalExpand(elementOrTreePath, false); 2551 if (w instanceof Item) { 2552 newSelection.add((Item) w); 2553 } else if (w == null && elementOrTreePath instanceof TreePath) { 2554 TreePath treePath = (TreePath) elementOrTreePath; 2555 Object element = treePath.getLastSegment(); 2556 if (element != null) { 2557 w = internalExpand(element, false); 2558 if (w instanceof Item) { 2559 newSelection.add((Item) w); 2560 } 2561 } 2562 } 2563 } 2564 setSelection(newSelection); 2565 2566 // Although setting the selection in the control should reveal it, 2567 // setSelection may be a no-op if the selection is unchanged, 2568 // so explicitly reveal items in the selection here. 2569 // See bug 100565 for more details. 2570 if (reveal && newSelection.size() > 0) { 2571 // Iterate backwards so the first item in the list 2572 // is the one guaranteed to be visible 2573 for (int i = (newSelection.size()-1); i >= 0; i--) { 2574 showItem(newSelection.get(i)); 2575 } 2576 } 2577 } 2578 2579 /** 2580 * Shows the given item. 2581 * 2582 * @param item 2583 * the item 2584 */ showItem(Item item)2585 protected abstract void showItem(Item item); 2586 2587 /** 2588 * Updates the tree items to correspond to the child elements of the given 2589 * parent element. If null is passed for the children, this method obtains 2590 * them (only if needed). 2591 * 2592 * @param widget 2593 * the widget 2594 * @param parent 2595 * the parent element 2596 * @param elementChildren 2597 * the child elements, or null 2598 * @deprecated this is no longer called by the framework 2599 */ 2600 @Deprecated updateChildren(Widget widget, Object parent, Object[] elementChildren)2601 protected void updateChildren(Widget widget, Object parent, 2602 Object[] elementChildren) { 2603 updateChildren(widget, parent, elementChildren, true); 2604 } 2605 2606 /** 2607 * Updates the tree items to correspond to the child elements of the given 2608 * parent element. If null is passed for the children, this method obtains 2609 * them (only if needed). 2610 * 2611 * @param widget 2612 * the widget 2613 * @param parent 2614 * the parent element 2615 * @param elementChildren 2616 * the child elements, or null 2617 * @param updateLabels 2618 * <code>true</code> to update labels for existing elements, 2619 * <code>false</code> to only update labels as needed, assuming 2620 * that labels for existing elements are unchanged. 2621 * @since 2.1 2622 */ updateChildren(Widget widget, Object parent, Object[] elementChildren, boolean updateLabels)2623 private void updateChildren(Widget widget, Object parent, 2624 Object[] elementChildren, boolean updateLabels) { 2625 // optimization! prune collapsed subtrees 2626 if (widget instanceof Item) { 2627 Item ti = (Item) widget; 2628 if (!getExpanded(ti)) { 2629 if (optionallyPruneChildren(ti, parent)) { 2630 // children were pruned, nothing left to do 2631 return; 2632 } 2633 // The following code is being executed if children were not pruned. 2634 // This is (as of 3.5) only the case for CheckboxTreeViewer. 2635 Item[] its = getItems(ti); 2636 if (isExpandable(ti, null, parent)) { 2637 if (its.length == 0) { 2638 // need dummy node 2639 newItem(ti, SWT.NULL, -1); 2640 return; 2641 } else if (its.length == 1 && its[0].getData() == null) { 2642 // dummy node exists, nothing left to do 2643 return; 2644 } 2645 // else fall through to normal update code below 2646 } else { 2647 for (Item it : its) { 2648 if (it.getData() != null) { 2649 disassociate(it); 2650 } 2651 it.dispose(); 2652 } 2653 // nothing left to do 2654 return; 2655 } 2656 } 2657 } 2658 2659 // If the children weren't passed in, get them now since they're needed 2660 // below. 2661 if (elementChildren == null) { 2662 if (isTreePathContentProvider() && widget instanceof Item) { 2663 TreePath path = getTreePathFromItem((Item) widget); 2664 elementChildren = getSortedChildren(path); 2665 } else { 2666 elementChildren = getSortedChildren(parent); 2667 } 2668 } 2669 2670 Control tree = getControl(); 2671 2672 // WORKAROUND 2673 int oldCnt = -1; 2674 if (widget == tree) { 2675 oldCnt = getItemCount(tree); 2676 } 2677 2678 Item[] items = getChildren(widget); 2679 2680 // save the expanded elements 2681 CustomHashtable expanded = newHashtable(CustomHashtable.DEFAULT_CAPACITY); // assume 2682 // num 2683 // expanded 2684 // is 2685 // small 2686 for (Item item : items) { 2687 if (getExpanded(item)) { 2688 Object element = item.getData(); 2689 if (element != null) { 2690 expanded.put(element, element); 2691 } 2692 } 2693 } 2694 2695 int min = Math.min(elementChildren.length, items.length); 2696 2697 // dispose of surplus items, optimizing for the case where elements have 2698 // been deleted but not reordered, or all elements have been removed. 2699 int numItemsToDispose = items.length - min; 2700 if (numItemsToDispose > 0) { 2701 CustomHashtable children = newHashtable(elementChildren.length * 2); 2702 for (Object elementChild : elementChildren) { 2703 children.put(elementChild, elementChild); 2704 } 2705 int i = 0; 2706 while (numItemsToDispose > 0 && i < items.length) { 2707 Object data = items[i].getData(); 2708 if (data == null || !children.containsKey(data)) { 2709 if (data != null) { 2710 disassociate(items[i]); 2711 } 2712 items[i].dispose(); 2713 if (i + 1 < items.length) { 2714 // The components at positions i+1 through 2715 // items.length-1 in the source array are copied into 2716 // positions i through items.length-2 2717 System.arraycopy(items, i + 1, items, i, items.length - (i+1)); 2718 } 2719 numItemsToDispose--; 2720 } else { 2721 i++; 2722 } 2723 } 2724 } 2725 2726 // compare first min items, and update item if necessary 2727 // need to do it in two passes: 2728 // 1: disassociate old items 2729 // 2: associate new items 2730 // because otherwise a later disassociate can remove a mapping made for 2731 // a previous associate, 2732 // making the map inconsistent 2733 for (int i = 0; i < min; ++i) { 2734 Item item = items[i]; 2735 Object oldElement = item.getData(); 2736 if (oldElement != null) { 2737 Object newElement = elementChildren[i]; 2738 if (newElement != oldElement) { 2739 if (equals(newElement, oldElement)) { 2740 // update the data to be the new element, since 2741 // although the elements 2742 // may be equal, they may still have different labels 2743 // or children 2744 Object data = item.getData(); 2745 if (data != null) { 2746 unmapElement(data, item); 2747 } 2748 item.setData(newElement); 2749 mapElement(newElement, item); 2750 } else { 2751 disassociate(item); 2752 // Clear the text and image to force a label update 2753 item.setImage(null); 2754 item.setText("");//$NON-NLS-1$ 2755 2756 } 2757 } 2758 } 2759 } 2760 2761 for (int i = 0; i < min; ++i) { 2762 Item item = items[i]; 2763 Object newElement = elementChildren[i]; 2764 if (item.getData() == null) { 2765 // old and new elements are not equal 2766 associate(newElement, item); 2767 updatePlus(item, newElement); 2768 updateItem(item, newElement); 2769 } else { 2770 // old and new elements are equal 2771 updatePlus(item, newElement); 2772 if (updateLabels) { 2773 updateItem(item, newElement); 2774 } else { 2775 associate(newElement, item); 2776 } 2777 } 2778 } 2779 2780 // Restore expanded state for items that changed position. 2781 // Make sure setExpanded is called after updatePlus, since 2782 // setExpanded(false) fails if item has no children. 2783 // Need to call setExpanded for both expanded and unexpanded 2784 // cases since the expanded state can change either way. 2785 // This needs to be done in a second loop, see bug 148025. 2786 for (int i = 0; i < min; ++i) { 2787 Item item = items[i]; 2788 Object newElement = elementChildren[i]; 2789 setExpanded(item, expanded.containsKey(newElement)); 2790 } 2791 2792 // add any remaining elements 2793 if (min < elementChildren.length) { 2794 for (int i = min; i < elementChildren.length; ++i) { 2795 createTreeItem(widget, elementChildren[i], -1); 2796 } 2797 2798 // Need to restore expanded state in a separate pass 2799 // because createTreeItem does not return the new item. 2800 // Avoid doing this unless needed. 2801 if (expanded.size() > 0) { 2802 // get the items again, to include the new items 2803 items = getChildren(widget); 2804 for (int i = min; i < elementChildren.length; ++i) { 2805 // Restore expanded state for items that changed position. 2806 // Make sure setExpanded is called after updatePlus (called 2807 // in createTreeItem), since 2808 // setExpanded(false) fails if item has no children. 2809 // Only need to call setExpanded if element was expanded 2810 // since new items are initially unexpanded. 2811 if (expanded.containsKey(elementChildren[i])) { 2812 setExpanded(items[i], true); 2813 } 2814 } 2815 } 2816 } 2817 2818 // WORKAROUND 2819 if (widget == tree && oldCnt == 0 && getItemCount(tree) != 0) { 2820 // System.out.println("WORKAROUND setRedraw"); 2821 tree.setRedraw(false); 2822 tree.setRedraw(true); 2823 } 2824 } 2825 2826 /** Returns true if children were pruned */ optionallyPruneChildren(Item item, Object element)2827 /*package*/ boolean optionallyPruneChildren(Item item, Object element) { 2828 // need a dummy node if element is expandable; 2829 // but try to avoid recreating the dummy node 2830 boolean needDummy = isExpandable(item, null, element); 2831 boolean haveDummy = false; 2832 // remove all children 2833 Item[] items = getItems(item); 2834 for (Item child : items) { 2835 if (child.getData() != null) { 2836 disassociate(child); 2837 child.dispose(); 2838 } else { 2839 if (needDummy && !haveDummy) { 2840 haveDummy = true; 2841 } else { 2842 child.dispose(); 2843 } 2844 } 2845 } 2846 if (needDummy && !haveDummy) { 2847 newItem(item, SWT.NULL, -1); 2848 } 2849 return true; 2850 } 2851 2852 /** 2853 * Not to be called by clients. Return the items to be refreshed as part of 2854 * an update. elementChildren are the new elements. 2855 * 2856 * @param widget widget to get children for 2857 * @param elementChildren unused 2858 * @since 3.4 2859 * @return Item[] 2860 * 2861 * @deprecated This method was inadvertently released as API but is not 2862 * intended to be called by clients. 2863 */ 2864 @Deprecated getChildren(Widget widget, Object[] elementChildren)2865 public Item[] getChildren(Widget widget, Object[] elementChildren) { 2866 return getChildren(widget); 2867 } 2868 2869 /** 2870 * Updates the "+"/"-" icon of the tree node from the given element. It 2871 * calls <code>isExpandable</code> to determine whether an element is 2872 * expandable. 2873 * 2874 * @param item 2875 * the item 2876 * @param element 2877 * the element 2878 */ updatePlus(Item item, Object element)2879 protected void updatePlus(Item item, Object element) { 2880 boolean hasPlus = getItemCount(item) > 0; 2881 boolean needsPlus = isExpandable(item, null, element); 2882 boolean removeAll = false; 2883 boolean addDummy = false; 2884 Object data = item.getData(); 2885 if (data != null && equals(element, data)) { 2886 // item shows same element 2887 if (hasPlus != needsPlus) { 2888 if (needsPlus) { 2889 addDummy = true; 2890 } else { 2891 removeAll = true; 2892 } 2893 } 2894 } else { 2895 // item shows different element 2896 removeAll = true; 2897 addDummy = needsPlus; 2898 2899 // we cannot maintain expand state so collapse it 2900 setExpanded(item, false); 2901 } 2902 if (removeAll) { 2903 // remove all children 2904 Item[] items = getItems(item); 2905 for (Item item2 : items) { 2906 if (item2.getData() != null) { 2907 disassociate(item2); 2908 } 2909 item2.dispose(); 2910 } 2911 } 2912 if (addDummy) { 2913 newItem(item, SWT.NULL, -1); // append a dummy 2914 } 2915 } 2916 2917 /** 2918 * Gets the expanded elements that are visible to the user. An expanded 2919 * element is only visible if the parent is expanded. 2920 * 2921 * @return the visible expanded elements 2922 * @since 2.0 2923 */ getVisibleExpandedElements()2924 public Object[] getVisibleExpandedElements() { 2925 ArrayList<Object> v = new ArrayList<>(); 2926 internalCollectVisibleExpanded(v, getControl()); 2927 return v.toArray(); 2928 } 2929 internalCollectVisibleExpanded(ArrayList<Object> result, Widget widget)2930 private void internalCollectVisibleExpanded(ArrayList<Object> result, Widget widget) { 2931 Item[] items = getChildren(widget); 2932 for (Item item : items) { 2933 if (getExpanded(item)) { 2934 Object data = item.getData(); 2935 if (data != null) { 2936 result.add(data); 2937 } 2938 // Only recurse if it is expanded - if 2939 // not then the children aren't visible 2940 internalCollectVisibleExpanded(result, item); 2941 } 2942 } 2943 } 2944 2945 /** 2946 * Returns the tree path for the given item. 2947 * 2948 * @param item item to get path for 2949 * @return {@link TreePath} 2950 * 2951 * @since 3.2 2952 */ getTreePathFromItem(Item item)2953 protected TreePath getTreePathFromItem(Item item) { 2954 LinkedList<Object> segments = new LinkedList<>(); 2955 while (item != null) { 2956 Object segment = item.getData(); 2957 Assert.isNotNull(segment); 2958 segments.addFirst(segment); 2959 item = getParentItem(item); 2960 } 2961 return new TreePath(segments.toArray()); 2962 } 2963 2964 /** 2965 * The <code>AbstractTreeViewer</code> implementation of this method returns 2966 * the result as an <code>ITreeSelection</code>. 2967 * <p> 2968 * Call {@link #getStructuredSelection()} instead to get an instance of 2969 * <code>ITreeSelection</code> directly. 2970 * </p> 2971 * Subclasses do not typically override this method, but implement 2972 * <code>getSelectionFromWidget(List)</code> instead. If they override this 2973 * method, they should return an <code>ITreeSelection</code> as well. 2974 * 2975 * @since 3.2 2976 */ 2977 @Override getSelection()2978 public ISelection getSelection() { 2979 Control control = getControl(); 2980 if (control == null || control.isDisposed()) { 2981 return TreeSelection.EMPTY; 2982 } 2983 Widget[] items = getSelection(getControl()); 2984 ArrayList<TreePath> list = new ArrayList<>(items.length); 2985 for (Widget item : items) { 2986 if (item.getData() != null) { 2987 list.add(getTreePathFromItem((Item) item)); 2988 } 2989 } 2990 return new TreeSelection(list.toArray(new TreePath[list.size()]), getComparer()); 2991 } 2992 2993 /** 2994 * Returns the <code>ITreeSelection</code> of this viewer. 2995 * <p> 2996 * Subclasses whose {@link #getSelection()} specifies to return a more 2997 * specific type should also override this method and return that type. 2998 * </p> 2999 * 3000 * @return ITreeSelection 3001 * @throws ClassCastException 3002 * if the selection of the viewer is not an instance of 3003 * ITreeSelection 3004 * @since 3.11 3005 */ 3006 @Override getStructuredSelection()3007 public ITreeSelection getStructuredSelection() throws ClassCastException { 3008 ISelection selection = getSelection(); 3009 if (selection instanceof ITreeSelection) { 3010 return (ITreeSelection) selection; 3011 } 3012 throw new ClassCastException( 3013 getClass().getName() + " should return an instance of ITreeSelection from its getSelection() method."); //$NON-NLS-1$ 3014 } 3015 3016 @Override setSelectionToWidget(ISelection selection, boolean reveal)3017 protected void setSelectionToWidget(ISelection selection, boolean reveal) { 3018 if (selection instanceof ITreeSelection) { 3019 ITreeSelection treeSelection = (ITreeSelection) selection; 3020 setSelectionToWidget(Arrays.asList(treeSelection.getPaths()), reveal); 3021 } else { 3022 super.setSelectionToWidget(selection, reveal); 3023 } 3024 } 3025 3026 /** 3027 * Returns a list of tree paths corresponding to expanded nodes in this 3028 * viewer's tree, including currently hidden ones that are marked as 3029 * expanded but are under a collapsed ancestor. 3030 * <p> 3031 * This method is typically used when preserving the interesting state of a 3032 * viewer; <code>setExpandedElements</code> is used during the restore. 3033 * </p> 3034 * 3035 * @return the array of expanded tree paths 3036 * @see #setExpandedElements 3037 * 3038 * @since 3.2 3039 */ getExpandedTreePaths()3040 public TreePath[] getExpandedTreePaths() { 3041 ArrayList<Item> items = new ArrayList<>(); 3042 internalCollectExpandedItems(items, getControl()); 3043 ArrayList<TreePath> result = new ArrayList<>(items.size()); 3044 for (Item item : items) { 3045 TreePath treePath = getTreePathFromItem(item); 3046 if (treePath != null) { 3047 result.add(treePath); 3048 } 3049 } 3050 return result.toArray(new TreePath[items.size()]); 3051 } 3052 isTreePathContentProvider()3053 private boolean isTreePathContentProvider() { 3054 return getContentProvider() instanceof ITreePathContentProvider; 3055 } 3056 3057 /** 3058 * Inserts the given element as a new child element of the given parent 3059 * element at the given position. If this viewer has a sorter, the position 3060 * is ignored and the element is inserted at the correct position in the 3061 * sort order. 3062 * <p> 3063 * This method should be called (by the content provider) when elements have 3064 * been added to the model, in order to cause the viewer to accurately 3065 * reflect the model. This method only affects the viewer, not the model. 3066 * </p> 3067 * 3068 * @param parentElementOrTreePath 3069 * the parent element, or the tree path to the parent 3070 * @param element 3071 * the element 3072 * @param position 3073 * a 0-based position relative to the model, or -1 to indicate 3074 * the last position 3075 * 3076 * @since 3.2 3077 */ insert(Object parentElementOrTreePath, Object element, int position)3078 public void insert(Object parentElementOrTreePath, Object element, 3079 int position) { 3080 Assert.isNotNull(parentElementOrTreePath); 3081 Assert.isNotNull(element); 3082 if (checkBusy()) 3083 return; 3084 if (getComparator() != null || hasFilters()) { 3085 add(parentElementOrTreePath, new Object[] { element }); 3086 return; 3087 } 3088 Widget[] items; 3089 if (internalIsInputOrEmptyPath(parentElementOrTreePath)) { 3090 items = new Widget[] { getControl() }; 3091 } else { 3092 items = internalFindItems(parentElementOrTreePath); 3093 } 3094 3095 for (Widget widget : items) { 3096 if (widget instanceof Item) { 3097 Item item = (Item) widget; 3098 3099 Item[] childItems = getChildren(item); 3100 if (getExpanded(item) 3101 || (childItems.length > 0 && childItems[0].getData() != null)) { 3102 // item has real children, go ahead and add 3103 int insertionPosition = position; 3104 if (insertionPosition == -1) { 3105 insertionPosition = getItemCount(item); 3106 } 3107 3108 createTreeItem(item, element, insertionPosition); 3109 } else { 3110 Object parentElement = parentElementOrTreePath; 3111 if (element instanceof TreePath) 3112 parentElement = ((TreePath) parentElement).getLastSegment(); 3113 updatePlus(item, parentElement); 3114 } 3115 } else { 3116 int insertionPosition = position; 3117 if (insertionPosition == -1) { 3118 insertionPosition = getItemCount((Control) widget); 3119 } 3120 3121 createTreeItem(widget, element, insertionPosition); 3122 } 3123 } 3124 } 3125 3126 @Override getColumnViewerOwner(int columnIndex)3127 protected Widget getColumnViewerOwner(int columnIndex) { 3128 // Return null by default 3129 return null; 3130 } 3131 3132 /** 3133 * This implementation of {@link #getItemAt(Point)} returns null to ensure 3134 * API backwards compatibility. Subclasses should override. 3135 * 3136 * @since 3.3 3137 */ 3138 @Override getItemAt(Point point)3139 protected Item getItemAt(Point point) { 3140 return null; 3141 } 3142 3143 /** 3144 * This implementation of {@link #createViewerEditor()} returns null to ensure 3145 * API backwards compatibility. Subclasses should override. 3146 * 3147 * @since 3.3 3148 */ 3149 @Override createViewerEditor()3150 protected ColumnViewerEditor createViewerEditor() { 3151 return null; 3152 } 3153 3154 /** 3155 * Returns the number of columns of this viewer. 3156 * <p><b>Subclasses should overwrite this method, which has a default 3157 * implementation (returning 0) for API backwards compatility reasons</b></p> 3158 * 3159 * @return the number of columns 3160 * 3161 * @since 3.3 3162 */ 3163 @Override doGetColumnCount()3164 protected int doGetColumnCount() { 3165 return 0; 3166 } 3167 3168 3169 /** 3170 * This implementation of buildLabel handles tree paths as well as elements. 3171 * 3172 * @param updateLabel 3173 * the ViewerLabel to collect the result in 3174 * @param elementOrPath 3175 * the element or tree path for which a label should be built 3176 * 3177 * @see org.eclipse.jface.viewers.StructuredViewer#buildLabel(org.eclipse.jface.viewers.ViewerLabel, 3178 * java.lang.Object) 3179 */ 3180 @Override buildLabel(ViewerLabel updateLabel, Object elementOrPath)3181 protected void buildLabel(ViewerLabel updateLabel, Object elementOrPath) { 3182 Object element; 3183 if (elementOrPath instanceof TreePath) { 3184 TreePath path = (TreePath) elementOrPath; 3185 IBaseLabelProvider provider = getLabelProvider(); 3186 if (provider instanceof ITreePathLabelProvider) { 3187 ITreePathLabelProvider pprov = (ITreePathLabelProvider) provider; 3188 buildLabel(updateLabel, path, pprov); 3189 return; 3190 } 3191 element = path.getLastSegment(); 3192 } else { 3193 element = elementOrPath; 3194 } 3195 super.buildLabel(updateLabel, element); 3196 } 3197 3198 /** 3199 * Returns true if the given object is either the input or an empty tree path. 3200 * 3201 * @param elementOrTreePath an element which could either be the viewer's input, or a tree path 3202 * 3203 * @return <code>true</code> if the given object is either the input or an empty tree path, 3204 * <code>false</code> otherwise. 3205 * @since 3.3 3206 */ internalIsInputOrEmptyPath(final Object elementOrTreePath)3207 final protected boolean internalIsInputOrEmptyPath(final Object elementOrTreePath) { 3208 if (elementOrTreePath.equals(getRoot())) 3209 return true; 3210 if (!(elementOrTreePath instanceof TreePath)) 3211 return false; 3212 return ((TreePath) elementOrTreePath).getSegmentCount() == 0; 3213 } 3214 3215 /* 3216 * Subclasses should implement 3217 */ 3218 @Override getViewerRowFromItem(Widget item)3219 protected ViewerRow getViewerRowFromItem(Widget item) { 3220 return null; 3221 } 3222 3223 /** 3224 * Instructs {@link #isExpandable(Object)} to consult filters to more accurately 3225 * determine if an item can be expanded. 3226 * <p> 3227 * Setting this value to <code>true</code> will affect performance of the tree 3228 * viewer. 3229 * </p><p> 3230 * To improve performance, by default the tree viewer does not consult filters when 3231 * determining if a tree node could be expanded. 3232 * </p> 3233 * @param checkFilters <code>true</code> to instruct tree viewer to consult filters 3234 * @see #isExpandable(Object) 3235 * @since 3.8 3236 */ setExpandPreCheckFilters(boolean checkFilters)3237 public void setExpandPreCheckFilters(boolean checkFilters) { 3238 if (checkFilters != isExpandableCheckFilters) { 3239 this.isExpandableCheckFilters = checkFilters; 3240 refresh(); 3241 } 3242 } 3243 3244 } 3245