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