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