1 /* 2 * Copyright (c) 1997, 2010, 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<sup><font size="-2">TM</font></sup> 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 1669 /** 1670 * Sets the path identified as the anchor. 1671 * The anchor is not maintained by <code>JTree</code>, rather the UI will 1672 * update it. 1673 * <p> 1674 * This is a bound property. 1675 * 1676 * @param newPath the new anchor path 1677 * @since 1.3 1678 * @beaninfo 1679 * bound: true 1680 * description: Anchor selection path 1681 */ 1682 public void setAnchorSelectionPath(TreePath newPath) { 1683 TreePath oldValue = anchorPath; 1684 1685 anchorPath = newPath; 1686 firePropertyChange(ANCHOR_SELECTION_PATH_PROPERTY, oldValue, newPath); 1687 } 1688 1689 /** 1690 * Selects the node at the specified row in the display. 1691 * 1692 * @param row the row to select, where 0 is the first row in 1693 * the display 1694 */ 1695 public void setSelectionRow(int row) { 1696 int[] rows = { row }; 1697 1698 setSelectionRows(rows); 1699 } 1700 1701 /** 1702 * Selects the nodes corresponding to each of the specified rows 1703 * in the display. If a particular element of <code>rows</code> is 1704 * < 0 or >= <code>getRowCount</code>, it will be ignored. 1705 * If none of the elements 1706 * in <code>rows</code> are valid rows, the selection will 1707 * be cleared. That is it will be as if <code>clearSelection</code> 1708 * was invoked. 1709 * 1710 * @param rows an array of ints specifying the rows to select, 1711 * where 0 indicates the first row in the display 1712 */ 1713 public void setSelectionRows(int[] rows) { 1714 TreeUI ui = getUI(); 1715 1716 if(ui != null && rows != null) { 1717 int numRows = rows.length; 1718 TreePath[] paths = new TreePath[numRows]; 1719 1720 for(int counter = 0; counter < numRows; counter++) { 1721 paths[counter] = ui.getPathForRow(this, rows[counter]); 1722 } 1723 setSelectionPaths(paths); 1724 } 1725 } 1726 1727 /** 1728 * Adds the node identified by the specified <code>TreePath</code> 1729 * to the current selection. If any component of the path isn't 1730 * viewable, and <code>getExpandsSelectedPaths</code> is true it is 1731 * made viewable. 1732 * <p> 1733 * Note that <code>JTree</code> does not allow duplicate nodes to 1734 * exist as children under the same parent -- each sibling must be 1735 * a unique object. 1736 * 1737 * @param path the <code>TreePath</code> to add 1738 */ 1739 public void addSelectionPath(TreePath path) { 1740 getSelectionModel().addSelectionPath(path); 1741 } 1742 1743 /** 1744 * Adds each path in the array of paths to the current selection. If 1745 * any component of any of the paths isn't viewable and 1746 * <code>getExpandsSelectedPaths</code> is true, it is 1747 * made viewable. 1748 * <p> 1749 * Note that <code>JTree</code> does not allow duplicate nodes to 1750 * exist as children under the same parent -- each sibling must be 1751 * a unique object. 1752 * 1753 * @param paths an array of <code>TreePath</code> objects that specifies 1754 * the nodes to add 1755 */ 1756 public void addSelectionPaths(TreePath[] paths) { 1757 getSelectionModel().addSelectionPaths(paths); 1758 } 1759 1760 /** 1761 * Adds the path at the specified row to the current selection. 1762 * 1763 * @param row an integer specifying the row of the node to add, 1764 * where 0 is the first row in the display 1765 */ 1766 public void addSelectionRow(int row) { 1767 int[] rows = { row }; 1768 1769 addSelectionRows(rows); 1770 } 1771 1772 /** 1773 * Adds the paths at each of the specified rows to the current selection. 1774 * 1775 * @param rows an array of ints specifying the rows to add, 1776 * where 0 indicates the first row in the display 1777 */ 1778 public void addSelectionRows(int[] rows) { 1779 TreeUI ui = getUI(); 1780 1781 if(ui != null && rows != null) { 1782 int numRows = rows.length; 1783 TreePath[] paths = new TreePath[numRows]; 1784 1785 for(int counter = 0; counter < numRows; counter++) 1786 paths[counter] = ui.getPathForRow(this, rows[counter]); 1787 addSelectionPaths(paths); 1788 } 1789 } 1790 1791 /** 1792 * Returns the last path component of the selected path. This is 1793 * a convenience method for 1794 * {@code getSelectionModel().getSelectionPath().getLastPathComponent()}. 1795 * This is typically only useful if the selection has one path. 1796 * 1797 * @return the last path component of the selected path, or 1798 * <code>null</code> if nothing is selected 1799 * @see TreePath#getLastPathComponent 1800 */ 1801 public Object getLastSelectedPathComponent() { 1802 TreePath selPath = getSelectionModel().getSelectionPath(); 1803 1804 if(selPath != null) 1805 return selPath.getLastPathComponent(); 1806 return null; 1807 } 1808 1809 /** 1810 * Returns the path identified as the lead. 1811 * @return path identified as the lead 1812 */ 1813 public TreePath getLeadSelectionPath() { 1814 return leadPath; 1815 } 1816 1817 /** 1818 * Returns the path identified as the anchor. 1819 * @return path identified as the anchor 1820 * @since 1.3 1821 */ 1822 public TreePath getAnchorSelectionPath() { 1823 return anchorPath; 1824 } 1825 1826 /** 1827 * Returns the path to the first selected node. 1828 * 1829 * @return the <code>TreePath</code> for the first selected node, 1830 * or <code>null</code> if nothing is currently selected 1831 */ 1832 public TreePath getSelectionPath() { 1833 return getSelectionModel().getSelectionPath(); 1834 } 1835 1836 /** 1837 * Returns the paths of all selected values. 1838 * 1839 * @return an array of <code>TreePath</code> objects indicating the selected 1840 * nodes, or <code>null</code> if nothing is currently selected 1841 */ 1842 public TreePath[] getSelectionPaths() { 1843 TreePath[] selectionPaths = getSelectionModel().getSelectionPaths(); 1844 1845 return (selectionPaths != null && selectionPaths.length > 0) ? selectionPaths : null; 1846 } 1847 1848 /** 1849 * Returns all of the currently selected rows. This method is simply 1850 * forwarded to the <code>TreeSelectionModel</code>. 1851 * If nothing is selected <code>null</code> or an empty array will 1852 * be returned, based on the <code>TreeSelectionModel</code> 1853 * implementation. 1854 * 1855 * @return an array of integers that identifies all currently selected rows 1856 * where 0 is the first row in the display 1857 */ 1858 public int[] getSelectionRows() { 1859 return getSelectionModel().getSelectionRows(); 1860 } 1861 1862 /** 1863 * Returns the number of nodes selected. 1864 * 1865 * @return the number of nodes selected 1866 */ 1867 public int getSelectionCount() { 1868 return selectionModel.getSelectionCount(); 1869 } 1870 1871 /** 1872 * Returns the smallest selected row. If the selection is empty, or 1873 * none of the selected paths are viewable, {@code -1} is returned. 1874 * 1875 * @return the smallest selected row 1876 */ 1877 public int getMinSelectionRow() { 1878 return getSelectionModel().getMinSelectionRow(); 1879 } 1880 1881 /** 1882 * Returns the largest selected row. If the selection is empty, or 1883 * none of the selected paths are viewable, {@code -1} is returned. 1884 * 1885 * @return the largest selected row 1886 */ 1887 public int getMaxSelectionRow() { 1888 return getSelectionModel().getMaxSelectionRow(); 1889 } 1890 1891 /** 1892 * Returns the row index corresponding to the lead path. 1893 * 1894 * @return an integer giving the row index of the lead path, 1895 * where 0 is the first row in the display; or -1 1896 * if <code>leadPath</code> is <code>null</code> 1897 */ 1898 public int getLeadSelectionRow() { 1899 TreePath leadPath = getLeadSelectionPath(); 1900 1901 if (leadPath != null) { 1902 return getRowForPath(leadPath); 1903 } 1904 return -1; 1905 } 1906 1907 /** 1908 * Returns true if the item identified by the path is currently selected. 1909 * 1910 * @param path a <code>TreePath</code> identifying a node 1911 * @return true if the node is selected 1912 */ 1913 public boolean isPathSelected(TreePath path) { 1914 return getSelectionModel().isPathSelected(path); 1915 } 1916 1917 /** 1918 * Returns true if the node identified by row is selected. 1919 * 1920 * @param row an integer specifying a display row, where 0 is the first 1921 * row in the display 1922 * @return true if the node is selected 1923 */ 1924 public boolean isRowSelected(int row) { 1925 return getSelectionModel().isRowSelected(row); 1926 } 1927 1928 /** 1929 * Returns an <code>Enumeration</code> of the descendants of the 1930 * path <code>parent</code> that 1931 * are currently expanded. If <code>parent</code> is not currently 1932 * expanded, this will return <code>null</code>. 1933 * If you expand/collapse nodes while 1934 * iterating over the returned <code>Enumeration</code> 1935 * this may not return all 1936 * the expanded paths, or may return paths that are no longer expanded. 1937 * 1938 * @param parent the path which is to be examined 1939 * @return an <code>Enumeration</code> of the descendents of 1940 * <code>parent</code>, or <code>null</code> if 1941 * <code>parent</code> is not currently expanded 1942 */ 1943 public Enumeration<TreePath> getExpandedDescendants(TreePath parent) { 1944 if(!isExpanded(parent)) 1945 return null; 1946 1947 Enumeration<TreePath> toggledPaths = expandedState.keys(); 1948 Vector<TreePath> elements = null; 1949 TreePath path; 1950 Object value; 1951 1952 if(toggledPaths != null) { 1953 while(toggledPaths.hasMoreElements()) { 1954 path = toggledPaths.nextElement(); 1955 value = expandedState.get(path); 1956 // Add the path if it is expanded, a descendant of parent, 1957 // and it is visible (all parents expanded). This is rather 1958 // expensive! 1959 if(path != parent && value != null && 1960 ((Boolean)value).booleanValue() && 1961 parent.isDescendant(path) && isVisible(path)) { 1962 if (elements == null) { 1963 elements = new Vector<TreePath>(); 1964 } 1965 elements.addElement(path); 1966 } 1967 } 1968 } 1969 if (elements == null) { 1970 Set<TreePath> empty = Collections.emptySet(); 1971 return Collections.enumeration(empty); 1972 } 1973 return elements.elements(); 1974 } 1975 1976 /** 1977 * Returns true if the node identified by the path has ever been 1978 * expanded. 1979 * @return true if the <code>path</code> has ever been expanded 1980 */ 1981 public boolean hasBeenExpanded(TreePath path) { 1982 return (path != null && expandedState.get(path) != null); 1983 } 1984 1985 /** 1986 * Returns true if the node identified by the path is currently expanded, 1987 * 1988 * @param path the <code>TreePath</code> specifying the node to check 1989 * @return false if any of the nodes in the node's path are collapsed, 1990 * true if all nodes in the path are expanded 1991 */ 1992 public boolean isExpanded(TreePath path) { 1993 1994 if(path == null) 1995 return false; 1996 Object value; 1997 1998 do{ 1999 value = expandedState.get(path); 2000 if(value == null || !((Boolean)value).booleanValue()) 2001 return false; 2002 } while( (path=path.getParentPath())!=null ); 2003 2004 return true; 2005 } 2006 2007 /** 2008 * Returns true if the node at the specified display row is currently 2009 * expanded. 2010 * 2011 * @param row the row to check, where 0 is the first row in the 2012 * display 2013 * @return true if the node is currently expanded, otherwise false 2014 */ 2015 public boolean isExpanded(int row) { 2016 TreeUI tree = getUI(); 2017 2018 if(tree != null) { 2019 TreePath path = tree.getPathForRow(this, row); 2020 2021 if(path != null) { 2022 Boolean value = expandedState.get(path); 2023 2024 return (value != null && value.booleanValue()); 2025 } 2026 } 2027 return false; 2028 } 2029 2030 /** 2031 * Returns true if the value identified by path is currently collapsed, 2032 * this will return false if any of the values in path are currently 2033 * not being displayed. 2034 * 2035 * @param path the <code>TreePath</code> to check 2036 * @return true if any of the nodes in the node's path are collapsed, 2037 * false if all nodes in the path are expanded 2038 */ 2039 public boolean isCollapsed(TreePath path) { 2040 return !isExpanded(path); 2041 } 2042 2043 /** 2044 * Returns true if the node at the specified display row is collapsed. 2045 * 2046 * @param row the row to check, where 0 is the first row in the 2047 * display 2048 * @return true if the node is currently collapsed, otherwise false 2049 */ 2050 public boolean isCollapsed(int row) { 2051 return !isExpanded(row); 2052 } 2053 2054 /** 2055 * Ensures that the node identified by path is currently viewable. 2056 * 2057 * @param path the <code>TreePath</code> to make visible 2058 */ 2059 public void makeVisible(TreePath path) { 2060 if(path != null) { 2061 TreePath parentPath = path.getParentPath(); 2062 2063 if(parentPath != null) { 2064 expandPath(parentPath); 2065 } 2066 } 2067 } 2068 2069 /** 2070 * Returns true if the value identified by path is currently viewable, 2071 * which means it is either the root or all of its parents are expanded. 2072 * Otherwise, this method returns false. 2073 * 2074 * @return true if the node is viewable, otherwise false 2075 */ 2076 public boolean isVisible(TreePath path) { 2077 if(path != null) { 2078 TreePath parentPath = path.getParentPath(); 2079 2080 if(parentPath != null) 2081 return isExpanded(parentPath); 2082 // Root. 2083 return true; 2084 } 2085 return false; 2086 } 2087 2088 /** 2089 * Returns the <code>Rectangle</code> that the specified node will be drawn 2090 * into. Returns <code>null</code> if any component in the path is hidden 2091 * (under a collapsed parent). 2092 * <p> 2093 * Note:<br> 2094 * This method returns a valid rectangle, even if the specified 2095 * node is not currently displayed. 2096 * 2097 * @param path the <code>TreePath</code> identifying the node 2098 * @return the <code>Rectangle</code> the node is drawn in, 2099 * or <code>null</code> 2100 */ 2101 public Rectangle getPathBounds(TreePath path) { 2102 TreeUI tree = getUI(); 2103 2104 if(tree != null) 2105 return tree.getPathBounds(this, path); 2106 return null; 2107 } 2108 2109 /** 2110 * Returns the <code>Rectangle</code> that the node at the specified row is 2111 * drawn in. 2112 * 2113 * @param row the row to be drawn, where 0 is the first row in the 2114 * display 2115 * @return the <code>Rectangle</code> the node is drawn in 2116 */ 2117 public Rectangle getRowBounds(int row) { 2118 return getPathBounds(getPathForRow(row)); 2119 } 2120 2121 /** 2122 * Makes sure all the path components in path are expanded (except 2123 * for the last path component) and scrolls so that the 2124 * node identified by the path is displayed. Only works when this 2125 * <code>JTree</code> is contained in a <code>JScrollPane</code>. 2126 * 2127 * @param path the <code>TreePath</code> identifying the node to 2128 * bring into view 2129 */ 2130 public void scrollPathToVisible(TreePath path) { 2131 if(path != null) { 2132 makeVisible(path); 2133 2134 Rectangle bounds = getPathBounds(path); 2135 2136 if(bounds != null) { 2137 scrollRectToVisible(bounds); 2138 if (accessibleContext != null) { 2139 ((AccessibleJTree)accessibleContext).fireVisibleDataPropertyChange(); 2140 } 2141 } 2142 } 2143 } 2144 2145 /** 2146 * Scrolls the item identified by row until it is displayed. The minimum 2147 * of amount of scrolling necessary to bring the row into view 2148 * is performed. Only works when this <code>JTree</code> is contained in a 2149 * <code>JScrollPane</code>. 2150 * 2151 * @param row an integer specifying the row to scroll, where 0 is the 2152 * first row in the display 2153 */ 2154 public void scrollRowToVisible(int row) { 2155 scrollPathToVisible(getPathForRow(row)); 2156 } 2157 2158 /** 2159 * Returns the path for the specified row. If <code>row</code> is 2160 * not visible, or a {@code TreeUI} has not been set, <code>null</code> 2161 * is returned. 2162 * 2163 * @param row an integer specifying a row 2164 * @return the <code>TreePath</code> to the specified node, 2165 * <code>null</code> if <code>row < 0</code> 2166 * or <code>row >= getRowCount()</code> 2167 */ 2168 public TreePath getPathForRow(int row) { 2169 TreeUI tree = getUI(); 2170 2171 if(tree != null) 2172 return tree.getPathForRow(this, row); 2173 return null; 2174 } 2175 2176 /** 2177 * Returns the row that displays the node identified by the specified 2178 * path. 2179 * 2180 * @param path the <code>TreePath</code> identifying a node 2181 * @return an integer specifying the display row, where 0 is the first 2182 * row in the display, or -1 if any of the elements in path 2183 * are hidden under a collapsed parent. 2184 */ 2185 public int getRowForPath(TreePath path) { 2186 TreeUI tree = getUI(); 2187 2188 if(tree != null) 2189 return tree.getRowForPath(this, path); 2190 return -1; 2191 } 2192 2193 /** 2194 * Ensures that the node identified by the specified path is 2195 * expanded and viewable. If the last item in the path is a 2196 * leaf, this will have no effect. 2197 * 2198 * @param path the <code>TreePath</code> identifying a node 2199 */ 2200 public void expandPath(TreePath path) { 2201 // Only expand if not leaf! 2202 TreeModel model = getModel(); 2203 2204 if(path != null && model != null && 2205 !model.isLeaf(path.getLastPathComponent())) { 2206 setExpandedState(path, true); 2207 } 2208 } 2209 2210 /** 2211 * Ensures that the node in the specified row is expanded and 2212 * viewable. 2213 * <p> 2214 * If <code>row</code> is < 0 or >= <code>getRowCount</code> this 2215 * will have no effect. 2216 * 2217 * @param row an integer specifying a display row, where 0 is the 2218 * first row in the display 2219 */ 2220 public void expandRow(int row) { 2221 expandPath(getPathForRow(row)); 2222 } 2223 2224 /** 2225 * Ensures that the node identified by the specified path is 2226 * collapsed and viewable. 2227 * 2228 * @param path the <code>TreePath</code> identifying a node 2229 */ 2230 public void collapsePath(TreePath path) { 2231 setExpandedState(path, false); 2232 } 2233 2234 /** 2235 * Ensures that the node in the specified row is collapsed. 2236 * <p> 2237 * If <code>row</code> is < 0 or >= <code>getRowCount</code> this 2238 * will have no effect. 2239 * 2240 * @param row an integer specifying a display row, where 0 is the 2241 * first row in the display 2242 */ 2243 public void collapseRow(int row) { 2244 collapsePath(getPathForRow(row)); 2245 } 2246 2247 /** 2248 * Returns the path for the node at the specified location. 2249 * 2250 * @param x an integer giving the number of pixels horizontally from 2251 * the left edge of the display area, minus any left margin 2252 * @param y an integer giving the number of pixels vertically from 2253 * the top of the display area, minus any top margin 2254 * @return the <code>TreePath</code> for the node at that location 2255 */ 2256 public TreePath getPathForLocation(int x, int y) { 2257 TreePath closestPath = getClosestPathForLocation(x, y); 2258 2259 if(closestPath != null) { 2260 Rectangle pathBounds = getPathBounds(closestPath); 2261 2262 if(pathBounds != null && 2263 x >= pathBounds.x && x < (pathBounds.x + pathBounds.width) && 2264 y >= pathBounds.y && y < (pathBounds.y + pathBounds.height)) 2265 return closestPath; 2266 } 2267 return null; 2268 } 2269 2270 /** 2271 * Returns the row for the specified location. 2272 * 2273 * @param x an integer giving the number of pixels horizontally from 2274 * the left edge of the display area, minus any left margin 2275 * @param y an integer giving the number of pixels vertically from 2276 * the top of the display area, minus any top margin 2277 * @return the row corresponding to the location, or -1 if the 2278 * location is not within the bounds of a displayed cell 2279 * @see #getClosestRowForLocation 2280 */ 2281 public int getRowForLocation(int x, int y) { 2282 return getRowForPath(getPathForLocation(x, y)); 2283 } 2284 2285 /** 2286 * Returns the path to the node that is closest to x,y. If 2287 * no nodes are currently viewable, or there is no model, returns 2288 * <code>null</code>, otherwise it always returns a valid path. To test if 2289 * the node is exactly at x, y, get the node's bounds and 2290 * test x, y against that. 2291 * 2292 * @param x an integer giving the number of pixels horizontally from 2293 * the left edge of the display area, minus any left margin 2294 * @param y an integer giving the number of pixels vertically from 2295 * the top of the display area, minus any top margin 2296 * @return the <code>TreePath</code> for the node closest to that location, 2297 * <code>null</code> if nothing is viewable or there is no model 2298 * 2299 * @see #getPathForLocation 2300 * @see #getPathBounds 2301 */ 2302 public TreePath getClosestPathForLocation(int x, int y) { 2303 TreeUI tree = getUI(); 2304 2305 if(tree != null) 2306 return tree.getClosestPathForLocation(this, x, y); 2307 return null; 2308 } 2309 2310 /** 2311 * Returns the row to the node that is closest to x,y. If no nodes 2312 * are viewable or there is no model, returns -1. Otherwise, 2313 * it always returns a valid row. To test if the returned object is 2314 * exactly at x, y, get the bounds for the node at the returned 2315 * row and test x, y against that. 2316 * 2317 * @param x an integer giving the number of pixels horizontally from 2318 * the left edge of the display area, minus any left margin 2319 * @param y an integer giving the number of pixels vertically from 2320 * the top of the display area, minus any top margin 2321 * @return the row closest to the location, -1 if nothing is 2322 * viewable or there is no model 2323 * 2324 * @see #getRowForLocation 2325 * @see #getRowBounds 2326 */ 2327 public int getClosestRowForLocation(int x, int y) { 2328 return getRowForPath(getClosestPathForLocation(x, y)); 2329 } 2330 2331 /** 2332 * Returns true if the tree is being edited. The item that is being 2333 * edited can be obtained using <code>getSelectionPath</code>. 2334 * 2335 * @return true if the user is currently editing a node 2336 * @see #getSelectionPath 2337 */ 2338 public boolean isEditing() { 2339 TreeUI tree = getUI(); 2340 2341 if(tree != null) 2342 return tree.isEditing(this); 2343 return false; 2344 } 2345 2346 /** 2347 * Ends the current editing session. 2348 * (The <code>DefaultTreeCellEditor</code> 2349 * object saves any edits that are currently in progress on a cell. 2350 * Other implementations may operate differently.) 2351 * Has no effect if the tree isn't being edited. 2352 * <blockquote> 2353 * <b>Note:</b><br> 2354 * To make edit-saves automatic whenever the user changes 2355 * their position in the tree, use {@link #setInvokesStopCellEditing}. 2356 * </blockquote> 2357 * 2358 * @return true if editing was in progress and is now stopped, 2359 * false if editing was not in progress 2360 */ 2361 public boolean stopEditing() { 2362 TreeUI tree = getUI(); 2363 2364 if(tree != null) 2365 return tree.stopEditing(this); 2366 return false; 2367 } 2368 2369 /** 2370 * Cancels the current editing session. Has no effect if the 2371 * tree isn't being edited. 2372 */ 2373 public void cancelEditing() { 2374 TreeUI tree = getUI(); 2375 2376 if(tree != null) 2377 tree.cancelEditing(this); 2378 } 2379 2380 /** 2381 * Selects the node identified by the specified path and initiates 2382 * editing. The edit-attempt fails if the <code>CellEditor</code> 2383 * does not allow 2384 * editing for the specified item. 2385 * 2386 * @param path the <code>TreePath</code> identifying a node 2387 */ 2388 public void startEditingAtPath(TreePath path) { 2389 TreeUI tree = getUI(); 2390 2391 if(tree != null) 2392 tree.startEditingAtPath(this, path); 2393 } 2394 2395 /** 2396 * Returns the path to the element that is currently being edited. 2397 * 2398 * @return the <code>TreePath</code> for the node being edited 2399 */ 2400 public TreePath getEditingPath() { 2401 TreeUI tree = getUI(); 2402 2403 if(tree != null) 2404 return tree.getEditingPath(this); 2405 return null; 2406 } 2407 2408 // 2409 // Following are primarily convenience methods for mapping from 2410 // row based selections to path selections. Sometimes it is 2411 // easier to deal with these than paths (mouse downs, key downs 2412 // usually just deal with index based selections). 2413 // Since row based selections require a UI many of these won't work 2414 // without one. 2415 // 2416 2417 /** 2418 * Sets the tree's selection model. When a <code>null</code> value is 2419 * specified an empty 2420 * <code>selectionModel</code> is used, which does not allow selections. 2421 * <p> 2422 * This is a bound property. 2423 * 2424 * @param selectionModel the <code>TreeSelectionModel</code> to use, 2425 * or <code>null</code> to disable selections 2426 * @see TreeSelectionModel 2427 * @beaninfo 2428 * bound: true 2429 * description: The tree's selection model. 2430 */ 2431 public void setSelectionModel(TreeSelectionModel selectionModel) { 2432 if(selectionModel == null) 2433 selectionModel = EmptySelectionModel.sharedInstance(); 2434 2435 TreeSelectionModel oldValue = this.selectionModel; 2436 2437 if (this.selectionModel != null && selectionRedirector != null) { 2438 this.selectionModel.removeTreeSelectionListener 2439 (selectionRedirector); 2440 } 2441 if (accessibleContext != null) { 2442 this.selectionModel.removeTreeSelectionListener((TreeSelectionListener)accessibleContext); 2443 selectionModel.addTreeSelectionListener((TreeSelectionListener)accessibleContext); 2444 } 2445 2446 this.selectionModel = selectionModel; 2447 if (selectionRedirector != null) { 2448 this.selectionModel.addTreeSelectionListener(selectionRedirector); 2449 } 2450 firePropertyChange(SELECTION_MODEL_PROPERTY, oldValue, 2451 this.selectionModel); 2452 2453 if (accessibleContext != null) { 2454 accessibleContext.firePropertyChange( 2455 AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY, 2456 Boolean.valueOf(false), Boolean.valueOf(true)); 2457 } 2458 } 2459 2460 /** 2461 * Returns the model for selections. This should always return a 2462 * non-<code>null</code> value. If you don't want to allow anything 2463 * to be selected 2464 * set the selection model to <code>null</code>, which forces an empty 2465 * selection model to be used. 2466 * 2467 * @see #setSelectionModel 2468 */ 2469 public TreeSelectionModel getSelectionModel() { 2470 return selectionModel; 2471 } 2472 2473 /** 2474 * Returns the paths (inclusive) between the specified rows. If 2475 * the specified indices are within the viewable set of rows, or 2476 * bound the viewable set of rows, then the indices are 2477 * constrained by the viewable set of rows. If the specified 2478 * indices are not within the viewable set of rows, or do not 2479 * bound the viewable set of rows, then an empty array is 2480 * returned. For example, if the row count is {@code 10}, and this 2481 * method is invoked with {@code -1, 20}, then the specified 2482 * indices are constrained to the viewable set of rows, and this is 2483 * treated as if invoked with {@code 0, 9}. On the other hand, if 2484 * this were invoked with {@code -10, -1}, then the specified 2485 * indices do not bound the viewable set of rows, and an empty 2486 * array is returned. 2487 * <p> 2488 * The parameters are not order dependent. That is, {@code 2489 * getPathBetweenRows(x, y)} is equivalent to 2490 * {@code getPathBetweenRows(y, x)}. 2491 * <p> 2492 * An empty array is returned if the row count is {@code 0}, or 2493 * the specified indices do not bound the viewable set of rows. 2494 * 2495 * @param index0 the first index in the range 2496 * @param index1 the last index in the range 2497 * @return the paths (inclusive) between the specified row indices 2498 */ 2499 protected TreePath[] getPathBetweenRows(int index0, int index1) { 2500 TreeUI tree = getUI(); 2501 if (tree != null) { 2502 int rowCount = getRowCount(); 2503 if (rowCount > 0 && !((index0 < 0 && index1 < 0) || 2504 (index0 >= rowCount && index1 >= rowCount))){ 2505 index0 = Math.min(rowCount - 1, Math.max(index0, 0)); 2506 index1 = Math.min(rowCount - 1, Math.max(index1, 0)); 2507 int minIndex = Math.min(index0, index1); 2508 int maxIndex = Math.max(index0, index1); 2509 TreePath[] selection = new TreePath[ 2510 maxIndex - minIndex + 1]; 2511 for(int counter = minIndex; counter <= maxIndex; counter++) { 2512 selection[counter - minIndex] = 2513 tree.getPathForRow(this, counter); 2514 } 2515 return selection; 2516 } 2517 } 2518 return new TreePath[0]; 2519 } 2520 2521 /** 2522 * Selects the rows in the specified interval (inclusive). If 2523 * the specified indices are within the viewable set of rows, or bound 2524 * the viewable set of rows, then the specified rows are constrained by 2525 * the viewable set of rows. If the specified indices are not within the 2526 * viewable set of rows, or do not bound the viewable set of rows, then 2527 * the selection is cleared. For example, if the row count is {@code 2528 * 10}, and this method is invoked with {@code -1, 20}, then the 2529 * specified indices bounds the viewable range, and this is treated as 2530 * if invoked with {@code 0, 9}. On the other hand, if this were 2531 * invoked with {@code -10, -1}, then the specified indices do not 2532 * bound the viewable set of rows, and the selection is cleared. 2533 * <p> 2534 * The parameters are not order dependent. That is, {@code 2535 * setSelectionInterval(x, y)} is equivalent to 2536 * {@code setSelectionInterval(y, x)}. 2537 * 2538 * @param index0 the first index in the range to select 2539 * @param index1 the last index in the range to select 2540 */ 2541 public void setSelectionInterval(int index0, int index1) { 2542 TreePath[] paths = getPathBetweenRows(index0, index1); 2543 2544 this.getSelectionModel().setSelectionPaths(paths); 2545 } 2546 2547 /** 2548 * Adds the specified rows (inclusive) to the selection. If the 2549 * specified indices are within the viewable set of rows, or bound 2550 * the viewable set of rows, then the specified indices are 2551 * constrained by the viewable set of rows. If the indices are not 2552 * within the viewable set of rows, or do not bound the viewable 2553 * set of rows, then the selection is unchanged. For example, if 2554 * the row count is {@code 10}, and this method is invoked with 2555 * {@code -1, 20}, then the specified indices bounds the viewable 2556 * range, and this is treated as if invoked with {@code 0, 9}. On 2557 * the other hand, if this were invoked with {@code -10, -1}, then 2558 * the specified indices do not bound the viewable set of rows, 2559 * and the selection is unchanged. 2560 * <p> 2561 * The parameters are not order dependent. That is, {@code 2562 * addSelectionInterval(x, y)} is equivalent to 2563 * {@code addSelectionInterval(y, x)}. 2564 * 2565 * @param index0 the first index in the range to add to the selection 2566 * @param index1 the last index in the range to add to the selection 2567 */ 2568 public void addSelectionInterval(int index0, int index1) { 2569 TreePath[] paths = getPathBetweenRows(index0, index1); 2570 2571 if (paths != null && paths.length > 0) { 2572 this.getSelectionModel().addSelectionPaths(paths); 2573 } 2574 } 2575 2576 /** 2577 * Removes the specified rows (inclusive) from the selection. If 2578 * the specified indices are within the viewable set of rows, or bound 2579 * the viewable set of rows, then the specified indices are constrained by 2580 * the viewable set of rows. If the specified indices are not within the 2581 * viewable set of rows, or do not bound the viewable set of rows, then 2582 * the selection is unchanged. For example, if the row count is {@code 2583 * 10}, and this method is invoked with {@code -1, 20}, then the 2584 * specified range bounds the viewable range, and this is treated as 2585 * if invoked with {@code 0, 9}. On the other hand, if this were 2586 * invoked with {@code -10, -1}, then the specified range does not 2587 * bound the viewable set of rows, and the selection is unchanged. 2588 * <p> 2589 * The parameters are not order dependent. That is, {@code 2590 * removeSelectionInterval(x, y)} is equivalent to 2591 * {@code removeSelectionInterval(y, x)}. 2592 * 2593 * @param index0 the first row to remove from the selection 2594 * @param index1 the last row to remove from the selection 2595 */ 2596 public void removeSelectionInterval(int index0, int index1) { 2597 TreePath[] paths = getPathBetweenRows(index0, index1); 2598 2599 if (paths != null && paths.length > 0) { 2600 this.getSelectionModel().removeSelectionPaths(paths); 2601 } 2602 } 2603 2604 /** 2605 * Removes the node identified by the specified path from the current 2606 * selection. 2607 * 2608 * @param path the <code>TreePath</code> identifying a node 2609 */ 2610 public void removeSelectionPath(TreePath path) { 2611 this.getSelectionModel().removeSelectionPath(path); 2612 } 2613 2614 /** 2615 * Removes the nodes identified by the specified paths from the 2616 * current selection. 2617 * 2618 * @param paths an array of <code>TreePath</code> objects that 2619 * specifies the nodes to remove 2620 */ 2621 public void removeSelectionPaths(TreePath[] paths) { 2622 this.getSelectionModel().removeSelectionPaths(paths); 2623 } 2624 2625 /** 2626 * Removes the row at the index <code>row</code> from the current 2627 * selection. 2628 * 2629 * @param row the row to remove 2630 */ 2631 public void removeSelectionRow(int row) { 2632 int[] rows = { row }; 2633 2634 removeSelectionRows(rows); 2635 } 2636 2637 /** 2638 * Removes the rows that are selected at each of the specified 2639 * rows. 2640 * 2641 * @param rows an array of ints specifying display rows, where 0 is 2642 * the first row in the display 2643 */ 2644 public void removeSelectionRows(int[] rows) { 2645 TreeUI ui = getUI(); 2646 2647 if(ui != null && rows != null) { 2648 int numRows = rows.length; 2649 TreePath[] paths = new TreePath[numRows]; 2650 2651 for(int counter = 0; counter < numRows; counter++) 2652 paths[counter] = ui.getPathForRow(this, rows[counter]); 2653 removeSelectionPaths(paths); 2654 } 2655 } 2656 2657 /** 2658 * Clears the selection. 2659 */ 2660 public void clearSelection() { 2661 getSelectionModel().clearSelection(); 2662 } 2663 2664 /** 2665 * Returns true if the selection is currently empty. 2666 * 2667 * @return true if the selection is currently empty 2668 */ 2669 public boolean isSelectionEmpty() { 2670 return getSelectionModel().isSelectionEmpty(); 2671 } 2672 2673 /** 2674 * Adds a listener for <code>TreeExpansion</code> events. 2675 * 2676 * @param tel a TreeExpansionListener that will be notified when 2677 * a tree node is expanded or collapsed (a "negative 2678 * expansion") 2679 */ 2680 public void addTreeExpansionListener(TreeExpansionListener tel) { 2681 if (settingUI) { 2682 uiTreeExpansionListener = tel; 2683 } 2684 listenerList.add(TreeExpansionListener.class, tel); 2685 } 2686 2687 /** 2688 * Removes a listener for <code>TreeExpansion</code> events. 2689 * 2690 * @param tel the <code>TreeExpansionListener</code> to remove 2691 */ 2692 public void removeTreeExpansionListener(TreeExpansionListener tel) { 2693 listenerList.remove(TreeExpansionListener.class, tel); 2694 if (uiTreeExpansionListener == tel) { 2695 uiTreeExpansionListener = null; 2696 } 2697 } 2698 2699 /** 2700 * Returns an array of all the <code>TreeExpansionListener</code>s added 2701 * to this JTree with addTreeExpansionListener(). 2702 * 2703 * @return all of the <code>TreeExpansionListener</code>s added or an empty 2704 * array if no listeners have been added 2705 * @since 1.4 2706 */ 2707 public TreeExpansionListener[] getTreeExpansionListeners() { 2708 return listenerList.getListeners(TreeExpansionListener.class); 2709 } 2710 2711 /** 2712 * Adds a listener for <code>TreeWillExpand</code> events. 2713 * 2714 * @param tel a <code>TreeWillExpandListener</code> that will be notified 2715 * when a tree node will be expanded or collapsed (a "negative 2716 * expansion") 2717 */ 2718 public void addTreeWillExpandListener(TreeWillExpandListener tel) { 2719 listenerList.add(TreeWillExpandListener.class, tel); 2720 } 2721 2722 /** 2723 * Removes a listener for <code>TreeWillExpand</code> events. 2724 * 2725 * @param tel the <code>TreeWillExpandListener</code> to remove 2726 */ 2727 public void removeTreeWillExpandListener(TreeWillExpandListener tel) { 2728 listenerList.remove(TreeWillExpandListener.class, tel); 2729 } 2730 2731 /** 2732 * Returns an array of all the <code>TreeWillExpandListener</code>s added 2733 * to this JTree with addTreeWillExpandListener(). 2734 * 2735 * @return all of the <code>TreeWillExpandListener</code>s added or an empty 2736 * array if no listeners have been added 2737 * @since 1.4 2738 */ 2739 public TreeWillExpandListener[] getTreeWillExpandListeners() { 2740 return listenerList.getListeners(TreeWillExpandListener.class); 2741 } 2742 2743 /** 2744 * Notifies all listeners that have registered interest for 2745 * notification on this event type. The event instance 2746 * is lazily created using the <code>path</code> parameter. 2747 * 2748 * @param path the <code>TreePath</code> indicating the node that was 2749 * expanded 2750 * @see EventListenerList 2751 */ 2752 public void fireTreeExpanded(TreePath path) { 2753 // Guaranteed to return a non-null array 2754 Object[] listeners = listenerList.getListenerList(); 2755 TreeExpansionEvent e = null; 2756 if (uiTreeExpansionListener != null) { 2757 e = new TreeExpansionEvent(this, path); 2758 uiTreeExpansionListener.treeExpanded(e); 2759 } 2760 // Process the listeners last to first, notifying 2761 // those that are interested in this event 2762 for (int i = listeners.length-2; i>=0; i-=2) { 2763 if (listeners[i]==TreeExpansionListener.class && 2764 listeners[i + 1] != uiTreeExpansionListener) { 2765 // Lazily create the event: 2766 if (e == null) 2767 e = new TreeExpansionEvent(this, path); 2768 ((TreeExpansionListener)listeners[i+1]). 2769 treeExpanded(e); 2770 } 2771 } 2772 } 2773 2774 /** 2775 * Notifies all listeners that have registered interest for 2776 * notification on this event type. The event instance 2777 * is lazily created using the <code>path</code> parameter. 2778 * 2779 * @param path the <code>TreePath</code> indicating the node that was 2780 * collapsed 2781 * @see EventListenerList 2782 */ 2783 public void fireTreeCollapsed(TreePath path) { 2784 // Guaranteed to return a non-null array 2785 Object[] listeners = listenerList.getListenerList(); 2786 TreeExpansionEvent e = null; 2787 if (uiTreeExpansionListener != null) { 2788 e = new TreeExpansionEvent(this, path); 2789 uiTreeExpansionListener.treeCollapsed(e); 2790 } 2791 // Process the listeners last to first, notifying 2792 // those that are interested in this event 2793 for (int i = listeners.length-2; i>=0; i-=2) { 2794 if (listeners[i]==TreeExpansionListener.class && 2795 listeners[i + 1] != uiTreeExpansionListener) { 2796 // Lazily create the event: 2797 if (e == null) 2798 e = new TreeExpansionEvent(this, path); 2799 ((TreeExpansionListener)listeners[i+1]). 2800 treeCollapsed(e); 2801 } 2802 } 2803 } 2804 2805 /** 2806 * Notifies all listeners that have registered interest for 2807 * notification on this event type. The event instance 2808 * is lazily created using the <code>path</code> parameter. 2809 * 2810 * @param path the <code>TreePath</code> indicating the node that was 2811 * expanded 2812 * @see EventListenerList 2813 */ 2814 public void fireTreeWillExpand(TreePath path) throws ExpandVetoException { 2815 // Guaranteed to return a non-null array 2816 Object[] listeners = listenerList.getListenerList(); 2817 TreeExpansionEvent e = null; 2818 // Process the listeners last to first, notifying 2819 // those that are interested in this event 2820 for (int i = listeners.length-2; i>=0; i-=2) { 2821 if (listeners[i]==TreeWillExpandListener.class) { 2822 // Lazily create the event: 2823 if (e == null) 2824 e = new TreeExpansionEvent(this, path); 2825 ((TreeWillExpandListener)listeners[i+1]). 2826 treeWillExpand(e); 2827 } 2828 } 2829 } 2830 2831 /** 2832 * Notifies all listeners that have registered interest for 2833 * notification on this event type. The event instance 2834 * is lazily created using the <code>path</code> parameter. 2835 * 2836 * @param path the <code>TreePath</code> indicating the node that was 2837 * expanded 2838 * @see EventListenerList 2839 */ 2840 public void fireTreeWillCollapse(TreePath path) throws ExpandVetoException { 2841 // Guaranteed to return a non-null array 2842 Object[] listeners = listenerList.getListenerList(); 2843 TreeExpansionEvent e = null; 2844 // Process the listeners last to first, notifying 2845 // those that are interested in this event 2846 for (int i = listeners.length-2; i>=0; i-=2) { 2847 if (listeners[i]==TreeWillExpandListener.class) { 2848 // Lazily create the event: 2849 if (e == null) 2850 e = new TreeExpansionEvent(this, path); 2851 ((TreeWillExpandListener)listeners[i+1]). 2852 treeWillCollapse(e); 2853 } 2854 } 2855 } 2856 2857 /** 2858 * Adds a listener for <code>TreeSelection</code> events. 2859 * 2860 * @param tsl the <code>TreeSelectionListener</code> that will be notified 2861 * when a node is selected or deselected (a "negative 2862 * selection") 2863 */ 2864 public void addTreeSelectionListener(TreeSelectionListener tsl) { 2865 listenerList.add(TreeSelectionListener.class,tsl); 2866 if(listenerList.getListenerCount(TreeSelectionListener.class) != 0 2867 && selectionRedirector == null) { 2868 selectionRedirector = new TreeSelectionRedirector(); 2869 selectionModel.addTreeSelectionListener(selectionRedirector); 2870 } 2871 } 2872 2873 /** 2874 * Removes a <code>TreeSelection</code> listener. 2875 * 2876 * @param tsl the <code>TreeSelectionListener</code> to remove 2877 */ 2878 public void removeTreeSelectionListener(TreeSelectionListener tsl) { 2879 listenerList.remove(TreeSelectionListener.class,tsl); 2880 if(listenerList.getListenerCount(TreeSelectionListener.class) == 0 2881 && selectionRedirector != null) { 2882 selectionModel.removeTreeSelectionListener 2883 (selectionRedirector); 2884 selectionRedirector = null; 2885 } 2886 } 2887 2888 /** 2889 * Returns an array of all the <code>TreeSelectionListener</code>s added 2890 * to this JTree with addTreeSelectionListener(). 2891 * 2892 * @return all of the <code>TreeSelectionListener</code>s added or an empty 2893 * array if no listeners have been added 2894 * @since 1.4 2895 */ 2896 public TreeSelectionListener[] getTreeSelectionListeners() { 2897 return listenerList.getListeners(TreeSelectionListener.class); 2898 } 2899 2900 /** 2901 * Notifies all listeners that have registered interest for 2902 * notification on this event type. 2903 * 2904 * @param e the <code>TreeSelectionEvent</code> to be fired; 2905 * generated by the 2906 * <code>TreeSelectionModel</code> 2907 * when a node is selected or deselected 2908 * @see EventListenerList 2909 */ 2910 protected void fireValueChanged(TreeSelectionEvent e) { 2911 // Guaranteed to return a non-null array 2912 Object[] listeners = listenerList.getListenerList(); 2913 // Process the listeners last to first, notifying 2914 // those that are interested in this event 2915 for (int i = listeners.length-2; i>=0; i-=2) { 2916 // TreeSelectionEvent e = null; 2917 if (listeners[i]==TreeSelectionListener.class) { 2918 // Lazily create the event: 2919 // if (e == null) 2920 // e = new ListSelectionEvent(this, firstIndex, lastIndex); 2921 ((TreeSelectionListener)listeners[i+1]).valueChanged(e); 2922 } 2923 } 2924 } 2925 2926 /** 2927 * Sent when the tree has changed enough that we need to resize 2928 * the bounds, but not enough that we need to remove the 2929 * expanded node set (e.g nodes were expanded or collapsed, or 2930 * nodes were inserted into the tree). You should never have to 2931 * invoke this, the UI will invoke this as it needs to. 2932 */ 2933 public void treeDidChange() { 2934 revalidate(); 2935 repaint(); 2936 } 2937 2938 /** 2939 * Sets the number of rows that are to be displayed. 2940 * This will only work if the tree is contained in a 2941 * <code>JScrollPane</code>, 2942 * and will adjust the preferred size and size of that scrollpane. 2943 * <p> 2944 * This is a bound property. 2945 * 2946 * @param newCount the number of rows to display 2947 * @beaninfo 2948 * bound: true 2949 * description: The number of rows that are to be displayed. 2950 */ 2951 public void setVisibleRowCount(int newCount) { 2952 int oldCount = visibleRowCount; 2953 2954 visibleRowCount = newCount; 2955 firePropertyChange(VISIBLE_ROW_COUNT_PROPERTY, oldCount, 2956 visibleRowCount); 2957 invalidate(); 2958 if (accessibleContext != null) { 2959 ((AccessibleJTree)accessibleContext).fireVisibleDataPropertyChange(); 2960 } 2961 } 2962 2963 /** 2964 * Returns the number of rows that are displayed in the display area. 2965 * 2966 * @return the number of rows displayed 2967 */ 2968 public int getVisibleRowCount() { 2969 return visibleRowCount; 2970 } 2971 2972 /** 2973 * Expands the root path, assuming the current TreeModel has been set. 2974 */ 2975 private void expandRoot() { 2976 TreeModel model = getModel(); 2977 2978 if(model != null && model.getRoot() != null) { 2979 expandPath(new TreePath(model.getRoot())); 2980 } 2981 } 2982 2983 /** 2984 * Returns the TreePath to the next tree element that 2985 * begins with a prefix. To handle the conversion of a 2986 * <code>TreePath</code> into a String, <code>convertValueToText</code> 2987 * is used. 2988 * 2989 * @param prefix the string to test for a match 2990 * @param startingRow the row for starting the search 2991 * @param bias the search direction, either 2992 * Position.Bias.Forward or Position.Bias.Backward. 2993 * @return the TreePath of the next tree element that 2994 * starts with the prefix; otherwise null 2995 * @exception IllegalArgumentException if prefix is null 2996 * or startingRow is out of bounds 2997 * @since 1.4 2998 */ 2999 public TreePath getNextMatch(String prefix, int startingRow, 3000 Position.Bias bias) { 3001 3002 int max = getRowCount(); 3003 if (prefix == null) { 3004 throw new IllegalArgumentException(); 3005 } 3006 if (startingRow < 0 || startingRow >= max) { 3007 throw new IllegalArgumentException(); 3008 } 3009 prefix = prefix.toUpperCase(); 3010 3011 // start search from the next/previous element froom the 3012 // selected element 3013 int increment = (bias == Position.Bias.Forward) ? 1 : -1; 3014 int row = startingRow; 3015 do { 3016 TreePath path = getPathForRow(row); 3017 String text = convertValueToText( 3018 path.getLastPathComponent(), isRowSelected(row), 3019 isExpanded(row), true, row, false); 3020 3021 if (text.toUpperCase().startsWith(prefix)) { 3022 return path; 3023 } 3024 row = (row + increment + max) % max; 3025 } while (row != startingRow); 3026 return null; 3027 } 3028 3029 // Serialization support. 3030 private void writeObject(ObjectOutputStream s) throws IOException { 3031 Vector<Object> values = new Vector<Object>(); 3032 3033 s.defaultWriteObject(); 3034 // Save the cellRenderer, if its Serializable. 3035 if(cellRenderer != null && cellRenderer instanceof Serializable) { 3036 values.addElement("cellRenderer"); 3037 values.addElement(cellRenderer); 3038 } 3039 // Save the cellEditor, if its Serializable. 3040 if(cellEditor != null && cellEditor instanceof Serializable) { 3041 values.addElement("cellEditor"); 3042 values.addElement(cellEditor); 3043 } 3044 // Save the treeModel, if its Serializable. 3045 if(treeModel != null && treeModel instanceof Serializable) { 3046 values.addElement("treeModel"); 3047 values.addElement(treeModel); 3048 } 3049 // Save the selectionModel, if its Serializable. 3050 if(selectionModel != null && selectionModel instanceof Serializable) { 3051 values.addElement("selectionModel"); 3052 values.addElement(selectionModel); 3053 } 3054 3055 Object expandedData = getArchivableExpandedState(); 3056 3057 if(expandedData != null) { 3058 values.addElement("expandedState"); 3059 values.addElement(expandedData); 3060 } 3061 3062 s.writeObject(values); 3063 if (getUIClassID().equals(uiClassID)) { 3064 byte count = JComponent.getWriteObjCounter(this); 3065 JComponent.setWriteObjCounter(this, --count); 3066 if (count == 0 && ui != null) { 3067 ui.installUI(this); 3068 } 3069 } 3070 } 3071 3072 private void readObject(ObjectInputStream s) 3073 throws IOException, ClassNotFoundException { 3074 s.defaultReadObject(); 3075 3076 // Create an instance of expanded state. 3077 3078 expandedState = new Hashtable<TreePath, Boolean>(); 3079 3080 expandedStack = new Stack<Stack<TreePath>>(); 3081 3082 Vector<?> values = (Vector)s.readObject(); 3083 int indexCounter = 0; 3084 int maxCounter = values.size(); 3085 3086 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3087 equals("cellRenderer")) { 3088 cellRenderer = (TreeCellRenderer)values.elementAt(++indexCounter); 3089 indexCounter++; 3090 } 3091 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3092 equals("cellEditor")) { 3093 cellEditor = (TreeCellEditor)values.elementAt(++indexCounter); 3094 indexCounter++; 3095 } 3096 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3097 equals("treeModel")) { 3098 treeModel = (TreeModel)values.elementAt(++indexCounter); 3099 indexCounter++; 3100 } 3101 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3102 equals("selectionModel")) { 3103 selectionModel = (TreeSelectionModel)values.elementAt(++indexCounter); 3104 indexCounter++; 3105 } 3106 if(indexCounter < maxCounter && values.elementAt(indexCounter). 3107 equals("expandedState")) { 3108 unarchiveExpandedState(values.elementAt(++indexCounter)); 3109 indexCounter++; 3110 } 3111 // Reinstall the redirector. 3112 if(listenerList.getListenerCount(TreeSelectionListener.class) != 0) { 3113 selectionRedirector = new TreeSelectionRedirector(); 3114 selectionModel.addTreeSelectionListener(selectionRedirector); 3115 } 3116 // Listener to TreeModel. 3117 if(treeModel != null) { 3118 treeModelListener = createTreeModelListener(); 3119 if(treeModelListener != null) 3120 treeModel.addTreeModelListener(treeModelListener); 3121 } 3122 } 3123 3124 /** 3125 * Returns an object that can be archived indicating what nodes are 3126 * expanded and what aren't. The objects from the model are NOT 3127 * written out. 3128 */ 3129 private Object getArchivableExpandedState() { 3130 TreeModel model = getModel(); 3131 3132 if(model != null) { 3133 Enumeration<TreePath> paths = expandedState.keys(); 3134 3135 if(paths != null) { 3136 Vector<Object> state = new Vector<Object>(); 3137 3138 while(paths.hasMoreElements()) { 3139 TreePath path = paths.nextElement(); 3140 Object archivePath; 3141 3142 try { 3143 archivePath = getModelIndexsForPath(path); 3144 } catch (Error error) { 3145 archivePath = null; 3146 } 3147 if(archivePath != null) { 3148 state.addElement(archivePath); 3149 state.addElement(expandedState.get(path)); 3150 } 3151 } 3152 return state; 3153 } 3154 } 3155 return null; 3156 } 3157 3158 /** 3159 * Updates the expanded state of nodes in the tree based on the 3160 * previously archived state <code>state</code>. 3161 */ 3162 private void unarchiveExpandedState(Object state) { 3163 if(state instanceof Vector) { 3164 Vector<?> paths = (Vector)state; 3165 3166 for(int counter = paths.size() - 1; counter >= 0; counter--) { 3167 Boolean eState = (Boolean)paths.elementAt(counter--); 3168 TreePath path; 3169 3170 try { 3171 path = getPathForIndexs((int[])paths.elementAt(counter)); 3172 if(path != null) 3173 expandedState.put(path, eState); 3174 } catch (Error error) {} 3175 } 3176 } 3177 } 3178 3179 /** 3180 * Returns an array of integers specifying the indexs of the 3181 * components in the <code>path</code>. If <code>path</code> is 3182 * the root, this will return an empty array. If <code>path</code> 3183 * is <code>null</code>, <code>null</code> will be returned. 3184 */ 3185 private int[] getModelIndexsForPath(TreePath path) { 3186 if(path != null) { 3187 TreeModel model = getModel(); 3188 int count = path.getPathCount(); 3189 int[] indexs = new int[count - 1]; 3190 Object parent = model.getRoot(); 3191 3192 for(int counter = 1; counter < count; counter++) { 3193 indexs[counter - 1] = model.getIndexOfChild 3194 (parent, path.getPathComponent(counter)); 3195 parent = path.getPathComponent(counter); 3196 if(indexs[counter - 1] < 0) 3197 return null; 3198 } 3199 return indexs; 3200 } 3201 return null; 3202 } 3203 3204 /** 3205 * Returns a <code>TreePath</code> created by obtaining the children 3206 * for each of the indices in <code>indexs</code>. If <code>indexs</code> 3207 * or the <code>TreeModel</code> is <code>null</code>, it will return 3208 * <code>null</code>. 3209 */ 3210 private TreePath getPathForIndexs(int[] indexs) { 3211 if(indexs == null) 3212 return null; 3213 3214 TreeModel model = getModel(); 3215 3216 if(model == null) 3217 return null; 3218 3219 int count = indexs.length; 3220 Object parent = model.getRoot(); 3221 TreePath parentPath = new TreePath(parent); 3222 3223 for(int counter = 0; counter < count; counter++) { 3224 parent = model.getChild(parent, indexs[counter]); 3225 if(parent == null) 3226 return null; 3227 parentPath = parentPath.pathByAddingChild(parent); 3228 } 3229 return parentPath; 3230 } 3231 3232 /** 3233 * <code>EmptySelectionModel</code> is a <code>TreeSelectionModel</code> 3234 * that does not allow anything to be selected. 3235 * <p> 3236 * <strong>Warning:</strong> 3237 * Serialized objects of this class will not be compatible with 3238 * future Swing releases. The current serialization support is 3239 * appropriate for short term storage or RMI between applications running 3240 * the same version of Swing. As of 1.4, support for long term storage 3241 * of all JavaBeans<sup><font size="-2">TM</font></sup> 3242 * has been added to the <code>java.beans</code> package. 3243 * Please see {@link java.beans.XMLEncoder}. 3244 */ 3245 @SuppressWarnings("serial") 3246 protected static class EmptySelectionModel extends 3247 DefaultTreeSelectionModel 3248 { 3249 /** 3250 * The single instance of {@code EmptySelectionModel}. 3251 */ 3252 protected static final EmptySelectionModel sharedInstance = 3253 new EmptySelectionModel(); 3254 3255 /** 3256 * Returns the single instance of {@code EmptySelectionModel}. 3257 * 3258 * @return single instance of {@code EmptySelectionModel} 3259 */ 3260 static public EmptySelectionModel sharedInstance() { 3261 return sharedInstance; 3262 } 3263 3264 /** 3265 * This is overriden to do nothing; {@code EmptySelectionModel} 3266 * does not allow a selection. 3267 * 3268 * @param paths the paths to select; this is ignored 3269 */ 3270 public void setSelectionPaths(TreePath[] paths) {} 3271 3272 /** 3273 * This is overriden to do nothing; {@code EmptySelectionModel} 3274 * does not allow a selection. 3275 * 3276 * @param paths the paths to add to the selection; this is ignored 3277 */ 3278 public void addSelectionPaths(TreePath[] paths) {} 3279 3280 /** 3281 * This is overriden to do nothing; {@code EmptySelectionModel} 3282 * does not allow a selection. 3283 * 3284 * @param paths the paths to remove; this is ignored 3285 */ 3286 public void removeSelectionPaths(TreePath[] paths) {} 3287 3288 /** 3289 * This is overriden to do nothing; {@code EmptySelectionModel} 3290 * does not allow a selection. 3291 * 3292 * @param mode the selection mode; this is ignored 3293 * @since 1.7 3294 */ 3295 public void setSelectionMode(int mode) { 3296 } 3297 3298 /** 3299 * This is overriden to do nothing; {@code EmptySelectionModel} 3300 * does not allow a selection. 3301 * 3302 * @param mapper the {@code RowMapper} instance; this is ignored 3303 * @since 1.7 3304 */ 3305 public void setRowMapper(RowMapper mapper) { 3306 } 3307 3308 /** 3309 * This is overriden to do nothing; {@code EmptySelectionModel} 3310 * does not allow a selection. 3311 * 3312 * @param listener the listener to add; this is ignored 3313 * @since 1.7 3314 */ 3315 public void addTreeSelectionListener(TreeSelectionListener listener) { 3316 } 3317 3318 /** 3319 * This is overriden to do nothing; {@code EmptySelectionModel} 3320 * does not allow a selection. 3321 * 3322 * @param listener the listener to remove; this is ignored 3323 * @since 1.7 3324 */ 3325 public void removeTreeSelectionListener( 3326 TreeSelectionListener listener) { 3327 } 3328 3329 /** 3330 * This is overriden to do nothing; {@code EmptySelectionModel} 3331 * does not allow a selection. 3332 * 3333 * @param listener the listener to add; this is ignored 3334 * @since 1.7 3335 */ 3336 public void addPropertyChangeListener( 3337 PropertyChangeListener listener) { 3338 } 3339 3340 /** 3341 * This is overriden to do nothing; {@code EmptySelectionModel} 3342 * does not allow a selection. 3343 * 3344 * @param listener the listener to remove; this is ignored 3345 * @since 1.7 3346 */ 3347 public void removePropertyChangeListener( 3348 PropertyChangeListener listener) { 3349 } 3350 } 3351 3352 3353 /** 3354 * Handles creating a new <code>TreeSelectionEvent</code> with the 3355 * <code>JTree</code> as the 3356 * source and passing it off to all the listeners. 3357 * <p> 3358 * <strong>Warning:</strong> 3359 * Serialized objects of this class will not be compatible with 3360 * future Swing releases. The current serialization support is 3361 * appropriate for short term storage or RMI between applications running 3362 * the same version of Swing. As of 1.4, support for long term storage 3363 * of all JavaBeans<sup><font size="-2">TM</font></sup> 3364 * has been added to the <code>java.beans</code> package. 3365 * Please see {@link java.beans.XMLEncoder}. 3366 */ 3367 @SuppressWarnings("serial") 3368 protected class TreeSelectionRedirector implements Serializable, 3369 TreeSelectionListener 3370 { 3371 /** 3372 * Invoked by the <code>TreeSelectionModel</code> when the 3373 * selection changes. 3374 * 3375 * @param e the <code>TreeSelectionEvent</code> generated by the 3376 * <code>TreeSelectionModel</code> 3377 */ 3378 public void valueChanged(TreeSelectionEvent e) { 3379 TreeSelectionEvent newE; 3380 3381 newE = (TreeSelectionEvent)e.cloneWithSource(JTree.this); 3382 fireValueChanged(newE); 3383 } 3384 } // End of class JTree.TreeSelectionRedirector 3385 3386 // 3387 // Scrollable interface 3388 // 3389 3390 /** 3391 * Returns the preferred display size of a <code>JTree</code>. The height is 3392 * determined from <code>getVisibleRowCount</code> and the width 3393 * is the current preferred width. 3394 * 3395 * @return a <code>Dimension</code> object containing the preferred size 3396 */ 3397 public Dimension getPreferredScrollableViewportSize() { 3398 int width = getPreferredSize().width; 3399 int visRows = getVisibleRowCount(); 3400 int height = -1; 3401 3402 if(isFixedRowHeight()) 3403 height = visRows * getRowHeight(); 3404 else { 3405 TreeUI ui = getUI(); 3406 3407 if (ui != null && visRows > 0) { 3408 int rc = ui.getRowCount(this); 3409 3410 if (rc >= visRows) { 3411 Rectangle bounds = getRowBounds(visRows - 1); 3412 if (bounds != null) { 3413 height = bounds.y + bounds.height; 3414 } 3415 } 3416 else if (rc > 0) { 3417 Rectangle bounds = getRowBounds(0); 3418 if (bounds != null) { 3419 height = bounds.height * visRows; 3420 } 3421 } 3422 } 3423 if (height == -1) { 3424 height = 16 * visRows; 3425 } 3426 } 3427 return new Dimension(width, height); 3428 } 3429 3430 /** 3431 * Returns the amount to increment when scrolling. The amount is 3432 * the height of the first displayed row that isn't completely in view 3433 * or, if it is totally displayed, the height of the next row in the 3434 * scrolling direction. 3435 * 3436 * @param visibleRect the view area visible within the viewport 3437 * @param orientation either <code>SwingConstants.VERTICAL</code> 3438 * or <code>SwingConstants.HORIZONTAL</code> 3439 * @param direction less than zero to scroll up/left, 3440 * greater than zero for down/right 3441 * @return the "unit" increment for scrolling in the specified direction 3442 * @see JScrollBar#setUnitIncrement(int) 3443 */ 3444 public int getScrollableUnitIncrement(Rectangle visibleRect, 3445 int orientation, int direction) { 3446 if(orientation == SwingConstants.VERTICAL) { 3447 Rectangle rowBounds; 3448 int firstIndex = getClosestRowForLocation 3449 (0, visibleRect.y); 3450 3451 if(firstIndex != -1) { 3452 rowBounds = getRowBounds(firstIndex); 3453 if(rowBounds.y != visibleRect.y) { 3454 if(direction < 0) { 3455 // UP 3456 return Math.max(0, (visibleRect.y - rowBounds.y)); 3457 } 3458 return (rowBounds.y + rowBounds.height - visibleRect.y); 3459 } 3460 if(direction < 0) { // UP 3461 if(firstIndex != 0) { 3462 rowBounds = getRowBounds(firstIndex - 1); 3463 return rowBounds.height; 3464 } 3465 } 3466 else { 3467 return rowBounds.height; 3468 } 3469 } 3470 return 0; 3471 } 3472 return 4; 3473 } 3474 3475 3476 /** 3477 * Returns the amount for a block increment, which is the height or 3478 * width of <code>visibleRect</code>, based on <code>orientation</code>. 3479 * 3480 * @param visibleRect the view area visible within the viewport 3481 * @param orientation either <code>SwingConstants.VERTICAL</code> 3482 * or <code>SwingConstants.HORIZONTAL</code> 3483 * @param direction less than zero to scroll up/left, 3484 * greater than zero for down/right. 3485 * @return the "block" increment for scrolling in the specified direction 3486 * @see JScrollBar#setBlockIncrement(int) 3487 */ 3488 public int getScrollableBlockIncrement(Rectangle visibleRect, 3489 int orientation, int direction) { 3490 return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : 3491 visibleRect.width; 3492 } 3493 3494 /** 3495 * Returns false to indicate that the width of the viewport does not 3496 * determine the width of the table, unless the preferred width of 3497 * the tree is smaller than the viewports width. In other words: 3498 * ensure that the tree is never smaller than its viewport. 3499 * 3500 * @return whether the tree should track the width of the viewport 3501 * @see Scrollable#getScrollableTracksViewportWidth 3502 */ 3503 public boolean getScrollableTracksViewportWidth() { 3504 Container parent = SwingUtilities.getUnwrappedParent(this); 3505 if (parent instanceof JViewport) { 3506 return parent.getWidth() > getPreferredSize().width; 3507 } 3508 return false; 3509 } 3510 3511 /** 3512 * Returns false to indicate that the height of the viewport does not 3513 * determine the height of the table, unless the preferred height 3514 * of the tree is smaller than the viewports height. In other words: 3515 * ensure that the tree is never smaller than its viewport. 3516 * 3517 * @return whether the tree should track the height of the viewport 3518 * @see Scrollable#getScrollableTracksViewportHeight 3519 */ 3520 public boolean getScrollableTracksViewportHeight() { 3521 Container parent = SwingUtilities.getUnwrappedParent(this); 3522 if (parent instanceof JViewport) { 3523 return parent.getHeight() > getPreferredSize().height; 3524 } 3525 return false; 3526 } 3527 3528 /** 3529 * Sets the expanded state of this <code>JTree</code>. 3530 * If <code>state</code> is 3531 * true, all parents of <code>path</code> and path are marked as 3532 * expanded. If <code>state</code> is false, all parents of 3533 * <code>path</code> are marked EXPANDED, but <code>path</code> itself 3534 * is marked collapsed.<p> 3535 * This will fail if a <code>TreeWillExpandListener</code> vetos it. 3536 */ 3537 protected void setExpandedState(TreePath path, boolean state) { 3538 if(path != null) { 3539 // Make sure all parents of path are expanded. 3540 Stack<TreePath> stack; 3541 TreePath parentPath = path.getParentPath(); 3542 3543 if (expandedStack.size() == 0) { 3544 stack = new Stack<TreePath>(); 3545 } 3546 else { 3547 stack = expandedStack.pop(); 3548 } 3549 3550 try { 3551 while(parentPath != null) { 3552 if(isExpanded(parentPath)) { 3553 parentPath = null; 3554 } 3555 else { 3556 stack.push(parentPath); 3557 parentPath = parentPath.getParentPath(); 3558 } 3559 } 3560 for(int counter = stack.size() - 1; counter >= 0; counter--) { 3561 parentPath = stack.pop(); 3562 if(!isExpanded(parentPath)) { 3563 try { 3564 fireTreeWillExpand(parentPath); 3565 } catch (ExpandVetoException eve) { 3566 // Expand vetoed! 3567 return; 3568 } 3569 expandedState.put(parentPath, Boolean.TRUE); 3570 fireTreeExpanded(parentPath); 3571 if (accessibleContext != null) { 3572 ((AccessibleJTree)accessibleContext). 3573 fireVisibleDataPropertyChange(); 3574 } 3575 } 3576 } 3577 } 3578 finally { 3579 if (expandedStack.size() < TEMP_STACK_SIZE) { 3580 stack.removeAllElements(); 3581 expandedStack.push(stack); 3582 } 3583 } 3584 if(!state) { 3585 // collapse last path. 3586 Object cValue = expandedState.get(path); 3587 3588 if(cValue != null && ((Boolean)cValue).booleanValue()) { 3589 try { 3590 fireTreeWillCollapse(path); 3591 } 3592 catch (ExpandVetoException eve) { 3593 return; 3594 } 3595 expandedState.put(path, Boolean.FALSE); 3596 fireTreeCollapsed(path); 3597 if (removeDescendantSelectedPaths(path, false) && 3598 !isPathSelected(path)) { 3599 // A descendant was selected, select the parent. 3600 addSelectionPath(path); 3601 } 3602 if (accessibleContext != null) { 3603 ((AccessibleJTree)accessibleContext). 3604 fireVisibleDataPropertyChange(); 3605 } 3606 } 3607 } 3608 else { 3609 // Expand last path. 3610 Object cValue = expandedState.get(path); 3611 3612 if(cValue == null || !((Boolean)cValue).booleanValue()) { 3613 try { 3614 fireTreeWillExpand(path); 3615 } 3616 catch (ExpandVetoException eve) { 3617 return; 3618 } 3619 expandedState.put(path, Boolean.TRUE); 3620 fireTreeExpanded(path); 3621 if (accessibleContext != null) { 3622 ((AccessibleJTree)accessibleContext). 3623 fireVisibleDataPropertyChange(); 3624 } 3625 } 3626 } 3627 } 3628 } 3629 3630 /** 3631 * Returns an <code>Enumeration</code> of <code>TreePaths</code> 3632 * that have been expanded that 3633 * are descendants of <code>parent</code>. 3634 */ 3635 protected Enumeration<TreePath> 3636 getDescendantToggledPaths(TreePath parent) 3637 { 3638 if(parent == null) 3639 return null; 3640 3641 Vector<TreePath> descendants = new Vector<TreePath>(); 3642 Enumeration<TreePath> nodes = expandedState.keys(); 3643 3644 while(nodes.hasMoreElements()) { 3645 TreePath path = nodes.nextElement(); 3646 if(parent.isDescendant(path)) 3647 descendants.addElement(path); 3648 } 3649 return descendants.elements(); 3650 } 3651 3652 /** 3653 * Removes any descendants of the <code>TreePaths</code> in 3654 * <code>toRemove</code> 3655 * that have been expanded. 3656 * 3657 * @param toRemove an enumeration of the paths to remove; a value of 3658 * {@code null} is ignored 3659 * @throws ClassCastException if {@code toRemove} contains an 3660 * element that is not a {@code TreePath}; {@code null} 3661 * values are ignored 3662 */ 3663 protected void 3664 removeDescendantToggledPaths(Enumeration<TreePath> toRemove) 3665 { 3666 if(toRemove != null) { 3667 while(toRemove.hasMoreElements()) { 3668 Enumeration<?> descendants = getDescendantToggledPaths 3669 (toRemove.nextElement()); 3670 3671 if(descendants != null) { 3672 while(descendants.hasMoreElements()) { 3673 expandedState.remove(descendants.nextElement()); 3674 } 3675 } 3676 } 3677 } 3678 } 3679 3680 /** 3681 * Clears the cache of toggled tree paths. This does NOT send out 3682 * any <code>TreeExpansionListener</code> events. 3683 */ 3684 protected void clearToggledPaths() { 3685 expandedState.clear(); 3686 } 3687 3688 /** 3689 * Creates and returns an instance of <code>TreeModelHandler</code>. 3690 * The returned 3691 * object is responsible for updating the expanded state when the 3692 * <code>TreeModel</code> changes. 3693 * <p> 3694 * For more information on what expanded state means, see the 3695 * <a href=#jtree_description>JTree description</a> above. 3696 */ 3697 protected TreeModelListener createTreeModelListener() { 3698 return new TreeModelHandler(); 3699 } 3700 3701 /** 3702 * Removes any paths in the selection that are descendants of 3703 * <code>path</code>. If <code>includePath</code> is true and 3704 * <code>path</code> is selected, it will be removed from the selection. 3705 * 3706 * @return true if a descendant was selected 3707 * @since 1.3 3708 */ 3709 protected boolean removeDescendantSelectedPaths(TreePath path, 3710 boolean includePath) { 3711 TreePath[] toRemove = getDescendantSelectedPaths(path, includePath); 3712 3713 if (toRemove != null) { 3714 getSelectionModel().removeSelectionPaths(toRemove); 3715 return true; 3716 } 3717 return false; 3718 } 3719 3720 /** 3721 * Returns an array of paths in the selection that are descendants of 3722 * <code>path</code>. The returned array may contain <code>null</code>s. 3723 */ 3724 private TreePath[] getDescendantSelectedPaths(TreePath path, 3725 boolean includePath) { 3726 TreeSelectionModel sm = getSelectionModel(); 3727 TreePath[] selPaths = (sm != null) ? sm.getSelectionPaths() : 3728 null; 3729 3730 if(selPaths != null) { 3731 boolean shouldRemove = false; 3732 3733 for(int counter = selPaths.length - 1; counter >= 0; counter--) { 3734 if(selPaths[counter] != null && 3735 path.isDescendant(selPaths[counter]) && 3736 (!path.equals(selPaths[counter]) || includePath)) 3737 shouldRemove = true; 3738 else 3739 selPaths[counter] = null; 3740 } 3741 if(!shouldRemove) { 3742 selPaths = null; 3743 } 3744 return selPaths; 3745 } 3746 return null; 3747 } 3748 3749 /** 3750 * Removes any paths from the selection model that are descendants of 3751 * the nodes identified by in <code>e</code>. 3752 */ 3753 void removeDescendantSelectedPaths(TreeModelEvent e) { 3754 TreePath pPath = SwingUtilities2.getTreePath(e, getModel()); 3755 Object[] oldChildren = e.getChildren(); 3756 TreeSelectionModel sm = getSelectionModel(); 3757 3758 if (sm != null && pPath != null && oldChildren != null && 3759 oldChildren.length > 0) { 3760 for (int counter = oldChildren.length - 1; counter >= 0; 3761 counter--) { 3762 // Might be better to call getDescendantSelectedPaths 3763 // numerous times, then push to the model. 3764 removeDescendantSelectedPaths(pPath.pathByAddingChild 3765 (oldChildren[counter]), true); 3766 } 3767 } 3768 } 3769 3770 3771 /** 3772 * Listens to the model and updates the <code>expandedState</code> 3773 * accordingly when nodes are removed, or changed. 3774 */ 3775 protected class TreeModelHandler implements TreeModelListener { 3776 public void treeNodesChanged(TreeModelEvent e) { } 3777 3778 public void treeNodesInserted(TreeModelEvent e) { } 3779 3780 public void treeStructureChanged(TreeModelEvent e) { 3781 if(e == null) 3782 return; 3783 3784 // NOTE: If I change this to NOT remove the descendants 3785 // and update BasicTreeUIs treeStructureChanged method 3786 // to update descendants in response to a treeStructureChanged 3787 // event, all the children of the event won't collapse! 3788 TreePath parent = SwingUtilities2.getTreePath(e, getModel()); 3789 3790 if(parent == null) 3791 return; 3792 3793 if (parent.getPathCount() == 1) { 3794 // New root, remove everything! 3795 clearToggledPaths(); 3796 if(treeModel.getRoot() != null && 3797 !treeModel.isLeaf(treeModel.getRoot())) { 3798 // Mark the root as expanded, if it isn't a leaf. 3799 expandedState.put(parent, Boolean.TRUE); 3800 } 3801 } 3802 else if(expandedState.get(parent) != null) { 3803 Vector<TreePath> toRemove = new Vector<TreePath>(1); 3804 boolean isExpanded = isExpanded(parent); 3805 3806 toRemove.addElement(parent); 3807 removeDescendantToggledPaths(toRemove.elements()); 3808 if(isExpanded) { 3809 TreeModel model = getModel(); 3810 3811 if(model == null || model.isLeaf 3812 (parent.getLastPathComponent())) 3813 collapsePath(parent); 3814 else 3815 expandedState.put(parent, Boolean.TRUE); 3816 } 3817 } 3818 removeDescendantSelectedPaths(parent, false); 3819 } 3820 3821 public void treeNodesRemoved(TreeModelEvent e) { 3822 if(e == null) 3823 return; 3824 3825 TreePath parent = SwingUtilities2.getTreePath(e, getModel()); 3826 Object[] children = e.getChildren(); 3827 3828 if(children == null) 3829 return; 3830 3831 TreePath rPath; 3832 Vector<TreePath> toRemove 3833 = new Vector<TreePath>(Math.max(1, children.length)); 3834 3835 for(int counter = children.length - 1; counter >= 0; counter--) { 3836 rPath = parent.pathByAddingChild(children[counter]); 3837 if(expandedState.get(rPath) != null) 3838 toRemove.addElement(rPath); 3839 } 3840 if(toRemove.size() > 0) 3841 removeDescendantToggledPaths(toRemove.elements()); 3842 3843 TreeModel model = getModel(); 3844 3845 if(model == null || model.isLeaf(parent.getLastPathComponent())) 3846 expandedState.remove(parent); 3847 3848 removeDescendantSelectedPaths(e); 3849 } 3850 } 3851 3852 3853 /** 3854 * <code>DynamicUtilTreeNode</code> can wrap 3855 * vectors/hashtables/arrays/strings and 3856 * create the appropriate children tree nodes as necessary. It is 3857 * dynamic in that it will only create the children as necessary. 3858 * <p> 3859 * <strong>Warning:</strong> 3860 * Serialized objects of this class will not be compatible with 3861 * future Swing releases. The current serialization support is 3862 * appropriate for short term storage or RMI between applications running 3863 * the same version of Swing. As of 1.4, support for long term storage 3864 * of all JavaBeans<sup><font size="-2">TM</font></sup> 3865 * has been added to the <code>java.beans</code> package. 3866 * Please see {@link java.beans.XMLEncoder}. 3867 */ 3868 @SuppressWarnings("serial") 3869 public static class DynamicUtilTreeNode extends DefaultMutableTreeNode { 3870 /** 3871 * Does the this <code>JTree</code> have children? 3872 * This property is currently not implemented. 3873 */ 3874 protected boolean hasChildren; 3875 /** Value to create children with. */ 3876 protected Object childValue; 3877 /** Have the children been loaded yet? */ 3878 protected boolean loadedChildren; 3879 3880 /** 3881 * Adds to parent all the children in <code>children</code>. 3882 * If <code>children</code> is an array or vector all of its 3883 * elements are added is children, otherwise if <code>children</code> 3884 * is a hashtable all the key/value pairs are added in the order 3885 * <code>Enumeration</code> returns them. 3886 */ 3887 public static void createChildren(DefaultMutableTreeNode parent, 3888 Object children) { 3889 if(children instanceof Vector) { 3890 Vector<?> childVector = (Vector)children; 3891 3892 for(int counter = 0, maxCounter = childVector.size(); 3893 counter < maxCounter; counter++) 3894 parent.add(new DynamicUtilTreeNode 3895 (childVector.elementAt(counter), 3896 childVector.elementAt(counter))); 3897 } 3898 else if(children instanceof Hashtable) { 3899 Hashtable<?,?> childHT = (Hashtable)children; 3900 Enumeration<?> keys = childHT.keys(); 3901 Object aKey; 3902 3903 while(keys.hasMoreElements()) { 3904 aKey = keys.nextElement(); 3905 parent.add(new DynamicUtilTreeNode(aKey, 3906 childHT.get(aKey))); 3907 } 3908 } 3909 else if(children instanceof Object[]) { 3910 Object[] childArray = (Object[])children; 3911 3912 for(int counter = 0, maxCounter = childArray.length; 3913 counter < maxCounter; counter++) 3914 parent.add(new DynamicUtilTreeNode(childArray[counter], 3915 childArray[counter])); 3916 } 3917 } 3918 3919 /** 3920 * Creates a node with the specified object as its value and 3921 * with the specified children. For the node to allow children, 3922 * the children-object must be an array of objects, a 3923 * <code>Vector</code>, or a <code>Hashtable</code> -- even 3924 * if empty. Otherwise, the node is not 3925 * allowed to have children. 3926 * 3927 * @param value the <code>Object</code> that is the value for the 3928 * new node 3929 * @param children an array of <code>Object</code>s, a 3930 * <code>Vector</code>, or a <code>Hashtable</code> 3931 * used to create the child nodes; if any other 3932 * object is specified, or if the value is 3933 * <code>null</code>, 3934 * then the node is not allowed to have children 3935 */ 3936 public DynamicUtilTreeNode(Object value, Object children) { 3937 super(value); 3938 loadedChildren = false; 3939 childValue = children; 3940 if(children != null) { 3941 if(children instanceof Vector) 3942 setAllowsChildren(true); 3943 else if(children instanceof Hashtable) 3944 setAllowsChildren(true); 3945 else if(children instanceof Object[]) 3946 setAllowsChildren(true); 3947 else 3948 setAllowsChildren(false); 3949 } 3950 else 3951 setAllowsChildren(false); 3952 } 3953 3954 /** 3955 * Returns true if this node allows children. Whether the node 3956 * allows children depends on how it was created. 3957 * 3958 * @return true if this node allows children, false otherwise 3959 * @see #JTree.DynamicUtilTreeNode 3960 */ 3961 public boolean isLeaf() { 3962 return !getAllowsChildren(); 3963 } 3964 3965 /** 3966 * Returns the number of child nodes. 3967 * 3968 * @return the number of child nodes 3969 */ 3970 public int getChildCount() { 3971 if(!loadedChildren) 3972 loadChildren(); 3973 return super.getChildCount(); 3974 } 3975 3976 /** 3977 * Loads the children based on <code>childValue</code>. 3978 * If <code>childValue</code> is a <code>Vector</code> 3979 * or array each element is added as a child, 3980 * if <code>childValue</code> is a <code>Hashtable</code> 3981 * each key/value pair is added in the order that 3982 * <code>Enumeration</code> returns the keys. 3983 */ 3984 protected void loadChildren() { 3985 loadedChildren = true; 3986 createChildren(this, childValue); 3987 } 3988 3989 /** 3990 * Subclassed to load the children, if necessary. 3991 */ 3992 public TreeNode getChildAt(int index) { 3993 if(!loadedChildren) 3994 loadChildren(); 3995 return super.getChildAt(index); 3996 } 3997 3998 /** 3999 * Subclassed to load the children, if necessary. 4000 */ 4001 public Enumeration children() { 4002 if(!loadedChildren) 4003 loadChildren(); 4004 return super.children(); 4005 } 4006 } 4007 4008 void setUIProperty(String propertyName, Object value) { 4009 if (propertyName == "rowHeight") { 4010 if (!rowHeightSet) { 4011 setRowHeight(((Number)value).intValue()); 4012 rowHeightSet = false; 4013 } 4014 } else if (propertyName == "scrollsOnExpand") { 4015 if (!scrollsOnExpandSet) { 4016 setScrollsOnExpand(((Boolean)value).booleanValue()); 4017 scrollsOnExpandSet = false; 4018 } 4019 } else if (propertyName == "showsRootHandles") { 4020 if (!showsRootHandlesSet) { 4021 setShowsRootHandles(((Boolean)value).booleanValue()); 4022 showsRootHandlesSet = false; 4023 } 4024 } else { 4025 super.setUIProperty(propertyName, value); 4026 } 4027 } 4028 4029 4030 /** 4031 * Returns a string representation of this <code>JTree</code>. 4032 * This method 4033 * is intended to be used only for debugging purposes, and the 4034 * content and format of the returned string may vary between 4035 * implementations. The returned string may be empty but may not 4036 * be <code>null</code>. 4037 * 4038 * @return a string representation of this <code>JTree</code>. 4039 */ 4040 protected String paramString() { 4041 String rootVisibleString = (rootVisible ? 4042 "true" : "false"); 4043 String showsRootHandlesString = (showsRootHandles ? 4044 "true" : "false"); 4045 String editableString = (editable ? 4046 "true" : "false"); 4047 String largeModelString = (largeModel ? 4048 "true" : "false"); 4049 String invokesStopCellEditingString = (invokesStopCellEditing ? 4050 "true" : "false"); 4051 String scrollsOnExpandString = (scrollsOnExpand ? 4052 "true" : "false"); 4053 4054 return super.paramString() + 4055 ",editable=" + editableString + 4056 ",invokesStopCellEditing=" + invokesStopCellEditingString + 4057 ",largeModel=" + largeModelString + 4058 ",rootVisible=" + rootVisibleString + 4059 ",rowHeight=" + rowHeight + 4060 ",scrollsOnExpand=" + scrollsOnExpandString + 4061 ",showsRootHandles=" + showsRootHandlesString + 4062 ",toggleClickCount=" + toggleClickCount + 4063 ",visibleRowCount=" + visibleRowCount; 4064 } 4065 4066 ///////////////// 4067 // Accessibility support 4068 //////////////// 4069 4070 /** 4071 * Gets the AccessibleContext associated with this JTree. 4072 * For JTrees, the AccessibleContext takes the form of an 4073 * AccessibleJTree. 4074 * A new AccessibleJTree instance is created if necessary. 4075 * 4076 * @return an AccessibleJTree that serves as the 4077 * AccessibleContext of this JTree 4078 */ 4079 public AccessibleContext getAccessibleContext() { 4080 if (accessibleContext == null) { 4081 accessibleContext = new AccessibleJTree(); 4082 } 4083 return accessibleContext; 4084 } 4085 4086 /** 4087 * This class implements accessibility support for the 4088 * <code>JTree</code> class. It provides an implementation of the 4089 * Java Accessibility API appropriate to tree user-interface elements. 4090 * <p> 4091 * <strong>Warning:</strong> 4092 * Serialized objects of this class will not be compatible with 4093 * future Swing releases. The current serialization support is 4094 * appropriate for short term storage or RMI between applications running 4095 * the same version of Swing. As of 1.4, support for long term storage 4096 * of all JavaBeans<sup><font size="-2">TM</font></sup> 4097 * has been added to the <code>java.beans</code> package. 4098 * Please see {@link java.beans.XMLEncoder}. 4099 */ 4100 @SuppressWarnings("serial") 4101 protected class AccessibleJTree extends AccessibleJComponent 4102 implements AccessibleSelection, TreeSelectionListener, 4103 TreeModelListener, TreeExpansionListener { 4104 4105 TreePath leadSelectionPath; 4106 Accessible leadSelectionAccessible; 4107 4108 public AccessibleJTree() { 4109 // Add a tree model listener for JTree 4110 TreeModel model = JTree.this.getModel(); 4111 if (model != null) { 4112 model.addTreeModelListener(this); 4113 } 4114 JTree.this.addTreeExpansionListener(this); 4115 JTree.this.addTreeSelectionListener(this); 4116 leadSelectionPath = JTree.this.getLeadSelectionPath(); 4117 leadSelectionAccessible = (leadSelectionPath != null) 4118 ? new AccessibleJTreeNode(JTree.this, 4119 leadSelectionPath, 4120 JTree.this) 4121 : null; 4122 } 4123 4124 /** 4125 * Tree Selection Listener value change method. Used to fire the 4126 * property change 4127 * 4128 * @param e ListSelectionEvent 4129 * 4130 */ 4131 public void valueChanged(TreeSelectionEvent e) { 4132 // Fixes 4546503 - JTree is sending incorrect active 4133 // descendant events 4134 TreePath oldLeadSelectionPath = e.getOldLeadSelectionPath(); 4135 leadSelectionPath = e.getNewLeadSelectionPath(); 4136 4137 if (oldLeadSelectionPath != leadSelectionPath) { 4138 // Set parent to null so AccessibleJTreeNode computes 4139 // its parent. 4140 Accessible oldLSA = leadSelectionAccessible; 4141 leadSelectionAccessible = (leadSelectionPath != null) 4142 ? new AccessibleJTreeNode(JTree.this, 4143 leadSelectionPath, 4144 null) // parent 4145 : null; 4146 firePropertyChange(AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY, 4147 oldLSA, leadSelectionAccessible); 4148 } 4149 firePropertyChange(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY, 4150 Boolean.valueOf(false), Boolean.valueOf(true)); 4151 } 4152 4153 /** 4154 * Fire a visible data property change notification. 4155 * A 'visible' data property is one that represents 4156 * something about the way the component appears on the 4157 * display, where that appearance isn't bound to any other 4158 * property. It notifies screen readers that the visual 4159 * appearance of the component has changed, so they can 4160 * notify the user. 4161 */ 4162 public void fireVisibleDataPropertyChange() { 4163 firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, 4164 Boolean.valueOf(false), Boolean.valueOf(true)); 4165 } 4166 4167 // Fire the visible data changes for the model changes. 4168 4169 /** 4170 * Tree Model Node change notification. 4171 * 4172 * @param e a Tree Model event 4173 */ 4174 public void treeNodesChanged(TreeModelEvent e) { 4175 fireVisibleDataPropertyChange(); 4176 } 4177 4178 /** 4179 * Tree Model Node change notification. 4180 * 4181 * @param e a Tree node insertion event 4182 */ 4183 public void treeNodesInserted(TreeModelEvent e) { 4184 fireVisibleDataPropertyChange(); 4185 } 4186 4187 /** 4188 * Tree Model Node change notification. 4189 * 4190 * @param e a Tree node(s) removal event 4191 */ 4192 public void treeNodesRemoved(TreeModelEvent e) { 4193 fireVisibleDataPropertyChange(); 4194 } 4195 4196 /** 4197 * Tree Model structure change change notification. 4198 * 4199 * @param e a Tree Model event 4200 */ 4201 public void treeStructureChanged(TreeModelEvent e) { 4202 fireVisibleDataPropertyChange(); 4203 } 4204 4205 /** 4206 * Tree Collapsed notification. 4207 * 4208 * @param e a TreeExpansionEvent 4209 */ 4210 public void treeCollapsed(TreeExpansionEvent e) { 4211 fireVisibleDataPropertyChange(); 4212 TreePath path = e.getPath(); 4213 if (path != null) { 4214 // Set parent to null so AccessibleJTreeNode computes 4215 // its parent. 4216 AccessibleJTreeNode node = new AccessibleJTreeNode(JTree.this, 4217 path, 4218 null); 4219 PropertyChangeEvent pce = new PropertyChangeEvent(node, 4220 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 4221 AccessibleState.EXPANDED, 4222 AccessibleState.COLLAPSED); 4223 firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 4224 null, pce); 4225 } 4226 } 4227 4228 /** 4229 * Tree Model Expansion notification. 4230 * 4231 * @param e a Tree node insertion event 4232 */ 4233 public void treeExpanded(TreeExpansionEvent e) { 4234 fireVisibleDataPropertyChange(); 4235 TreePath path = e.getPath(); 4236 if (path != null) { 4237 // TIGER - 4839971 4238 // Set parent to null so AccessibleJTreeNode computes 4239 // its parent. 4240 AccessibleJTreeNode node = new AccessibleJTreeNode(JTree.this, 4241 path, 4242 null); 4243 PropertyChangeEvent pce = new PropertyChangeEvent(node, 4244 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 4245 AccessibleState.COLLAPSED, 4246 AccessibleState.EXPANDED); 4247 firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 4248 null, pce); 4249 } 4250 } 4251 4252 4253 private AccessibleContext getCurrentAccessibleContext() { 4254 Component c = getCurrentComponent(); 4255 if (c instanceof Accessible) { 4256 return c.getAccessibleContext(); 4257 } else { 4258 return null; 4259 } 4260 } 4261 4262 private Component getCurrentComponent() { 4263 // is the object visible? 4264 // if so, get row, selected, focus & leaf state, 4265 // and then get the renderer component and return it 4266 TreeModel model = JTree.this.getModel(); 4267 if (model == null) { 4268 return null; 4269 } 4270 TreePath path = new TreePath(model.getRoot()); 4271 if (JTree.this.isVisible(path)) { 4272 TreeCellRenderer r = JTree.this.getCellRenderer(); 4273 TreeUI ui = JTree.this.getUI(); 4274 if (ui != null) { 4275 int row = ui.getRowForPath(JTree.this, path); 4276 int lsr = JTree.this.getLeadSelectionRow(); 4277 boolean hasFocus = JTree.this.isFocusOwner() 4278 && (lsr == row); 4279 boolean selected = JTree.this.isPathSelected(path); 4280 boolean expanded = JTree.this.isExpanded(path); 4281 4282 return r.getTreeCellRendererComponent(JTree.this, 4283 model.getRoot(), selected, expanded, 4284 model.isLeaf(model.getRoot()), row, hasFocus); 4285 } 4286 } 4287 return null; 4288 } 4289 4290 // Overridden methods from AccessibleJComponent 4291 4292 /** 4293 * Get the role of this object. 4294 * 4295 * @return an instance of AccessibleRole describing the role of the 4296 * object 4297 * @see AccessibleRole 4298 */ 4299 public AccessibleRole getAccessibleRole() { 4300 return AccessibleRole.TREE; 4301 } 4302 4303 /** 4304 * Returns the <code>Accessible</code> child, if one exists, 4305 * contained at the local coordinate <code>Point</code>. 4306 * Otherwise returns <code>null</code>. 4307 * 4308 * @param p point in local coordinates of this <code>Accessible</code> 4309 * @return the <code>Accessible</code>, if it exists, 4310 * at the specified location; else <code>null</code> 4311 */ 4312 public Accessible getAccessibleAt(Point p) { 4313 TreePath path = getClosestPathForLocation(p.x, p.y); 4314 if (path != null) { 4315 // JTree.this is NOT the parent; parent will get computed later 4316 return new AccessibleJTreeNode(JTree.this, path, null); 4317 } else { 4318 return null; 4319 } 4320 } 4321 4322 /** 4323 * Returns the number of top-level children nodes of this 4324 * JTree. Each of these nodes may in turn have children nodes. 4325 * 4326 * @return the number of accessible children nodes in the tree. 4327 */ 4328 public int getAccessibleChildrenCount() { 4329 TreeModel model = JTree.this.getModel(); 4330 if (model == null) { 4331 return 0; 4332 } 4333 if (isRootVisible()) { 4334 return 1; // the root node 4335 } 4336 4337 // return the root's first set of children count 4338 return model.getChildCount(model.getRoot()); 4339 } 4340 4341 /** 4342 * Return the nth Accessible child of the object. 4343 * 4344 * @param i zero-based index of child 4345 * @return the nth Accessible child of the object 4346 */ 4347 public Accessible getAccessibleChild(int i) { 4348 TreeModel model = JTree.this.getModel(); 4349 if (model == null) { 4350 return null; 4351 } 4352 if (isRootVisible()) { 4353 if (i == 0) { // return the root node Accessible 4354 Object[] objPath = { model.getRoot() }; 4355 TreePath path = new TreePath(objPath); 4356 return new AccessibleJTreeNode(JTree.this, path, JTree.this); 4357 } else { 4358 return null; 4359 } 4360 } 4361 4362 // return Accessible for one of root's child nodes 4363 int count = model.getChildCount(model.getRoot()); 4364 if (i < 0 || i >= count) { 4365 return null; 4366 } 4367 Object obj = model.getChild(model.getRoot(), i); 4368 Object[] objPath = { model.getRoot(), obj }; 4369 TreePath path = new TreePath(objPath); 4370 return new AccessibleJTreeNode(JTree.this, path, JTree.this); 4371 } 4372 4373 /** 4374 * Get the index of this object in its accessible parent. 4375 * 4376 * @return the index of this object in its parent. Since a JTree 4377 * top-level object does not have an accessible parent. 4378 * @see #getAccessibleParent 4379 */ 4380 public int getAccessibleIndexInParent() { 4381 // didn't ever need to override this... 4382 return super.getAccessibleIndexInParent(); 4383 } 4384 4385 // AccessibleSelection methods 4386 /** 4387 * Get the AccessibleSelection associated with this object. In the 4388 * implementation of the Java Accessibility API for this class, 4389 * return this object, which is responsible for implementing the 4390 * AccessibleSelection interface on behalf of itself. 4391 * 4392 * @return this object 4393 */ 4394 public AccessibleSelection getAccessibleSelection() { 4395 return this; 4396 } 4397 4398 /** 4399 * Returns the number of items currently selected. 4400 * If no items are selected, the return value will be 0. 4401 * 4402 * @return the number of items currently selected. 4403 */ 4404 public int getAccessibleSelectionCount() { 4405 Object[] rootPath = new Object[1]; 4406 rootPath[0] = treeModel.getRoot(); 4407 TreePath childPath = new TreePath(rootPath); 4408 if (JTree.this.isPathSelected(childPath)) { 4409 return 1; 4410 } else { 4411 return 0; 4412 } 4413 } 4414 4415 /** 4416 * Returns an Accessible representing the specified selected item 4417 * in the object. If there isn't a selection, or there are 4418 * fewer items selected than the integer passed in, the return 4419 * value will be null. 4420 * 4421 * @param i the zero-based index of selected items 4422 * @return an Accessible containing the selected item 4423 */ 4424 public Accessible getAccessibleSelection(int i) { 4425 // The JTree can have only one accessible child, the root. 4426 if (i == 0) { 4427 Object[] rootPath = new Object[1]; 4428 rootPath[0] = treeModel.getRoot(); 4429 TreePath childPath = new TreePath(rootPath); 4430 if (JTree.this.isPathSelected(childPath)) { 4431 return new AccessibleJTreeNode(JTree.this, childPath, JTree.this); 4432 } 4433 } 4434 return null; 4435 } 4436 4437 /** 4438 * Returns true if the current child of this object is selected. 4439 * 4440 * @param i the zero-based index of the child in this Accessible object. 4441 * @see AccessibleContext#getAccessibleChild 4442 */ 4443 public boolean isAccessibleChildSelected(int i) { 4444 // The JTree can have only one accessible child, the root. 4445 if (i == 0) { 4446 Object[] rootPath = new Object[1]; 4447 rootPath[0] = treeModel.getRoot(); 4448 TreePath childPath = new TreePath(rootPath); 4449 return JTree.this.isPathSelected(childPath); 4450 } else { 4451 return false; 4452 } 4453 } 4454 4455 /** 4456 * Adds the specified selected item in the object to the object's 4457 * selection. If the object supports multiple selections, 4458 * the specified item is added to any existing selection, otherwise 4459 * it replaces any existing selection in the object. If the 4460 * specified item is already selected, this method has no effect. 4461 * 4462 * @param i the zero-based index of selectable items 4463 */ 4464 public void addAccessibleSelection(int i) { 4465 TreeModel model = JTree.this.getModel(); 4466 if (model != null) { 4467 if (i == 0) { 4468 Object[] objPath = {model.getRoot()}; 4469 TreePath path = new TreePath(objPath); 4470 JTree.this.addSelectionPath(path); 4471 } 4472 } 4473 } 4474 4475 /** 4476 * Removes the specified selected item in the object from the object's 4477 * selection. If the specified item isn't currently selected, this 4478 * method has no effect. 4479 * 4480 * @param i the zero-based index of selectable items 4481 */ 4482 public void removeAccessibleSelection(int i) { 4483 TreeModel model = JTree.this.getModel(); 4484 if (model != null) { 4485 if (i == 0) { 4486 Object[] objPath = {model.getRoot()}; 4487 TreePath path = new TreePath(objPath); 4488 JTree.this.removeSelectionPath(path); 4489 } 4490 } 4491 } 4492 4493 /** 4494 * Clears the selection in the object, so that nothing in the 4495 * object is selected. 4496 */ 4497 public void clearAccessibleSelection() { 4498 int childCount = getAccessibleChildrenCount(); 4499 for (int i = 0; i < childCount; i++) { 4500 removeAccessibleSelection(i); 4501 } 4502 } 4503 4504 /** 4505 * Causes every selected item in the object to be selected 4506 * if the object supports multiple selections. 4507 */ 4508 public void selectAllAccessibleSelection() { 4509 TreeModel model = JTree.this.getModel(); 4510 if (model != null) { 4511 Object[] objPath = {model.getRoot()}; 4512 TreePath path = new TreePath(objPath); 4513 JTree.this.addSelectionPath(path); 4514 } 4515 } 4516 4517 /** 4518 * This class implements accessibility support for the 4519 * <code>JTree</code> child. It provides an implementation of the 4520 * Java Accessibility API appropriate to tree nodes. 4521 */ 4522 protected class AccessibleJTreeNode extends AccessibleContext 4523 implements Accessible, AccessibleComponent, AccessibleSelection, 4524 AccessibleAction { 4525 4526 private JTree tree = null; 4527 private TreeModel treeModel = null; 4528 private Object obj = null; 4529 private TreePath path = null; 4530 private Accessible accessibleParent = null; 4531 private int index = 0; 4532 private boolean isLeaf = false; 4533 4534 /** 4535 * Constructs an AccessibleJTreeNode 4536 * @since 1.4 4537 */ 4538 public AccessibleJTreeNode(JTree t, TreePath p, Accessible ap) { 4539 tree = t; 4540 path = p; 4541 accessibleParent = ap; 4542 treeModel = t.getModel(); 4543 obj = p.getLastPathComponent(); 4544 if (treeModel != null) { 4545 isLeaf = treeModel.isLeaf(obj); 4546 } 4547 } 4548 4549 private TreePath getChildTreePath(int i) { 4550 // Tree nodes can't be so complex that they have 4551 // two sets of children -> we're ignoring that case 4552 if (i < 0 || i >= getAccessibleChildrenCount()) { 4553 return null; 4554 } else { 4555 Object childObj = treeModel.getChild(obj, i); 4556 Object[] objPath = path.getPath(); 4557 Object[] objChildPath = new Object[objPath.length+1]; 4558 java.lang.System.arraycopy(objPath, 0, objChildPath, 0, objPath.length); 4559 objChildPath[objChildPath.length-1] = childObj; 4560 return new TreePath(objChildPath); 4561 } 4562 } 4563 4564 /** 4565 * Get the AccessibleContext associated with this tree node. 4566 * In the implementation of the Java Accessibility API for 4567 * this class, return this object, which is its own 4568 * AccessibleContext. 4569 * 4570 * @return this object 4571 */ 4572 public AccessibleContext getAccessibleContext() { 4573 return this; 4574 } 4575 4576 private AccessibleContext getCurrentAccessibleContext() { 4577 Component c = getCurrentComponent(); 4578 if (c instanceof Accessible) { 4579 return c.getAccessibleContext(); 4580 } else { 4581 return null; 4582 } 4583 } 4584 4585 private Component getCurrentComponent() { 4586 // is the object visible? 4587 // if so, get row, selected, focus & leaf state, 4588 // and then get the renderer component and return it 4589 if (tree.isVisible(path)) { 4590 TreeCellRenderer r = tree.getCellRenderer(); 4591 if (r == null) { 4592 return null; 4593 } 4594 TreeUI ui = tree.getUI(); 4595 if (ui != null) { 4596 int row = ui.getRowForPath(JTree.this, path); 4597 boolean selected = tree.isPathSelected(path); 4598 boolean expanded = tree.isExpanded(path); 4599 boolean hasFocus = false; // how to tell?? -PK 4600 return r.getTreeCellRendererComponent(tree, obj, 4601 selected, expanded, isLeaf, row, hasFocus); 4602 } 4603 } 4604 return null; 4605 } 4606 4607 // AccessibleContext methods 4608 4609 /** 4610 * Get the accessible name of this object. 4611 * 4612 * @return the localized name of the object; null if this 4613 * object does not have a name 4614 */ 4615 public String getAccessibleName() { 4616 AccessibleContext ac = getCurrentAccessibleContext(); 4617 if (ac != null) { 4618 String name = ac.getAccessibleName(); 4619 if ((name != null) && (name != "")) { 4620 return ac.getAccessibleName(); 4621 } else { 4622 return null; 4623 } 4624 } 4625 if ((accessibleName != null) && (accessibleName != "")) { 4626 return accessibleName; 4627 } else { 4628 // fall back to the client property 4629 return (String)getClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY); 4630 } 4631 } 4632 4633 /** 4634 * Set the localized accessible name of this object. 4635 * 4636 * @param s the new localized name of the object. 4637 */ 4638 public void setAccessibleName(String s) { 4639 AccessibleContext ac = getCurrentAccessibleContext(); 4640 if (ac != null) { 4641 ac.setAccessibleName(s); 4642 } else { 4643 super.setAccessibleName(s); 4644 } 4645 } 4646 4647 // 4648 // *** should check tooltip text for desc. (needs MouseEvent) 4649 // 4650 /** 4651 * Get the accessible description of this object. 4652 * 4653 * @return the localized description of the object; null if 4654 * this object does not have a description 4655 */ 4656 public String getAccessibleDescription() { 4657 AccessibleContext ac = getCurrentAccessibleContext(); 4658 if (ac != null) { 4659 return ac.getAccessibleDescription(); 4660 } else { 4661 return super.getAccessibleDescription(); 4662 } 4663 } 4664 4665 /** 4666 * Set the accessible description of this object. 4667 * 4668 * @param s the new localized description of the object 4669 */ 4670 public void setAccessibleDescription(String s) { 4671 AccessibleContext ac = getCurrentAccessibleContext(); 4672 if (ac != null) { 4673 ac.setAccessibleDescription(s); 4674 } else { 4675 super.setAccessibleDescription(s); 4676 } 4677 } 4678 4679 /** 4680 * Get the role of this object. 4681 * 4682 * @return an instance of AccessibleRole describing the role of the object 4683 * @see AccessibleRole 4684 */ 4685 public AccessibleRole getAccessibleRole() { 4686 AccessibleContext ac = getCurrentAccessibleContext(); 4687 if (ac != null) { 4688 return ac.getAccessibleRole(); 4689 } else { 4690 return AccessibleRole.UNKNOWN; 4691 } 4692 } 4693 4694 /** 4695 * Get the state set of this object. 4696 * 4697 * @return an instance of AccessibleStateSet containing the 4698 * current state set of the object 4699 * @see AccessibleState 4700 */ 4701 public AccessibleStateSet getAccessibleStateSet() { 4702 AccessibleContext ac = getCurrentAccessibleContext(); 4703 AccessibleStateSet states; 4704 if (ac != null) { 4705 states = ac.getAccessibleStateSet(); 4706 } else { 4707 states = new AccessibleStateSet(); 4708 } 4709 // need to test here, 'cause the underlying component 4710 // is a cellRenderer, which is never showing... 4711 if (isShowing()) { 4712 states.add(AccessibleState.SHOWING); 4713 } else if (states.contains(AccessibleState.SHOWING)) { 4714 states.remove(AccessibleState.SHOWING); 4715 } 4716 if (isVisible()) { 4717 states.add(AccessibleState.VISIBLE); 4718 } else if (states.contains(AccessibleState.VISIBLE)) { 4719 states.remove(AccessibleState.VISIBLE); 4720 } 4721 if (tree.isPathSelected(path)){ 4722 states.add(AccessibleState.SELECTED); 4723 } 4724 if (path == getLeadSelectionPath()) { 4725 states.add(AccessibleState.ACTIVE); 4726 } 4727 if (!isLeaf) { 4728 states.add(AccessibleState.EXPANDABLE); 4729 } 4730 if (tree.isExpanded(path)) { 4731 states.add(AccessibleState.EXPANDED); 4732 } else { 4733 states.add(AccessibleState.COLLAPSED); 4734 } 4735 if (tree.isEditable()) { 4736 states.add(AccessibleState.EDITABLE); 4737 } 4738 return states; 4739 } 4740 4741 /** 4742 * Get the Accessible parent of this object. 4743 * 4744 * @return the Accessible parent of this object; null if this 4745 * object does not have an Accessible parent 4746 */ 4747 public Accessible getAccessibleParent() { 4748 // someone wants to know, so we need to create our parent 4749 // if we don't have one (hey, we're a talented kid!) 4750 if (accessibleParent == null) { 4751 Object[] objPath = path.getPath(); 4752 if (objPath.length > 1) { 4753 Object objParent = objPath[objPath.length-2]; 4754 if (treeModel != null) { 4755 index = treeModel.getIndexOfChild(objParent, obj); 4756 } 4757 Object[] objParentPath = new Object[objPath.length-1]; 4758 java.lang.System.arraycopy(objPath, 0, objParentPath, 4759 0, objPath.length-1); 4760 TreePath parentPath = new TreePath(objParentPath); 4761 accessibleParent = new AccessibleJTreeNode(tree, 4762 parentPath, 4763 null); 4764 this.setAccessibleParent(accessibleParent); 4765 } else if (treeModel != null) { 4766 accessibleParent = tree; // we're the top! 4767 index = 0; // we're an only child! 4768 this.setAccessibleParent(accessibleParent); 4769 } 4770 } 4771 return accessibleParent; 4772 } 4773 4774 /** 4775 * Get the index of this object in its accessible parent. 4776 * 4777 * @return the index of this object in its parent; -1 if this 4778 * object does not have an accessible parent. 4779 * @see #getAccessibleParent 4780 */ 4781 public int getAccessibleIndexInParent() { 4782 // index is invalid 'till we have an accessibleParent... 4783 if (accessibleParent == null) { 4784 getAccessibleParent(); 4785 } 4786 Object[] objPath = path.getPath(); 4787 if (objPath.length > 1) { 4788 Object objParent = objPath[objPath.length-2]; 4789 if (treeModel != null) { 4790 index = treeModel.getIndexOfChild(objParent, obj); 4791 } 4792 } 4793 return index; 4794 } 4795 4796 /** 4797 * Returns the number of accessible children in the object. 4798 * 4799 * @return the number of accessible children in the object. 4800 */ 4801 public int getAccessibleChildrenCount() { 4802 // Tree nodes can't be so complex that they have 4803 // two sets of children -> we're ignoring that case 4804 return treeModel.getChildCount(obj); 4805 } 4806 4807 /** 4808 * Return the specified Accessible child of the object. 4809 * 4810 * @param i zero-based index of child 4811 * @return the Accessible child of the object 4812 */ 4813 public Accessible getAccessibleChild(int i) { 4814 // Tree nodes can't be so complex that they have 4815 // two sets of children -> we're ignoring that case 4816 if (i < 0 || i >= getAccessibleChildrenCount()) { 4817 return null; 4818 } else { 4819 Object childObj = treeModel.getChild(obj, i); 4820 Object[] objPath = path.getPath(); 4821 Object[] objChildPath = new Object[objPath.length+1]; 4822 java.lang.System.arraycopy(objPath, 0, objChildPath, 0, objPath.length); 4823 objChildPath[objChildPath.length-1] = childObj; 4824 TreePath childPath = new TreePath(objChildPath); 4825 return new AccessibleJTreeNode(JTree.this, childPath, this); 4826 } 4827 } 4828 4829 /** 4830 * Gets the locale of the component. If the component does not have 4831 * a locale, then the locale of its parent is returned. 4832 * 4833 * @return This component's locale. If this component does not have 4834 * a locale, the locale of its parent is returned. 4835 * @exception IllegalComponentStateException 4836 * If the Component does not have its own locale and has not yet 4837 * been added to a containment hierarchy such that the locale can be 4838 * determined from the containing parent. 4839 * @see #setLocale 4840 */ 4841 public Locale getLocale() { 4842 AccessibleContext ac = getCurrentAccessibleContext(); 4843 if (ac != null) { 4844 return ac.getLocale(); 4845 } else { 4846 return tree.getLocale(); 4847 } 4848 } 4849 4850 /** 4851 * Add a PropertyChangeListener to the listener list. 4852 * The listener is registered for all properties. 4853 * 4854 * @param l The PropertyChangeListener to be added 4855 */ 4856 public void addPropertyChangeListener(PropertyChangeListener l) { 4857 AccessibleContext ac = getCurrentAccessibleContext(); 4858 if (ac != null) { 4859 ac.addPropertyChangeListener(l); 4860 } else { 4861 super.addPropertyChangeListener(l); 4862 } 4863 } 4864 4865 /** 4866 * Remove a PropertyChangeListener from the listener list. 4867 * This removes a PropertyChangeListener that was registered 4868 * for all properties. 4869 * 4870 * @param l The PropertyChangeListener to be removed 4871 */ 4872 public void removePropertyChangeListener(PropertyChangeListener l) { 4873 AccessibleContext ac = getCurrentAccessibleContext(); 4874 if (ac != null) { 4875 ac.removePropertyChangeListener(l); 4876 } else { 4877 super.removePropertyChangeListener(l); 4878 } 4879 } 4880 4881 /** 4882 * Get the AccessibleAction associated with this object. In the 4883 * implementation of the Java Accessibility API for this class, 4884 * return this object, which is responsible for implementing the 4885 * AccessibleAction interface on behalf of itself. 4886 * 4887 * @return this object 4888 */ 4889 public AccessibleAction getAccessibleAction() { 4890 return this; 4891 } 4892 4893 /** 4894 * Get the AccessibleComponent associated with this object. In the 4895 * implementation of the Java Accessibility API for this class, 4896 * return this object, which is responsible for implementing the 4897 * AccessibleComponent interface on behalf of itself. 4898 * 4899 * @return this object 4900 */ 4901 public AccessibleComponent getAccessibleComponent() { 4902 return this; // to override getBounds() 4903 } 4904 4905 /** 4906 * Get the AccessibleSelection associated with this object if one 4907 * exists. Otherwise return null. 4908 * 4909 * @return the AccessibleSelection, or null 4910 */ 4911 public AccessibleSelection getAccessibleSelection() { 4912 AccessibleContext ac = getCurrentAccessibleContext(); 4913 if (ac != null && isLeaf) { 4914 return getCurrentAccessibleContext().getAccessibleSelection(); 4915 } else { 4916 return this; 4917 } 4918 } 4919 4920 /** 4921 * Get the AccessibleText associated with this object if one 4922 * exists. Otherwise return null. 4923 * 4924 * @return the AccessibleText, or null 4925 */ 4926 public AccessibleText getAccessibleText() { 4927 AccessibleContext ac = getCurrentAccessibleContext(); 4928 if (ac != null) { 4929 return getCurrentAccessibleContext().getAccessibleText(); 4930 } else { 4931 return null; 4932 } 4933 } 4934 4935 /** 4936 * Get the AccessibleValue associated with this object if one 4937 * exists. Otherwise return null. 4938 * 4939 * @return the AccessibleValue, or null 4940 */ 4941 public AccessibleValue getAccessibleValue() { 4942 AccessibleContext ac = getCurrentAccessibleContext(); 4943 if (ac != null) { 4944 return getCurrentAccessibleContext().getAccessibleValue(); 4945 } else { 4946 return null; 4947 } 4948 } 4949 4950 4951 // AccessibleComponent methods 4952 4953 /** 4954 * Get the background color of this object. 4955 * 4956 * @return the background color, if supported, of the object; 4957 * otherwise, null 4958 */ 4959 public Color getBackground() { 4960 AccessibleContext ac = getCurrentAccessibleContext(); 4961 if (ac instanceof AccessibleComponent) { 4962 return ((AccessibleComponent) ac).getBackground(); 4963 } else { 4964 Component c = getCurrentComponent(); 4965 if (c != null) { 4966 return c.getBackground(); 4967 } else { 4968 return null; 4969 } 4970 } 4971 } 4972 4973 /** 4974 * Set the background color of this object. 4975 * 4976 * @param c the new Color for the background 4977 */ 4978 public void setBackground(Color c) { 4979 AccessibleContext ac = getCurrentAccessibleContext(); 4980 if (ac instanceof AccessibleComponent) { 4981 ((AccessibleComponent) ac).setBackground(c); 4982 } else { 4983 Component cp = getCurrentComponent(); 4984 if (cp != null) { 4985 cp.setBackground(c); 4986 } 4987 } 4988 } 4989 4990 4991 /** 4992 * Get the foreground color of this object. 4993 * 4994 * @return the foreground color, if supported, of the object; 4995 * otherwise, null 4996 */ 4997 public Color getForeground() { 4998 AccessibleContext ac = getCurrentAccessibleContext(); 4999 if (ac instanceof AccessibleComponent) { 5000 return ((AccessibleComponent) ac).getForeground(); 5001 } else { 5002 Component c = getCurrentComponent(); 5003 if (c != null) { 5004 return c.getForeground(); 5005 } else { 5006 return null; 5007 } 5008 } 5009 } 5010 5011 public void setForeground(Color c) { 5012 AccessibleContext ac = getCurrentAccessibleContext(); 5013 if (ac instanceof AccessibleComponent) { 5014 ((AccessibleComponent) ac).setForeground(c); 5015 } else { 5016 Component cp = getCurrentComponent(); 5017 if (cp != null) { 5018 cp.setForeground(c); 5019 } 5020 } 5021 } 5022 5023 public Cursor getCursor() { 5024 AccessibleContext ac = getCurrentAccessibleContext(); 5025 if (ac instanceof AccessibleComponent) { 5026 return ((AccessibleComponent) ac).getCursor(); 5027 } else { 5028 Component c = getCurrentComponent(); 5029 if (c != null) { 5030 return c.getCursor(); 5031 } else { 5032 Accessible ap = getAccessibleParent(); 5033 if (ap instanceof AccessibleComponent) { 5034 return ((AccessibleComponent) ap).getCursor(); 5035 } else { 5036 return null; 5037 } 5038 } 5039 } 5040 } 5041 5042 public void setCursor(Cursor c) { 5043 AccessibleContext ac = getCurrentAccessibleContext(); 5044 if (ac instanceof AccessibleComponent) { 5045 ((AccessibleComponent) ac).setCursor(c); 5046 } else { 5047 Component cp = getCurrentComponent(); 5048 if (cp != null) { 5049 cp.setCursor(c); 5050 } 5051 } 5052 } 5053 5054 public Font getFont() { 5055 AccessibleContext ac = getCurrentAccessibleContext(); 5056 if (ac instanceof AccessibleComponent) { 5057 return ((AccessibleComponent) ac).getFont(); 5058 } else { 5059 Component c = getCurrentComponent(); 5060 if (c != null) { 5061 return c.getFont(); 5062 } else { 5063 return null; 5064 } 5065 } 5066 } 5067 5068 public void setFont(Font f) { 5069 AccessibleContext ac = getCurrentAccessibleContext(); 5070 if (ac instanceof AccessibleComponent) { 5071 ((AccessibleComponent) ac).setFont(f); 5072 } else { 5073 Component c = getCurrentComponent(); 5074 if (c != null) { 5075 c.setFont(f); 5076 } 5077 } 5078 } 5079 5080 public FontMetrics getFontMetrics(Font f) { 5081 AccessibleContext ac = getCurrentAccessibleContext(); 5082 if (ac instanceof AccessibleComponent) { 5083 return ((AccessibleComponent) ac).getFontMetrics(f); 5084 } else { 5085 Component c = getCurrentComponent(); 5086 if (c != null) { 5087 return c.getFontMetrics(f); 5088 } else { 5089 return null; 5090 } 5091 } 5092 } 5093 5094 public boolean isEnabled() { 5095 AccessibleContext ac = getCurrentAccessibleContext(); 5096 if (ac instanceof AccessibleComponent) { 5097 return ((AccessibleComponent) ac).isEnabled(); 5098 } else { 5099 Component c = getCurrentComponent(); 5100 if (c != null) { 5101 return c.isEnabled(); 5102 } else { 5103 return false; 5104 } 5105 } 5106 } 5107 5108 public void setEnabled(boolean b) { 5109 AccessibleContext ac = getCurrentAccessibleContext(); 5110 if (ac instanceof AccessibleComponent) { 5111 ((AccessibleComponent) ac).setEnabled(b); 5112 } else { 5113 Component c = getCurrentComponent(); 5114 if (c != null) { 5115 c.setEnabled(b); 5116 } 5117 } 5118 } 5119 5120 public boolean isVisible() { 5121 Rectangle pathBounds = tree.getPathBounds(path); 5122 Rectangle parentBounds = tree.getVisibleRect(); 5123 return pathBounds != null && parentBounds != null && 5124 parentBounds.intersects(pathBounds); 5125 } 5126 5127 public void setVisible(boolean b) { 5128 } 5129 5130 public boolean isShowing() { 5131 return (tree.isShowing() && isVisible()); 5132 } 5133 5134 public boolean contains(Point p) { 5135 AccessibleContext ac = getCurrentAccessibleContext(); 5136 if (ac instanceof AccessibleComponent) { 5137 Rectangle r = ((AccessibleComponent) ac).getBounds(); 5138 return r.contains(p); 5139 } else { 5140 Component c = getCurrentComponent(); 5141 if (c != null) { 5142 Rectangle r = c.getBounds(); 5143 return r.contains(p); 5144 } else { 5145 return getBounds().contains(p); 5146 } 5147 } 5148 } 5149 5150 public Point getLocationOnScreen() { 5151 if (tree != null) { 5152 Point treeLocation = tree.getLocationOnScreen(); 5153 Rectangle pathBounds = tree.getPathBounds(path); 5154 if (treeLocation != null && pathBounds != null) { 5155 Point nodeLocation = new Point(pathBounds.x, 5156 pathBounds.y); 5157 nodeLocation.translate(treeLocation.x, treeLocation.y); 5158 return nodeLocation; 5159 } else { 5160 return null; 5161 } 5162 } else { 5163 return null; 5164 } 5165 } 5166 5167 protected Point getLocationInJTree() { 5168 Rectangle r = tree.getPathBounds(path); 5169 if (r != null) { 5170 return r.getLocation(); 5171 } else { 5172 return null; 5173 } 5174 } 5175 5176 public Point getLocation() { 5177 Rectangle r = getBounds(); 5178 if (r != null) { 5179 return r.getLocation(); 5180 } else { 5181 return null; 5182 } 5183 } 5184 5185 public void setLocation(Point p) { 5186 } 5187 5188 public Rectangle getBounds() { 5189 Rectangle r = tree.getPathBounds(path); 5190 Accessible parent = getAccessibleParent(); 5191 if (parent != null) { 5192 if (parent instanceof AccessibleJTreeNode) { 5193 Point parentLoc = ((AccessibleJTreeNode) parent).getLocationInJTree(); 5194 if (parentLoc != null && r != null) { 5195 r.translate(-parentLoc.x, -parentLoc.y); 5196 } else { 5197 return null; // not visible! 5198 } 5199 } 5200 } 5201 return r; 5202 } 5203 5204 public void setBounds(Rectangle r) { 5205 AccessibleContext ac = getCurrentAccessibleContext(); 5206 if (ac instanceof AccessibleComponent) { 5207 ((AccessibleComponent) ac).setBounds(r); 5208 } else { 5209 Component c = getCurrentComponent(); 5210 if (c != null) { 5211 c.setBounds(r); 5212 } 5213 } 5214 } 5215 5216 public Dimension getSize() { 5217 return getBounds().getSize(); 5218 } 5219 5220 public void setSize (Dimension d) { 5221 AccessibleContext ac = getCurrentAccessibleContext(); 5222 if (ac instanceof AccessibleComponent) { 5223 ((AccessibleComponent) ac).setSize(d); 5224 } else { 5225 Component c = getCurrentComponent(); 5226 if (c != null) { 5227 c.setSize(d); 5228 } 5229 } 5230 } 5231 5232 /** 5233 * Returns the <code>Accessible</code> child, if one exists, 5234 * contained at the local coordinate <code>Point</code>. 5235 * Otherwise returns <code>null</code>. 5236 * 5237 * @param p point in local coordinates of this 5238 * <code>Accessible</code> 5239 * @return the <code>Accessible</code>, if it exists, 5240 * at the specified location; else <code>null</code> 5241 */ 5242 public Accessible getAccessibleAt(Point p) { 5243 AccessibleContext ac = getCurrentAccessibleContext(); 5244 if (ac instanceof AccessibleComponent) { 5245 return ((AccessibleComponent) ac).getAccessibleAt(p); 5246 } else { 5247 return null; 5248 } 5249 } 5250 5251 @SuppressWarnings("deprecation") 5252 public boolean isFocusTraversable() { 5253 AccessibleContext ac = getCurrentAccessibleContext(); 5254 if (ac instanceof AccessibleComponent) { 5255 return ((AccessibleComponent) ac).isFocusTraversable(); 5256 } else { 5257 Component c = getCurrentComponent(); 5258 if (c != null) { 5259 return c.isFocusTraversable(); 5260 } else { 5261 return false; 5262 } 5263 } 5264 } 5265 5266 public void requestFocus() { 5267 AccessibleContext ac = getCurrentAccessibleContext(); 5268 if (ac instanceof AccessibleComponent) { 5269 ((AccessibleComponent) ac).requestFocus(); 5270 } else { 5271 Component c = getCurrentComponent(); 5272 if (c != null) { 5273 c.requestFocus(); 5274 } 5275 } 5276 } 5277 5278 public void addFocusListener(FocusListener l) { 5279 AccessibleContext ac = getCurrentAccessibleContext(); 5280 if (ac instanceof AccessibleComponent) { 5281 ((AccessibleComponent) ac).addFocusListener(l); 5282 } else { 5283 Component c = getCurrentComponent(); 5284 if (c != null) { 5285 c.addFocusListener(l); 5286 } 5287 } 5288 } 5289 5290 public void removeFocusListener(FocusListener l) { 5291 AccessibleContext ac = getCurrentAccessibleContext(); 5292 if (ac instanceof AccessibleComponent) { 5293 ((AccessibleComponent) ac).removeFocusListener(l); 5294 } else { 5295 Component c = getCurrentComponent(); 5296 if (c != null) { 5297 c.removeFocusListener(l); 5298 } 5299 } 5300 } 5301 5302 // AccessibleSelection methods 5303 5304 /** 5305 * Returns the number of items currently selected. 5306 * If no items are selected, the return value will be 0. 5307 * 5308 * @return the number of items currently selected. 5309 */ 5310 public int getAccessibleSelectionCount() { 5311 int count = 0; 5312 int childCount = getAccessibleChildrenCount(); 5313 for (int i = 0; i < childCount; i++) { 5314 TreePath childPath = getChildTreePath(i); 5315 if (tree.isPathSelected(childPath)) { 5316 count++; 5317 } 5318 } 5319 return count; 5320 } 5321 5322 /** 5323 * Returns an Accessible representing the specified selected item 5324 * in the object. If there isn't a selection, or there are 5325 * fewer items selected than the integer passed in, the return 5326 * value will be null. 5327 * 5328 * @param i the zero-based index of selected items 5329 * @return an Accessible containing the selected item 5330 */ 5331 public Accessible getAccessibleSelection(int i) { 5332 int childCount = getAccessibleChildrenCount(); 5333 if (i < 0 || i >= childCount) { 5334 return null; // out of range 5335 } 5336 int count = 0; 5337 for (int j = 0; j < childCount && i >= count; j++) { 5338 TreePath childPath = getChildTreePath(j); 5339 if (tree.isPathSelected(childPath)) { 5340 if (count == i) { 5341 return new AccessibleJTreeNode(tree, childPath, this); 5342 } else { 5343 count++; 5344 } 5345 } 5346 } 5347 return null; 5348 } 5349 5350 /** 5351 * Returns true if the current child of this object is selected. 5352 * 5353 * @param i the zero-based index of the child in this Accessible 5354 * object. 5355 * @see AccessibleContext#getAccessibleChild 5356 */ 5357 public boolean isAccessibleChildSelected(int i) { 5358 int childCount = getAccessibleChildrenCount(); 5359 if (i < 0 || i >= childCount) { 5360 return false; // out of range 5361 } else { 5362 TreePath childPath = getChildTreePath(i); 5363 return tree.isPathSelected(childPath); 5364 } 5365 } 5366 5367 /** 5368 * Adds the specified selected item in the object to the object's 5369 * selection. If the object supports multiple selections, 5370 * the specified item is added to any existing selection, otherwise 5371 * it replaces any existing selection in the object. If the 5372 * specified item is already selected, this method has no effect. 5373 * 5374 * @param i the zero-based index of selectable items 5375 */ 5376 public void addAccessibleSelection(int i) { 5377 TreeModel model = JTree.this.getModel(); 5378 if (model != null) { 5379 if (i >= 0 && i < getAccessibleChildrenCount()) { 5380 TreePath path = getChildTreePath(i); 5381 JTree.this.addSelectionPath(path); 5382 } 5383 } 5384 } 5385 5386 /** 5387 * Removes the specified selected item in the object from the 5388 * object's 5389 * selection. If the specified item isn't currently selected, this 5390 * method has no effect. 5391 * 5392 * @param i the zero-based index of selectable items 5393 */ 5394 public void removeAccessibleSelection(int i) { 5395 TreeModel model = JTree.this.getModel(); 5396 if (model != null) { 5397 if (i >= 0 && i < getAccessibleChildrenCount()) { 5398 TreePath path = getChildTreePath(i); 5399 JTree.this.removeSelectionPath(path); 5400 } 5401 } 5402 } 5403 5404 /** 5405 * Clears the selection in the object, so that nothing in the 5406 * object is selected. 5407 */ 5408 public void clearAccessibleSelection() { 5409 int childCount = getAccessibleChildrenCount(); 5410 for (int i = 0; i < childCount; i++) { 5411 removeAccessibleSelection(i); 5412 } 5413 } 5414 5415 /** 5416 * Causes every selected item in the object to be selected 5417 * if the object supports multiple selections. 5418 */ 5419 public void selectAllAccessibleSelection() { 5420 TreeModel model = JTree.this.getModel(); 5421 if (model != null) { 5422 int childCount = getAccessibleChildrenCount(); 5423 TreePath path; 5424 for (int i = 0; i < childCount; i++) { 5425 path = getChildTreePath(i); 5426 JTree.this.addSelectionPath(path); 5427 } 5428 } 5429 } 5430 5431 // AccessibleAction methods 5432 5433 /** 5434 * Returns the number of accessible actions available in this 5435 * tree node. If this node is not a leaf, there is at least 5436 * one action (toggle expand), in addition to any available 5437 * on the object behind the TreeCellRenderer. 5438 * 5439 * @return the number of Actions in this object 5440 */ 5441 public int getAccessibleActionCount() { 5442 AccessibleContext ac = getCurrentAccessibleContext(); 5443 if (ac != null) { 5444 AccessibleAction aa = ac.getAccessibleAction(); 5445 if (aa != null) { 5446 return (aa.getAccessibleActionCount() + (isLeaf ? 0 : 1)); 5447 } 5448 } 5449 return isLeaf ? 0 : 1; 5450 } 5451 5452 /** 5453 * Return a description of the specified action of the tree node. 5454 * If this node is not a leaf, there is at least one action 5455 * description (toggle expand), in addition to any available 5456 * on the object behind the TreeCellRenderer. 5457 * 5458 * @param i zero-based index of the actions 5459 * @return a description of the action 5460 */ 5461 public String getAccessibleActionDescription(int i) { 5462 if (i < 0 || i >= getAccessibleActionCount()) { 5463 return null; 5464 } 5465 AccessibleContext ac = getCurrentAccessibleContext(); 5466 if (i == 0) { 5467 // TIGER - 4766636 5468 return AccessibleAction.TOGGLE_EXPAND; 5469 } else if (ac != null) { 5470 AccessibleAction aa = ac.getAccessibleAction(); 5471 if (aa != null) { 5472 return aa.getAccessibleActionDescription(i - 1); 5473 } 5474 } 5475 return null; 5476 } 5477 5478 /** 5479 * Perform the specified Action on the tree node. If this node 5480 * is not a leaf, there is at least one action which can be 5481 * done (toggle expand), in addition to any available on the 5482 * object behind the TreeCellRenderer. 5483 * 5484 * @param i zero-based index of actions 5485 * @return true if the the action was performed; else false. 5486 */ 5487 public boolean doAccessibleAction(int i) { 5488 if (i < 0 || i >= getAccessibleActionCount()) { 5489 return false; 5490 } 5491 AccessibleContext ac = getCurrentAccessibleContext(); 5492 if (i == 0) { 5493 if (JTree.this.isExpanded(path)) { 5494 JTree.this.collapsePath(path); 5495 } else { 5496 JTree.this.expandPath(path); 5497 } 5498 return true; 5499 } else if (ac != null) { 5500 AccessibleAction aa = ac.getAccessibleAction(); 5501 if (aa != null) { 5502 return aa.doAccessibleAction(i - 1); 5503 } 5504 } 5505 return false; 5506 } 5507 5508 } // inner class AccessibleJTreeNode 5509 5510 } // inner class AccessibleJTree 5511 5512 } // End of class JTree