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