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