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