1 /* 2 * Copyright (c) 1997, 2017, 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 */ 225 public static ComponentUI createUI(JComponent x) { 226 return new BasicTreeUI(); 227 } 228 229 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 */ 297 public BasicTreeUI() { 298 super(); 299 } 300 301 /** 302 * Returns the hash color. 303 * 304 * @return the hash color 305 */ 306 protected Color getHashColor() { 307 return hashColor; 308 } 309 310 /** 311 * Sets the hash color. 312 * 313 * @param color the hash color 314 */ 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 */ 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 */ 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 */ 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 */ 359 public int getRightChildIndent() { 360 return rightChildIndent; 361 } 362 363 /** 364 * Sets the expanded icon. 365 * 366 * @param newG the expanded icon 367 */ 368 public void setExpandedIcon(Icon newG) { 369 expandedIcon = newG; 370 } 371 372 /** 373 * Returns the expanded icon. 374 * 375 * @return the expanded icon 376 */ 377 public Icon getExpandedIcon() { 378 return expandedIcon; 379 } 380 381 /** 382 * Sets the collapsed icon. 383 * 384 * @param newG the collapsed icon 385 */ 386 public void setCollapsedIcon(Icon newG) { 387 collapsedIcon = newG; 388 } 389 390 /** 391 * Returns the collapsed icon. 392 * 393 * @return the collapsed icon 394 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 476 protected TreeCellRenderer getCellRenderer() { 477 return currentCellRenderer; 478 } 479 480 /** 481 * Sets the {@code TreeModel}. 482 * 483 * @param model the new value 484 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 749 public TreePath getEditingPath(JTree tree) { 750 return editingPath; 751 } 752 753 // 754 // Install methods 755 // 756 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 988 protected PropertyChangeListener createPropertyChangeListener() { 989 return getHandler(); 990 } 991 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 1132 protected TreeModelListener createTreeModelListener() { 1133 return getHandler(); 1134 } 1135 1136 // 1137 // Uninstall methods 1138 // 1139 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 */ 1156 protected void prepareForUIUninstall() { 1157 } 1158 1159 /** 1160 * Uninstalls UI. 1161 */ 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 */ 1191 protected void uninstallDefaults() { 1192 if (tree.getTransferHandler() instanceof UIResource) { 1193 tree.setTransferHandler(null); 1194 } 1195 } 1196 1197 /** 1198 * Unregisters listeners. 1199 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 1844 protected int getHorizontalLegBuffer() { 1845 return 0; 1846 } 1847 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 */ 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 */ 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 */ 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 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 */ 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 */ 1949 protected void updateLayoutCacheExpandedNodes() { 1950 if(treeModel != null && treeModel.getRoot() != null) 1951 updateExpandedDescendants(new TreePath(treeModel.getRoot())); 1952 } 1953 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 2129 protected void updateSize() { 2130 validCachedPreferredSize = false; 2131 tree.treeDidChange(); 2132 } 2133 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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") 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 // 2762 private void setAnchorSelectionPath(TreePath newPath) { 2763 ignoreLAChange = true; 2764 try { 2765 tree.setAnchorSelectionPath(newPath); 2766 } finally{ 2767 ignoreLAChange = false; 2768 } 2769 } 2770 2771 private TreePath getAnchorSelectionPath() { 2772 return tree.getAnchorSelectionPath(); 2773 } 2774 2775 private void setLeadSelectionPath(TreePath newPath) { 2776 setLeadSelectionPath(newPath, false); 2777 } 2778 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 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 2810 private TreePath getLeadSelectionPath() { 2811 return tree.getLeadSelectionPath(); 2812 } 2813 2814 /** 2815 * Updates the lead row of the selection. 2816 * @since 1.7 2817 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 2983 public void treeNodesChanged(TreeModelEvent e) { 2984 getHandler().treeNodesChanged(e); 2985 } 2986 2987 public void treeNodesInserted(TreeModelEvent e) { 2988 getHandler().treeNodesInserted(e); 2989 } 2990 2991 public void treeNodesRemoved(TreeModelEvent e) { 2992 getHandler().treeNodesRemoved(e); 2993 } 2994 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 */ 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. */ 3034 public void editingStopped(ChangeEvent e) { 3035 getHandler().editingStopped(e); 3036 } 3037 3038 /** Messaged when editing has been canceled in the tree. */ 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 */ 3075 public void keyTyped(KeyEvent e) { 3076 getHandler().keyTyped(e); 3077 } 3078 3079 public void keyPressed(KeyEvent e) { 3080 getHandler().keyPressed(e); 3081 } 3082 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 */ 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 */ 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 */ 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 */ 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 */ 3207 public void mousePressed(MouseEvent e) { 3208 getHandler().mousePressed(e); 3209 } 3210 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 */ 3220 public void mouseMoved(MouseEvent e) { 3221 getHandler().mouseMoved(e); 3222 } 3223 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 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 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 */ 3286 public TreeTraverseAction(int direction, String name) { 3287 this(direction, name, true); 3288 } 3289 3290 private TreeTraverseAction(int direction, String name, 3291 boolean changeSelection) { 3292 this.direction = direction; 3293 this.changeSelection = changeSelection; 3294 } 3295 3296 public void actionPerformed(ActionEvent e) { 3297 if (tree != null) { 3298 SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction, 3299 changeSelection); 3300 } 3301 } 3302 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 */ 3324 public TreePageAction(int direction, String name) { 3325 this(direction, name, false, true); 3326 } 3327 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 3336 public void actionPerformed(ActionEvent e) { 3337 if (tree != null) { 3338 SHARED_ACTION.page(tree, BasicTreeUI.this, direction, 3339 addToSelection, changeSelection); 3340 } 3341 } 3342 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 */ 3367 public TreeIncrementAction(int direction, String name) { 3368 this(direction, name, false, true); 3369 } 3370 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 3379 public void actionPerformed(ActionEvent e) { 3380 if (tree != null) { 3381 SHARED_ACTION.increment(tree, BasicTreeUI.this, direction, 3382 addToSelection, changeSelection); 3383 } 3384 } 3385 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 */ 3412 public TreeHomeAction(int direction, String name) { 3413 this(direction, name, false, true); 3414 } 3415 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 3424 public void actionPerformed(ActionEvent e) { 3425 if (tree != null) { 3426 SHARED_ACTION.home(tree, BasicTreeUI.this, direction, 3427 addToSelection, changeSelection); 3428 } 3429 } 3430 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 */ 3447 public TreeToggleAction(String name) { 3448 } 3449 3450 public void actionPerformed(ActionEvent e) { 3451 if(tree != null) { 3452 SHARED_ACTION.toggle(tree, BasicTreeUI.this); 3453 } 3454 } 3455 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 */ 3472 public TreeCancelEditingAction(String name) { 3473 } 3474 3475 public void actionPerformed(ActionEvent e) { 3476 if(tree != null) { 3477 SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this); 3478 } 3479 } 3480 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 */ 3510 public MouseInputHandler(Component source, Component destination, 3511 MouseEvent event){ 3512 this(source, destination, event, null); 3513 } 3514 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 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 3539 public void mousePressed(MouseEvent e) { 3540 } 3541 3542 public void mouseReleased(MouseEvent e) { 3543 if(destination != null) 3544 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3545 (source, e, destination)); 3546 removeFromSource(); 3547 } 3548 3549 public void mouseEntered(MouseEvent e) { 3550 if (!SwingUtilities.isLeftMouseButton(e)) { 3551 removeFromSource(); 3552 } 3553 } 3554 3555 public void mouseExited(MouseEvent e) { 3556 if (!SwingUtilities.isLeftMouseButton(e)) { 3557 removeFromSource(); 3558 } 3559 } 3560 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 3569 public void mouseMoved(MouseEvent e) { 3570 removeFromSource(); 3571 } 3572 3573 /** 3574 * Removes an event from the source. 3575 */ 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 */ 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 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 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 */ 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 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 */ 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 */ 3768 public void keyPressed(KeyEvent e) { 3769 if (tree != null && isNavigationKey(e)) { 3770 prefix = ""; 3771 typedString = ""; 3772 lastTime = 0L; 3773 } 3774 } 3775 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 */ 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 // 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 completeEditing(); 3848 if(treeState != null) 3849 treeState.invalidateSizes(); 3850 updateSize(); 3851 } 3852 else if (changeName == "componentOrientation") { 3853 if (tree != null) { 3854 leftToRight = BasicGraphicsUtils.isLeftToRight(tree); 3855 redoTheLayout(); 3856 tree.treeDidChange(); 3857 3858 InputMap km = getInputMap(JComponent.WHEN_FOCUSED); 3859 SwingUtilities.replaceUIInputMap(tree, 3860 JComponent.WHEN_FOCUSED, km); 3861 } 3862 } else if ("dropLocation" == changeName) { 3863 JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue(); 3864 repaintDropLocation(oldValue); 3865 repaintDropLocation(tree.getDropLocation()); 3866 } 3867 } 3868 } 3869 3870 private void repaintDropLocation(JTree.DropLocation loc) { 3871 if (loc == null) { 3872 return; 3873 } 3874 3875 Rectangle r; 3876 3877 if (isDropLine(loc)) { 3878 r = getDropLineRect(loc); 3879 } else { 3880 r = tree.getPathBounds(loc.getPath()); 3881 } 3882 3883 if (r != null) { 3884 tree.repaint(r); 3885 } 3886 } 3887 3888 // 3889 // MouseListener 3890 // 3891 3892 // Whether or not the mouse press (which is being considered as part 3893 // of a drag sequence) also caused the selection change to be fully 3894 // processed. 3895 private boolean dragPressDidSelection; 3896 3897 // Set to true when a drag gesture has been fully recognized and DnD 3898 // begins. Use this to ignore further mouse events which could be 3899 // delivered if DnD is cancelled (via ESCAPE for example) 3900 private boolean dragStarted; 3901 3902 // The path over which the press occurred and the press event itself 3903 private TreePath pressedPath; 3904 private MouseEvent pressedEvent; 3905 3906 // Used to detect whether the press event causes a selection change. 3907 // If it does, we won't try to start editing on the release. 3908 private boolean valueChangedOnPress; 3909 3910 private boolean isActualPath(TreePath path, int x, int y) { 3911 if (path == null) { 3912 return false; 3913 } 3914 3915 Rectangle bounds = getPathBounds(tree, path); 3916 if (bounds == null || y > (bounds.y + bounds.height)) { 3917 return false; 3918 } 3919 3920 return (x >= bounds.x) && (x <= (bounds.x + bounds.width)); 3921 } 3922 3923 public void mouseClicked(MouseEvent e) { 3924 } 3925 3926 public void mouseEntered(MouseEvent e) { 3927 } 3928 3929 public void mouseExited(MouseEvent e) { 3930 } 3931 3932 /** 3933 * Invoked when a mouse button has been pressed on a component. 3934 */ 3935 public void mousePressed(MouseEvent e) { 3936 if (SwingUtilities2.shouldIgnore(e, tree)) { 3937 return; 3938 } 3939 3940 // if we can't stop any ongoing editing, do nothing 3941 if (isEditing(tree) && tree.getInvokesStopCellEditing() 3942 && !stopEditing(tree)) { 3943 return; 3944 } 3945 3946 completeEditing(); 3947 3948 pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY()); 3949 3950 if (tree.getDragEnabled()) { 3951 mousePressedDND(e); 3952 } else { 3953 SwingUtilities2.adjustFocus(tree); 3954 handleSelection(e); 3955 } 3956 } 3957 3958 private void mousePressedDND(MouseEvent e) { 3959 pressedEvent = e; 3960 boolean grabFocus = true; 3961 dragStarted = false; 3962 valueChangedOnPress = false; 3963 3964 // if we have a valid path and this is a drag initiating event 3965 if (isActualPath(pressedPath, e.getX(), e.getY()) && 3966 DragRecognitionSupport.mousePressed(e)) { 3967 3968 dragPressDidSelection = false; 3969 3970 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) { 3971 // do nothing for control - will be handled on release 3972 // or when drag starts 3973 return; 3974 } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) { 3975 // clicking on something that's already selected 3976 // and need to make it the lead now 3977 setAnchorSelectionPath(pressedPath); 3978 setLeadSelectionPath(pressedPath, true); 3979 return; 3980 } 3981 3982 dragPressDidSelection = true; 3983 3984 // could be a drag initiating event - don't grab focus 3985 grabFocus = false; 3986 } 3987 3988 if (grabFocus) { 3989 SwingUtilities2.adjustFocus(tree); 3990 } 3991 3992 handleSelection(e); 3993 } 3994 3995 void handleSelection(MouseEvent e) { 3996 if(pressedPath != null) { 3997 Rectangle bounds = getPathBounds(tree, pressedPath); 3998 3999 if (bounds == null || e.getY() >= (bounds.y + bounds.height)) { 4000 return; 4001 } 4002 4003 // Preferably checkForClickInExpandControl could take 4004 // the Event to do this it self! 4005 if(SwingUtilities.isLeftMouseButton(e)) { 4006 checkForClickInExpandControl(pressedPath, e.getX(), e.getY()); 4007 } 4008 4009 int x = e.getX(); 4010 4011 // Perhaps they clicked the cell itself. If so, 4012 // select it. 4013 if (x >= bounds.x && x < (bounds.x + bounds.width)) { 4014 if (tree.getDragEnabled() || !startEditing(pressedPath, e)) { 4015 selectPathForEvent(pressedPath, e); 4016 } 4017 } 4018 } 4019 } 4020 4021 public void dragStarting(MouseEvent me) { 4022 dragStarted = true; 4023 4024 if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) { 4025 tree.addSelectionPath(pressedPath); 4026 setAnchorSelectionPath(pressedPath); 4027 setLeadSelectionPath(pressedPath, true); 4028 } 4029 4030 pressedEvent = null; 4031 pressedPath = null; 4032 } 4033 4034 public void mouseDragged(MouseEvent e) { 4035 if (SwingUtilities2.shouldIgnore(e, tree)) { 4036 return; 4037 } 4038 4039 if (tree.getDragEnabled()) { 4040 DragRecognitionSupport.mouseDragged(e, this); 4041 } 4042 } 4043 4044 /** 4045 * Invoked when the mouse button has been moved on a component 4046 * (with no buttons no down). 4047 */ 4048 public void mouseMoved(MouseEvent e) { 4049 } 4050 4051 public void mouseReleased(MouseEvent e) { 4052 if (SwingUtilities2.shouldIgnore(e, tree)) { 4053 return; 4054 } 4055 4056 if (tree.getDragEnabled()) { 4057 mouseReleasedDND(e); 4058 } 4059 4060 pressedEvent = null; 4061 pressedPath = null; 4062 } 4063 4064 private void mouseReleasedDND(MouseEvent e) { 4065 MouseEvent me = DragRecognitionSupport.mouseReleased(e); 4066 if (me != null) { 4067 SwingUtilities2.adjustFocus(tree); 4068 if (!dragPressDidSelection) { 4069 handleSelection(me); 4070 } 4071 } 4072 4073 if (!dragStarted) { 4074 4075 // Note: We don't give the tree a chance to start editing if the 4076 // mouse press caused a selection change. Otherwise the default 4077 // tree cell editor will start editing on EVERY press and 4078 // release. If it turns out that this affects some editors, we 4079 // can always parameterize this with a client property. ex: 4080 // 4081 // if (pressedPath != null && 4082 // (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") || 4083 // !valueChangedOnPress) && ... 4084 if (pressedPath != null && !valueChangedOnPress && 4085 isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) { 4086 4087 startEditingOnRelease(pressedPath, pressedEvent, e); 4088 } 4089 } 4090 } 4091 4092 // 4093 // FocusListener 4094 // 4095 public void focusGained(FocusEvent e) { 4096 if(tree != null) { 4097 Rectangle pBounds; 4098 4099 pBounds = getPathBounds(tree, tree.getLeadSelectionPath()); 4100 if(pBounds != null) 4101 tree.repaint(getRepaintPathBounds(pBounds)); 4102 pBounds = getPathBounds(tree, getLeadSelectionPath()); 4103 if(pBounds != null) 4104 tree.repaint(getRepaintPathBounds(pBounds)); 4105 } 4106 } 4107 4108 public void focusLost(FocusEvent e) { 4109 focusGained(e); 4110 } 4111 4112 // 4113 // CellEditorListener 4114 // 4115 public void editingStopped(ChangeEvent e) { 4116 completeEditing(false, false, true); 4117 } 4118 4119 /** Messaged when editing has been canceled in the tree. */ 4120 public void editingCanceled(ChangeEvent e) { 4121 completeEditing(false, false, false); 4122 } 4123 4124 4125 // 4126 // TreeSelectionListener 4127 // 4128 public void valueChanged(TreeSelectionEvent event) { 4129 valueChangedOnPress = true; 4130 4131 // Stop editing 4132 completeEditing(); 4133 // Make sure all the paths are visible, if necessary. 4134 // PENDING: This should be tweaked when isAdjusting is added 4135 if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) { 4136 TreePath[] paths = treeSelectionModel 4137 .getSelectionPaths(); 4138 4139 if(paths != null) { 4140 for(int counter = paths.length - 1; counter >= 0; 4141 counter--) { 4142 TreePath path = paths[counter].getParentPath(); 4143 boolean expand = true; 4144 4145 while (path != null) { 4146 // Indicates this path isn't valid anymore, 4147 // we shouldn't attempt to expand it then. 4148 if (treeModel.isLeaf(path.getLastPathComponent())){ 4149 expand = false; 4150 path = null; 4151 } 4152 else { 4153 path = path.getParentPath(); 4154 } 4155 } 4156 if (expand) { 4157 tree.makeVisible(paths[counter]); 4158 } 4159 } 4160 } 4161 } 4162 4163 TreePath oldLead = getLeadSelectionPath(); 4164 lastSelectedRow = tree.getMinSelectionRow(); 4165 TreePath lead = tree.getSelectionModel().getLeadSelectionPath(); 4166 setAnchorSelectionPath(lead); 4167 setLeadSelectionPath(lead); 4168 4169 TreePath[] changedPaths = event.getPaths(); 4170 Rectangle nodeBounds; 4171 Rectangle visRect = tree.getVisibleRect(); 4172 boolean paintPaths = true; 4173 int nWidth = tree.getWidth(); 4174 4175 if(changedPaths != null) { 4176 int counter, maxCounter = changedPaths.length; 4177 4178 if(maxCounter > 4) { 4179 tree.repaint(); 4180 paintPaths = false; 4181 } 4182 else { 4183 for (counter = 0; counter < maxCounter; counter++) { 4184 nodeBounds = getPathBounds(tree, 4185 changedPaths[counter]); 4186 if(nodeBounds != null && 4187 visRect.intersects(nodeBounds)) 4188 tree.repaint(0, nodeBounds.y, nWidth, 4189 nodeBounds.height); 4190 } 4191 } 4192 } 4193 if(paintPaths) { 4194 nodeBounds = getPathBounds(tree, oldLead); 4195 if(nodeBounds != null && visRect.intersects(nodeBounds)) 4196 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 4197 nodeBounds = getPathBounds(tree, lead); 4198 if(nodeBounds != null && visRect.intersects(nodeBounds)) 4199 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 4200 } 4201 } 4202 4203 4204 // 4205 // TreeExpansionListener 4206 // 4207 public void treeExpanded(TreeExpansionEvent event) { 4208 if(event != null && tree != null) { 4209 TreePath path = event.getPath(); 4210 4211 updateExpandedDescendants(path); 4212 } 4213 } 4214 4215 public void treeCollapsed(TreeExpansionEvent event) { 4216 if(event != null && tree != null) { 4217 TreePath path = event.getPath(); 4218 4219 completeEditing(); 4220 if(path != null && tree.isVisible(path)) { 4221 treeState.setExpandedState(path, false); 4222 updateLeadSelectionRow(); 4223 updateSize(); 4224 } 4225 } 4226 } 4227 4228 // 4229 // TreeModelListener 4230 // 4231 public void treeNodesChanged(TreeModelEvent e) { 4232 if(treeState != null && e != null) { 4233 TreePath parentPath = SwingUtilities2.getTreePath(e, getModel()); 4234 int[] indices = e.getChildIndices(); 4235 if (indices == null || indices.length == 0) { 4236 // The root has changed 4237 treeState.treeNodesChanged(e); 4238 updateSize(); 4239 } 4240 else if (treeState.isExpanded(parentPath)) { 4241 // Changed nodes are visible 4242 // Find the minimum index, we only need paint from there 4243 // down. 4244 int minIndex = indices[0]; 4245 for (int i = indices.length - 1; i > 0; i--) { 4246 minIndex = Math.min(indices[i], minIndex); 4247 } 4248 Object minChild = treeModel.getChild( 4249 parentPath.getLastPathComponent(), minIndex); 4250 TreePath minPath = parentPath.pathByAddingChild(minChild); 4251 Rectangle minBounds = getPathBounds(tree, minPath); 4252 4253 // Forward to the treestate 4254 treeState.treeNodesChanged(e); 4255 4256 // Mark preferred size as bogus. 4257 updateSize0(); 4258 4259 // And repaint 4260 Rectangle newMinBounds = getPathBounds(tree, minPath); 4261 if (minBounds == null || newMinBounds == null) { 4262 return; 4263 } 4264 4265 if (indices.length == 1 && 4266 newMinBounds.height == minBounds.height) { 4267 tree.repaint(0, minBounds.y, tree.getWidth(), 4268 minBounds.height); 4269 } 4270 else { 4271 tree.repaint(0, minBounds.y, tree.getWidth(), 4272 tree.getHeight() - minBounds.y); 4273 } 4274 } 4275 else { 4276 // Nodes that changed aren't visible. No need to paint 4277 treeState.treeNodesChanged(e); 4278 } 4279 } 4280 } 4281 4282 public void treeNodesInserted(TreeModelEvent e) { 4283 if(treeState != null && e != null) { 4284 treeState.treeNodesInserted(e); 4285 4286 updateLeadSelectionRow(); 4287 4288 TreePath path = SwingUtilities2.getTreePath(e, getModel()); 4289 4290 if(treeState.isExpanded(path)) { 4291 updateSize(); 4292 } 4293 else { 4294 // PENDING(sky): Need a method in TreeModelEvent 4295 // that can return the count, getChildIndices allocs 4296 // a new array! 4297 int[] indices = e.getChildIndices(); 4298 int childCount = treeModel.getChildCount 4299 (path.getLastPathComponent()); 4300 4301 if(indices != null && (childCount - indices.length) == 0) 4302 updateSize(); 4303 } 4304 } 4305 } 4306 4307 public void treeNodesRemoved(TreeModelEvent e) { 4308 if(treeState != null && e != null) { 4309 treeState.treeNodesRemoved(e); 4310 4311 updateLeadSelectionRow(); 4312 4313 TreePath path = SwingUtilities2.getTreePath(e, getModel()); 4314 4315 if(treeState.isExpanded(path) || 4316 treeModel.getChildCount(path.getLastPathComponent()) == 0) 4317 updateSize(); 4318 } 4319 } 4320 4321 public void treeStructureChanged(TreeModelEvent e) { 4322 if(treeState != null && e != null) { 4323 treeState.treeStructureChanged(e); 4324 4325 updateLeadSelectionRow(); 4326 4327 TreePath pPath = SwingUtilities2.getTreePath(e, getModel()); 4328 4329 if (pPath != null) { 4330 pPath = pPath.getParentPath(); 4331 } 4332 if(pPath == null || treeState.isExpanded(pPath)) 4333 updateSize(); 4334 } 4335 } 4336 } 4337 4338 4339 4340 private static class Actions extends UIAction { 4341 private static final String SELECT_PREVIOUS = "selectPrevious"; 4342 private static final String SELECT_PREVIOUS_CHANGE_LEAD = 4343 "selectPreviousChangeLead"; 4344 private static final String SELECT_PREVIOUS_EXTEND_SELECTION = 4345 "selectPreviousExtendSelection"; 4346 private static final String SELECT_NEXT = "selectNext"; 4347 private static final String SELECT_NEXT_CHANGE_LEAD = 4348 "selectNextChangeLead"; 4349 private static final String SELECT_NEXT_EXTEND_SELECTION = 4350 "selectNextExtendSelection"; 4351 private static final String SELECT_CHILD = "selectChild"; 4352 private static final String SELECT_CHILD_CHANGE_LEAD = 4353 "selectChildChangeLead"; 4354 private static final String SELECT_PARENT = "selectParent"; 4355 private static final String SELECT_PARENT_CHANGE_LEAD = 4356 "selectParentChangeLead"; 4357 private static final String SCROLL_UP_CHANGE_SELECTION = 4358 "scrollUpChangeSelection"; 4359 private static final String SCROLL_UP_CHANGE_LEAD = 4360 "scrollUpChangeLead"; 4361 private static final String SCROLL_UP_EXTEND_SELECTION = 4362 "scrollUpExtendSelection"; 4363 private static final String SCROLL_DOWN_CHANGE_SELECTION = 4364 "scrollDownChangeSelection"; 4365 private static final String SCROLL_DOWN_EXTEND_SELECTION = 4366 "scrollDownExtendSelection"; 4367 private static final String SCROLL_DOWN_CHANGE_LEAD = 4368 "scrollDownChangeLead"; 4369 private static final String SELECT_FIRST = "selectFirst"; 4370 private static final String SELECT_FIRST_CHANGE_LEAD = 4371 "selectFirstChangeLead"; 4372 private static final String SELECT_FIRST_EXTEND_SELECTION = 4373 "selectFirstExtendSelection"; 4374 private static final String SELECT_LAST = "selectLast"; 4375 private static final String SELECT_LAST_CHANGE_LEAD = 4376 "selectLastChangeLead"; 4377 private static final String SELECT_LAST_EXTEND_SELECTION = 4378 "selectLastExtendSelection"; 4379 private static final String TOGGLE = "toggle"; 4380 private static final String CANCEL_EDITING = "cancel"; 4381 private static final String START_EDITING = "startEditing"; 4382 private static final String SELECT_ALL = "selectAll"; 4383 private static final String CLEAR_SELECTION = "clearSelection"; 4384 private static final String SCROLL_LEFT = "scrollLeft"; 4385 private static final String SCROLL_RIGHT = "scrollRight"; 4386 private static final String SCROLL_LEFT_EXTEND_SELECTION = 4387 "scrollLeftExtendSelection"; 4388 private static final String SCROLL_RIGHT_EXTEND_SELECTION = 4389 "scrollRightExtendSelection"; 4390 private static final String SCROLL_RIGHT_CHANGE_LEAD = 4391 "scrollRightChangeLead"; 4392 private static final String SCROLL_LEFT_CHANGE_LEAD = 4393 "scrollLeftChangeLead"; 4394 private static final String EXPAND = "expand"; 4395 private static final String COLLAPSE = "collapse"; 4396 private static final String MOVE_SELECTION_TO_PARENT = 4397 "moveSelectionToParent"; 4398 4399 // add the lead item to the selection without changing lead or anchor 4400 private static final String ADD_TO_SELECTION = "addToSelection"; 4401 4402 // toggle the selected state of the lead item and move the anchor to it 4403 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor"; 4404 4405 // extend the selection to the lead item 4406 private static final String EXTEND_TO = "extendTo"; 4407 4408 // move the anchor to the lead and ensure only that item is selected 4409 private static final String MOVE_SELECTION_TO = "moveSelectionTo"; 4410 4411 Actions() { 4412 super(null); 4413 } 4414 4415 Actions(String key) { 4416 super(key); 4417 } 4418 4419 @Override 4420 public boolean accept(Object o) { 4421 if (o instanceof JTree) { 4422 if (getName() == CANCEL_EDITING) { 4423 return ((JTree)o).isEditing(); 4424 } 4425 } 4426 return true; 4427 } 4428 4429 public void actionPerformed(ActionEvent e) { 4430 JTree tree = (JTree)e.getSource(); 4431 BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType( 4432 tree.getUI(), BasicTreeUI.class); 4433 if (ui == null) { 4434 return; 4435 } 4436 String key = getName(); 4437 if (key == SELECT_PREVIOUS) { 4438 increment(tree, ui, -1, false, true); 4439 } 4440 else if (key == SELECT_PREVIOUS_CHANGE_LEAD) { 4441 increment(tree, ui, -1, false, false); 4442 } 4443 else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) { 4444 increment(tree, ui, -1, true, true); 4445 } 4446 else if (key == SELECT_NEXT) { 4447 increment(tree, ui, 1, false, true); 4448 } 4449 else if (key == SELECT_NEXT_CHANGE_LEAD) { 4450 increment(tree, ui, 1, false, false); 4451 } 4452 else if (key == SELECT_NEXT_EXTEND_SELECTION) { 4453 increment(tree, ui, 1, true, true); 4454 } 4455 else if (key == SELECT_CHILD) { 4456 traverse(tree, ui, 1, true); 4457 } 4458 else if (key == SELECT_CHILD_CHANGE_LEAD) { 4459 traverse(tree, ui, 1, false); 4460 } 4461 else if (key == SELECT_PARENT) { 4462 traverse(tree, ui, -1, true); 4463 } 4464 else if (key == SELECT_PARENT_CHANGE_LEAD) { 4465 traverse(tree, ui, -1, false); 4466 } 4467 else if (key == SCROLL_UP_CHANGE_SELECTION) { 4468 page(tree, ui, -1, false, true); 4469 } 4470 else if (key == SCROLL_UP_CHANGE_LEAD) { 4471 page(tree, ui, -1, false, false); 4472 } 4473 else if (key == SCROLL_UP_EXTEND_SELECTION) { 4474 page(tree, ui, -1, true, true); 4475 } 4476 else if (key == SCROLL_DOWN_CHANGE_SELECTION) { 4477 page(tree, ui, 1, false, true); 4478 } 4479 else if (key == SCROLL_DOWN_EXTEND_SELECTION) { 4480 page(tree, ui, 1, true, true); 4481 } 4482 else if (key == SCROLL_DOWN_CHANGE_LEAD) { 4483 page(tree, ui, 1, false, false); 4484 } 4485 else if (key == SELECT_FIRST) { 4486 home(tree, ui, -1, false, true); 4487 } 4488 else if (key == SELECT_FIRST_CHANGE_LEAD) { 4489 home(tree, ui, -1, false, false); 4490 } 4491 else if (key == SELECT_FIRST_EXTEND_SELECTION) { 4492 home(tree, ui, -1, true, true); 4493 } 4494 else if (key == SELECT_LAST) { 4495 home(tree, ui, 1, false, true); 4496 } 4497 else if (key == SELECT_LAST_CHANGE_LEAD) { 4498 home(tree, ui, 1, false, false); 4499 } 4500 else if (key == SELECT_LAST_EXTEND_SELECTION) { 4501 home(tree, ui, 1, true, true); 4502 } 4503 else if (key == TOGGLE) { 4504 toggle(tree, ui); 4505 } 4506 else if (key == CANCEL_EDITING) { 4507 cancelEditing(tree, ui); 4508 } 4509 else if (key == START_EDITING) { 4510 startEditing(tree, ui); 4511 } 4512 else if (key == SELECT_ALL) { 4513 selectAll(tree, ui, true); 4514 } 4515 else if (key == CLEAR_SELECTION) { 4516 selectAll(tree, ui, false); 4517 } 4518 else if (key == ADD_TO_SELECTION) { 4519 if (ui.getRowCount(tree) > 0) { 4520 int lead = ui.getLeadSelectionRow(); 4521 if (!tree.isRowSelected(lead)) { 4522 TreePath aPath = ui.getAnchorSelectionPath(); 4523 tree.addSelectionRow(lead); 4524 ui.setAnchorSelectionPath(aPath); 4525 } 4526 } 4527 } 4528 else if (key == TOGGLE_AND_ANCHOR) { 4529 if (ui.getRowCount(tree) > 0) { 4530 int lead = ui.getLeadSelectionRow(); 4531 TreePath lPath = ui.getLeadSelectionPath(); 4532 if (!tree.isRowSelected(lead)) { 4533 tree.addSelectionRow(lead); 4534 } else { 4535 tree.removeSelectionRow(lead); 4536 ui.setLeadSelectionPath(lPath); 4537 } 4538 ui.setAnchorSelectionPath(lPath); 4539 } 4540 } 4541 else if (key == EXTEND_TO) { 4542 extendSelection(tree, ui); 4543 } 4544 else if (key == MOVE_SELECTION_TO) { 4545 if (ui.getRowCount(tree) > 0) { 4546 int lead = ui.getLeadSelectionRow(); 4547 tree.setSelectionInterval(lead, lead); 4548 } 4549 } 4550 else if (key == SCROLL_LEFT) { 4551 scroll(tree, ui, SwingConstants.HORIZONTAL, -10); 4552 } 4553 else if (key == SCROLL_RIGHT) { 4554 scroll(tree, ui, SwingConstants.HORIZONTAL, 10); 4555 } 4556 else if (key == SCROLL_LEFT_EXTEND_SELECTION) { 4557 scrollChangeSelection(tree, ui, -1, true, true); 4558 } 4559 else if (key == SCROLL_RIGHT_EXTEND_SELECTION) { 4560 scrollChangeSelection(tree, ui, 1, true, true); 4561 } 4562 else if (key == SCROLL_RIGHT_CHANGE_LEAD) { 4563 scrollChangeSelection(tree, ui, 1, false, false); 4564 } 4565 else if (key == SCROLL_LEFT_CHANGE_LEAD) { 4566 scrollChangeSelection(tree, ui, -1, false, false); 4567 } 4568 else if (key == EXPAND) { 4569 expand(tree, ui); 4570 } 4571 else if (key == COLLAPSE) { 4572 collapse(tree, ui); 4573 } 4574 else if (key == MOVE_SELECTION_TO_PARENT) { 4575 moveSelectionToParent(tree, ui); 4576 } 4577 } 4578 4579 private void scrollChangeSelection(JTree tree, BasicTreeUI ui, 4580 int direction, boolean addToSelection, 4581 boolean changeSelection) { 4582 int rowCount; 4583 4584 if((rowCount = ui.getRowCount(tree)) > 0 && 4585 ui.treeSelectionModel != null) { 4586 TreePath newPath; 4587 Rectangle visRect = tree.getVisibleRect(); 4588 4589 if (direction == -1) { 4590 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4591 visRect.y); 4592 visRect.x = Math.max(0, visRect.x - visRect.width); 4593 } 4594 else { 4595 visRect.x = Math.min(Math.max(0, tree.getWidth() - 4596 visRect.width), visRect.x + visRect.width); 4597 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4598 visRect.y + visRect.height); 4599 } 4600 // Scroll 4601 tree.scrollRectToVisible(visRect); 4602 // select 4603 if (addToSelection) { 4604 ui.extendSelection(newPath); 4605 } 4606 else if(changeSelection) { 4607 tree.setSelectionPath(newPath); 4608 } 4609 else { 4610 ui.setLeadSelectionPath(newPath, true); 4611 } 4612 } 4613 } 4614 4615 private void scroll(JTree component, BasicTreeUI ui, int direction, 4616 int amount) { 4617 Rectangle visRect = component.getVisibleRect(); 4618 Dimension size = component.getSize(); 4619 if (direction == SwingConstants.HORIZONTAL) { 4620 visRect.x += amount; 4621 visRect.x = Math.max(0, visRect.x); 4622 visRect.x = Math.min(Math.max(0, size.width - visRect.width), 4623 visRect.x); 4624 } 4625 else { 4626 visRect.y += amount; 4627 visRect.y = Math.max(0, visRect.y); 4628 visRect.y = Math.min(Math.max(0, size.width - visRect.height), 4629 visRect.y); 4630 } 4631 component.scrollRectToVisible(visRect); 4632 } 4633 4634 private void extendSelection(JTree tree, BasicTreeUI ui) { 4635 if (ui.getRowCount(tree) > 0) { 4636 int lead = ui.getLeadSelectionRow(); 4637 4638 if (lead != -1) { 4639 TreePath leadP = ui.getLeadSelectionPath(); 4640 TreePath aPath = ui.getAnchorSelectionPath(); 4641 int aRow = ui.getRowForPath(tree, aPath); 4642 4643 if(aRow == -1) 4644 aRow = 0; 4645 tree.setSelectionInterval(aRow, lead); 4646 ui.setLeadSelectionPath(leadP); 4647 ui.setAnchorSelectionPath(aPath); 4648 } 4649 } 4650 } 4651 4652 private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) { 4653 int rowCount = ui.getRowCount(tree); 4654 4655 if(rowCount > 0) { 4656 if(selectAll) { 4657 if (tree.getSelectionModel().getSelectionMode() == 4658 TreeSelectionModel.SINGLE_TREE_SELECTION) { 4659 4660 int lead = ui.getLeadSelectionRow(); 4661 if (lead != -1) { 4662 tree.setSelectionRow(lead); 4663 } else if (tree.getMinSelectionRow() == -1) { 4664 tree.setSelectionRow(0); 4665 ui.ensureRowsAreVisible(0, 0); 4666 } 4667 return; 4668 } 4669 4670 TreePath lastPath = ui.getLeadSelectionPath(); 4671 TreePath aPath = ui.getAnchorSelectionPath(); 4672 4673 if(lastPath != null && !tree.isVisible(lastPath)) { 4674 lastPath = null; 4675 } 4676 tree.setSelectionInterval(0, rowCount - 1); 4677 if(lastPath != null) { 4678 ui.setLeadSelectionPath(lastPath); 4679 } 4680 if(aPath != null && tree.isVisible(aPath)) { 4681 ui.setAnchorSelectionPath(aPath); 4682 } 4683 } 4684 else { 4685 TreePath lastPath = ui.getLeadSelectionPath(); 4686 TreePath aPath = ui.getAnchorSelectionPath(); 4687 4688 tree.clearSelection(); 4689 ui.setAnchorSelectionPath(aPath); 4690 ui.setLeadSelectionPath(lastPath); 4691 } 4692 } 4693 } 4694 4695 private void startEditing(JTree tree, BasicTreeUI ui) { 4696 TreePath lead = ui.getLeadSelectionPath(); 4697 int editRow = (lead != null) ? 4698 ui.getRowForPath(tree, lead) : -1; 4699 4700 if(editRow != -1) { 4701 tree.startEditingAtPath(lead); 4702 } 4703 } 4704 4705 private void cancelEditing(JTree tree, BasicTreeUI ui) { 4706 tree.cancelEditing(); 4707 } 4708 4709 private void toggle(JTree tree, BasicTreeUI ui) { 4710 int selRow = ui.getLeadSelectionRow(); 4711 4712 if(selRow != -1 && !ui.isLeaf(selRow)) { 4713 TreePath aPath = ui.getAnchorSelectionPath(); 4714 TreePath lPath = ui.getLeadSelectionPath(); 4715 4716 ui.toggleExpandState(ui.getPathForRow(tree, selRow)); 4717 ui.setAnchorSelectionPath(aPath); 4718 ui.setLeadSelectionPath(lPath); 4719 } 4720 } 4721 4722 private void expand(JTree tree, BasicTreeUI ui) { 4723 int selRow = ui.getLeadSelectionRow(); 4724 tree.expandRow(selRow); 4725 } 4726 4727 private void collapse(JTree tree, BasicTreeUI ui) { 4728 int selRow = ui.getLeadSelectionRow(); 4729 tree.collapseRow(selRow); 4730 } 4731 4732 private void increment(JTree tree, BasicTreeUI ui, int direction, 4733 boolean addToSelection, 4734 boolean changeSelection) { 4735 4736 // disable moving of lead unless in discontiguous mode 4737 if (!addToSelection && !changeSelection && 4738 tree.getSelectionModel().getSelectionMode() != 4739 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4740 changeSelection = true; 4741 } 4742 4743 int rowCount; 4744 4745 if(ui.treeSelectionModel != null && 4746 (rowCount = tree.getRowCount()) > 0) { 4747 int selIndex = ui.getLeadSelectionRow(); 4748 int newIndex; 4749 4750 if(selIndex == -1) { 4751 if(direction == 1) 4752 newIndex = 0; 4753 else 4754 newIndex = rowCount - 1; 4755 } 4756 else 4757 /* Aparently people don't like wrapping;( */ 4758 newIndex = Math.min(rowCount - 1, Math.max 4759 (0, (selIndex + direction))); 4760 if(addToSelection && ui.treeSelectionModel. 4761 getSelectionMode() != TreeSelectionModel. 4762 SINGLE_TREE_SELECTION) { 4763 ui.extendSelection(tree.getPathForRow(newIndex)); 4764 } 4765 else if(changeSelection) { 4766 tree.setSelectionInterval(newIndex, newIndex); 4767 } 4768 else { 4769 ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true); 4770 } 4771 ui.ensureRowsAreVisible(newIndex, newIndex); 4772 ui.lastSelectedRow = newIndex; 4773 } 4774 } 4775 4776 private void traverse(JTree tree, BasicTreeUI ui, int direction, 4777 boolean changeSelection) { 4778 4779 // disable moving of lead unless in discontiguous mode 4780 if (!changeSelection && 4781 tree.getSelectionModel().getSelectionMode() != 4782 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4783 changeSelection = true; 4784 } 4785 4786 int rowCount; 4787 4788 if((rowCount = tree.getRowCount()) > 0) { 4789 int minSelIndex = ui.getLeadSelectionRow(); 4790 int newIndex; 4791 4792 if(minSelIndex == -1) 4793 newIndex = 0; 4794 else { 4795 /* Try and expand the node, otherwise go to next 4796 node. */ 4797 if(direction == 1) { 4798 TreePath minSelPath = ui.getPathForRow(tree, minSelIndex); 4799 int childCount = tree.getModel(). 4800 getChildCount(minSelPath.getLastPathComponent()); 4801 newIndex = -1; 4802 if (!ui.isLeaf(minSelIndex)) { 4803 if (!tree.isExpanded(minSelIndex)) { 4804 ui.toggleExpandState(minSelPath); 4805 } 4806 else if (childCount > 0) { 4807 newIndex = Math.min(minSelIndex + 1, rowCount - 1); 4808 } 4809 } 4810 } 4811 /* Try to collapse node. */ 4812 else { 4813 if(!ui.isLeaf(minSelIndex) && 4814 tree.isExpanded(minSelIndex)) { 4815 ui.toggleExpandState(ui.getPathForRow 4816 (tree, minSelIndex)); 4817 newIndex = -1; 4818 } 4819 else { 4820 TreePath path = ui.getPathForRow(tree, 4821 minSelIndex); 4822 4823 if(path != null && path.getPathCount() > 1) { 4824 newIndex = ui.getRowForPath(tree, path. 4825 getParentPath()); 4826 } 4827 else 4828 newIndex = -1; 4829 } 4830 } 4831 } 4832 if(newIndex != -1) { 4833 if(changeSelection) { 4834 tree.setSelectionInterval(newIndex, newIndex); 4835 } 4836 else { 4837 ui.setLeadSelectionPath(ui.getPathForRow( 4838 tree, newIndex), true); 4839 } 4840 ui.ensureRowsAreVisible(newIndex, newIndex); 4841 } 4842 } 4843 } 4844 4845 private void moveSelectionToParent(JTree tree, BasicTreeUI ui) { 4846 int selRow = ui.getLeadSelectionRow(); 4847 TreePath path = ui.getPathForRow(tree, selRow); 4848 if (path != null && path.getPathCount() > 1) { 4849 int newIndex = ui.getRowForPath(tree, path.getParentPath()); 4850 if (newIndex != -1) { 4851 tree.setSelectionInterval(newIndex, newIndex); 4852 ui.ensureRowsAreVisible(newIndex, newIndex); 4853 } 4854 } 4855 } 4856 4857 private void page(JTree tree, BasicTreeUI ui, int direction, 4858 boolean addToSelection, boolean changeSelection) { 4859 4860 // disable moving of lead unless in discontiguous mode 4861 if (!addToSelection && !changeSelection && 4862 tree.getSelectionModel().getSelectionMode() != 4863 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4864 changeSelection = true; 4865 } 4866 4867 int rowCount; 4868 4869 if((rowCount = ui.getRowCount(tree)) > 0 && 4870 ui.treeSelectionModel != null) { 4871 Dimension maxSize = tree.getSize(); 4872 TreePath lead = ui.getLeadSelectionPath(); 4873 TreePath newPath; 4874 Rectangle visRect = tree.getVisibleRect(); 4875 4876 if(direction == -1) { 4877 // up. 4878 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4879 visRect.y); 4880 if(newPath.equals(lead)) { 4881 visRect.y = Math.max(0, visRect.y - visRect.height); 4882 newPath = tree.getClosestPathForLocation(visRect.x, 4883 visRect.y); 4884 } 4885 } 4886 else { 4887 // down 4888 visRect.y = Math.min(maxSize.height, visRect.y + 4889 visRect.height - 1); 4890 newPath = tree.getClosestPathForLocation(visRect.x, 4891 visRect.y); 4892 if(newPath.equals(lead)) { 4893 visRect.y = Math.min(maxSize.height, visRect.y + 4894 visRect.height - 1); 4895 newPath = tree.getClosestPathForLocation(visRect.x, 4896 visRect.y); 4897 } 4898 } 4899 Rectangle newRect = ui.getPathBounds(tree, newPath); 4900 if (newRect != null) { 4901 newRect.x = visRect.x; 4902 newRect.width = visRect.width; 4903 if(direction == -1) { 4904 newRect.height = visRect.height; 4905 } 4906 else { 4907 newRect.y -= (visRect.height - newRect.height); 4908 newRect.height = visRect.height; 4909 } 4910 4911 if(addToSelection) { 4912 ui.extendSelection(newPath); 4913 } 4914 else if(changeSelection) { 4915 tree.setSelectionPath(newPath); 4916 } 4917 else { 4918 ui.setLeadSelectionPath(newPath, true); 4919 } 4920 tree.scrollRectToVisible(newRect); 4921 } 4922 } 4923 } 4924 4925 private void home(JTree tree, final BasicTreeUI ui, int direction, 4926 boolean addToSelection, boolean changeSelection) { 4927 4928 // disable moving of lead unless in discontiguous mode 4929 if (!addToSelection && !changeSelection && 4930 tree.getSelectionModel().getSelectionMode() != 4931 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4932 changeSelection = true; 4933 } 4934 4935 final int rowCount = ui.getRowCount(tree); 4936 4937 if (rowCount > 0) { 4938 if(direction == -1) { 4939 ui.ensureRowsAreVisible(0, 0); 4940 if (addToSelection) { 4941 TreePath aPath = ui.getAnchorSelectionPath(); 4942 int aRow = (aPath == null) ? -1 : 4943 ui.getRowForPath(tree, aPath); 4944 4945 if (aRow == -1) { 4946 tree.setSelectionInterval(0, 0); 4947 } 4948 else { 4949 tree.setSelectionInterval(0, aRow); 4950 ui.setAnchorSelectionPath(aPath); 4951 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0)); 4952 } 4953 } 4954 else if(changeSelection) { 4955 tree.setSelectionInterval(0, 0); 4956 } 4957 else { 4958 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0), 4959 true); 4960 } 4961 } 4962 else { 4963 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); 4964 if (addToSelection) { 4965 TreePath aPath = ui.getAnchorSelectionPath(); 4966 int aRow = (aPath == null) ? -1 : 4967 ui.getRowForPath(tree, aPath); 4968 4969 if (aRow == -1) { 4970 tree.setSelectionInterval(rowCount - 1, 4971 rowCount -1); 4972 } 4973 else { 4974 tree.setSelectionInterval(aRow, rowCount - 1); 4975 ui.setAnchorSelectionPath(aPath); 4976 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4977 rowCount -1)); 4978 } 4979 } 4980 else if(changeSelection) { 4981 tree.setSelectionInterval(rowCount - 1, rowCount - 1); 4982 } 4983 else { 4984 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4985 rowCount - 1), true); 4986 } 4987 if (ui.isLargeModel()){ 4988 SwingUtilities.invokeLater(new Runnable() { 4989 public void run() { 4990 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); 4991 } 4992 }); 4993 } 4994 } 4995 } 4996 } 4997 } 4998 } // End of class BasicTreeUI