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