1 /* 2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing; 27 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.beans.*; 31 import java.io.*; 32 import java.util.*; 33 import javax.swing.event.*; 34 import javax.swing.plaf.*; 35 import javax.swing.tree.*; 36 import javax.swing.text.Position; 37 import javax.accessibility.*; 38 import sun.swing.SwingUtilities2; 39 import sun.swing.SwingUtilities2.Section; 40 import static sun.swing.SwingUtilities2.Section.*; 41 42 43 /** 44 * <a name="jtree_description"></a> 45 * A control that displays a set of hierarchical data as an outline. 46 * You can find task-oriented documentation and examples of using trees in 47 * <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/tree.html">How to Use Trees</a>, 48 * a section in <em>The Java Tutorial.</em> 49 * <p> 50 * A specific node in a tree can be identified either by a 51 * <code>TreePath</code> (an object 52 * that encapsulates a node and all of its ancestors), or by its 53 * display row, where each row in the display area displays one node. 54 * An <i>expanded</i> node is a non-leaf node (as identified by 55 * <code>TreeModel.isLeaf(node)</code> returning false) that will displays 56 * its children when all its ancestors are <i>expanded</i>. 57 * A <i>collapsed</i> 58 * node is one which hides them. A <i>hidden</i> node is one which is 59 * under a collapsed ancestor. All of a <i>viewable</i> nodes parents 60 * are expanded, but may or may not be displayed. A <i>displayed</i> node 61 * is both viewable and in the display area, where it can be seen. 62 * </p> 63 * The following <code>JTree</code> methods use "visible" to mean "displayed": 64 * <ul> 65 * <li><code>isRootVisible()</code> 66 * <li><code>setRootVisible()</code> 67 * <li><code>scrollPathToVisible()</code> 68 * <li><code>scrollRowToVisible()</code> 69 * <li><code>getVisibleRowCount()</code> 70 * <li><code>setVisibleRowCount()</code> 71 * </ul> 72 * The next group of <code>JTree</code> methods use "visible" to mean 73 * "viewable" (under an expanded parent): 74 * <ul> 75 * <li><code>isVisible()</code> 76 * <li><code>makeVisible()</code> 77 * </ul> 78 * If you are interested in knowing when the selection changes implement 79 * the <code>TreeSelectionListener</code> interface and add the instance 80 * using the method <code>addTreeSelectionListener</code>. 81 * <code>valueChanged</code> will be invoked when the 82 * selection changes, that is if the user clicks twice on the same 83 * node <code>valueChanged</code> will only be invoked once. 84 * <p> 85 * If you are interested in detecting either double-click events or when 86 * a user clicks on a node, regardless of whether or not it was selected, 87 * we recommend you do the following: 88 * </p> 89 * <pre> 90 * final JTree tree = ...; 91 * 92 * MouseListener ml = new MouseAdapter() { 93 * public void <b>mousePressed</b>(MouseEvent e) { 94 * int selRow = tree.getRowForLocation(e.getX(), e.getY()); 95 * TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); 96 * if(selRow != -1) { 97 * if(e.getClickCount() == 1) { 98 * mySingleClick(selRow, selPath); 99 * } 100 * else if(e.getClickCount() == 2) { 101 * myDoubleClick(selRow, selPath); 102 * } 103 * } 104 * } 105 * }; 106 * tree.addMouseListener(ml); 107 * </pre> 108 * NOTE: This example obtains both the path and row, but you only need to 109 * get the one you're interested in. 110 * <p> 111 * To use <code>JTree</code> to display compound nodes 112 * (for example, nodes containing both 113 * a graphic icon and text), subclass {@link TreeCellRenderer} and use 114 * {@link #setCellRenderer} to tell the tree to use it. To edit such nodes, 115 * subclass {@link TreeCellEditor} and use {@link #setCellEditor}. 116 * </p> 117 * <p> 118 * Like all <code>JComponent</code> classes, you can use {@link InputMap} and 119 * {@link ActionMap} 120 * to associate an {@link Action} object with a {@link KeyStroke} 121 * and execute the action under specified conditions. 122 * </p> 123 * <strong>Warning:</strong> Swing is not thread safe. For more 124 * information see <a 125 * href="package-summary.html#threading">Swing's Threading 126 * Policy</a>. 127 * <p> 128 * <strong>Warning:</strong> 129 * Serialized objects of this class will not be compatible with 130 * future Swing releases. The current serialization support is 131 * appropriate for short term storage or RMI between applications running 132 * the same version of Swing. As of 1.4, support for long term storage 133 * of all JavaBeans™ 134 * has been added to the <code>java.beans</code> package. 135 * Please see {@link java.beans.XMLEncoder}. 136 *</p> 137 * @beaninfo 138 * attribute: isContainer false 139 * description: A component that displays a set of hierarchical data as an outline. 140 * 141 * @author Rob Davis 142 * @author Ray Ryan 143 * @author Scott Violet 144 */ 145 @SuppressWarnings("serial") 146 public class JTree extends JComponent implements Scrollable, Accessible 147 { 148 /** 149 * @see #getUIClassID 150 * @see #readObject 151 */ 152 private static final String uiClassID = "TreeUI"; 153 154 /** 155 * The model that defines the tree displayed by this object. 156 */ 157 transient protected TreeModel treeModel; 158 159 /** 160 * Models the set of selected nodes in this tree. 161 */ 162 transient protected TreeSelectionModel selectionModel; 163 164 /** 165 * True if the root node is displayed, false if its children are 166 * the highest visible nodes. 167 */ 168 protected boolean rootVisible; 169 170 /** 171 * The cell used to draw nodes. If <code>null</code>, the UI uses a default 172 * <code>cellRenderer</code>. 173 */ 174 transient protected TreeCellRenderer cellRenderer; 175 176 /** 177 * Height to use for each display row. If this is <= 0 the renderer 178 * determines the height for each row. 179 */ 180 protected int rowHeight; 181 private boolean rowHeightSet = false; 182 183 /** 184 * Maps from <code>TreePath</code> to <code>Boolean</code> 185 * indicating whether or not the 186 * particular path is expanded. This ONLY indicates whether a 187 * given path is expanded, and NOT if it is visible or not. That 188 * information must be determined by visiting all the parent 189 * paths and seeing if they are visible. 190 */ 191 transient private Hashtable<TreePath, Boolean> expandedState; 192 193 194 /** 195 * True if handles are displayed at the topmost level of the tree. 196 * <p> 197 * A handle is a small icon that displays adjacent to the node which 198 * allows the user to click once to expand or collapse the node. A 199 * common interface shows a plus sign (+) for a node which can be 200 * expanded and a minus sign (-) for a node which can be collapsed. 201 * Handles are always shown for nodes below the topmost level. 202 * <p> 203 * If the <code>rootVisible</code> setting specifies that the root 204 * node is to be displayed, then that is the only node at the topmost 205 * level. If the root node is not displayed, then all of its 206 * children are at the topmost level of the tree. Handles are 207 * always displayed for nodes other than the topmost. 208 * <p> 209 * If the root node isn't visible, it is generally a good to make 210 * this value true. Otherwise, the tree looks exactly like a list, 211 * and users may not know that the "list entries" are actually 212 * tree nodes. 213 * 214 * @see #rootVisible 215 */ 216 protected boolean showsRootHandles; 217 private boolean showsRootHandlesSet = false; 218 219 /** 220 * Creates a new event and passed it off the 221 * <code>selectionListeners</code>. 222 */ 223 protected transient TreeSelectionRedirector selectionRedirector; 224 225 /** 226 * Editor for the entries. Default is <code>null</code> 227 * (tree is not editable). 228 */ 229 transient protected TreeCellEditor cellEditor; 230 231 /** 232 * Is the tree editable? Default is false. 233 */ 234 protected boolean editable; 235 236 /** 237 * Is this tree a large model? This is a code-optimization setting. 238 * A large model can be used when the cell height is the same for all 239 * nodes. The UI will then cache very little information and instead 240 * continually message the model. Without a large model the UI caches 241 * most of the information, resulting in fewer method calls to the model. 242 * <p> 243 * This value is only a suggestion to the UI. Not all UIs will 244 * take advantage of it. Default value is false. 245 */ 246 protected boolean largeModel; 247 248 /** 249 * Number of rows to make visible at one time. This value is used for 250 * the <code>Scrollable</code> interface. It determines the preferred 251 * size of the display area. 252 */ 253 protected int visibleRowCount; 254 255 /** 256 * If true, when editing is to be stopped by way of selection changing, 257 * data in tree changing or other means <code>stopCellEditing</code> 258 * is invoked, and changes are saved. If false, 259 * <code>cancelCellEditing</code> is invoked, and changes 260 * are discarded. Default is false. 261 */ 262 protected boolean invokesStopCellEditing; 263 264 /** 265 * If true, when a node is expanded, as many of the descendants are 266 * scrolled to be visible. 267 */ 268 protected boolean scrollsOnExpand; 269 private boolean scrollsOnExpandSet = false; 270 271 /** 272 * Number of mouse clicks before a node is expanded. 273 */ 274 protected int toggleClickCount; 275 276 /** 277 * Updates the <code>expandedState</code>. 278 */ 279 transient protected TreeModelListener treeModelListener; 280 281 /** 282 * Used when <code>setExpandedState</code> is invoked, 283 * will be a <code>Stack</code> of <code>Stack</code>s. 284 */ 285 transient private Stack<Stack<TreePath>> expandedStack; 286 287 /** 288 * Lead selection path, may not be <code>null</code>. 289 */ 290 private TreePath leadPath; 291 292 /** 293 * Anchor path. 294 */ 295 private TreePath anchorPath; 296 297 /** 298 * True if paths in the selection should be expanded. 299 */ 300 private boolean expandsSelectedPaths; 301 302 /** 303 * This is set to true for the life of the <code>setUI</code> call. 304 */ 305 private boolean settingUI; 306 307 /** If true, mouse presses on selections initiate a drag operation. */ 308 private boolean dragEnabled; 309 310 /** 311 * The drop mode for this component. 312 */ 313 private DropMode dropMode = DropMode.USE_SELECTION; 314 315 /** 316 * The drop location. 317 */ 318 private transient DropLocation dropLocation; 319 320 /** 321 * A subclass of <code>TransferHandler.DropLocation</code> representing 322 * a drop location for a <code>JTree</code>. 323 * 324 * @see #getDropLocation 325 * @since 1.6 326 */ 327 public static final class DropLocation extends TransferHandler.DropLocation { 328 private final TreePath path; 329 private final int index; 330 331 private DropLocation(Point p, TreePath path, int index) { 332 super(p); 333 this.path = path; 334 this.index = index; 335 } 336 337 /** 338 * Returns the index where the dropped data should be inserted 339 * with respect to the path returned by <code>getPath()</code>. 340 * <p> 341 * For drop modes <code>DropMode.USE_SELECTION</code> and 342 * <code>DropMode.ON</code>, this index is unimportant (and it will 343 * always be <code>-1</code>) as the only interesting data is the 344 * path over which the drop operation occurred. 345 * <p> 346 * For drop mode <code>DropMode.INSERT</code>, this index 347 * indicates the index at which the data should be inserted into 348 * the parent path represented by <code>getPath()</code>. 349 * <code>-1</code> indicates that the drop occurred over the 350 * parent itself, and in most cases should be treated as inserting 351 * into either the beginning or the end of the parent's list of 352 * children. 353 * <p> 354 * For <code>DropMode.ON_OR_INSERT</code>, this value will be 355 * an insert index, as described above, or <code>-1</code> if 356 * the drop occurred over the path itself. 357 * 358 * @return the child index 359 * @see #getPath 360 */ 361 public int getChildIndex() { 362 return index; 363 } 364 365 /** 366 * Returns the path where dropped data should be placed in the 367 * tree. 368 * <p> 369 * Interpretation of this value depends on the drop mode set on the 370 * component. If the drop mode is <code>DropMode.USE_SELECTION</code> 371 * or <code>DropMode.ON</code>, the return value is the path in the 372 * tree over which the data has been (or will be) dropped. 373 * <code>null</code> indicates that the drop is over empty space, 374 * not associated with a particular path. 375 * <p> 376 * If the drop mode is <code>DropMode.INSERT</code>, the return value 377 * refers to the path that should become the parent of the new data, 378 * in which case <code>getChildIndex()</code> indicates where the 379 * new item should be inserted into this parent path. A 380 * <code>null</code> path indicates that no parent path has been 381 * determined, which can happen for multiple reasons: 382 * <ul> 383 * <li>The tree has no model 384 * <li>There is no root in the tree 385 * <li>The root is collapsed 386 * <li>The root is a leaf node 387 * </ul> 388 * It is up to the developer to decide if and how they wish to handle 389 * the <code>null</code> case. 390 * <p> 391 * If the drop mode is <code>DropMode.ON_OR_INSERT</code>, 392 * <code>getChildIndex</code> can be used to determine whether the 393 * drop is on top of the path itself (<code>-1</code>) or the index 394 * at which it should be inserted into the path (values other than 395 * <code>-1</code>). 396 * 397 * @return the drop path 398 * @see #getChildIndex 399 */ 400 public TreePath getPath() { 401 return path; 402 } 403 404 /** 405 * Returns a string representation of this drop location. 406 * This method is intended to be used for debugging purposes, 407 * and the content and format of the returned string may vary 408 * between implementations. 409 * 410 * @return a string representation of this drop location 411 */ 412 public String toString() { 413 return getClass().getName() 414 + "[dropPoint=" + getDropPoint() + "," 415 + "path=" + path + "," 416 + "childIndex=" + index + "]"; 417 } 418 } 419 420 /** 421 * The row to expand during DnD. 422 */ 423 private int expandRow = -1; 424 425 @SuppressWarnings("serial") 426 private class TreeTimer extends Timer { 427 public TreeTimer() { 428 super(2000, null); 429 setRepeats(false); 430 } 431 432 public void fireActionPerformed(ActionEvent ae) { 433 JTree.this.expandRow(expandRow); 434 } 435 } 436 437 /** 438 * A timer to expand nodes during drop. 439 */ 440 private TreeTimer dropTimer; 441 442 /** 443 * When <code>addTreeExpansionListener</code> is invoked, 444 * and <code>settingUI</code> is true, this ivar gets set to the passed in 445 * <code>Listener</code>. This listener is then notified first in 446 * <code>fireTreeCollapsed</code> and <code>fireTreeExpanded</code>. 447 * <p>This is an ugly workaround for a way to have the UI listener 448 * get notified before other listeners. 449 */ 450 private transient TreeExpansionListener uiTreeExpansionListener; 451 452 /** 453 * Max number of stacks to keep around. 454 */ 455 private static int TEMP_STACK_SIZE = 11; 456 457 // 458 // Bound property names 459 // 460 /** Bound property name for <code>cellRenderer</code>. */ 461 public final static String CELL_RENDERER_PROPERTY = "cellRenderer"; 462 /** Bound property name for <code>treeModel</code>. */ 463 public final static String TREE_MODEL_PROPERTY = "model"; 464 /** Bound property name for <code>rootVisible</code>. */ 465 public final static String ROOT_VISIBLE_PROPERTY = "rootVisible"; 466 /** Bound property name for <code>showsRootHandles</code>. */ 467 public final static String SHOWS_ROOT_HANDLES_PROPERTY = "showsRootHandles"; 468 /** Bound property name for <code>rowHeight</code>. */ 469 public final static String ROW_HEIGHT_PROPERTY = "rowHeight"; 470 /** Bound property name for <code>cellEditor</code>. */ 471 public final static String CELL_EDITOR_PROPERTY = "cellEditor"; 472 /** Bound property name for <code>editable</code>. */ 473 public final static String EDITABLE_PROPERTY = "editable"; 474 /** Bound property name for <code>largeModel</code>. */ 475 public final static String LARGE_MODEL_PROPERTY = "largeModel"; 476 /** Bound property name for selectionModel. */ 477 public final static String SELECTION_MODEL_PROPERTY = "selectionModel"; 478 /** Bound property name for <code>visibleRowCount</code>. */ 479 public final static String VISIBLE_ROW_COUNT_PROPERTY = "visibleRowCount"; 480 /** Bound property name for <code>messagesStopCellEditing</code>. */ 481 public final static String INVOKES_STOP_CELL_EDITING_PROPERTY = "invokesStopCellEditing"; 482 /** Bound property name for <code>scrollsOnExpand</code>. */ 483 public final static String SCROLLS_ON_EXPAND_PROPERTY = "scrollsOnExpand"; 484 /** Bound property name for <code>toggleClickCount</code>. */ 485 public final static String TOGGLE_CLICK_COUNT_PROPERTY = "toggleClickCount"; 486 /** Bound property name for <code>leadSelectionPath</code>. 487 * @since 1.3 */ 488 public final static String LEAD_SELECTION_PATH_PROPERTY = "leadSelectionPath"; 489 /** Bound property name for anchor selection path. 490 * @since 1.3 */ 491 public final static String ANCHOR_SELECTION_PATH_PROPERTY = "anchorSelectionPath"; 492 /** Bound property name for expands selected paths property 493 * @since 1.3 */ 494 public final static String EXPANDS_SELECTED_PATHS_PROPERTY = "expandsSelectedPaths"; 495 496 497 /** 498 * Creates and returns a sample <code>TreeModel</code>. 499 * Used primarily for beanbuilders to show something interesting. 500 * 501 * @return the default <code>TreeModel</code> 502 */ 503 protected static TreeModel getDefaultTreeModel() { 504 DefaultMutableTreeNode root = new DefaultMutableTreeNode("JTree"); 505 DefaultMutableTreeNode parent; 506 507 parent = new DefaultMutableTreeNode("colors"); 508 root.add(parent); 509 parent.add(new DefaultMutableTreeNode("blue")); 510 parent.add(new DefaultMutableTreeNode("violet")); 511 parent.add(new DefaultMutableTreeNode("red")); 512 parent.add(new DefaultMutableTreeNode("yellow")); 513 514 parent = new DefaultMutableTreeNode("sports"); 515 root.add(parent); 516 parent.add(new DefaultMutableTreeNode("basketball")); 517 parent.add(new DefaultMutableTreeNode("soccer")); 518 parent.add(new DefaultMutableTreeNode("football")); 519 parent.add(new DefaultMutableTreeNode("hockey")); 520 521 parent = new DefaultMutableTreeNode("food"); 522 root.add(parent); 523 parent.add(new DefaultMutableTreeNode("hot dogs")); 524 parent.add(new DefaultMutableTreeNode("pizza")); 525 parent.add(new DefaultMutableTreeNode("ravioli")); 526 parent.add(new DefaultMutableTreeNode("bananas")); 527 return new DefaultTreeModel(root); 528 } 529 530 /** 531 * Returns a <code>TreeModel</code> wrapping the specified object. 532 * If the object is:<ul> 533 * <li>an array of <code>Object</code>s, 534 * <li>a <code>Hashtable</code>, or 535 * <li>a <code>Vector</code> 536 * </ul>then a new root node is created with each of the incoming 537 * objects as children. Otherwise, a new root is created with 538 * a value of {@code "root"}. 539 * 540 * @param value the <code>Object</code> used as the foundation for 541 * the <code>TreeModel</code> 542 * @return a <code>TreeModel</code> wrapping the specified object 543 */ 544 protected static TreeModel createTreeModel(Object value) { 545 DefaultMutableTreeNode root; 546 547 if((value instanceof Object[]) || (value instanceof Hashtable) || 548 (value instanceof Vector)) { 549 root = new DefaultMutableTreeNode("root"); 550 DynamicUtilTreeNode.createChildren(root, value); 551 } 552 else { 553 root = new DynamicUtilTreeNode("root", value); 554 } 555 return new DefaultTreeModel(root, false); 556 } 557 558 /** 559 * Returns a <code>JTree</code> with a sample model. 560 * The default model used by the tree defines a leaf node as any node 561 * without children. 562 * 563 * @see DefaultTreeModel#asksAllowsChildren 564 */ 565 public JTree() { 566 this(getDefaultTreeModel()); 567 } 568 569 /** 570 * Returns a <code>JTree</code> with each element of the 571 * specified array as the 572 * child of a new root node which is not displayed. 573 * By default, the tree defines a leaf node as any node without 574 * children. 575 * 576 * @param value an array of <code>Object</code>s 577 * @see DefaultTreeModel#asksAllowsChildren 578 */ 579 public JTree(Object[] value) { 580 this(createTreeModel(value)); 581 this.setRootVisible(false); 582 this.setShowsRootHandles(true); 583 expandRoot(); 584 } 585 586 /** 587 * Returns a <code>JTree</code> with each element of the specified 588 * <code>Vector</code> as the 589 * child of a new root node which is not displayed. By default, the 590 * tree defines a leaf node as any node without children. 591 * 592 * @param value a <code>Vector</code> 593 * @see DefaultTreeModel#asksAllowsChildren 594 */ 595 public JTree(Vector<?> value) { 596 this(createTreeModel(value)); 597 this.setRootVisible(false); 598 this.setShowsRootHandles(true); 599 expandRoot(); 600 } 601 602 /** 603 * Returns a <code>JTree</code> created from a <code>Hashtable</code> 604 * which does not display with root. 605 * Each value-half of the key/value pairs in the <code>HashTable</code> 606 * becomes a child of the new root node. By default, the tree defines 607 * a leaf node as any node without children. 608 * 609 * @param value a <code>Hashtable</code> 610 * @see DefaultTreeModel#asksAllowsChildren 611 */ 612 public JTree(Hashtable<?,?> value) { 613 this(createTreeModel(value)); 614 this.setRootVisible(false); 615 this.setShowsRootHandles(true); 616 expandRoot(); 617 } 618 619 /** 620 * Returns a <code>JTree</code> with the specified 621 * <code>TreeNode</code> as its root, 622 * which displays the root node. 623 * By default, the tree defines a leaf node as any node without children. 624 * 625 * @param root a <code>TreeNode</code> object 626 * @see DefaultTreeModel#asksAllowsChildren 627 */ 628 public JTree(TreeNode root) { 629 this(root, false); 630 } 631 632 /** 633 * Returns a <code>JTree</code> with the specified <code>TreeNode</code> 634 * as its root, which 635 * displays the root node and which decides whether a node is a 636 * leaf node in the specified manner. 637 * 638 * @param root a <code>TreeNode</code> object 639 * @param asksAllowsChildren if false, any node without children is a 640 * leaf node; if true, only nodes that do not allow 641 * children are leaf nodes 642 * @see DefaultTreeModel#asksAllowsChildren 643 */ 644 public JTree(TreeNode root, boolean asksAllowsChildren) { 645 this(new DefaultTreeModel(root, asksAllowsChildren)); 646 } 647 648 /** 649 * Returns an instance of <code>JTree</code> which displays the root node 650 * -- the tree is created using the specified data model. 651 * 652 * @param newModel the <code>TreeModel</code> to use as the data model 653 */ 654 @ConstructorProperties({"model"}) 655 public JTree(TreeModel newModel) { 656 super(); 657 expandedStack = new Stack<Stack<TreePath>>(); 658 toggleClickCount = 2; 659 expandedState = new Hashtable<TreePath, Boolean>(); 660 setLayout(null); 661 rowHeight = 16; 662 visibleRowCount = 20; 663 rootVisible = true; 664 selectionModel = new DefaultTreeSelectionModel(); 665 cellRenderer = null; 666 scrollsOnExpand = true; 667 setOpaque(true); 668 expandsSelectedPaths = true; 669 updateUI(); 670 setModel(newModel); 671 } 672 673 /** 674 * Returns the L&F object that renders this component. 675 * 676 * @return the <code>TreeUI</code> object that renders this component 677 */ 678 public TreeUI getUI() { 679 return (TreeUI)ui; 680 } 681 682 /** 683 * Sets the L&F object that renders this component. 684 * <p> 685 * This is a bound property. 686 * 687 * @param ui the <code>TreeUI</code> L&F object 688 * @see UIDefaults#getUI 689 * @beaninfo 690 * bound: true 691 * hidden: true 692 * attribute: visualUpdate true 693 * description: The UI object that implements the Component's LookAndFeel. 694 */ 695 public void setUI(TreeUI ui) { 696 if (this.ui != ui) { 697 settingUI = true; 698 uiTreeExpansionListener = null; 699 try { 700 super.setUI(ui); 701 } 702 finally { 703 settingUI = false; 704 } 705 } 706 } 707 708 /** 709 * Notification from the <code>UIManager</code> that the L&F has changed. 710 * Replaces the current UI object with the latest version from the 711 * <code>UIManager</code>. 712 * 713 * @see JComponent#updateUI 714 */ 715 public void updateUI() { 716 setUI((TreeUI)UIManager.getUI(this)); 717 718 SwingUtilities.updateRendererOrEditorUI(getCellRenderer()); 719 SwingUtilities.updateRendererOrEditorUI(getCellEditor()); 720 } 721 722 723 /** 724 * Returns the name of the L&F class that renders this component. 725 * 726 * @return the string "TreeUI" 727 * @see JComponent#getUIClassID 728 * @see UIDefaults#getUI 729 */ 730 public String getUIClassID() { 731 return uiClassID; 732 } 733 734 735 /** 736 * Returns the current <code>TreeCellRenderer</code> 737 * that is rendering each cell. 738 * 739 * @return the <code>TreeCellRenderer</code> that is rendering each cell 740 */ 741 public TreeCellRenderer getCellRenderer() { 742 return cellRenderer; 743 } 744 745 /** 746 * Sets the <code>TreeCellRenderer</code> that will be used to 747 * draw each cell. 748 * <p> 749 * This is a bound property. 750 * 751 * @param x the <code>TreeCellRenderer</code> that is to render each cell 752 * @beaninfo 753 * bound: true 754 * description: The TreeCellRenderer that will be used to draw 755 * each cell. 756 */ 757 public void setCellRenderer(TreeCellRenderer x) { 758 TreeCellRenderer oldValue = cellRenderer; 759 760 cellRenderer = x; 761 firePropertyChange(CELL_RENDERER_PROPERTY, oldValue, cellRenderer); 762 invalidate(); 763 } 764 765 /** 766 * Determines whether the tree is editable. Fires a property 767 * change event if the new setting is different from the existing 768 * setting. 769 * <p> 770 * This is a bound property. 771 * 772 * @param flag a boolean value, true if the tree is editable 773 * @beaninfo 774 * bound: true 775 * description: Whether the tree is editable. 776 */ 777 public void setEditable(boolean flag) { 778 boolean oldValue = this.editable; 779 780 this.editable = flag; 781 firePropertyChange(EDITABLE_PROPERTY, oldValue, flag); 782 if (accessibleContext != null) { 783 accessibleContext.firePropertyChange( 784 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 785 (oldValue ? AccessibleState.EDITABLE : null), 786 (flag ? AccessibleState.EDITABLE : null)); 787 } 788 } 789 790 /** 791 * Returns true if the tree is editable. 792 * 793 * @return true if the tree is editable 794 */ 795 public boolean isEditable() { 796 return editable; 797 } 798 799 /** 800 * Sets the cell editor. A <code>null</code> value implies that the 801 * tree cannot be edited. If this represents a change in the 802 * <code>cellEditor</code>, the <code>propertyChange</code> 803 * method is invoked on all listeners. 804 * <p> 805 * This is a bound property. 806 * 807 * @param cellEditor the <code>TreeCellEditor</code> to use 808 * @beaninfo 809 * bound: true 810 * description: The cell editor. A null value implies the tree 811 * cannot be edited. 812 */ 813 public void setCellEditor(TreeCellEditor cellEditor) { 814 TreeCellEditor oldEditor = this.cellEditor; 815 816 this.cellEditor = cellEditor; 817 firePropertyChange(CELL_EDITOR_PROPERTY, oldEditor, cellEditor); 818 invalidate(); 819 } 820 821 /** 822 * Returns the editor used to edit entries in the tree. 823 * 824 * @return the <code>TreeCellEditor</code> in use, 825 * or <code>null</code> if the tree cannot be edited 826 */ 827 public TreeCellEditor getCellEditor() { 828 return cellEditor; 829 } 830 831 /** 832 * Returns the <code>TreeModel</code> that is providing the data. 833 * 834 * @return the <code>TreeModel</code> that is providing the data 835 */ 836 public TreeModel getModel() { 837 return treeModel; 838 } 839 840 /** 841 * Sets the <code>TreeModel</code> that will provide the data. 842 * <p> 843 * This is a bound property. 844 * 845 * @param newModel the <code>TreeModel</code> that is to provide the data 846 * @beaninfo 847 * bound: true 848 * description: The TreeModel that will provide the data. 849 */ 850 public void setModel(TreeModel newModel) { 851 clearSelection(); 852 853 TreeModel oldModel = treeModel; 854 855 if(treeModel != null && treeModelListener != null) 856 treeModel.removeTreeModelListener(treeModelListener); 857 858 if (accessibleContext != null) { 859 if (treeModel != null) { 860 treeModel.removeTreeModelListener((TreeModelListener)accessibleContext); 861 } 862 if (newModel != null) { 863 newModel.addTreeModelListener((TreeModelListener)accessibleContext); 864 } 865 } 866 867 treeModel = newModel; 868 clearToggledPaths(); 869 if(treeModel != null) { 870 if(treeModelListener == null) 871 treeModelListener = createTreeModelListener(); 872 if(treeModelListener != null) 873 treeModel.addTreeModelListener(treeModelListener); 874 // Mark the root as expanded, if it isn't a leaf. 875 Object treeRoot = treeModel.getRoot(); 876 if(treeRoot != null && 877 !treeModel.isLeaf(treeRoot)) { 878 expandedState.put(new TreePath(treeRoot), 879 Boolean.TRUE); 880 } 881 } 882 firePropertyChange(TREE_MODEL_PROPERTY, oldModel, treeModel); 883 invalidate(); 884 } 885 886 /** 887 * Returns true if the root node of the tree is displayed. 888 * 889 * @return true if the root node of the tree is displayed 890 * @see #rootVisible 891 */ 892 public boolean isRootVisible() { 893 return rootVisible; 894 } 895 896 /** 897 * Determines whether or not the root node from 898 * the <code>TreeModel</code> is visible. 899 * <p> 900 * This is a bound property. 901 * 902 * @param rootVisible true if the root node of the tree is to be displayed 903 * @see #rootVisible 904 * @beaninfo 905 * bound: true 906 * description: Whether or not the root node 907 * from the TreeModel is visible. 908 */ 909 public void setRootVisible(boolean rootVisible) { 910 boolean oldValue = this.rootVisible; 911 912 this.rootVisible = rootVisible; 913 firePropertyChange(ROOT_VISIBLE_PROPERTY, oldValue, this.rootVisible); 914 if (accessibleContext != null) { 915 ((AccessibleJTree)accessibleContext).fireVisibleDataPropertyChange(); 916 } 917 } 918 919 /** 920 * Sets the value of the <code>showsRootHandles</code> property, 921 * which specifies whether the node handles should be displayed. 922 * The default value of this property depends on the constructor 923 * used to create the <code>JTree</code>. 924 * Some look and feels might not support handles; 925 * they will ignore this property. 926 * <p> 927 * This is a bound property. 928 * 929 * @param newValue <code>true</code> if root handles should be displayed; 930 * otherwise, <code>false</code> 931 * @see #showsRootHandles 932 * @see #getShowsRootHandles 933 * @beaninfo 934 * bound: true 935 * description: Whether the node handles are to be 936 * displayed. 937 */ 938 public void setShowsRootHandles(boolean newValue) { 939 boolean oldValue = showsRootHandles; 940 TreeModel model = getModel(); 941 942 showsRootHandles = newValue; 943 showsRootHandlesSet = true; 944 firePropertyChange(SHOWS_ROOT_HANDLES_PROPERTY, oldValue, 945 showsRootHandles); 946 if (accessibleContext != null) { 947 ((AccessibleJTree)accessibleContext).fireVisibleDataPropertyChange(); 948 } 949 invalidate(); 950 } 951 952 /** 953 * Returns the value of the <code>showsRootHandles</code> property. 954 * 955 * @return the value of the <code>showsRootHandles</code> property 956 * @see #showsRootHandles 957 */ 958 public boolean getShowsRootHandles() 959 { 960 return showsRootHandles; 961 } 962 963 /** 964 * Sets the height of each cell, in pixels. If the specified value 965 * is less than or equal to zero the current cell renderer is 966 * queried for each row's height. 967 * <p> 968 * This is a bound property. 969 * 970 * @param rowHeight the height of each cell, in pixels 971 * @beaninfo 972 * bound: true 973 * description: The height of each cell. 974 */ 975 public void setRowHeight(int rowHeight) 976 { 977 int oldValue = this.rowHeight; 978 979 this.rowHeight = rowHeight; 980 rowHeightSet = true; 981 firePropertyChange(ROW_HEIGHT_PROPERTY, oldValue, this.rowHeight); 982 invalidate(); 983 } 984 985 /** 986 * Returns the height of each row. If the returned value is less than 987 * or equal to 0 the height for each row is determined by the 988 * renderer. 989 * 990 */ 991 public int getRowHeight() 992 { 993 return rowHeight; 994 } 995 996 /** 997 * Returns true if the height of each display row is a fixed size. 998 * 999 * @return true if the height of each row is a fixed size 1000 */ 1001 public boolean isFixedRowHeight() 1002 { 1003 return (rowHeight > 0); 1004 } 1005 1006 /** 1007 * Specifies whether the UI should use a large model. 1008 * (Not all UIs will implement this.) Fires a property change 1009 * for the LARGE_MODEL_PROPERTY. 1010 * <p> 1011 * This is a bound property. 1012 * 1013 * @param newValue true to suggest a large model to the UI 1014 * @see #largeModel 1015 * @beaninfo 1016 * bound: true 1017 * description: Whether the UI should use a 1018 * large model. 1019 */ 1020 public void setLargeModel(boolean newValue) { 1021 boolean oldValue = largeModel; 1022 1023 largeModel = newValue; 1024 firePropertyChange(LARGE_MODEL_PROPERTY, oldValue, newValue); 1025 } 1026 1027 /** 1028 * Returns true if the tree is configured for a large model. 1029 * 1030 * @return true if a large model is suggested 1031 * @see #largeModel 1032 */ 1033 public boolean isLargeModel() { 1034 return largeModel; 1035 } 1036 1037 /** 1038 * Determines what happens when editing is interrupted by selecting 1039 * another node in the tree, a change in the tree's data, or by some 1040 * other means. Setting this property to <code>true</code> causes the 1041 * changes to be automatically saved when editing is interrupted. 1042 * <p> 1043 * Fires a property change for the INVOKES_STOP_CELL_EDITING_PROPERTY. 1044 * 1045 * @param newValue true means that <code>stopCellEditing</code> is invoked 1046 * when editing is interrupted, and data is saved; false means that 1047 * <code>cancelCellEditing</code> is invoked, and changes are lost 1048 * @beaninfo 1049 * bound: true 1050 * description: Determines what happens when editing is interrupted, 1051 * selecting another node in the tree, a change in the 1052 * tree's data, or some other means. 1053 */ 1054 public void setInvokesStopCellEditing(boolean newValue) { 1055 boolean oldValue = invokesStopCellEditing; 1056 1057 invokesStopCellEditing = newValue; 1058 firePropertyChange(INVOKES_STOP_CELL_EDITING_PROPERTY, oldValue, 1059 newValue); 1060 } 1061 1062 /** 1063 * Returns the indicator that tells what happens when editing is 1064 * interrupted. 1065 * 1066 * @return the indicator that tells what happens when editing is 1067 * interrupted 1068 * @see #setInvokesStopCellEditing 1069 */ 1070 public boolean getInvokesStopCellEditing() { 1071 return invokesStopCellEditing; 1072 } 1073 1074 /** 1075 * Sets the <code>scrollsOnExpand</code> property, 1076 * which determines whether the 1077 * tree might scroll to show previously hidden children. 1078 * If this property is <code>true</code> (the default), 1079 * when a node expands 1080 * the tree can use scrolling to make 1081 * the maximum possible number of the node's descendants visible. 1082 * In some look and feels, trees might not need to scroll when expanded; 1083 * those look and feels will ignore this property. 1084 * <p> 1085 * This is a bound property. 1086 * 1087 * @param newValue <code>false</code> to disable scrolling on expansion; 1088 * <code>true</code> to enable it 1089 * @see #getScrollsOnExpand 1090 * 1091 * @beaninfo 1092 * bound: true 1093 * description: Indicates if a node descendant should be scrolled when expanded. 1094 */ 1095 public void setScrollsOnExpand(boolean newValue) { 1096 boolean oldValue = scrollsOnExpand; 1097 1098 scrollsOnExpand = newValue; 1099 scrollsOnExpandSet = true; 1100 firePropertyChange(SCROLLS_ON_EXPAND_PROPERTY, oldValue, 1101 newValue); 1102 } 1103 1104 /** 1105 * Returns the value of the <code>scrollsOnExpand</code> property. 1106 * 1107 * @return the value of the <code>scrollsOnExpand</code> property 1108 */ 1109 public boolean getScrollsOnExpand() { 1110 return scrollsOnExpand; 1111 } 1112 1113 /** 1114 * Sets the number of mouse clicks before a node will expand or close. 1115 * The default is two. 1116 * <p> 1117 * This is a bound property. 1118 * 1119 * @since 1.3 1120 * @beaninfo 1121 * bound: true 1122 * description: Number of clicks before a node will expand/collapse. 1123 */ 1124 public void setToggleClickCount(int clickCount) { 1125 int oldCount = toggleClickCount; 1126 1127 toggleClickCount = clickCount; 1128 firePropertyChange(TOGGLE_CLICK_COUNT_PROPERTY, oldCount, 1129 clickCount); 1130 } 1131 1132 /** 1133 * Returns the number of mouse clicks needed to expand or close a node. 1134 * 1135 * @return number of mouse clicks before node is expanded 1136 * @since 1.3 1137 */ 1138 public int getToggleClickCount() { 1139 return toggleClickCount; 1140 } 1141 1142 /** 1143 * Configures the <code>expandsSelectedPaths</code> property. If 1144 * true, any time the selection is changed, either via the 1145 * <code>TreeSelectionModel</code>, or the cover methods provided by 1146 * <code>JTree</code>, the <code>TreePath</code>s parents will be 1147 * expanded to make them visible (visible meaning the parent path is 1148 * expanded, not necessarily in the visible rectangle of the 1149 * <code>JTree</code>). If false, when the selection 1150 * changes the nodes parent is not made visible (all its parents expanded). 1151 * This is useful if you wish to have your selection model maintain paths 1152 * that are not always visible (all parents expanded). 1153 * <p> 1154 * This is a bound property. 1155 * 1156 * @param newValue the new value for <code>expandsSelectedPaths</code> 1157 * 1158 * @since 1.3 1159 * @beaninfo 1160 * bound: true 1161 * description: Indicates whether changes to the selection should make 1162 * the parent of the path visible. 1163 */ 1164 public void setExpandsSelectedPaths(boolean newValue) { 1165 boolean oldValue = expandsSelectedPaths; 1166 1167 expandsSelectedPaths = newValue; 1168 firePropertyChange(EXPANDS_SELECTED_PATHS_PROPERTY, oldValue, 1169 newValue); 1170 } 1171 1172 /** 1173 * Returns the <code>expandsSelectedPaths</code> property. 1174 * @return true if selection changes result in the parent path being 1175 * expanded 1176 * @since 1.3 1177 * @see #setExpandsSelectedPaths 1178 */ 1179 public boolean getExpandsSelectedPaths() { 1180 return expandsSelectedPaths; 1181 } 1182 1183 /** 1184 * Turns on or off automatic drag handling. In order to enable automatic 1185 * drag handling, this property should be set to {@code true}, and the 1186 * tree's {@code TransferHandler} needs to be {@code non-null}. 1187 * The default value of the {@code dragEnabled} property is {@code false}. 1188 * <p> 1189 * The job of honoring this property, and recognizing a user drag gesture, 1190 * lies with the look and feel implementation, and in particular, the tree's 1191 * {@code TreeUI}. When automatic drag handling is enabled, most look and 1192 * feels (including those that subclass {@code BasicLookAndFeel}) begin a 1193 * drag and drop operation whenever the user presses the mouse button over 1194 * an item and then moves the mouse a few pixels. Setting this property to 1195 * {@code true} can therefore have a subtle effect on how selections behave. 1196 * <p> 1197 * If a look and feel is used that ignores this property, you can still 1198 * begin a drag and drop operation by calling {@code exportAsDrag} on the 1199 * tree's {@code TransferHandler}. 1200 * 1201 * @param b whether or not to enable automatic drag handling 1202 * @exception HeadlessException if 1203 * <code>b</code> is <code>true</code> and 1204 * <code>GraphicsEnvironment.isHeadless()</code> 1205 * returns <code>true</code> 1206 * @see java.awt.GraphicsEnvironment#isHeadless 1207 * @see #getDragEnabled 1208 * @see #setTransferHandler 1209 * @see TransferHandler 1210 * @since 1.4 1211 * 1212 * @beaninfo 1213 * description: determines whether automatic drag handling is enabled 1214 * bound: false 1215 */ 1216 public void setDragEnabled(boolean b) { 1217 if (b && GraphicsEnvironment.isHeadless()) { 1218 throw new HeadlessException(); 1219 } 1220 dragEnabled = b; 1221 } 1222 1223 /** 1224 * Returns whether or not automatic drag handling is enabled. 1225 * 1226 * @return the value of the {@code dragEnabled} property 1227 * @see #setDragEnabled 1228 * @since 1.4 1229 */ 1230 public boolean getDragEnabled() { 1231 return dragEnabled; 1232 } 1233 1234 /** 1235 * Sets the drop mode for this component. For backward compatibility, 1236 * the default for this property is <code>DropMode.USE_SELECTION</code>. 1237 * Usage of one of the other modes is recommended, however, for an 1238 * improved user experience. <code>DropMode.ON</code>, for instance, 1239 * offers similar behavior of showing items as selected, but does so without 1240 * affecting the actual selection in the tree. 1241 * <p> 1242 * <code>JTree</code> supports the following drop modes: 1243 * <ul> 1244 * <li><code>DropMode.USE_SELECTION</code></li> 1245 * <li><code>DropMode.ON</code></li> 1246 * <li><code>DropMode.INSERT</code></li> 1247 * <li><code>DropMode.ON_OR_INSERT</code></li> 1248 * </ul> 1249 * <p> 1250 * The drop mode is only meaningful if this component has a 1251 * <code>TransferHandler</code> that accepts drops. 1252 * 1253 * @param dropMode the drop mode to use 1254 * @throws IllegalArgumentException if the drop mode is unsupported 1255 * or <code>null</code> 1256 * @see #getDropMode 1257 * @see #getDropLocation 1258 * @see #setTransferHandler 1259 * @see TransferHandler 1260 * @since 1.6 1261 */ 1262 public final void setDropMode(DropMode dropMode) { 1263 if (dropMode != null) { 1264 switch (dropMode) { 1265 case USE_SELECTION: 1266 case ON: 1267 case INSERT: 1268 case ON_OR_INSERT: 1269 this.dropMode = dropMode; 1270 return; 1271 } 1272 } 1273 1274 throw new IllegalArgumentException(dropMode + ": Unsupported drop mode for tree"); 1275 } 1276 1277 /** 1278 * Returns the drop mode for this component. 1279 * 1280 * @return the drop mode for this component 1281 * @see #setDropMode 1282 * @since 1.6 1283 */ 1284 public final DropMode getDropMode() { 1285 return dropMode; 1286 } 1287 1288 /** 1289 * Calculates a drop location in this component, representing where a 1290 * drop at the given point should insert data. 1291 * 1292 * @param p the point to calculate a drop location for 1293 * @return the drop location, or <code>null</code> 1294 */ 1295 DropLocation dropLocationForPoint(Point p) { 1296 DropLocation location = null; 1297 1298 int row = getClosestRowForLocation(p.x, p.y); 1299 Rectangle bounds = getRowBounds(row); 1300 TreeModel model = getModel(); 1301 Object root = (model == null) ? null : model.getRoot(); 1302 TreePath rootPath = (root == null) ? null : new TreePath(root); 1303 1304 TreePath child; 1305 TreePath parent; 1306 boolean outside = row == -1 1307 || p.y < bounds.y 1308 || p.y >= bounds.y + bounds.height; 1309 1310 switch(dropMode) { 1311 case USE_SELECTION: 1312 case ON: 1313 if (outside) { 1314 location = new DropLocation(p, null, -1); 1315 } else { 1316 location = new DropLocation(p, getPathForRow(row), -1); 1317 } 1318 1319 break; 1320 case INSERT: 1321 case ON_OR_INSERT: 1322 if (row == -1) { 1323 if (root != null && !model.isLeaf(root) && isExpanded(rootPath)) { 1324 location = new DropLocation(p, rootPath, 0); 1325 } else { 1326 location = new DropLocation(p, null, -1); 1327 } 1328 1329 break; 1330 } 1331 1332 boolean checkOn = dropMode == DropMode.ON_OR_INSERT 1333 || !model.isLeaf(getPathForRow(row).getLastPathComponent()); 1334 1335 Section section = SwingUtilities2.liesInVertical(bounds, p, checkOn); 1336 if(section == LEADING) { 1337 child = getPathForRow(row); 1338 parent = child.getParentPath(); 1339 } else if (section == TRAILING) { 1340 int index = row + 1; 1341 if (index >= getRowCount()) { 1342 if (model.isLeaf(root) || !isExpanded(rootPath)) { 1343 location = new DropLocation(p, null, -1); 1344 } else { 1345 parent = rootPath; 1346 index = model.getChildCount(root); 1347 location = new DropLocation(p, parent, index); 1348 } 1349 1350 break; 1351 } 1352 1353 child = getPathForRow(index); 1354 parent = child.getParentPath(); 1355 } else { 1356 assert checkOn; 1357 location = new DropLocation(p, getPathForRow(row), -1); 1358 break; 1359 } 1360 1361 if (parent != null) { 1362 location = new DropLocation(p, parent, 1363 model.getIndexOfChild(parent.getLastPathComponent(), 1364 child.getLastPathComponent())); 1365 } else if (checkOn || !model.isLeaf(root)) { 1366 location = new DropLocation(p, rootPath, -1); 1367 } else { 1368 location = new DropLocation(p, null, -1); 1369 } 1370 1371 break; 1372 default: 1373 assert false : "Unexpected drop mode"; 1374 } 1375 1376 if (outside || row != expandRow) { 1377 cancelDropTimer(); 1378 } 1379 1380 if (!outside && row != expandRow) { 1381 if (isCollapsed(row)) { 1382 expandRow = row; 1383 startDropTimer(); 1384 } 1385 } 1386 1387 return location; 1388 } 1389 1390 /** 1391 * Called to set or clear the drop location during a DnD operation. 1392 * In some cases, the component may need to use it's internal selection 1393 * temporarily to indicate the drop location. To help facilitate this, 1394 * this method returns and accepts as a parameter a state object. 1395 * This state object can be used to store, and later restore, the selection 1396 * state. Whatever this method returns will be passed back to it in 1397 * future calls, as the state parameter. If it wants the DnD system to 1398 * continue storing the same state, it must pass it back every time. 1399 * Here's how this is used: 1400 * <p> 1401 * Let's say that on the first call to this method the component decides 1402 * to save some state (because it is about to use the selection to show 1403 * a drop index). It can return a state object to the caller encapsulating 1404 * any saved selection state. On a second call, let's say the drop location 1405 * is being changed to something else. The component doesn't need to 1406 * restore anything yet, so it simply passes back the same state object 1407 * to have the DnD system continue storing it. Finally, let's say this 1408 * method is messaged with <code>null</code>. This means DnD 1409 * is finished with this component for now, meaning it should restore 1410 * state. At this point, it can use the state parameter to restore 1411 * said state, and of course return <code>null</code> since there's 1412 * no longer anything to store. 1413 * 1414 * @param location the drop location (as calculated by 1415 * <code>dropLocationForPoint</code>) or <code>null</code> 1416 * if there's no longer a valid drop location 1417 * @param state the state object saved earlier for this component, 1418 * or <code>null</code> 1419 * @param forDrop whether or not the method is being called because an 1420 * actual drop occurred 1421 * @return any saved state for this component, or <code>null</code> if none 1422 */ 1423 Object setDropLocation(TransferHandler.DropLocation location, 1424 Object state, 1425 boolean forDrop) { 1426 1427 Object retVal = null; 1428 DropLocation treeLocation = (DropLocation)location; 1429 1430 if (dropMode == DropMode.USE_SELECTION) { 1431 if (treeLocation == null) { 1432 if (!forDrop && state != null) { 1433 setSelectionPaths(((TreePath[][])state)[0]); 1434 setAnchorSelectionPath(((TreePath[][])state)[1][0]); 1435 setLeadSelectionPath(((TreePath[][])state)[1][1]); 1436 } 1437 } else { 1438 if (dropLocation == null) { 1439 TreePath[] paths = getSelectionPaths(); 1440 if (paths == null) { 1441 paths = new TreePath[0]; 1442 } 1443 1444 retVal = new TreePath[][] {paths, 1445 {getAnchorSelectionPath(), getLeadSelectionPath()}}; 1446 } else { 1447 retVal = state; 1448 } 1449 1450 setSelectionPath(treeLocation.getPath()); 1451 } 1452 } 1453 1454 DropLocation old = dropLocation; 1455 dropLocation = treeLocation; 1456 firePropertyChange("dropLocation", old, dropLocation); 1457 1458 return retVal; 1459 } 1460 1461 /** 1462 * Called to indicate to this component that DnD is done. 1463 * Allows for us to cancel the expand timer. 1464 */ 1465 void dndDone() { 1466 cancelDropTimer(); 1467 dropTimer = null; 1468 } 1469 1470 /** 1471 * Returns the location that this component should visually indicate 1472 * as the drop location during a DnD operation over the component, 1473 * or {@code null} if no location is to currently be shown. 1474 * <p> 1475 * This method is not meant for querying the drop location 1476 * from a {@code TransferHandler}, as the drop location is only 1477 * set after the {@code TransferHandler}'s <code>canImport</code> 1478 * has returned and has allowed for the location to be shown. 1479 * <p> 1480 * When this property changes, a property change event with 1481 * name "dropLocation" is fired by the component. 1482 * 1483 * @return the drop location 1484 * @see #setDropMode 1485 * @see TransferHandler#canImport(TransferHandler.TransferSupport) 1486 * @since 1.6 1487 */ 1488 public final DropLocation getDropLocation() { 1489 return dropLocation; 1490 } 1491 1492 private void startDropTimer() { 1493 if (dropTimer == null) { 1494 dropTimer = new TreeTimer(); 1495 } 1496 dropTimer.start(); 1497 } 1498 1499 private void cancelDropTimer() { 1500 if (dropTimer != null && dropTimer.isRunning()) { 1501 expandRow = -1; 1502 dropTimer.stop(); 1503 } 1504 } 1505 1506 /** 1507 * Returns <code>isEditable</code>. This is invoked from the UI before 1508 * editing begins to insure that the given path can be edited. This 1509 * is provided as an entry point for subclassers to add filtered 1510 * editing without having to resort to creating a new editor. 1511 * 1512 * @return true if every parent node and the node itself is editable 1513 * @see #isEditable 1514 */ 1515 public boolean isPathEditable(TreePath path) { 1516 return isEditable(); 1517 } 1518 1519 /** 1520 * Overrides <code>JComponent</code>'s <code>getToolTipText</code> 1521 * method in order to allow 1522 * renderer's tips to be used if it has text set. 1523 * <p> 1524 * NOTE: For <code>JTree</code> to properly display tooltips of its 1525 * renderers, <code>JTree</code> must be a registered component with the 1526 * <code>ToolTipManager</code>. This can be done by invoking 1527 * <code>ToolTipManager.sharedInstance().registerComponent(tree)</code>. 1528 * This is not done automatically! 1529 * 1530 * @param event the <code>MouseEvent</code> that initiated the 1531 * <code>ToolTip</code> display 1532 * @return a string containing the tooltip or <code>null</code> 1533 * if <code>event</code> is null 1534 */ 1535 public String getToolTipText(MouseEvent event) { 1536 String tip = null; 1537 1538 if(event != null) { 1539 Point p = event.getPoint(); 1540 int selRow = getRowForLocation(p.x, p.y); 1541 TreeCellRenderer r = getCellRenderer(); 1542 1543 if(selRow != -1 && r != null) { 1544 TreePath path = getPathForRow(selRow); 1545 Object lastPath = path.getLastPathComponent(); 1546 Component rComponent = r.getTreeCellRendererComponent 1547 (this, lastPath, isRowSelected(selRow), 1548 isExpanded(selRow), getModel().isLeaf(lastPath), selRow, 1549 true); 1550 1551 if(rComponent instanceof JComponent) { 1552 MouseEvent newEvent; 1553 Rectangle pathBounds = getPathBounds(path); 1554 1555 p.translate(-pathBounds.x, -pathBounds.y); 1556 newEvent = new MouseEvent(rComponent, event.getID(), 1557 event.getWhen(), 1558 event.getModifiers(), 1559 p.x, p.y, 1560 event.getXOnScreen(), 1561 event.getYOnScreen(), 1562 event.getClickCount(), 1563 event.isPopupTrigger(), 1564 MouseEvent.NOBUTTON); 1565 1566 tip = ((JComponent)rComponent).getToolTipText(newEvent); 1567 } 1568 } 1569 } 1570 // No tip from the renderer get our own tip 1571 if (tip == null) { 1572 tip = getToolTipText(); 1573 } 1574 return tip; 1575 } 1576 1577 /** 1578 * Called by the renderers to convert the specified value to 1579 * text. This implementation returns <code>value.toString</code>, ignoring 1580 * all other arguments. To control the conversion, subclass this 1581 * method and use any of the arguments you need. 1582 * 1583 * @param value the <code>Object</code> to convert to text 1584 * @param selected true if the node is selected 1585 * @param expanded true if the node is expanded 1586 * @param leaf true if the node is a leaf node 1587 * @param row an integer specifying the node's display row, where 0 is 1588 * the first row in the display 1589 * @param hasFocus true if the node has the focus 1590 * @return the <code>String</code> representation of the node's value 1591 */ 1592 public String convertValueToText(Object value, boolean selected, 1593 boolean expanded, boolean leaf, int row, 1594 boolean hasFocus) { 1595 if(value != null) { 1596 String sValue = value.toString(); 1597 if (sValue != null) { 1598 return sValue; 1599 } 1600 } 1601 return ""; 1602 } 1603 1604 // 1605 // The following are convenience methods that get forwarded to the 1606 // current TreeUI. 1607 // 1608 1609 /** 1610 * Returns the number of viewable nodes. A node is viewable if all of its 1611 * parents are expanded. The root is only included in this count if 1612 * {@code isRootVisible()} is {@code true}. This returns {@code 0} if 1613 * the UI has not been set. 1614 * 1615 * @return the number of viewable nodes 1616 */ 1617 public int getRowCount() { 1618 TreeUI tree = getUI(); 1619 1620 if(tree != null) 1621 return tree.getRowCount(this); 1622 return 0; 1623 } 1624 1625 /** 1626 * Selects the node identified by the specified path. If any 1627 * component of the path is hidden (under a collapsed node), and 1628 * <code>getExpandsSelectedPaths</code> is true it is 1629 * exposed (made viewable). 1630 * 1631 * @param path the <code>TreePath</code> specifying the node to select 1632 */ 1633 public void setSelectionPath(TreePath path) { 1634 getSelectionModel().setSelectionPath(path); 1635 } 1636 1637 /** 1638 * Selects the nodes identified by the specified array of paths. 1639 * If any component in any of the paths is hidden (under a collapsed 1640 * node), and <code>getExpandsSelectedPaths</code> is true 1641 * it is exposed (made viewable). 1642 * 1643 * @param paths an array of <code>TreePath</code> objects that specifies 1644 * the nodes to select 1645 */ 1646 public void setSelectionPaths(TreePath[] paths) { 1647 getSelectionModel().setSelectionPaths(paths); 1648 } 1649 1650 /** 1651 * Sets the path identifies as the lead. The lead may not be selected. 1652 * The lead is not maintained by <code>JTree</code>, 1653 * rather the UI will update it. 1654 * <p> 1655 * This is a bound property. 1656 * 1657 * @param newPath the new lead path 1658 * @since 1.3 1659 * @beaninfo 1660 * bound: true 1661 * description: Lead selection path 1662 */ 1663 public void setLeadSelectionPath(TreePath newPath) { 1664 TreePath oldValue = leadPath; 1665 1666 leadPath = newPath; 1667 firePropertyChange(LEAD_SELECTION_PATH_PROPERTY, oldValue, newPath); 1668 1669 if (accessibleContext != null){ 1670 ((AccessibleJTree)accessibleContext). 1671 fireActiveDescendantPropertyChange(oldValue, newPath); 1672 } 1673 } 1674 1675 /** 1676 * Sets the path identified as the anchor. 1677 * The anchor is not maintained by <code>JTree</code>, rather the UI will 1678 * update it. 1679 * <p> 1680 * This is a bound property. 1681 * 1682 * @param newPath the new anchor path 1683 * @since 1.3 1684 * @beaninfo 1685 * bound: true 1686 * description: Anchor selection path 1687 */ 1688 public void setAnchorSelectionPath(TreePath newPath) { 1689 TreePath oldValue = anchorPath; 1690 1691 anchorPath = newPath; 1692 firePropertyChange(ANCHOR_SELECTION_PATH_PROPERTY, oldValue, newPath); 1693 } 1694 1695 /** 1696 * Selects the node at the specified row in the display. 1697 * 1698 * @param row the row to select, where 0 is the first row in 1699 * the display 1700 */ 1701 public void setSelectionRow(int row) { 1702 int[] rows = { row }; 1703 1704 setSelectionRows(rows); 1705 } 1706 1707 /** 1708 * Selects the nodes corresponding to each of the specified rows 1709 * in the display. If a particular element of <code>rows</code> is 1710 * < 0 or >= <code>getRowCount</code>, it will be ignored. 1711 * If none of the elements 1712 * in <code>rows</code> are valid rows, the selection will 1713 * be cleared. That is it will be as if <code>clearSelection</code> 1714 * was invoked. 1715 * 1716 * @param rows an array of ints specifying the rows to select, 1717 * where 0 indicates the first row in the display 1718 */ 1719 public void setSelectionRows(int[] rows) { 1720 TreeUI ui = getUI(); 1721 1722 if(ui != null && rows != null) { 1723 int numRows = rows.length; 1724 TreePath[] paths = new TreePath[numRows]; 1725 1726 for(int counter = 0; counter < numRows; counter++) { 1727 paths[counter] = ui.getPathForRow(this, rows[counter]); 1728 } 1729 setSelectionPaths(paths); 1730 } 1731 } 1732 1733 /** 1734 * Adds the node identified by the specified <code>TreePath</code> 1735 * to the current selection. If any component of the path isn't 1736 * viewable, and <code>getExpandsSelectedPaths</code> is true it is 1737 * made viewable. 1738 * <p> 1739 * Note that <code>JTree</code> does not allow duplicate nodes to 1740 * exist as children under the same parent -- each sibling must be 1741 * a unique object. 1742 * 1743 * @param path the <code>TreePath</code> to add 1744 */ 1745 public void addSelectionPath(TreePath path) { 1746 getSelectionModel().addSelectionPath(path); 1747 } 1748 1749 /** 1750 * Adds each path in the array of paths to the current selection. If 1751 * any component of any of the paths isn't viewable and 1752 * <code>getExpandsSelectedPaths</code> is true, it is 1753 * made viewable. 1754 * <p> 1755 * Note that <code>JTree</code> does not allow duplicate nodes to 1756 * exist as children under the same parent -- each sibling must be 1757 * a unique object. 1758 * 1759 * @param paths an array of <code>TreePath</code> objects that specifies 1760 * the nodes to add 1761 */ 1762 public void addSelectionPaths(TreePath[] paths) { 1763 getSelectionModel().addSelectionPaths(paths); 1764 } 1765 1766 /** 1767 * Adds the path at the specified row to the current selection. 1768 * 1769 * @param row an integer specifying the row of the node to add, 1770 * where 0 is the first row in the display 1771 */ 1772 public void addSelectionRow(int row) { 1773 int[] rows = { row }; 1774 1775 addSelectionRows(rows); 1776 } 1777 1778 /** 1779 * Adds the paths at each of the specified rows to the current selection. 1780 * 1781 * @param rows an array of ints specifying the rows to add, 1782 * where 0 indicates the first row in the display 1783 */ 1784 public void addSelectionRows(int[] rows) { 1785 TreeUI ui = getUI(); 1786 1787 if(ui != null && rows != null) { 1788 int numRows = rows.length; 1789 TreePath[] paths = new TreePath[numRows]; 1790 1791 for(int counter = 0; counter < numRows; counter++) 1792 paths[counter] = ui.getPathForRow(this, rows[counter]); 1793 addSelectionPaths(paths); 1794 } 1795 } 1796 1797 /** 1798 * Returns the last path component of the selected path. This is 1799 * a convenience method for 1800 * {@code getSelectionModel().getSelectionPath().getLastPathComponent()}. 1801 * This is typically only useful if the selection has one path. 1802 * 1803 * @return the last path component of the selected path, or 1804 * <code>null</code> if nothing is selected 1805 * @see TreePath#getLastPathComponent 1806 */ 1807 public Object getLastSelectedPathComponent() { 1808 TreePath selPath = getSelectionModel().getSelectionPath(); 1809 1810 if(selPath != null) 1811 return selPath.getLastPathComponent(); 1812 return null; 1813 } 1814 1815 /** 1816 * Returns the path identified as the lead. 1817 * @return path identified as the lead 1818 */ 1819 public TreePath getLeadSelectionPath() { 1820 return leadPath; 1821 } 1822 1823 /** 1824 * Returns the path identified as the anchor. 1825 * @return path identified as the anchor 1826 * @since 1.3 1827 */ 1828 public TreePath getAnchorSelectionPath() { 1829 return anchorPath; 1830 } 1831 1832 /** 1833 * Returns the path to the first selected node. 1834 * 1835 * @return the <code>TreePath</code> for the first selected node, 1836 * or <code>null</code> if nothing is currently selected 1837 */ 1838 public TreePath getSelectionPath() { 1839 return getSelectionModel().getSelectionPath(); 1840 } 1841 1842 /** 1843 * Returns the paths of all selected values. 1844 * 1845 * @return an array of <code>TreePath</code> objects indicating the selected 1846 * nodes, or <code>null</code> if nothing is currently selected 1847 */ 1848 public TreePath[] getSelectionPaths() { 1849 TreePath[] selectionPaths = getSelectionModel().getSelectionPaths(); 1850 1851 return (selectionPaths != null && selectionPaths.length > 0) ? selectionPaths : null; 1852 } 1853 1854 /** 1855 * Returns all of the currently selected rows. This method is simply 1856 * forwarded to the <code>TreeSelectionModel</code>. 1857 * If nothing is selected <code>null</code> or an empty array will 1858 * be returned, based on the <code>TreeSelectionModel</code> 1859 * implementation. 1860 * 1861 * @return an array of integers that identifies all currently selected rows 1862 * where 0 is the first row in the display 1863 */ 1864 public int[] getSelectionRows() { 1865 return getSelectionModel().getSelectionRows(); 1866 } 1867 1868 /** 1869 * Returns the number of nodes selected. 1870 * 1871 * @return the number of nodes selected 1872 */ 1873 public int getSelectionCount() { 1874 return selectionModel.getSelectionCount(); 1875 } 1876 1877 /** 1878 * Returns the smallest selected row. If the selection is empty, or 1879 * none of the selected paths are viewable, {@code -1} is returned. 1880 * 1881 * @return the smallest selected row 1882 */ 1883 public int getMinSelectionRow() { 1884 return getSelectionModel().getMinSelectionRow(); 1885 } 1886 1887 /** 1888 * Returns the largest selected row. If the selection is empty, or 1889 * none of the selected paths are viewable, {@code -1} is returned. 1890 * 1891 * @return the largest selected row 1892 */ 1893 public int getMaxSelectionRow() { 1894 return getSelectionModel().getMaxSelectionRow(); 1895 } 1896 1897 /** 1898 * Returns the row index corresponding to the lead path. 1899 * 1900 * @return an integer giving the row index of the lead path, 1901 * where 0 is the first row in the display; or -1 1902 * if <code>leadPath</code> is <code>null</code> 1903 */ 1904 public int getLeadSelectionRow() { 1905 TreePath leadPath = getLeadSelectionPath(); 1906 1907 if (leadPath != null) { 1908 return getRowForPath(leadPath); 1909 } 1910 return -1; 1911 } 1912 1913 /** 1914 * Returns true if the item identified by the path is currently selected. 1915 * 1916 * @param path a <code>TreePath</code> identifying a node 1917 * @return true if the node is selected 1918 */ 1919 public boolean isPathSelected(TreePath path) { 1920 return getSelectionModel().isPathSelected(path); 1921 } 1922 1923 /** 1924 * Returns true if the node identified by row is selected. 1925 * 1926 * @param row an integer specifying a display row, where 0 is the first 1927 * row in the display 1928 * @return true if the node is selected 1929 */ 1930 public boolean isRowSelected(int row) { 1931 return getSelectionModel().isRowSelected(row); 1932 } 1933 1934 /** 1935 * Returns an <code>Enumeration</code> of the descendants of the 1936 * path <code>parent</code> that 1937 * are currently expanded. If <code>parent</code> is not currently 1938 * expanded, this will return <code>null</code>. 1939 * If you expand/collapse nodes while 1940 * iterating over the returned <code>Enumeration</code> 1941 * this may not return all 1942 * the expanded paths, or may return paths that are no longer expanded. 1943 * 1944 * @param parent the path which is to be examined 1945 * @return an <code>Enumeration</code> of the descendents of 1946 * <code>parent</code>, or <code>null</code> if 1947 * <code>parent</code> is not currently expanded 1948 */ 1949 public Enumeration<TreePath> getExpandedDescendants(TreePath parent) { 1950 if(!isExpanded(parent)) 1951 return null; 1952 1953 Enumeration<TreePath> toggledPaths = expandedState.keys(); 1954 Vector<TreePath> elements = null; 1955 TreePath path; 1956 Object value; 1957 1958 if(toggledPaths != null) { 1959 while(toggledPaths.hasMoreElements()) { 1960 path = toggledPaths.nextElement(); 1961 value = expandedState.get(path); 1962 // Add the path if it is expanded, a descendant of parent, 1963 // and it is visible (all parents expanded). This is rather 1964 // expensive! 1965 if(path != parent && value != null && 1966 ((Boolean)value).booleanValue() && 1967 parent.isDescendant(path) && isVisible(path)) { 1968 if (elements == null) { 1969 elements = new Vector<TreePath>(); 1970 } 1971 elements.addElement(path); 1972 } 1973 } 1974 } 1975 if (elements == null) { 1976 Set<TreePath> empty = Collections.emptySet(); 1977 return Collections.enumeration(empty); 1978 } 1979 return elements.elements(); 1980 } 1981 1982 /** 1983 * Returns true if the node identified by the path has ever been 1984 * expanded. 1985 * @return true if the <code>path</code> has ever been expanded 1986 */ 1987 public boolean hasBeenExpanded(TreePath path) { 1988 return (path != null && expandedState.get(path) != null); 1989 } 1990 1991 /** 1992 * Returns true if the node identified by the path is currently expanded, 1993 * 1994 * @param path the <code>TreePath</code> specifying the node to check 1995 * @return false if any of the nodes in the node's path are collapsed, 1996 * true if all nodes in the path are expanded 1997 */ 1998 public boolean isExpanded(TreePath path) { 1999 2000 if(path == null) 2001 return false; 2002 Object value; 2003 2004 do{ 2005 value = expandedState.get(path); 2006 if(value == null || !((Boolean)value).booleanValue()) 2007 return false; 2008 } while( (path=path.getParentPath())!=null ); 2009 2010 return true; 2011 } 2012 2013 /** 2014 * Returns true if the node at the specified display row is currently 2015 * expanded. 2016 * 2017 * @param row the row to check, where 0 is the first row in the 2018 * display 2019 * @return true if the node is currently expanded, otherwise false 2020 */ 2021 public boolean isExpanded(int row) { 2022 TreeUI tree = getUI(); 2023 2024 if(tree != null) { 2025 TreePath path = tree.getPathForRow(this, row); 2026 2027 if(path != null) { 2028 Boolean value = expandedState.get(path); 2029 2030 return (value != null && value.booleanValue()); 2031 } 2032 } 2033 return false; 2034 } 2035 2036 /** 2037 * Returns true if the value identified by path is currently collapsed, 2038 * this will return false if any of the values in path are currently 2039 * not being displayed. 2040 * 2041 * @param path the <code>TreePath</code> to check 2042 * @return true if any of the nodes in the node's path are collapsed, 2043 * false if all nodes in the path are expanded 2044 */ 2045 public boolean isCollapsed(TreePath path) { 2046 return !isExpanded(path); 2047 } 2048 2049 /** 2050 * Returns true if the node at the specified display row is collapsed. 2051 * 2052 * @param row the row to check, where 0 is the first row in the 2053 * display 2054 * @return true if the node is currently collapsed, otherwise false 2055 */ 2056 public boolean isCollapsed(int row) { 2057 return !isExpanded(row); 2058 } 2059 2060 /** 2061 * Ensures that the node identified by path is currently viewable. 2062 * 2063 * @param path the <code>TreePath</code> to make visible 2064 */ 2065 public void makeVisible(TreePath path) { 2066 if(path != null) { 2067 TreePath parentPath = path.getParentPath(); 2068 2069 if(parentPath != null) { 2070 expandPath(parentPath); 2071 } 2072 } 2073 } 2074 2075 /** 2076 * Returns true if the value identified by path is currently viewable, 2077 * which means it is either the root or all of its parents are expanded. 2078 * Otherwise, this method returns false. 2079 * 2080 * @return true if the node is viewable, otherwise false 2081 */ 2082 public boolean isVisible(TreePath path) { 2083 if(path != null) { 2084 TreePath parentPath = path.getParentPath(); 2085 2086 if(parentPath != null) 2087 return isExpanded(parentPath); 2088 // Root. 2089 return true; 2090 } 2091 return false; 2092 } 2093 2094 /** 2095 * Returns the <code>Rectangle</code> that the specified node will be drawn 2096 * into. Returns <code>null</code> if any component in the path is hidden 2097 * (under a collapsed parent). 2098 * <p> 2099 * Note:<br> 2100 * This method returns a valid rectangle, even if the specified 2101 * node is not currently displayed. 2102 * 2103 * @param path the <code>TreePath</code> identifying the node 2104 * @return the <code>Rectangle</code> the node is drawn in, 2105 * or <code>null</code> 2106 */ 2107 public Rectangle getPathBounds(TreePath path) { 2108 TreeUI tree = getUI(); 2109 2110 if(tree != null) 2111 return tree.getPathBounds(this, path); 2112 return null; 2113 } 2114 2115 /** 2116 * Returns the <code>Rectangle</code> that the node at the specified row is 2117 * drawn in. 2118 * 2119 * @param row the row to be drawn, where 0 is the first row in the 2120 * display 2121 * @return the <code>Rectangle</code> the node is drawn in 2122 */ 2123 public Rectangle getRowBounds(int row) { 2124 return getPathBounds(getPathForRow(row)); 2125 } 2126 2127 /** 2128 * Makes sure all the path components in path are expanded (except 2129 * for the last path component) and scrolls so that the 2130 * node identified by the path is displayed. Only works when this 2131 * <code>JTree</code> is contained in a <code>JScrollPane</code>. 2132 * 2133 * @param path the <code>TreePath</code> identifying the node to 2134 * bring into view 2135 */ 2136 public void scrollPathToVisible(TreePath path) { 2137 if(path != null) { 2138 makeVisible(path); 2139 2140 Rectangle bounds = getPathBounds(path); 2141 2142 if(bounds != null) { 2143 scrollRectToVisible(bounds); 2144 if (accessibleContext != null) { 2145 ((AccessibleJTree)accessibleContext).fireVisibleDataPropertyChange(); 2146 } 2147 } 2148 } 2149 } 2150 2151 /** 2152 * Scrolls the item identified by row until it is displayed. The minimum 2153 * of amount of scrolling necessary to bring the row into view 2154 * is performed. Only works when this <code>JTree</code> is contained in a 2155 * <code>JScrollPane</code>. 2156 * 2157 * @param row an integer specifying the row to scroll, where 0 is the 2158 * first row in the display 2159 */ 2160 public void scrollRowToVisible(int row) { 2161 scrollPathToVisible(getPathForRow(row)); 2162 } 2163 2164 /** 2165 * Returns the path for the specified row. If <code>row</code> is 2166 * not visible, or a {@code TreeUI} has not been set, <code>null</code> 2167 * is returned. 2168 * 2169 * @param row an integer specifying a row 2170 * @return the <code>TreePath</code> to the specified node, 2171 * <code>null</code> if <code>row < 0</code> 2172 * or <code>row >= getRowCount()</code> 2173 */ 2174 public TreePath getPathForRow(int row) { 2175 TreeUI tree = getUI(); 2176 2177 if(tree != null) 2178 return tree.getPathForRow(this, row); 2179 return null; 2180 } 2181 2182 /** 2183 * Returns the row that displays the node identified by the specified 2184 * path. 2185 * 2186 * @param path the <code>TreePath</code> identifying a node 2187 * @return an integer specifying the display row, where 0 is the first 2188 * row in the display, or -1 if any of the elements in path 2189 * are hidden under a collapsed parent. 2190 */ 2191 public int getRowForPath(TreePath path) { 2192 TreeUI tree = getUI(); 2193 2194 if(tree != null) 2195 return tree.getRowForPath(this, path); 2196 return -1; 2197 } 2198 2199 /** 2200 * Ensures that the node identified by the specified path is 2201 * expanded and viewable. If the last item in the path is a 2202 * leaf, this will have no effect. 2203 * 2204 * @param path the <code>TreePath</code> identifying a node 2205 */ 2206 public void expandPath(TreePath path) { 2207 // Only expand if not leaf! 2208 TreeModel model = getModel(); 2209 2210 if(path != null && model != null && 2211 !model.isLeaf(path.getLastPathComponent())) { 2212 setExpandedState(path, true); 2213 } 2214 } 2215 2216 /** 2217 * Ensures that the node in the specified row is expanded and 2218 * viewable. 2219 * <p> 2220 * If <code>row</code> is < 0 or >= <code>getRowCount</code> this 2221 * will have no effect. 2222 * 2223 * @param row an integer specifying a display row, where 0 is the 2224 * first row in the display 2225 */ 2226 public void expandRow(int row) { 2227 expandPath(getPathForRow(row)); 2228 } 2229 2230 /** 2231 * Ensures that the node identified by the specified path is 2232 * collapsed and viewable. 2233 * 2234 * @param path the <code>TreePath</code> identifying a node 2235 */ 2236 public void collapsePath(TreePath path) { 2237 setExpandedState(path, false); 2238 } 2239 2240 /** 2241 * Ensures that the node in the specified row is collapsed. 2242 * <p> 2243 * If <code>row</code> is < 0 or >= <code>getRowCount</code> this 2244 * will have no effect. 2245 * 2246 * @param row an integer specifying a display row, where 0 is the 2247 * first row in the display 2248 */ 2249 public void collapseRow(int row) { 2250 collapsePath(getPathForRow(row)); 2251 } 2252 2253 /** 2254 * Returns the path for the node at the specified location. 2255 * 2256 * @param x an integer giving the number of pixels horizontally from 2257 * the left edge of the display area, minus any left margin 2258 * @param y an integer giving the number of pixels vertically from 2259 * the top of the display area, minus any top margin 2260 * @return the <code>TreePath</code> for the node at that location 2261 */ 2262 public TreePath getPathForLocation(int x, int y) { 2263 TreePath closestPath = getClosestPathForLocation(x, y); 2264 2265 if(closestPath != null) { 2266 Rectangle pathBounds = getPathBounds(closestPath); 2267 2268 if(pathBounds != null && 2269 x >= pathBounds.x && x < (pathBounds.x + pathBounds.width) && 2270 y >= pathBounds.y && y < (pathBounds.y + pathBounds.height)) 2271 return closestPath; 2272 } 2273 return null; 2274 } 2275 2276 /** 2277 * Returns the row for the specified location. 2278 * 2279 * @param x an integer giving the number of pixels horizontally from 2280 * the left edge of the display area, minus any left margin 2281 * @param y an integer giving the number of pixels vertically from 2282 * the top of the display area, minus any top margin 2283 * @return the row corresponding to the location, or -1 if the 2284 * location is not within the bounds of a displayed cell 2285 * @see #getClosestRowForLocation 2286 */ 2287 public int getRowForLocation(int x, int y) { 2288 return getRowForPath(getPathForLocation(x, y)); 2289 } 2290 2291 /** 2292 * Returns the path to the node that is closest to x,y. If 2293 * no nodes are currently viewable, or there is no model, returns 2294 * <code>null</code>, otherwise it always returns a valid path. To test if 2295 * the node is exactly at x, y, get the node's bounds and 2296 * test x, y against that. 2297 * 2298 * @param x an integer giving the number of pixels horizontally from 2299 * the left edge of the display area, minus any left margin 2300 * @param y an integer giving the number of pixels vertically from 2301 * the top of the display area, minus any top margin 2302 * @return the <code>TreePath</code> for the node closest to that location, 2303 * <code>null</code> if nothing is viewable or there is no model 2304 * 2305 * @see #getPathForLocation 2306 * @see #getPathBounds 2307 */ 2308 public TreePath getClosestPathForLocation(int x, int y) { 2309 TreeUI tree = getUI(); 2310 2311 if(tree != null) 2312 return tree.getClosestPathForLocation(this, x, y); 2313 return null; 2314 } 2315 2316 /** 2317 * Returns the row to the node that is closest to x,y. If no nodes 2318 * are viewable or there is no model, returns -1. Otherwise, 2319 * it always returns a valid row. To test if the returned object is 2320 * exactly at x, y, get the bounds for the node at the returned 2321 * row and test x, y against that. 2322 * 2323 * @param x an integer giving the number of pixels horizontally from 2324 * the left edge of the display area, minus any left margin 2325 * @param y an integer giving the number of pixels vertically from 2326 * the top of the display area, minus any top margin 2327 * @return the row closest to the location, -1 if nothing is 2328 * viewable or there is no model 2329 * 2330 * @see #getRowForLocation 2331 * @see #getRowBounds 2332 */ 2333 public int getClosestRowForLocation(int x, int y) { 2334 return getRowForPath(getClosestPathForLocation(x, y)); 2335 } 2336 2337 /** 2338 * Returns true if the tree is being edited. The item that is being 2339 * edited can be obtained using <code>getSelectionPath</code>. 2340 * 2341 * @return true if the user is currently editing a node 2342 * @see #getSelectionPath 2343 */ 2344 public boolean isEditing() { 2345 TreeUI tree = getUI(); 2346 2347 if(tree != null) 2348 return tree.isEditing(this); 2349 return false; 2350 } 2351 2352 /** 2353 * Ends the current editing session. 2354 * (The <code>DefaultTreeCellEditor</code> 2355 * object saves any edits that are currently in progress on a cell. 2356 * Other implementations may operate differently.) 2357 * Has no effect if the tree isn't being edited. 2358 * <blockquote> 2359 * <b>Note:</b><br> 2360 * To make edit-saves automatic whenever the user changes 2361 * their position in the tree, use {@link #setInvokesStopCellEditing}. 2362 * </blockquote> 2363 * 2364 * @return true if editing was in progress and is now stopped, 2365 * false if editing was not in progress 2366 */ 2367 public boolean stopEditing() { 2368 TreeUI tree = getUI(); 2369 2370 if(tree != null) 2371 return tree.stopEditing(this); 2372 return false; 2373 } 2374 2375 /** 2376 * Cancels the current editing session. Has no effect if the 2377 * tree isn't being edited. 2378 */ 2379 public void cancelEditing() { 2380 TreeUI tree = getUI(); 2381 2382 if(tree != null) 2383 tree.cancelEditing(this); 2384 } 2385 2386 /** 2387 * Selects the node identified by the specified path and initiates 2388 * editing. The edit-attempt fails if the <code>CellEditor</code> 2389 * does not allow 2390 * editing for the specified item. 2391 * 2392 * @param path the <code>TreePath</code> identifying a node 2393 */ 2394 public void startEditingAtPath(TreePath path) { 2395 TreeUI tree = getUI(); 2396 2397 if(tree != null) 2398 tree.startEditingAtPath(this, path); 2399 } 2400 2401 /** 2402 * Returns the path to the element that is currently being edited. 2403 * 2404 * @return the <code>TreePath</code> for the node being edited 2405 */ 2406 public TreePath getEditingPath() { 2407 TreeUI tree = getUI(); 2408 2409 if(tree != null) 2410 return tree.getEditingPath(this); 2411 return null; 2412 } 2413 2414 // 2415 // Following are primarily convenience methods for mapping from 2416 // row based selections to path selections. Sometimes it is 2417 // easier to deal with these than paths (mouse downs, key downs 2418 // usually just deal with index based selections). 2419 // Since row based selections require a UI many of these won't work 2420 // without one. 2421 // 2422 2423 /** 2424 * Sets the tree's selection model. When a <code>null</code> value is 2425 * specified an empty 2426 * <code>selectionModel</code> is used, which does not allow selections. 2427 * <p> 2428 * This is a bound property. 2429 * 2430 * @param selectionModel the <code>TreeSelectionModel</code> to use, 2431 * or <code>null</code> to disable selections 2432 * @see TreeSelectionModel 2433 * @beaninfo 2434 * bound: true 2435 * description: The tree's selection model. 2436 */ 2437 public void setSelectionModel(TreeSelectionModel selectionModel) { 2438 if(selectionModel == null) 2439 selectionModel = EmptySelectionModel.sharedInstance(); 2440 2441 TreeSelectionModel oldValue = this.selectionModel; 2442 2443 if (this.selectionModel != null && selectionRedirector != null) { 2444 this.selectionModel.removeTreeSelectionListener 2445 (selectionRedirector); 2446 } 2447 if (accessibleContext != null) { 2448 this.selectionModel.removeTreeSelectionListener((TreeSelectionListener)accessibleContext); 2449 selectionModel.addTreeSelectionListener((TreeSelectionListener)accessibleContext); 2450 } 2451 2452 this.selectionModel = selectionModel; 2453 if (selectionRedirector != null) { 2454 this.selectionModel.addTreeSelectionListener(selectionRedirector); 2455 } 2456 firePropertyChange(SELECTION_MODEL_PROPERTY, oldValue, 2457 this.selectionModel); 2458 2459 if (accessibleContext != null) { 2460 accessibleContext.firePropertyChange( 2461 AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY, 2462 Boolean.valueOf(false), Boolean.valueOf(true)); 2463 } 2464 } 2465 2466 /** 2467 * Returns the model for selections. This should always return a 2468 * non-<code>null</code> value. If you don't want to allow anything 2469 * to be selected 2470 * set the selection model to <code>null</code>, which forces an empty 2471 * selection model to be used. 2472 * 2473 * @see #setSelectionModel 2474 */ 2475 public TreeSelectionModel getSelectionModel() { 2476 return selectionModel; 2477 } 2478 2479 /** 2480 * Returns the paths (inclusive) between the specified rows. If 2481 * the specified indices are within the viewable set of rows, or 2482 * bound the viewable set of rows, then the indices are 2483 * constrained by the viewable set of rows. If the specified 2484 * indices are not within the viewable set of rows, or do not 2485 * bound the viewable set of rows, then an empty array is 2486 * returned. For example, if the row count is {@code 10}, and this 2487 * method is invoked with {@code -1, 20}, then the specified 2488 * indices are constrained to the viewable set of rows, and this is 2489 * treated as if invoked with {@code 0, 9}. On the other hand, if 2490 * this were invoked with {@code -10, -1}, then the specified 2491 * indices do not bound the viewable set of rows, and an empty 2492 * array is returned. 2493 * <p> 2494 * The parameters are not order dependent. That is, {@code 2495 * getPathBetweenRows(x, y)} is equivalent to 2496 * {@code getPathBetweenRows(y, x)}. 2497 * <p> 2498 * An empty array is returned if the row count is {@code 0}, or 2499 * the specified indices do not bound the viewable set of rows. 2500 * 2501 * @param index0 the first index in the range 2502 * @param index1 the last index in the range 2503 * @return the paths (inclusive) between the specified row indices 2504 */ 2505 protected TreePath[] getPathBetweenRows(int index0, int index1) { 2506 TreeUI tree = getUI(); 2507 if (tree != null) { 2508 int rowCount = getRowCount(); 2509 if (rowCount > 0 && !((index0 < 0 && index1 < 0) || 2510 (index0 >= rowCount && index1 >= rowCount))){ 2511 index0 = Math.min(rowCount - 1, Math.max(index0, 0)); 2512 index1 = Math.min(rowCount - 1, Math.max(index1, 0)); 2513 int minIndex = Math.min(index0, index1); 2514 int maxIndex = Math.max(index0, index1); 2515 TreePath[] selection = new TreePath[ 2516 maxIndex - minIndex + 1]; 2517 for(int counter = minIndex; counter <= maxIndex; counter++) { 2518 selection[counter - minIndex] = 2519 tree.getPathForRow(this, counter); 2520 } 2521 return selection; 2522 } 2523 } 2524 return new TreePath[0]; 2525 } 2526 2527 /** 2528 * Selects the rows in the specified interval (inclusive). If 2529 * the specified indices are within the viewable set of rows, or bound 2530 * the viewable set of rows, then the specified rows are constrained by 2531 * the viewable set of rows. If the specified indices are not within the 2532 * viewable set of rows, or do not bound the viewable set of rows, then 2533 * the selection is cleared. For example, if the row count is {@code 2534 * 10}, and this method is invoked with {@code -1, 20}, then the 2535 * specified indices bounds the viewable range, and this is treated as 2536 * if invoked with {@code 0, 9}. On the other hand, if this were 2537 * invoked with {@code -10, -1}, then the specified indices do not 2538 * bound the viewable set of rows, and the selection is cleared. 2539 * <p> 2540 * The parameters are not order dependent. That is, {@code 2541 * setSelectionInterval(x, y)} is equivalent to 2542 * {@code setSelectionInterval(y, x)}. 2543 * 2544 * @param index0 the first index in the range to select 2545 * @param index1 the last index in the range to select 2546 */ 2547 public void setSelectionInterval(int index0, int index1) { 2548 TreePath[] paths = getPathBetweenRows(index0, index1); 2549 2550 this.getSelectionModel().setSelectionPaths(paths); 2551 } 2552 2553 /** 2554 * Adds the specified rows (inclusive) to the selection. If the 2555 * specified indices are within the viewable set of rows, or bound 2556 * the viewable set of rows, then the specified indices are 2557 * constrained by the viewable set of rows. If the indices are not 2558 * within the viewable set of rows, or do not bound the viewable 2559 * set of rows, then the selection is unchanged. For example, if 2560 * the row count is {@code 10}, and this method is invoked with 2561 * {@code -1, 20}, then the specified indices bounds the viewable 2562 * range, and this is treated as if invoked with {@code 0, 9}. On 2563 * the other hand, if this were invoked with {@code -10, -1}, then 2564 * the specified indices do not bound the viewable set of rows, 2565 * and the selection is unchanged. 2566 * <p> 2567 * The parameters are not order dependent. That is, {@code 2568 * addSelectionInterval(x, y)} is equivalent to 2569 * {@code addSelectionInterval(y, x)}. 2570 * 2571 * @param index0 the first index in the range to add to the selection 2572 * @param index1 the last index in the range to add to the selection 2573 */ 2574 public void addSelectionInterval(int index0, int index1) { 2575 TreePath[] paths = getPathBetweenRows(index0, index1); 2576 2577 if (paths != null && paths.length > 0) { 2578 this.getSelectionModel().addSelectionPaths(paths); 2579 } 2580 } 2581 2582 /** 2583 * Removes the specified rows (inclusive) from the selection. If 2584 * the specified indices are within the viewable set of rows, or bound 2585 * the viewable set of rows, then the specified indices are constrained by 2586 * the viewable set of rows. If the specified indices are not within the 2587 * viewable set of rows, or do not bound the viewable set of rows, then 2588 * the selection is unchanged. For example, if the row count is {@code 2589 * 10}, and this method is invoked with {@code -1, 20}, then the 2590 * specified range bounds the viewable range, and this is treated as 2591 * if invoked with {@code 0, 9}. On the other hand, if this were 2592 * invoked with {@code -10, -1}, then the specified range does not 2593 * bound the viewable set of rows, and the selection is unchanged. 2594 * <p> 2595 * The parameters are not order dependent. That is, {@code 2596 * removeSelectionInterval(x, y)} is equivalent to 2597 * {@code removeSelectionInterval(y, x)}. 2598 * 2599 * @param index0 the first row to remove from the selection 2600 * @param index1 the last row to remove from the selection 2601 */ 2602 public void removeSelectionInterval(int index0, int index1) { 2603 TreePath[] paths = getPathBetweenRows(index0, index1); 2604 2605 if (paths != null && paths.length > 0) { 2606 this.getSelectionModel().removeSelectionPaths(paths); 2607 } 2608 } 2609 2610 /** 2611 * Removes the node identified by the specified path from the current 2612 * selection. 2613 * 2614 * @param path the <code>TreePath</code> identifying a node 2615 */ 2616 public void removeSelectionPath(TreePath path) { 2617 this.getSelectionModel().removeSelectionPath(path); 2618 } 2619 2620 /** 2621 * Removes the nodes identified by the specified paths from the 2622 * current selection. 2623 * 2624 * @param paths an array of <code>TreePath</code> objects that 2625 * specifies the nodes to remove 2626 */ 2627 public void removeSelectionPaths(TreePath[] paths) { 2628 this.getSelectionModel().removeSelectionPaths(paths); 2629 } 2630 2631 /** 2632 * Removes the row at the index <code>row</code> from the current 2633 * selection. 2634 * 2635 * @param row the row to remove 2636 */ 2637 public void removeSelectionRow(int row) { 2638 int[] rows = { row }; 2639 2640 removeSelectionRows(rows); 2641 } 2642 2643 /** 2644 * Removes the rows that are selected at each of the specified 2645 * rows. 2646 * 2647 * @param rows an array of ints specifying display rows, where 0 is 2648 * the first row in the display 2649 */ 2650 public void removeSelectionRows(int[] rows) { 2651 TreeUI ui = getUI(); 2652 2653 if(ui != null && rows != null) { 2654 int numRows = rows.length; 2655 TreePath[] paths = new TreePath[numRows]; 2656 2657 for(int counter = 0; counter < numRows; counter++) 2658 paths[counter] = ui.getPathForRow(this, rows[counter]); 2659 removeSelectionPaths(paths); 2660 } 2661 } 2662 2663 /** 2664 * Clears the selection. 2665 */ 2666 public void clearSelection() { 2667 getSelectionModel().clearSelection(); 2668 } 2669 2670 /** 2671 * Returns true if the selection is currently empty. 2672 * 2673 * @return true if the selection is currently empty 2674 */ 2675 public boolean isSelectionEmpty() { 2676 return getSelectionModel().isSelectionEmpty(); 2677 } 2678 2679 /** 2680 * Adds a listener for <code>TreeExpansion</code> events. 2681 * 2682 * @param tel a TreeExpansionListener that will be notified when 2683 * a tree node is expanded or collapsed (a "negative 2684 * expansion") 2685 */ 2686 public void addTreeExpansionListener(TreeExpansionListener tel) { 2687 if (settingUI) { 2688 uiTreeExpansionListener = tel; 2689 } 2690 listenerList.add(TreeExpansionListener.class, tel); 2691 } 2692 2693 /** 2694 * Removes a listener for <code>TreeExpansion</code> events. 2695 * 2696 * @param tel the <code>TreeExpansionListener</code> to remove 2697 */ 2698 public void removeTreeExpansionListener(TreeExpansionListener tel) { 2699 listenerList.remove(TreeExpansionListener.class, tel); 2700 if (uiTreeExpansionListener == tel) { 2701 uiTreeExpansionListener = null; 2702 } 2703 } 2704 2705 /** 2706 * Returns an array of all the <code>TreeExpansionListener</code>s added 2707 * to this JTree with addTreeExpansionListener(). 2708 * 2709 * @return all of the <code>TreeExpansionListener</code>s added or an empty 2710 * array if no listeners have been added 2711 * @since 1.4 2712 */ 2713 public TreeExpansionListener[] getTreeExpansionListeners() { 2714 return listenerList.getListeners(TreeExpansionListener.class); 2715 } 2716 2717 /** 2718 * Adds a listener for <code>TreeWillExpand</code> events. 2719 * 2720 * @param tel a <code>TreeWillExpandListener</code> that will be notified 2721 * when a tree node will be expanded or collapsed (a "negative 2722 * expansion") 2723 */ 2724 public void addTreeWillExpandListener(TreeWillExpandListener tel) { 2725 listenerList.add(TreeWillExpandListener.class, tel); 2726 } 2727 2728 /** 2729 * Removes a listener for <code>TreeWillExpand</code> events. 2730 * 2731 * @param tel the <code>TreeWillExpandListener</code> to remove 2732 */ 2733 public void removeTreeWillExpandListener(TreeWillExpandListener tel) { 2734 listenerList.remove(TreeWillExpandListener.class, tel); 2735 } 2736 2737 /** 2738 * Returns an array of all the <code>TreeWillExpandListener</code>s added 2739 * to this JTree with addTreeWillExpandListener(). 2740 * 2741 * @return all of the <code>TreeWillExpandListener</code>s added or an empty 2742 * array if no listeners have been added 2743 * @since 1.4 2744 */ 2745 public TreeWillExpandListener[] getTreeWillExpandListeners() { 2746 return listenerList.getListeners(TreeWillExpandListener.class); 2747 } 2748 2749 /** 2750 * Notifies all listeners that have registered interest for 2751 * notification on this event type. The event instance 2752 * is lazily created using the <code>path</code> parameter. 2753 * 2754 * @param path the <code>TreePath</code> indicating the node that was 2755 * expanded 2756 * @see EventListenerList 2757 */ 2758 public void fireTreeExpanded(TreePath path) { 2759 // Guaranteed to return a non-null array 2760 Object[] listeners = listenerList.getListenerList(); 2761 TreeExpansionEvent e = null; 2762 if (uiTreeExpansionListener != null) { 2763 e = new TreeExpansionEvent(this, path); 2764 uiTreeExpansionListener.treeExpanded(e); 2765 } 2766 // Process the listeners last to first, notifying 2767 // those that are interested in this event 2768 for (int i = listeners.length-2; i>=0; i-=2) { 2769 if (listeners[i]==TreeExpansionListener.class && 2770 listeners[i + 1] != uiTreeExpansionListener) { 2771 // Lazily create the event: 2772 if (e == null) 2773 e = new TreeExpansionEvent(this, path); 2774 ((TreeExpansionListener)listeners[i+1]). 2775 treeExpanded(e); 2776 } 2777 } 2778 } 2779 2780 /** 2781 * Notifies all listeners that have registered interest for 2782 * notification on this event type. The event instance 2783 * is lazily created using the <code>path</code> parameter. 2784 * 2785 * @param path the <code>TreePath</code> indicating the node that was 2786 * collapsed 2787 * @see EventListenerList 2788 */ 2789 public void fireTreeCollapsed(TreePath path) { 2790 // Guaranteed to return a non-null array 2791 Object[] listeners = listenerList.getListenerList(); 2792 TreeExpansionEvent e = null; 2793 if (uiTreeExpansionListener != null) { 2794 e = new TreeExpansionEvent(this, path); 2795 uiTreeExpansionListener.treeCollapsed(e); 2796 } 2797 // Process the listeners last to first, notifying 2798 // those that are interested in this event 2799 for (int i = listeners.length-2; i>=0; i-=2) { 2800 if (listeners[i]==TreeExpansionListener.class && 2801 listeners[i + 1] != uiTreeExpansionListener) { 2802 // Lazily create the event: 2803 if (e == null) 2804 e = new TreeExpansionEvent(this, path); 2805 ((TreeExpansionListener)listeners[i+1]). 2806 treeCollapsed(e); 2807 } 2808 } 2809 } 2810 2811 /** 2812 * Notifies all listeners that have registered interest for 2813 * notification on this event type. The event instance 2814 * is lazily created using the <code>path</code> parameter. 2815 * 2816 * @param path the <code>TreePath</code> indicating the node that was 2817 * expanded 2818 * @see EventListenerList 2819 */ 2820 public void fireTreeWillExpand(TreePath path) throws ExpandVetoException { 2821 // Guaranteed to return a non-null array 2822 Object[] listeners = listenerList.getListenerList(); 2823 TreeExpansionEvent e = null; 2824 // Process the listeners last to first, notifying 2825 // those that are interested in this event 2826 for (int i = listeners.length-2; i>=0; i-=2) { 2827 if (listeners[i]==TreeWillExpandListener.class) { 2828 // Lazily create the event: 2829 if (e == null) 2830 e = new TreeExpansionEvent(this, path); 2831 ((TreeWillExpandListener)listeners[i+1]). 2832 treeWillExpand(e); 2833 } 2834 } 2835 } 2836 2837 /** 2838 * Notifies all listeners that have registered interest for 2839 * notification on this event type. The event instance 2840 * is lazily created using the <code>path</code> parameter. 2841 * 2842 * @param path the <code>TreePath</code> indicating the node that was 2843 * expanded 2844 * @see EventListenerList 2845 */ 2846 public void fireTreeWillCollapse(TreePath path) throws ExpandVetoException { 2847 // Guaranteed to return a non-null array 2848 Object[] listeners = listenerList.getListenerList(); 2849 TreeExpansionEvent e = null; 2850 // Process the listeners last to first, notifying 2851 // those that are interested in this event 2852 for (int i = listeners.length-2; i>=0; i-=2) { 2853 if (listeners[i]==TreeWillExpandListener.class) { 2854 // Lazily create the event: 2855 if (e == null) 2856 e = new TreeExpansionEvent(this, path); 2857 ((TreeWillExpandListener)listeners[i+1]). 2858 treeWillCollapse(e); 2859 } 2860 } 2861 } 2862 2863 /** 2864 * Adds a listener for <code>TreeSelection</code> events. 2865 * 2866 * @param tsl the <code>TreeSelectionListener</code> that will be notified 2867 * when a node is selected or deselected (a "negative 2868 * selection") 2869 */ 2870 public void addTreeSelectionListener(TreeSelectionListener tsl) { 2871 listenerList.add(TreeSelectionListener.class,tsl); 2872 if(listenerList.getListenerCount(TreeSelectionListener.class) != 0 2873 && selectionRedirector == null) { 2874 selectionRedirector = new TreeSelectionRedirector(); 2875 selectionModel.addTreeSelectionListener(selectionRedirector); 2876 } 2877 } 2878 2879 /** 2880 * Removes a <code>TreeSelection</code> listener. 2881 * 2882 * @param tsl the <code>TreeSelectionListener</code> to remove 2883 */ 2884 public void removeTreeSelectionListener(TreeSelectionListener tsl) { 2885 listenerList.remove(TreeSelectionListener.class,tsl); 2886 if(listenerList.getListenerCount(TreeSelectionListener.class) == 0 2887 && selectionRedirector != null) { 2888 selectionModel.removeTreeSelectionListener 2889 (selectionRedirector); 2890 selectionRedirector = null; 2891 } 2892 } 2893 2894 /** 2895 * Returns an array of all the <code>TreeSelectionListener</code>s added 2896 * to this JTree with addTreeSelectionListener(). 2897 * 2898 * @return all of the <code>TreeSelectionListener</code>s added or an empty 2899 * array if no listeners have been added 2900 * @since 1.4 2901 */ 2902 public TreeSelectionListener[] getTreeSelectionListeners() { 2903 return listenerList.getListeners(TreeSelectionListener.class); 2904 } 2905 2906 /** 2907 * Notifies all listeners that have registered interest for 2908 * notification on this event type. 2909 * 2910 * @param e the <code>TreeSelectionEvent</code> to be fired; 2911 * generated by the 2912 * <code>TreeSelectionModel</code> 2913 * when a node is selected or deselected 2914 * @see EventListenerList 2915 */ 2916 protected void fireValueChanged(TreeSelectionEvent e) { 2917 // Guaranteed to return a non-null array 2918 Object[] listeners = listenerList.getListenerList(); 2919 // Process the listeners last to first, notifying 2920 // those that are interested in this event 2921 for (int i = listeners.length-2; i>=0; i-=2) { 2922 // TreeSelectionEvent e = null; 2923 if (listeners[i]==TreeSelectionListener.class) { 2924 // Lazily create the event: 2925 // if (e == null) 2926 // e = new ListSelectionEvent(this, firstIndex, lastIndex); 2927 ((TreeSelectionListener)listeners[i+1]).valueChanged(e); 2928 } 2929 } 2930 } 2931 2932 /** 2933 * Sent when the tree has changed enough that we need to resize 2934 * the bounds, but not enough that we need to remove the 2935 * expanded node set (e.g nodes were expanded or collapsed, or 2936 * nodes were inserted into the tree). You should never have to 2937 * invoke this, the UI will invoke this as it needs to. 2938 */ 2939 public void treeDidChange() { 2940 revalidate(); 2941 repaint(); 2942 } 2943 2944 /** 2945 * Sets the number of rows that are to be displayed. 2946 * This will only work if the tree is contained in a 2947 * <code>JScrollPane</code>, 2948 * and will adjust the preferred size and size of that scrollpane. 2949 * <p> 2950 * This is a bound property. 2951 * 2952 * @param newCount the number of rows to display 2953 * @beaninfo 2954 * bound: true 2955 * description: The number of rows that are to be displayed. 2956 */ 2957 public void setVisibleRowCount(int newCount) { 2958 int oldCount = visibleRowCount; 2959 2960 visibleRowCount = newCount; 2961 firePropertyChange(VISIBLE_ROW_COUNT_PROPERTY, oldCount, 2962 visibleRowCount); 2963 invalidate(); 2964 if (accessibleContext != null) { 2965 ((AccessibleJTree)accessibleContext).fireVisibleDataPropertyChange(); 2966 } 2967 } 2968 2969 /** 2970 * Returns the number of rows that are displayed in the display area. 2971 * 2972 * @return the number of rows displayed 2973 */ 2974 public int getVisibleRowCount() { 2975 return visibleRowCount; 2976 } 2977 2978 /** 2979 * Expands the root path, assuming the current TreeModel has been set. 2980 */ 2981 private void expandRoot() { 2982 TreeModel model = getModel(); 2983 2984 if(model != null && model.getRoot() != null) { 2985 expandPath(new TreePath(model.getRoot())); 2986 } 2987 } 2988 2989 /** 2990 * Returns the TreePath to the next tree element that 2991 * begins with a prefix. To handle the conversion of a 2992 * <code>TreePath</code> into a String, <code>convertValueToText</code> 2993 * is used. 2994 * 2995 * @param prefix the string to test for a match 2996 * @param startingRow the row for starting the search 2997 * @param bias the search direction, either 2998 * Position.Bias.Forward or Position.Bias.Backward. 2999 * @return the TreePath of the next tree element that 3000 * starts with the prefix; otherwise null 3001 * @exception IllegalArgumentException if prefix is null 3002 * or startingRow is out of bounds 3003 * @since 1.4 3004 */ 3005 public TreePath getNextMatch(String prefix, int startingRow, 3006 Position.Bias bias) { 3007 3008 int max = getRowCount(); 3009 if (prefix == null) { 3010 throw new IllegalArgumentException(); 3011 } 3012 if (startingRow < 0 || startingRow >= max) { 3013 throw new IllegalArgumentException(); 3014 } 3015 prefix = prefix.toUpperCase(); 3016 3017 // start search from the next/previous element froom the 3018 // selected element 3019 int increment = (bias == Position.Bias.Forward) ? 1 : -1; 3020 int row = startingRow; 3021 do { 3022 TreePath path = getPathForRow(row); 3023 String text = convertValueToText( 3024 path.getLastPathComponent(), isRowSelected(row), 3025 isExpanded(row), true, row, false); 3026 3027 if (text.toUpperCase().startsWith(prefix)) { 3028 return path; 3029 } 3030 row = (row + increment + max) % max; 3031 } while (row != startingRow); 3032 return null; 3033 } 3034 3035 // Serialization support. 3036 private void writeObject(ObjectOutputStream s) throws IOException { 3037 Vector<Object> values = new Vector<Object>(); 3038 3039 s.defaultWriteObject(); 3040 // Save the cellRenderer, if its Serializable. 3041 if(cellRenderer != null && cellRenderer instanceof Serializable) { 3042 values.addElement("cellRenderer"); 3043 values.addElement(cellRenderer); 3044 } 3045 // Save the cellEditor, if its Serializable. 3046 if(cellEditor != null && cellEditor instanceof Serializable) { 3047 values.addElement("cellEditor"); 3048 values.addElement(cellEditor); 3049 } 3050 // Save the treeModel, if its Serializable. 3051 if(treeModel != null && treeModel instanceof Serializable) { 3052 values.addElement("treeModel"); 3053 values.addElement(treeModel); 3054 } 3055 // Save the selectionModel, if its Serializable. 3056 if(selectionModel != null && selectionModel instanceof Serializable) { 3057 values.addElement("selectionModel"); 3058 values.addElement(selectionModel); 3059 } 3060 3061 Object expandedData = getArchivableExpandedState(); 3062 3063 if(expandedData != null) { 3064 values.addElement("expandedState"); 3065 values.addElement(expandedData); 3066 } 3067 3068 s.writeObject(values); 3069 if (getUIClassID().equals(uiClassID)) { 3070 byte count = JComponent.getWriteObjCounter(this); 3071 JComponent.setWriteObjCounter(this, --count); 3072 if (count == 0 && ui != null) { 3073 ui.installUI(this); 3074 } 3075 } 3076 } 3077 3078 private void readObject(ObjectInputStream s) 3079 throws IOException, ClassNotFoundException { 3080 s.defaultReadObject(); 3081 3082 // Create an instance of expanded state. 3083 3084 expandedState = new Hashtable<TreePath, Boolean>(); 3085 3086 expandedStack = new Stack<Stack<TreePath>>(); 3087 3088 Vector<?> values = (Vector)s.readObject(); 3089 int indexCounter = 0; 3090 int maxCounter = values.size(); 3091 3092 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3093 equals("cellRenderer")) { 3094 cellRenderer = (TreeCellRenderer)values.elementAt(++indexCounter); 3095 indexCounter++; 3096 } 3097 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3098 equals("cellEditor")) { 3099 cellEditor = (TreeCellEditor)values.elementAt(++indexCounter); 3100 indexCounter++; 3101 } 3102 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3103 equals("treeModel")) { 3104 treeModel = (TreeModel)values.elementAt(++indexCounter); 3105 indexCounter++; 3106 } 3107 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3108 equals("selectionModel")) { 3109 selectionModel = (TreeSelectionModel)values.elementAt(++indexCounter); 3110 indexCounter++; 3111 } 3112 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3113 equals("expandedState")) { 3114 unarchiveExpandedState(values.elementAt(++indexCounter)); 3115 indexCounter++; 3116 } 3117 // Reinstall the redirector. 3118 if(listenerList.getListenerCount(TreeSelectionListener.class) != 0) { 3119 selectionRedirector = new TreeSelectionRedirector(); 3120 selectionModel.addTreeSelectionListener(selectionRedirector); 3121 } 3122 // Listener to TreeModel. 3123 if(treeModel != null) { 3124 treeModelListener = createTreeModelListener(); 3125 if(treeModelListener != null) 3126 treeModel.addTreeModelListener(treeModelListener); 3127 } 3128 } 3129 3130 /** 3131 * Returns an object that can be archived indicating what nodes are 3132 * expanded and what aren't. The objects from the model are NOT 3133 * written out. 3134 */ 3135 private Object getArchivableExpandedState() { 3136 TreeModel model = getModel(); 3137 3138 if(model != null) { 3139 Enumeration<TreePath> paths = expandedState.keys(); 3140 3141 if(paths != null) { 3142 Vector<Object> state = new Vector<Object>(); 3143 3144 while(paths.hasMoreElements()) { 3145 TreePath path = paths.nextElement(); 3146 Object archivePath; 3147 3148 try { 3149 archivePath = getModelIndexsForPath(path); 3150 } catch (Error error) { 3151 archivePath = null; 3152 } 3153 if(archivePath != null) { 3154 state.addElement(archivePath); 3155 state.addElement(expandedState.get(path)); 3156 } 3157 } 3158 return state; 3159 } 3160 } 3161 return null; 3162 } 3163 3164 /** 3165 * Updates the expanded state of nodes in the tree based on the 3166 * previously archived state <code>state</code>. 3167 */ 3168 private void unarchiveExpandedState(Object state) { 3169 if(state instanceof Vector) { 3170 Vector<?> paths = (Vector)state; 3171 3172 for(int counter = paths.size() - 1; counter >= 0; counter--) { 3173 Boolean eState = (Boolean)paths.elementAt(counter--); 3174 TreePath path; 3175 3176 try { 3177 path = getPathForIndexs((int[])paths.elementAt(counter)); 3178 if(path != null) 3179 expandedState.put(path, eState); 3180 } catch (Error error) {} 3181 } 3182 } 3183 } 3184 3185 /** 3186 * Returns an array of integers specifying the indexs of the 3187 * components in the <code>path</code>. If <code>path</code> is 3188 * the root, this will return an empty array. If <code>path</code> 3189 * is <code>null</code>, <code>null</code> will be returned. 3190 */ 3191 private int[] getModelIndexsForPath(TreePath path) { 3192 if(path != null) { 3193 TreeModel model = getModel(); 3194 int count = path.getPathCount(); 3195 int[] indexs = new int[count - 1]; 3196 Object parent = model.getRoot(); 3197 3198 for(int counter = 1; counter < count; counter++) { 3199 indexs[counter - 1] = model.getIndexOfChild 3200 (parent, path.getPathComponent(counter)); 3201 parent = path.getPathComponent(counter); 3202 if(indexs[counter - 1] < 0) 3203 return null; 3204 } 3205 return indexs; 3206 } 3207 return null; 3208 } 3209 3210 /** 3211 * Returns a <code>TreePath</code> created by obtaining the children 3212 * for each of the indices in <code>indexs</code>. If <code>indexs</code> 3213 * or the <code>TreeModel</code> is <code>null</code>, it will return 3214 * <code>null</code>. 3215 */ 3216 private TreePath getPathForIndexs(int[] indexs) { 3217 if(indexs == null) 3218 return null; 3219 3220 TreeModel model = getModel(); 3221 3222 if(model == null) 3223 return null; 3224 3225 int count = indexs.length; 3226 Object parent = model.getRoot(); 3227 if (parent == null) 3228 return null; 3229 3230 TreePath parentPath = new TreePath(parent); 3231 3232 for(int counter = 0; counter < count; counter++) { 3233 parent = model.getChild(parent, indexs[counter]); 3234 if(parent == null) 3235 return null; 3236 parentPath = parentPath.pathByAddingChild(parent); 3237 } 3238 return parentPath; 3239 } 3240 3241 /** 3242 * <code>EmptySelectionModel</code> is a <code>TreeSelectionModel</code> 3243 * that does not allow anything to be selected. 3244 * <p> 3245 * <strong>Warning:</strong> 3246 * Serialized objects of this class will not be compatible with 3247 * future Swing releases. The current serialization support is 3248 * appropriate for short term storage or RMI between applications running 3249 * the same version of Swing. As of 1.4, support for long term storage 3250 * of all JavaBeans™ 3251 * has been added to the <code>java.beans</code> package. 3252 * Please see {@link java.beans.XMLEncoder}. 3253 */ 3254 @SuppressWarnings("serial") 3255 protected static class EmptySelectionModel extends 3256 DefaultTreeSelectionModel 3257 { 3258 /** 3259 * The single instance of {@code EmptySelectionModel}. 3260 */ 3261 protected static final EmptySelectionModel sharedInstance = 3262 new EmptySelectionModel(); 3263 3264 /** 3265 * Returns the single instance of {@code EmptySelectionModel}. 3266 * 3267 * @return single instance of {@code EmptySelectionModel} 3268 */ 3269 static public EmptySelectionModel sharedInstance() { 3270 return sharedInstance; 3271 } 3272 3273 /** 3274 * This is overriden to do nothing; {@code EmptySelectionModel} 3275 * does not allow a selection. 3276 * 3277 * @param paths the paths to select; this is ignored 3278 */ 3279 public void setSelectionPaths(TreePath[] paths) {} 3280 3281 /** 3282 * This is overriden to do nothing; {@code EmptySelectionModel} 3283 * does not allow a selection. 3284 * 3285 * @param paths the paths to add to the selection; this is ignored 3286 */ 3287 public void addSelectionPaths(TreePath[] paths) {} 3288 3289 /** 3290 * This is overriden to do nothing; {@code EmptySelectionModel} 3291 * does not allow a selection. 3292 * 3293 * @param paths the paths to remove; this is ignored 3294 */ 3295 public void removeSelectionPaths(TreePath[] paths) {} 3296 3297 /** 3298 * This is overriden to do nothing; {@code EmptySelectionModel} 3299 * does not allow a selection. 3300 * 3301 * @param mode the selection mode; this is ignored 3302 * @since 1.7 3303 */ 3304 public void setSelectionMode(int mode) { 3305 } 3306 3307 /** 3308 * This is overriden to do nothing; {@code EmptySelectionModel} 3309 * does not allow a selection. 3310 * 3311 * @param mapper the {@code RowMapper} instance; this is ignored 3312 * @since 1.7 3313 */ 3314 public void setRowMapper(RowMapper mapper) { 3315 } 3316 3317 /** 3318 * This is overriden to do nothing; {@code EmptySelectionModel} 3319 * does not allow a selection. 3320 * 3321 * @param listener the listener to add; this is ignored 3322 * @since 1.7 3323 */ 3324 public void addTreeSelectionListener(TreeSelectionListener listener) { 3325 } 3326 3327 /** 3328 * This is overriden to do nothing; {@code EmptySelectionModel} 3329 * does not allow a selection. 3330 * 3331 * @param listener the listener to remove; this is ignored 3332 * @since 1.7 3333 */ 3334 public void removeTreeSelectionListener( 3335 TreeSelectionListener listener) { 3336 } 3337 3338 /** 3339 * This is overriden to do nothing; {@code EmptySelectionModel} 3340 * does not allow a selection. 3341 * 3342 * @param listener the listener to add; this is ignored 3343 * @since 1.7 3344 */ 3345 public void addPropertyChangeListener( 3346 PropertyChangeListener listener) { 3347 } 3348 3349 /** 3350 * This is overriden to do nothing; {@code EmptySelectionModel} 3351 * does not allow a selection. 3352 * 3353 * @param listener the listener to remove; this is ignored 3354 * @since 1.7 3355 */ 3356 public void removePropertyChangeListener( 3357 PropertyChangeListener listener) { 3358 } 3359 } 3360 3361 3362 /** 3363 * Handles creating a new <code>TreeSelectionEvent</code> with the 3364 * <code>JTree</code> as the 3365 * source and passing it off to all the listeners. 3366 * <p> 3367 * <strong>Warning:</strong> 3368 * Serialized objects of this class will not be compatible with 3369 * future Swing releases. The current serialization support is 3370 * appropriate for short term storage or RMI between applications running 3371 * the same version of Swing. As of 1.4, support for long term storage 3372 * of all JavaBeans™ 3373 * has been added to the <code>java.beans</code> package. 3374 * Please see {@link java.beans.XMLEncoder}. 3375 */ 3376 @SuppressWarnings("serial") 3377 protected class TreeSelectionRedirector implements Serializable, 3378 TreeSelectionListener 3379 { 3380 /** 3381 * Invoked by the <code>TreeSelectionModel</code> when the 3382 * selection changes. 3383 * 3384 * @param e the <code>TreeSelectionEvent</code> generated by the 3385 * <code>TreeSelectionModel</code> 3386 */ 3387 public void valueChanged(TreeSelectionEvent e) { 3388 TreeSelectionEvent newE; 3389 3390 newE = (TreeSelectionEvent)e.cloneWithSource(JTree.this); 3391 fireValueChanged(newE); 3392 } 3393 } // End of class JTree.TreeSelectionRedirector 3394 3395 // 3396 // Scrollable interface 3397 // 3398 3399 /** 3400 * Returns the preferred display size of a <code>JTree</code>. The height is 3401 * determined from <code>getVisibleRowCount</code> and the width 3402 * is the current preferred width. 3403 * 3404 * @return a <code>Dimension</code> object containing the preferred size 3405 */ 3406 public Dimension getPreferredScrollableViewportSize() { 3407 int width = getPreferredSize().width; 3408 int visRows = getVisibleRowCount(); 3409 int height = -1; 3410 3411 if(isFixedRowHeight()) 3412 height = visRows * getRowHeight(); 3413 else { 3414 TreeUI ui = getUI(); 3415 3416 if (ui != null && visRows > 0) { 3417 int rc = ui.getRowCount(this); 3418 3419 if (rc >= visRows) { 3420 Rectangle bounds = getRowBounds(visRows - 1); 3421 if (bounds != null) { 3422 height = bounds.y + bounds.height; 3423 } 3424 } 3425 else if (rc > 0) { 3426 Rectangle bounds = getRowBounds(0); 3427 if (bounds != null) { 3428 height = bounds.height * visRows; 3429 } 3430 } 3431 } 3432 if (height == -1) { 3433 height = 16 * visRows; 3434 } 3435 } 3436 return new Dimension(width, height); 3437 } 3438 3439 /** 3440 * Returns the amount to increment when scrolling. The amount is 3441 * the height of the first displayed row that isn't completely in view 3442 * or, if it is totally displayed, the height of the next row in the 3443 * scrolling direction. 3444 * 3445 * @param visibleRect the view area visible within the viewport 3446 * @param orientation either <code>SwingConstants.VERTICAL</code> 3447 * or <code>SwingConstants.HORIZONTAL</code> 3448 * @param direction less than zero to scroll up/left, 3449 * greater than zero for down/right 3450 * @return the "unit" increment for scrolling in the specified direction 3451 * @see JScrollBar#setUnitIncrement(int) 3452 */ 3453 public int getScrollableUnitIncrement(Rectangle visibleRect, 3454 int orientation, int direction) { 3455 if(orientation == SwingConstants.VERTICAL) { 3456 Rectangle rowBounds; 3457 int firstIndex = getClosestRowForLocation 3458 (0, visibleRect.y); 3459 3460 if(firstIndex != -1) { 3461 rowBounds = getRowBounds(firstIndex); 3462 if(rowBounds.y != visibleRect.y) { 3463 if(direction < 0) { 3464 // UP 3465 return Math.max(0, (visibleRect.y - rowBounds.y)); 3466 } 3467 return (rowBounds.y + rowBounds.height - visibleRect.y); 3468 } 3469 if(direction < 0) { // UP 3470 if(firstIndex != 0) { 3471 rowBounds = getRowBounds(firstIndex - 1); 3472 return rowBounds.height; 3473 } 3474 } 3475 else { 3476 return rowBounds.height; 3477 } 3478 } 3479 return 0; 3480 } 3481 return 4; 3482 } 3483 3484 3485 /** 3486 * Returns the amount for a block increment, which is the height or 3487 * width of <code>visibleRect</code>, based on <code>orientation</code>. 3488 * 3489 * @param visibleRect the view area visible within the viewport 3490 * @param orientation either <code>SwingConstants.VERTICAL</code> 3491 * or <code>SwingConstants.HORIZONTAL</code> 3492 * @param direction less than zero to scroll up/left, 3493 * greater than zero for down/right. 3494 * @return the "block" increment for scrolling in the specified direction 3495 * @see JScrollBar#setBlockIncrement(int) 3496 */ 3497 public int getScrollableBlockIncrement(Rectangle visibleRect, 3498 int orientation, int direction) { 3499 return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : 3500 visibleRect.width; 3501 } 3502 3503 /** 3504 * Returns false to indicate that the width of the viewport does not 3505 * determine the width of the table, unless the preferred width of 3506 * the tree is smaller than the viewports width. In other words: 3507 * ensure that the tree is never smaller than its viewport. 3508 * 3509 * @return whether the tree should track the width of the viewport 3510 * @see Scrollable#getScrollableTracksViewportWidth 3511 */ 3512 public boolean getScrollableTracksViewportWidth() { 3513 Container parent = SwingUtilities.getUnwrappedParent(this); 3514 if (parent instanceof JViewport) { 3515 return parent.getWidth() > getPreferredSize().width; 3516 } 3517 return false; 3518 } 3519 3520 /** 3521 * Returns false to indicate that the height of the viewport does not 3522 * determine the height of the table, unless the preferred height 3523 * of the tree is smaller than the viewports height. In other words: 3524 * ensure that the tree is never smaller than its viewport. 3525 * 3526 * @return whether the tree should track the height of the viewport 3527 * @see Scrollable#getScrollableTracksViewportHeight 3528 */ 3529 public boolean getScrollableTracksViewportHeight() { 3530 Container parent = SwingUtilities.getUnwrappedParent(this); 3531 if (parent instanceof JViewport) { 3532 return parent.getHeight() > getPreferredSize().height; 3533 } 3534 return false; 3535 } 3536 3537 /** 3538 * Sets the expanded state of this <code>JTree</code>. 3539 * If <code>state</code> is 3540 * true, all parents of <code>path</code> and path are marked as 3541 * expanded. If <code>state</code> is false, all parents of 3542 * <code>path</code> are marked EXPANDED, but <code>path</code> itself 3543 * is marked collapsed.<p> 3544 * This will fail if a <code>TreeWillExpandListener</code> vetos it. 3545 */ 3546 protected void setExpandedState(TreePath path, boolean state) { 3547 if(path != null) { 3548 // Make sure all parents of path are expanded. 3549 Stack<TreePath> stack; 3550 TreePath parentPath = path.getParentPath(); 3551 3552 if (expandedStack.size() == 0) { 3553 stack = new Stack<TreePath>(); 3554 } 3555 else { 3556 stack = expandedStack.pop(); 3557 } 3558 3559 try { 3560 while(parentPath != null) { 3561 if(isExpanded(parentPath)) { 3562 parentPath = null; 3563 } 3564 else { 3565 stack.push(parentPath); 3566 parentPath = parentPath.getParentPath(); 3567 } 3568 } 3569 for(int counter = stack.size() - 1; counter >= 0; counter--) { 3570 parentPath = stack.pop(); 3571 if(!isExpanded(parentPath)) { 3572 try { 3573 fireTreeWillExpand(parentPath); 3574 } catch (ExpandVetoException eve) { 3575 // Expand vetoed! 3576 return; 3577 } 3578 expandedState.put(parentPath, Boolean.TRUE); 3579 fireTreeExpanded(parentPath); 3580 if (accessibleContext != null) { 3581 ((AccessibleJTree)accessibleContext). 3582 fireVisibleDataPropertyChange(); 3583 } 3584 } 3585 } 3586 } 3587 finally { 3588 if (expandedStack.size() < TEMP_STACK_SIZE) { 3589 stack.removeAllElements(); 3590 expandedStack.push(stack); 3591 } 3592 } 3593 if(!state) { 3594 // collapse last path. 3595 Object cValue = expandedState.get(path); 3596 3597 if(cValue != null && ((Boolean)cValue).booleanValue()) { 3598 try { 3599 fireTreeWillCollapse(path); 3600 } 3601 catch (ExpandVetoException eve) { 3602 return; 3603 } 3604 expandedState.put(path, Boolean.FALSE); 3605 fireTreeCollapsed(path); 3606 if (removeDescendantSelectedPaths(path, false) && 3607 !isPathSelected(path)) { 3608 // A descendant was selected, select the parent. 3609 addSelectionPath(path); 3610 } 3611 if (accessibleContext != null) { 3612 ((AccessibleJTree)accessibleContext). 3613 fireVisibleDataPropertyChange(); 3614 } 3615 } 3616 } 3617 else { 3618 // Expand last path. 3619 Object cValue = expandedState.get(path); 3620 3621 if(cValue == null || !((Boolean)cValue).booleanValue()) { 3622 try { 3623 fireTreeWillExpand(path); 3624 } 3625 catch (ExpandVetoException eve) { 3626 return; 3627 } 3628 expandedState.put(path, Boolean.TRUE); 3629 fireTreeExpanded(path); 3630 if (accessibleContext != null) { 3631 ((AccessibleJTree)accessibleContext). 3632 fireVisibleDataPropertyChange(); 3633 } 3634 } 3635 } 3636 } 3637 } 3638 3639 /** 3640 * Returns an <code>Enumeration</code> of <code>TreePaths</code> 3641 * that have been expanded that 3642 * are descendants of <code>parent</code>. 3643 */ 3644 protected Enumeration<TreePath> 3645 getDescendantToggledPaths(TreePath parent) 3646 { 3647 if(parent == null) 3648 return null; 3649 3650 Vector<TreePath> descendants = new Vector<TreePath>(); 3651 Enumeration<TreePath> nodes = expandedState.keys(); 3652 3653 while(nodes.hasMoreElements()) { 3654 TreePath path = nodes.nextElement(); 3655 if(parent.isDescendant(path)) 3656 descendants.addElement(path); 3657 } 3658 return descendants.elements(); 3659 } 3660 3661 /** 3662 * Removes any descendants of the <code>TreePaths</code> in 3663 * <code>toRemove</code> 3664 * that have been expanded. 3665 * 3666 * @param toRemove an enumeration of the paths to remove; a value of 3667 * {@code null} is ignored 3668 * @throws ClassCastException if {@code toRemove} contains an 3669 * element that is not a {@code TreePath}; {@code null} 3670 * values are ignored 3671 */ 3672 protected void 3673 removeDescendantToggledPaths(Enumeration<TreePath> toRemove) 3674 { 3675 if(toRemove != null) { 3676 while(toRemove.hasMoreElements()) { 3677 Enumeration<?> descendants = getDescendantToggledPaths 3678 (toRemove.nextElement()); 3679 3680 if(descendants != null) { 3681 while(descendants.hasMoreElements()) { 3682 expandedState.remove(descendants.nextElement()); 3683 } 3684 } 3685 } 3686 } 3687 } 3688 3689 /** 3690 * Clears the cache of toggled tree paths. This does NOT send out 3691 * any <code>TreeExpansionListener</code> events. 3692 */ 3693 protected void clearToggledPaths() { 3694 expandedState.clear(); 3695 } 3696 3697 /** 3698 * Creates and returns an instance of <code>TreeModelHandler</code>. 3699 * The returned 3700 * object is responsible for updating the expanded state when the 3701 * <code>TreeModel</code> changes. 3702 * <p> 3703 * For more information on what expanded state means, see the 3704 * <a href=#jtree_description>JTree description</a> above. 3705 */ 3706 protected TreeModelListener createTreeModelListener() { 3707 return new TreeModelHandler(); 3708 } 3709 3710 /** 3711 * Removes any paths in the selection that are descendants of 3712 * <code>path</code>. If <code>includePath</code> is true and 3713 * <code>path</code> is selected, it will be removed from the selection. 3714 * 3715 * @return true if a descendant was selected 3716 * @since 1.3 3717 */ 3718 protected boolean removeDescendantSelectedPaths(TreePath path, 3719 boolean includePath) { 3720 TreePath[] toRemove = getDescendantSelectedPaths(path, includePath); 3721 3722 if (toRemove != null) { 3723 getSelectionModel().removeSelectionPaths(toRemove); 3724 return true; 3725 } 3726 return false; 3727 } 3728 3729 /** 3730 * Returns an array of paths in the selection that are descendants of 3731 * <code>path</code>. The returned array may contain <code>null</code>s. 3732 */ 3733 private TreePath[] getDescendantSelectedPaths(TreePath path, 3734 boolean includePath) { 3735 TreeSelectionModel sm = getSelectionModel(); 3736 TreePath[] selPaths = (sm != null) ? sm.getSelectionPaths() : 3737 null; 3738 3739 if(selPaths != null) { 3740 boolean shouldRemove = false; 3741 3742 for(int counter = selPaths.length - 1; counter >= 0; counter--) { 3743 if(selPaths[counter] != null && 3744 path.isDescendant(selPaths[counter]) && 3745 (!path.equals(selPaths[counter]) || includePath)) 3746 shouldRemove = true; 3747 else 3748 selPaths[counter] = null; 3749 } 3750 if(!shouldRemove) { 3751 selPaths = null; 3752 } 3753 return selPaths; 3754 } 3755 return null; 3756 } 3757 3758 /** 3759 * Removes any paths from the selection model that are descendants of 3760 * the nodes identified by in <code>e</code>. 3761 */ 3762 void removeDescendantSelectedPaths(TreeModelEvent e) { 3763 TreePath pPath = SwingUtilities2.getTreePath(e, getModel()); 3764 Object[] oldChildren = e.getChildren(); 3765 TreeSelectionModel sm = getSelectionModel(); 3766 3767 if (sm != null && pPath != null && oldChildren != null && 3768 oldChildren.length > 0) { 3769 for (int counter = oldChildren.length - 1; counter >= 0; 3770 counter--) { 3771 // Might be better to call getDescendantSelectedPaths 3772 // numerous times, then push to the model. 3773 removeDescendantSelectedPaths(pPath.pathByAddingChild 3774 (oldChildren[counter]), true); 3775 } 3776 } 3777 } 3778 3779 3780 /** 3781 * Listens to the model and updates the <code>expandedState</code> 3782 * accordingly when nodes are removed, or changed. 3783 */ 3784 protected class TreeModelHandler implements TreeModelListener { 3785 public void treeNodesChanged(TreeModelEvent e) { } 3786 3787 public void treeNodesInserted(TreeModelEvent e) { } 3788 3789 public void treeStructureChanged(TreeModelEvent e) { 3790 if(e == null) 3791 return; 3792 3793 // NOTE: If I change this to NOT remove the descendants 3794 // and update BasicTreeUIs treeStructureChanged method 3795 // to update descendants in response to a treeStructureChanged 3796 // event, all the children of the event won't collapse! 3797 TreePath parent = SwingUtilities2.getTreePath(e, getModel()); 3798 3799 if(parent == null) 3800 return; 3801 3802 if (parent.getPathCount() == 1) { 3803 // New root, remove everything! 3804 clearToggledPaths(); 3805 Object treeRoot = treeModel.getRoot(); 3806 if(treeRoot != null && 3807 !treeModel.isLeaf(treeRoot)) { 3808 // Mark the root as expanded, if it isn't a leaf. 3809 expandedState.put(parent, Boolean.TRUE); 3810 } 3811 } 3812 else if(expandedState.get(parent) != null) { 3813 Vector<TreePath> toRemove = new Vector<TreePath>(1); 3814 boolean isExpanded = isExpanded(parent); 3815 3816 toRemove.addElement(parent); 3817 removeDescendantToggledPaths(toRemove.elements()); 3818 if(isExpanded) { 3819 TreeModel model = getModel(); 3820 3821 if(model == null || model.isLeaf 3822 (parent.getLastPathComponent())) 3823 collapsePath(parent); 3824 else 3825 expandedState.put(parent, Boolean.TRUE); 3826 } 3827 } 3828 removeDescendantSelectedPaths(parent, false); 3829 } 3830 3831 public void treeNodesRemoved(TreeModelEvent e) { 3832 if(e == null) 3833 return; 3834 3835 TreePath parent = SwingUtilities2.getTreePath(e, getModel()); 3836 Object[] children = e.getChildren(); 3837 3838 if(children == null) 3839 return; 3840 3841 TreePath rPath; 3842 Vector<TreePath> toRemove 3843 = new Vector<TreePath>(Math.max(1, children.length)); 3844 3845 for(int counter = children.length - 1; counter >= 0; counter--) { 3846 rPath = parent.pathByAddingChild(children[counter]); 3847 if(expandedState.get(rPath) != null) 3848 toRemove.addElement(rPath); 3849 } 3850 if(toRemove.size() > 0) 3851 removeDescendantToggledPaths(toRemove.elements()); 3852 3853 TreeModel model = getModel(); 3854 3855 if(model == null || model.isLeaf(parent.getLastPathComponent())) 3856 expandedState.remove(parent); 3857 3858 removeDescendantSelectedPaths(e); 3859 } 3860 } 3861 3862 3863 /** 3864 * <code>DynamicUtilTreeNode</code> can wrap 3865 * vectors/hashtables/arrays/strings and 3866 * create the appropriate children tree nodes as necessary. It is 3867 * dynamic in that it will only create the children as necessary. 3868 * <p> 3869 * <strong>Warning:</strong> 3870 * Serialized objects of this class will not be compatible with 3871 * future Swing releases. The current serialization support is 3872 * appropriate for short term storage or RMI between applications running 3873 * the same version of Swing. As of 1.4, support for long term storage 3874 * of all JavaBeans™ 3875 * has been added to the <code>java.beans</code> package. 3876 * Please see {@link java.beans.XMLEncoder}. 3877 */ 3878 @SuppressWarnings("serial") 3879 public static class DynamicUtilTreeNode extends DefaultMutableTreeNode { 3880 /** 3881 * Does the this <code>JTree</code> have children? 3882 * This property is currently not implemented. 3883 */ 3884 protected boolean hasChildren; 3885 /** Value to create children with. */ 3886 protected Object childValue; 3887 /** Have the children been loaded yet? */ 3888 protected boolean loadedChildren; 3889 3890 /** 3891 * Adds to parent all the children in <code>children</code>. 3892 * If <code>children</code> is an array or vector all of its 3893 * elements are added is children, otherwise if <code>children</code> 3894 * is a hashtable all the key/value pairs are added in the order 3895 * <code>Enumeration</code> returns them. 3896 */ 3897 public static void createChildren(DefaultMutableTreeNode parent, 3898 Object children) { 3899 if(children instanceof Vector) { 3900 Vector<?> childVector = (Vector)children; 3901 3902 for(int counter = 0, maxCounter = childVector.size(); 3903 counter < maxCounter; counter++) 3904 parent.add(new DynamicUtilTreeNode 3905 (childVector.elementAt(counter), 3906 childVector.elementAt(counter))); 3907 } 3908 else if(children instanceof Hashtable) { 3909 Hashtable<?,?> childHT = (Hashtable)children; 3910 Enumeration<?> keys = childHT.keys(); 3911 Object aKey; 3912 3913 while(keys.hasMoreElements()) { 3914 aKey = keys.nextElement(); 3915 parent.add(new DynamicUtilTreeNode(aKey, 3916 childHT.get(aKey))); 3917 } 3918 } 3919 else if(children instanceof Object[]) { 3920 Object[] childArray = (Object[])children; 3921 3922 for(int counter = 0, maxCounter = childArray.length; 3923 counter < maxCounter; counter++) 3924 parent.add(new DynamicUtilTreeNode(childArray[counter], 3925 childArray[counter])); 3926 } 3927 } 3928 3929 /** 3930 * Creates a node with the specified object as its value and 3931 * with the specified children. For the node to allow children, 3932 * the children-object must be an array of objects, a 3933 * <code>Vector</code>, or a <code>Hashtable</code> -- even 3934 * if empty. Otherwise, the node is not 3935 * allowed to have children. 3936 * 3937 * @param value the <code>Object</code> that is the value for the 3938 * new node 3939 * @param children an array of <code>Object</code>s, a 3940 * <code>Vector</code>, or a <code>Hashtable</code> 3941 * used to create the child nodes; if any other 3942 * object is specified, or if the value is 3943 * <code>null</code>, 3944 * then the node is not allowed to have children 3945 */ 3946 public DynamicUtilTreeNode(Object value, Object children) { 3947 super(value); 3948 loadedChildren = false; 3949 childValue = children; 3950 if(children != null) { 3951 if(children instanceof Vector) 3952 setAllowsChildren(true); 3953 else if(children instanceof Hashtable) 3954 setAllowsChildren(true); 3955 else if(children instanceof Object[]) 3956 setAllowsChildren(true); 3957 else 3958 setAllowsChildren(false); 3959 } 3960 else 3961 setAllowsChildren(false); 3962 } 3963 3964 /** 3965 * Returns true if this node allows children. Whether the node 3966 * allows children depends on how it was created. 3967 * 3968 * @return true if this node allows children, false otherwise 3969 * @see JTree.DynamicUtilTreeNode 3970 */ 3971 public boolean isLeaf() { 3972 return !getAllowsChildren(); 3973 } 3974 3975 /** 3976 * Returns the number of child nodes. 3977 * 3978 * @return the number of child nodes 3979 */ 3980 public int getChildCount() { 3981 if(!loadedChildren) 3982 loadChildren(); 3983 return super.getChildCount(); 3984 } 3985 3986 /** 3987 * Loads the children based on <code>childValue</code>. 3988 * If <code>childValue</code> is a <code>Vector</code> 3989 * or array each element is added as a child, 3990 * if <code>childValue</code> is a <code>Hashtable</code> 3991 * each key/value pair is added in the order that 3992 * <code>Enumeration</code> returns the keys. 3993 */ 3994 protected void loadChildren() { 3995 loadedChildren = true; 3996 createChildren(this, childValue); 3997 } 3998 3999 /** 4000 * Subclassed to load the children, if necessary. 4001 */ 4002 public TreeNode getChildAt(int index) { 4003 if(!loadedChildren) 4004 loadChildren(); 4005 return super.getChildAt(index); 4006 } 4007 4008 /** 4009 * Subclassed to load the children, if necessary. 4010 */ 4011 public Enumeration children() { 4012 if(!loadedChildren) 4013 loadChildren(); 4014 return super.children(); 4015 } 4016 } 4017 4018 void setUIProperty(String propertyName, Object value) { 4019 if (propertyName == "rowHeight") { 4020 if (!rowHeightSet) { 4021 setRowHeight(((Number)value).intValue()); 4022 rowHeightSet = false; 4023 } 4024 } else if (propertyName == "scrollsOnExpand") { 4025 if (!scrollsOnExpandSet) { 4026 setScrollsOnExpand(((Boolean)value).booleanValue()); 4027 scrollsOnExpandSet = false; 4028 } 4029 } else if (propertyName == "showsRootHandles") { 4030 if (!showsRootHandlesSet) { 4031 setShowsRootHandles(((Boolean)value).booleanValue()); 4032 showsRootHandlesSet = false; 4033 } 4034 } else { 4035 super.setUIProperty(propertyName, value); 4036 } 4037 } 4038 4039 4040 /** 4041 * Returns a string representation of this <code>JTree</code>. 4042 * This method 4043 * is intended to be used only for debugging purposes, and the 4044 * content and format of the returned string may vary between 4045 * implementations. The returned string may be empty but may not 4046 * be <code>null</code>. 4047 * 4048 * @return a string representation of this <code>JTree</code>. 4049 */ 4050 protected String paramString() { 4051 String rootVisibleString = (rootVisible ? 4052 "true" : "false"); 4053 String showsRootHandlesString = (showsRootHandles ? 4054 "true" : "false"); 4055 String editableString = (editable ? 4056 "true" : "false"); 4057 String largeModelString = (largeModel ? 4058 "true" : "false"); 4059 String invokesStopCellEditingString = (invokesStopCellEditing ? 4060 "true" : "false"); 4061 String scrollsOnExpandString = (scrollsOnExpand ? 4062 "true" : "false"); 4063 4064 return super.paramString() + 4065 ",editable=" + editableString + 4066 ",invokesStopCellEditing=" + invokesStopCellEditingString + 4067 ",largeModel=" + largeModelString + 4068 ",rootVisible=" + rootVisibleString + 4069 ",rowHeight=" + rowHeight + 4070 ",scrollsOnExpand=" + scrollsOnExpandString + 4071 ",showsRootHandles=" + showsRootHandlesString + 4072 ",toggleClickCount=" + toggleClickCount + 4073 ",visibleRowCount=" + visibleRowCount; 4074 } 4075 4076 ///////////////// 4077 // Accessibility support 4078 //////////////// 4079 4080 /** 4081 * Gets the AccessibleContext associated with this JTree. 4082 * For JTrees, the AccessibleContext takes the form of an 4083 * AccessibleJTree. 4084 * A new AccessibleJTree instance is created if necessary. 4085 * 4086 * @return an AccessibleJTree that serves as the 4087 * AccessibleContext of this JTree 4088 */ 4089 public AccessibleContext getAccessibleContext() { 4090 if (accessibleContext == null) { 4091 accessibleContext = new AccessibleJTree(); 4092 } 4093 return accessibleContext; 4094 } 4095 4096 /** 4097 * This class implements accessibility support for the 4098 * <code>JTree</code> class. It provides an implementation of the 4099 * Java Accessibility API appropriate to tree user-interface elements. 4100 * <p> 4101 * <strong>Warning:</strong> 4102 * Serialized objects of this class will not be compatible with 4103 * future Swing releases. The current serialization support is 4104 * appropriate for short term storage or RMI between applications running 4105 * the same version of Swing. As of 1.4, support for long term storage 4106 * of all JavaBeans™ 4107 * has been added to the <code>java.beans</code> package. 4108 * Please see {@link java.beans.XMLEncoder}. 4109 */ 4110 @SuppressWarnings("serial") 4111 protected class AccessibleJTree extends AccessibleJComponent 4112 implements AccessibleSelection, TreeSelectionListener, 4113 TreeModelListener, TreeExpansionListener { 4114 4115 TreePath leadSelectionPath; 4116 Accessible leadSelectionAccessible; 4117 4118 public AccessibleJTree() { 4119 // Add a tree model listener for JTree 4120 TreeModel model = JTree.this.getModel(); 4121 if (model != null) { 4122 model.addTreeModelListener(this); 4123 } 4124 JTree.this.addTreeExpansionListener(this); 4125 JTree.this.addTreeSelectionListener(this); 4126 leadSelectionPath = JTree.this.getLeadSelectionPath(); 4127 leadSelectionAccessible = (leadSelectionPath != null) 4128 ? new AccessibleJTreeNode(JTree.this, 4129 leadSelectionPath, 4130 JTree.this) 4131 : null; 4132 } 4133 4134 /** 4135 * Tree Selection Listener value change method. Used to fire the 4136 * property change 4137 * 4138 * @param e ListSelectionEvent 4139 * 4140 */ 4141 public void valueChanged(TreeSelectionEvent e) { 4142 firePropertyChange(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY, 4143 Boolean.valueOf(false), Boolean.valueOf(true)); 4144 } 4145 4146 /** 4147 * Fire a visible data property change notification. 4148 * A 'visible' data property is one that represents 4149 * something about the way the component appears on the 4150 * display, where that appearance isn't bound to any other 4151 * property. It notifies screen readers that the visual 4152 * appearance of the component has changed, so they can 4153 * notify the user. 4154 */ 4155 public void fireVisibleDataPropertyChange() { 4156 firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, 4157 Boolean.valueOf(false), Boolean.valueOf(true)); 4158 } 4159 4160 // Fire the visible data changes for the model changes. 4161 4162 /** 4163 * Tree Model Node change notification. 4164 * 4165 * @param e a Tree Model event 4166 */ 4167 public void treeNodesChanged(TreeModelEvent e) { 4168 fireVisibleDataPropertyChange(); 4169 } 4170 4171 /** 4172 * Tree Model Node change notification. 4173 * 4174 * @param e a Tree node insertion event 4175 */ 4176 public void treeNodesInserted(TreeModelEvent e) { 4177 fireVisibleDataPropertyChange(); 4178 } 4179 4180 /** 4181 * Tree Model Node change notification. 4182 * 4183 * @param e a Tree node(s) removal event 4184 */ 4185 public void treeNodesRemoved(TreeModelEvent e) { 4186 fireVisibleDataPropertyChange(); 4187 } 4188 4189 /** 4190 * Tree Model structure change change notification. 4191 * 4192 * @param e a Tree Model event 4193 */ 4194 public void treeStructureChanged(TreeModelEvent e) { 4195 fireVisibleDataPropertyChange(); 4196 } 4197 4198 /** 4199 * Tree Collapsed notification. 4200 * 4201 * @param e a TreeExpansionEvent 4202 */ 4203 public void treeCollapsed(TreeExpansionEvent e) { 4204 fireVisibleDataPropertyChange(); 4205 TreePath path = e.getPath(); 4206 if (path != null) { 4207 // Set parent to null so AccessibleJTreeNode computes 4208 // its parent. 4209 AccessibleJTreeNode node = new AccessibleJTreeNode(JTree.this, 4210 path, 4211 null); 4212 PropertyChangeEvent pce = new PropertyChangeEvent(node, 4213 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 4214 AccessibleState.EXPANDED, 4215 AccessibleState.COLLAPSED); 4216 firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 4217 null, pce); 4218 } 4219 } 4220 4221 /** 4222 * Tree Model Expansion notification. 4223 * 4224 * @param e a Tree node insertion event 4225 */ 4226 public void treeExpanded(TreeExpansionEvent e) { 4227 fireVisibleDataPropertyChange(); 4228 TreePath path = e.getPath(); 4229 if (path != null) { 4230 // TIGER - 4839971 4231 // Set parent to null so AccessibleJTreeNode computes 4232 // its parent. 4233 AccessibleJTreeNode node = new AccessibleJTreeNode(JTree.this, 4234 path, 4235 null); 4236 PropertyChangeEvent pce = new PropertyChangeEvent(node, 4237 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 4238 AccessibleState.COLLAPSED, 4239 AccessibleState.EXPANDED); 4240 firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 4241 null, pce); 4242 } 4243 } 4244 4245 /** 4246 * Fire an active descendant property change notification. 4247 * The active descendant is used for objects such as list, 4248 * tree, and table, which may have transient children. 4249 * It notifies screen readers the active child of the component 4250 * has been changed so user can be notified from there. 4251 * 4252 * @param oldPath - lead path of previous active child 4253 * @param newPath - lead path of current active child 4254 * 4255 */ 4256 void fireActiveDescendantPropertyChange(TreePath oldPath, TreePath newPath){ 4257 if(oldPath != newPath){ 4258 Accessible oldLSA = (oldPath != null) 4259 ? new AccessibleJTreeNode(JTree.this, 4260 oldPath, 4261 null) 4262 : null; 4263 4264 Accessible newLSA = (newPath != null) 4265 ? new AccessibleJTreeNode(JTree.this, 4266 newPath, 4267 null) 4268 : null; 4269 firePropertyChange(AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY, 4270 oldLSA, newLSA); 4271 } 4272 } 4273 4274 private AccessibleContext getCurrentAccessibleContext() { 4275 Component c = getCurrentComponent(); 4276 if (c instanceof Accessible) { 4277 return c.getAccessibleContext(); 4278 } else { 4279 return null; 4280 } 4281 } 4282 4283 private Component getCurrentComponent() { 4284 // is the object visible? 4285 // if so, get row, selected, focus & leaf state, 4286 // and then get the renderer component and return it 4287 TreeModel model = JTree.this.getModel(); 4288 if (model == null) { 4289 return null; 4290 } 4291 Object treeRoot = model.getRoot(); 4292 if (treeRoot == null) 4293 return null; 4294 4295 TreePath path = new TreePath(treeRoot); 4296 if (JTree.this.isVisible(path)) { 4297 TreeCellRenderer r = JTree.this.getCellRenderer(); 4298 TreeUI ui = JTree.this.getUI(); 4299 if (ui != null) { 4300 int row = ui.getRowForPath(JTree.this, path); 4301 int lsr = JTree.this.getLeadSelectionRow(); 4302 boolean hasFocus = JTree.this.isFocusOwner() 4303 && (lsr == row); 4304 boolean selected = JTree.this.isPathSelected(path); 4305 boolean expanded = JTree.this.isExpanded(path); 4306 4307 return r.getTreeCellRendererComponent(JTree.this, 4308 treeRoot, selected, expanded, 4309 model.isLeaf(treeRoot), row, hasFocus); 4310 } 4311 } 4312 return null; 4313 } 4314 4315 // Overridden methods from AccessibleJComponent 4316 4317 /** 4318 * Get the role of this object. 4319 * 4320 * @return an instance of AccessibleRole describing the role of the 4321 * object 4322 * @see AccessibleRole 4323 */ 4324 public AccessibleRole getAccessibleRole() { 4325 return AccessibleRole.TREE; 4326 } 4327 4328 /** 4329 * Returns the <code>Accessible</code> child, if one exists, 4330 * contained at the local coordinate <code>Point</code>. 4331 * Otherwise returns <code>null</code>. 4332 * 4333 * @param p point in local coordinates of this <code>Accessible</code> 4334 * @return the <code>Accessible</code>, if it exists, 4335 * at the specified location; else <code>null</code> 4336 */ 4337 public Accessible getAccessibleAt(Point p) { 4338 TreePath path = getClosestPathForLocation(p.x, p.y); 4339 if (path != null) { 4340 // JTree.this is NOT the parent; parent will get computed later 4341 return new AccessibleJTreeNode(JTree.this, path, null); 4342 } else { 4343 return null; 4344 } 4345 } 4346 4347 /** 4348 * Returns the number of top-level children nodes of this 4349 * JTree. Each of these nodes may in turn have children nodes. 4350 * 4351 * @return the number of accessible children nodes in the tree. 4352 */ 4353 public int getAccessibleChildrenCount() { 4354 TreeModel model = JTree.this.getModel(); 4355 if (model == null) { 4356 return 0; 4357 } 4358 if (isRootVisible()) { 4359 return 1; // the root node 4360 } 4361 4362 Object treeRoot = model.getRoot(); 4363 if (treeRoot == null) 4364 return 0; 4365 // return the root's first set of children count 4366 return model.getChildCount(treeRoot); 4367 } 4368 4369 /** 4370 * Return the nth Accessible child of the object. 4371 * 4372 * @param i zero-based index of child 4373 * @return the nth Accessible child of the object 4374 */ 4375 public Accessible getAccessibleChild(int i) { 4376 TreeModel model = JTree.this.getModel(); 4377 if (model == null) { 4378 return null; 4379 } 4380 4381 Object treeRoot = model.getRoot(); 4382 if (treeRoot == null) { 4383 return null; 4384 } 4385 4386 if (isRootVisible()) { 4387 if (i == 0) { // return the root node Accessible 4388 Object[] objPath = { treeRoot }; 4389 if (objPath[0] == null) 4390 return null; 4391 TreePath path = new TreePath(objPath); 4392 return new AccessibleJTreeNode(JTree.this, path, JTree.this); 4393 } else { 4394 return null; 4395 } 4396 } 4397 4398 // return Accessible for one of root's child nodes 4399 int count = model.getChildCount(treeRoot); 4400 if (i < 0 || i >= count) { 4401 return null; 4402 } 4403 Object obj = model.getChild(treeRoot, i); 4404 if (obj == null) 4405 return null; 4406 Object[] objPath = { treeRoot, obj }; 4407 TreePath path = new TreePath(objPath); 4408 return new AccessibleJTreeNode(JTree.this, path, JTree.this); 4409 } 4410 4411 /** 4412 * Get the index of this object in its accessible parent. 4413 * 4414 * @return the index of this object in its parent. Since a JTree 4415 * top-level object does not have an accessible parent. 4416 * @see #getAccessibleParent 4417 */ 4418 public int getAccessibleIndexInParent() { 4419 // didn't ever need to override this... 4420 return super.getAccessibleIndexInParent(); 4421 } 4422 4423 // AccessibleSelection methods 4424 /** 4425 * Get the AccessibleSelection associated with this object. In the 4426 * implementation of the Java Accessibility API for this class, 4427 * return this object, which is responsible for implementing the 4428 * AccessibleSelection interface on behalf of itself. 4429 * 4430 * @return this object 4431 */ 4432 public AccessibleSelection getAccessibleSelection() { 4433 return this; 4434 } 4435 4436 /** 4437 * Returns the number of items currently selected. 4438 * If no items are selected, the return value will be 0. 4439 * 4440 * @return the number of items currently selected. 4441 */ 4442 public int getAccessibleSelectionCount() { 4443 Object[] rootPath = new Object[1]; 4444 rootPath[0] = treeModel.getRoot(); 4445 if (rootPath[0] == null) 4446 return 0; 4447 4448 TreePath childPath = new TreePath(rootPath); 4449 if (JTree.this.isPathSelected(childPath)) { 4450 return 1; 4451 } else { 4452 return 0; 4453 } 4454 } 4455 4456 /** 4457 * Returns an Accessible representing the specified selected item 4458 * in the object. If there isn't a selection, or there are 4459 * fewer items selected than the integer passed in, the return 4460 * value will be null. 4461 * 4462 * @param i the zero-based index of selected items 4463 * @return an Accessible containing the selected item 4464 */ 4465 public Accessible getAccessibleSelection(int i) { 4466 // The JTree can have only one accessible child, the root. 4467 if (i == 0) { 4468 Object[] rootPath = new Object[1]; 4469 rootPath[0] = treeModel.getRoot(); 4470 if (rootPath[0] == null) 4471 return null; 4472 TreePath childPath = new TreePath(rootPath); 4473 if (JTree.this.isPathSelected(childPath)) { 4474 return new AccessibleJTreeNode(JTree.this, childPath, JTree.this); 4475 } 4476 } 4477 return null; 4478 } 4479 4480 /** 4481 * Returns true if the current child of this object is selected. 4482 * 4483 * @param i the zero-based index of the child in this Accessible object. 4484 * @see AccessibleContext#getAccessibleChild 4485 */ 4486 public boolean isAccessibleChildSelected(int i) { 4487 // The JTree can have only one accessible child, the root. 4488 if (i == 0) { 4489 Object[] rootPath = new Object[1]; 4490 rootPath[0] = treeModel.getRoot(); 4491 if (rootPath[0] == null) 4492 return false; 4493 TreePath childPath = new TreePath(rootPath); 4494 return JTree.this.isPathSelected(childPath); 4495 } else { 4496 return false; 4497 } 4498 } 4499 4500 /** 4501 * Adds the specified selected item in the object to the object's 4502 * selection. If the object supports multiple selections, 4503 * the specified item is added to any existing selection, otherwise 4504 * it replaces any existing selection in the object. If the 4505 * specified item is already selected, this method has no effect. 4506 * 4507 * @param i the zero-based index of selectable items 4508 */ 4509 public void addAccessibleSelection(int i) { 4510 TreeModel model = JTree.this.getModel(); 4511 if (model != null) { 4512 if (i == 0) { 4513 Object[] objPath = {model.getRoot()}; 4514 if (objPath[0] == null) 4515 return; 4516 TreePath path = new TreePath(objPath); 4517 JTree.this.addSelectionPath(path); 4518 } 4519 } 4520 } 4521 4522 /** 4523 * Removes the specified selected item in the object from the object's 4524 * selection. If the specified item isn't currently selected, this 4525 * method has no effect. 4526 * 4527 * @param i the zero-based index of selectable items 4528 */ 4529 public void removeAccessibleSelection(int i) { 4530 TreeModel model = JTree.this.getModel(); 4531 if (model != null) { 4532 if (i == 0) { 4533 Object[] objPath = {model.getRoot()}; 4534 if (objPath[0] == null) 4535 return; 4536 TreePath path = new TreePath(objPath); 4537 JTree.this.removeSelectionPath(path); 4538 } 4539 } 4540 } 4541 4542 /** 4543 * Clears the selection in the object, so that nothing in the 4544 * object is selected. 4545 */ 4546 public void clearAccessibleSelection() { 4547 int childCount = getAccessibleChildrenCount(); 4548 for (int i = 0; i < childCount; i++) { 4549 removeAccessibleSelection(i); 4550 } 4551 } 4552 4553 /** 4554 * Causes every selected item in the object to be selected 4555 * if the object supports multiple selections. 4556 */ 4557 public void selectAllAccessibleSelection() { 4558 TreeModel model = JTree.this.getModel(); 4559 if (model != null) { 4560 Object[] objPath = {model.getRoot()}; 4561 if (objPath[0] == null) 4562 return; 4563 TreePath path = new TreePath(objPath); 4564 JTree.this.addSelectionPath(path); 4565 } 4566 } 4567 4568 /** 4569 * This class implements accessibility support for the 4570 * <code>JTree</code> child. It provides an implementation of the 4571 * Java Accessibility API appropriate to tree nodes. 4572 */ 4573 protected class AccessibleJTreeNode extends AccessibleContext 4574 implements Accessible, AccessibleComponent, AccessibleSelection, 4575 AccessibleAction { 4576 4577 private JTree tree = null; 4578 private TreeModel treeModel = null; 4579 private Object obj = null; 4580 private TreePath path = null; 4581 private Accessible accessibleParent = null; 4582 private int index = 0; 4583 private boolean isLeaf = false; 4584 4585 /** 4586 * Constructs an AccessibleJTreeNode 4587 * @since 1.4 4588 */ 4589 public AccessibleJTreeNode(JTree t, TreePath p, Accessible ap) { 4590 tree = t; 4591 path = p; 4592 accessibleParent = ap; 4593 treeModel = t.getModel(); 4594 obj = p.getLastPathComponent(); 4595 if (treeModel != null) { 4596 isLeaf = treeModel.isLeaf(obj); 4597 } 4598 } 4599 4600 private TreePath getChildTreePath(int i) { 4601 // Tree nodes can't be so complex that they have 4602 // two sets of children -> we're ignoring that case 4603 if (i < 0 || i >= getAccessibleChildrenCount()) { 4604 return null; 4605 } else { 4606 Object childObj = treeModel.getChild(obj, i); 4607 Object[] objPath = path.getPath(); 4608 Object[] objChildPath = new Object[objPath.length+1]; 4609 java.lang.System.arraycopy(objPath, 0, objChildPath, 0, objPath.length); 4610 objChildPath[objChildPath.length-1] = childObj; 4611 return new TreePath(objChildPath); 4612 } 4613 } 4614 4615 /** 4616 * Get the AccessibleContext associated with this tree node. 4617 * In the implementation of the Java Accessibility API for 4618 * this class, return this object, which is its own 4619 * AccessibleContext. 4620 * 4621 * @return this object 4622 */ 4623 public AccessibleContext getAccessibleContext() { 4624 return this; 4625 } 4626 4627 private AccessibleContext getCurrentAccessibleContext() { 4628 Component c = getCurrentComponent(); 4629 if (c instanceof Accessible) { 4630 return c.getAccessibleContext(); 4631 } else { 4632 return null; 4633 } 4634 } 4635 4636 private Component getCurrentComponent() { 4637 // is the object visible? 4638 // if so, get row, selected, focus & leaf state, 4639 // and then get the renderer component and return it 4640 if (tree.isVisible(path)) { 4641 TreeCellRenderer r = tree.getCellRenderer(); 4642 if (r == null) { 4643 return null; 4644 } 4645 TreeUI ui = tree.getUI(); 4646 if (ui != null) { 4647 int row = ui.getRowForPath(JTree.this, path); 4648 boolean selected = tree.isPathSelected(path); 4649 boolean expanded = tree.isExpanded(path); 4650 boolean hasFocus = false; // how to tell?? -PK 4651 return r.getTreeCellRendererComponent(tree, obj, 4652 selected, expanded, isLeaf, row, hasFocus); 4653 } 4654 } 4655 return null; 4656 } 4657 4658 // AccessibleContext methods 4659 4660 /** 4661 * Get the accessible name of this object. 4662 * 4663 * @return the localized name of the object; null if this 4664 * object does not have a name 4665 */ 4666 public String getAccessibleName() { 4667 AccessibleContext ac = getCurrentAccessibleContext(); 4668 if (ac != null) { 4669 String name = ac.getAccessibleName(); 4670 if ((name != null) && (name != "")) { 4671 return ac.getAccessibleName(); 4672 } else { 4673 return null; 4674 } 4675 } 4676 if ((accessibleName != null) && (accessibleName != "")) { 4677 return accessibleName; 4678 } else { 4679 // fall back to the client property 4680 return (String)getClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY); 4681 } 4682 } 4683 4684 /** 4685 * Set the localized accessible name of this object. 4686 * 4687 * @param s the new localized name of the object. 4688 */ 4689 public void setAccessibleName(String s) { 4690 AccessibleContext ac = getCurrentAccessibleContext(); 4691 if (ac != null) { 4692 ac.setAccessibleName(s); 4693 } else { 4694 super.setAccessibleName(s); 4695 } 4696 } 4697 4698 // 4699 // *** should check tooltip text for desc. (needs MouseEvent) 4700 // 4701 /** 4702 * Get the accessible description of this object. 4703 * 4704 * @return the localized description of the object; null if 4705 * this object does not have a description 4706 */ 4707 public String getAccessibleDescription() { 4708 AccessibleContext ac = getCurrentAccessibleContext(); 4709 if (ac != null) { 4710 return ac.getAccessibleDescription(); 4711 } else { 4712 return super.getAccessibleDescription(); 4713 } 4714 } 4715 4716 /** 4717 * Set the accessible description of this object. 4718 * 4719 * @param s the new localized description of the object 4720 */ 4721 public void setAccessibleDescription(String s) { 4722 AccessibleContext ac = getCurrentAccessibleContext(); 4723 if (ac != null) { 4724 ac.setAccessibleDescription(s); 4725 } else { 4726 super.setAccessibleDescription(s); 4727 } 4728 } 4729 4730 /** 4731 * Get the role of this object. 4732 * 4733 * @return an instance of AccessibleRole describing the role of the object 4734 * @see AccessibleRole 4735 */ 4736 public AccessibleRole getAccessibleRole() { 4737 AccessibleContext ac = getCurrentAccessibleContext(); 4738 if (ac != null) { 4739 return ac.getAccessibleRole(); 4740 } else { 4741 return AccessibleRole.UNKNOWN; 4742 } 4743 } 4744 4745 /** 4746 * Get the state set of this object. 4747 * 4748 * @return an instance of AccessibleStateSet containing the 4749 * current state set of the object 4750 * @see AccessibleState 4751 */ 4752 public AccessibleStateSet getAccessibleStateSet() { 4753 AccessibleContext ac = getCurrentAccessibleContext(); 4754 AccessibleStateSet states; 4755 if (ac != null) { 4756 states = ac.getAccessibleStateSet(); 4757 } else { 4758 states = new AccessibleStateSet(); 4759 } 4760 // need to test here, 'cause the underlying component 4761 // is a cellRenderer, which is never showing... 4762 if (isShowing()) { 4763 states.add(AccessibleState.SHOWING); 4764 } else if (states.contains(AccessibleState.SHOWING)) { 4765 states.remove(AccessibleState.SHOWING); 4766 } 4767 if (isVisible()) { 4768 states.add(AccessibleState.VISIBLE); 4769 } else if (states.contains(AccessibleState.VISIBLE)) { 4770 states.remove(AccessibleState.VISIBLE); 4771 } 4772 if (tree.isPathSelected(path)){ 4773 states.add(AccessibleState.SELECTED); 4774 } 4775 if (path == getLeadSelectionPath()) { 4776 states.add(AccessibleState.ACTIVE); 4777 } 4778 if (!isLeaf) { 4779 states.add(AccessibleState.EXPANDABLE); 4780 } 4781 if (tree.isExpanded(path)) { 4782 states.add(AccessibleState.EXPANDED); 4783 } else { 4784 states.add(AccessibleState.COLLAPSED); 4785 } 4786 if (tree.isEditable()) { 4787 states.add(AccessibleState.EDITABLE); 4788 } 4789 return states; 4790 } 4791 4792 /** 4793 * Get the Accessible parent of this object. 4794 * 4795 * @return the Accessible parent of this object; null if this 4796 * object does not have an Accessible parent 4797 */ 4798 public Accessible getAccessibleParent() { 4799 // someone wants to know, so we need to create our parent 4800 // if we don't have one (hey, we're a talented kid!) 4801 if (accessibleParent == null) { 4802 Object[] objPath = path.getPath(); 4803 if (objPath.length > 1) { 4804 Object objParent = objPath[objPath.length-2]; 4805 if (treeModel != null) { 4806 index = treeModel.getIndexOfChild(objParent, obj); 4807 } 4808 Object[] objParentPath = new Object[objPath.length-1]; 4809 java.lang.System.arraycopy(objPath, 0, objParentPath, 4810 0, objPath.length-1); 4811 TreePath parentPath = new TreePath(objParentPath); 4812 accessibleParent = new AccessibleJTreeNode(tree, 4813 parentPath, 4814 null); 4815 this.setAccessibleParent(accessibleParent); 4816 } else if (treeModel != null) { 4817 accessibleParent = tree; // we're the top! 4818 index = 0; // we're an only child! 4819 this.setAccessibleParent(accessibleParent); 4820 } 4821 } 4822 return accessibleParent; 4823 } 4824 4825 /** 4826 * Get the index of this object in its accessible parent. 4827 * 4828 * @return the index of this object in its parent; -1 if this 4829 * object does not have an accessible parent. 4830 * @see #getAccessibleParent 4831 */ 4832 public int getAccessibleIndexInParent() { 4833 // index is invalid 'till we have an accessibleParent... 4834 if (accessibleParent == null) { 4835 getAccessibleParent(); 4836 } 4837 Object[] objPath = path.getPath(); 4838 if (objPath.length > 1) { 4839 Object objParent = objPath[objPath.length-2]; 4840 if (treeModel != null) { 4841 index = treeModel.getIndexOfChild(objParent, obj); 4842 } 4843 } 4844 return index; 4845 } 4846 4847 /** 4848 * Returns the number of accessible children in the object. 4849 * 4850 * @return the number of accessible children in the object. 4851 */ 4852 public int getAccessibleChildrenCount() { 4853 // Tree nodes can't be so complex that they have 4854 // two sets of children -> we're ignoring that case 4855 return treeModel.getChildCount(obj); 4856 } 4857 4858 /** 4859 * Return the specified Accessible child of the object. 4860 * 4861 * @param i zero-based index of child 4862 * @return the Accessible child of the object 4863 */ 4864 public Accessible getAccessibleChild(int i) { 4865 // Tree nodes can't be so complex that they have 4866 // two sets of children -> we're ignoring that case 4867 if (i < 0 || i >= getAccessibleChildrenCount()) { 4868 return null; 4869 } else { 4870 Object childObj = treeModel.getChild(obj, i); 4871 Object[] objPath = path.getPath(); 4872 Object[] objChildPath = new Object[objPath.length+1]; 4873 java.lang.System.arraycopy(objPath, 0, objChildPath, 0, objPath.length); 4874 objChildPath[objChildPath.length-1] = childObj; 4875 TreePath childPath = new TreePath(objChildPath); 4876 return new AccessibleJTreeNode(JTree.this, childPath, this); 4877 } 4878 } 4879 4880 /** 4881 * Gets the locale of the component. If the component does not have 4882 * a locale, then the locale of its parent is returned. 4883 * 4884 * @return This component's locale. If this component does not have 4885 * a locale, the locale of its parent is returned. 4886 * @exception IllegalComponentStateException 4887 * If the Component does not have its own locale and has not yet 4888 * been added to a containment hierarchy such that the locale can be 4889 * determined from the containing parent. 4890 * @see #setLocale 4891 */ 4892 public Locale getLocale() { 4893 AccessibleContext ac = getCurrentAccessibleContext(); 4894 if (ac != null) { 4895 return ac.getLocale(); 4896 } else { 4897 return tree.getLocale(); 4898 } 4899 } 4900 4901 /** 4902 * Add a PropertyChangeListener to the listener list. 4903 * The listener is registered for all properties. 4904 * 4905 * @param l The PropertyChangeListener to be added 4906 */ 4907 public void addPropertyChangeListener(PropertyChangeListener l) { 4908 AccessibleContext ac = getCurrentAccessibleContext(); 4909 if (ac != null) { 4910 ac.addPropertyChangeListener(l); 4911 } else { 4912 super.addPropertyChangeListener(l); 4913 } 4914 } 4915 4916 /** 4917 * Remove a PropertyChangeListener from the listener list. 4918 * This removes a PropertyChangeListener that was registered 4919 * for all properties. 4920 * 4921 * @param l The PropertyChangeListener to be removed 4922 */ 4923 public void removePropertyChangeListener(PropertyChangeListener l) { 4924 AccessibleContext ac = getCurrentAccessibleContext(); 4925 if (ac != null) { 4926 ac.removePropertyChangeListener(l); 4927 } else { 4928 super.removePropertyChangeListener(l); 4929 } 4930 } 4931 4932 /** 4933 * Get the AccessibleAction associated with this object. In the 4934 * implementation of the Java Accessibility API for this class, 4935 * return this object, which is responsible for implementing the 4936 * AccessibleAction interface on behalf of itself. 4937 * 4938 * @return this object 4939 */ 4940 public AccessibleAction getAccessibleAction() { 4941 return this; 4942 } 4943 4944 /** 4945 * Get the AccessibleComponent associated with this object. In the 4946 * implementation of the Java Accessibility API for this class, 4947 * return this object, which is responsible for implementing the 4948 * AccessibleComponent interface on behalf of itself. 4949 * 4950 * @return this object 4951 */ 4952 public AccessibleComponent getAccessibleComponent() { 4953 return this; // to override getBounds() 4954 } 4955 4956 /** 4957 * Get the AccessibleSelection associated with this object if one 4958 * exists. Otherwise return null. 4959 * 4960 * @return the AccessibleSelection, or null 4961 */ 4962 public AccessibleSelection getAccessibleSelection() { 4963 AccessibleContext ac = getCurrentAccessibleContext(); 4964 if (ac != null && isLeaf) { 4965 return getCurrentAccessibleContext().getAccessibleSelection(); 4966 } else { 4967 return this; 4968 } 4969 } 4970 4971 /** 4972 * Get the AccessibleText associated with this object if one 4973 * exists. Otherwise return null. 4974 * 4975 * @return the AccessibleText, or null 4976 */ 4977 public AccessibleText getAccessibleText() { 4978 AccessibleContext ac = getCurrentAccessibleContext(); 4979 if (ac != null) { 4980 return getCurrentAccessibleContext().getAccessibleText(); 4981 } else { 4982 return null; 4983 } 4984 } 4985 4986 /** 4987 * Get the AccessibleValue associated with this object if one 4988 * exists. Otherwise return null. 4989 * 4990 * @return the AccessibleValue, or null 4991 */ 4992 public AccessibleValue getAccessibleValue() { 4993 AccessibleContext ac = getCurrentAccessibleContext(); 4994 if (ac != null) { 4995 return getCurrentAccessibleContext().getAccessibleValue(); 4996 } else { 4997 return null; 4998 } 4999 } 5000 5001 5002 // AccessibleComponent methods 5003 5004 /** 5005 * Get the background color of this object. 5006 * 5007 * @return the background color, if supported, of the object; 5008 * otherwise, null 5009 */ 5010 public Color getBackground() { 5011 AccessibleContext ac = getCurrentAccessibleContext(); 5012 if (ac instanceof AccessibleComponent) { 5013 return ((AccessibleComponent) ac).getBackground(); 5014 } else { 5015 Component c = getCurrentComponent(); 5016 if (c != null) { 5017 return c.getBackground(); 5018 } else { 5019 return null; 5020 } 5021 } 5022 } 5023 5024 /** 5025 * Set the background color of this object. 5026 * 5027 * @param c the new Color for the background 5028 */ 5029 public void setBackground(Color c) { 5030 AccessibleContext ac = getCurrentAccessibleContext(); 5031 if (ac instanceof AccessibleComponent) { 5032 ((AccessibleComponent) ac).setBackground(c); 5033 } else { 5034 Component cp = getCurrentComponent(); 5035 if (cp != null) { 5036 cp.setBackground(c); 5037 } 5038 } 5039 } 5040 5041 5042 /** 5043 * Get the foreground color of this object. 5044 * 5045 * @return the foreground color, if supported, of the object; 5046 * otherwise, null 5047 */ 5048 public Color getForeground() { 5049 AccessibleContext ac = getCurrentAccessibleContext(); 5050 if (ac instanceof AccessibleComponent) { 5051 return ((AccessibleComponent) ac).getForeground(); 5052 } else { 5053 Component c = getCurrentComponent(); 5054 if (c != null) { 5055 return c.getForeground(); 5056 } else { 5057 return null; 5058 } 5059 } 5060 } 5061 5062 public void setForeground(Color c) { 5063 AccessibleContext ac = getCurrentAccessibleContext(); 5064 if (ac instanceof AccessibleComponent) { 5065 ((AccessibleComponent) ac).setForeground(c); 5066 } else { 5067 Component cp = getCurrentComponent(); 5068 if (cp != null) { 5069 cp.setForeground(c); 5070 } 5071 } 5072 } 5073 5074 public Cursor getCursor() { 5075 AccessibleContext ac = getCurrentAccessibleContext(); 5076 if (ac instanceof AccessibleComponent) { 5077 return ((AccessibleComponent) ac).getCursor(); 5078 } else { 5079 Component c = getCurrentComponent(); 5080 if (c != null) { 5081 return c.getCursor(); 5082 } else { 5083 Accessible ap = getAccessibleParent(); 5084 if (ap instanceof AccessibleComponent) { 5085 return ((AccessibleComponent) ap).getCursor(); 5086 } else { 5087 return null; 5088 } 5089 } 5090 } 5091 } 5092 5093 public void setCursor(Cursor c) { 5094 AccessibleContext ac = getCurrentAccessibleContext(); 5095 if (ac instanceof AccessibleComponent) { 5096 ((AccessibleComponent) ac).setCursor(c); 5097 } else { 5098 Component cp = getCurrentComponent(); 5099 if (cp != null) { 5100 cp.setCursor(c); 5101 } 5102 } 5103 } 5104 5105 public Font getFont() { 5106 AccessibleContext ac = getCurrentAccessibleContext(); 5107 if (ac instanceof AccessibleComponent) { 5108 return ((AccessibleComponent) ac).getFont(); 5109 } else { 5110 Component c = getCurrentComponent(); 5111 if (c != null) { 5112 return c.getFont(); 5113 } else { 5114 return null; 5115 } 5116 } 5117 } 5118 5119 public void setFont(Font f) { 5120 AccessibleContext ac = getCurrentAccessibleContext(); 5121 if (ac instanceof AccessibleComponent) { 5122 ((AccessibleComponent) ac).setFont(f); 5123 } else { 5124 Component c = getCurrentComponent(); 5125 if (c != null) { 5126 c.setFont(f); 5127 } 5128 } 5129 } 5130 5131 public FontMetrics getFontMetrics(Font f) { 5132 AccessibleContext ac = getCurrentAccessibleContext(); 5133 if (ac instanceof AccessibleComponent) { 5134 return ((AccessibleComponent) ac).getFontMetrics(f); 5135 } else { 5136 Component c = getCurrentComponent(); 5137 if (c != null) { 5138 return c.getFontMetrics(f); 5139 } else { 5140 return null; 5141 } 5142 } 5143 } 5144 5145 public boolean isEnabled() { 5146 AccessibleContext ac = getCurrentAccessibleContext(); 5147 if (ac instanceof AccessibleComponent) { 5148 return ((AccessibleComponent) ac).isEnabled(); 5149 } else { 5150 Component c = getCurrentComponent(); 5151 if (c != null) { 5152 return c.isEnabled(); 5153 } else { 5154 return false; 5155 } 5156 } 5157 } 5158 5159 public void setEnabled(boolean b) { 5160 AccessibleContext ac = getCurrentAccessibleContext(); 5161 if (ac instanceof AccessibleComponent) { 5162 ((AccessibleComponent) ac).setEnabled(b); 5163 } else { 5164 Component c = getCurrentComponent(); 5165 if (c != null) { 5166 c.setEnabled(b); 5167 } 5168 } 5169 } 5170 5171 public boolean isVisible() { 5172 Rectangle pathBounds = tree.getPathBounds(path); 5173 Rectangle parentBounds = tree.getVisibleRect(); 5174 return pathBounds != null && parentBounds != null && 5175 parentBounds.intersects(pathBounds); 5176 } 5177 5178 public void setVisible(boolean b) { 5179 } 5180 5181 public boolean isShowing() { 5182 return (tree.isShowing() && isVisible()); 5183 } 5184 5185 public boolean contains(Point p) { 5186 AccessibleContext ac = getCurrentAccessibleContext(); 5187 if (ac instanceof AccessibleComponent) { 5188 Rectangle r = ((AccessibleComponent) ac).getBounds(); 5189 return r.contains(p); 5190 } else { 5191 Component c = getCurrentComponent(); 5192 if (c != null) { 5193 Rectangle r = c.getBounds(); 5194 return r.contains(p); 5195 } else { 5196 return getBounds().contains(p); 5197 } 5198 } 5199 } 5200 5201 public Point getLocationOnScreen() { 5202 if (tree != null) { 5203 Point treeLocation = tree.getLocationOnScreen(); 5204 Rectangle pathBounds = tree.getPathBounds(path); 5205 if (treeLocation != null && pathBounds != null) { 5206 Point nodeLocation = new Point(pathBounds.x, 5207 pathBounds.y); 5208 nodeLocation.translate(treeLocation.x, treeLocation.y); 5209 return nodeLocation; 5210 } else { 5211 return null; 5212 } 5213 } else { 5214 return null; 5215 } 5216 } 5217 5218 protected Point getLocationInJTree() { 5219 Rectangle r = tree.getPathBounds(path); 5220 if (r != null) { 5221 return r.getLocation(); 5222 } else { 5223 return null; 5224 } 5225 } 5226 5227 public Point getLocation() { 5228 Rectangle r = getBounds(); 5229 if (r != null) { 5230 return r.getLocation(); 5231 } else { 5232 return null; 5233 } 5234 } 5235 5236 public void setLocation(Point p) { 5237 } 5238 5239 public Rectangle getBounds() { 5240 Rectangle r = tree.getPathBounds(path); 5241 Accessible parent = getAccessibleParent(); 5242 if (parent != null) { 5243 if (parent instanceof AccessibleJTreeNode) { 5244 Point parentLoc = ((AccessibleJTreeNode) parent).getLocationInJTree(); 5245 if (parentLoc != null && r != null) { 5246 r.translate(-parentLoc.x, -parentLoc.y); 5247 } else { 5248 return null; // not visible! 5249 } 5250 } 5251 } 5252 return r; 5253 } 5254 5255 public void setBounds(Rectangle r) { 5256 AccessibleContext ac = getCurrentAccessibleContext(); 5257 if (ac instanceof AccessibleComponent) { 5258 ((AccessibleComponent) ac).setBounds(r); 5259 } else { 5260 Component c = getCurrentComponent(); 5261 if (c != null) { 5262 c.setBounds(r); 5263 } 5264 } 5265 } 5266 5267 public Dimension getSize() { 5268 return getBounds().getSize(); 5269 } 5270 5271 public void setSize (Dimension d) { 5272 AccessibleContext ac = getCurrentAccessibleContext(); 5273 if (ac instanceof AccessibleComponent) { 5274 ((AccessibleComponent) ac).setSize(d); 5275 } else { 5276 Component c = getCurrentComponent(); 5277 if (c != null) { 5278 c.setSize(d); 5279 } 5280 } 5281 } 5282 5283 /** 5284 * Returns the <code>Accessible</code> child, if one exists, 5285 * contained at the local coordinate <code>Point</code>. 5286 * Otherwise returns <code>null</code>. 5287 * 5288 * @param p point in local coordinates of this 5289 * <code>Accessible</code> 5290 * @return the <code>Accessible</code>, if it exists, 5291 * at the specified location; else <code>null</code> 5292 */ 5293 public Accessible getAccessibleAt(Point p) { 5294 AccessibleContext ac = getCurrentAccessibleContext(); 5295 if (ac instanceof AccessibleComponent) { 5296 return ((AccessibleComponent) ac).getAccessibleAt(p); 5297 } else { 5298 return null; 5299 } 5300 } 5301 5302 @SuppressWarnings("deprecation") 5303 public boolean isFocusTraversable() { 5304 AccessibleContext ac = getCurrentAccessibleContext(); 5305 if (ac instanceof AccessibleComponent) { 5306 return ((AccessibleComponent) ac).isFocusTraversable(); 5307 } else { 5308 Component c = getCurrentComponent(); 5309 if (c != null) { 5310 return c.isFocusTraversable(); 5311 } else { 5312 return false; 5313 } 5314 } 5315 } 5316 5317 public void requestFocus() { 5318 AccessibleContext ac = getCurrentAccessibleContext(); 5319 if (ac instanceof AccessibleComponent) { 5320 ((AccessibleComponent) ac).requestFocus(); 5321 } else { 5322 Component c = getCurrentComponent(); 5323 if (c != null) { 5324 c.requestFocus(); 5325 } 5326 } 5327 } 5328 5329 public void addFocusListener(FocusListener l) { 5330 AccessibleContext ac = getCurrentAccessibleContext(); 5331 if (ac instanceof AccessibleComponent) { 5332 ((AccessibleComponent) ac).addFocusListener(l); 5333 } else { 5334 Component c = getCurrentComponent(); 5335 if (c != null) { 5336 c.addFocusListener(l); 5337 } 5338 } 5339 } 5340 5341 public void removeFocusListener(FocusListener l) { 5342 AccessibleContext ac = getCurrentAccessibleContext(); 5343 if (ac instanceof AccessibleComponent) { 5344 ((AccessibleComponent) ac).removeFocusListener(l); 5345 } else { 5346 Component c = getCurrentComponent(); 5347 if (c != null) { 5348 c.removeFocusListener(l); 5349 } 5350 } 5351 } 5352 5353 // AccessibleSelection methods 5354 5355 /** 5356 * Returns the number of items currently selected. 5357 * If no items are selected, the return value will be 0. 5358 * 5359 * @return the number of items currently selected. 5360 */ 5361 public int getAccessibleSelectionCount() { 5362 int count = 0; 5363 int childCount = getAccessibleChildrenCount(); 5364 for (int i = 0; i < childCount; i++) { 5365 TreePath childPath = getChildTreePath(i); 5366 if (tree.isPathSelected(childPath)) { 5367 count++; 5368 } 5369 } 5370 return count; 5371 } 5372 5373 /** 5374 * Returns an Accessible representing the specified selected item 5375 * in the object. If there isn't a selection, or there are 5376 * fewer items selected than the integer passed in, the return 5377 * value will be null. 5378 * 5379 * @param i the zero-based index of selected items 5380 * @return an Accessible containing the selected item 5381 */ 5382 public Accessible getAccessibleSelection(int i) { 5383 int childCount = getAccessibleChildrenCount(); 5384 if (i < 0 || i >= childCount) { 5385 return null; // out of range 5386 } 5387 int count = 0; 5388 for (int j = 0; j < childCount && i >= count; j++) { 5389 TreePath childPath = getChildTreePath(j); 5390 if (tree.isPathSelected(childPath)) { 5391 if (count == i) { 5392 return new AccessibleJTreeNode(tree, childPath, this); 5393 } else { 5394 count++; 5395 } 5396 } 5397 } 5398 return null; 5399 } 5400 5401 /** 5402 * Returns true if the current child of this object is selected. 5403 * 5404 * @param i the zero-based index of the child in this Accessible 5405 * object. 5406 * @see AccessibleContext#getAccessibleChild 5407 */ 5408 public boolean isAccessibleChildSelected(int i) { 5409 int childCount = getAccessibleChildrenCount(); 5410 if (i < 0 || i >= childCount) { 5411 return false; // out of range 5412 } else { 5413 TreePath childPath = getChildTreePath(i); 5414 return tree.isPathSelected(childPath); 5415 } 5416 } 5417 5418 /** 5419 * Adds the specified selected item in the object to the object's 5420 * selection. If the object supports multiple selections, 5421 * the specified item is added to any existing selection, otherwise 5422 * it replaces any existing selection in the object. If the 5423 * specified item is already selected, this method has no effect. 5424 * 5425 * @param i the zero-based index of selectable items 5426 */ 5427 public void addAccessibleSelection(int i) { 5428 TreeModel model = JTree.this.getModel(); 5429 if (model != null) { 5430 if (i >= 0 && i < getAccessibleChildrenCount()) { 5431 TreePath path = getChildTreePath(i); 5432 JTree.this.addSelectionPath(path); 5433 } 5434 } 5435 } 5436 5437 /** 5438 * Removes the specified selected item in the object from the 5439 * object's 5440 * selection. If the specified item isn't currently selected, this 5441 * method has no effect. 5442 * 5443 * @param i the zero-based index of selectable items 5444 */ 5445 public void removeAccessibleSelection(int i) { 5446 TreeModel model = JTree.this.getModel(); 5447 if (model != null) { 5448 if (i >= 0 && i < getAccessibleChildrenCount()) { 5449 TreePath path = getChildTreePath(i); 5450 JTree.this.removeSelectionPath(path); 5451 } 5452 } 5453 } 5454 5455 /** 5456 * Clears the selection in the object, so that nothing in the 5457 * object is selected. 5458 */ 5459 public void clearAccessibleSelection() { 5460 int childCount = getAccessibleChildrenCount(); 5461 for (int i = 0; i < childCount; i++) { 5462 removeAccessibleSelection(i); 5463 } 5464 } 5465 5466 /** 5467 * Causes every selected item in the object to be selected 5468 * if the object supports multiple selections. 5469 */ 5470 public void selectAllAccessibleSelection() { 5471 TreeModel model = JTree.this.getModel(); 5472 if (model != null) { 5473 int childCount = getAccessibleChildrenCount(); 5474 TreePath path; 5475 for (int i = 0; i < childCount; i++) { 5476 path = getChildTreePath(i); 5477 JTree.this.addSelectionPath(path); 5478 } 5479 } 5480 } 5481 5482 // AccessibleAction methods 5483 5484 /** 5485 * Returns the number of accessible actions available in this 5486 * tree node. If this node is not a leaf, there is at least 5487 * one action (toggle expand), in addition to any available 5488 * on the object behind the TreeCellRenderer. 5489 * 5490 * @return the number of Actions in this object 5491 */ 5492 public int getAccessibleActionCount() { 5493 AccessibleContext ac = getCurrentAccessibleContext(); 5494 if (ac != null) { 5495 AccessibleAction aa = ac.getAccessibleAction(); 5496 if (aa != null) { 5497 return (aa.getAccessibleActionCount() + (isLeaf ? 0 : 1)); 5498 } 5499 } 5500 return isLeaf ? 0 : 1; 5501 } 5502 5503 /** 5504 * Return a description of the specified action of the tree node. 5505 * If this node is not a leaf, there is at least one action 5506 * description (toggle expand), in addition to any available 5507 * on the object behind the TreeCellRenderer. 5508 * 5509 * @param i zero-based index of the actions 5510 * @return a description of the action 5511 */ 5512 public String getAccessibleActionDescription(int i) { 5513 if (i < 0 || i >= getAccessibleActionCount()) { 5514 return null; 5515 } 5516 AccessibleContext ac = getCurrentAccessibleContext(); 5517 if (i == 0) { 5518 // TIGER - 4766636 5519 return AccessibleAction.TOGGLE_EXPAND; 5520 } else if (ac != null) { 5521 AccessibleAction aa = ac.getAccessibleAction(); 5522 if (aa != null) { 5523 return aa.getAccessibleActionDescription(i - 1); 5524 } 5525 } 5526 return null; 5527 } 5528 5529 /** 5530 * Perform the specified Action on the tree node. If this node 5531 * is not a leaf, there is at least one action which can be 5532 * done (toggle expand), in addition to any available on the 5533 * object behind the TreeCellRenderer. 5534 * 5535 * @param i zero-based index of actions 5536 * @return true if the the action was performed; else false. 5537 */ 5538 public boolean doAccessibleAction(int i) { 5539 if (i < 0 || i >= getAccessibleActionCount()) { 5540 return false; 5541 } 5542 AccessibleContext ac = getCurrentAccessibleContext(); 5543 if (i == 0) { 5544 if (JTree.this.isExpanded(path)) { 5545 JTree.this.collapsePath(path); 5546 } else { 5547 JTree.this.expandPath(path); 5548 } 5549 return true; 5550 } else if (ac != null) { 5551 AccessibleAction aa = ac.getAccessibleAction(); 5552 if (aa != null) { 5553 return aa.doAccessibleAction(i - 1); 5554 } 5555 } 5556 return false; 5557 } 5558 5559 } // inner class AccessibleJTreeNode 5560 5561 } // inner class AccessibleJTree 5562 5563 } // End of class JTree