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