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