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