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