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