1 /* 2 * Copyright (c) 1997, 2009, 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.plaf.basic; 27 28 import javax.swing.*; 29 import javax.swing.event.*; 30 import java.awt.*; 31 import java.awt.event.*; 32 import java.awt.datatransfer.*; 33 import java.beans.*; 34 import java.util.Enumeration; 35 import java.util.Hashtable; 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.Comparator; 39 import javax.swing.plaf.ComponentUI; 40 import javax.swing.plaf.UIResource; 41 import javax.swing.plaf.TreeUI; 42 import javax.swing.tree.*; 43 import javax.swing.text.Position; 44 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag; 45 import sun.swing.SwingUtilities2; 46 47 import sun.swing.DefaultLookup; 48 import sun.swing.UIAction; 49 50 /** 51 * The basic L&F for a hierarchical data structure. 52 * <p> 53 * 54 * @author Scott Violet 55 * @author Shannon Hickey (drag and drop) 56 */ 57 58 public class BasicTreeUI extends TreeUI 59 { 60 private static final Object BASELINE_COMPONENT_KEY = new Object(); // Tree.baselineComponent 61 62 // Old actions forward to an instance of this. 63 static private final Actions SHARED_ACTION = new Actions(); 64 65 transient protected Icon collapsedIcon; 66 transient protected Icon expandedIcon; 67 68 /** 69 * Color used to draw hash marks. If <code>null</code> no hash marks 70 * will be drawn. 71 */ 72 private Color hashColor; 73 74 /** Distance between left margin and where vertical dashes will be 75 * drawn. */ 76 protected int leftChildIndent; 77 /** Distance to add to leftChildIndent to determine where cell 78 * contents will be drawn. */ 79 protected int rightChildIndent; 80 /** Total distance that will be indented. The sum of leftChildIndent 81 * and rightChildIndent. */ 82 protected int totalChildIndent; 83 84 /** Minimum preferred size. */ 85 protected Dimension preferredMinSize; 86 87 /** Index of the row that was last selected. */ 88 protected int lastSelectedRow; 89 90 /** Component that we're going to be drawing into. */ 91 protected JTree tree; 92 93 /** Renderer that is being used to do the actual cell drawing. */ 94 transient protected TreeCellRenderer currentCellRenderer; 95 96 /** Set to true if the renderer that is currently in the tree was 97 * created by this instance. */ 98 protected boolean createdRenderer; 99 100 /** Editor for the tree. */ 101 transient protected TreeCellEditor cellEditor; 102 103 /** Set to true if editor that is currently in the tree was 104 * created by this instance. */ 105 protected boolean createdCellEditor; 106 107 /** Set to false when editing and shouldSelectCell() returns true meaning 108 * the node should be selected before editing, used in completeEditing. */ 109 protected boolean stopEditingInCompleteEditing; 110 111 /** Used to paint the TreeCellRenderer. */ 112 protected CellRendererPane rendererPane; 113 114 /** Size needed to completely display all the nodes. */ 115 protected Dimension preferredSize; 116 117 /** Is the preferredSize valid? */ 118 protected boolean validCachedPreferredSize; 119 120 /** Object responsible for handling sizing and expanded issues. */ 121 // WARNING: Be careful with the bounds held by treeState. They are 122 // always in terms of left-to-right. They get mapped to right-to-left 123 // by the various methods of this class. 124 protected AbstractLayoutCache treeState; 125 126 127 /** Used for minimizing the drawing of vertical lines. */ 128 protected Hashtable<TreePath,Boolean> drawingCache; 129 130 /** True if doing optimizations for a largeModel. Subclasses that 131 * don't support this may wish to override createLayoutCache to not 132 * return a FixedHeightLayoutCache instance. */ 133 protected boolean largeModel; 134 135 /** Reponsible for telling the TreeState the size needed for a node. */ 136 protected AbstractLayoutCache.NodeDimensions nodeDimensions; 137 138 /** Used to determine what to display. */ 139 protected TreeModel treeModel; 140 141 /** Model maintaing the selection. */ 142 protected TreeSelectionModel treeSelectionModel; 143 144 /** How much the depth should be offset to properly calculate 145 * x locations. This is based on whether or not the root is visible, 146 * and if the root handles are visible. */ 147 protected int depthOffset; 148 149 // Following 4 ivars are only valid when editing. 150 151 /** When editing, this will be the Component that is doing the actual 152 * editing. */ 153 protected Component editingComponent; 154 155 /** Path that is being edited. */ 156 protected TreePath editingPath; 157 158 /** Row that is being edited. Should only be referenced if 159 * editingComponent is not null. */ 160 protected int editingRow; 161 162 /** Set to true if the editor has a different size than the renderer. */ 163 protected boolean editorHasDifferentSize; 164 165 /** Row correspondin to lead path. */ 166 private int leadRow; 167 /** If true, the property change event for LEAD_SELECTION_PATH_PROPERTY, 168 * or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint. */ 169 private boolean ignoreLAChange; 170 171 /** Indicates the orientation. */ 172 private boolean leftToRight; 173 174 // Cached listeners 175 private PropertyChangeListener propertyChangeListener; 176 private PropertyChangeListener selectionModelPropertyChangeListener; 177 private MouseListener mouseListener; 178 private FocusListener focusListener; 179 private KeyListener keyListener; 180 /** Used for large models, listens for moved/resized events and 181 * updates the validCachedPreferredSize bit accordingly. */ 182 private ComponentListener componentListener; 183 /** Listens for CellEditor events. */ 184 private CellEditorListener cellEditorListener; 185 /** Updates the display when the selection changes. */ 186 private TreeSelectionListener treeSelectionListener; 187 /** Is responsible for updating the display based on model events. */ 188 private TreeModelListener treeModelListener; 189 /** Updates the treestate as the nodes expand. */ 190 private TreeExpansionListener treeExpansionListener; 191 192 /** UI property indicating whether to paint lines */ 193 private boolean paintLines = true; 194 195 /** UI property for painting dashed lines */ 196 private boolean lineTypeDashed; 197 198 /** 199 * The time factor to treate the series of typed alphanumeric key 200 * as prefix for first letter navigation. 201 */ 202 private long timeFactor = 1000L; 203 204 private Handler handler; 205 206 /** 207 * A temporary variable for communication between startEditingOnRelease 208 * and startEditing. 209 */ 210 private MouseEvent releaseEvent; 211 212 public static ComponentUI createUI(JComponent x) { 213 return new BasicTreeUI(); 214 } 215 216 217 static void loadActionMap(LazyActionMap map) { 218 map.put(new Actions(Actions.SELECT_PREVIOUS)); 219 map.put(new Actions(Actions.SELECT_PREVIOUS_CHANGE_LEAD)); 220 map.put(new Actions(Actions.SELECT_PREVIOUS_EXTEND_SELECTION)); 221 222 map.put(new Actions(Actions.SELECT_NEXT)); 223 map.put(new Actions(Actions.SELECT_NEXT_CHANGE_LEAD)); 224 map.put(new Actions(Actions.SELECT_NEXT_EXTEND_SELECTION)); 225 226 map.put(new Actions(Actions.SELECT_CHILD)); 227 map.put(new Actions(Actions.SELECT_CHILD_CHANGE_LEAD)); 228 229 map.put(new Actions(Actions.SELECT_PARENT)); 230 map.put(new Actions(Actions.SELECT_PARENT_CHANGE_LEAD)); 231 232 map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION)); 233 map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD)); 234 map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION)); 235 236 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION)); 237 map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION)); 238 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD)); 239 240 map.put(new Actions(Actions.SELECT_FIRST)); 241 map.put(new Actions(Actions.SELECT_FIRST_CHANGE_LEAD)); 242 map.put(new Actions(Actions.SELECT_FIRST_EXTEND_SELECTION)); 243 244 map.put(new Actions(Actions.SELECT_LAST)); 245 map.put(new Actions(Actions.SELECT_LAST_CHANGE_LEAD)); 246 map.put(new Actions(Actions.SELECT_LAST_EXTEND_SELECTION)); 247 248 map.put(new Actions(Actions.TOGGLE)); 249 250 map.put(new Actions(Actions.CANCEL_EDITING)); 251 252 map.put(new Actions(Actions.START_EDITING)); 253 254 map.put(new Actions(Actions.SELECT_ALL)); 255 256 map.put(new Actions(Actions.CLEAR_SELECTION)); 257 258 map.put(new Actions(Actions.SCROLL_LEFT)); 259 map.put(new Actions(Actions.SCROLL_RIGHT)); 260 261 map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION)); 262 map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION)); 263 264 map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_LEAD)); 265 map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_LEAD)); 266 267 map.put(new Actions(Actions.EXPAND)); 268 map.put(new Actions(Actions.COLLAPSE)); 269 map.put(new Actions(Actions.MOVE_SELECTION_TO_PARENT)); 270 271 map.put(new Actions(Actions.ADD_TO_SELECTION)); 272 map.put(new Actions(Actions.TOGGLE_AND_ANCHOR)); 273 map.put(new Actions(Actions.EXTEND_TO)); 274 map.put(new Actions(Actions.MOVE_SELECTION_TO)); 275 276 map.put(TransferHandler.getCutAction()); 277 map.put(TransferHandler.getCopyAction()); 278 map.put(TransferHandler.getPasteAction()); 279 } 280 281 282 public BasicTreeUI() { 283 super(); 284 } 285 286 protected Color getHashColor() { 287 return hashColor; 288 } 289 290 protected void setHashColor(Color color) { 291 hashColor = color; 292 } 293 294 public void setLeftChildIndent(int newAmount) { 295 leftChildIndent = newAmount; 296 totalChildIndent = leftChildIndent + rightChildIndent; 297 if(treeState != null) 298 treeState.invalidateSizes(); 299 updateSize(); 300 } 301 302 public int getLeftChildIndent() { 303 return leftChildIndent; 304 } 305 306 public void setRightChildIndent(int newAmount) { 307 rightChildIndent = newAmount; 308 totalChildIndent = leftChildIndent + rightChildIndent; 309 if(treeState != null) 310 treeState.invalidateSizes(); 311 updateSize(); 312 } 313 314 public int getRightChildIndent() { 315 return rightChildIndent; 316 } 317 318 public void setExpandedIcon(Icon newG) { 319 expandedIcon = newG; 320 } 321 322 public Icon getExpandedIcon() { 323 return expandedIcon; 324 } 325 326 public void setCollapsedIcon(Icon newG) { 327 collapsedIcon = newG; 328 } 329 330 public Icon getCollapsedIcon() { 331 return collapsedIcon; 332 } 333 334 // 335 // Methods for configuring the behavior of the tree. None of them 336 // push the value to the JTree instance. You should really only 337 // call these methods on the JTree. 338 // 339 340 /** 341 * Updates the componentListener, if necessary. 342 */ 343 protected void setLargeModel(boolean largeModel) { 344 if(getRowHeight() < 1) 345 largeModel = false; 346 if(this.largeModel != largeModel) { 347 completeEditing(); 348 this.largeModel = largeModel; 349 treeState = createLayoutCache(); 350 configureLayoutCache(); 351 updateLayoutCacheExpandedNodesIfNecessary(); 352 updateSize(); 353 } 354 } 355 356 protected boolean isLargeModel() { 357 return largeModel; 358 } 359 360 /** 361 * Sets the row height, this is forwarded to the treeState. 362 */ 363 protected void setRowHeight(int rowHeight) { 364 completeEditing(); 365 if(treeState != null) { 366 setLargeModel(tree.isLargeModel()); 367 treeState.setRowHeight(rowHeight); 368 updateSize(); 369 } 370 } 371 372 protected int getRowHeight() { 373 return (tree == null) ? -1 : tree.getRowHeight(); 374 } 375 376 /** 377 * Sets the TreeCellRenderer to <code>tcr</code>. This invokes 378 * <code>updateRenderer</code>. 379 */ 380 protected void setCellRenderer(TreeCellRenderer tcr) { 381 completeEditing(); 382 updateRenderer(); 383 if(treeState != null) { 384 treeState.invalidateSizes(); 385 updateSize(); 386 } 387 } 388 389 /** 390 * Return currentCellRenderer, which will either be the trees 391 * renderer, or defaultCellRenderer, which ever wasn't null. 392 */ 393 protected TreeCellRenderer getCellRenderer() { 394 return currentCellRenderer; 395 } 396 397 /** 398 * Sets the TreeModel. 399 */ 400 protected void setModel(TreeModel model) { 401 completeEditing(); 402 if(treeModel != null && treeModelListener != null) 403 treeModel.removeTreeModelListener(treeModelListener); 404 treeModel = model; 405 if(treeModel != null) { 406 if(treeModelListener != null) 407 treeModel.addTreeModelListener(treeModelListener); 408 } 409 if(treeState != null) { 410 treeState.setModel(model); 411 updateLayoutCacheExpandedNodesIfNecessary(); 412 updateSize(); 413 } 414 } 415 416 protected TreeModel getModel() { 417 return treeModel; 418 } 419 420 /** 421 * Sets the root to being visible. 422 */ 423 protected void setRootVisible(boolean newValue) { 424 completeEditing(); 425 updateDepthOffset(); 426 if(treeState != null) { 427 treeState.setRootVisible(newValue); 428 treeState.invalidateSizes(); 429 updateSize(); 430 } 431 } 432 433 protected boolean isRootVisible() { 434 return (tree != null) ? tree.isRootVisible() : false; 435 } 436 437 /** 438 * Determines whether the node handles are to be displayed. 439 */ 440 protected void setShowsRootHandles(boolean newValue) { 441 completeEditing(); 442 updateDepthOffset(); 443 if(treeState != null) { 444 treeState.invalidateSizes(); 445 updateSize(); 446 } 447 } 448 449 protected boolean getShowsRootHandles() { 450 return (tree != null) ? tree.getShowsRootHandles() : false; 451 } 452 453 /** 454 * Sets the cell editor. 455 */ 456 protected void setCellEditor(TreeCellEditor editor) { 457 updateCellEditor(); 458 } 459 460 protected TreeCellEditor getCellEditor() { 461 return (tree != null) ? tree.getCellEditor() : null; 462 } 463 464 /** 465 * Configures the receiver to allow, or not allow, editing. 466 */ 467 protected void setEditable(boolean newValue) { 468 updateCellEditor(); 469 } 470 471 protected boolean isEditable() { 472 return (tree != null) ? tree.isEditable() : false; 473 } 474 475 /** 476 * Resets the selection model. The appropriate listener are installed 477 * on the model. 478 */ 479 protected void setSelectionModel(TreeSelectionModel newLSM) { 480 completeEditing(); 481 if(selectionModelPropertyChangeListener != null && 482 treeSelectionModel != null) 483 treeSelectionModel.removePropertyChangeListener 484 (selectionModelPropertyChangeListener); 485 if(treeSelectionListener != null && treeSelectionModel != null) 486 treeSelectionModel.removeTreeSelectionListener 487 (treeSelectionListener); 488 treeSelectionModel = newLSM; 489 if(treeSelectionModel != null) { 490 if(selectionModelPropertyChangeListener != null) 491 treeSelectionModel.addPropertyChangeListener 492 (selectionModelPropertyChangeListener); 493 if(treeSelectionListener != null) 494 treeSelectionModel.addTreeSelectionListener 495 (treeSelectionListener); 496 if(treeState != null) 497 treeState.setSelectionModel(treeSelectionModel); 498 } 499 else if(treeState != null) 500 treeState.setSelectionModel(null); 501 if(tree != null) 502 tree.repaint(); 503 } 504 505 protected TreeSelectionModel getSelectionModel() { 506 return treeSelectionModel; 507 } 508 509 // 510 // TreeUI methods 511 // 512 513 /** 514 * Returns the Rectangle enclosing the label portion that the 515 * last item in path will be drawn into. Will return null if 516 * any component in path is currently valid. 517 */ 518 public Rectangle getPathBounds(JTree tree, TreePath path) { 519 if(tree != null && treeState != null) { 520 return getPathBounds(path, tree.getInsets(), new Rectangle()); 521 } 522 return null; 523 } 524 525 private Rectangle getPathBounds(TreePath path, Insets insets, 526 Rectangle bounds) { 527 bounds = treeState.getBounds(path, bounds); 528 if (bounds != null) { 529 if (leftToRight) { 530 bounds.x += insets.left; 531 } else { 532 bounds.x = tree.getWidth() - (bounds.x + bounds.width) - 533 insets.right; 534 } 535 bounds.y += insets.top; 536 } 537 return bounds; 538 } 539 540 /** 541 * Returns the path for passed in row. If row is not visible 542 * null is returned. 543 */ 544 public TreePath getPathForRow(JTree tree, int row) { 545 return (treeState != null) ? treeState.getPathForRow(row) : null; 546 } 547 548 /** 549 * Returns the row that the last item identified in path is visible 550 * at. Will return -1 if any of the elements in path are not 551 * currently visible. 552 */ 553 public int getRowForPath(JTree tree, TreePath path) { 554 return (treeState != null) ? treeState.getRowForPath(path) : -1; 555 } 556 557 /** 558 * Returns the number of rows that are being displayed. 559 */ 560 public int getRowCount(JTree tree) { 561 return (treeState != null) ? treeState.getRowCount() : 0; 562 } 563 564 /** 565 * Returns the path to the node that is closest to x,y. If 566 * there is nothing currently visible this will return null, otherwise 567 * it'll always return a valid path. If you need to test if the 568 * returned object is exactly at x, y you should get the bounds for 569 * the returned path and test x, y against that. 570 */ 571 public TreePath getClosestPathForLocation(JTree tree, int x, int y) { 572 if(tree != null && treeState != null) { 573 // TreeState doesn't care about the x location, hence it isn't 574 // adjusted 575 y -= tree.getInsets().top; 576 return treeState.getPathClosestTo(x, y); 577 } 578 return null; 579 } 580 581 /** 582 * Returns true if the tree is being edited. The item that is being 583 * edited can be returned by getEditingPath(). 584 */ 585 public boolean isEditing(JTree tree) { 586 return (editingComponent != null); 587 } 588 589 /** 590 * Stops the current editing session. This has no effect if the 591 * tree isn't being edited. Returns true if the editor allows the 592 * editing session to stop. 593 */ 594 public boolean stopEditing(JTree tree) { 595 if(editingComponent != null && cellEditor.stopCellEditing()) { 596 completeEditing(false, false, true); 597 return true; 598 } 599 return false; 600 } 601 602 /** 603 * Cancels the current editing session. 604 */ 605 public void cancelEditing(JTree tree) { 606 if(editingComponent != null) { 607 completeEditing(false, true, false); 608 } 609 } 610 611 /** 612 * Selects the last item in path and tries to edit it. Editing will 613 * fail if the CellEditor won't allow it for the selected item. 614 */ 615 public void startEditingAtPath(JTree tree, TreePath path) { 616 tree.scrollPathToVisible(path); 617 if(path != null && tree.isVisible(path)) 618 startEditing(path, null); 619 } 620 621 /** 622 * Returns the path to the element that is being edited. 623 */ 624 public TreePath getEditingPath(JTree tree) { 625 return editingPath; 626 } 627 628 // 629 // Install methods 630 // 631 632 public void installUI(JComponent c) { 633 if ( c == null ) { 634 throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" ); 635 } 636 637 tree = (JTree)c; 638 639 prepareForUIInstall(); 640 641 // Boilerplate install block 642 installDefaults(); 643 installKeyboardActions(); 644 installComponents(); 645 installListeners(); 646 647 completeUIInstall(); 648 } 649 650 /** 651 * Invoked after the <code>tree</code> instance variable has been 652 * set, but before any defaults/listeners have been installed. 653 */ 654 protected void prepareForUIInstall() { 655 drawingCache = new Hashtable<TreePath,Boolean>(7); 656 657 // Data member initializations 658 leftToRight = BasicGraphicsUtils.isLeftToRight(tree); 659 stopEditingInCompleteEditing = true; 660 lastSelectedRow = -1; 661 leadRow = -1; 662 preferredSize = new Dimension(); 663 664 largeModel = tree.isLargeModel(); 665 if(getRowHeight() <= 0) 666 largeModel = false; 667 setModel(tree.getModel()); 668 } 669 670 /** 671 * Invoked from installUI after all the defaults/listeners have been 672 * installed. 673 */ 674 protected void completeUIInstall() { 675 // Custom install code 676 677 this.setShowsRootHandles(tree.getShowsRootHandles()); 678 679 updateRenderer(); 680 681 updateDepthOffset(); 682 683 setSelectionModel(tree.getSelectionModel()); 684 685 // Create, if necessary, the TreeState instance. 686 treeState = createLayoutCache(); 687 configureLayoutCache(); 688 689 updateSize(); 690 } 691 692 protected void installDefaults() { 693 if(tree.getBackground() == null || 694 tree.getBackground() instanceof UIResource) { 695 tree.setBackground(UIManager.getColor("Tree.background")); 696 } 697 if(getHashColor() == null || getHashColor() instanceof UIResource) { 698 setHashColor(UIManager.getColor("Tree.hash")); 699 } 700 if (tree.getFont() == null || tree.getFont() instanceof UIResource) 701 tree.setFont( UIManager.getFont("Tree.font") ); 702 // JTree's original row height is 16. To correctly display the 703 // contents on Linux we should have set it to 18, Windows 19 and 704 // Solaris 20. As these values vary so much it's too hard to 705 // be backward compatable and try to update the row height, we're 706 // therefor NOT going to adjust the row height based on font. If the 707 // developer changes the font, it's there responsibility to update 708 // the row height. 709 710 setExpandedIcon( (Icon)UIManager.get( "Tree.expandedIcon" ) ); 711 setCollapsedIcon( (Icon)UIManager.get( "Tree.collapsedIcon" ) ); 712 713 setLeftChildIndent(((Integer)UIManager.get("Tree.leftChildIndent")). 714 intValue()); 715 setRightChildIndent(((Integer)UIManager.get("Tree.rightChildIndent")). 716 intValue()); 717 718 LookAndFeel.installProperty(tree, "rowHeight", 719 UIManager.get("Tree.rowHeight")); 720 721 largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0); 722 723 Object scrollsOnExpand = UIManager.get("Tree.scrollsOnExpand"); 724 if (scrollsOnExpand != null) { 725 LookAndFeel.installProperty(tree, "scrollsOnExpand", scrollsOnExpand); 726 } 727 728 paintLines = UIManager.getBoolean("Tree.paintLines"); 729 lineTypeDashed = UIManager.getBoolean("Tree.lineTypeDashed"); 730 731 Long l = (Long)UIManager.get("Tree.timeFactor"); 732 timeFactor = (l!=null) ? l.longValue() : 1000L; 733 734 Object showsRootHandles = UIManager.get("Tree.showsRootHandles"); 735 if (showsRootHandles != null) { 736 LookAndFeel.installProperty(tree, 737 JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles); 738 } 739 } 740 741 protected void installListeners() { 742 if ( (propertyChangeListener = createPropertyChangeListener()) 743 != null ) { 744 tree.addPropertyChangeListener(propertyChangeListener); 745 } 746 if ( (mouseListener = createMouseListener()) != null ) { 747 tree.addMouseListener(mouseListener); 748 if (mouseListener instanceof MouseMotionListener) { 749 tree.addMouseMotionListener((MouseMotionListener)mouseListener); 750 } 751 } 752 if ((focusListener = createFocusListener()) != null ) { 753 tree.addFocusListener(focusListener); 754 } 755 if ((keyListener = createKeyListener()) != null) { 756 tree.addKeyListener(keyListener); 757 } 758 if((treeExpansionListener = createTreeExpansionListener()) != null) { 759 tree.addTreeExpansionListener(treeExpansionListener); 760 } 761 if((treeModelListener = createTreeModelListener()) != null && 762 treeModel != null) { 763 treeModel.addTreeModelListener(treeModelListener); 764 } 765 if((selectionModelPropertyChangeListener = 766 createSelectionModelPropertyChangeListener()) != null && 767 treeSelectionModel != null) { 768 treeSelectionModel.addPropertyChangeListener 769 (selectionModelPropertyChangeListener); 770 } 771 if((treeSelectionListener = createTreeSelectionListener()) != null && 772 treeSelectionModel != null) { 773 treeSelectionModel.addTreeSelectionListener(treeSelectionListener); 774 } 775 776 TransferHandler th = tree.getTransferHandler(); 777 if (th == null || th instanceof UIResource) { 778 tree.setTransferHandler(defaultTransferHandler); 779 // default TransferHandler doesn't support drop 780 // so we don't want drop handling 781 if (tree.getDropTarget() instanceof UIResource) { 782 tree.setDropTarget(null); 783 } 784 } 785 786 LookAndFeel.installProperty(tree, "opaque", Boolean.TRUE); 787 } 788 789 protected void installKeyboardActions() { 790 InputMap km = getInputMap(JComponent. 791 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 792 793 SwingUtilities.replaceUIInputMap(tree, JComponent. 794 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 795 km); 796 km = getInputMap(JComponent.WHEN_FOCUSED); 797 SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, km); 798 799 LazyActionMap.installLazyActionMap(tree, BasicTreeUI.class, 800 "Tree.actionMap"); 801 } 802 803 InputMap getInputMap(int condition) { 804 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 805 return (InputMap)DefaultLookup.get(tree, this, 806 "Tree.ancestorInputMap"); 807 } 808 else if (condition == JComponent.WHEN_FOCUSED) { 809 InputMap keyMap = (InputMap)DefaultLookup.get(tree, this, 810 "Tree.focusInputMap"); 811 InputMap rtlKeyMap; 812 813 if (tree.getComponentOrientation().isLeftToRight() || 814 ((rtlKeyMap = (InputMap)DefaultLookup.get(tree, this, 815 "Tree.focusInputMap.RightToLeft")) == null)) { 816 return keyMap; 817 } else { 818 rtlKeyMap.setParent(keyMap); 819 return rtlKeyMap; 820 } 821 } 822 return null; 823 } 824 825 /** 826 * Intalls the subcomponents of the tree, which is the renderer pane. 827 */ 828 protected void installComponents() { 829 if ((rendererPane = createCellRendererPane()) != null) { 830 tree.add( rendererPane ); 831 } 832 } 833 834 // 835 // Create methods. 836 // 837 838 /** 839 * Creates an instance of NodeDimensions that is able to determine 840 * the size of a given node in the tree. 841 */ 842 protected AbstractLayoutCache.NodeDimensions createNodeDimensions() { 843 return new NodeDimensionsHandler(); 844 } 845 846 /** 847 * Creates a listener that is responsible that updates the UI based on 848 * how the tree changes. 849 */ 850 protected PropertyChangeListener createPropertyChangeListener() { 851 return getHandler(); 852 } 853 854 private Handler getHandler() { 855 if (handler == null) { 856 handler = new Handler(); 857 } 858 return handler; 859 } 860 861 /** 862 * Creates the listener responsible for updating the selection based on 863 * mouse events. 864 */ 865 protected MouseListener createMouseListener() { 866 return getHandler(); 867 } 868 869 /** 870 * Creates a listener that is responsible for updating the display 871 * when focus is lost/gained. 872 */ 873 protected FocusListener createFocusListener() { 874 return getHandler(); 875 } 876 877 /** 878 * Creates the listener reponsible for getting key events from 879 * the tree. 880 */ 881 protected KeyListener createKeyListener() { 882 return getHandler(); 883 } 884 885 /** 886 * Creates the listener responsible for getting property change 887 * events from the selection model. 888 */ 889 protected PropertyChangeListener createSelectionModelPropertyChangeListener() { 890 return getHandler(); 891 } 892 893 /** 894 * Creates the listener that updates the display based on selection change 895 * methods. 896 */ 897 protected TreeSelectionListener createTreeSelectionListener() { 898 return getHandler(); 899 } 900 901 /** 902 * Creates a listener to handle events from the current editor. 903 */ 904 protected CellEditorListener createCellEditorListener() { 905 return getHandler(); 906 } 907 908 /** 909 * Creates and returns a new ComponentHandler. This is used for 910 * the large model to mark the validCachedPreferredSize as invalid 911 * when the component moves. 912 */ 913 protected ComponentListener createComponentListener() { 914 return new ComponentHandler(); 915 } 916 917 /** 918 * Creates and returns the object responsible for updating the treestate 919 * when nodes expanded state changes. 920 */ 921 protected TreeExpansionListener createTreeExpansionListener() { 922 return getHandler(); 923 } 924 925 /** 926 * Creates the object responsible for managing what is expanded, as 927 * well as the size of nodes. 928 */ 929 protected AbstractLayoutCache createLayoutCache() { 930 if(isLargeModel() && getRowHeight() > 0) { 931 return new FixedHeightLayoutCache(); 932 } 933 return new VariableHeightLayoutCache(); 934 } 935 936 /** 937 * Returns the renderer pane that renderer components are placed in. 938 */ 939 protected CellRendererPane createCellRendererPane() { 940 return new CellRendererPane(); 941 } 942 943 /** 944 * Creates a default cell editor. 945 */ 946 protected TreeCellEditor createDefaultCellEditor() { 947 if(currentCellRenderer != null && 948 (currentCellRenderer instanceof DefaultTreeCellRenderer)) { 949 DefaultTreeCellEditor editor = new DefaultTreeCellEditor 950 (tree, (DefaultTreeCellRenderer)currentCellRenderer); 951 952 return editor; 953 } 954 return new DefaultTreeCellEditor(tree, null); 955 } 956 957 /** 958 * Returns the default cell renderer that is used to do the 959 * stamping of each node. 960 */ 961 protected TreeCellRenderer createDefaultCellRenderer() { 962 return new DefaultTreeCellRenderer(); 963 } 964 965 /** 966 * Returns a listener that can update the tree when the model changes. 967 */ 968 protected TreeModelListener createTreeModelListener() { 969 return getHandler(); 970 } 971 972 // 973 // Uninstall methods 974 // 975 976 public void uninstallUI(JComponent c) { 977 completeEditing(); 978 979 prepareForUIUninstall(); 980 981 uninstallDefaults(); 982 uninstallListeners(); 983 uninstallKeyboardActions(); 984 uninstallComponents(); 985 986 completeUIUninstall(); 987 } 988 989 protected void prepareForUIUninstall() { 990 } 991 992 protected void completeUIUninstall() { 993 if(createdRenderer) { 994 tree.setCellRenderer(null); 995 } 996 if(createdCellEditor) { 997 tree.setCellEditor(null); 998 } 999 cellEditor = null; 1000 currentCellRenderer = null; 1001 rendererPane = null; 1002 componentListener = null; 1003 propertyChangeListener = null; 1004 mouseListener = null; 1005 focusListener = null; 1006 keyListener = null; 1007 setSelectionModel(null); 1008 treeState = null; 1009 drawingCache = null; 1010 selectionModelPropertyChangeListener = null; 1011 tree = null; 1012 treeModel = null; 1013 treeSelectionModel = null; 1014 treeSelectionListener = null; 1015 treeExpansionListener = null; 1016 } 1017 1018 protected void uninstallDefaults() { 1019 if (tree.getTransferHandler() instanceof UIResource) { 1020 tree.setTransferHandler(null); 1021 } 1022 } 1023 1024 protected void uninstallListeners() { 1025 if(componentListener != null) { 1026 tree.removeComponentListener(componentListener); 1027 } 1028 if (propertyChangeListener != null) { 1029 tree.removePropertyChangeListener(propertyChangeListener); 1030 } 1031 if (mouseListener != null) { 1032 tree.removeMouseListener(mouseListener); 1033 if (mouseListener instanceof MouseMotionListener) { 1034 tree.removeMouseMotionListener((MouseMotionListener)mouseListener); 1035 } 1036 } 1037 if (focusListener != null) { 1038 tree.removeFocusListener(focusListener); 1039 } 1040 if (keyListener != null) { 1041 tree.removeKeyListener(keyListener); 1042 } 1043 if(treeExpansionListener != null) { 1044 tree.removeTreeExpansionListener(treeExpansionListener); 1045 } 1046 if(treeModel != null && treeModelListener != null) { 1047 treeModel.removeTreeModelListener(treeModelListener); 1048 } 1049 if(selectionModelPropertyChangeListener != null && 1050 treeSelectionModel != null) { 1051 treeSelectionModel.removePropertyChangeListener 1052 (selectionModelPropertyChangeListener); 1053 } 1054 if(treeSelectionListener != null && treeSelectionModel != null) { 1055 treeSelectionModel.removeTreeSelectionListener 1056 (treeSelectionListener); 1057 } 1058 handler = null; 1059 } 1060 1061 protected void uninstallKeyboardActions() { 1062 SwingUtilities.replaceUIActionMap(tree, null); 1063 SwingUtilities.replaceUIInputMap(tree, JComponent. 1064 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 1065 null); 1066 SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, null); 1067 } 1068 1069 /** 1070 * Uninstalls the renderer pane. 1071 */ 1072 protected void uninstallComponents() { 1073 if(rendererPane != null) { 1074 tree.remove(rendererPane); 1075 } 1076 } 1077 1078 /** 1079 * Recomputes the right margin, and invalidates any tree states 1080 */ 1081 private void redoTheLayout() { 1082 if (treeState != null) { 1083 treeState.invalidateSizes(); 1084 } 1085 } 1086 1087 /** 1088 * Returns the baseline. 1089 * 1090 * @throws NullPointerException {@inheritDoc} 1091 * @throws IllegalArgumentException {@inheritDoc} 1092 * @see javax.swing.JComponent#getBaseline(int, int) 1093 * @since 1.6 1094 */ 1095 public int getBaseline(JComponent c, int width, int height) { 1096 super.getBaseline(c, width, height); 1097 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults(); 1098 Component renderer = (Component)lafDefaults.get( 1099 BASELINE_COMPONENT_KEY); 1100 if (renderer == null) { 1101 TreeCellRenderer tcr = createDefaultCellRenderer(); 1102 renderer = tcr.getTreeCellRendererComponent( 1103 tree, "a", false, false, false, -1, false); 1104 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer); 1105 } 1106 int rowHeight = tree.getRowHeight(); 1107 int baseline; 1108 if (rowHeight > 0) { 1109 baseline = renderer.getBaseline(Integer.MAX_VALUE, rowHeight); 1110 } 1111 else { 1112 Dimension pref = renderer.getPreferredSize(); 1113 baseline = renderer.getBaseline(pref.width, pref.height); 1114 } 1115 return baseline + tree.getInsets().top; 1116 } 1117 1118 /** 1119 * Returns an enum indicating how the baseline of the component 1120 * changes as the size changes. 1121 * 1122 * @throws NullPointerException {@inheritDoc} 1123 * @see javax.swing.JComponent#getBaseline(int, int) 1124 * @since 1.6 1125 */ 1126 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 1127 JComponent c) { 1128 super.getBaselineResizeBehavior(c); 1129 return Component.BaselineResizeBehavior.CONSTANT_ASCENT; 1130 } 1131 1132 // 1133 // Painting routines. 1134 // 1135 1136 public void paint(Graphics g, JComponent c) { 1137 if (tree != c) { 1138 throw new InternalError("incorrect component"); 1139 } 1140 1141 // Should never happen if installed for a UI 1142 if(treeState == null) { 1143 return; 1144 } 1145 1146 Rectangle paintBounds = g.getClipBounds(); 1147 Insets insets = tree.getInsets(); 1148 TreePath initialPath = getClosestPathForLocation 1149 (tree, 0, paintBounds.y); 1150 Enumeration paintingEnumerator = treeState.getVisiblePathsFrom 1151 (initialPath); 1152 int row = treeState.getRowForPath(initialPath); 1153 int endY = paintBounds.y + paintBounds.height; 1154 1155 drawingCache.clear(); 1156 1157 if(initialPath != null && paintingEnumerator != null) { 1158 TreePath parentPath = initialPath; 1159 1160 // Draw the lines, knobs, and rows 1161 1162 // Find each parent and have them draw a line to their last child 1163 parentPath = parentPath.getParentPath(); 1164 while(parentPath != null) { 1165 paintVerticalPartOfLeg(g, paintBounds, insets, parentPath); 1166 drawingCache.put(parentPath, Boolean.TRUE); 1167 parentPath = parentPath.getParentPath(); 1168 } 1169 1170 boolean done = false; 1171 // Information for the node being rendered. 1172 boolean isExpanded; 1173 boolean hasBeenExpanded; 1174 boolean isLeaf; 1175 Rectangle boundsBuffer = new Rectangle(); 1176 Rectangle bounds; 1177 TreePath path; 1178 boolean rootVisible = isRootVisible(); 1179 1180 while(!done && paintingEnumerator.hasMoreElements()) { 1181 path = (TreePath)paintingEnumerator.nextElement(); 1182 if(path != null) { 1183 isLeaf = treeModel.isLeaf(path.getLastPathComponent()); 1184 if(isLeaf) 1185 isExpanded = hasBeenExpanded = false; 1186 else { 1187 isExpanded = treeState.getExpandedState(path); 1188 hasBeenExpanded = tree.hasBeenExpanded(path); 1189 } 1190 bounds = getPathBounds(path, insets, boundsBuffer); 1191 if(bounds == null) 1192 // This will only happen if the model changes out 1193 // from under us (usually in another thread). 1194 // Swing isn't multithreaded, but I'll put this 1195 // check in anyway. 1196 return; 1197 // See if the vertical line to the parent has been drawn. 1198 parentPath = path.getParentPath(); 1199 if(parentPath != null) { 1200 if(drawingCache.get(parentPath) == null) { 1201 paintVerticalPartOfLeg(g, paintBounds, 1202 insets, parentPath); 1203 drawingCache.put(parentPath, Boolean.TRUE); 1204 } 1205 paintHorizontalPartOfLeg(g, paintBounds, insets, 1206 bounds, path, row, 1207 isExpanded, 1208 hasBeenExpanded, isLeaf); 1209 } 1210 else if(rootVisible && row == 0) { 1211 paintHorizontalPartOfLeg(g, paintBounds, insets, 1212 bounds, path, row, 1213 isExpanded, 1214 hasBeenExpanded, isLeaf); 1215 } 1216 if(shouldPaintExpandControl(path, row, isExpanded, 1217 hasBeenExpanded, isLeaf)) { 1218 paintExpandControl(g, paintBounds, insets, bounds, 1219 path, row, isExpanded, 1220 hasBeenExpanded, isLeaf); 1221 } 1222 paintRow(g, paintBounds, insets, bounds, path, 1223 row, isExpanded, hasBeenExpanded, isLeaf); 1224 if((bounds.y + bounds.height) >= endY) 1225 done = true; 1226 } 1227 else { 1228 done = true; 1229 } 1230 row++; 1231 } 1232 } 1233 1234 paintDropLine(g); 1235 1236 // Empty out the renderer pane, allowing renderers to be gc'ed. 1237 rendererPane.removeAll(); 1238 1239 drawingCache.clear(); 1240 } 1241 1242 private boolean isDropLine(JTree.DropLocation loc) { 1243 return loc != null && loc.getPath() != null && loc.getChildIndex() != -1; 1244 } 1245 1246 private void paintDropLine(Graphics g) { 1247 JTree.DropLocation loc = tree.getDropLocation(); 1248 if (!isDropLine(loc)) { 1249 return; 1250 } 1251 1252 Color c = UIManager.getColor("Tree.dropLineColor"); 1253 if (c != null) { 1254 g.setColor(c); 1255 Rectangle rect = getDropLineRect(loc); 1256 g.fillRect(rect.x, rect.y, rect.width, rect.height); 1257 } 1258 } 1259 1260 private Rectangle getDropLineRect(JTree.DropLocation loc) { 1261 Rectangle rect = null; 1262 TreePath path = loc.getPath(); 1263 int index = loc.getChildIndex(); 1264 boolean ltr = leftToRight; 1265 1266 Insets insets = tree.getInsets(); 1267 1268 if (tree.getRowCount() == 0) { 1269 rect = new Rectangle(insets.left, 1270 insets.top, 1271 tree.getWidth() - insets.left - insets.right, 1272 0); 1273 } else { 1274 TreeModel model = getModel(); 1275 Object root = model.getRoot(); 1276 1277 if (path.getLastPathComponent() == root 1278 && index >= model.getChildCount(root)) { 1279 1280 rect = tree.getRowBounds(tree.getRowCount() - 1); 1281 rect.y = rect.y + rect.height; 1282 Rectangle xRect; 1283 1284 if (!tree.isRootVisible()) { 1285 xRect = tree.getRowBounds(0); 1286 } else if (model.getChildCount(root) == 0){ 1287 xRect = tree.getRowBounds(0); 1288 xRect.x += totalChildIndent; 1289 xRect.width -= totalChildIndent + totalChildIndent; 1290 } else { 1291 TreePath lastChildPath = path.pathByAddingChild( 1292 model.getChild(root, model.getChildCount(root) - 1)); 1293 xRect = tree.getPathBounds(lastChildPath); 1294 } 1295 1296 rect.x = xRect.x; 1297 rect.width = xRect.width; 1298 } else { 1299 rect = tree.getPathBounds(path.pathByAddingChild( 1300 model.getChild(path.getLastPathComponent(), index))); 1301 } 1302 } 1303 1304 if (rect.y != 0) { 1305 rect.y--; 1306 } 1307 1308 if (!ltr) { 1309 rect.x = rect.x + rect.width - 100; 1310 } 1311 1312 rect.width = 100; 1313 rect.height = 2; 1314 1315 return rect; 1316 } 1317 1318 /** 1319 * Paints the horizontal part of the leg. The receiver should 1320 * NOT modify <code>clipBounds</code>, or <code>insets</code>.<p> 1321 * NOTE: <code>parentRow</code> can be -1 if the root is not visible. 1322 */ 1323 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, 1324 Insets insets, Rectangle bounds, 1325 TreePath path, int row, 1326 boolean isExpanded, 1327 boolean hasBeenExpanded, boolean 1328 isLeaf) { 1329 if (!paintLines) { 1330 return; 1331 } 1332 1333 // Don't paint the legs for the root'ish node if the 1334 int depth = path.getPathCount() - 1; 1335 if((depth == 0 || (depth == 1 && !isRootVisible())) && 1336 !getShowsRootHandles()) { 1337 return; 1338 } 1339 1340 int clipLeft = clipBounds.x; 1341 int clipRight = clipBounds.x + clipBounds.width; 1342 int clipTop = clipBounds.y; 1343 int clipBottom = clipBounds.y + clipBounds.height; 1344 int lineY = bounds.y + bounds.height / 2; 1345 1346 if (leftToRight) { 1347 int leftX = bounds.x - getRightChildIndent(); 1348 int nodeX = bounds.x - getHorizontalLegBuffer(); 1349 1350 if(lineY >= clipTop 1351 && lineY < clipBottom 1352 && nodeX >= clipLeft 1353 && leftX < clipRight 1354 && leftX < nodeX) { 1355 1356 g.setColor(getHashColor()); 1357 paintHorizontalLine(g, tree, lineY, leftX, nodeX - 1); 1358 } 1359 } else { 1360 int nodeX = bounds.x + bounds.width + getHorizontalLegBuffer(); 1361 int rightX = bounds.x + bounds.width + getRightChildIndent(); 1362 1363 if(lineY >= clipTop 1364 && lineY < clipBottom 1365 && rightX >= clipLeft 1366 && nodeX < clipRight 1367 && nodeX < rightX) { 1368 1369 g.setColor(getHashColor()); 1370 paintHorizontalLine(g, tree, lineY, nodeX, rightX - 1); 1371 } 1372 } 1373 } 1374 1375 /** 1376 * Paints the vertical part of the leg. The receiver should 1377 * NOT modify <code>clipBounds</code>, <code>insets</code>.<p> 1378 */ 1379 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, 1380 Insets insets, TreePath path) { 1381 if (!paintLines) { 1382 return; 1383 } 1384 1385 int depth = path.getPathCount() - 1; 1386 if (depth == 0 && !getShowsRootHandles() && !isRootVisible()) { 1387 return; 1388 } 1389 int lineX = getRowX(-1, depth + 1); 1390 if (leftToRight) { 1391 lineX = lineX - getRightChildIndent() + insets.left; 1392 } 1393 else { 1394 lineX = tree.getWidth() - lineX - insets.right + 1395 getRightChildIndent() - 1; 1396 } 1397 int clipLeft = clipBounds.x; 1398 int clipRight = clipBounds.x + (clipBounds.width - 1); 1399 1400 if (lineX >= clipLeft && lineX <= clipRight) { 1401 int clipTop = clipBounds.y; 1402 int clipBottom = clipBounds.y + clipBounds.height; 1403 Rectangle parentBounds = getPathBounds(tree, path); 1404 Rectangle lastChildBounds = getPathBounds(tree, 1405 getLastChildPath(path)); 1406 1407 if(lastChildBounds == null) 1408 // This shouldn't happen, but if the model is modified 1409 // in another thread it is possible for this to happen. 1410 // Swing isn't multithreaded, but I'll add this check in 1411 // anyway. 1412 return; 1413 1414 int top; 1415 1416 if(parentBounds == null) { 1417 top = Math.max(insets.top + getVerticalLegBuffer(), 1418 clipTop); 1419 } 1420 else 1421 top = Math.max(parentBounds.y + parentBounds.height + 1422 getVerticalLegBuffer(), clipTop); 1423 if(depth == 0 && !isRootVisible()) { 1424 TreeModel model = getModel(); 1425 1426 if(model != null) { 1427 Object root = model.getRoot(); 1428 1429 if(model.getChildCount(root) > 0) { 1430 parentBounds = getPathBounds(tree, path. 1431 pathByAddingChild(model.getChild(root, 0))); 1432 if(parentBounds != null) 1433 top = Math.max(insets.top + getVerticalLegBuffer(), 1434 parentBounds.y + 1435 parentBounds.height / 2); 1436 } 1437 } 1438 } 1439 1440 int bottom = Math.min(lastChildBounds.y + 1441 (lastChildBounds.height / 2), clipBottom); 1442 1443 if (top <= bottom) { 1444 g.setColor(getHashColor()); 1445 paintVerticalLine(g, tree, lineX, top, bottom); 1446 } 1447 } 1448 } 1449 1450 /** 1451 * Paints the expand (toggle) part of a row. The receiver should 1452 * NOT modify <code>clipBounds</code>, or <code>insets</code>. 1453 */ 1454 protected void paintExpandControl(Graphics g, 1455 Rectangle clipBounds, Insets insets, 1456 Rectangle bounds, TreePath path, 1457 int row, boolean isExpanded, 1458 boolean hasBeenExpanded, 1459 boolean isLeaf) { 1460 Object value = path.getLastPathComponent(); 1461 1462 // Draw icons if not a leaf and either hasn't been loaded, 1463 // or the model child count is > 0. 1464 if (!isLeaf && (!hasBeenExpanded || 1465 treeModel.getChildCount(value) > 0)) { 1466 int middleXOfKnob; 1467 if (leftToRight) { 1468 middleXOfKnob = bounds.x - getRightChildIndent() + 1; 1469 } else { 1470 middleXOfKnob = bounds.x + bounds.width + getRightChildIndent() - 1; 1471 } 1472 int middleYOfKnob = bounds.y + (bounds.height / 2); 1473 1474 if (isExpanded) { 1475 Icon expandedIcon = getExpandedIcon(); 1476 if(expandedIcon != null) 1477 drawCentered(tree, g, expandedIcon, middleXOfKnob, 1478 middleYOfKnob ); 1479 } 1480 else { 1481 Icon collapsedIcon = getCollapsedIcon(); 1482 if(collapsedIcon != null) 1483 drawCentered(tree, g, collapsedIcon, middleXOfKnob, 1484 middleYOfKnob); 1485 } 1486 } 1487 } 1488 1489 /** 1490 * Paints the renderer part of a row. The receiver should 1491 * NOT modify <code>clipBounds</code>, or <code>insets</code>. 1492 */ 1493 protected void paintRow(Graphics g, Rectangle clipBounds, 1494 Insets insets, Rectangle bounds, TreePath path, 1495 int row, boolean isExpanded, 1496 boolean hasBeenExpanded, boolean isLeaf) { 1497 // Don't paint the renderer if editing this row. 1498 if(editingComponent != null && editingRow == row) 1499 return; 1500 1501 int leadIndex; 1502 1503 if(tree.hasFocus()) { 1504 leadIndex = getLeadSelectionRow(); 1505 } 1506 else 1507 leadIndex = -1; 1508 1509 Component component; 1510 1511 component = currentCellRenderer.getTreeCellRendererComponent 1512 (tree, path.getLastPathComponent(), 1513 tree.isRowSelected(row), isExpanded, isLeaf, row, 1514 (leadIndex == row)); 1515 1516 rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y, 1517 bounds.width, bounds.height, true); 1518 } 1519 1520 /** 1521 * Returns true if the expand (toggle) control should be drawn for 1522 * the specified row. 1523 */ 1524 protected boolean shouldPaintExpandControl(TreePath path, int row, 1525 boolean isExpanded, 1526 boolean hasBeenExpanded, 1527 boolean isLeaf) { 1528 if(isLeaf) 1529 return false; 1530 1531 int depth = path.getPathCount() - 1; 1532 1533 if((depth == 0 || (depth == 1 && !isRootVisible())) && 1534 !getShowsRootHandles()) 1535 return false; 1536 return true; 1537 } 1538 1539 /** 1540 * Paints a vertical line. 1541 */ 1542 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, 1543 int bottom) { 1544 if (lineTypeDashed) { 1545 drawDashedVerticalLine(g, x, top, bottom); 1546 } else { 1547 g.drawLine(x, top, x, bottom); 1548 } 1549 } 1550 1551 /** 1552 * Paints a horizontal line. 1553 */ 1554 protected void paintHorizontalLine(Graphics g, JComponent c, int y, 1555 int left, int right) { 1556 if (lineTypeDashed) { 1557 drawDashedHorizontalLine(g, y, left, right); 1558 } else { 1559 g.drawLine(left, y, right, y); 1560 } 1561 } 1562 1563 /** 1564 * The vertical element of legs between nodes starts at the bottom of the 1565 * parent node by default. This method makes the leg start below that. 1566 */ 1567 protected int getVerticalLegBuffer() { 1568 return 0; 1569 } 1570 1571 /** 1572 * The horizontal element of legs between nodes starts at the 1573 * right of the left-hand side of the child node by default. This 1574 * method makes the leg end before that. 1575 */ 1576 protected int getHorizontalLegBuffer() { 1577 return 0; 1578 } 1579 1580 private int findCenteredX(int x, int iconWidth) { 1581 return leftToRight 1582 ? x - (int)Math.ceil(iconWidth / 2.0) 1583 : x - (int)Math.floor(iconWidth / 2.0); 1584 } 1585 1586 // 1587 // Generic painting methods 1588 // 1589 1590 // Draws the icon centered at (x,y) 1591 protected void drawCentered(Component c, Graphics graphics, Icon icon, 1592 int x, int y) { 1593 icon.paintIcon(c, graphics, 1594 findCenteredX(x, icon.getIconWidth()), 1595 y - icon.getIconHeight() / 2); 1596 } 1597 1598 // This method is slow -- revisit when Java2D is ready. 1599 // assumes x1 <= x2 1600 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2){ 1601 // Drawing only even coordinates helps join line segments so they 1602 // appear as one line. This can be defeated by translating the 1603 // Graphics by an odd amount. 1604 x1 += (x1 % 2); 1605 1606 for (int x = x1; x <= x2; x+=2) { 1607 g.drawLine(x, y, x, y); 1608 } 1609 } 1610 1611 // This method is slow -- revisit when Java2D is ready. 1612 // assumes y1 <= y2 1613 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) { 1614 // Drawing only even coordinates helps join line segments so they 1615 // appear as one line. This can be defeated by translating the 1616 // Graphics by an odd amount. 1617 y1 += (y1 % 2); 1618 1619 for (int y = y1; y <= y2; y+=2) { 1620 g.drawLine(x, y, x, y); 1621 } 1622 } 1623 1624 // 1625 // Various local methods 1626 // 1627 1628 /** 1629 * Returns the location, along the x-axis, to render a particular row 1630 * at. The return value does not include any Insets specified on the JTree. 1631 * This does not check for the validity of the row or depth, it is assumed 1632 * to be correct and will not throw an Exception if the row or depth 1633 * doesn't match that of the tree. 1634 * 1635 * @param row Row to return x location for 1636 * @param depth Depth of the row 1637 * @return amount to indent the given row. 1638 * @since 1.5 1639 */ 1640 protected int getRowX(int row, int depth) { 1641 return totalChildIndent * (depth + depthOffset); 1642 } 1643 1644 /** 1645 * Makes all the nodes that are expanded in JTree expanded in LayoutCache. 1646 * This invokes updateExpandedDescendants with the root path. 1647 */ 1648 protected void updateLayoutCacheExpandedNodes() { 1649 if(treeModel != null && treeModel.getRoot() != null) 1650 updateExpandedDescendants(new TreePath(treeModel.getRoot())); 1651 } 1652 1653 private void updateLayoutCacheExpandedNodesIfNecessary() { 1654 if (treeModel != null && treeModel.getRoot() != null) { 1655 TreePath rootPath = new TreePath(treeModel.getRoot()); 1656 if (tree.isExpanded(rootPath)) { 1657 updateLayoutCacheExpandedNodes(); 1658 } else { 1659 treeState.setExpandedState(rootPath, false); 1660 } 1661 } 1662 } 1663 1664 /** 1665 * Updates the expanded state of all the descendants of <code>path</code> 1666 * by getting the expanded descendants from the tree and forwarding 1667 * to the tree state. 1668 */ 1669 protected void updateExpandedDescendants(TreePath path) { 1670 completeEditing(); 1671 if(treeState != null) { 1672 treeState.setExpandedState(path, true); 1673 1674 Enumeration descendants = tree.getExpandedDescendants(path); 1675 1676 if(descendants != null) { 1677 while(descendants.hasMoreElements()) { 1678 path = (TreePath)descendants.nextElement(); 1679 treeState.setExpandedState(path, true); 1680 } 1681 } 1682 updateLeadRow(); 1683 updateSize(); 1684 } 1685 } 1686 1687 /** 1688 * Returns a path to the last child of <code>parent</code>. 1689 */ 1690 protected TreePath getLastChildPath(TreePath parent) { 1691 if(treeModel != null) { 1692 int childCount = treeModel.getChildCount 1693 (parent.getLastPathComponent()); 1694 1695 if(childCount > 0) 1696 return parent.pathByAddingChild(treeModel.getChild 1697 (parent.getLastPathComponent(), childCount - 1)); 1698 } 1699 return null; 1700 } 1701 1702 /** 1703 * Updates how much each depth should be offset by. 1704 */ 1705 protected void updateDepthOffset() { 1706 if(isRootVisible()) { 1707 if(getShowsRootHandles()) 1708 depthOffset = 1; 1709 else 1710 depthOffset = 0; 1711 } 1712 else if(!getShowsRootHandles()) 1713 depthOffset = -1; 1714 else 1715 depthOffset = 0; 1716 } 1717 1718 /** 1719 * Updates the cellEditor based on the editability of the JTree that 1720 * we're contained in. If the tree is editable but doesn't have a 1721 * cellEditor, a basic one will be used. 1722 */ 1723 protected void updateCellEditor() { 1724 TreeCellEditor newEditor; 1725 1726 completeEditing(); 1727 if(tree == null) 1728 newEditor = null; 1729 else { 1730 if(tree.isEditable()) { 1731 newEditor = tree.getCellEditor(); 1732 if(newEditor == null) { 1733 newEditor = createDefaultCellEditor(); 1734 if(newEditor != null) { 1735 tree.setCellEditor(newEditor); 1736 createdCellEditor = true; 1737 } 1738 } 1739 } 1740 else 1741 newEditor = null; 1742 } 1743 if(newEditor != cellEditor) { 1744 if(cellEditor != null && cellEditorListener != null) 1745 cellEditor.removeCellEditorListener(cellEditorListener); 1746 cellEditor = newEditor; 1747 if(cellEditorListener == null) 1748 cellEditorListener = createCellEditorListener(); 1749 if(newEditor != null && cellEditorListener != null) 1750 newEditor.addCellEditorListener(cellEditorListener); 1751 createdCellEditor = false; 1752 } 1753 } 1754 1755 /** 1756 * Messaged from the tree we're in when the renderer has changed. 1757 */ 1758 protected void updateRenderer() { 1759 if(tree != null) { 1760 TreeCellRenderer newCellRenderer; 1761 1762 newCellRenderer = tree.getCellRenderer(); 1763 if(newCellRenderer == null) { 1764 tree.setCellRenderer(createDefaultCellRenderer()); 1765 createdRenderer = true; 1766 } 1767 else { 1768 createdRenderer = false; 1769 currentCellRenderer = newCellRenderer; 1770 if(createdCellEditor) { 1771 tree.setCellEditor(null); 1772 } 1773 } 1774 } 1775 else { 1776 createdRenderer = false; 1777 currentCellRenderer = null; 1778 } 1779 updateCellEditor(); 1780 } 1781 1782 /** 1783 * Resets the TreeState instance based on the tree we're providing the 1784 * look and feel for. 1785 */ 1786 protected void configureLayoutCache() { 1787 if(treeState != null && tree != null) { 1788 if(nodeDimensions == null) 1789 nodeDimensions = createNodeDimensions(); 1790 treeState.setNodeDimensions(nodeDimensions); 1791 treeState.setRootVisible(tree.isRootVisible()); 1792 treeState.setRowHeight(tree.getRowHeight()); 1793 treeState.setSelectionModel(getSelectionModel()); 1794 // Only do this if necessary, may loss state if call with 1795 // same model as it currently has. 1796 if(treeState.getModel() != tree.getModel()) 1797 treeState.setModel(tree.getModel()); 1798 updateLayoutCacheExpandedNodesIfNecessary(); 1799 // Create a listener to update preferred size when bounds 1800 // changes, if necessary. 1801 if(isLargeModel()) { 1802 if(componentListener == null) { 1803 componentListener = createComponentListener(); 1804 if(componentListener != null) 1805 tree.addComponentListener(componentListener); 1806 } 1807 } 1808 else if(componentListener != null) { 1809 tree.removeComponentListener(componentListener); 1810 componentListener = null; 1811 } 1812 } 1813 else if(componentListener != null) { 1814 tree.removeComponentListener(componentListener); 1815 componentListener = null; 1816 } 1817 } 1818 1819 /** 1820 * Marks the cached size as being invalid, and messages the 1821 * tree with <code>treeDidChange</code>. 1822 */ 1823 protected void updateSize() { 1824 validCachedPreferredSize = false; 1825 tree.treeDidChange(); 1826 } 1827 1828 private void updateSize0() { 1829 validCachedPreferredSize = false; 1830 tree.revalidate(); 1831 } 1832 1833 /** 1834 * Updates the <code>preferredSize</code> instance variable, 1835 * which is returned from <code>getPreferredSize()</code>.<p> 1836 * For left to right orientations, the size is determined from the 1837 * current AbstractLayoutCache. For RTL orientations, the preferred size 1838 * becomes the width minus the minimum x position. 1839 */ 1840 protected void updateCachedPreferredSize() { 1841 if(treeState != null) { 1842 Insets i = tree.getInsets(); 1843 1844 if(isLargeModel()) { 1845 Rectangle visRect = tree.getVisibleRect(); 1846 1847 if (visRect.x == 0 && visRect.y == 0 && 1848 visRect.width == 0 && visRect.height == 0 && 1849 tree.getVisibleRowCount() > 0) { 1850 // The tree doesn't have a valid bounds yet. Calculate 1851 // based on visible row count. 1852 visRect.width = 1; 1853 visRect.height = tree.getRowHeight() * 1854 tree.getVisibleRowCount(); 1855 } else { 1856 visRect.x -= i.left; 1857 visRect.y -= i.top; 1858 } 1859 preferredSize.width = treeState.getPreferredWidth(visRect); 1860 } 1861 else { 1862 preferredSize.width = treeState.getPreferredWidth(null); 1863 } 1864 preferredSize.height = treeState.getPreferredHeight(); 1865 preferredSize.width += i.left + i.right; 1866 preferredSize.height += i.top + i.bottom; 1867 } 1868 validCachedPreferredSize = true; 1869 } 1870 1871 /** 1872 * Messaged from the VisibleTreeNode after it has been expanded. 1873 */ 1874 protected void pathWasExpanded(TreePath path) { 1875 if(tree != null) { 1876 tree.fireTreeExpanded(path); 1877 } 1878 } 1879 1880 /** 1881 * Messaged from the VisibleTreeNode after it has collapsed. 1882 */ 1883 protected void pathWasCollapsed(TreePath path) { 1884 if(tree != null) { 1885 tree.fireTreeCollapsed(path); 1886 } 1887 } 1888 1889 /** 1890 * Ensures that the rows identified by beginRow through endRow are 1891 * visible. 1892 */ 1893 protected void ensureRowsAreVisible(int beginRow, int endRow) { 1894 if(tree != null && beginRow >= 0 && endRow < getRowCount(tree)) { 1895 boolean scrollVert = DefaultLookup.getBoolean(tree, this, 1896 "Tree.scrollsHorizontallyAndVertically", false); 1897 if(beginRow == endRow) { 1898 Rectangle scrollBounds = getPathBounds(tree, getPathForRow 1899 (tree, beginRow)); 1900 1901 if(scrollBounds != null) { 1902 if (!scrollVert) { 1903 scrollBounds.x = tree.getVisibleRect().x; 1904 scrollBounds.width = 1; 1905 } 1906 tree.scrollRectToVisible(scrollBounds); 1907 } 1908 } 1909 else { 1910 Rectangle beginRect = getPathBounds(tree, getPathForRow 1911 (tree, beginRow)); 1912 Rectangle visRect = tree.getVisibleRect(); 1913 Rectangle testRect = beginRect; 1914 int beginY = beginRect.y; 1915 int maxY = beginY + visRect.height; 1916 1917 for(int counter = beginRow + 1; counter <= endRow; counter++) { 1918 testRect = getPathBounds(tree, 1919 getPathForRow(tree, counter)); 1920 if((testRect.y + testRect.height) > maxY) 1921 counter = endRow; 1922 } 1923 tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1, 1924 testRect.y + testRect.height- 1925 beginY)); 1926 } 1927 } 1928 } 1929 1930 /** Sets the preferred minimum size. 1931 */ 1932 public void setPreferredMinSize(Dimension newSize) { 1933 preferredMinSize = newSize; 1934 } 1935 1936 /** Returns the minimum preferred size. 1937 */ 1938 public Dimension getPreferredMinSize() { 1939 if(preferredMinSize == null) 1940 return null; 1941 return new Dimension(preferredMinSize); 1942 } 1943 1944 /** Returns the preferred size to properly display the tree, 1945 * this is a cover method for getPreferredSize(c, false). 1946 */ 1947 public Dimension getPreferredSize(JComponent c) { 1948 return getPreferredSize(c, true); 1949 } 1950 1951 /** Returns the preferred size to represent the tree in 1952 * <I>c</I>. If <I>checkConsistancy</I> is true 1953 * <b>checkConsistancy</b> is messaged first. 1954 */ 1955 public Dimension getPreferredSize(JComponent c, 1956 boolean checkConsistancy) { 1957 Dimension pSize = this.getPreferredMinSize(); 1958 1959 if(!validCachedPreferredSize) 1960 updateCachedPreferredSize(); 1961 if(tree != null) { 1962 if(pSize != null) 1963 return new Dimension(Math.max(pSize.width, 1964 preferredSize.width), 1965 Math.max(pSize.height, preferredSize.height)); 1966 return new Dimension(preferredSize.width, preferredSize.height); 1967 } 1968 else if(pSize != null) 1969 return pSize; 1970 else 1971 return new Dimension(0, 0); 1972 } 1973 1974 /** 1975 * Returns the minimum size for this component. Which will be 1976 * the min preferred size or 0, 0. 1977 */ 1978 public Dimension getMinimumSize(JComponent c) { 1979 if(this.getPreferredMinSize() != null) 1980 return this.getPreferredMinSize(); 1981 return new Dimension(0, 0); 1982 } 1983 1984 /** 1985 * Returns the maximum size for this component, which will be the 1986 * preferred size if the instance is currently in a JTree, or 0, 0. 1987 */ 1988 public Dimension getMaximumSize(JComponent c) { 1989 if(tree != null) 1990 return getPreferredSize(tree); 1991 if(this.getPreferredMinSize() != null) 1992 return this.getPreferredMinSize(); 1993 return new Dimension(0, 0); 1994 } 1995 1996 1997 /** 1998 * Messages to stop the editing session. If the UI the receiver 1999 * is providing the look and feel for returns true from 2000 * <code>getInvokesStopCellEditing</code>, stopCellEditing will 2001 * invoked on the current editor. Then completeEditing will 2002 * be messaged with false, true, false to cancel any lingering 2003 * editing. 2004 */ 2005 protected void completeEditing() { 2006 /* If should invoke stopCellEditing, try that */ 2007 if(tree.getInvokesStopCellEditing() && 2008 stopEditingInCompleteEditing && editingComponent != null) { 2009 cellEditor.stopCellEditing(); 2010 } 2011 /* Invoke cancelCellEditing, this will do nothing if stopCellEditing 2012 was successful. */ 2013 completeEditing(false, true, false); 2014 } 2015 2016 /** 2017 * Stops the editing session. If messageStop is true the editor 2018 * is messaged with stopEditing, if messageCancel is true the 2019 * editor is messaged with cancelEditing. If messageTree is true 2020 * the treeModel is messaged with valueForPathChanged. 2021 */ 2022 protected void completeEditing(boolean messageStop, 2023 boolean messageCancel, 2024 boolean messageTree) { 2025 if(stopEditingInCompleteEditing && editingComponent != null) { 2026 Component oldComponent = editingComponent; 2027 TreePath oldPath = editingPath; 2028 TreeCellEditor oldEditor = cellEditor; 2029 Object newValue = oldEditor.getCellEditorValue(); 2030 Rectangle editingBounds = getPathBounds(tree, 2031 editingPath); 2032 boolean requestFocus = (tree != null && 2033 (tree.hasFocus() || SwingUtilities. 2034 findFocusOwner(editingComponent) != null)); 2035 2036 editingComponent = null; 2037 editingPath = null; 2038 if(messageStop) 2039 oldEditor.stopCellEditing(); 2040 else if(messageCancel) 2041 oldEditor.cancelCellEditing(); 2042 tree.remove(oldComponent); 2043 if(editorHasDifferentSize) { 2044 treeState.invalidatePathBounds(oldPath); 2045 updateSize(); 2046 } 2047 else { 2048 editingBounds.x = 0; 2049 editingBounds.width = tree.getSize().width; 2050 tree.repaint(editingBounds); 2051 } 2052 if(requestFocus) 2053 tree.requestFocus(); 2054 if(messageTree) 2055 treeModel.valueForPathChanged(oldPath, newValue); 2056 } 2057 } 2058 2059 // cover method for startEditing that allows us to pass extra 2060 // information into that method via a class variable 2061 private boolean startEditingOnRelease(TreePath path, 2062 MouseEvent event, 2063 MouseEvent releaseEvent) { 2064 this.releaseEvent = releaseEvent; 2065 try { 2066 return startEditing(path, event); 2067 } finally { 2068 this.releaseEvent = null; 2069 } 2070 } 2071 2072 /** 2073 * Will start editing for node if there is a cellEditor and 2074 * shouldSelectCell returns true.<p> 2075 * This assumes that path is valid and visible. 2076 */ 2077 protected boolean startEditing(TreePath path, MouseEvent event) { 2078 if (isEditing(tree) && tree.getInvokesStopCellEditing() && 2079 !stopEditing(tree)) { 2080 return false; 2081 } 2082 completeEditing(); 2083 if(cellEditor != null && tree.isPathEditable(path)) { 2084 int row = getRowForPath(tree, path); 2085 2086 if(cellEditor.isCellEditable(event)) { 2087 editingComponent = cellEditor.getTreeCellEditorComponent 2088 (tree, path.getLastPathComponent(), 2089 tree.isPathSelected(path), tree.isExpanded(path), 2090 treeModel.isLeaf(path.getLastPathComponent()), row); 2091 Rectangle nodeBounds = getPathBounds(tree, path); 2092 2093 editingRow = row; 2094 2095 Dimension editorSize = editingComponent.getPreferredSize(); 2096 2097 // Only allow odd heights if explicitly set. 2098 if(editorSize.height != nodeBounds.height && 2099 getRowHeight() > 0) 2100 editorSize.height = getRowHeight(); 2101 2102 if(editorSize.width != nodeBounds.width || 2103 editorSize.height != nodeBounds.height) { 2104 // Editor wants different width or height, invalidate 2105 // treeState and relayout. 2106 editorHasDifferentSize = true; 2107 treeState.invalidatePathBounds(path); 2108 updateSize(); 2109 // To make sure x/y are updated correctly, fetch 2110 // the bounds again. 2111 nodeBounds = getPathBounds(tree, path); 2112 } 2113 else 2114 editorHasDifferentSize = false; 2115 tree.add(editingComponent); 2116 editingComponent.setBounds(nodeBounds.x, nodeBounds.y, 2117 nodeBounds.width, 2118 nodeBounds.height); 2119 editingPath = path; 2120 if (editingComponent instanceof JComponent) { 2121 ((JComponent)editingComponent).revalidate(); 2122 } else { 2123 editingComponent.validate(); 2124 } 2125 editingComponent.repaint(); 2126 if(cellEditor.shouldSelectCell(event)) { 2127 stopEditingInCompleteEditing = false; 2128 tree.setSelectionRow(row); 2129 stopEditingInCompleteEditing = true; 2130 } 2131 2132 Component focusedComponent = SwingUtilities2. 2133 compositeRequestFocus(editingComponent); 2134 boolean selectAll = true; 2135 2136 if(event != null && event instanceof MouseEvent) { 2137 /* Find the component that will get forwarded all the 2138 mouse events until mouseReleased. */ 2139 Point componentPoint = SwingUtilities.convertPoint 2140 (tree, new Point(event.getX(), event.getY()), 2141 editingComponent); 2142 2143 /* Create an instance of BasicTreeMouseListener to handle 2144 passing the mouse/motion events to the necessary 2145 component. */ 2146 // We really want similar behavior to getMouseEventTarget, 2147 // but it is package private. 2148 Component activeComponent = SwingUtilities. 2149 getDeepestComponentAt(editingComponent, 2150 componentPoint.x, componentPoint.y); 2151 if (activeComponent != null) { 2152 MouseInputHandler handler = 2153 new MouseInputHandler(tree, activeComponent, 2154 event, focusedComponent); 2155 2156 if (releaseEvent != null) { 2157 handler.mouseReleased(releaseEvent); 2158 } 2159 2160 selectAll = false; 2161 } 2162 } 2163 if (selectAll && focusedComponent instanceof JTextField) { 2164 ((JTextField)focusedComponent).selectAll(); 2165 } 2166 return true; 2167 } 2168 else 2169 editingComponent = null; 2170 } 2171 return false; 2172 } 2173 2174 // 2175 // Following are primarily for handling mouse events. 2176 // 2177 2178 /** 2179 * If the <code>mouseX</code> and <code>mouseY</code> are in the 2180 * expand/collapse region of the <code>row</code>, this will toggle 2181 * the row. 2182 */ 2183 protected void checkForClickInExpandControl(TreePath path, 2184 int mouseX, int mouseY) { 2185 if (isLocationInExpandControl(path, mouseX, mouseY)) { 2186 handleExpandControlClick(path, mouseX, mouseY); 2187 } 2188 } 2189 2190 /** 2191 * Returns true if <code>mouseX</code> and <code>mouseY</code> fall 2192 * in the area of row that is used to expand/collapse the node and 2193 * the node at <code>row</code> does not represent a leaf. 2194 */ 2195 protected boolean isLocationInExpandControl(TreePath path, 2196 int mouseX, int mouseY) { 2197 if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){ 2198 int boxWidth; 2199 Insets i = tree.getInsets(); 2200 2201 if(getExpandedIcon() != null) 2202 boxWidth = getExpandedIcon().getIconWidth(); 2203 else 2204 boxWidth = 8; 2205 2206 int boxLeftX = getRowX(tree.getRowForPath(path), 2207 path.getPathCount() - 1); 2208 2209 if (leftToRight) { 2210 boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1; 2211 } else { 2212 boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1; 2213 } 2214 2215 boxLeftX = findCenteredX(boxLeftX, boxWidth); 2216 2217 return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth)); 2218 } 2219 return false; 2220 } 2221 2222 /** 2223 * Messaged when the user clicks the particular row, this invokes 2224 * toggleExpandState. 2225 */ 2226 protected void handleExpandControlClick(TreePath path, int mouseX, 2227 int mouseY) { 2228 toggleExpandState(path); 2229 } 2230 2231 /** 2232 * Expands path if it is not expanded, or collapses row if it is expanded. 2233 * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible 2234 * is invoked to scroll as many of the children to visible as possible 2235 * (tries to scroll to last visible descendant of path). 2236 */ 2237 protected void toggleExpandState(TreePath path) { 2238 if(!tree.isExpanded(path)) { 2239 int row = getRowForPath(tree, path); 2240 2241 tree.expandPath(path); 2242 updateSize(); 2243 if(row != -1) { 2244 if(tree.getScrollsOnExpand()) 2245 ensureRowsAreVisible(row, row + treeState. 2246 getVisibleChildCount(path)); 2247 else 2248 ensureRowsAreVisible(row, row); 2249 } 2250 } 2251 else { 2252 tree.collapsePath(path); 2253 updateSize(); 2254 } 2255 } 2256 2257 /** 2258 * Returning true signifies a mouse event on the node should toggle 2259 * the selection of only the row under mouse. 2260 */ 2261 protected boolean isToggleSelectionEvent(MouseEvent event) { 2262 return (SwingUtilities.isLeftMouseButton(event) && 2263 event.isControlDown()); 2264 } 2265 2266 /** 2267 * Returning true signifies a mouse event on the node should select 2268 * from the anchor point. 2269 */ 2270 protected boolean isMultiSelectEvent(MouseEvent event) { 2271 return (SwingUtilities.isLeftMouseButton(event) && 2272 event.isShiftDown()); 2273 } 2274 2275 /** 2276 * Returning true indicates the row under the mouse should be toggled 2277 * based on the event. This is invoked after checkForClickInExpandControl, 2278 * implying the location is not in the expand (toggle) control 2279 */ 2280 protected boolean isToggleEvent(MouseEvent event) { 2281 if(!SwingUtilities.isLeftMouseButton(event)) { 2282 return false; 2283 } 2284 int clickCount = tree.getToggleClickCount(); 2285 2286 if(clickCount <= 0) { 2287 return false; 2288 } 2289 return ((event.getClickCount() % clickCount) == 0); 2290 } 2291 2292 /** 2293 * Messaged to update the selection based on a MouseEvent over a 2294 * particular row. If the event is a toggle selection event, the 2295 * row is either selected, or deselected. If the event identifies 2296 * a multi selection event, the selection is updated from the 2297 * anchor point. Otherwise the row is selected, and if the event 2298 * specified a toggle event the row is expanded/collapsed. 2299 */ 2300 protected void selectPathForEvent(TreePath path, MouseEvent event) { 2301 /* Adjust from the anchor point. */ 2302 if(isMultiSelectEvent(event)) { 2303 TreePath anchor = getAnchorSelectionPath(); 2304 int anchorRow = (anchor == null) ? -1 : 2305 getRowForPath(tree, anchor); 2306 2307 if(anchorRow == -1 || tree.getSelectionModel(). 2308 getSelectionMode() == TreeSelectionModel. 2309 SINGLE_TREE_SELECTION) { 2310 tree.setSelectionPath(path); 2311 } 2312 else { 2313 int row = getRowForPath(tree, path); 2314 TreePath lastAnchorPath = anchor; 2315 2316 if (isToggleSelectionEvent(event)) { 2317 if (tree.isRowSelected(anchorRow)) { 2318 tree.addSelectionInterval(anchorRow, row); 2319 } else { 2320 tree.removeSelectionInterval(anchorRow, row); 2321 tree.addSelectionInterval(row, row); 2322 } 2323 } else if(row < anchorRow) { 2324 tree.setSelectionInterval(row, anchorRow); 2325 } else { 2326 tree.setSelectionInterval(anchorRow, row); 2327 } 2328 lastSelectedRow = row; 2329 setAnchorSelectionPath(lastAnchorPath); 2330 setLeadSelectionPath(path); 2331 } 2332 } 2333 2334 // Should this event toggle the selection of this row? 2335 /* Control toggles just this node. */ 2336 else if(isToggleSelectionEvent(event)) { 2337 if(tree.isPathSelected(path)) 2338 tree.removeSelectionPath(path); 2339 else 2340 tree.addSelectionPath(path); 2341 lastSelectedRow = getRowForPath(tree, path); 2342 setAnchorSelectionPath(path); 2343 setLeadSelectionPath(path); 2344 } 2345 2346 /* Otherwise set the selection to just this interval. */ 2347 else if(SwingUtilities.isLeftMouseButton(event)) { 2348 tree.setSelectionPath(path); 2349 if(isToggleEvent(event)) { 2350 toggleExpandState(path); 2351 } 2352 } 2353 } 2354 2355 /** 2356 * @return true if the node at <code>row</code> is a leaf. 2357 */ 2358 protected boolean isLeaf(int row) { 2359 TreePath path = getPathForRow(tree, row); 2360 2361 if(path != null) 2362 return treeModel.isLeaf(path.getLastPathComponent()); 2363 // Have to return something here... 2364 return true; 2365 } 2366 2367 // 2368 // The following selection methods (lead/anchor) are covers for the 2369 // methods in JTree. 2370 // 2371 private void setAnchorSelectionPath(TreePath newPath) { 2372 ignoreLAChange = true; 2373 try { 2374 tree.setAnchorSelectionPath(newPath); 2375 } finally{ 2376 ignoreLAChange = false; 2377 } 2378 } 2379 2380 private TreePath getAnchorSelectionPath() { 2381 return tree.getAnchorSelectionPath(); 2382 } 2383 2384 private void setLeadSelectionPath(TreePath newPath) { 2385 setLeadSelectionPath(newPath, false); 2386 } 2387 2388 private void setLeadSelectionPath(TreePath newPath, boolean repaint) { 2389 Rectangle bounds = repaint ? 2390 getPathBounds(tree, getLeadSelectionPath()) : null; 2391 2392 ignoreLAChange = true; 2393 try { 2394 tree.setLeadSelectionPath(newPath); 2395 } finally { 2396 ignoreLAChange = false; 2397 } 2398 leadRow = getRowForPath(tree, newPath); 2399 2400 if (repaint) { 2401 if (bounds != null) { 2402 tree.repaint(getRepaintPathBounds(bounds)); 2403 } 2404 bounds = getPathBounds(tree, newPath); 2405 if (bounds != null) { 2406 tree.repaint(getRepaintPathBounds(bounds)); 2407 } 2408 } 2409 } 2410 2411 private Rectangle getRepaintPathBounds(Rectangle bounds) { 2412 if (UIManager.getBoolean("Tree.repaintWholeRow")) { 2413 bounds.x = 0; 2414 bounds.width = tree.getWidth(); 2415 } 2416 return bounds; 2417 } 2418 2419 private TreePath getLeadSelectionPath() { 2420 return tree.getLeadSelectionPath(); 2421 } 2422 2423 private void updateLeadRow() { 2424 leadRow = getRowForPath(tree, getLeadSelectionPath()); 2425 } 2426 2427 private int getLeadSelectionRow() { 2428 return leadRow; 2429 } 2430 2431 /** 2432 * Extends the selection from the anchor to make <code>newLead</code> 2433 * the lead of the selection. This does not scroll. 2434 */ 2435 private void extendSelection(TreePath newLead) { 2436 TreePath aPath = getAnchorSelectionPath(); 2437 int aRow = (aPath == null) ? -1 : 2438 getRowForPath(tree, aPath); 2439 int newIndex = getRowForPath(tree, newLead); 2440 2441 if(aRow == -1) { 2442 tree.setSelectionRow(newIndex); 2443 } 2444 else { 2445 if(aRow < newIndex) { 2446 tree.setSelectionInterval(aRow, newIndex); 2447 } 2448 else { 2449 tree.setSelectionInterval(newIndex, aRow); 2450 } 2451 setAnchorSelectionPath(aPath); 2452 setLeadSelectionPath(newLead); 2453 } 2454 } 2455 2456 /** 2457 * Invokes <code>repaint</code> on the JTree for the passed in TreePath, 2458 * <code>path</code>. 2459 */ 2460 private void repaintPath(TreePath path) { 2461 if (path != null) { 2462 Rectangle bounds = getPathBounds(tree, path); 2463 if (bounds != null) { 2464 tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height); 2465 } 2466 } 2467 } 2468 2469 /** 2470 * Updates the TreeState in response to nodes expanding/collapsing. 2471 */ 2472 public class TreeExpansionHandler implements TreeExpansionListener { 2473 // NOTE: This class exists only for backward compatability. All 2474 // its functionality has been moved into Handler. If you need to add 2475 // new functionality add it to the Handler, but make sure this 2476 // class calls into the Handler. 2477 2478 /** 2479 * Called whenever an item in the tree has been expanded. 2480 */ 2481 public void treeExpanded(TreeExpansionEvent event) { 2482 getHandler().treeExpanded(event); 2483 } 2484 2485 /** 2486 * Called whenever an item in the tree has been collapsed. 2487 */ 2488 public void treeCollapsed(TreeExpansionEvent event) { 2489 getHandler().treeCollapsed(event); 2490 } 2491 } // BasicTreeUI.TreeExpansionHandler 2492 2493 2494 /** 2495 * Updates the preferred size when scrolling (if necessary). 2496 */ 2497 public class ComponentHandler extends ComponentAdapter implements 2498 ActionListener { 2499 /** Timer used when inside a scrollpane and the scrollbar is 2500 * adjusting. */ 2501 protected Timer timer; 2502 /** ScrollBar that is being adjusted. */ 2503 protected JScrollBar scrollBar; 2504 2505 public void componentMoved(ComponentEvent e) { 2506 if(timer == null) { 2507 JScrollPane scrollPane = getScrollPane(); 2508 2509 if(scrollPane == null) 2510 updateSize(); 2511 else { 2512 scrollBar = scrollPane.getVerticalScrollBar(); 2513 if(scrollBar == null || 2514 !scrollBar.getValueIsAdjusting()) { 2515 // Try the horizontal scrollbar. 2516 if((scrollBar = scrollPane.getHorizontalScrollBar()) 2517 != null && scrollBar.getValueIsAdjusting()) 2518 startTimer(); 2519 else 2520 updateSize(); 2521 } 2522 else 2523 startTimer(); 2524 } 2525 } 2526 } 2527 2528 /** 2529 * Creates, if necessary, and starts a Timer to check if need to 2530 * resize the bounds. 2531 */ 2532 protected void startTimer() { 2533 if(timer == null) { 2534 timer = new Timer(200, this); 2535 timer.setRepeats(true); 2536 } 2537 timer.start(); 2538 } 2539 2540 /** 2541 * Returns the JScrollPane housing the JTree, or null if one isn't 2542 * found. 2543 */ 2544 protected JScrollPane getScrollPane() { 2545 Component c = tree.getParent(); 2546 2547 while(c != null && !(c instanceof JScrollPane)) 2548 c = c.getParent(); 2549 if(c instanceof JScrollPane) 2550 return (JScrollPane)c; 2551 return null; 2552 } 2553 2554 /** 2555 * Public as a result of Timer. If the scrollBar is null, or 2556 * not adjusting, this stops the timer and updates the sizing. 2557 */ 2558 public void actionPerformed(ActionEvent ae) { 2559 if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { 2560 if(timer != null) 2561 timer.stop(); 2562 updateSize(); 2563 timer = null; 2564 scrollBar = null; 2565 } 2566 } 2567 } // End of BasicTreeUI.ComponentHandler 2568 2569 2570 /** 2571 * Forwards all TreeModel events to the TreeState. 2572 */ 2573 public class TreeModelHandler implements TreeModelListener { 2574 2575 // NOTE: This class exists only for backward compatability. All 2576 // its functionality has been moved into Handler. If you need to add 2577 // new functionality add it to the Handler, but make sure this 2578 // class calls into the Handler. 2579 2580 public void treeNodesChanged(TreeModelEvent e) { 2581 getHandler().treeNodesChanged(e); 2582 } 2583 2584 public void treeNodesInserted(TreeModelEvent e) { 2585 getHandler().treeNodesInserted(e); 2586 } 2587 2588 public void treeNodesRemoved(TreeModelEvent e) { 2589 getHandler().treeNodesRemoved(e); 2590 } 2591 2592 public void treeStructureChanged(TreeModelEvent e) { 2593 getHandler().treeStructureChanged(e); 2594 } 2595 } // End of BasicTreeUI.TreeModelHandler 2596 2597 2598 /** 2599 * Listens for changes in the selection model and updates the display 2600 * accordingly. 2601 */ 2602 public class TreeSelectionHandler implements TreeSelectionListener { 2603 2604 // NOTE: This class exists only for backward compatability. All 2605 // its functionality has been moved into Handler. If you need to add 2606 // new functionality add it to the Handler, but make sure this 2607 // class calls into the Handler. 2608 2609 /** 2610 * Messaged when the selection changes in the tree we're displaying 2611 * for. Stops editing, messages super and displays the changed paths. 2612 */ 2613 public void valueChanged(TreeSelectionEvent event) { 2614 getHandler().valueChanged(event); 2615 } 2616 }// End of BasicTreeUI.TreeSelectionHandler 2617 2618 2619 /** 2620 * Listener responsible for getting cell editing events and updating 2621 * the tree accordingly. 2622 */ 2623 public class CellEditorHandler implements CellEditorListener { 2624 2625 // NOTE: This class exists only for backward compatability. All 2626 // its functionality has been moved into Handler. If you need to add 2627 // new functionality add it to the Handler, but make sure this 2628 // class calls into the Handler. 2629 2630 /** Messaged when editing has stopped in the tree. */ 2631 public void editingStopped(ChangeEvent e) { 2632 getHandler().editingStopped(e); 2633 } 2634 2635 /** Messaged when editing has been canceled in the tree. */ 2636 public void editingCanceled(ChangeEvent e) { 2637 getHandler().editingCanceled(e); 2638 } 2639 } // BasicTreeUI.CellEditorHandler 2640 2641 2642 /** 2643 * This is used to get mutliple key down events to appropriately generate 2644 * events. 2645 */ 2646 public class KeyHandler extends KeyAdapter { 2647 2648 // NOTE: This class exists only for backward compatability. All 2649 // its functionality has been moved into Handler. If you need to add 2650 // new functionality add it to the Handler, but make sure this 2651 // class calls into the Handler. 2652 2653 // Also note these fields aren't use anymore, nor does Handler have 2654 // the old functionality. This behavior worked around an old bug 2655 // in JComponent that has long since been fixed. 2656 2657 /** Key code that is being generated for. */ 2658 protected Action repeatKeyAction; 2659 2660 /** Set to true while keyPressed is active. */ 2661 protected boolean isKeyDown; 2662 2663 /** 2664 * Invoked when a key has been typed. 2665 * 2666 * Moves the keyboard focus to the first element 2667 * whose first letter matches the alphanumeric key 2668 * pressed by the user. Subsequent same key presses 2669 * move the keyboard focus to the next object that 2670 * starts with the same letter. 2671 */ 2672 public void keyTyped(KeyEvent e) { 2673 getHandler().keyTyped(e); 2674 } 2675 2676 public void keyPressed(KeyEvent e) { 2677 getHandler().keyPressed(e); 2678 } 2679 2680 public void keyReleased(KeyEvent e) { 2681 getHandler().keyReleased(e); 2682 } 2683 } // End of BasicTreeUI.KeyHandler 2684 2685 2686 /** 2687 * Repaints the lead selection row when focus is lost/gained. 2688 */ 2689 public class FocusHandler implements FocusListener { 2690 // NOTE: This class exists only for backward compatability. All 2691 // its functionality has been moved into Handler. If you need to add 2692 // new functionality add it to the Handler, but make sure this 2693 // class calls into the Handler. 2694 2695 /** 2696 * Invoked when focus is activated on the tree we're in, redraws the 2697 * lead row. 2698 */ 2699 public void focusGained(FocusEvent e) { 2700 getHandler().focusGained(e); 2701 } 2702 2703 /** 2704 * Invoked when focus is activated on the tree we're in, redraws the 2705 * lead row. 2706 */ 2707 public void focusLost(FocusEvent e) { 2708 getHandler().focusLost(e); 2709 } 2710 } // End of class BasicTreeUI.FocusHandler 2711 2712 2713 /** 2714 * Class responsible for getting size of node, method is forwarded 2715 * to BasicTreeUI method. X location does not include insets, that is 2716 * handled in getPathBounds. 2717 */ 2718 // This returns locations that don't include any Insets. 2719 public class NodeDimensionsHandler extends 2720 AbstractLayoutCache.NodeDimensions { 2721 /** 2722 * Responsible for getting the size of a particular node. 2723 */ 2724 public Rectangle getNodeDimensions(Object value, int row, 2725 int depth, boolean expanded, 2726 Rectangle size) { 2727 // Return size of editing component, if editing and asking 2728 // for editing row. 2729 if(editingComponent != null && editingRow == row) { 2730 Dimension prefSize = editingComponent. 2731 getPreferredSize(); 2732 int rh = getRowHeight(); 2733 2734 if(rh > 0 && rh != prefSize.height) 2735 prefSize.height = rh; 2736 if(size != null) { 2737 size.x = getRowX(row, depth); 2738 size.width = prefSize.width; 2739 size.height = prefSize.height; 2740 } 2741 else { 2742 size = new Rectangle(getRowX(row, depth), 0, 2743 prefSize.width, prefSize.height); 2744 } 2745 return size; 2746 } 2747 // Not editing, use renderer. 2748 if(currentCellRenderer != null) { 2749 Component aComponent; 2750 2751 aComponent = currentCellRenderer.getTreeCellRendererComponent 2752 (tree, value, tree.isRowSelected(row), 2753 expanded, treeModel.isLeaf(value), row, 2754 false); 2755 if(tree != null) { 2756 // Only ever removed when UI changes, this is OK! 2757 rendererPane.add(aComponent); 2758 aComponent.validate(); 2759 } 2760 Dimension prefSize = aComponent.getPreferredSize(); 2761 2762 if(size != null) { 2763 size.x = getRowX(row, depth); 2764 size.width = prefSize.width; 2765 size.height = prefSize.height; 2766 } 2767 else { 2768 size = new Rectangle(getRowX(row, depth), 0, 2769 prefSize.width, prefSize.height); 2770 } 2771 return size; 2772 } 2773 return null; 2774 } 2775 2776 /** 2777 * @return amount to indent the given row. 2778 */ 2779 protected int getRowX(int row, int depth) { 2780 return BasicTreeUI.this.getRowX(row, depth); 2781 } 2782 2783 } // End of class BasicTreeUI.NodeDimensionsHandler 2784 2785 2786 /** 2787 * TreeMouseListener is responsible for updating the selection 2788 * based on mouse events. 2789 */ 2790 public class MouseHandler extends MouseAdapter implements MouseMotionListener 2791 { 2792 // NOTE: This class exists only for backward compatability. All 2793 // its functionality has been moved into Handler. If you need to add 2794 // new functionality add it to the Handler, but make sure this 2795 // class calls into the Handler. 2796 2797 /** 2798 * Invoked when a mouse button has been pressed on a component. 2799 */ 2800 public void mousePressed(MouseEvent e) { 2801 getHandler().mousePressed(e); 2802 } 2803 2804 public void mouseDragged(MouseEvent e) { 2805 getHandler().mouseDragged(e); 2806 } 2807 2808 /** 2809 * Invoked when the mouse button has been moved on a component 2810 * (with no buttons no down). 2811 * @since 1.4 2812 */ 2813 public void mouseMoved(MouseEvent e) { 2814 getHandler().mouseMoved(e); 2815 } 2816 2817 public void mouseReleased(MouseEvent e) { 2818 getHandler().mouseReleased(e); 2819 } 2820 } // End of BasicTreeUI.MouseHandler 2821 2822 2823 /** 2824 * PropertyChangeListener for the tree. Updates the appropriate 2825 * varaible, or TreeState, based on what changes. 2826 */ 2827 public class PropertyChangeHandler implements 2828 PropertyChangeListener { 2829 2830 // NOTE: This class exists only for backward compatability. All 2831 // its functionality has been moved into Handler. If you need to add 2832 // new functionality add it to the Handler, but make sure this 2833 // class calls into the Handler. 2834 2835 public void propertyChange(PropertyChangeEvent event) { 2836 getHandler().propertyChange(event); 2837 } 2838 } // End of BasicTreeUI.PropertyChangeHandler 2839 2840 2841 /** 2842 * Listener on the TreeSelectionModel, resets the row selection if 2843 * any of the properties of the model change. 2844 */ 2845 public class SelectionModelPropertyChangeHandler implements 2846 PropertyChangeListener { 2847 2848 // NOTE: This class exists only for backward compatability. All 2849 // its functionality has been moved into Handler. If you need to add 2850 // new functionality add it to the Handler, but make sure this 2851 // class calls into the Handler. 2852 2853 public void propertyChange(PropertyChangeEvent event) { 2854 getHandler().propertyChange(event); 2855 } 2856 } // End of BasicTreeUI.SelectionModelPropertyChangeHandler 2857 2858 2859 /** 2860 * <code>TreeTraverseAction</code> is the action used for left/right keys. 2861 * Will toggle the expandedness of a node, as well as potentially 2862 * incrementing the selection. 2863 */ 2864 public class TreeTraverseAction extends AbstractAction { 2865 /** Determines direction to traverse, 1 means expand, -1 means 2866 * collapse. */ 2867 protected int direction; 2868 /** True if the selection is reset, false means only the lead path 2869 * changes. */ 2870 private boolean changeSelection; 2871 2872 public TreeTraverseAction(int direction, String name) { 2873 this(direction, name, true); 2874 } 2875 2876 private TreeTraverseAction(int direction, String name, 2877 boolean changeSelection) { 2878 this.direction = direction; 2879 this.changeSelection = changeSelection; 2880 } 2881 2882 public void actionPerformed(ActionEvent e) { 2883 if (tree != null) { 2884 SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction, 2885 changeSelection); 2886 } 2887 } 2888 2889 public boolean isEnabled() { return (tree != null && 2890 tree.isEnabled()); } 2891 } // BasicTreeUI.TreeTraverseAction 2892 2893 2894 /** TreePageAction handles page up and page down events. 2895 */ 2896 public class TreePageAction extends AbstractAction { 2897 /** Specifies the direction to adjust the selection by. */ 2898 protected int direction; 2899 /** True indicates should set selection from anchor path. */ 2900 private boolean addToSelection; 2901 private boolean changeSelection; 2902 2903 public TreePageAction(int direction, String name) { 2904 this(direction, name, false, true); 2905 } 2906 2907 private TreePageAction(int direction, String name, 2908 boolean addToSelection, 2909 boolean changeSelection) { 2910 this.direction = direction; 2911 this.addToSelection = addToSelection; 2912 this.changeSelection = changeSelection; 2913 } 2914 2915 public void actionPerformed(ActionEvent e) { 2916 if (tree != null) { 2917 SHARED_ACTION.page(tree, BasicTreeUI.this, direction, 2918 addToSelection, changeSelection); 2919 } 2920 } 2921 2922 public boolean isEnabled() { return (tree != null && 2923 tree.isEnabled()); } 2924 2925 } // BasicTreeUI.TreePageAction 2926 2927 2928 /** TreeIncrementAction is used to handle up/down actions. Selection 2929 * is moved up or down based on direction. 2930 */ 2931 public class TreeIncrementAction extends AbstractAction { 2932 /** Specifies the direction to adjust the selection by. */ 2933 protected int direction; 2934 /** If true the new item is added to the selection, if false the 2935 * selection is reset. */ 2936 private boolean addToSelection; 2937 private boolean changeSelection; 2938 2939 public TreeIncrementAction(int direction, String name) { 2940 this(direction, name, false, true); 2941 } 2942 2943 private TreeIncrementAction(int direction, String name, 2944 boolean addToSelection, 2945 boolean changeSelection) { 2946 this.direction = direction; 2947 this.addToSelection = addToSelection; 2948 this.changeSelection = changeSelection; 2949 } 2950 2951 public void actionPerformed(ActionEvent e) { 2952 if (tree != null) { 2953 SHARED_ACTION.increment(tree, BasicTreeUI.this, direction, 2954 addToSelection, changeSelection); 2955 } 2956 } 2957 2958 public boolean isEnabled() { return (tree != null && 2959 tree.isEnabled()); } 2960 2961 } // End of class BasicTreeUI.TreeIncrementAction 2962 2963 /** 2964 * TreeHomeAction is used to handle end/home actions. 2965 * Scrolls either the first or last cell to be visible based on 2966 * direction. 2967 */ 2968 public class TreeHomeAction extends AbstractAction { 2969 protected int direction; 2970 /** Set to true if append to selection. */ 2971 private boolean addToSelection; 2972 private boolean changeSelection; 2973 2974 public TreeHomeAction(int direction, String name) { 2975 this(direction, name, false, true); 2976 } 2977 2978 private TreeHomeAction(int direction, String name, 2979 boolean addToSelection, 2980 boolean changeSelection) { 2981 this.direction = direction; 2982 this.changeSelection = changeSelection; 2983 this.addToSelection = addToSelection; 2984 } 2985 2986 public void actionPerformed(ActionEvent e) { 2987 if (tree != null) { 2988 SHARED_ACTION.home(tree, BasicTreeUI.this, direction, 2989 addToSelection, changeSelection); 2990 } 2991 } 2992 2993 public boolean isEnabled() { return (tree != null && 2994 tree.isEnabled()); } 2995 2996 } // End of class BasicTreeUI.TreeHomeAction 2997 2998 2999 /** 3000 * For the first selected row expandedness will be toggled. 3001 */ 3002 public class TreeToggleAction extends AbstractAction { 3003 public TreeToggleAction(String name) { 3004 } 3005 3006 public void actionPerformed(ActionEvent e) { 3007 if(tree != null) { 3008 SHARED_ACTION.toggle(tree, BasicTreeUI.this); 3009 } 3010 } 3011 3012 public boolean isEnabled() { return (tree != null && 3013 tree.isEnabled()); } 3014 3015 } // End of class BasicTreeUI.TreeToggleAction 3016 3017 3018 /** 3019 * ActionListener that invokes cancelEditing when action performed. 3020 */ 3021 public class TreeCancelEditingAction extends AbstractAction { 3022 public TreeCancelEditingAction(String name) { 3023 } 3024 3025 public void actionPerformed(ActionEvent e) { 3026 if(tree != null) { 3027 SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this); 3028 } 3029 } 3030 3031 public boolean isEnabled() { return (tree != null && 3032 tree.isEnabled() && 3033 isEditing(tree)); } 3034 } // End of class BasicTreeUI.TreeCancelEditingAction 3035 3036 3037 /** 3038 * MouseInputHandler handles passing all mouse events, 3039 * including mouse motion events, until the mouse is released to 3040 * the destination it is constructed with. It is assumed all the 3041 * events are currently target at source. 3042 */ 3043 public class MouseInputHandler extends Object implements 3044 MouseInputListener 3045 { 3046 /** Source that events are coming from. */ 3047 protected Component source; 3048 /** Destination that receives all events. */ 3049 protected Component destination; 3050 private Component focusComponent; 3051 private boolean dispatchedEvent; 3052 3053 public MouseInputHandler(Component source, Component destination, 3054 MouseEvent event){ 3055 this(source, destination, event, null); 3056 } 3057 3058 MouseInputHandler(Component source, Component destination, 3059 MouseEvent event, Component focusComponent) { 3060 this.source = source; 3061 this.destination = destination; 3062 this.source.addMouseListener(this); 3063 this.source.addMouseMotionListener(this); 3064 3065 SwingUtilities2.setSkipClickCount(destination, 3066 event.getClickCount() - 1); 3067 3068 /* Dispatch the editing event! */ 3069 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3070 (source, event, destination)); 3071 this.focusComponent = focusComponent; 3072 } 3073 3074 public void mouseClicked(MouseEvent e) { 3075 if(destination != null) { 3076 dispatchedEvent = true; 3077 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3078 (source, e, destination)); 3079 } 3080 } 3081 3082 public void mousePressed(MouseEvent e) { 3083 } 3084 3085 public void mouseReleased(MouseEvent e) { 3086 if(destination != null) 3087 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3088 (source, e, destination)); 3089 removeFromSource(); 3090 } 3091 3092 public void mouseEntered(MouseEvent e) { 3093 if (!SwingUtilities.isLeftMouseButton(e)) { 3094 removeFromSource(); 3095 } 3096 } 3097 3098 public void mouseExited(MouseEvent e) { 3099 if (!SwingUtilities.isLeftMouseButton(e)) { 3100 removeFromSource(); 3101 } 3102 } 3103 3104 public void mouseDragged(MouseEvent e) { 3105 if(destination != null) { 3106 dispatchedEvent = true; 3107 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3108 (source, e, destination)); 3109 } 3110 } 3111 3112 public void mouseMoved(MouseEvent e) { 3113 removeFromSource(); 3114 } 3115 3116 protected void removeFromSource() { 3117 if(source != null) { 3118 source.removeMouseListener(this); 3119 source.removeMouseMotionListener(this); 3120 if (focusComponent != null && 3121 focusComponent == destination && !dispatchedEvent && 3122 (focusComponent instanceof JTextField)) { 3123 ((JTextField)focusComponent).selectAll(); 3124 } 3125 } 3126 source = destination = null; 3127 } 3128 3129 } // End of class BasicTreeUI.MouseInputHandler 3130 3131 private static final TransferHandler defaultTransferHandler = new TreeTransferHandler(); 3132 3133 static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator { 3134 3135 private JTree tree; 3136 3137 /** 3138 * Create a Transferable to use as the source for a data transfer. 3139 * 3140 * @param c The component holding the data to be transfered. This 3141 * argument is provided to enable sharing of TransferHandlers by 3142 * multiple components. 3143 * @return The representation of the data to be transfered. 3144 * 3145 */ 3146 protected Transferable createTransferable(JComponent c) { 3147 if (c instanceof JTree) { 3148 tree = (JTree) c; 3149 TreePath[] paths = tree.getSelectionPaths(); 3150 3151 if (paths == null || paths.length == 0) { 3152 return null; 3153 } 3154 3155 StringBuffer plainBuf = new StringBuffer(); 3156 StringBuffer htmlBuf = new StringBuffer(); 3157 3158 htmlBuf.append("<html>\n<body>\n<ul>\n"); 3159 3160 TreeModel model = tree.getModel(); 3161 TreePath lastPath = null; 3162 TreePath[] displayPaths = getDisplayOrderPaths(paths); 3163 3164 for (int i = 0; i < displayPaths.length; i++) { 3165 TreePath path = displayPaths[i]; 3166 3167 Object node = path.getLastPathComponent(); 3168 boolean leaf = model.isLeaf(node); 3169 String label = getDisplayString(path, true, leaf); 3170 3171 plainBuf.append(label + "\n"); 3172 htmlBuf.append(" <li>" + label + "\n"); 3173 } 3174 3175 // remove the last newline 3176 plainBuf.deleteCharAt(plainBuf.length() - 1); 3177 htmlBuf.append("</ul>\n</body>\n</html>"); 3178 3179 tree = null; 3180 3181 return new BasicTransferable(plainBuf.toString(), htmlBuf.toString()); 3182 } 3183 3184 return null; 3185 } 3186 3187 public int compare(Object o1, Object o2) { 3188 int row1 = tree.getRowForPath((TreePath)o1); 3189 int row2 = tree.getRowForPath((TreePath)o2); 3190 return row1 - row2; 3191 } 3192 3193 String getDisplayString(TreePath path, boolean selected, boolean leaf) { 3194 int row = tree.getRowForPath(path); 3195 boolean hasFocus = tree.getLeadSelectionRow() == row; 3196 Object node = path.getLastPathComponent(); 3197 return tree.convertValueToText(node, selected, tree.isExpanded(row), 3198 leaf, row, hasFocus); 3199 } 3200 3201 /** 3202 * Selection paths are in selection order. The conversion to 3203 * HTML requires display order. This method resorts the paths 3204 * to be in the display order. 3205 */ 3206 TreePath[] getDisplayOrderPaths(TreePath[] paths) { 3207 // sort the paths to display order rather than selection order 3208 ArrayList selOrder = new ArrayList(); 3209 for (int i = 0; i < paths.length; i++) { 3210 selOrder.add(paths[i]); 3211 } 3212 Collections.sort(selOrder, this); 3213 int n = selOrder.size(); 3214 TreePath[] displayPaths = new TreePath[n]; 3215 for (int i = 0; i < n; i++) { 3216 displayPaths[i] = (TreePath) selOrder.get(i); 3217 } 3218 return displayPaths; 3219 } 3220 3221 public int getSourceActions(JComponent c) { 3222 return COPY; 3223 } 3224 3225 } 3226 3227 3228 private class Handler implements CellEditorListener, FocusListener, 3229 KeyListener, MouseListener, MouseMotionListener, 3230 PropertyChangeListener, TreeExpansionListener, 3231 TreeModelListener, TreeSelectionListener, 3232 BeforeDrag { 3233 // 3234 // KeyListener 3235 // 3236 private String prefix = ""; 3237 private String typedString = ""; 3238 private long lastTime = 0L; 3239 3240 /** 3241 * Invoked when a key has been typed. 3242 * 3243 * Moves the keyboard focus to the first element whose prefix matches the 3244 * sequence of alphanumeric keys pressed by the user with delay less 3245 * than value of <code>timeFactor</code> property (or 1000 milliseconds 3246 * if it is not defined). Subsequent same key presses move the keyboard 3247 * focus to the next object that starts with the same letter until another 3248 * key is pressed, then it is treated as the prefix with appropriate number 3249 * of the same letters followed by first typed another letter. 3250 */ 3251 public void keyTyped(KeyEvent e) { 3252 // handle first letter navigation 3253 if(tree != null && tree.getRowCount()>0 && tree.hasFocus() && 3254 tree.isEnabled()) { 3255 if (e.isAltDown() || e.isControlDown() || e.isMetaDown() || 3256 isNavigationKey(e)) { 3257 return; 3258 } 3259 boolean startingFromSelection = true; 3260 3261 char c = e.getKeyChar(); 3262 3263 long time = e.getWhen(); 3264 int startingRow = tree.getLeadSelectionRow(); 3265 if (time - lastTime < timeFactor) { 3266 typedString += c; 3267 if((prefix.length() == 1) && (c == prefix.charAt(0))) { 3268 // Subsequent same key presses move the keyboard focus to the next 3269 // object that starts with the same letter. 3270 startingRow++; 3271 } else { 3272 prefix = typedString; 3273 } 3274 } else { 3275 startingRow++; 3276 typedString = "" + c; 3277 prefix = typedString; 3278 } 3279 lastTime = time; 3280 3281 if (startingRow < 0 || startingRow >= tree.getRowCount()) { 3282 startingFromSelection = false; 3283 startingRow = 0; 3284 } 3285 TreePath path = tree.getNextMatch(prefix, startingRow, 3286 Position.Bias.Forward); 3287 if (path != null) { 3288 tree.setSelectionPath(path); 3289 int row = getRowForPath(tree, path); 3290 ensureRowsAreVisible(row, row); 3291 } else if (startingFromSelection) { 3292 path = tree.getNextMatch(prefix, 0, 3293 Position.Bias.Forward); 3294 if (path != null) { 3295 tree.setSelectionPath(path); 3296 int row = getRowForPath(tree, path); 3297 ensureRowsAreVisible(row, row); 3298 } 3299 } 3300 } 3301 } 3302 3303 /** 3304 * Invoked when a key has been pressed. 3305 * 3306 * Checks to see if the key event is a navigation key to prevent 3307 * dispatching these keys for the first letter navigation. 3308 */ 3309 public void keyPressed(KeyEvent e) { 3310 if ( isNavigationKey(e) ) { 3311 prefix = ""; 3312 typedString = ""; 3313 lastTime = 0L; 3314 } 3315 } 3316 3317 public void keyReleased(KeyEvent e) { 3318 } 3319 3320 /** 3321 * Returns whether or not the supplied key event maps to a key that is used for 3322 * navigation. This is used for optimizing key input by only passing non- 3323 * navigation keys to the first letter navigation mechanism. 3324 */ 3325 private boolean isNavigationKey(KeyEvent event) { 3326 InputMap inputMap = tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 3327 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event); 3328 3329 if (inputMap != null && inputMap.get(key) != null) { 3330 return true; 3331 } 3332 return false; 3333 } 3334 3335 3336 // 3337 // PropertyChangeListener 3338 // 3339 public void propertyChange(PropertyChangeEvent event) { 3340 if (event.getSource() == treeSelectionModel) { 3341 treeSelectionModel.resetRowSelection(); 3342 } 3343 else if(event.getSource() == tree) { 3344 String changeName = event.getPropertyName(); 3345 3346 if (changeName == JTree.LEAD_SELECTION_PATH_PROPERTY) { 3347 if (!ignoreLAChange) { 3348 updateLeadRow(); 3349 repaintPath((TreePath)event.getOldValue()); 3350 repaintPath((TreePath)event.getNewValue()); 3351 } 3352 } 3353 else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) { 3354 if (!ignoreLAChange) { 3355 repaintPath((TreePath)event.getOldValue()); 3356 repaintPath((TreePath)event.getNewValue()); 3357 } 3358 } 3359 if(changeName == JTree.CELL_RENDERER_PROPERTY) { 3360 setCellRenderer((TreeCellRenderer)event.getNewValue()); 3361 redoTheLayout(); 3362 } 3363 else if(changeName == JTree.TREE_MODEL_PROPERTY) { 3364 setModel((TreeModel)event.getNewValue()); 3365 } 3366 else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) { 3367 setRootVisible(((Boolean)event.getNewValue()). 3368 booleanValue()); 3369 } 3370 else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) { 3371 setShowsRootHandles(((Boolean)event.getNewValue()). 3372 booleanValue()); 3373 } 3374 else if(changeName == JTree.ROW_HEIGHT_PROPERTY) { 3375 setRowHeight(((Integer)event.getNewValue()). 3376 intValue()); 3377 } 3378 else if(changeName == JTree.CELL_EDITOR_PROPERTY) { 3379 setCellEditor((TreeCellEditor)event.getNewValue()); 3380 } 3381 else if(changeName == JTree.EDITABLE_PROPERTY) { 3382 setEditable(((Boolean)event.getNewValue()).booleanValue()); 3383 } 3384 else if(changeName == JTree.LARGE_MODEL_PROPERTY) { 3385 setLargeModel(tree.isLargeModel()); 3386 } 3387 else if(changeName == JTree.SELECTION_MODEL_PROPERTY) { 3388 setSelectionModel(tree.getSelectionModel()); 3389 } 3390 else if(changeName == "font") { 3391 completeEditing(); 3392 if(treeState != null) 3393 treeState.invalidateSizes(); 3394 updateSize(); 3395 } 3396 else if (changeName == "componentOrientation") { 3397 if (tree != null) { 3398 leftToRight = BasicGraphicsUtils.isLeftToRight(tree); 3399 redoTheLayout(); 3400 tree.treeDidChange(); 3401 3402 InputMap km = getInputMap(JComponent.WHEN_FOCUSED); 3403 SwingUtilities.replaceUIInputMap(tree, 3404 JComponent.WHEN_FOCUSED, km); 3405 } 3406 } else if ("dropLocation" == changeName) { 3407 JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue(); 3408 repaintDropLocation(oldValue); 3409 repaintDropLocation(tree.getDropLocation()); 3410 } 3411 } 3412 } 3413 3414 private void repaintDropLocation(JTree.DropLocation loc) { 3415 if (loc == null) { 3416 return; 3417 } 3418 3419 Rectangle r; 3420 3421 if (isDropLine(loc)) { 3422 r = getDropLineRect(loc); 3423 } else { 3424 r = tree.getPathBounds(loc.getPath()); 3425 } 3426 3427 if (r != null) { 3428 tree.repaint(r); 3429 } 3430 } 3431 3432 // 3433 // MouseListener 3434 // 3435 3436 // Whether or not the mouse press (which is being considered as part 3437 // of a drag sequence) also caused the selection change to be fully 3438 // processed. 3439 private boolean dragPressDidSelection; 3440 3441 // Set to true when a drag gesture has been fully recognized and DnD 3442 // begins. Use this to ignore further mouse events which could be 3443 // delivered if DnD is cancelled (via ESCAPE for example) 3444 private boolean dragStarted; 3445 3446 // The path over which the press occurred and the press event itself 3447 private TreePath pressedPath; 3448 private MouseEvent pressedEvent; 3449 3450 // Used to detect whether the press event causes a selection change. 3451 // If it does, we won't try to start editing on the release. 3452 private boolean valueChangedOnPress; 3453 3454 private boolean isActualPath(TreePath path, int x, int y) { 3455 if (path == null) { 3456 return false; 3457 } 3458 3459 Rectangle bounds = getPathBounds(tree, path); 3460 if (y > (bounds.y + bounds.height)) { 3461 return false; 3462 } 3463 3464 return (x >= bounds.x) && (x <= (bounds.x + bounds.width)); 3465 } 3466 3467 public void mouseClicked(MouseEvent e) { 3468 } 3469 3470 public void mouseEntered(MouseEvent e) { 3471 } 3472 3473 public void mouseExited(MouseEvent e) { 3474 } 3475 3476 /** 3477 * Invoked when a mouse button has been pressed on a component. 3478 */ 3479 public void mousePressed(MouseEvent e) { 3480 if (SwingUtilities2.shouldIgnore(e, tree)) { 3481 return; 3482 } 3483 3484 // if we can't stop any ongoing editing, do nothing 3485 if (isEditing(tree) && tree.getInvokesStopCellEditing() 3486 && !stopEditing(tree)) { 3487 return; 3488 } 3489 3490 completeEditing(); 3491 3492 pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY()); 3493 3494 if (tree.getDragEnabled()) { 3495 mousePressedDND(e); 3496 } else { 3497 SwingUtilities2.adjustFocus(tree); 3498 handleSelection(e); 3499 } 3500 } 3501 3502 private void mousePressedDND(MouseEvent e) { 3503 pressedEvent = e; 3504 boolean grabFocus = true; 3505 dragStarted = false; 3506 valueChangedOnPress = false; 3507 3508 // if we have a valid path and this is a drag initiating event 3509 if (isActualPath(pressedPath, e.getX(), e.getY()) && 3510 DragRecognitionSupport.mousePressed(e)) { 3511 3512 dragPressDidSelection = false; 3513 3514 if (e.isControlDown()) { 3515 // do nothing for control - will be handled on release 3516 // or when drag starts 3517 return; 3518 } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) { 3519 // clicking on something that's already selected 3520 // and need to make it the lead now 3521 setAnchorSelectionPath(pressedPath); 3522 setLeadSelectionPath(pressedPath, true); 3523 return; 3524 } 3525 3526 dragPressDidSelection = true; 3527 3528 // could be a drag initiating event - don't grab focus 3529 grabFocus = false; 3530 } 3531 3532 if (grabFocus) { 3533 SwingUtilities2.adjustFocus(tree); 3534 } 3535 3536 handleSelection(e); 3537 } 3538 3539 void handleSelection(MouseEvent e) { 3540 if(pressedPath != null) { 3541 Rectangle bounds = getPathBounds(tree, pressedPath); 3542 3543 if(e.getY() >= (bounds.y + bounds.height)) { 3544 return; 3545 } 3546 3547 // Preferably checkForClickInExpandControl could take 3548 // the Event to do this it self! 3549 if(SwingUtilities.isLeftMouseButton(e)) { 3550 checkForClickInExpandControl(pressedPath, e.getX(), e.getY()); 3551 } 3552 3553 int x = e.getX(); 3554 3555 // Perhaps they clicked the cell itself. If so, 3556 // select it. 3557 if (x >= bounds.x && x < (bounds.x + bounds.width)) { 3558 if (tree.getDragEnabled() || !startEditing(pressedPath, e)) { 3559 selectPathForEvent(pressedPath, e); 3560 } 3561 } 3562 } 3563 } 3564 3565 public void dragStarting(MouseEvent me) { 3566 dragStarted = true; 3567 3568 if (me.isControlDown()) { 3569 tree.addSelectionPath(pressedPath); 3570 setAnchorSelectionPath(pressedPath); 3571 setLeadSelectionPath(pressedPath, true); 3572 } 3573 3574 pressedEvent = null; 3575 pressedPath = null; 3576 } 3577 3578 public void mouseDragged(MouseEvent e) { 3579 if (SwingUtilities2.shouldIgnore(e, tree)) { 3580 return; 3581 } 3582 3583 if (tree.getDragEnabled()) { 3584 DragRecognitionSupport.mouseDragged(e, this); 3585 } 3586 } 3587 3588 /** 3589 * Invoked when the mouse button has been moved on a component 3590 * (with no buttons no down). 3591 */ 3592 public void mouseMoved(MouseEvent e) { 3593 } 3594 3595 public void mouseReleased(MouseEvent e) { 3596 if (SwingUtilities2.shouldIgnore(e, tree)) { 3597 return; 3598 } 3599 3600 if (tree.getDragEnabled()) { 3601 mouseReleasedDND(e); 3602 } 3603 3604 pressedEvent = null; 3605 pressedPath = null; 3606 } 3607 3608 private void mouseReleasedDND(MouseEvent e) { 3609 MouseEvent me = DragRecognitionSupport.mouseReleased(e); 3610 if (me != null) { 3611 SwingUtilities2.adjustFocus(tree); 3612 if (!dragPressDidSelection) { 3613 handleSelection(me); 3614 } 3615 } 3616 3617 if (!dragStarted) { 3618 3619 // Note: We don't give the tree a chance to start editing if the 3620 // mouse press caused a selection change. Otherwise the default 3621 // tree cell editor will start editing on EVERY press and 3622 // release. If it turns out that this affects some editors, we 3623 // can always parameterize this with a client property. ex: 3624 // 3625 // if (pressedPath != null && 3626 // (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") || 3627 // !valueChangedOnPress) && ... 3628 if (pressedPath != null && !valueChangedOnPress && 3629 isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) { 3630 3631 startEditingOnRelease(pressedPath, pressedEvent, e); 3632 } 3633 } 3634 } 3635 3636 // 3637 // FocusListener 3638 // 3639 public void focusGained(FocusEvent e) { 3640 if(tree != null) { 3641 Rectangle pBounds; 3642 3643 pBounds = getPathBounds(tree, tree.getLeadSelectionPath()); 3644 if(pBounds != null) 3645 tree.repaint(getRepaintPathBounds(pBounds)); 3646 pBounds = getPathBounds(tree, getLeadSelectionPath()); 3647 if(pBounds != null) 3648 tree.repaint(getRepaintPathBounds(pBounds)); 3649 } 3650 } 3651 3652 public void focusLost(FocusEvent e) { 3653 focusGained(e); 3654 } 3655 3656 // 3657 // CellEditorListener 3658 // 3659 public void editingStopped(ChangeEvent e) { 3660 completeEditing(false, false, true); 3661 } 3662 3663 /** Messaged when editing has been canceled in the tree. */ 3664 public void editingCanceled(ChangeEvent e) { 3665 completeEditing(false, false, false); 3666 } 3667 3668 3669 // 3670 // TreeSelectionListener 3671 // 3672 public void valueChanged(TreeSelectionEvent event) { 3673 valueChangedOnPress = true; 3674 3675 // Stop editing 3676 completeEditing(); 3677 // Make sure all the paths are visible, if necessary. 3678 // PENDING: This should be tweaked when isAdjusting is added 3679 if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) { 3680 TreePath[] paths = treeSelectionModel 3681 .getSelectionPaths(); 3682 3683 if(paths != null) { 3684 for(int counter = paths.length - 1; counter >= 0; 3685 counter--) { 3686 TreePath path = paths[counter].getParentPath(); 3687 boolean expand = true; 3688 3689 while (path != null) { 3690 // Indicates this path isn't valid anymore, 3691 // we shouldn't attempt to expand it then. 3692 if (treeModel.isLeaf(path.getLastPathComponent())){ 3693 expand = false; 3694 path = null; 3695 } 3696 else { 3697 path = path.getParentPath(); 3698 } 3699 } 3700 if (expand) { 3701 tree.makeVisible(paths[counter]); 3702 } 3703 } 3704 } 3705 } 3706 3707 TreePath oldLead = getLeadSelectionPath(); 3708 lastSelectedRow = tree.getMinSelectionRow(); 3709 TreePath lead = tree.getSelectionModel().getLeadSelectionPath(); 3710 setAnchorSelectionPath(lead); 3711 setLeadSelectionPath(lead); 3712 3713 TreePath[] changedPaths = event.getPaths(); 3714 Rectangle nodeBounds; 3715 Rectangle visRect = tree.getVisibleRect(); 3716 boolean paintPaths = true; 3717 int nWidth = tree.getWidth(); 3718 3719 if(changedPaths != null) { 3720 int counter, maxCounter = changedPaths.length; 3721 3722 if(maxCounter > 4) { 3723 tree.repaint(); 3724 paintPaths = false; 3725 } 3726 else { 3727 for (counter = 0; counter < maxCounter; counter++) { 3728 nodeBounds = getPathBounds(tree, 3729 changedPaths[counter]); 3730 if(nodeBounds != null && 3731 visRect.intersects(nodeBounds)) 3732 tree.repaint(0, nodeBounds.y, nWidth, 3733 nodeBounds.height); 3734 } 3735 } 3736 } 3737 if(paintPaths) { 3738 nodeBounds = getPathBounds(tree, oldLead); 3739 if(nodeBounds != null && visRect.intersects(nodeBounds)) 3740 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 3741 nodeBounds = getPathBounds(tree, lead); 3742 if(nodeBounds != null && visRect.intersects(nodeBounds)) 3743 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 3744 } 3745 } 3746 3747 3748 // 3749 // TreeExpansionListener 3750 // 3751 public void treeExpanded(TreeExpansionEvent event) { 3752 if(event != null && tree != null) { 3753 TreePath path = event.getPath(); 3754 3755 updateExpandedDescendants(path); 3756 } 3757 } 3758 3759 public void treeCollapsed(TreeExpansionEvent event) { 3760 if(event != null && tree != null) { 3761 TreePath path = event.getPath(); 3762 3763 completeEditing(); 3764 if(path != null && tree.isVisible(path)) { 3765 treeState.setExpandedState(path, false); 3766 updateLeadRow(); 3767 updateSize(); 3768 } 3769 } 3770 } 3771 3772 // 3773 // TreeModelListener 3774 // 3775 public void treeNodesChanged(TreeModelEvent e) { 3776 if(treeState != null && e != null) { 3777 TreePath parentPath = e.getTreePath(); 3778 int[] indices = e.getChildIndices(); 3779 if (indices == null || indices.length == 0) { 3780 // The root has changed 3781 treeState.treeNodesChanged(e); 3782 updateSize(); 3783 } 3784 else if (treeState.isExpanded(parentPath)) { 3785 // Changed nodes are visible 3786 // Find the minimum index, we only need paint from there 3787 // down. 3788 int minIndex = indices[0]; 3789 for (int i = indices.length - 1; i > 0; i--) { 3790 minIndex = Math.min(indices[i], minIndex); 3791 } 3792 Object minChild = treeModel.getChild( 3793 parentPath.getLastPathComponent(), minIndex); 3794 TreePath minPath = parentPath.pathByAddingChild(minChild); 3795 Rectangle minBounds = getPathBounds(tree, minPath); 3796 3797 // Forward to the treestate 3798 treeState.treeNodesChanged(e); 3799 3800 // Mark preferred size as bogus. 3801 updateSize0(); 3802 3803 // And repaint 3804 Rectangle newMinBounds = getPathBounds(tree, minPath); 3805 if (indices.length == 1 && 3806 newMinBounds.height == minBounds.height) { 3807 tree.repaint(0, minBounds.y, tree.getWidth(), 3808 minBounds.height); 3809 } 3810 else { 3811 tree.repaint(0, minBounds.y, tree.getWidth(), 3812 tree.getHeight() - minBounds.y); 3813 } 3814 } 3815 else { 3816 // Nodes that changed aren't visible. No need to paint 3817 treeState.treeNodesChanged(e); 3818 } 3819 } 3820 } 3821 3822 public void treeNodesInserted(TreeModelEvent e) { 3823 if(treeState != null && e != null) { 3824 treeState.treeNodesInserted(e); 3825 3826 updateLeadRow(); 3827 3828 TreePath path = e.getTreePath(); 3829 3830 if(treeState.isExpanded(path)) { 3831 updateSize(); 3832 } 3833 else { 3834 // PENDING(sky): Need a method in TreeModelEvent 3835 // that can return the count, getChildIndices allocs 3836 // a new array! 3837 int[] indices = e.getChildIndices(); 3838 int childCount = treeModel.getChildCount 3839 (path.getLastPathComponent()); 3840 3841 if(indices != null && (childCount - indices.length) == 0) 3842 updateSize(); 3843 } 3844 } 3845 } 3846 3847 public void treeNodesRemoved(TreeModelEvent e) { 3848 if(treeState != null && e != null) { 3849 treeState.treeNodesRemoved(e); 3850 3851 updateLeadRow(); 3852 3853 TreePath path = e.getTreePath(); 3854 3855 if(treeState.isExpanded(path) || 3856 treeModel.getChildCount(path.getLastPathComponent()) == 0) 3857 updateSize(); 3858 } 3859 } 3860 3861 public void treeStructureChanged(TreeModelEvent e) { 3862 if(treeState != null && e != null) { 3863 treeState.treeStructureChanged(e); 3864 3865 updateLeadRow(); 3866 3867 TreePath pPath = e.getTreePath(); 3868 3869 if (pPath != null) { 3870 pPath = pPath.getParentPath(); 3871 } 3872 if(pPath == null || treeState.isExpanded(pPath)) 3873 updateSize(); 3874 } 3875 } 3876 } 3877 3878 3879 3880 private static class Actions extends UIAction { 3881 private static final String SELECT_PREVIOUS = "selectPrevious"; 3882 private static final String SELECT_PREVIOUS_CHANGE_LEAD = 3883 "selectPreviousChangeLead"; 3884 private static final String SELECT_PREVIOUS_EXTEND_SELECTION = 3885 "selectPreviousExtendSelection"; 3886 private static final String SELECT_NEXT = "selectNext"; 3887 private static final String SELECT_NEXT_CHANGE_LEAD = 3888 "selectNextChangeLead"; 3889 private static final String SELECT_NEXT_EXTEND_SELECTION = 3890 "selectNextExtendSelection"; 3891 private static final String SELECT_CHILD = "selectChild"; 3892 private static final String SELECT_CHILD_CHANGE_LEAD = 3893 "selectChildChangeLead"; 3894 private static final String SELECT_PARENT = "selectParent"; 3895 private static final String SELECT_PARENT_CHANGE_LEAD = 3896 "selectParentChangeLead"; 3897 private static final String SCROLL_UP_CHANGE_SELECTION = 3898 "scrollUpChangeSelection"; 3899 private static final String SCROLL_UP_CHANGE_LEAD = 3900 "scrollUpChangeLead"; 3901 private static final String SCROLL_UP_EXTEND_SELECTION = 3902 "scrollUpExtendSelection"; 3903 private static final String SCROLL_DOWN_CHANGE_SELECTION = 3904 "scrollDownChangeSelection"; 3905 private static final String SCROLL_DOWN_EXTEND_SELECTION = 3906 "scrollDownExtendSelection"; 3907 private static final String SCROLL_DOWN_CHANGE_LEAD = 3908 "scrollDownChangeLead"; 3909 private static final String SELECT_FIRST = "selectFirst"; 3910 private static final String SELECT_FIRST_CHANGE_LEAD = 3911 "selectFirstChangeLead"; 3912 private static final String SELECT_FIRST_EXTEND_SELECTION = 3913 "selectFirstExtendSelection"; 3914 private static final String SELECT_LAST = "selectLast"; 3915 private static final String SELECT_LAST_CHANGE_LEAD = 3916 "selectLastChangeLead"; 3917 private static final String SELECT_LAST_EXTEND_SELECTION = 3918 "selectLastExtendSelection"; 3919 private static final String TOGGLE = "toggle"; 3920 private static final String CANCEL_EDITING = "cancel"; 3921 private static final String START_EDITING = "startEditing"; 3922 private static final String SELECT_ALL = "selectAll"; 3923 private static final String CLEAR_SELECTION = "clearSelection"; 3924 private static final String SCROLL_LEFT = "scrollLeft"; 3925 private static final String SCROLL_RIGHT = "scrollRight"; 3926 private static final String SCROLL_LEFT_EXTEND_SELECTION = 3927 "scrollLeftExtendSelection"; 3928 private static final String SCROLL_RIGHT_EXTEND_SELECTION = 3929 "scrollRightExtendSelection"; 3930 private static final String SCROLL_RIGHT_CHANGE_LEAD = 3931 "scrollRightChangeLead"; 3932 private static final String SCROLL_LEFT_CHANGE_LEAD = 3933 "scrollLeftChangeLead"; 3934 private static final String EXPAND = "expand"; 3935 private static final String COLLAPSE = "collapse"; 3936 private static final String MOVE_SELECTION_TO_PARENT = 3937 "moveSelectionToParent"; 3938 3939 // add the lead item to the selection without changing lead or anchor 3940 private static final String ADD_TO_SELECTION = "addToSelection"; 3941 3942 // toggle the selected state of the lead item and move the anchor to it 3943 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor"; 3944 3945 // extend the selection to the lead item 3946 private static final String EXTEND_TO = "extendTo"; 3947 3948 // move the anchor to the lead and ensure only that item is selected 3949 private static final String MOVE_SELECTION_TO = "moveSelectionTo"; 3950 3951 Actions() { 3952 super(null); 3953 } 3954 3955 Actions(String key) { 3956 super(key); 3957 } 3958 3959 public boolean isEnabled(Object o) { 3960 if (o instanceof JTree) { 3961 if (getName() == CANCEL_EDITING) { 3962 return ((JTree)o).isEditing(); 3963 } 3964 } 3965 return true; 3966 } 3967 3968 public void actionPerformed(ActionEvent e) { 3969 JTree tree = (JTree)e.getSource(); 3970 BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType( 3971 tree.getUI(), BasicTreeUI.class); 3972 if (ui == null) { 3973 return; 3974 } 3975 String key = getName(); 3976 if (key == SELECT_PREVIOUS) { 3977 increment(tree, ui, -1, false, true); 3978 } 3979 else if (key == SELECT_PREVIOUS_CHANGE_LEAD) { 3980 increment(tree, ui, -1, false, false); 3981 } 3982 else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) { 3983 increment(tree, ui, -1, true, true); 3984 } 3985 else if (key == SELECT_NEXT) { 3986 increment(tree, ui, 1, false, true); 3987 } 3988 else if (key == SELECT_NEXT_CHANGE_LEAD) { 3989 increment(tree, ui, 1, false, false); 3990 } 3991 else if (key == SELECT_NEXT_EXTEND_SELECTION) { 3992 increment(tree, ui, 1, true, true); 3993 } 3994 else if (key == SELECT_CHILD) { 3995 traverse(tree, ui, 1, true); 3996 } 3997 else if (key == SELECT_CHILD_CHANGE_LEAD) { 3998 traverse(tree, ui, 1, false); 3999 } 4000 else if (key == SELECT_PARENT) { 4001 traverse(tree, ui, -1, true); 4002 } 4003 else if (key == SELECT_PARENT_CHANGE_LEAD) { 4004 traverse(tree, ui, -1, false); 4005 } 4006 else if (key == SCROLL_UP_CHANGE_SELECTION) { 4007 page(tree, ui, -1, false, true); 4008 } 4009 else if (key == SCROLL_UP_CHANGE_LEAD) { 4010 page(tree, ui, -1, false, false); 4011 } 4012 else if (key == SCROLL_UP_EXTEND_SELECTION) { 4013 page(tree, ui, -1, true, true); 4014 } 4015 else if (key == SCROLL_DOWN_CHANGE_SELECTION) { 4016 page(tree, ui, 1, false, true); 4017 } 4018 else if (key == SCROLL_DOWN_EXTEND_SELECTION) { 4019 page(tree, ui, 1, true, true); 4020 } 4021 else if (key == SCROLL_DOWN_CHANGE_LEAD) { 4022 page(tree, ui, 1, false, false); 4023 } 4024 else if (key == SELECT_FIRST) { 4025 home(tree, ui, -1, false, true); 4026 } 4027 else if (key == SELECT_FIRST_CHANGE_LEAD) { 4028 home(tree, ui, -1, false, false); 4029 } 4030 else if (key == SELECT_FIRST_EXTEND_SELECTION) { 4031 home(tree, ui, -1, true, true); 4032 } 4033 else if (key == SELECT_LAST) { 4034 home(tree, ui, 1, false, true); 4035 } 4036 else if (key == SELECT_LAST_CHANGE_LEAD) { 4037 home(tree, ui, 1, false, false); 4038 } 4039 else if (key == SELECT_LAST_EXTEND_SELECTION) { 4040 home(tree, ui, 1, true, true); 4041 } 4042 else if (key == TOGGLE) { 4043 toggle(tree, ui); 4044 } 4045 else if (key == CANCEL_EDITING) { 4046 cancelEditing(tree, ui); 4047 } 4048 else if (key == START_EDITING) { 4049 startEditing(tree, ui); 4050 } 4051 else if (key == SELECT_ALL) { 4052 selectAll(tree, ui, true); 4053 } 4054 else if (key == CLEAR_SELECTION) { 4055 selectAll(tree, ui, false); 4056 } 4057 else if (key == ADD_TO_SELECTION) { 4058 if (ui.getRowCount(tree) > 0) { 4059 int lead = ui.getLeadSelectionRow(); 4060 if (!tree.isRowSelected(lead)) { 4061 TreePath aPath = ui.getAnchorSelectionPath(); 4062 tree.addSelectionRow(lead); 4063 ui.setAnchorSelectionPath(aPath); 4064 } 4065 } 4066 } 4067 else if (key == TOGGLE_AND_ANCHOR) { 4068 if (ui.getRowCount(tree) > 0) { 4069 int lead = ui.getLeadSelectionRow(); 4070 TreePath lPath = ui.getLeadSelectionPath(); 4071 if (!tree.isRowSelected(lead)) { 4072 tree.addSelectionRow(lead); 4073 } else { 4074 tree.removeSelectionRow(lead); 4075 ui.setLeadSelectionPath(lPath); 4076 } 4077 ui.setAnchorSelectionPath(lPath); 4078 } 4079 } 4080 else if (key == EXTEND_TO) { 4081 extendSelection(tree, ui); 4082 } 4083 else if (key == MOVE_SELECTION_TO) { 4084 if (ui.getRowCount(tree) > 0) { 4085 int lead = ui.getLeadSelectionRow(); 4086 tree.setSelectionInterval(lead, lead); 4087 } 4088 } 4089 else if (key == SCROLL_LEFT) { 4090 scroll(tree, ui, SwingConstants.HORIZONTAL, -10); 4091 } 4092 else if (key == SCROLL_RIGHT) { 4093 scroll(tree, ui, SwingConstants.HORIZONTAL, 10); 4094 } 4095 else if (key == SCROLL_LEFT_EXTEND_SELECTION) { 4096 scrollChangeSelection(tree, ui, -1, true, true); 4097 } 4098 else if (key == SCROLL_RIGHT_EXTEND_SELECTION) { 4099 scrollChangeSelection(tree, ui, 1, true, true); 4100 } 4101 else if (key == SCROLL_RIGHT_CHANGE_LEAD) { 4102 scrollChangeSelection(tree, ui, 1, false, false); 4103 } 4104 else if (key == SCROLL_LEFT_CHANGE_LEAD) { 4105 scrollChangeSelection(tree, ui, -1, false, false); 4106 } 4107 else if (key == EXPAND) { 4108 expand(tree, ui); 4109 } 4110 else if (key == COLLAPSE) { 4111 collapse(tree, ui); 4112 } 4113 else if (key == MOVE_SELECTION_TO_PARENT) { 4114 moveSelectionToParent(tree, ui); 4115 } 4116 } 4117 4118 private void scrollChangeSelection(JTree tree, BasicTreeUI ui, 4119 int direction, boolean addToSelection, 4120 boolean changeSelection) { 4121 int rowCount; 4122 4123 if((rowCount = ui.getRowCount(tree)) > 0 && 4124 ui.treeSelectionModel != null) { 4125 TreePath newPath; 4126 Rectangle visRect = tree.getVisibleRect(); 4127 4128 if (direction == -1) { 4129 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4130 visRect.y); 4131 visRect.x = Math.max(0, visRect.x - visRect.width); 4132 } 4133 else { 4134 visRect.x = Math.min(Math.max(0, tree.getWidth() - 4135 visRect.width), visRect.x + visRect.width); 4136 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4137 visRect.y + visRect.height); 4138 } 4139 // Scroll 4140 tree.scrollRectToVisible(visRect); 4141 // select 4142 if (addToSelection) { 4143 ui.extendSelection(newPath); 4144 } 4145 else if(changeSelection) { 4146 tree.setSelectionPath(newPath); 4147 } 4148 else { 4149 ui.setLeadSelectionPath(newPath, true); 4150 } 4151 } 4152 } 4153 4154 private void scroll(JTree component, BasicTreeUI ui, int direction, 4155 int amount) { 4156 Rectangle visRect = component.getVisibleRect(); 4157 Dimension size = component.getSize(); 4158 if (direction == SwingConstants.HORIZONTAL) { 4159 visRect.x += amount; 4160 visRect.x = Math.max(0, visRect.x); 4161 visRect.x = Math.min(Math.max(0, size.width - visRect.width), 4162 visRect.x); 4163 } 4164 else { 4165 visRect.y += amount; 4166 visRect.y = Math.max(0, visRect.y); 4167 visRect.y = Math.min(Math.max(0, size.width - visRect.height), 4168 visRect.y); 4169 } 4170 component.scrollRectToVisible(visRect); 4171 } 4172 4173 private void extendSelection(JTree tree, BasicTreeUI ui) { 4174 if (ui.getRowCount(tree) > 0) { 4175 int lead = ui.getLeadSelectionRow(); 4176 4177 if (lead != -1) { 4178 TreePath leadP = ui.getLeadSelectionPath(); 4179 TreePath aPath = ui.getAnchorSelectionPath(); 4180 int aRow = ui.getRowForPath(tree, aPath); 4181 4182 if(aRow == -1) 4183 aRow = 0; 4184 tree.setSelectionInterval(aRow, lead); 4185 ui.setLeadSelectionPath(leadP); 4186 ui.setAnchorSelectionPath(aPath); 4187 } 4188 } 4189 } 4190 4191 private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) { 4192 int rowCount = ui.getRowCount(tree); 4193 4194 if(rowCount > 0) { 4195 if(selectAll) { 4196 if (tree.getSelectionModel().getSelectionMode() == 4197 TreeSelectionModel.SINGLE_TREE_SELECTION) { 4198 4199 int lead = ui.getLeadSelectionRow(); 4200 if (lead != -1) { 4201 tree.setSelectionRow(lead); 4202 } else if (tree.getMinSelectionRow() == -1) { 4203 tree.setSelectionRow(0); 4204 ui.ensureRowsAreVisible(0, 0); 4205 } 4206 return; 4207 } 4208 4209 TreePath lastPath = ui.getLeadSelectionPath(); 4210 TreePath aPath = ui.getAnchorSelectionPath(); 4211 4212 if(lastPath != null && !tree.isVisible(lastPath)) { 4213 lastPath = null; 4214 } 4215 tree.setSelectionInterval(0, rowCount - 1); 4216 if(lastPath != null) { 4217 ui.setLeadSelectionPath(lastPath); 4218 } 4219 if(aPath != null && tree.isVisible(aPath)) { 4220 ui.setAnchorSelectionPath(aPath); 4221 } 4222 } 4223 else { 4224 TreePath lastPath = ui.getLeadSelectionPath(); 4225 TreePath aPath = ui.getAnchorSelectionPath(); 4226 4227 tree.clearSelection(); 4228 ui.setAnchorSelectionPath(aPath); 4229 ui.setLeadSelectionPath(lastPath); 4230 } 4231 } 4232 } 4233 4234 private void startEditing(JTree tree, BasicTreeUI ui) { 4235 TreePath lead = ui.getLeadSelectionPath(); 4236 int editRow = (lead != null) ? 4237 ui.getRowForPath(tree, lead) : -1; 4238 4239 if(editRow != -1) { 4240 tree.startEditingAtPath(lead); 4241 } 4242 } 4243 4244 private void cancelEditing(JTree tree, BasicTreeUI ui) { 4245 tree.cancelEditing(); 4246 } 4247 4248 private void toggle(JTree tree, BasicTreeUI ui) { 4249 int selRow = ui.getLeadSelectionRow(); 4250 4251 if(selRow != -1 && !ui.isLeaf(selRow)) { 4252 TreePath aPath = ui.getAnchorSelectionPath(); 4253 TreePath lPath = ui.getLeadSelectionPath(); 4254 4255 ui.toggleExpandState(ui.getPathForRow(tree, selRow)); 4256 ui.setAnchorSelectionPath(aPath); 4257 ui.setLeadSelectionPath(lPath); 4258 } 4259 } 4260 4261 private void expand(JTree tree, BasicTreeUI ui) { 4262 int selRow = ui.getLeadSelectionRow(); 4263 tree.expandRow(selRow); 4264 } 4265 4266 private void collapse(JTree tree, BasicTreeUI ui) { 4267 int selRow = ui.getLeadSelectionRow(); 4268 tree.collapseRow(selRow); 4269 } 4270 4271 private void increment(JTree tree, BasicTreeUI ui, int direction, 4272 boolean addToSelection, 4273 boolean changeSelection) { 4274 4275 // disable moving of lead unless in discontiguous mode 4276 if (!addToSelection && !changeSelection && 4277 tree.getSelectionModel().getSelectionMode() != 4278 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4279 changeSelection = true; 4280 } 4281 4282 int rowCount; 4283 4284 if(ui.treeSelectionModel != null && 4285 (rowCount = tree.getRowCount()) > 0) { 4286 int selIndex = ui.getLeadSelectionRow(); 4287 int newIndex; 4288 4289 if(selIndex == -1) { 4290 if(direction == 1) 4291 newIndex = 0; 4292 else 4293 newIndex = rowCount - 1; 4294 } 4295 else 4296 /* Aparently people don't like wrapping;( */ 4297 newIndex = Math.min(rowCount - 1, Math.max 4298 (0, (selIndex + direction))); 4299 if(addToSelection && ui.treeSelectionModel. 4300 getSelectionMode() != TreeSelectionModel. 4301 SINGLE_TREE_SELECTION) { 4302 ui.extendSelection(tree.getPathForRow(newIndex)); 4303 } 4304 else if(changeSelection) { 4305 tree.setSelectionInterval(newIndex, newIndex); 4306 } 4307 else { 4308 ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true); 4309 } 4310 ui.ensureRowsAreVisible(newIndex, newIndex); 4311 ui.lastSelectedRow = newIndex; 4312 } 4313 } 4314 4315 private void traverse(JTree tree, BasicTreeUI ui, int direction, 4316 boolean changeSelection) { 4317 4318 // disable moving of lead unless in discontiguous mode 4319 if (!changeSelection && 4320 tree.getSelectionModel().getSelectionMode() != 4321 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4322 changeSelection = true; 4323 } 4324 4325 int rowCount; 4326 4327 if((rowCount = tree.getRowCount()) > 0) { 4328 int minSelIndex = ui.getLeadSelectionRow(); 4329 int newIndex; 4330 4331 if(minSelIndex == -1) 4332 newIndex = 0; 4333 else { 4334 /* Try and expand the node, otherwise go to next 4335 node. */ 4336 if(direction == 1) { 4337 TreePath minSelPath = ui.getPathForRow(tree, minSelIndex); 4338 int childCount = tree.getModel(). 4339 getChildCount(minSelPath.getLastPathComponent()); 4340 newIndex = -1; 4341 if (!ui.isLeaf(minSelIndex)) { 4342 if (!tree.isExpanded(minSelIndex)) { 4343 ui.toggleExpandState(minSelPath); 4344 } 4345 else if (childCount > 0) { 4346 newIndex = Math.min(minSelIndex + 1, rowCount - 1); 4347 } 4348 } 4349 } 4350 /* Try to collapse node. */ 4351 else { 4352 if(!ui.isLeaf(minSelIndex) && 4353 tree.isExpanded(minSelIndex)) { 4354 ui.toggleExpandState(ui.getPathForRow 4355 (tree, minSelIndex)); 4356 newIndex = -1; 4357 } 4358 else { 4359 TreePath path = ui.getPathForRow(tree, 4360 minSelIndex); 4361 4362 if(path != null && path.getPathCount() > 1) { 4363 newIndex = ui.getRowForPath(tree, path. 4364 getParentPath()); 4365 } 4366 else 4367 newIndex = -1; 4368 } 4369 } 4370 } 4371 if(newIndex != -1) { 4372 if(changeSelection) { 4373 tree.setSelectionInterval(newIndex, newIndex); 4374 } 4375 else { 4376 ui.setLeadSelectionPath(ui.getPathForRow( 4377 tree, newIndex), true); 4378 } 4379 ui.ensureRowsAreVisible(newIndex, newIndex); 4380 } 4381 } 4382 } 4383 4384 private void moveSelectionToParent(JTree tree, BasicTreeUI ui) { 4385 int selRow = ui.getLeadSelectionRow(); 4386 TreePath path = ui.getPathForRow(tree, selRow); 4387 if (path != null && path.getPathCount() > 1) { 4388 int newIndex = ui.getRowForPath(tree, path.getParentPath()); 4389 if (newIndex != -1) { 4390 tree.setSelectionInterval(newIndex, newIndex); 4391 ui.ensureRowsAreVisible(newIndex, newIndex); 4392 } 4393 } 4394 } 4395 4396 private void page(JTree tree, BasicTreeUI ui, int direction, 4397 boolean addToSelection, boolean changeSelection) { 4398 4399 // disable moving of lead unless in discontiguous mode 4400 if (!addToSelection && !changeSelection && 4401 tree.getSelectionModel().getSelectionMode() != 4402 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4403 changeSelection = true; 4404 } 4405 4406 int rowCount; 4407 4408 if((rowCount = ui.getRowCount(tree)) > 0 && 4409 ui.treeSelectionModel != null) { 4410 Dimension maxSize = tree.getSize(); 4411 TreePath lead = ui.getLeadSelectionPath(); 4412 TreePath newPath; 4413 Rectangle visRect = tree.getVisibleRect(); 4414 4415 if(direction == -1) { 4416 // up. 4417 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4418 visRect.y); 4419 if(newPath.equals(lead)) { 4420 visRect.y = Math.max(0, visRect.y - visRect.height); 4421 newPath = tree.getClosestPathForLocation(visRect.x, 4422 visRect.y); 4423 } 4424 } 4425 else { 4426 // down 4427 visRect.y = Math.min(maxSize.height, visRect.y + 4428 visRect.height - 1); 4429 newPath = tree.getClosestPathForLocation(visRect.x, 4430 visRect.y); 4431 if(newPath.equals(lead)) { 4432 visRect.y = Math.min(maxSize.height, visRect.y + 4433 visRect.height - 1); 4434 newPath = tree.getClosestPathForLocation(visRect.x, 4435 visRect.y); 4436 } 4437 } 4438 Rectangle newRect = ui.getPathBounds(tree, newPath); 4439 4440 newRect.x = visRect.x; 4441 newRect.width = visRect.width; 4442 if(direction == -1) { 4443 newRect.height = visRect.height; 4444 } 4445 else { 4446 newRect.y -= (visRect.height - newRect.height); 4447 newRect.height = visRect.height; 4448 } 4449 4450 if(addToSelection) { 4451 ui.extendSelection(newPath); 4452 } 4453 else if(changeSelection) { 4454 tree.setSelectionPath(newPath); 4455 } 4456 else { 4457 ui.setLeadSelectionPath(newPath, true); 4458 } 4459 tree.scrollRectToVisible(newRect); 4460 } 4461 } 4462 4463 private void home(JTree tree, BasicTreeUI ui, int direction, 4464 boolean addToSelection, boolean changeSelection) { 4465 4466 // disable moving of lead unless in discontiguous mode 4467 if (!addToSelection && !changeSelection && 4468 tree.getSelectionModel().getSelectionMode() != 4469 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4470 changeSelection = true; 4471 } 4472 4473 int rowCount = ui.getRowCount(tree); 4474 4475 if (rowCount > 0) { 4476 if(direction == -1) { 4477 ui.ensureRowsAreVisible(0, 0); 4478 if (addToSelection) { 4479 TreePath aPath = ui.getAnchorSelectionPath(); 4480 int aRow = (aPath == null) ? -1 : 4481 ui.getRowForPath(tree, aPath); 4482 4483 if (aRow == -1) { 4484 tree.setSelectionInterval(0, 0); 4485 } 4486 else { 4487 tree.setSelectionInterval(0, aRow); 4488 ui.setAnchorSelectionPath(aPath); 4489 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0)); 4490 } 4491 } 4492 else if(changeSelection) { 4493 tree.setSelectionInterval(0, 0); 4494 } 4495 else { 4496 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0), 4497 true); 4498 } 4499 } 4500 else { 4501 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); 4502 if (addToSelection) { 4503 TreePath aPath = ui.getAnchorSelectionPath(); 4504 int aRow = (aPath == null) ? -1 : 4505 ui.getRowForPath(tree, aPath); 4506 4507 if (aRow == -1) { 4508 tree.setSelectionInterval(rowCount - 1, 4509 rowCount -1); 4510 } 4511 else { 4512 tree.setSelectionInterval(aRow, rowCount - 1); 4513 ui.setAnchorSelectionPath(aPath); 4514 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4515 rowCount -1)); 4516 } 4517 } 4518 else if(changeSelection) { 4519 tree.setSelectionInterval(rowCount - 1, rowCount - 1); 4520 } 4521 else { 4522 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4523 rowCount - 1), true); 4524 } 4525 } 4526 } 4527 } 4528 } 4529 } // End of class BasicTreeUI