1 /* 2 * Copyright (c) 1997, 2008, 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 Rectangle visRect = tree.getVisibleRect(); 1936 Rectangle testRect = beginRect; 1937 int beginY = beginRect.y; 1938 int maxY = beginY + visRect.height; 1939 1940 for(int counter = beginRow + 1; counter <= endRow; counter++) { 1941 testRect = getPathBounds(tree, 1942 getPathForRow(tree, counter)); 1943 if((testRect.y + testRect.height) > maxY) 1944 counter = endRow; 1945 } 1946 tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1, 1947 testRect.y + testRect.height- 1948 beginY)); 1949 } 1950 } 1951 } 1952 1953 /** Sets the preferred minimum size. 1954 */ 1955 public void setPreferredMinSize(Dimension newSize) { 1956 preferredMinSize = newSize; 1957 } 1958 1959 /** Returns the minimum preferred size. 1960 */ 1961 public Dimension getPreferredMinSize() { 1962 if(preferredMinSize == null) 1963 return null; 1964 return new Dimension(preferredMinSize); 1965 } 1966 1967 /** Returns the preferred size to properly display the tree, 1968 * this is a cover method for getPreferredSize(c, true). 1969 */ 1970 public Dimension getPreferredSize(JComponent c) { 1971 return getPreferredSize(c, true); 1972 } 1973 1974 /** Returns the preferred size to represent the tree in 1975 * <I>c</I>. If <I>checkConsistency</I> is true 1976 * <b>checkConsistency</b> is messaged first. 1977 */ 1978 public Dimension getPreferredSize(JComponent c, 1979 boolean checkConsistency) { 1980 Dimension pSize = this.getPreferredMinSize(); 1981 1982 if(!validCachedPreferredSize) 1983 updateCachedPreferredSize(); 1984 if(tree != null) { 1985 if(pSize != null) 1986 return new Dimension(Math.max(pSize.width, 1987 preferredSize.width), 1988 Math.max(pSize.height, preferredSize.height)); 1989 return new Dimension(preferredSize.width, preferredSize.height); 1990 } 1991 else if(pSize != null) 1992 return pSize; 1993 else 1994 return new Dimension(0, 0); 1995 } 1996 1997 /** 1998 * Returns the minimum size for this component. Which will be 1999 * the min preferred size or 0, 0. 2000 */ 2001 public Dimension getMinimumSize(JComponent c) { 2002 if(this.getPreferredMinSize() != null) 2003 return this.getPreferredMinSize(); 2004 return new Dimension(0, 0); 2005 } 2006 2007 /** 2008 * Returns the maximum size for this component, which will be the 2009 * preferred size if the instance is currently in a JTree, or 0, 0. 2010 */ 2011 public Dimension getMaximumSize(JComponent c) { 2012 if(tree != null) 2013 return getPreferredSize(tree); 2014 if(this.getPreferredMinSize() != null) 2015 return this.getPreferredMinSize(); 2016 return new Dimension(0, 0); 2017 } 2018 2019 2020 /** 2021 * Messages to stop the editing session. If the UI the receiver 2022 * is providing the look and feel for returns true from 2023 * <code>getInvokesStopCellEditing</code>, stopCellEditing will 2024 * invoked on the current editor. Then completeEditing will 2025 * be messaged with false, true, false to cancel any lingering 2026 * editing. 2027 */ 2028 protected void completeEditing() { 2029 /* If should invoke stopCellEditing, try that */ 2030 if(tree.getInvokesStopCellEditing() && 2031 stopEditingInCompleteEditing && editingComponent != null) { 2032 cellEditor.stopCellEditing(); 2033 } 2034 /* Invoke cancelCellEditing, this will do nothing if stopCellEditing 2035 was successful. */ 2036 completeEditing(false, true, false); 2037 } 2038 2039 /** 2040 * Stops the editing session. If messageStop is true the editor 2041 * is messaged with stopEditing, if messageCancel is true the 2042 * editor is messaged with cancelEditing. If messageTree is true 2043 * the treeModel is messaged with valueForPathChanged. 2044 */ 2045 protected void completeEditing(boolean messageStop, 2046 boolean messageCancel, 2047 boolean messageTree) { 2048 if(stopEditingInCompleteEditing && editingComponent != null) { 2049 Component oldComponent = editingComponent; 2050 TreePath oldPath = editingPath; 2051 TreeCellEditor oldEditor = cellEditor; 2052 Object newValue = oldEditor.getCellEditorValue(); 2053 Rectangle editingBounds = getPathBounds(tree, 2054 editingPath); 2055 boolean requestFocus = (tree != null && 2056 (tree.hasFocus() || SwingUtilities. 2057 findFocusOwner(editingComponent) != null)); 2058 2059 editingComponent = null; 2060 editingPath = null; 2061 if(messageStop) 2062 oldEditor.stopCellEditing(); 2063 else if(messageCancel) 2064 oldEditor.cancelCellEditing(); 2065 tree.remove(oldComponent); 2066 if(editorHasDifferentSize) { 2067 treeState.invalidatePathBounds(oldPath); 2068 updateSize(); 2069 } 2070 else { 2071 editingBounds.x = 0; 2072 editingBounds.width = tree.getSize().width; 2073 tree.repaint(editingBounds); 2074 } 2075 if(requestFocus) 2076 tree.requestFocus(); 2077 if(messageTree) 2078 treeModel.valueForPathChanged(oldPath, newValue); 2079 } 2080 } 2081 2082 // cover method for startEditing that allows us to pass extra 2083 // information into that method via a class variable 2084 private boolean startEditingOnRelease(TreePath path, 2085 MouseEvent event, 2086 MouseEvent releaseEvent) { 2087 this.releaseEvent = releaseEvent; 2088 try { 2089 return startEditing(path, event); 2090 } finally { 2091 this.releaseEvent = null; 2092 } 2093 } 2094 2095 /** 2096 * Will start editing for node if there is a cellEditor and 2097 * shouldSelectCell returns true.<p> 2098 * This assumes that path is valid and visible. 2099 */ 2100 protected boolean startEditing(TreePath path, MouseEvent event) { 2101 if (isEditing(tree) && tree.getInvokesStopCellEditing() && 2102 !stopEditing(tree)) { 2103 return false; 2104 } 2105 completeEditing(); 2106 if(cellEditor != null && tree.isPathEditable(path)) { 2107 int row = getRowForPath(tree, path); 2108 2109 if(cellEditor.isCellEditable(event)) { 2110 editingComponent = cellEditor.getTreeCellEditorComponent 2111 (tree, path.getLastPathComponent(), 2112 tree.isPathSelected(path), tree.isExpanded(path), 2113 treeModel.isLeaf(path.getLastPathComponent()), row); 2114 Rectangle nodeBounds = getPathBounds(tree, path); 2115 2116 editingRow = row; 2117 2118 Dimension editorSize = editingComponent.getPreferredSize(); 2119 2120 // Only allow odd heights if explicitly set. 2121 if(editorSize.height != nodeBounds.height && 2122 getRowHeight() > 0) 2123 editorSize.height = getRowHeight(); 2124 2125 if(editorSize.width != nodeBounds.width || 2126 editorSize.height != nodeBounds.height) { 2127 // Editor wants different width or height, invalidate 2128 // treeState and relayout. 2129 editorHasDifferentSize = true; 2130 treeState.invalidatePathBounds(path); 2131 updateSize(); 2132 // To make sure x/y are updated correctly, fetch 2133 // the bounds again. 2134 nodeBounds = getPathBounds(tree, path); 2135 } 2136 else 2137 editorHasDifferentSize = false; 2138 tree.add(editingComponent); 2139 editingComponent.setBounds(nodeBounds.x, nodeBounds.y, 2140 nodeBounds.width, 2141 nodeBounds.height); 2142 editingPath = path; 2143 if (editingComponent instanceof JComponent) { 2144 ((JComponent)editingComponent).revalidate(); 2145 } else { 2146 editingComponent.validate(); 2147 } 2148 editingComponent.repaint(); 2149 if(cellEditor.shouldSelectCell(event)) { 2150 stopEditingInCompleteEditing = false; 2151 tree.setSelectionRow(row); 2152 stopEditingInCompleteEditing = true; 2153 } 2154 2155 Component focusedComponent = SwingUtilities2. 2156 compositeRequestFocus(editingComponent); 2157 boolean selectAll = true; 2158 2159 if(event != null) { 2160 /* Find the component that will get forwarded all the 2161 mouse events until mouseReleased. */ 2162 Point componentPoint = SwingUtilities.convertPoint 2163 (tree, new Point(event.getX(), event.getY()), 2164 editingComponent); 2165 2166 /* Create an instance of BasicTreeMouseListener to handle 2167 passing the mouse/motion events to the necessary 2168 component. */ 2169 // We really want similar behavior to getMouseEventTarget, 2170 // but it is package private. 2171 Component activeComponent = SwingUtilities. 2172 getDeepestComponentAt(editingComponent, 2173 componentPoint.x, componentPoint.y); 2174 if (activeComponent != null) { 2175 MouseInputHandler handler = 2176 new MouseInputHandler(tree, activeComponent, 2177 event, focusedComponent); 2178 2179 if (releaseEvent != null) { 2180 handler.mouseReleased(releaseEvent); 2181 } 2182 2183 selectAll = false; 2184 } 2185 } 2186 if (selectAll && focusedComponent instanceof JTextField) { 2187 ((JTextField)focusedComponent).selectAll(); 2188 } 2189 return true; 2190 } 2191 else 2192 editingComponent = null; 2193 } 2194 return false; 2195 } 2196 2197 // 2198 // Following are primarily for handling mouse events. 2199 // 2200 2201 /** 2202 * If the <code>mouseX</code> and <code>mouseY</code> are in the 2203 * expand/collapse region of the <code>row</code>, this will toggle 2204 * the row. 2205 */ 2206 protected void checkForClickInExpandControl(TreePath path, 2207 int mouseX, int mouseY) { 2208 if (isLocationInExpandControl(path, mouseX, mouseY)) { 2209 handleExpandControlClick(path, mouseX, mouseY); 2210 } 2211 } 2212 2213 /** 2214 * Returns true if <code>mouseX</code> and <code>mouseY</code> fall 2215 * in the area of row that is used to expand/collapse the node and 2216 * the node at <code>row</code> does not represent a leaf. 2217 */ 2218 protected boolean isLocationInExpandControl(TreePath path, 2219 int mouseX, int mouseY) { 2220 if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){ 2221 int boxWidth; 2222 Insets i = tree.getInsets(); 2223 2224 if(getExpandedIcon() != null) 2225 boxWidth = getExpandedIcon().getIconWidth(); 2226 else 2227 boxWidth = 8; 2228 2229 int boxLeftX = getRowX(tree.getRowForPath(path), 2230 path.getPathCount() - 1); 2231 2232 if (leftToRight) { 2233 boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1; 2234 } else { 2235 boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1; 2236 } 2237 2238 boxLeftX = findCenteredX(boxLeftX, boxWidth); 2239 2240 return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth)); 2241 } 2242 return false; 2243 } 2244 2245 /** 2246 * Messaged when the user clicks the particular row, this invokes 2247 * toggleExpandState. 2248 */ 2249 protected void handleExpandControlClick(TreePath path, int mouseX, 2250 int mouseY) { 2251 toggleExpandState(path); 2252 } 2253 2254 /** 2255 * Expands path if it is not expanded, or collapses row if it is expanded. 2256 * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible 2257 * is invoked to scroll as many of the children to visible as possible 2258 * (tries to scroll to last visible descendant of path). 2259 */ 2260 protected void toggleExpandState(TreePath path) { 2261 if(!tree.isExpanded(path)) { 2262 int row = getRowForPath(tree, path); 2263 2264 tree.expandPath(path); 2265 updateSize(); 2266 if(row != -1) { 2267 if(tree.getScrollsOnExpand()) 2268 ensureRowsAreVisible(row, row + treeState. 2269 getVisibleChildCount(path)); 2270 else 2271 ensureRowsAreVisible(row, row); 2272 } 2273 } 2274 else { 2275 tree.collapsePath(path); 2276 updateSize(); 2277 } 2278 } 2279 2280 /** 2281 * Returning true signifies a mouse event on the node should toggle 2282 * the selection of only the row under mouse. 2283 */ 2284 protected boolean isToggleSelectionEvent(MouseEvent event) { 2285 return (SwingUtilities.isLeftMouseButton(event) && 2286 BasicGraphicsUtils.isMenuShortcutKeyDown(event)); 2287 } 2288 2289 /** 2290 * Returning true signifies a mouse event on the node should select 2291 * from the anchor point. 2292 */ 2293 protected boolean isMultiSelectEvent(MouseEvent event) { 2294 return (SwingUtilities.isLeftMouseButton(event) && 2295 event.isShiftDown()); 2296 } 2297 2298 /** 2299 * Returning true indicates the row under the mouse should be toggled 2300 * based on the event. This is invoked after checkForClickInExpandControl, 2301 * implying the location is not in the expand (toggle) control 2302 */ 2303 protected boolean isToggleEvent(MouseEvent event) { 2304 if(!SwingUtilities.isLeftMouseButton(event)) { 2305 return false; 2306 } 2307 int clickCount = tree.getToggleClickCount(); 2308 2309 if(clickCount <= 0) { 2310 return false; 2311 } 2312 return ((event.getClickCount() % clickCount) == 0); 2313 } 2314 2315 /** 2316 * Messaged to update the selection based on a MouseEvent over a 2317 * particular row. If the event is a toggle selection event, the 2318 * row is either selected, or deselected. If the event identifies 2319 * a multi selection event, the selection is updated from the 2320 * anchor point. Otherwise the row is selected, and if the event 2321 * specified a toggle event the row is expanded/collapsed. 2322 */ 2323 protected void selectPathForEvent(TreePath path, MouseEvent event) { 2324 /* Adjust from the anchor point. */ 2325 if(isMultiSelectEvent(event)) { 2326 TreePath anchor = getAnchorSelectionPath(); 2327 int anchorRow = (anchor == null) ? -1 : 2328 getRowForPath(tree, anchor); 2329 2330 if(anchorRow == -1 || tree.getSelectionModel(). 2331 getSelectionMode() == TreeSelectionModel. 2332 SINGLE_TREE_SELECTION) { 2333 tree.setSelectionPath(path); 2334 } 2335 else { 2336 int row = getRowForPath(tree, path); 2337 TreePath lastAnchorPath = anchor; 2338 2339 if (isToggleSelectionEvent(event)) { 2340 if (tree.isRowSelected(anchorRow)) { 2341 tree.addSelectionInterval(anchorRow, row); 2342 } else { 2343 tree.removeSelectionInterval(anchorRow, row); 2344 tree.addSelectionInterval(row, row); 2345 } 2346 } else if(row < anchorRow) { 2347 tree.setSelectionInterval(row, anchorRow); 2348 } else { 2349 tree.setSelectionInterval(anchorRow, row); 2350 } 2351 lastSelectedRow = row; 2352 setAnchorSelectionPath(lastAnchorPath); 2353 setLeadSelectionPath(path); 2354 } 2355 } 2356 2357 // Should this event toggle the selection of this row? 2358 /* Control toggles just this node. */ 2359 else if(isToggleSelectionEvent(event)) { 2360 if(tree.isPathSelected(path)) 2361 tree.removeSelectionPath(path); 2362 else 2363 tree.addSelectionPath(path); 2364 lastSelectedRow = getRowForPath(tree, path); 2365 setAnchorSelectionPath(path); 2366 setLeadSelectionPath(path); 2367 } 2368 2369 /* Otherwise set the selection to just this interval. */ 2370 else if(SwingUtilities.isLeftMouseButton(event)) { 2371 tree.setSelectionPath(path); 2372 if(isToggleEvent(event)) { 2373 toggleExpandState(path); 2374 } 2375 } 2376 } 2377 2378 /** 2379 * @return true if the node at <code>row</code> is a leaf. 2380 */ 2381 protected boolean isLeaf(int row) { 2382 TreePath path = getPathForRow(tree, row); 2383 2384 if(path != null) 2385 return treeModel.isLeaf(path.getLastPathComponent()); 2386 // Have to return something here... 2387 return true; 2388 } 2389 2390 // 2391 // The following selection methods (lead/anchor) are covers for the 2392 // methods in JTree. 2393 // 2394 private void setAnchorSelectionPath(TreePath newPath) { 2395 ignoreLAChange = true; 2396 try { 2397 tree.setAnchorSelectionPath(newPath); 2398 } finally{ 2399 ignoreLAChange = false; 2400 } 2401 } 2402 2403 private TreePath getAnchorSelectionPath() { 2404 return tree.getAnchorSelectionPath(); 2405 } 2406 2407 private void setLeadSelectionPath(TreePath newPath) { 2408 setLeadSelectionPath(newPath, false); 2409 } 2410 2411 private void setLeadSelectionPath(TreePath newPath, boolean repaint) { 2412 Rectangle bounds = repaint ? 2413 getPathBounds(tree, getLeadSelectionPath()) : null; 2414 2415 ignoreLAChange = true; 2416 try { 2417 tree.setLeadSelectionPath(newPath); 2418 } finally { 2419 ignoreLAChange = false; 2420 } 2421 leadRow = getRowForPath(tree, newPath); 2422 2423 if (repaint) { 2424 if (bounds != null) { 2425 tree.repaint(getRepaintPathBounds(bounds)); 2426 } 2427 bounds = getPathBounds(tree, newPath); 2428 if (bounds != null) { 2429 tree.repaint(getRepaintPathBounds(bounds)); 2430 } 2431 } 2432 } 2433 2434 private Rectangle getRepaintPathBounds(Rectangle bounds) { 2435 if (UIManager.getBoolean("Tree.repaintWholeRow")) { 2436 bounds.x = 0; 2437 bounds.width = tree.getWidth(); 2438 } 2439 return bounds; 2440 } 2441 2442 private TreePath getLeadSelectionPath() { 2443 return tree.getLeadSelectionPath(); 2444 } 2445 2446 /** 2447 * Updates the lead row of the selection. 2448 * @since 1.7 2449 */ 2450 protected void updateLeadSelectionRow() { 2451 leadRow = getRowForPath(tree, getLeadSelectionPath()); 2452 } 2453 2454 /** 2455 * Returns the lead row of the selection. 2456 * 2457 * @return selection lead row 2458 * @since 1.7 2459 */ 2460 protected int getLeadSelectionRow() { 2461 return leadRow; 2462 } 2463 2464 /** 2465 * Extends the selection from the anchor to make <code>newLead</code> 2466 * the lead of the selection. This does not scroll. 2467 */ 2468 private void extendSelection(TreePath newLead) { 2469 TreePath aPath = getAnchorSelectionPath(); 2470 int aRow = (aPath == null) ? -1 : 2471 getRowForPath(tree, aPath); 2472 int newIndex = getRowForPath(tree, newLead); 2473 2474 if(aRow == -1) { 2475 tree.setSelectionRow(newIndex); 2476 } 2477 else { 2478 if(aRow < newIndex) { 2479 tree.setSelectionInterval(aRow, newIndex); 2480 } 2481 else { 2482 tree.setSelectionInterval(newIndex, aRow); 2483 } 2484 setAnchorSelectionPath(aPath); 2485 setLeadSelectionPath(newLead); 2486 } 2487 } 2488 2489 /** 2490 * Invokes <code>repaint</code> on the JTree for the passed in TreePath, 2491 * <code>path</code>. 2492 */ 2493 private void repaintPath(TreePath path) { 2494 if (path != null) { 2495 Rectangle bounds = getPathBounds(tree, path); 2496 if (bounds != null) { 2497 tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height); 2498 } 2499 } 2500 } 2501 2502 /** 2503 * Updates the TreeState in response to nodes expanding/collapsing. 2504 */ 2505 public class TreeExpansionHandler implements TreeExpansionListener { 2506 // NOTE: This class exists only for backward compatability. All 2507 // its functionality has been moved into Handler. If you need to add 2508 // new functionality add it to the Handler, but make sure this 2509 // class calls into the Handler. 2510 2511 /** 2512 * Called whenever an item in the tree has been expanded. 2513 */ 2514 public void treeExpanded(TreeExpansionEvent event) { 2515 getHandler().treeExpanded(event); 2516 } 2517 2518 /** 2519 * Called whenever an item in the tree has been collapsed. 2520 */ 2521 public void treeCollapsed(TreeExpansionEvent event) { 2522 getHandler().treeCollapsed(event); 2523 } 2524 } // BasicTreeUI.TreeExpansionHandler 2525 2526 2527 /** 2528 * Updates the preferred size when scrolling (if necessary). 2529 */ 2530 public class ComponentHandler extends ComponentAdapter implements 2531 ActionListener { 2532 /** Timer used when inside a scrollpane and the scrollbar is 2533 * adjusting. */ 2534 protected Timer timer; 2535 /** ScrollBar that is being adjusted. */ 2536 protected JScrollBar scrollBar; 2537 2538 public void componentMoved(ComponentEvent e) { 2539 if(timer == null) { 2540 JScrollPane scrollPane = getScrollPane(); 2541 2542 if(scrollPane == null) 2543 updateSize(); 2544 else { 2545 scrollBar = scrollPane.getVerticalScrollBar(); 2546 if(scrollBar == null || 2547 !scrollBar.getValueIsAdjusting()) { 2548 // Try the horizontal scrollbar. 2549 if((scrollBar = scrollPane.getHorizontalScrollBar()) 2550 != null && scrollBar.getValueIsAdjusting()) 2551 startTimer(); 2552 else 2553 updateSize(); 2554 } 2555 else 2556 startTimer(); 2557 } 2558 } 2559 } 2560 2561 /** 2562 * Creates, if necessary, and starts a Timer to check if need to 2563 * resize the bounds. 2564 */ 2565 protected void startTimer() { 2566 if(timer == null) { 2567 timer = new Timer(200, this); 2568 timer.setRepeats(true); 2569 } 2570 timer.start(); 2571 } 2572 2573 /** 2574 * Returns the JScrollPane housing the JTree, or null if one isn't 2575 * found. 2576 */ 2577 protected JScrollPane getScrollPane() { 2578 Component c = tree.getParent(); 2579 2580 while(c != null && !(c instanceof JScrollPane)) 2581 c = c.getParent(); 2582 if(c instanceof JScrollPane) 2583 return (JScrollPane)c; 2584 return null; 2585 } 2586 2587 /** 2588 * Public as a result of Timer. If the scrollBar is null, or 2589 * not adjusting, this stops the timer and updates the sizing. 2590 */ 2591 public void actionPerformed(ActionEvent ae) { 2592 if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { 2593 if(timer != null) 2594 timer.stop(); 2595 updateSize(); 2596 timer = null; 2597 scrollBar = null; 2598 } 2599 } 2600 } // End of BasicTreeUI.ComponentHandler 2601 2602 2603 /** 2604 * Forwards all TreeModel events to the TreeState. 2605 */ 2606 public class TreeModelHandler implements TreeModelListener { 2607 2608 // NOTE: This class exists only for backward compatability. All 2609 // its functionality has been moved into Handler. If you need to add 2610 // new functionality add it to the Handler, but make sure this 2611 // class calls into the Handler. 2612 2613 public void treeNodesChanged(TreeModelEvent e) { 2614 getHandler().treeNodesChanged(e); 2615 } 2616 2617 public void treeNodesInserted(TreeModelEvent e) { 2618 getHandler().treeNodesInserted(e); 2619 } 2620 2621 public void treeNodesRemoved(TreeModelEvent e) { 2622 getHandler().treeNodesRemoved(e); 2623 } 2624 2625 public void treeStructureChanged(TreeModelEvent e) { 2626 getHandler().treeStructureChanged(e); 2627 } 2628 } // End of BasicTreeUI.TreeModelHandler 2629 2630 2631 /** 2632 * Listens for changes in the selection model and updates the display 2633 * accordingly. 2634 */ 2635 public class TreeSelectionHandler implements TreeSelectionListener { 2636 2637 // NOTE: This class exists only for backward compatability. All 2638 // its functionality has been moved into Handler. If you need to add 2639 // new functionality add it to the Handler, but make sure this 2640 // class calls into the Handler. 2641 2642 /** 2643 * Messaged when the selection changes in the tree we're displaying 2644 * for. Stops editing, messages super and displays the changed paths. 2645 */ 2646 public void valueChanged(TreeSelectionEvent event) { 2647 getHandler().valueChanged(event); 2648 } 2649 }// End of BasicTreeUI.TreeSelectionHandler 2650 2651 2652 /** 2653 * Listener responsible for getting cell editing events and updating 2654 * the tree accordingly. 2655 */ 2656 public class CellEditorHandler implements CellEditorListener { 2657 2658 // NOTE: This class exists only for backward compatability. All 2659 // its functionality has been moved into Handler. If you need to add 2660 // new functionality add it to the Handler, but make sure this 2661 // class calls into the Handler. 2662 2663 /** Messaged when editing has stopped in the tree. */ 2664 public void editingStopped(ChangeEvent e) { 2665 getHandler().editingStopped(e); 2666 } 2667 2668 /** Messaged when editing has been canceled in the tree. */ 2669 public void editingCanceled(ChangeEvent e) { 2670 getHandler().editingCanceled(e); 2671 } 2672 } // BasicTreeUI.CellEditorHandler 2673 2674 2675 /** 2676 * This is used to get mutliple key down events to appropriately generate 2677 * events. 2678 */ 2679 public class KeyHandler extends KeyAdapter { 2680 2681 // NOTE: This class exists only for backward compatability. All 2682 // its functionality has been moved into Handler. If you need to add 2683 // new functionality add it to the Handler, but make sure this 2684 // class calls into the Handler. 2685 2686 // Also note these fields aren't use anymore, nor does Handler have 2687 // the old functionality. This behavior worked around an old bug 2688 // in JComponent that has long since been fixed. 2689 2690 /** Key code that is being generated for. */ 2691 protected Action repeatKeyAction; 2692 2693 /** Set to true while keyPressed is active. */ 2694 protected boolean isKeyDown; 2695 2696 /** 2697 * Invoked when a key has been typed. 2698 * 2699 * Moves the keyboard focus to the first element 2700 * whose first letter matches the alphanumeric key 2701 * pressed by the user. Subsequent same key presses 2702 * move the keyboard focus to the next object that 2703 * starts with the same letter. 2704 */ 2705 public void keyTyped(KeyEvent e) { 2706 getHandler().keyTyped(e); 2707 } 2708 2709 public void keyPressed(KeyEvent e) { 2710 getHandler().keyPressed(e); 2711 } 2712 2713 public void keyReleased(KeyEvent e) { 2714 getHandler().keyReleased(e); 2715 } 2716 } // End of BasicTreeUI.KeyHandler 2717 2718 2719 /** 2720 * Repaints the lead selection row when focus is lost/gained. 2721 */ 2722 public class FocusHandler implements FocusListener { 2723 // NOTE: This class exists only for backward compatability. All 2724 // its functionality has been moved into Handler. If you need to add 2725 // new functionality add it to the Handler, but make sure this 2726 // class calls into the Handler. 2727 2728 /** 2729 * Invoked when focus is activated on the tree we're in, redraws the 2730 * lead row. 2731 */ 2732 public void focusGained(FocusEvent e) { 2733 getHandler().focusGained(e); 2734 } 2735 2736 /** 2737 * Invoked when focus is activated on the tree we're in, redraws the 2738 * lead row. 2739 */ 2740 public void focusLost(FocusEvent e) { 2741 getHandler().focusLost(e); 2742 } 2743 } // End of class BasicTreeUI.FocusHandler 2744 2745 2746 /** 2747 * Class responsible for getting size of node, method is forwarded 2748 * to BasicTreeUI method. X location does not include insets, that is 2749 * handled in getPathBounds. 2750 */ 2751 // This returns locations that don't include any Insets. 2752 public class NodeDimensionsHandler extends 2753 AbstractLayoutCache.NodeDimensions { 2754 /** 2755 * Responsible for getting the size of a particular node. 2756 */ 2757 public Rectangle getNodeDimensions(Object value, int row, 2758 int depth, boolean expanded, 2759 Rectangle size) { 2760 // Return size of editing component, if editing and asking 2761 // for editing row. 2762 if(editingComponent != null && editingRow == row) { 2763 Dimension prefSize = editingComponent. 2764 getPreferredSize(); 2765 int rh = getRowHeight(); 2766 2767 if(rh > 0 && rh != prefSize.height) 2768 prefSize.height = rh; 2769 if(size != null) { 2770 size.x = getRowX(row, depth); 2771 size.width = prefSize.width; 2772 size.height = prefSize.height; 2773 } 2774 else { 2775 size = new Rectangle(getRowX(row, depth), 0, 2776 prefSize.width, prefSize.height); 2777 } 2778 return size; 2779 } 2780 // Not editing, use renderer. 2781 if(currentCellRenderer != null) { 2782 Component aComponent; 2783 2784 aComponent = currentCellRenderer.getTreeCellRendererComponent 2785 (tree, value, tree.isRowSelected(row), 2786 expanded, treeModel.isLeaf(value), row, 2787 false); 2788 if(tree != null) { 2789 // Only ever removed when UI changes, this is OK! 2790 rendererPane.add(aComponent); 2791 aComponent.validate(); 2792 } 2793 Dimension prefSize = aComponent.getPreferredSize(); 2794 2795 if(size != null) { 2796 size.x = getRowX(row, depth); 2797 size.width = prefSize.width; 2798 size.height = prefSize.height; 2799 } 2800 else { 2801 size = new Rectangle(getRowX(row, depth), 0, 2802 prefSize.width, prefSize.height); 2803 } 2804 return size; 2805 } 2806 return null; 2807 } 2808 2809 /** 2810 * @return amount to indent the given row. 2811 */ 2812 protected int getRowX(int row, int depth) { 2813 return BasicTreeUI.this.getRowX(row, depth); 2814 } 2815 2816 } // End of class BasicTreeUI.NodeDimensionsHandler 2817 2818 2819 /** 2820 * TreeMouseListener is responsible for updating the selection 2821 * based on mouse events. 2822 */ 2823 public class MouseHandler extends MouseAdapter implements MouseMotionListener 2824 { 2825 // NOTE: This class exists only for backward compatability. All 2826 // its functionality has been moved into Handler. If you need to add 2827 // new functionality add it to the Handler, but make sure this 2828 // class calls into the Handler. 2829 2830 /** 2831 * Invoked when a mouse button has been pressed on a component. 2832 */ 2833 public void mousePressed(MouseEvent e) { 2834 getHandler().mousePressed(e); 2835 } 2836 2837 public void mouseDragged(MouseEvent e) { 2838 getHandler().mouseDragged(e); 2839 } 2840 2841 /** 2842 * Invoked when the mouse button has been moved on a component 2843 * (with no buttons no down). 2844 * @since 1.4 2845 */ 2846 public void mouseMoved(MouseEvent e) { 2847 getHandler().mouseMoved(e); 2848 } 2849 2850 public void mouseReleased(MouseEvent e) { 2851 getHandler().mouseReleased(e); 2852 } 2853 } // End of BasicTreeUI.MouseHandler 2854 2855 2856 /** 2857 * PropertyChangeListener for the tree. Updates the appropriate 2858 * varaible, or TreeState, based on what changes. 2859 */ 2860 public class PropertyChangeHandler implements 2861 PropertyChangeListener { 2862 2863 // NOTE: This class exists only for backward compatability. All 2864 // its functionality has been moved into Handler. If you need to add 2865 // new functionality add it to the Handler, but make sure this 2866 // class calls into the Handler. 2867 2868 public void propertyChange(PropertyChangeEvent event) { 2869 getHandler().propertyChange(event); 2870 } 2871 } // End of BasicTreeUI.PropertyChangeHandler 2872 2873 2874 /** 2875 * Listener on the TreeSelectionModel, resets the row selection if 2876 * any of the properties of the model change. 2877 */ 2878 public class SelectionModelPropertyChangeHandler implements 2879 PropertyChangeListener { 2880 2881 // NOTE: This class exists only for backward compatability. All 2882 // its functionality has been moved into Handler. If you need to add 2883 // new functionality add it to the Handler, but make sure this 2884 // class calls into the Handler. 2885 2886 public void propertyChange(PropertyChangeEvent event) { 2887 getHandler().propertyChange(event); 2888 } 2889 } // End of BasicTreeUI.SelectionModelPropertyChangeHandler 2890 2891 2892 /** 2893 * <code>TreeTraverseAction</code> is the action used for left/right keys. 2894 * Will toggle the expandedness of a node, as well as potentially 2895 * incrementing the selection. 2896 */ 2897 public class TreeTraverseAction extends AbstractAction { 2898 /** Determines direction to traverse, 1 means expand, -1 means 2899 * collapse. */ 2900 protected int direction; 2901 /** True if the selection is reset, false means only the lead path 2902 * changes. */ 2903 private boolean changeSelection; 2904 2905 public TreeTraverseAction(int direction, String name) { 2906 this(direction, name, true); 2907 } 2908 2909 private TreeTraverseAction(int direction, String name, 2910 boolean changeSelection) { 2911 this.direction = direction; 2912 this.changeSelection = changeSelection; 2913 } 2914 2915 public void actionPerformed(ActionEvent e) { 2916 if (tree != null) { 2917 SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction, 2918 changeSelection); 2919 } 2920 } 2921 2922 public boolean isEnabled() { return (tree != null && 2923 tree.isEnabled()); } 2924 } // BasicTreeUI.TreeTraverseAction 2925 2926 2927 /** TreePageAction handles page up and page down events. 2928 */ 2929 public class TreePageAction extends AbstractAction { 2930 /** Specifies the direction to adjust the selection by. */ 2931 protected int direction; 2932 /** True indicates should set selection from anchor path. */ 2933 private boolean addToSelection; 2934 private boolean changeSelection; 2935 2936 public TreePageAction(int direction, String name) { 2937 this(direction, name, false, true); 2938 } 2939 2940 private TreePageAction(int direction, String name, 2941 boolean addToSelection, 2942 boolean changeSelection) { 2943 this.direction = direction; 2944 this.addToSelection = addToSelection; 2945 this.changeSelection = changeSelection; 2946 } 2947 2948 public void actionPerformed(ActionEvent e) { 2949 if (tree != null) { 2950 SHARED_ACTION.page(tree, BasicTreeUI.this, direction, 2951 addToSelection, changeSelection); 2952 } 2953 } 2954 2955 public boolean isEnabled() { return (tree != null && 2956 tree.isEnabled()); } 2957 2958 } // BasicTreeUI.TreePageAction 2959 2960 2961 /** TreeIncrementAction is used to handle up/down actions. Selection 2962 * is moved up or down based on direction. 2963 */ 2964 public class TreeIncrementAction extends AbstractAction { 2965 /** Specifies the direction to adjust the selection by. */ 2966 protected int direction; 2967 /** If true the new item is added to the selection, if false the 2968 * selection is reset. */ 2969 private boolean addToSelection; 2970 private boolean changeSelection; 2971 2972 public TreeIncrementAction(int direction, String name) { 2973 this(direction, name, false, true); 2974 } 2975 2976 private TreeIncrementAction(int direction, String name, 2977 boolean addToSelection, 2978 boolean changeSelection) { 2979 this.direction = direction; 2980 this.addToSelection = addToSelection; 2981 this.changeSelection = changeSelection; 2982 } 2983 2984 public void actionPerformed(ActionEvent e) { 2985 if (tree != null) { 2986 SHARED_ACTION.increment(tree, BasicTreeUI.this, direction, 2987 addToSelection, changeSelection); 2988 } 2989 } 2990 2991 public boolean isEnabled() { return (tree != null && 2992 tree.isEnabled()); } 2993 2994 } // End of class BasicTreeUI.TreeIncrementAction 2995 2996 /** 2997 * TreeHomeAction is used to handle end/home actions. 2998 * Scrolls either the first or last cell to be visible based on 2999 * direction. 3000 */ 3001 public class TreeHomeAction extends AbstractAction { 3002 protected int direction; 3003 /** Set to true if append to selection. */ 3004 private boolean addToSelection; 3005 private boolean changeSelection; 3006 3007 public TreeHomeAction(int direction, String name) { 3008 this(direction, name, false, true); 3009 } 3010 3011 private TreeHomeAction(int direction, String name, 3012 boolean addToSelection, 3013 boolean changeSelection) { 3014 this.direction = direction; 3015 this.changeSelection = changeSelection; 3016 this.addToSelection = addToSelection; 3017 } 3018 3019 public void actionPerformed(ActionEvent e) { 3020 if (tree != null) { 3021 SHARED_ACTION.home(tree, BasicTreeUI.this, direction, 3022 addToSelection, changeSelection); 3023 } 3024 } 3025 3026 public boolean isEnabled() { return (tree != null && 3027 tree.isEnabled()); } 3028 3029 } // End of class BasicTreeUI.TreeHomeAction 3030 3031 3032 /** 3033 * For the first selected row expandedness will be toggled. 3034 */ 3035 public class TreeToggleAction extends AbstractAction { 3036 public TreeToggleAction(String name) { 3037 } 3038 3039 public void actionPerformed(ActionEvent e) { 3040 if(tree != null) { 3041 SHARED_ACTION.toggle(tree, BasicTreeUI.this); 3042 } 3043 } 3044 3045 public boolean isEnabled() { return (tree != null && 3046 tree.isEnabled()); } 3047 3048 } // End of class BasicTreeUI.TreeToggleAction 3049 3050 3051 /** 3052 * ActionListener that invokes cancelEditing when action performed. 3053 */ 3054 public class TreeCancelEditingAction extends AbstractAction { 3055 public TreeCancelEditingAction(String name) { 3056 } 3057 3058 public void actionPerformed(ActionEvent e) { 3059 if(tree != null) { 3060 SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this); 3061 } 3062 } 3063 3064 public boolean isEnabled() { return (tree != null && 3065 tree.isEnabled() && 3066 isEditing(tree)); } 3067 } // End of class BasicTreeUI.TreeCancelEditingAction 3068 3069 3070 /** 3071 * MouseInputHandler handles passing all mouse events, 3072 * including mouse motion events, until the mouse is released to 3073 * the destination it is constructed with. It is assumed all the 3074 * events are currently target at source. 3075 */ 3076 public class MouseInputHandler extends Object implements 3077 MouseInputListener 3078 { 3079 /** Source that events are coming from. */ 3080 protected Component source; 3081 /** Destination that receives all events. */ 3082 protected Component destination; 3083 private Component focusComponent; 3084 private boolean dispatchedEvent; 3085 3086 public MouseInputHandler(Component source, Component destination, 3087 MouseEvent event){ 3088 this(source, destination, event, null); 3089 } 3090 3091 MouseInputHandler(Component source, Component destination, 3092 MouseEvent event, Component focusComponent) { 3093 this.source = source; 3094 this.destination = destination; 3095 this.source.addMouseListener(this); 3096 this.source.addMouseMotionListener(this); 3097 3098 SwingUtilities2.setSkipClickCount(destination, 3099 event.getClickCount() - 1); 3100 3101 /* Dispatch the editing event! */ 3102 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3103 (source, event, destination)); 3104 this.focusComponent = focusComponent; 3105 } 3106 3107 public void mouseClicked(MouseEvent e) { 3108 if(destination != null) { 3109 dispatchedEvent = true; 3110 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3111 (source, e, destination)); 3112 } 3113 } 3114 3115 public void mousePressed(MouseEvent e) { 3116 } 3117 3118 public void mouseReleased(MouseEvent e) { 3119 if(destination != null) 3120 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3121 (source, e, destination)); 3122 removeFromSource(); 3123 } 3124 3125 public void mouseEntered(MouseEvent e) { 3126 if (!SwingUtilities.isLeftMouseButton(e)) { 3127 removeFromSource(); 3128 } 3129 } 3130 3131 public void mouseExited(MouseEvent e) { 3132 if (!SwingUtilities.isLeftMouseButton(e)) { 3133 removeFromSource(); 3134 } 3135 } 3136 3137 public void mouseDragged(MouseEvent e) { 3138 if(destination != null) { 3139 dispatchedEvent = true; 3140 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3141 (source, e, destination)); 3142 } 3143 } 3144 3145 public void mouseMoved(MouseEvent e) { 3146 removeFromSource(); 3147 } 3148 3149 protected void removeFromSource() { 3150 if(source != null) { 3151 source.removeMouseListener(this); 3152 source.removeMouseMotionListener(this); 3153 if (focusComponent != null && 3154 focusComponent == destination && !dispatchedEvent && 3155 (focusComponent instanceof JTextField)) { 3156 ((JTextField)focusComponent).selectAll(); 3157 } 3158 } 3159 source = destination = null; 3160 } 3161 3162 } // End of class BasicTreeUI.MouseInputHandler 3163 3164 private static final TransferHandler defaultTransferHandler = new TreeTransferHandler(); 3165 3166 static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator<TreePath> { 3167 3168 private JTree tree; 3169 3170 /** 3171 * Create a Transferable to use as the source for a data transfer. 3172 * 3173 * @param c The component holding the data to be transfered. This 3174 * argument is provided to enable sharing of TransferHandlers by 3175 * multiple components. 3176 * @return The representation of the data to be transfered. 3177 * 3178 */ 3179 protected Transferable createTransferable(JComponent c) { 3180 if (c instanceof JTree) { 3181 tree = (JTree) c; 3182 TreePath[] paths = tree.getSelectionPaths(); 3183 3184 if (paths == null || paths.length == 0) { 3185 return null; 3186 } 3187 3188 StringBuffer plainBuf = new StringBuffer(); 3189 StringBuffer htmlBuf = new StringBuffer(); 3190 3191 htmlBuf.append("<html>\n<body>\n<ul>\n"); 3192 3193 TreeModel model = tree.getModel(); 3194 TreePath lastPath = null; 3195 TreePath[] displayPaths = getDisplayOrderPaths(paths); 3196 3197 for (TreePath path : displayPaths) { 3198 Object node = path.getLastPathComponent(); 3199 boolean leaf = model.isLeaf(node); 3200 String label = getDisplayString(path, true, leaf); 3201 3202 plainBuf.append(label + "\n"); 3203 htmlBuf.append(" <li>" + label + "\n"); 3204 } 3205 3206 // remove the last newline 3207 plainBuf.deleteCharAt(plainBuf.length() - 1); 3208 htmlBuf.append("</ul>\n</body>\n</html>"); 3209 3210 tree = null; 3211 3212 return new BasicTransferable(plainBuf.toString(), htmlBuf.toString()); 3213 } 3214 3215 return null; 3216 } 3217 3218 public int compare(TreePath o1, TreePath o2) { 3219 int row1 = tree.getRowForPath(o1); 3220 int row2 = tree.getRowForPath(o2); 3221 return row1 - row2; 3222 } 3223 3224 String getDisplayString(TreePath path, boolean selected, boolean leaf) { 3225 int row = tree.getRowForPath(path); 3226 boolean hasFocus = tree.getLeadSelectionRow() == row; 3227 Object node = path.getLastPathComponent(); 3228 return tree.convertValueToText(node, selected, tree.isExpanded(row), 3229 leaf, row, hasFocus); 3230 } 3231 3232 /** 3233 * Selection paths are in selection order. The conversion to 3234 * HTML requires display order. This method resorts the paths 3235 * to be in the display order. 3236 */ 3237 TreePath[] getDisplayOrderPaths(TreePath[] paths) { 3238 // sort the paths to display order rather than selection order 3239 ArrayList<TreePath> selOrder = new ArrayList<TreePath>(); 3240 for (TreePath path : paths) { 3241 selOrder.add(path); 3242 } 3243 Collections.sort(selOrder, this); 3244 int n = selOrder.size(); 3245 TreePath[] displayPaths = new TreePath[n]; 3246 for (int i = 0; i < n; i++) { 3247 displayPaths[i] = selOrder.get(i); 3248 } 3249 return displayPaths; 3250 } 3251 3252 public int getSourceActions(JComponent c) { 3253 return COPY; 3254 } 3255 3256 } 3257 3258 3259 private class Handler implements CellEditorListener, FocusListener, 3260 KeyListener, MouseListener, MouseMotionListener, 3261 PropertyChangeListener, TreeExpansionListener, 3262 TreeModelListener, TreeSelectionListener, 3263 BeforeDrag { 3264 // 3265 // KeyListener 3266 // 3267 private String prefix = ""; 3268 private String typedString = ""; 3269 private long lastTime = 0L; 3270 3271 /** 3272 * Invoked when a key has been typed. 3273 * 3274 * Moves the keyboard focus to the first element whose prefix matches the 3275 * sequence of alphanumeric keys pressed by the user with delay less 3276 * than value of <code>timeFactor</code> property (or 1000 milliseconds 3277 * if it is not defined). Subsequent same key presses move the keyboard 3278 * focus to the next object that starts with the same letter until another 3279 * key is pressed, then it is treated as the prefix with appropriate number 3280 * of the same letters followed by first typed another letter. 3281 */ 3282 public void keyTyped(KeyEvent e) { 3283 // handle first letter navigation 3284 if(tree != null && tree.getRowCount()>0 && tree.hasFocus() && 3285 tree.isEnabled()) { 3286 if (e.isAltDown() || BasicGraphicsUtils.isMenuShortcutKeyDown(e) || 3287 isNavigationKey(e)) { 3288 return; 3289 } 3290 boolean startingFromSelection = true; 3291 3292 char c = e.getKeyChar(); 3293 3294 long time = e.getWhen(); 3295 int startingRow = tree.getLeadSelectionRow(); 3296 if (time - lastTime < timeFactor) { 3297 typedString += c; 3298 if((prefix.length() == 1) && (c == prefix.charAt(0))) { 3299 // Subsequent same key presses move the keyboard focus to the next 3300 // object that starts with the same letter. 3301 startingRow++; 3302 } else { 3303 prefix = typedString; 3304 } 3305 } else { 3306 startingRow++; 3307 typedString = "" + c; 3308 prefix = typedString; 3309 } 3310 lastTime = time; 3311 3312 if (startingRow < 0 || startingRow >= tree.getRowCount()) { 3313 startingFromSelection = false; 3314 startingRow = 0; 3315 } 3316 TreePath path = tree.getNextMatch(prefix, startingRow, 3317 Position.Bias.Forward); 3318 if (path != null) { 3319 tree.setSelectionPath(path); 3320 int row = getRowForPath(tree, path); 3321 ensureRowsAreVisible(row, row); 3322 } else if (startingFromSelection) { 3323 path = tree.getNextMatch(prefix, 0, 3324 Position.Bias.Forward); 3325 if (path != null) { 3326 tree.setSelectionPath(path); 3327 int row = getRowForPath(tree, path); 3328 ensureRowsAreVisible(row, row); 3329 } 3330 } 3331 } 3332 } 3333 3334 /** 3335 * Invoked when a key has been pressed. 3336 * 3337 * Checks to see if the key event is a navigation key to prevent 3338 * dispatching these keys for the first letter navigation. 3339 */ 3340 public void keyPressed(KeyEvent e) { 3341 if (tree != null && isNavigationKey(e)) { 3342 prefix = ""; 3343 typedString = ""; 3344 lastTime = 0L; 3345 } 3346 } 3347 3348 public void keyReleased(KeyEvent e) { 3349 } 3350 3351 /** 3352 * Returns whether or not the supplied key event maps to a key that is used for 3353 * navigation. This is used for optimizing key input by only passing non- 3354 * navigation keys to the first letter navigation mechanism. 3355 */ 3356 private boolean isNavigationKey(KeyEvent event) { 3357 InputMap inputMap = tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 3358 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event); 3359 3360 return inputMap != null && inputMap.get(key) != null; 3361 } 3362 3363 3364 // 3365 // PropertyChangeListener 3366 // 3367 public void propertyChange(PropertyChangeEvent event) { 3368 if (event.getSource() == treeSelectionModel) { 3369 treeSelectionModel.resetRowSelection(); 3370 } 3371 else if(event.getSource() == tree) { 3372 String changeName = event.getPropertyName(); 3373 3374 if (changeName == JTree.LEAD_SELECTION_PATH_PROPERTY) { 3375 if (!ignoreLAChange) { 3376 updateLeadSelectionRow(); 3377 repaintPath((TreePath)event.getOldValue()); 3378 repaintPath((TreePath)event.getNewValue()); 3379 } 3380 } 3381 else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) { 3382 if (!ignoreLAChange) { 3383 repaintPath((TreePath)event.getOldValue()); 3384 repaintPath((TreePath)event.getNewValue()); 3385 } 3386 } 3387 if(changeName == JTree.CELL_RENDERER_PROPERTY) { 3388 setCellRenderer((TreeCellRenderer)event.getNewValue()); 3389 redoTheLayout(); 3390 } 3391 else if(changeName == JTree.TREE_MODEL_PROPERTY) { 3392 setModel((TreeModel)event.getNewValue()); 3393 } 3394 else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) { 3395 setRootVisible(((Boolean)event.getNewValue()). 3396 booleanValue()); 3397 } 3398 else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) { 3399 setShowsRootHandles(((Boolean)event.getNewValue()). 3400 booleanValue()); 3401 } 3402 else if(changeName == JTree.ROW_HEIGHT_PROPERTY) { 3403 setRowHeight(((Integer)event.getNewValue()). 3404 intValue()); 3405 } 3406 else if(changeName == JTree.CELL_EDITOR_PROPERTY) { 3407 setCellEditor((TreeCellEditor)event.getNewValue()); 3408 } 3409 else if(changeName == JTree.EDITABLE_PROPERTY) { 3410 setEditable(((Boolean)event.getNewValue()).booleanValue()); 3411 } 3412 else if(changeName == JTree.LARGE_MODEL_PROPERTY) { 3413 setLargeModel(tree.isLargeModel()); 3414 } 3415 else if(changeName == JTree.SELECTION_MODEL_PROPERTY) { 3416 setSelectionModel(tree.getSelectionModel()); 3417 } 3418 else if(changeName == "font") { 3419 completeEditing(); 3420 if(treeState != null) 3421 treeState.invalidateSizes(); 3422 updateSize(); 3423 } 3424 else if (changeName == "componentOrientation") { 3425 if (tree != null) { 3426 leftToRight = BasicGraphicsUtils.isLeftToRight(tree); 3427 redoTheLayout(); 3428 tree.treeDidChange(); 3429 3430 InputMap km = getInputMap(JComponent.WHEN_FOCUSED); 3431 SwingUtilities.replaceUIInputMap(tree, 3432 JComponent.WHEN_FOCUSED, km); 3433 } 3434 } else if ("dropLocation" == changeName) { 3435 JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue(); 3436 repaintDropLocation(oldValue); 3437 repaintDropLocation(tree.getDropLocation()); 3438 } 3439 } 3440 } 3441 3442 private void repaintDropLocation(JTree.DropLocation loc) { 3443 if (loc == null) { 3444 return; 3445 } 3446 3447 Rectangle r; 3448 3449 if (isDropLine(loc)) { 3450 r = getDropLineRect(loc); 3451 } else { 3452 r = tree.getPathBounds(loc.getPath()); 3453 } 3454 3455 if (r != null) { 3456 tree.repaint(r); 3457 } 3458 } 3459 3460 // 3461 // MouseListener 3462 // 3463 3464 // Whether or not the mouse press (which is being considered as part 3465 // of a drag sequence) also caused the selection change to be fully 3466 // processed. 3467 private boolean dragPressDidSelection; 3468 3469 // Set to true when a drag gesture has been fully recognized and DnD 3470 // begins. Use this to ignore further mouse events which could be 3471 // delivered if DnD is cancelled (via ESCAPE for example) 3472 private boolean dragStarted; 3473 3474 // The path over which the press occurred and the press event itself 3475 private TreePath pressedPath; 3476 private MouseEvent pressedEvent; 3477 3478 // Used to detect whether the press event causes a selection change. 3479 // If it does, we won't try to start editing on the release. 3480 private boolean valueChangedOnPress; 3481 3482 private boolean isActualPath(TreePath path, int x, int y) { 3483 if (path == null) { 3484 return false; 3485 } 3486 3487 Rectangle bounds = getPathBounds(tree, path); 3488 if (y > (bounds.y + bounds.height)) { 3489 return false; 3490 } 3491 3492 return (x >= bounds.x) && (x <= (bounds.x + bounds.width)); 3493 } 3494 3495 public void mouseClicked(MouseEvent e) { 3496 } 3497 3498 public void mouseEntered(MouseEvent e) { 3499 } 3500 3501 public void mouseExited(MouseEvent e) { 3502 } 3503 3504 /** 3505 * Invoked when a mouse button has been pressed on a component. 3506 */ 3507 public void mousePressed(MouseEvent e) { 3508 if (SwingUtilities2.shouldIgnore(e, tree)) { 3509 return; 3510 } 3511 3512 // if we can't stop any ongoing editing, do nothing 3513 if (isEditing(tree) && tree.getInvokesStopCellEditing() 3514 && !stopEditing(tree)) { 3515 return; 3516 } 3517 3518 completeEditing(); 3519 3520 pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY()); 3521 3522 if (tree.getDragEnabled()) { 3523 mousePressedDND(e); 3524 } else { 3525 SwingUtilities2.adjustFocus(tree); 3526 handleSelection(e); 3527 } 3528 } 3529 3530 private void mousePressedDND(MouseEvent e) { 3531 pressedEvent = e; 3532 boolean grabFocus = true; 3533 dragStarted = false; 3534 valueChangedOnPress = false; 3535 3536 // if we have a valid path and this is a drag initiating event 3537 if (isActualPath(pressedPath, e.getX(), e.getY()) && 3538 DragRecognitionSupport.mousePressed(e)) { 3539 3540 dragPressDidSelection = false; 3541 3542 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) { 3543 // do nothing for control - will be handled on release 3544 // or when drag starts 3545 return; 3546 } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) { 3547 // clicking on something that's already selected 3548 // and need to make it the lead now 3549 setAnchorSelectionPath(pressedPath); 3550 setLeadSelectionPath(pressedPath, true); 3551 return; 3552 } 3553 3554 dragPressDidSelection = true; 3555 3556 // could be a drag initiating event - don't grab focus 3557 grabFocus = false; 3558 } 3559 3560 if (grabFocus) { 3561 SwingUtilities2.adjustFocus(tree); 3562 } 3563 3564 handleSelection(e); 3565 } 3566 3567 void handleSelection(MouseEvent e) { 3568 if(pressedPath != null) { 3569 Rectangle bounds = getPathBounds(tree, pressedPath); 3570 3571 if(e.getY() >= (bounds.y + bounds.height)) { 3572 return; 3573 } 3574 3575 // Preferably checkForClickInExpandControl could take 3576 // the Event to do this it self! 3577 if(SwingUtilities.isLeftMouseButton(e)) { 3578 checkForClickInExpandControl(pressedPath, e.getX(), e.getY()); 3579 } 3580 3581 int x = e.getX(); 3582 3583 // Perhaps they clicked the cell itself. If so, 3584 // select it. 3585 if (x >= bounds.x && x < (bounds.x + bounds.width)) { 3586 if (tree.getDragEnabled() || !startEditing(pressedPath, e)) { 3587 selectPathForEvent(pressedPath, e); 3588 } 3589 } 3590 } 3591 } 3592 3593 public void dragStarting(MouseEvent me) { 3594 dragStarted = true; 3595 3596 if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) { 3597 tree.addSelectionPath(pressedPath); 3598 setAnchorSelectionPath(pressedPath); 3599 setLeadSelectionPath(pressedPath, true); 3600 } 3601 3602 pressedEvent = null; 3603 pressedPath = null; 3604 } 3605 3606 public void mouseDragged(MouseEvent e) { 3607 if (SwingUtilities2.shouldIgnore(e, tree)) { 3608 return; 3609 } 3610 3611 if (tree.getDragEnabled()) { 3612 DragRecognitionSupport.mouseDragged(e, this); 3613 } 3614 } 3615 3616 /** 3617 * Invoked when the mouse button has been moved on a component 3618 * (with no buttons no down). 3619 */ 3620 public void mouseMoved(MouseEvent e) { 3621 } 3622 3623 public void mouseReleased(MouseEvent e) { 3624 if (SwingUtilities2.shouldIgnore(e, tree)) { 3625 return; 3626 } 3627 3628 if (tree.getDragEnabled()) { 3629 mouseReleasedDND(e); 3630 } 3631 3632 pressedEvent = null; 3633 pressedPath = null; 3634 } 3635 3636 private void mouseReleasedDND(MouseEvent e) { 3637 MouseEvent me = DragRecognitionSupport.mouseReleased(e); 3638 if (me != null) { 3639 SwingUtilities2.adjustFocus(tree); 3640 if (!dragPressDidSelection) { 3641 handleSelection(me); 3642 } 3643 } 3644 3645 if (!dragStarted) { 3646 3647 // Note: We don't give the tree a chance to start editing if the 3648 // mouse press caused a selection change. Otherwise the default 3649 // tree cell editor will start editing on EVERY press and 3650 // release. If it turns out that this affects some editors, we 3651 // can always parameterize this with a client property. ex: 3652 // 3653 // if (pressedPath != null && 3654 // (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") || 3655 // !valueChangedOnPress) && ... 3656 if (pressedPath != null && !valueChangedOnPress && 3657 isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) { 3658 3659 startEditingOnRelease(pressedPath, pressedEvent, e); 3660 } 3661 } 3662 } 3663 3664 // 3665 // FocusListener 3666 // 3667 public void focusGained(FocusEvent e) { 3668 if(tree != null) { 3669 Rectangle pBounds; 3670 3671 pBounds = getPathBounds(tree, tree.getLeadSelectionPath()); 3672 if(pBounds != null) 3673 tree.repaint(getRepaintPathBounds(pBounds)); 3674 pBounds = getPathBounds(tree, getLeadSelectionPath()); 3675 if(pBounds != null) 3676 tree.repaint(getRepaintPathBounds(pBounds)); 3677 } 3678 } 3679 3680 public void focusLost(FocusEvent e) { 3681 focusGained(e); 3682 } 3683 3684 // 3685 // CellEditorListener 3686 // 3687 public void editingStopped(ChangeEvent e) { 3688 completeEditing(false, false, true); 3689 } 3690 3691 /** Messaged when editing has been canceled in the tree. */ 3692 public void editingCanceled(ChangeEvent e) { 3693 completeEditing(false, false, false); 3694 } 3695 3696 3697 // 3698 // TreeSelectionListener 3699 // 3700 public void valueChanged(TreeSelectionEvent event) { 3701 valueChangedOnPress = true; 3702 3703 // Stop editing 3704 completeEditing(); 3705 // Make sure all the paths are visible, if necessary. 3706 // PENDING: This should be tweaked when isAdjusting is added 3707 if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) { 3708 TreePath[] paths = treeSelectionModel 3709 .getSelectionPaths(); 3710 3711 if(paths != null) { 3712 for(int counter = paths.length - 1; counter >= 0; 3713 counter--) { 3714 TreePath path = paths[counter].getParentPath(); 3715 boolean expand = true; 3716 3717 while (path != null) { 3718 // Indicates this path isn't valid anymore, 3719 // we shouldn't attempt to expand it then. 3720 if (treeModel.isLeaf(path.getLastPathComponent())){ 3721 expand = false; 3722 path = null; 3723 } 3724 else { 3725 path = path.getParentPath(); 3726 } 3727 } 3728 if (expand) { 3729 tree.makeVisible(paths[counter]); 3730 } 3731 } 3732 } 3733 } 3734 3735 TreePath oldLead = getLeadSelectionPath(); 3736 lastSelectedRow = tree.getMinSelectionRow(); 3737 TreePath lead = tree.getSelectionModel().getLeadSelectionPath(); 3738 setAnchorSelectionPath(lead); 3739 setLeadSelectionPath(lead); 3740 3741 TreePath[] changedPaths = event.getPaths(); 3742 Rectangle nodeBounds; 3743 Rectangle visRect = tree.getVisibleRect(); 3744 boolean paintPaths = true; 3745 int nWidth = tree.getWidth(); 3746 3747 if(changedPaths != null) { 3748 int counter, maxCounter = changedPaths.length; 3749 3750 if(maxCounter > 4) { 3751 tree.repaint(); 3752 paintPaths = false; 3753 } 3754 else { 3755 for (counter = 0; counter < maxCounter; counter++) { 3756 nodeBounds = getPathBounds(tree, 3757 changedPaths[counter]); 3758 if(nodeBounds != null && 3759 visRect.intersects(nodeBounds)) 3760 tree.repaint(0, nodeBounds.y, nWidth, 3761 nodeBounds.height); 3762 } 3763 } 3764 } 3765 if(paintPaths) { 3766 nodeBounds = getPathBounds(tree, oldLead); 3767 if(nodeBounds != null && visRect.intersects(nodeBounds)) 3768 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 3769 nodeBounds = getPathBounds(tree, lead); 3770 if(nodeBounds != null && visRect.intersects(nodeBounds)) 3771 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 3772 } 3773 } 3774 3775 3776 // 3777 // TreeExpansionListener 3778 // 3779 public void treeExpanded(TreeExpansionEvent event) { 3780 if(event != null && tree != null) { 3781 TreePath path = event.getPath(); 3782 3783 updateExpandedDescendants(path); 3784 } 3785 } 3786 3787 public void treeCollapsed(TreeExpansionEvent event) { 3788 if(event != null && tree != null) { 3789 TreePath path = event.getPath(); 3790 3791 completeEditing(); 3792 if(path != null && tree.isVisible(path)) { 3793 treeState.setExpandedState(path, false); 3794 updateLeadSelectionRow(); 3795 updateSize(); 3796 } 3797 } 3798 } 3799 3800 // 3801 // TreeModelListener 3802 // 3803 public void treeNodesChanged(TreeModelEvent e) { 3804 if(treeState != null && e != null) { 3805 TreePath parentPath = e.getTreePath(); 3806 int[] indices = e.getChildIndices(); 3807 if (indices == null || indices.length == 0) { 3808 // The root has changed 3809 treeState.treeNodesChanged(e); 3810 updateSize(); 3811 } 3812 else if (treeState.isExpanded(parentPath)) { 3813 // Changed nodes are visible 3814 // Find the minimum index, we only need paint from there 3815 // down. 3816 int minIndex = indices[0]; 3817 for (int i = indices.length - 1; i > 0; i--) { 3818 minIndex = Math.min(indices[i], minIndex); 3819 } 3820 Object minChild = treeModel.getChild( 3821 parentPath.getLastPathComponent(), minIndex); 3822 TreePath minPath = parentPath.pathByAddingChild(minChild); 3823 Rectangle minBounds = getPathBounds(tree, minPath); 3824 3825 // Forward to the treestate 3826 treeState.treeNodesChanged(e); 3827 3828 // Mark preferred size as bogus. 3829 updateSize0(); 3830 3831 // And repaint 3832 Rectangle newMinBounds = getPathBounds(tree, minPath); 3833 if (indices.length == 1 && 3834 newMinBounds.height == minBounds.height) { 3835 tree.repaint(0, minBounds.y, tree.getWidth(), 3836 minBounds.height); 3837 } 3838 else { 3839 tree.repaint(0, minBounds.y, tree.getWidth(), 3840 tree.getHeight() - minBounds.y); 3841 } 3842 } 3843 else { 3844 // Nodes that changed aren't visible. No need to paint 3845 treeState.treeNodesChanged(e); 3846 } 3847 } 3848 } 3849 3850 public void treeNodesInserted(TreeModelEvent e) { 3851 if(treeState != null && e != null) { 3852 treeState.treeNodesInserted(e); 3853 3854 updateLeadSelectionRow(); 3855 3856 TreePath path = e.getTreePath(); 3857 3858 if(treeState.isExpanded(path)) { 3859 updateSize(); 3860 } 3861 else { 3862 // PENDING(sky): Need a method in TreeModelEvent 3863 // that can return the count, getChildIndices allocs 3864 // a new array! 3865 int[] indices = e.getChildIndices(); 3866 int childCount = treeModel.getChildCount 3867 (path.getLastPathComponent()); 3868 3869 if(indices != null && (childCount - indices.length) == 0) 3870 updateSize(); 3871 } 3872 } 3873 } 3874 3875 public void treeNodesRemoved(TreeModelEvent e) { 3876 if(treeState != null && e != null) { 3877 treeState.treeNodesRemoved(e); 3878 3879 updateLeadSelectionRow(); 3880 3881 TreePath path = e.getTreePath(); 3882 3883 if(treeState.isExpanded(path) || 3884 treeModel.getChildCount(path.getLastPathComponent()) == 0) 3885 updateSize(); 3886 } 3887 } 3888 3889 public void treeStructureChanged(TreeModelEvent e) { 3890 if(treeState != null && e != null) { 3891 treeState.treeStructureChanged(e); 3892 3893 updateLeadSelectionRow(); 3894 3895 TreePath pPath = e.getTreePath(); 3896 3897 if (pPath != null) { 3898 pPath = pPath.getParentPath(); 3899 } 3900 if(pPath == null || treeState.isExpanded(pPath)) 3901 updateSize(); 3902 } 3903 } 3904 } 3905 3906 3907 3908 private static class Actions extends UIAction { 3909 private static final String SELECT_PREVIOUS = "selectPrevious"; 3910 private static final String SELECT_PREVIOUS_CHANGE_LEAD = 3911 "selectPreviousChangeLead"; 3912 private static final String SELECT_PREVIOUS_EXTEND_SELECTION = 3913 "selectPreviousExtendSelection"; 3914 private static final String SELECT_NEXT = "selectNext"; 3915 private static final String SELECT_NEXT_CHANGE_LEAD = 3916 "selectNextChangeLead"; 3917 private static final String SELECT_NEXT_EXTEND_SELECTION = 3918 "selectNextExtendSelection"; 3919 private static final String SELECT_CHILD = "selectChild"; 3920 private static final String SELECT_CHILD_CHANGE_LEAD = 3921 "selectChildChangeLead"; 3922 private static final String SELECT_PARENT = "selectParent"; 3923 private static final String SELECT_PARENT_CHANGE_LEAD = 3924 "selectParentChangeLead"; 3925 private static final String SCROLL_UP_CHANGE_SELECTION = 3926 "scrollUpChangeSelection"; 3927 private static final String SCROLL_UP_CHANGE_LEAD = 3928 "scrollUpChangeLead"; 3929 private static final String SCROLL_UP_EXTEND_SELECTION = 3930 "scrollUpExtendSelection"; 3931 private static final String SCROLL_DOWN_CHANGE_SELECTION = 3932 "scrollDownChangeSelection"; 3933 private static final String SCROLL_DOWN_EXTEND_SELECTION = 3934 "scrollDownExtendSelection"; 3935 private static final String SCROLL_DOWN_CHANGE_LEAD = 3936 "scrollDownChangeLead"; 3937 private static final String SELECT_FIRST = "selectFirst"; 3938 private static final String SELECT_FIRST_CHANGE_LEAD = 3939 "selectFirstChangeLead"; 3940 private static final String SELECT_FIRST_EXTEND_SELECTION = 3941 "selectFirstExtendSelection"; 3942 private static final String SELECT_LAST = "selectLast"; 3943 private static final String SELECT_LAST_CHANGE_LEAD = 3944 "selectLastChangeLead"; 3945 private static final String SELECT_LAST_EXTEND_SELECTION = 3946 "selectLastExtendSelection"; 3947 private static final String TOGGLE = "toggle"; 3948 private static final String CANCEL_EDITING = "cancel"; 3949 private static final String START_EDITING = "startEditing"; 3950 private static final String SELECT_ALL = "selectAll"; 3951 private static final String CLEAR_SELECTION = "clearSelection"; 3952 private static final String SCROLL_LEFT = "scrollLeft"; 3953 private static final String SCROLL_RIGHT = "scrollRight"; 3954 private static final String SCROLL_LEFT_EXTEND_SELECTION = 3955 "scrollLeftExtendSelection"; 3956 private static final String SCROLL_RIGHT_EXTEND_SELECTION = 3957 "scrollRightExtendSelection"; 3958 private static final String SCROLL_RIGHT_CHANGE_LEAD = 3959 "scrollRightChangeLead"; 3960 private static final String SCROLL_LEFT_CHANGE_LEAD = 3961 "scrollLeftChangeLead"; 3962 private static final String EXPAND = "expand"; 3963 private static final String COLLAPSE = "collapse"; 3964 private static final String MOVE_SELECTION_TO_PARENT = 3965 "moveSelectionToParent"; 3966 3967 // add the lead item to the selection without changing lead or anchor 3968 private static final String ADD_TO_SELECTION = "addToSelection"; 3969 3970 // toggle the selected state of the lead item and move the anchor to it 3971 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor"; 3972 3973 // extend the selection to the lead item 3974 private static final String EXTEND_TO = "extendTo"; 3975 3976 // move the anchor to the lead and ensure only that item is selected 3977 private static final String MOVE_SELECTION_TO = "moveSelectionTo"; 3978 3979 Actions() { 3980 super(null); 3981 } 3982 3983 Actions(String key) { 3984 super(key); 3985 } 3986 3987 public boolean isEnabled(Object o) { 3988 if (o instanceof JTree) { 3989 if (getName() == CANCEL_EDITING) { 3990 return ((JTree)o).isEditing(); 3991 } 3992 } 3993 return true; 3994 } 3995 3996 public void actionPerformed(ActionEvent e) { 3997 JTree tree = (JTree)e.getSource(); 3998 BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType( 3999 tree.getUI(), BasicTreeUI.class); 4000 if (ui == null) { 4001 return; 4002 } 4003 String key = getName(); 4004 if (key == SELECT_PREVIOUS) { 4005 increment(tree, ui, -1, false, true); 4006 } 4007 else if (key == SELECT_PREVIOUS_CHANGE_LEAD) { 4008 increment(tree, ui, -1, false, false); 4009 } 4010 else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) { 4011 increment(tree, ui, -1, true, true); 4012 } 4013 else if (key == SELECT_NEXT) { 4014 increment(tree, ui, 1, false, true); 4015 } 4016 else if (key == SELECT_NEXT_CHANGE_LEAD) { 4017 increment(tree, ui, 1, false, false); 4018 } 4019 else if (key == SELECT_NEXT_EXTEND_SELECTION) { 4020 increment(tree, ui, 1, true, true); 4021 } 4022 else if (key == SELECT_CHILD) { 4023 traverse(tree, ui, 1, true); 4024 } 4025 else if (key == SELECT_CHILD_CHANGE_LEAD) { 4026 traverse(tree, ui, 1, false); 4027 } 4028 else if (key == SELECT_PARENT) { 4029 traverse(tree, ui, -1, true); 4030 } 4031 else if (key == SELECT_PARENT_CHANGE_LEAD) { 4032 traverse(tree, ui, -1, false); 4033 } 4034 else if (key == SCROLL_UP_CHANGE_SELECTION) { 4035 page(tree, ui, -1, false, true); 4036 } 4037 else if (key == SCROLL_UP_CHANGE_LEAD) { 4038 page(tree, ui, -1, false, false); 4039 } 4040 else if (key == SCROLL_UP_EXTEND_SELECTION) { 4041 page(tree, ui, -1, true, true); 4042 } 4043 else if (key == SCROLL_DOWN_CHANGE_SELECTION) { 4044 page(tree, ui, 1, false, true); 4045 } 4046 else if (key == SCROLL_DOWN_EXTEND_SELECTION) { 4047 page(tree, ui, 1, true, true); 4048 } 4049 else if (key == SCROLL_DOWN_CHANGE_LEAD) { 4050 page(tree, ui, 1, false, false); 4051 } 4052 else if (key == SELECT_FIRST) { 4053 home(tree, ui, -1, false, true); 4054 } 4055 else if (key == SELECT_FIRST_CHANGE_LEAD) { 4056 home(tree, ui, -1, false, false); 4057 } 4058 else if (key == SELECT_FIRST_EXTEND_SELECTION) { 4059 home(tree, ui, -1, true, true); 4060 } 4061 else if (key == SELECT_LAST) { 4062 home(tree, ui, 1, false, true); 4063 } 4064 else if (key == SELECT_LAST_CHANGE_LEAD) { 4065 home(tree, ui, 1, false, false); 4066 } 4067 else if (key == SELECT_LAST_EXTEND_SELECTION) { 4068 home(tree, ui, 1, true, true); 4069 } 4070 else if (key == TOGGLE) { 4071 toggle(tree, ui); 4072 } 4073 else if (key == CANCEL_EDITING) { 4074 cancelEditing(tree, ui); 4075 } 4076 else if (key == START_EDITING) { 4077 startEditing(tree, ui); 4078 } 4079 else if (key == SELECT_ALL) { 4080 selectAll(tree, ui, true); 4081 } 4082 else if (key == CLEAR_SELECTION) { 4083 selectAll(tree, ui, false); 4084 } 4085 else if (key == ADD_TO_SELECTION) { 4086 if (ui.getRowCount(tree) > 0) { 4087 int lead = ui.getLeadSelectionRow(); 4088 if (!tree.isRowSelected(lead)) { 4089 TreePath aPath = ui.getAnchorSelectionPath(); 4090 tree.addSelectionRow(lead); 4091 ui.setAnchorSelectionPath(aPath); 4092 } 4093 } 4094 } 4095 else if (key == TOGGLE_AND_ANCHOR) { 4096 if (ui.getRowCount(tree) > 0) { 4097 int lead = ui.getLeadSelectionRow(); 4098 TreePath lPath = ui.getLeadSelectionPath(); 4099 if (!tree.isRowSelected(lead)) { 4100 tree.addSelectionRow(lead); 4101 } else { 4102 tree.removeSelectionRow(lead); 4103 ui.setLeadSelectionPath(lPath); 4104 } 4105 ui.setAnchorSelectionPath(lPath); 4106 } 4107 } 4108 else if (key == EXTEND_TO) { 4109 extendSelection(tree, ui); 4110 } 4111 else if (key == MOVE_SELECTION_TO) { 4112 if (ui.getRowCount(tree) > 0) { 4113 int lead = ui.getLeadSelectionRow(); 4114 tree.setSelectionInterval(lead, lead); 4115 } 4116 } 4117 else if (key == SCROLL_LEFT) { 4118 scroll(tree, ui, SwingConstants.HORIZONTAL, -10); 4119 } 4120 else if (key == SCROLL_RIGHT) { 4121 scroll(tree, ui, SwingConstants.HORIZONTAL, 10); 4122 } 4123 else if (key == SCROLL_LEFT_EXTEND_SELECTION) { 4124 scrollChangeSelection(tree, ui, -1, true, true); 4125 } 4126 else if (key == SCROLL_RIGHT_EXTEND_SELECTION) { 4127 scrollChangeSelection(tree, ui, 1, true, true); 4128 } 4129 else if (key == SCROLL_RIGHT_CHANGE_LEAD) { 4130 scrollChangeSelection(tree, ui, 1, false, false); 4131 } 4132 else if (key == SCROLL_LEFT_CHANGE_LEAD) { 4133 scrollChangeSelection(tree, ui, -1, false, false); 4134 } 4135 else if (key == EXPAND) { 4136 expand(tree, ui); 4137 } 4138 else if (key == COLLAPSE) { 4139 collapse(tree, ui); 4140 } 4141 else if (key == MOVE_SELECTION_TO_PARENT) { 4142 moveSelectionToParent(tree, ui); 4143 } 4144 } 4145 4146 private void scrollChangeSelection(JTree tree, BasicTreeUI ui, 4147 int direction, boolean addToSelection, 4148 boolean changeSelection) { 4149 int rowCount; 4150 4151 if((rowCount = ui.getRowCount(tree)) > 0 && 4152 ui.treeSelectionModel != null) { 4153 TreePath newPath; 4154 Rectangle visRect = tree.getVisibleRect(); 4155 4156 if (direction == -1) { 4157 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4158 visRect.y); 4159 visRect.x = Math.max(0, visRect.x - visRect.width); 4160 } 4161 else { 4162 visRect.x = Math.min(Math.max(0, tree.getWidth() - 4163 visRect.width), visRect.x + visRect.width); 4164 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4165 visRect.y + visRect.height); 4166 } 4167 // Scroll 4168 tree.scrollRectToVisible(visRect); 4169 // select 4170 if (addToSelection) { 4171 ui.extendSelection(newPath); 4172 } 4173 else if(changeSelection) { 4174 tree.setSelectionPath(newPath); 4175 } 4176 else { 4177 ui.setLeadSelectionPath(newPath, true); 4178 } 4179 } 4180 } 4181 4182 private void scroll(JTree component, BasicTreeUI ui, int direction, 4183 int amount) { 4184 Rectangle visRect = component.getVisibleRect(); 4185 Dimension size = component.getSize(); 4186 if (direction == SwingConstants.HORIZONTAL) { 4187 visRect.x += amount; 4188 visRect.x = Math.max(0, visRect.x); 4189 visRect.x = Math.min(Math.max(0, size.width - visRect.width), 4190 visRect.x); 4191 } 4192 else { 4193 visRect.y += amount; 4194 visRect.y = Math.max(0, visRect.y); 4195 visRect.y = Math.min(Math.max(0, size.width - visRect.height), 4196 visRect.y); 4197 } 4198 component.scrollRectToVisible(visRect); 4199 } 4200 4201 private void extendSelection(JTree tree, BasicTreeUI ui) { 4202 if (ui.getRowCount(tree) > 0) { 4203 int lead = ui.getLeadSelectionRow(); 4204 4205 if (lead != -1) { 4206 TreePath leadP = ui.getLeadSelectionPath(); 4207 TreePath aPath = ui.getAnchorSelectionPath(); 4208 int aRow = ui.getRowForPath(tree, aPath); 4209 4210 if(aRow == -1) 4211 aRow = 0; 4212 tree.setSelectionInterval(aRow, lead); 4213 ui.setLeadSelectionPath(leadP); 4214 ui.setAnchorSelectionPath(aPath); 4215 } 4216 } 4217 } 4218 4219 private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) { 4220 int rowCount = ui.getRowCount(tree); 4221 4222 if(rowCount > 0) { 4223 if(selectAll) { 4224 if (tree.getSelectionModel().getSelectionMode() == 4225 TreeSelectionModel.SINGLE_TREE_SELECTION) { 4226 4227 int lead = ui.getLeadSelectionRow(); 4228 if (lead != -1) { 4229 tree.setSelectionRow(lead); 4230 } else if (tree.getMinSelectionRow() == -1) { 4231 tree.setSelectionRow(0); 4232 ui.ensureRowsAreVisible(0, 0); 4233 } 4234 return; 4235 } 4236 4237 TreePath lastPath = ui.getLeadSelectionPath(); 4238 TreePath aPath = ui.getAnchorSelectionPath(); 4239 4240 if(lastPath != null && !tree.isVisible(lastPath)) { 4241 lastPath = null; 4242 } 4243 tree.setSelectionInterval(0, rowCount - 1); 4244 if(lastPath != null) { 4245 ui.setLeadSelectionPath(lastPath); 4246 } 4247 if(aPath != null && tree.isVisible(aPath)) { 4248 ui.setAnchorSelectionPath(aPath); 4249 } 4250 } 4251 else { 4252 TreePath lastPath = ui.getLeadSelectionPath(); 4253 TreePath aPath = ui.getAnchorSelectionPath(); 4254 4255 tree.clearSelection(); 4256 ui.setAnchorSelectionPath(aPath); 4257 ui.setLeadSelectionPath(lastPath); 4258 } 4259 } 4260 } 4261 4262 private void startEditing(JTree tree, BasicTreeUI ui) { 4263 TreePath lead = ui.getLeadSelectionPath(); 4264 int editRow = (lead != null) ? 4265 ui.getRowForPath(tree, lead) : -1; 4266 4267 if(editRow != -1) { 4268 tree.startEditingAtPath(lead); 4269 } 4270 } 4271 4272 private void cancelEditing(JTree tree, BasicTreeUI ui) { 4273 tree.cancelEditing(); 4274 } 4275 4276 private void toggle(JTree tree, BasicTreeUI ui) { 4277 int selRow = ui.getLeadSelectionRow(); 4278 4279 if(selRow != -1 && !ui.isLeaf(selRow)) { 4280 TreePath aPath = ui.getAnchorSelectionPath(); 4281 TreePath lPath = ui.getLeadSelectionPath(); 4282 4283 ui.toggleExpandState(ui.getPathForRow(tree, selRow)); 4284 ui.setAnchorSelectionPath(aPath); 4285 ui.setLeadSelectionPath(lPath); 4286 } 4287 } 4288 4289 private void expand(JTree tree, BasicTreeUI ui) { 4290 int selRow = ui.getLeadSelectionRow(); 4291 tree.expandRow(selRow); 4292 } 4293 4294 private void collapse(JTree tree, BasicTreeUI ui) { 4295 int selRow = ui.getLeadSelectionRow(); 4296 tree.collapseRow(selRow); 4297 } 4298 4299 private void increment(JTree tree, BasicTreeUI ui, int direction, 4300 boolean addToSelection, 4301 boolean changeSelection) { 4302 4303 // disable moving of lead unless in discontiguous mode 4304 if (!addToSelection && !changeSelection && 4305 tree.getSelectionModel().getSelectionMode() != 4306 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4307 changeSelection = true; 4308 } 4309 4310 int rowCount; 4311 4312 if(ui.treeSelectionModel != null && 4313 (rowCount = tree.getRowCount()) > 0) { 4314 int selIndex = ui.getLeadSelectionRow(); 4315 int newIndex; 4316 4317 if(selIndex == -1) { 4318 if(direction == 1) 4319 newIndex = 0; 4320 else 4321 newIndex = rowCount - 1; 4322 } 4323 else 4324 /* Aparently people don't like wrapping;( */ 4325 newIndex = Math.min(rowCount - 1, Math.max 4326 (0, (selIndex + direction))); 4327 if(addToSelection && ui.treeSelectionModel. 4328 getSelectionMode() != TreeSelectionModel. 4329 SINGLE_TREE_SELECTION) { 4330 ui.extendSelection(tree.getPathForRow(newIndex)); 4331 } 4332 else if(changeSelection) { 4333 tree.setSelectionInterval(newIndex, newIndex); 4334 } 4335 else { 4336 ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true); 4337 } 4338 ui.ensureRowsAreVisible(newIndex, newIndex); 4339 ui.lastSelectedRow = newIndex; 4340 } 4341 } 4342 4343 private void traverse(JTree tree, BasicTreeUI ui, int direction, 4344 boolean changeSelection) { 4345 4346 // disable moving of lead unless in discontiguous mode 4347 if (!changeSelection && 4348 tree.getSelectionModel().getSelectionMode() != 4349 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4350 changeSelection = true; 4351 } 4352 4353 int rowCount; 4354 4355 if((rowCount = tree.getRowCount()) > 0) { 4356 int minSelIndex = ui.getLeadSelectionRow(); 4357 int newIndex; 4358 4359 if(minSelIndex == -1) 4360 newIndex = 0; 4361 else { 4362 /* Try and expand the node, otherwise go to next 4363 node. */ 4364 if(direction == 1) { 4365 TreePath minSelPath = ui.getPathForRow(tree, minSelIndex); 4366 int childCount = tree.getModel(). 4367 getChildCount(minSelPath.getLastPathComponent()); 4368 newIndex = -1; 4369 if (!ui.isLeaf(minSelIndex)) { 4370 if (!tree.isExpanded(minSelIndex)) { 4371 ui.toggleExpandState(minSelPath); 4372 } 4373 else if (childCount > 0) { 4374 newIndex = Math.min(minSelIndex + 1, rowCount - 1); 4375 } 4376 } 4377 } 4378 /* Try to collapse node. */ 4379 else { 4380 if(!ui.isLeaf(minSelIndex) && 4381 tree.isExpanded(minSelIndex)) { 4382 ui.toggleExpandState(ui.getPathForRow 4383 (tree, minSelIndex)); 4384 newIndex = -1; 4385 } 4386 else { 4387 TreePath path = ui.getPathForRow(tree, 4388 minSelIndex); 4389 4390 if(path != null && path.getPathCount() > 1) { 4391 newIndex = ui.getRowForPath(tree, path. 4392 getParentPath()); 4393 } 4394 else 4395 newIndex = -1; 4396 } 4397 } 4398 } 4399 if(newIndex != -1) { 4400 if(changeSelection) { 4401 tree.setSelectionInterval(newIndex, newIndex); 4402 } 4403 else { 4404 ui.setLeadSelectionPath(ui.getPathForRow( 4405 tree, newIndex), true); 4406 } 4407 ui.ensureRowsAreVisible(newIndex, newIndex); 4408 } 4409 } 4410 } 4411 4412 private void moveSelectionToParent(JTree tree, BasicTreeUI ui) { 4413 int selRow = ui.getLeadSelectionRow(); 4414 TreePath path = ui.getPathForRow(tree, selRow); 4415 if (path != null && path.getPathCount() > 1) { 4416 int newIndex = ui.getRowForPath(tree, path.getParentPath()); 4417 if (newIndex != -1) { 4418 tree.setSelectionInterval(newIndex, newIndex); 4419 ui.ensureRowsAreVisible(newIndex, newIndex); 4420 } 4421 } 4422 } 4423 4424 private void page(JTree tree, BasicTreeUI ui, int direction, 4425 boolean addToSelection, boolean changeSelection) { 4426 4427 // disable moving of lead unless in discontiguous mode 4428 if (!addToSelection && !changeSelection && 4429 tree.getSelectionModel().getSelectionMode() != 4430 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4431 changeSelection = true; 4432 } 4433 4434 int rowCount; 4435 4436 if((rowCount = ui.getRowCount(tree)) > 0 && 4437 ui.treeSelectionModel != null) { 4438 Dimension maxSize = tree.getSize(); 4439 TreePath lead = ui.getLeadSelectionPath(); 4440 TreePath newPath; 4441 Rectangle visRect = tree.getVisibleRect(); 4442 4443 if(direction == -1) { 4444 // up. 4445 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4446 visRect.y); 4447 if(newPath.equals(lead)) { 4448 visRect.y = Math.max(0, visRect.y - visRect.height); 4449 newPath = tree.getClosestPathForLocation(visRect.x, 4450 visRect.y); 4451 } 4452 } 4453 else { 4454 // down 4455 visRect.y = Math.min(maxSize.height, visRect.y + 4456 visRect.height - 1); 4457 newPath = tree.getClosestPathForLocation(visRect.x, 4458 visRect.y); 4459 if(newPath.equals(lead)) { 4460 visRect.y = Math.min(maxSize.height, visRect.y + 4461 visRect.height - 1); 4462 newPath = tree.getClosestPathForLocation(visRect.x, 4463 visRect.y); 4464 } 4465 } 4466 Rectangle newRect = ui.getPathBounds(tree, newPath); 4467 4468 newRect.x = visRect.x; 4469 newRect.width = visRect.width; 4470 if(direction == -1) { 4471 newRect.height = visRect.height; 4472 } 4473 else { 4474 newRect.y -= (visRect.height - newRect.height); 4475 newRect.height = visRect.height; 4476 } 4477 4478 if(addToSelection) { 4479 ui.extendSelection(newPath); 4480 } 4481 else if(changeSelection) { 4482 tree.setSelectionPath(newPath); 4483 } 4484 else { 4485 ui.setLeadSelectionPath(newPath, true); 4486 } 4487 tree.scrollRectToVisible(newRect); 4488 } 4489 } 4490 4491 private void home(JTree tree, BasicTreeUI ui, int direction, 4492 boolean addToSelection, boolean changeSelection) { 4493 4494 // disable moving of lead unless in discontiguous mode 4495 if (!addToSelection && !changeSelection && 4496 tree.getSelectionModel().getSelectionMode() != 4497 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4498 changeSelection = true; 4499 } 4500 4501 int rowCount = ui.getRowCount(tree); 4502 4503 if (rowCount > 0) { 4504 if(direction == -1) { 4505 ui.ensureRowsAreVisible(0, 0); 4506 if (addToSelection) { 4507 TreePath aPath = ui.getAnchorSelectionPath(); 4508 int aRow = (aPath == null) ? -1 : 4509 ui.getRowForPath(tree, aPath); 4510 4511 if (aRow == -1) { 4512 tree.setSelectionInterval(0, 0); 4513 } 4514 else { 4515 tree.setSelectionInterval(0, aRow); 4516 ui.setAnchorSelectionPath(aPath); 4517 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0)); 4518 } 4519 } 4520 else if(changeSelection) { 4521 tree.setSelectionInterval(0, 0); 4522 } 4523 else { 4524 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0), 4525 true); 4526 } 4527 } 4528 else { 4529 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); 4530 if (addToSelection) { 4531 TreePath aPath = ui.getAnchorSelectionPath(); 4532 int aRow = (aPath == null) ? -1 : 4533 ui.getRowForPath(tree, aPath); 4534 4535 if (aRow == -1) { 4536 tree.setSelectionInterval(rowCount - 1, 4537 rowCount -1); 4538 } 4539 else { 4540 tree.setSelectionInterval(aRow, rowCount - 1); 4541 ui.setAnchorSelectionPath(aPath); 4542 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4543 rowCount -1)); 4544 } 4545 } 4546 else if(changeSelection) { 4547 tree.setSelectionInterval(rowCount - 1, rowCount - 1); 4548 } 4549 else { 4550 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4551 rowCount - 1), true); 4552 } 4553 } 4554 } 4555 } 4556 } 4557 } // End of class BasicTreeUI