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