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