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