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