1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.basic; 27 28 import javax.swing.*; 29 import javax.swing.event.*; 30 import java.awt.*; 31 import java.awt.event.*; 32 import java.awt.datatransfer.*; 33 import java.beans.*; 34 import java.util.Enumeration; 35 import java.util.Hashtable; 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.Comparator; 39 import javax.swing.plaf.ComponentUI; 40 import javax.swing.plaf.UIResource; 41 import javax.swing.plaf.TreeUI; 42 import javax.swing.tree.*; 43 import javax.swing.text.Position; 44 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag; 45 import sun.awt.AWTAccessor; 46 import sun.swing.SwingUtilities2; 47 48 import sun.swing.DefaultLookup; 49 import sun.swing.UIAction; 50 51 /** 52 * The basic L&F for a hierarchical data structure. 53 * 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 static private final Actions SHARED_ACTION = new Actions(); 65 66 /** 67 * The collapsed icon. 68 */ 69 transient protected Icon collapsedIcon; 70 /** 71 * The expanded icon. 72 */ 73 transient protected 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 transient protected 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 transient protected 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 rect = tree.getPathBounds(path.pathByAddingChild( 1501 model.getChild(path.getLastPathComponent(), index))); 1502 } 1503 } 1504 1505 if (rect.y != 0) { 1506 rect.y--; 1507 } 1508 1509 if (!ltr) { 1510 rect.x = rect.x + rect.width - 100; 1511 } 1512 1513 rect.width = 100; 1514 rect.height = 2; 1515 1516 return rect; 1517 } 1518 1519 /** 1520 * Paints the horizontal part of the leg. The receiver should 1521 * NOT modify {@code clipBounds}, or {@code insets}.<p> 1522 * NOTE: {@code parentRow} can be -1 if the root is not visible. 1523 * 1524 * @param g a graphics context 1525 * @param clipBounds a clipped rectangle 1526 * @param insets insets 1527 * @param bounds a bounding rectangle 1528 * @param path a tree path 1529 * @param row a row 1530 * @param isExpanded {@code true} if the path is expanded 1531 * @param hasBeenExpanded {@code true} if the path has been expanded 1532 * @param isLeaf {@code true} if the path is leaf 1533 */ 1534 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, 1535 Insets insets, Rectangle bounds, 1536 TreePath path, int row, 1537 boolean isExpanded, 1538 boolean hasBeenExpanded, boolean 1539 isLeaf) { 1540 if (!paintLines) { 1541 return; 1542 } 1543 1544 // Don't paint the legs for the root'ish node if the 1545 int depth = path.getPathCount() - 1; 1546 if((depth == 0 || (depth == 1 && !isRootVisible())) && 1547 !getShowsRootHandles()) { 1548 return; 1549 } 1550 1551 int clipLeft = clipBounds.x; 1552 int clipRight = clipBounds.x + clipBounds.width; 1553 int clipTop = clipBounds.y; 1554 int clipBottom = clipBounds.y + clipBounds.height; 1555 int lineY = bounds.y + bounds.height / 2; 1556 1557 if (leftToRight) { 1558 int leftX = bounds.x - getRightChildIndent(); 1559 int nodeX = bounds.x - getHorizontalLegBuffer(); 1560 1561 if(lineY >= clipTop 1562 && lineY < clipBottom 1563 && nodeX >= clipLeft 1564 && leftX < clipRight 1565 && leftX < nodeX) { 1566 1567 g.setColor(getHashColor()); 1568 paintHorizontalLine(g, tree, lineY, leftX, nodeX - 1); 1569 } 1570 } else { 1571 int nodeX = bounds.x + bounds.width + getHorizontalLegBuffer(); 1572 int rightX = bounds.x + bounds.width + getRightChildIndent(); 1573 1574 if(lineY >= clipTop 1575 && lineY < clipBottom 1576 && rightX >= clipLeft 1577 && nodeX < clipRight 1578 && nodeX < rightX) { 1579 1580 g.setColor(getHashColor()); 1581 paintHorizontalLine(g, tree, lineY, nodeX, rightX - 1); 1582 } 1583 } 1584 } 1585 1586 /** 1587 * Paints the vertical part of the leg. The receiver should 1588 * NOT modify {@code clipBounds}, {@code insets}. 1589 * 1590 * @param g a graphics context 1591 * @param clipBounds a clipped rectangle 1592 * @param insets insets 1593 * @param path a tree path 1594 */ 1595 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, 1596 Insets insets, TreePath path) { 1597 if (!paintLines) { 1598 return; 1599 } 1600 1601 int depth = path.getPathCount() - 1; 1602 if (depth == 0 && !getShowsRootHandles() && !isRootVisible()) { 1603 return; 1604 } 1605 int lineX = getRowX(-1, depth + 1); 1606 if (leftToRight) { 1607 lineX = lineX - getRightChildIndent() + insets.left; 1608 } 1609 else { 1610 lineX = tree.getWidth() - lineX - insets.right + 1611 getRightChildIndent() - 1; 1612 } 1613 int clipLeft = clipBounds.x; 1614 int clipRight = clipBounds.x + (clipBounds.width - 1); 1615 1616 if (lineX >= clipLeft && lineX <= clipRight) { 1617 int clipTop = clipBounds.y; 1618 int clipBottom = clipBounds.y + clipBounds.height; 1619 Rectangle parentBounds = getPathBounds(tree, path); 1620 Rectangle lastChildBounds = getPathBounds(tree, 1621 getLastChildPath(path)); 1622 1623 if(lastChildBounds == null) 1624 // This shouldn't happen, but if the model is modified 1625 // in another thread it is possible for this to happen. 1626 // Swing isn't multithreaded, but I'll add this check in 1627 // anyway. 1628 return; 1629 1630 int top; 1631 1632 if(parentBounds == null) { 1633 top = Math.max(insets.top + getVerticalLegBuffer(), 1634 clipTop); 1635 } 1636 else 1637 top = Math.max(parentBounds.y + parentBounds.height + 1638 getVerticalLegBuffer(), clipTop); 1639 if(depth == 0 && !isRootVisible()) { 1640 TreeModel model = getModel(); 1641 1642 if(model != null) { 1643 Object root = model.getRoot(); 1644 1645 if(model.getChildCount(root) > 0) { 1646 parentBounds = getPathBounds(tree, path. 1647 pathByAddingChild(model.getChild(root, 0))); 1648 if(parentBounds != null) 1649 top = Math.max(insets.top + getVerticalLegBuffer(), 1650 parentBounds.y + 1651 parentBounds.height / 2); 1652 } 1653 } 1654 } 1655 1656 int bottom = Math.min(lastChildBounds.y + 1657 (lastChildBounds.height / 2), clipBottom); 1658 1659 if (top <= bottom) { 1660 g.setColor(getHashColor()); 1661 paintVerticalLine(g, tree, lineX, top, bottom); 1662 } 1663 } 1664 } 1665 1666 /** 1667 * Paints the expand (toggle) part of a row. The receiver should 1668 * NOT modify {@code clipBounds}, or {@code insets}. 1669 * 1670 * @param g a graphics context 1671 * @param clipBounds a clipped rectangle 1672 * @param insets insets 1673 * @param bounds a bounding rectangle 1674 * @param path a tree path 1675 * @param row a row 1676 * @param isExpanded {@code true} if the path is expanded 1677 * @param hasBeenExpanded {@code true} if the path has been expanded 1678 * @param isLeaf {@code true} if the row is leaf 1679 */ 1680 protected void paintExpandControl(Graphics g, 1681 Rectangle clipBounds, Insets insets, 1682 Rectangle bounds, TreePath path, 1683 int row, boolean isExpanded, 1684 boolean hasBeenExpanded, 1685 boolean isLeaf) { 1686 Object value = path.getLastPathComponent(); 1687 1688 // Draw icons if not a leaf and either hasn't been loaded, 1689 // or the model child count is > 0. 1690 if (!isLeaf && (!hasBeenExpanded || 1691 treeModel.getChildCount(value) > 0)) { 1692 int middleXOfKnob; 1693 if (leftToRight) { 1694 middleXOfKnob = bounds.x - getRightChildIndent() + 1; 1695 } else { 1696 middleXOfKnob = bounds.x + bounds.width + getRightChildIndent() - 1; 1697 } 1698 int middleYOfKnob = bounds.y + (bounds.height / 2); 1699 1700 if (isExpanded) { 1701 Icon expandedIcon = getExpandedIcon(); 1702 if(expandedIcon != null) 1703 drawCentered(tree, g, expandedIcon, middleXOfKnob, 1704 middleYOfKnob ); 1705 } 1706 else { 1707 Icon collapsedIcon = getCollapsedIcon(); 1708 if(collapsedIcon != null) 1709 drawCentered(tree, g, collapsedIcon, middleXOfKnob, 1710 middleYOfKnob); 1711 } 1712 } 1713 } 1714 1715 /** 1716 * Paints the renderer part of a row. The receiver should 1717 * NOT modify {@code clipBounds}, or {@code insets}. 1718 * 1719 * @param g a graphics context 1720 * @param clipBounds a clipped rectangle 1721 * @param insets insets 1722 * @param bounds a bounding rectangle 1723 * @param path a tree path 1724 * @param row a row 1725 * @param isExpanded {@code true} if the path is expanded 1726 * @param hasBeenExpanded {@code true} if the path has been expanded 1727 * @param isLeaf {@code true} if the path is leaf 1728 */ 1729 protected void paintRow(Graphics g, Rectangle clipBounds, 1730 Insets insets, Rectangle bounds, TreePath path, 1731 int row, boolean isExpanded, 1732 boolean hasBeenExpanded, boolean isLeaf) { 1733 // Don't paint the renderer if editing this row. 1734 if(editingComponent != null && editingRow == row) 1735 return; 1736 1737 int leadIndex; 1738 1739 if(tree.hasFocus()) { 1740 leadIndex = getLeadSelectionRow(); 1741 } 1742 else 1743 leadIndex = -1; 1744 1745 Component component; 1746 1747 component = currentCellRenderer.getTreeCellRendererComponent 1748 (tree, path.getLastPathComponent(), 1749 tree.isRowSelected(row), isExpanded, isLeaf, row, 1750 (leadIndex == row)); 1751 1752 rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y, 1753 bounds.width, bounds.height, true); 1754 } 1755 1756 /** 1757 * Returns {@code true} if the expand (toggle) control should be drawn for 1758 * the specified row. 1759 * 1760 * @param path a tree path 1761 * @param row a row 1762 * @param isExpanded {@code true} if the path is expanded 1763 * @param hasBeenExpanded {@code true} if the path has been expanded 1764 * @param isLeaf {@code true} if the row is leaf 1765 * @return {@code true} if the expand (toggle) control should be drawn 1766 * for the specified row 1767 */ 1768 protected boolean shouldPaintExpandControl(TreePath path, int row, 1769 boolean isExpanded, 1770 boolean hasBeenExpanded, 1771 boolean isLeaf) { 1772 if(isLeaf) 1773 return false; 1774 1775 int depth = path.getPathCount() - 1; 1776 1777 if((depth == 0 || (depth == 1 && !isRootVisible())) && 1778 !getShowsRootHandles()) 1779 return false; 1780 return true; 1781 } 1782 1783 /** 1784 * Paints a vertical line. 1785 * 1786 * @param g a graphics context 1787 * @param c a component 1788 * @param x an X coordinate 1789 * @param top an Y1 coordinate 1790 * @param bottom an Y2 coordinate 1791 */ 1792 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, 1793 int bottom) { 1794 if (lineTypeDashed) { 1795 drawDashedVerticalLine(g, x, top, bottom); 1796 } else { 1797 g.drawLine(x, top, x, bottom); 1798 } 1799 } 1800 1801 /** 1802 * Paints a horizontal line. 1803 * 1804 * @param g a graphics context 1805 * @param c a component 1806 * @param y an Y coordinate 1807 * @param left an X1 coordinate 1808 * @param right an X2 coordinate 1809 */ 1810 protected void paintHorizontalLine(Graphics g, JComponent c, int y, 1811 int left, int right) { 1812 if (lineTypeDashed) { 1813 drawDashedHorizontalLine(g, y, left, right); 1814 } else { 1815 g.drawLine(left, y, right, y); 1816 } 1817 } 1818 1819 /** 1820 * The vertical element of legs between nodes starts at the bottom of the 1821 * parent node by default. This method makes the leg start below that. 1822 * 1823 * @return the vertical leg buffer 1824 */ 1825 protected int getVerticalLegBuffer() { 1826 return 0; 1827 } 1828 1829 /** 1830 * The horizontal element of legs between nodes starts at the 1831 * right of the left-hand side of the child node by default. This 1832 * method makes the leg end before that. 1833 * 1834 * @return the horizontal leg buffer 1835 */ 1836 protected int getHorizontalLegBuffer() { 1837 return 0; 1838 } 1839 1840 private int findCenteredX(int x, int iconWidth) { 1841 return leftToRight 1842 ? x - (int)Math.ceil(iconWidth / 2.0) 1843 : x - (int)Math.floor(iconWidth / 2.0); 1844 } 1845 1846 // 1847 // Generic painting methods 1848 // 1849 1850 /** 1851 * Draws the {@code icon} centered at (x,y). 1852 * 1853 * @param c a component 1854 * @param graphics a graphics context 1855 * @param icon an icon 1856 * @param x an X coordinate 1857 * @param y an Y coordinate 1858 */ 1859 protected void drawCentered(Component c, Graphics graphics, Icon icon, 1860 int x, int y) { 1861 icon.paintIcon(c, graphics, 1862 findCenteredX(x, icon.getIconWidth()), 1863 y - icon.getIconHeight() / 2); 1864 } 1865 1866 /** 1867 * Draws a horizontal dashed line. It is assumed {@code x1} <= {@code x2}. 1868 * If {@code x1} is greater than {@code x2}, the method draws nothing. 1869 * 1870 * @param g an instance of {@code Graphics} 1871 * @param y an Y coordinate 1872 * @param x1 an X1 coordinate 1873 * @param x2 an X2 coordinate 1874 */ 1875 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2) { 1876 // Drawing only even coordinates helps join line segments so they 1877 // appear as one line. This can be defeated by translating the 1878 // Graphics by an odd amount. 1879 drawDashedLine(g, y, x1, x2, false); 1880 } 1881 1882 /** 1883 * Draws a vertical dashed line. It is assumed {@code y1} <= {@code y2}. 1884 * If {@code y1} is greater than {@code y2}, the method draws nothing. 1885 * 1886 * @param g an instance of {@code Graphics} 1887 * @param x an X coordinate 1888 * @param y1 an Y1 coordinate 1889 * @param y2 an Y2 coordinate 1890 */ 1891 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) { 1892 // Drawing only even coordinates helps join line segments so they 1893 // appear as one line. This can be defeated by translating the 1894 // Graphics by an odd amount. 1895 drawDashedLine(g, x, y1, y2, true); 1896 } 1897 1898 private void drawDashedLine(Graphics g, int v, int v1, int v2, boolean isVertical) { 1899 if (v1 >= v2) { 1900 return; 1901 } 1902 v1 += (v1 % 2); 1903 Graphics2D g2d = (Graphics2D) g; 1904 Stroke oldStroke = g2d.getStroke(); 1905 1906 BasicStroke dashedStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, 1907 BasicStroke.JOIN_ROUND, 0, new float[]{1}, 0); 1908 g2d.setStroke(dashedStroke); 1909 if (isVertical) { 1910 g2d.drawLine(v, v1, v, v2); 1911 } else { 1912 g2d.drawLine(v1, v, v2, v); 1913 } 1914 1915 g2d.setStroke(oldStroke); 1916 } 1917 // 1918 // Various local methods 1919 // 1920 1921 /** 1922 * Returns the location, along the x-axis, to render a particular row 1923 * at. The return value does not include any Insets specified on the JTree. 1924 * This does not check for the validity of the row or depth, it is assumed 1925 * to be correct and will not throw an Exception if the row or depth 1926 * doesn't match that of the tree. 1927 * 1928 * @param row Row to return x location for 1929 * @param depth Depth of the row 1930 * @return amount to indent the given row. 1931 * @since 1.5 1932 */ 1933 protected int getRowX(int row, int depth) { 1934 return totalChildIndent * (depth + depthOffset); 1935 } 1936 1937 /** 1938 * Makes all the nodes that are expanded in JTree expanded in LayoutCache. 1939 * This invokes updateExpandedDescendants with the root path. 1940 */ 1941 protected void updateLayoutCacheExpandedNodes() { 1942 if(treeModel != null && treeModel.getRoot() != null) 1943 updateExpandedDescendants(new TreePath(treeModel.getRoot())); 1944 } 1945 1946 private void updateLayoutCacheExpandedNodesIfNecessary() { 1947 if (treeModel != null && treeModel.getRoot() != null) { 1948 TreePath rootPath = new TreePath(treeModel.getRoot()); 1949 if (tree.isExpanded(rootPath)) { 1950 updateLayoutCacheExpandedNodes(); 1951 } else { 1952 treeState.setExpandedState(rootPath, false); 1953 } 1954 } 1955 } 1956 1957 /** 1958 * Updates the expanded state of all the descendants of {@code path} 1959 * by getting the expanded descendants from the tree and forwarding 1960 * to the tree state. 1961 * 1962 * @param path a tree path 1963 */ 1964 protected void updateExpandedDescendants(TreePath path) { 1965 completeEditing(); 1966 if(treeState != null) { 1967 treeState.setExpandedState(path, true); 1968 1969 Enumeration<?> descendants = tree.getExpandedDescendants(path); 1970 1971 if(descendants != null) { 1972 while(descendants.hasMoreElements()) { 1973 path = (TreePath)descendants.nextElement(); 1974 treeState.setExpandedState(path, true); 1975 } 1976 } 1977 updateLeadSelectionRow(); 1978 updateSize(); 1979 } 1980 } 1981 1982 /** 1983 * Returns a path to the last child of {@code parent}. 1984 * 1985 * @param parent a tree path 1986 * @return a path to the last child of {@code parent} 1987 */ 1988 protected TreePath getLastChildPath(TreePath parent) { 1989 if(treeModel != null) { 1990 int childCount = treeModel.getChildCount 1991 (parent.getLastPathComponent()); 1992 1993 if(childCount > 0) 1994 return parent.pathByAddingChild(treeModel.getChild 1995 (parent.getLastPathComponent(), childCount - 1)); 1996 } 1997 return null; 1998 } 1999 2000 /** 2001 * Updates how much each depth should be offset by. 2002 */ 2003 protected void updateDepthOffset() { 2004 if(isRootVisible()) { 2005 if(getShowsRootHandles()) 2006 depthOffset = 1; 2007 else 2008 depthOffset = 0; 2009 } 2010 else if(!getShowsRootHandles()) 2011 depthOffset = -1; 2012 else 2013 depthOffset = 0; 2014 } 2015 2016 /** 2017 * Updates the cellEditor based on the editability of the JTree that 2018 * we're contained in. If the tree is editable but doesn't have a 2019 * cellEditor, a basic one will be used. 2020 */ 2021 protected void updateCellEditor() { 2022 TreeCellEditor newEditor; 2023 2024 completeEditing(); 2025 if(tree == null) 2026 newEditor = null; 2027 else { 2028 if(tree.isEditable()) { 2029 newEditor = tree.getCellEditor(); 2030 if(newEditor == null) { 2031 newEditor = createDefaultCellEditor(); 2032 if(newEditor != null) { 2033 tree.setCellEditor(newEditor); 2034 createdCellEditor = true; 2035 } 2036 } 2037 } 2038 else 2039 newEditor = null; 2040 } 2041 if(newEditor != cellEditor) { 2042 if(cellEditor != null && cellEditorListener != null) 2043 cellEditor.removeCellEditorListener(cellEditorListener); 2044 cellEditor = newEditor; 2045 if(cellEditorListener == null) 2046 cellEditorListener = createCellEditorListener(); 2047 if(newEditor != null && cellEditorListener != null) 2048 newEditor.addCellEditorListener(cellEditorListener); 2049 createdCellEditor = false; 2050 } 2051 } 2052 2053 /** 2054 * Messaged from the tree we're in when the renderer has changed. 2055 */ 2056 protected void updateRenderer() { 2057 if(tree != null) { 2058 TreeCellRenderer newCellRenderer; 2059 2060 newCellRenderer = tree.getCellRenderer(); 2061 if(newCellRenderer == null) { 2062 tree.setCellRenderer(createDefaultCellRenderer()); 2063 createdRenderer = true; 2064 } 2065 else { 2066 createdRenderer = false; 2067 currentCellRenderer = newCellRenderer; 2068 if(createdCellEditor) { 2069 tree.setCellEditor(null); 2070 } 2071 } 2072 } 2073 else { 2074 createdRenderer = false; 2075 currentCellRenderer = null; 2076 } 2077 updateCellEditor(); 2078 } 2079 2080 /** 2081 * Resets the TreeState instance based on the tree we're providing the 2082 * look and feel for. 2083 */ 2084 protected void configureLayoutCache() { 2085 if(treeState != null && tree != null) { 2086 if(nodeDimensions == null) 2087 nodeDimensions = createNodeDimensions(); 2088 treeState.setNodeDimensions(nodeDimensions); 2089 treeState.setRootVisible(tree.isRootVisible()); 2090 treeState.setRowHeight(tree.getRowHeight()); 2091 treeState.setSelectionModel(getSelectionModel()); 2092 // Only do this if necessary, may loss state if call with 2093 // same model as it currently has. 2094 if(treeState.getModel() != tree.getModel()) 2095 treeState.setModel(tree.getModel()); 2096 updateLayoutCacheExpandedNodesIfNecessary(); 2097 // Create a listener to update preferred size when bounds 2098 // changes, if necessary. 2099 if(isLargeModel()) { 2100 if(componentListener == null) { 2101 componentListener = createComponentListener(); 2102 if(componentListener != null) 2103 tree.addComponentListener(componentListener); 2104 } 2105 } 2106 else if(componentListener != null) { 2107 tree.removeComponentListener(componentListener); 2108 componentListener = null; 2109 } 2110 } 2111 else if(componentListener != null) { 2112 tree.removeComponentListener(componentListener); 2113 componentListener = null; 2114 } 2115 } 2116 2117 /** 2118 * Marks the cached size as being invalid, and messages the 2119 * tree with <code>treeDidChange</code>. 2120 */ 2121 protected void updateSize() { 2122 validCachedPreferredSize = false; 2123 tree.treeDidChange(); 2124 } 2125 2126 private void updateSize0() { 2127 validCachedPreferredSize = false; 2128 tree.revalidate(); 2129 } 2130 2131 /** 2132 * Updates the <code>preferredSize</code> instance variable, 2133 * which is returned from <code>getPreferredSize()</code>.<p> 2134 * For left to right orientations, the size is determined from the 2135 * current AbstractLayoutCache. For RTL orientations, the preferred size 2136 * becomes the width minus the minimum x position. 2137 */ 2138 protected void updateCachedPreferredSize() { 2139 if(treeState != null) { 2140 Insets i = tree.getInsets(); 2141 2142 if(isLargeModel()) { 2143 Rectangle visRect = tree.getVisibleRect(); 2144 2145 if (visRect.x == 0 && visRect.y == 0 && 2146 visRect.width == 0 && visRect.height == 0 && 2147 tree.getVisibleRowCount() > 0) { 2148 // The tree doesn't have a valid bounds yet. Calculate 2149 // based on visible row count. 2150 visRect.width = 1; 2151 visRect.height = tree.getRowHeight() * 2152 tree.getVisibleRowCount(); 2153 } else { 2154 visRect.x -= i.left; 2155 visRect.y -= i.top; 2156 } 2157 // we should consider a non-visible area above 2158 Component component = SwingUtilities.getUnwrappedParent(tree); 2159 if (component instanceof JViewport) { 2160 component = component.getParent(); 2161 if (component instanceof JScrollPane) { 2162 JScrollPane pane = (JScrollPane) component; 2163 JScrollBar bar = pane.getHorizontalScrollBar(); 2164 if ((bar != null) && bar.isVisible()) { 2165 int height = bar.getHeight(); 2166 visRect.y -= height; 2167 visRect.height += height; 2168 } 2169 } 2170 } 2171 preferredSize.width = treeState.getPreferredWidth(visRect); 2172 } 2173 else { 2174 preferredSize.width = treeState.getPreferredWidth(null); 2175 } 2176 preferredSize.height = treeState.getPreferredHeight(); 2177 preferredSize.width += i.left + i.right; 2178 preferredSize.height += i.top + i.bottom; 2179 } 2180 validCachedPreferredSize = true; 2181 } 2182 2183 /** 2184 * Messaged from the {@code VisibleTreeNode} after it has been expanded. 2185 * 2186 * @param path a tree path 2187 */ 2188 protected void pathWasExpanded(TreePath path) { 2189 if(tree != null) { 2190 tree.fireTreeExpanded(path); 2191 } 2192 } 2193 2194 /** 2195 * Messaged from the {@code VisibleTreeNode} after it has collapsed. 2196 * 2197 * @param path a tree path 2198 */ 2199 protected void pathWasCollapsed(TreePath path) { 2200 if(tree != null) { 2201 tree.fireTreeCollapsed(path); 2202 } 2203 } 2204 2205 /** 2206 * Ensures that the rows identified by {@code beginRow} through 2207 * {@code endRow} are visible. 2208 * 2209 * @param beginRow the begin row 2210 * @param endRow the end row 2211 */ 2212 protected void ensureRowsAreVisible(int beginRow, int endRow) { 2213 if(tree != null && beginRow >= 0 && endRow < getRowCount(tree)) { 2214 boolean scrollVert = DefaultLookup.getBoolean(tree, this, 2215 "Tree.scrollsHorizontallyAndVertically", false); 2216 if(beginRow == endRow) { 2217 Rectangle scrollBounds = getPathBounds(tree, getPathForRow 2218 (tree, beginRow)); 2219 2220 if(scrollBounds != null) { 2221 if (!scrollVert) { 2222 scrollBounds.x = tree.getVisibleRect().x; 2223 scrollBounds.width = 1; 2224 } 2225 tree.scrollRectToVisible(scrollBounds); 2226 } 2227 } 2228 else { 2229 Rectangle beginRect = getPathBounds(tree, getPathForRow 2230 (tree, beginRow)); 2231 if (beginRect != null) { 2232 Rectangle visRect = tree.getVisibleRect(); 2233 Rectangle testRect = beginRect; 2234 int beginY = beginRect.y; 2235 int maxY = beginY + visRect.height; 2236 2237 for(int counter = beginRow + 1; counter <= endRow; counter++) { 2238 testRect = getPathBounds(tree, 2239 getPathForRow(tree, counter)); 2240 if (testRect == null) { 2241 return; 2242 } 2243 if((testRect.y + testRect.height) > maxY) 2244 counter = endRow; 2245 } 2246 tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1, 2247 testRect.y + testRect.height- 2248 beginY)); 2249 } 2250 } 2251 } 2252 } 2253 2254 /** 2255 * Sets the preferred minimum size. 2256 * 2257 * @param newSize the new preferred size 2258 */ 2259 public void setPreferredMinSize(Dimension newSize) { 2260 preferredMinSize = newSize; 2261 } 2262 2263 /** 2264 * Returns the minimum preferred size. 2265 * 2266 * @return the minimum preferred size 2267 */ 2268 public Dimension getPreferredMinSize() { 2269 if(preferredMinSize == null) 2270 return null; 2271 return new Dimension(preferredMinSize); 2272 } 2273 2274 /** 2275 * Returns the preferred size to properly display the tree, 2276 * this is a cover method for {@code getPreferredSize(c, true)}. 2277 * 2278 * @param c a component 2279 * @return the preferred size to represent the tree in the component 2280 */ 2281 public Dimension getPreferredSize(JComponent c) { 2282 return getPreferredSize(c, true); 2283 } 2284 2285 /** 2286 * Returns the preferred size to represent the tree in 2287 * <I>c</I>. If <I>checkConsistency</I> is {@code true} 2288 * <b>checkConsistency</b> is messaged first. 2289 * 2290 * @param c a component 2291 * @param checkConsistency if {@code true} consistency is checked 2292 * @return the preferred size to represent the tree in the component 2293 */ 2294 public Dimension getPreferredSize(JComponent c, 2295 boolean checkConsistency) { 2296 Dimension pSize = this.getPreferredMinSize(); 2297 2298 if(!validCachedPreferredSize) 2299 updateCachedPreferredSize(); 2300 if(tree != null) { 2301 if(pSize != null) 2302 return new Dimension(Math.max(pSize.width, 2303 preferredSize.width), 2304 Math.max(pSize.height, preferredSize.height)); 2305 return new Dimension(preferredSize.width, preferredSize.height); 2306 } 2307 else if(pSize != null) 2308 return pSize; 2309 else 2310 return new Dimension(0, 0); 2311 } 2312 2313 /** 2314 * Returns the minimum size for this component. Which will be 2315 * the min preferred size or 0, 0. 2316 */ 2317 public Dimension getMinimumSize(JComponent c) { 2318 if(this.getPreferredMinSize() != null) 2319 return this.getPreferredMinSize(); 2320 return new Dimension(0, 0); 2321 } 2322 2323 /** 2324 * Returns the maximum size for this component, which will be the 2325 * preferred size if the instance is currently in a JTree, or 0, 0. 2326 */ 2327 public Dimension getMaximumSize(JComponent c) { 2328 if(tree != null) 2329 return getPreferredSize(tree); 2330 if(this.getPreferredMinSize() != null) 2331 return this.getPreferredMinSize(); 2332 return new Dimension(0, 0); 2333 } 2334 2335 2336 /** 2337 * Messages to stop the editing session. If the UI the receiver 2338 * is providing the look and feel for returns true from 2339 * <code>getInvokesStopCellEditing</code>, stopCellEditing will 2340 * invoked on the current editor. Then completeEditing will 2341 * be messaged with false, true, false to cancel any lingering 2342 * editing. 2343 */ 2344 protected void completeEditing() { 2345 /* If should invoke stopCellEditing, try that */ 2346 if(tree.getInvokesStopCellEditing() && 2347 stopEditingInCompleteEditing && editingComponent != null) { 2348 cellEditor.stopCellEditing(); 2349 } 2350 /* Invoke cancelCellEditing, this will do nothing if stopCellEditing 2351 was successful. */ 2352 completeEditing(false, true, false); 2353 } 2354 2355 /** 2356 * Stops the editing session. If {@code messageStop} is {@code true} the editor 2357 * is messaged with {@code stopEditing}, if {@code messageCancel} 2358 * is {@code true} the editor is messaged with {@code cancelEditing}. 2359 * If {@code messageTree} is {@code true} the {@code treeModel} is messaged 2360 * with {@code valueForPathChanged}. 2361 * 2362 * @param messageStop message to stop editing 2363 * @param messageCancel message to cancel editing 2364 * @param messageTree message to tree 2365 */ 2366 @SuppressWarnings("deprecation") 2367 protected void completeEditing(boolean messageStop, 2368 boolean messageCancel, 2369 boolean messageTree) { 2370 if(stopEditingInCompleteEditing && editingComponent != null) { 2371 Component oldComponent = editingComponent; 2372 TreePath oldPath = editingPath; 2373 TreeCellEditor oldEditor = cellEditor; 2374 Object newValue = oldEditor.getCellEditorValue(); 2375 Rectangle editingBounds = getPathBounds(tree, 2376 editingPath); 2377 boolean requestFocus = (tree != null && 2378 (tree.hasFocus() || SwingUtilities. 2379 findFocusOwner(editingComponent) != null)); 2380 2381 editingComponent = null; 2382 editingPath = null; 2383 if(messageStop) 2384 oldEditor.stopCellEditing(); 2385 else if(messageCancel) 2386 oldEditor.cancelCellEditing(); 2387 tree.remove(oldComponent); 2388 if(editorHasDifferentSize) { 2389 treeState.invalidatePathBounds(oldPath); 2390 updateSize(); 2391 } 2392 else if (editingBounds != null) { 2393 editingBounds.x = 0; 2394 editingBounds.width = tree.getSize().width; 2395 tree.repaint(editingBounds); 2396 } 2397 if(requestFocus) 2398 tree.requestFocus(); 2399 if(messageTree) 2400 treeModel.valueForPathChanged(oldPath, newValue); 2401 } 2402 } 2403 2404 // cover method for startEditing that allows us to pass extra 2405 // information into that method via a class variable 2406 private boolean startEditingOnRelease(TreePath path, 2407 MouseEvent event, 2408 MouseEvent releaseEvent) { 2409 this.releaseEvent = releaseEvent; 2410 try { 2411 return startEditing(path, event); 2412 } finally { 2413 this.releaseEvent = null; 2414 } 2415 } 2416 2417 /** 2418 * Will start editing for node if there is a {@code cellEditor} and 2419 * {@code shouldSelectCell} returns {@code true}.<p> 2420 * This assumes that path is valid and visible. 2421 * 2422 * @param path a tree path 2423 * @param event a mouse event 2424 * @return {@code true} if the editing is successful 2425 */ 2426 protected boolean startEditing(TreePath path, MouseEvent event) { 2427 if (isEditing(tree) && tree.getInvokesStopCellEditing() && 2428 !stopEditing(tree)) { 2429 return false; 2430 } 2431 completeEditing(); 2432 if(cellEditor != null && tree.isPathEditable(path)) { 2433 int row = getRowForPath(tree, path); 2434 2435 if(cellEditor.isCellEditable(event)) { 2436 editingComponent = cellEditor.getTreeCellEditorComponent 2437 (tree, path.getLastPathComponent(), 2438 tree.isPathSelected(path), tree.isExpanded(path), 2439 treeModel.isLeaf(path.getLastPathComponent()), row); 2440 Rectangle nodeBounds = getPathBounds(tree, path); 2441 if (nodeBounds == null) { 2442 return false; 2443 } 2444 2445 editingRow = row; 2446 2447 Dimension editorSize = editingComponent.getPreferredSize(); 2448 2449 // Only allow odd heights if explicitly set. 2450 if(editorSize.height != nodeBounds.height && 2451 getRowHeight() > 0) 2452 editorSize.height = getRowHeight(); 2453 2454 if(editorSize.width != nodeBounds.width || 2455 editorSize.height != nodeBounds.height) { 2456 // Editor wants different width or height, invalidate 2457 // treeState and relayout. 2458 editorHasDifferentSize = true; 2459 treeState.invalidatePathBounds(path); 2460 updateSize(); 2461 // To make sure x/y are updated correctly, fetch 2462 // the bounds again. 2463 nodeBounds = getPathBounds(tree, path); 2464 if (nodeBounds == null) { 2465 return false; 2466 } 2467 } 2468 else 2469 editorHasDifferentSize = false; 2470 tree.add(editingComponent); 2471 editingComponent.setBounds(nodeBounds.x, nodeBounds.y, 2472 nodeBounds.width, 2473 nodeBounds.height); 2474 editingPath = path; 2475 AWTAccessor.getComponentAccessor().revalidateSynchronously(editingComponent); 2476 editingComponent.repaint(); 2477 if(cellEditor.shouldSelectCell(event)) { 2478 stopEditingInCompleteEditing = false; 2479 tree.setSelectionRow(row); 2480 stopEditingInCompleteEditing = true; 2481 } 2482 2483 Component focusedComponent = SwingUtilities2. 2484 compositeRequestFocus(editingComponent); 2485 boolean selectAll = true; 2486 2487 if(event != null) { 2488 /* Find the component that will get forwarded all the 2489 mouse events until mouseReleased. */ 2490 Point componentPoint = SwingUtilities.convertPoint 2491 (tree, new Point(event.getX(), event.getY()), 2492 editingComponent); 2493 2494 /* Create an instance of BasicTreeMouseListener to handle 2495 passing the mouse/motion events to the necessary 2496 component. */ 2497 // We really want similar behavior to getMouseEventTarget, 2498 // but it is package private. 2499 Component activeComponent = SwingUtilities. 2500 getDeepestComponentAt(editingComponent, 2501 componentPoint.x, componentPoint.y); 2502 if (activeComponent != null) { 2503 MouseInputHandler handler = 2504 new MouseInputHandler(tree, activeComponent, 2505 event, focusedComponent); 2506 2507 if (releaseEvent != null) { 2508 handler.mouseReleased(releaseEvent); 2509 } 2510 2511 selectAll = false; 2512 } 2513 } 2514 if (selectAll && focusedComponent instanceof JTextField) { 2515 ((JTextField)focusedComponent).selectAll(); 2516 } 2517 return true; 2518 } 2519 else 2520 editingComponent = null; 2521 } 2522 return false; 2523 } 2524 2525 // 2526 // Following are primarily for handling mouse events. 2527 // 2528 2529 /** 2530 * If the {@code mouseX} and {@code mouseY} are in the 2531 * expand/collapse region of the {@code row}, this will toggle 2532 * the row. 2533 * 2534 * @param path a tree path 2535 * @param mouseX an X coordinate 2536 * @param mouseY an Y coordinate 2537 */ 2538 protected void checkForClickInExpandControl(TreePath path, 2539 int mouseX, int mouseY) { 2540 if (isLocationInExpandControl(path, mouseX, mouseY)) { 2541 handleExpandControlClick(path, mouseX, mouseY); 2542 } 2543 } 2544 2545 /** 2546 * Returns {@code true} if {@code mouseX} and {@code mouseY} fall 2547 * in the area of row that is used to expand/collapse the node and 2548 * the node at {@code row} does not represent a leaf. 2549 * 2550 * @param path a tree path 2551 * @param mouseX an X coordinate 2552 * @param mouseY an Y coordinate 2553 * @return {@code true} if the mouse cursor fall in the area of row that 2554 * is used to expand/collapse the node and the node is not a leaf. 2555 */ 2556 protected boolean isLocationInExpandControl(TreePath path, 2557 int mouseX, int mouseY) { 2558 if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){ 2559 int boxWidth; 2560 Insets i = tree.getInsets(); 2561 2562 if(getExpandedIcon() != null) 2563 boxWidth = getExpandedIcon().getIconWidth(); 2564 else 2565 boxWidth = 8; 2566 2567 int boxLeftX = getRowX(tree.getRowForPath(path), 2568 path.getPathCount() - 1); 2569 2570 if (leftToRight) { 2571 boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1; 2572 } else { 2573 boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1; 2574 } 2575 2576 boxLeftX = findCenteredX(boxLeftX, boxWidth); 2577 2578 return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth)); 2579 } 2580 return false; 2581 } 2582 2583 /** 2584 * Messaged when the user clicks the particular row, this invokes 2585 * {@code toggleExpandState}. 2586 * 2587 * @param path a tree path 2588 * @param mouseX an X coordinate 2589 * @param mouseY an Y coordinate 2590 */ 2591 protected void handleExpandControlClick(TreePath path, int mouseX, 2592 int mouseY) { 2593 toggleExpandState(path); 2594 } 2595 2596 /** 2597 * Expands path if it is not expanded, or collapses row if it is expanded. 2598 * If expanding a path and {@code JTree} scrolls on expand, 2599 * {@code ensureRowsAreVisible} is invoked to scroll as many of the children 2600 * to visible as possible (tries to scroll to last visible descendant of path). 2601 * 2602 * @param path a tree path 2603 */ 2604 protected void toggleExpandState(TreePath path) { 2605 if(!tree.isExpanded(path)) { 2606 int row = getRowForPath(tree, path); 2607 2608 tree.expandPath(path); 2609 updateSize(); 2610 if(row != -1) { 2611 if(tree.getScrollsOnExpand()) 2612 ensureRowsAreVisible(row, row + treeState. 2613 getVisibleChildCount(path)); 2614 else 2615 ensureRowsAreVisible(row, row); 2616 } 2617 } 2618 else { 2619 tree.collapsePath(path); 2620 updateSize(); 2621 } 2622 } 2623 2624 /** 2625 * Returning {@code true} signifies a mouse event on the node should toggle 2626 * the selection of only the row under mouse. 2627 * 2628 * @param event a mouse event 2629 * @return {@code true} if a mouse event on the node should toggle the selection 2630 */ 2631 protected boolean isToggleSelectionEvent(MouseEvent event) { 2632 return (SwingUtilities.isLeftMouseButton(event) && 2633 BasicGraphicsUtils.isMenuShortcutKeyDown(event)); 2634 } 2635 2636 /** 2637 * Returning {@code true} signifies a mouse event on the node should select 2638 * from the anchor point. 2639 * 2640 * @param event a mouse event 2641 * @return {@code true} if a mouse event on the node should select 2642 * from the anchor point 2643 */ 2644 protected boolean isMultiSelectEvent(MouseEvent event) { 2645 return (SwingUtilities.isLeftMouseButton(event) && 2646 event.isShiftDown()); 2647 } 2648 2649 /** 2650 * Returning {@code true} indicates the row under the mouse should be toggled 2651 * based on the event. This is invoked after {@code checkForClickInExpandControl}, 2652 * implying the location is not in the expand (toggle) control. 2653 * 2654 * @param event a mouse event 2655 * @return {@code true} if the row under the mouse should be toggled 2656 */ 2657 protected boolean isToggleEvent(MouseEvent event) { 2658 if(!SwingUtilities.isLeftMouseButton(event)) { 2659 return false; 2660 } 2661 int clickCount = tree.getToggleClickCount(); 2662 2663 if(clickCount <= 0) { 2664 return false; 2665 } 2666 return ((event.getClickCount() % clickCount) == 0); 2667 } 2668 2669 /** 2670 * Messaged to update the selection based on a {@code MouseEvent} over a 2671 * particular row. If the event is a toggle selection event, the 2672 * row is either selected, or deselected. If the event identifies 2673 * a multi selection event, the selection is updated from the 2674 * anchor point. Otherwise the row is selected, and if the event 2675 * specified a toggle event the row is expanded/collapsed. 2676 * 2677 * @param path the selected path 2678 * @param event the mouse event 2679 */ 2680 protected void selectPathForEvent(TreePath path, MouseEvent event) { 2681 /* Adjust from the anchor point. */ 2682 if(isMultiSelectEvent(event)) { 2683 TreePath anchor = getAnchorSelectionPath(); 2684 int anchorRow = (anchor == null) ? -1 : 2685 getRowForPath(tree, anchor); 2686 2687 if(anchorRow == -1 || tree.getSelectionModel(). 2688 getSelectionMode() == TreeSelectionModel. 2689 SINGLE_TREE_SELECTION) { 2690 tree.setSelectionPath(path); 2691 } 2692 else { 2693 int row = getRowForPath(tree, path); 2694 TreePath lastAnchorPath = anchor; 2695 2696 if (isToggleSelectionEvent(event)) { 2697 if (tree.isRowSelected(anchorRow)) { 2698 tree.addSelectionInterval(anchorRow, row); 2699 } else { 2700 tree.removeSelectionInterval(anchorRow, row); 2701 tree.addSelectionInterval(row, row); 2702 } 2703 } else if(row < anchorRow) { 2704 tree.setSelectionInterval(row, anchorRow); 2705 } else { 2706 tree.setSelectionInterval(anchorRow, row); 2707 } 2708 lastSelectedRow = row; 2709 setAnchorSelectionPath(lastAnchorPath); 2710 setLeadSelectionPath(path); 2711 } 2712 } 2713 2714 // Should this event toggle the selection of this row? 2715 /* Control toggles just this node. */ 2716 else if(isToggleSelectionEvent(event)) { 2717 if(tree.isPathSelected(path)) 2718 tree.removeSelectionPath(path); 2719 else 2720 tree.addSelectionPath(path); 2721 lastSelectedRow = getRowForPath(tree, path); 2722 setAnchorSelectionPath(path); 2723 setLeadSelectionPath(path); 2724 } 2725 2726 /* Otherwise set the selection to just this interval. */ 2727 else if(SwingUtilities.isLeftMouseButton(event)) { 2728 tree.setSelectionPath(path); 2729 if(isToggleEvent(event)) { 2730 toggleExpandState(path); 2731 } 2732 } 2733 } 2734 2735 /** 2736 * Returns {@code true} if the node at {@code row} is a leaf. 2737 * 2738 * @param row a row 2739 * @return {@code true} if the node at {@code row} is a leaf 2740 */ 2741 protected boolean isLeaf(int row) { 2742 TreePath path = getPathForRow(tree, row); 2743 2744 if(path != null) 2745 return treeModel.isLeaf(path.getLastPathComponent()); 2746 // Have to return something here... 2747 return true; 2748 } 2749 2750 // 2751 // The following selection methods (lead/anchor) are covers for the 2752 // methods in JTree. 2753 // 2754 private void setAnchorSelectionPath(TreePath newPath) { 2755 ignoreLAChange = true; 2756 try { 2757 tree.setAnchorSelectionPath(newPath); 2758 } finally{ 2759 ignoreLAChange = false; 2760 } 2761 } 2762 2763 private TreePath getAnchorSelectionPath() { 2764 return tree.getAnchorSelectionPath(); 2765 } 2766 2767 private void setLeadSelectionPath(TreePath newPath) { 2768 setLeadSelectionPath(newPath, false); 2769 } 2770 2771 private void setLeadSelectionPath(TreePath newPath, boolean repaint) { 2772 Rectangle bounds = repaint ? 2773 getPathBounds(tree, getLeadSelectionPath()) : null; 2774 2775 ignoreLAChange = true; 2776 try { 2777 tree.setLeadSelectionPath(newPath); 2778 } finally { 2779 ignoreLAChange = false; 2780 } 2781 leadRow = getRowForPath(tree, newPath); 2782 2783 if (repaint) { 2784 if (bounds != null) { 2785 tree.repaint(getRepaintPathBounds(bounds)); 2786 } 2787 bounds = getPathBounds(tree, newPath); 2788 if (bounds != null) { 2789 tree.repaint(getRepaintPathBounds(bounds)); 2790 } 2791 } 2792 } 2793 2794 private Rectangle getRepaintPathBounds(Rectangle bounds) { 2795 if (UIManager.getBoolean("Tree.repaintWholeRow")) { 2796 bounds.x = 0; 2797 bounds.width = tree.getWidth(); 2798 } 2799 return bounds; 2800 } 2801 2802 private TreePath getLeadSelectionPath() { 2803 return tree.getLeadSelectionPath(); 2804 } 2805 2806 /** 2807 * Updates the lead row of the selection. 2808 * @since 1.7 2809 */ 2810 protected void updateLeadSelectionRow() { 2811 leadRow = getRowForPath(tree, getLeadSelectionPath()); 2812 } 2813 2814 /** 2815 * Returns the lead row of the selection. 2816 * 2817 * @return selection lead row 2818 * @since 1.7 2819 */ 2820 protected int getLeadSelectionRow() { 2821 return leadRow; 2822 } 2823 2824 /** 2825 * Extends the selection from the anchor to make <code>newLead</code> 2826 * the lead of the selection. This does not scroll. 2827 */ 2828 private void extendSelection(TreePath newLead) { 2829 TreePath aPath = getAnchorSelectionPath(); 2830 int aRow = (aPath == null) ? -1 : 2831 getRowForPath(tree, aPath); 2832 int newIndex = getRowForPath(tree, newLead); 2833 2834 if(aRow == -1) { 2835 tree.setSelectionRow(newIndex); 2836 } 2837 else { 2838 if(aRow < newIndex) { 2839 tree.setSelectionInterval(aRow, newIndex); 2840 } 2841 else { 2842 tree.setSelectionInterval(newIndex, aRow); 2843 } 2844 setAnchorSelectionPath(aPath); 2845 setLeadSelectionPath(newLead); 2846 } 2847 } 2848 2849 /** 2850 * Invokes <code>repaint</code> on the JTree for the passed in TreePath, 2851 * <code>path</code>. 2852 */ 2853 private void repaintPath(TreePath path) { 2854 if (path != null) { 2855 Rectangle bounds = getPathBounds(tree, path); 2856 if (bounds != null) { 2857 tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height); 2858 } 2859 } 2860 } 2861 2862 /** 2863 * Updates the TreeState in response to nodes expanding/collapsing. 2864 */ 2865 public class TreeExpansionHandler implements TreeExpansionListener { 2866 // NOTE: This class exists only for backward compatibility. All 2867 // its functionality has been moved into Handler. If you need to add 2868 // new functionality add it to the Handler, but make sure this 2869 // class calls into the Handler. 2870 2871 /** 2872 * Called whenever an item in the tree has been expanded. 2873 */ 2874 public void treeExpanded(TreeExpansionEvent event) { 2875 getHandler().treeExpanded(event); 2876 } 2877 2878 /** 2879 * Called whenever an item in the tree has been collapsed. 2880 */ 2881 public void treeCollapsed(TreeExpansionEvent event) { 2882 getHandler().treeCollapsed(event); 2883 } 2884 } // BasicTreeUI.TreeExpansionHandler 2885 2886 2887 /** 2888 * Updates the preferred size when scrolling (if necessary). 2889 */ 2890 public class ComponentHandler extends ComponentAdapter implements 2891 ActionListener { 2892 /** Timer used when inside a scrollpane and the scrollbar is 2893 * adjusting. */ 2894 protected Timer timer; 2895 /** ScrollBar that is being adjusted. */ 2896 protected JScrollBar scrollBar; 2897 2898 public void componentMoved(ComponentEvent e) { 2899 if(timer == null) { 2900 JScrollPane scrollPane = getScrollPane(); 2901 2902 if(scrollPane == null) 2903 updateSize(); 2904 else { 2905 scrollBar = scrollPane.getVerticalScrollBar(); 2906 if(scrollBar == null || 2907 !scrollBar.getValueIsAdjusting()) { 2908 // Try the horizontal scrollbar. 2909 if((scrollBar = scrollPane.getHorizontalScrollBar()) 2910 != null && scrollBar.getValueIsAdjusting()) 2911 startTimer(); 2912 else 2913 updateSize(); 2914 } 2915 else 2916 startTimer(); 2917 } 2918 } 2919 } 2920 2921 /** 2922 * Creates, if necessary, and starts a Timer to check if need to 2923 * resize the bounds. 2924 */ 2925 protected void startTimer() { 2926 if(timer == null) { 2927 timer = new Timer(200, this); 2928 timer.setRepeats(true); 2929 } 2930 timer.start(); 2931 } 2932 2933 /** 2934 * Returns the {@code JScrollPane} housing the {@code JTree}, 2935 * or null if one isn't found. 2936 * 2937 * @return the {@code JScrollPane} housing the {@code JTree} 2938 */ 2939 protected JScrollPane getScrollPane() { 2940 Component c = tree.getParent(); 2941 2942 while(c != null && !(c instanceof JScrollPane)) 2943 c = c.getParent(); 2944 if(c instanceof JScrollPane) 2945 return (JScrollPane)c; 2946 return null; 2947 } 2948 2949 /** 2950 * Public as a result of Timer. If the scrollBar is null, or 2951 * not adjusting, this stops the timer and updates the sizing. 2952 */ 2953 public void actionPerformed(ActionEvent ae) { 2954 if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { 2955 if(timer != null) 2956 timer.stop(); 2957 updateSize(); 2958 timer = null; 2959 scrollBar = null; 2960 } 2961 } 2962 } // End of BasicTreeUI.ComponentHandler 2963 2964 2965 /** 2966 * Forwards all TreeModel events to the TreeState. 2967 */ 2968 public class TreeModelHandler implements TreeModelListener { 2969 2970 // NOTE: This class exists only for backward compatibility. All 2971 // its functionality has been moved into Handler. If you need to add 2972 // new functionality add it to the Handler, but make sure this 2973 // class calls into the Handler. 2974 2975 public void treeNodesChanged(TreeModelEvent e) { 2976 getHandler().treeNodesChanged(e); 2977 } 2978 2979 public void treeNodesInserted(TreeModelEvent e) { 2980 getHandler().treeNodesInserted(e); 2981 } 2982 2983 public void treeNodesRemoved(TreeModelEvent e) { 2984 getHandler().treeNodesRemoved(e); 2985 } 2986 2987 public void treeStructureChanged(TreeModelEvent e) { 2988 getHandler().treeStructureChanged(e); 2989 } 2990 } // End of BasicTreeUI.TreeModelHandler 2991 2992 2993 /** 2994 * Listens for changes in the selection model and updates the display 2995 * accordingly. 2996 */ 2997 public class TreeSelectionHandler implements TreeSelectionListener { 2998 2999 // NOTE: This class exists only for backward compatibility. All 3000 // its functionality has been moved into Handler. If you need to add 3001 // new functionality add it to the Handler, but make sure this 3002 // class calls into the Handler. 3003 3004 /** 3005 * Messaged when the selection changes in the tree we're displaying 3006 * for. Stops editing, messages super and displays the changed paths. 3007 */ 3008 public void valueChanged(TreeSelectionEvent event) { 3009 getHandler().valueChanged(event); 3010 } 3011 }// End of BasicTreeUI.TreeSelectionHandler 3012 3013 3014 /** 3015 * Listener responsible for getting cell editing events and updating 3016 * the tree accordingly. 3017 */ 3018 public class CellEditorHandler implements CellEditorListener { 3019 3020 // NOTE: This class exists only for backward compatibility. All 3021 // its functionality has been moved into Handler. If you need to add 3022 // new functionality add it to the Handler, but make sure this 3023 // class calls into the Handler. 3024 3025 /** Messaged when editing has stopped in the tree. */ 3026 public void editingStopped(ChangeEvent e) { 3027 getHandler().editingStopped(e); 3028 } 3029 3030 /** Messaged when editing has been canceled in the tree. */ 3031 public void editingCanceled(ChangeEvent e) { 3032 getHandler().editingCanceled(e); 3033 } 3034 } // BasicTreeUI.CellEditorHandler 3035 3036 3037 /** 3038 * This is used to get multiple key down events to appropriately generate 3039 * events. 3040 */ 3041 public class KeyHandler extends KeyAdapter { 3042 3043 // NOTE: This class exists only for backward compatibility. All 3044 // its functionality has been moved into Handler. If you need to add 3045 // new functionality add it to the Handler, but make sure this 3046 // class calls into the Handler. 3047 3048 // Also note these fields aren't use anymore, nor does Handler have 3049 // the old functionality. This behavior worked around an old bug 3050 // in JComponent that has long since been fixed. 3051 3052 /** Key code that is being generated for. */ 3053 protected Action repeatKeyAction; 3054 3055 /** Set to true while keyPressed is active. */ 3056 protected boolean isKeyDown; 3057 3058 /** 3059 * Invoked when a key has been typed. 3060 * 3061 * Moves the keyboard focus to the first element 3062 * whose first letter matches the alphanumeric key 3063 * pressed by the user. Subsequent same key presses 3064 * move the keyboard focus to the next object that 3065 * starts with the same letter. 3066 */ 3067 public void keyTyped(KeyEvent e) { 3068 getHandler().keyTyped(e); 3069 } 3070 3071 public void keyPressed(KeyEvent e) { 3072 getHandler().keyPressed(e); 3073 } 3074 3075 public void keyReleased(KeyEvent e) { 3076 getHandler().keyReleased(e); 3077 } 3078 } // End of BasicTreeUI.KeyHandler 3079 3080 3081 /** 3082 * Repaints the lead selection row when focus is lost/gained. 3083 */ 3084 public class FocusHandler implements FocusListener { 3085 // NOTE: This class exists only for backward compatibility. All 3086 // its functionality has been moved into Handler. If you need to add 3087 // new functionality add it to the Handler, but make sure this 3088 // class calls into the Handler. 3089 3090 /** 3091 * Invoked when focus is activated on the tree we're in, redraws the 3092 * lead row. 3093 */ 3094 public void focusGained(FocusEvent e) { 3095 getHandler().focusGained(e); 3096 } 3097 3098 /** 3099 * Invoked when focus is activated on the tree we're in, redraws the 3100 * lead row. 3101 */ 3102 public void focusLost(FocusEvent e) { 3103 getHandler().focusLost(e); 3104 } 3105 } // End of class BasicTreeUI.FocusHandler 3106 3107 3108 /** 3109 * Class responsible for getting size of node, method is forwarded 3110 * to BasicTreeUI method. X location does not include insets, that is 3111 * handled in getPathBounds. 3112 */ 3113 // This returns locations that don't include any Insets. 3114 public class NodeDimensionsHandler extends 3115 AbstractLayoutCache.NodeDimensions { 3116 /** 3117 * Responsible for getting the size of a particular node. 3118 */ 3119 public Rectangle getNodeDimensions(Object value, int row, 3120 int depth, boolean expanded, 3121 Rectangle size) { 3122 // Return size of editing component, if editing and asking 3123 // for editing row. 3124 if(editingComponent != null && editingRow == row) { 3125 Dimension prefSize = editingComponent. 3126 getPreferredSize(); 3127 int rh = getRowHeight(); 3128 3129 if(rh > 0 && rh != prefSize.height) 3130 prefSize.height = rh; 3131 if(size != null) { 3132 size.x = getRowX(row, depth); 3133 size.width = prefSize.width; 3134 size.height = prefSize.height; 3135 } 3136 else { 3137 size = new Rectangle(getRowX(row, depth), 0, 3138 prefSize.width, prefSize.height); 3139 } 3140 return size; 3141 } 3142 // Not editing, use renderer. 3143 if(currentCellRenderer != null) { 3144 Component aComponent; 3145 3146 aComponent = currentCellRenderer.getTreeCellRendererComponent 3147 (tree, value, tree.isRowSelected(row), 3148 expanded, treeModel.isLeaf(value), row, 3149 false); 3150 if(tree != null) { 3151 // Only ever removed when UI changes, this is OK! 3152 rendererPane.add(aComponent); 3153 aComponent.validate(); 3154 } 3155 Dimension prefSize = aComponent.getPreferredSize(); 3156 3157 if(size != null) { 3158 size.x = getRowX(row, depth); 3159 size.width = prefSize.width; 3160 size.height = prefSize.height; 3161 } 3162 else { 3163 size = new Rectangle(getRowX(row, depth), 0, 3164 prefSize.width, prefSize.height); 3165 } 3166 return size; 3167 } 3168 return null; 3169 } 3170 3171 /** 3172 * Returns amount to indent the given row. 3173 * 3174 * @param row a row 3175 * @param depth a depth 3176 * @return amount to indent the given row 3177 */ 3178 protected int getRowX(int row, int depth) { 3179 return BasicTreeUI.this.getRowX(row, depth); 3180 } 3181 3182 } // End of class BasicTreeUI.NodeDimensionsHandler 3183 3184 3185 /** 3186 * TreeMouseListener is responsible for updating the selection 3187 * based on mouse events. 3188 */ 3189 public class MouseHandler extends MouseAdapter implements MouseMotionListener 3190 { 3191 // NOTE: This class exists only for backward compatibility. All 3192 // its functionality has been moved into Handler. If you need to add 3193 // new functionality add it to the Handler, but make sure this 3194 // class calls into the Handler. 3195 3196 /** 3197 * Invoked when a mouse button has been pressed on a component. 3198 */ 3199 public void mousePressed(MouseEvent e) { 3200 getHandler().mousePressed(e); 3201 } 3202 3203 public void mouseDragged(MouseEvent e) { 3204 getHandler().mouseDragged(e); 3205 } 3206 3207 /** 3208 * Invoked when the mouse button has been moved on a component 3209 * (with no buttons no down). 3210 * @since 1.4 3211 */ 3212 public void mouseMoved(MouseEvent e) { 3213 getHandler().mouseMoved(e); 3214 } 3215 3216 public void mouseReleased(MouseEvent e) { 3217 getHandler().mouseReleased(e); 3218 } 3219 } // End of BasicTreeUI.MouseHandler 3220 3221 3222 /** 3223 * PropertyChangeListener for the tree. Updates the appropriate 3224 * variable, or TreeState, based on what changes. 3225 */ 3226 public class PropertyChangeHandler implements 3227 PropertyChangeListener { 3228 3229 // NOTE: This class exists only for backward compatibility. All 3230 // its functionality has been moved into Handler. If you need to add 3231 // new functionality add it to the Handler, but make sure this 3232 // class calls into the Handler. 3233 3234 public void propertyChange(PropertyChangeEvent event) { 3235 getHandler().propertyChange(event); 3236 } 3237 } // End of BasicTreeUI.PropertyChangeHandler 3238 3239 3240 /** 3241 * Listener on the TreeSelectionModel, resets the row selection if 3242 * any of the properties of the model change. 3243 */ 3244 public class SelectionModelPropertyChangeHandler implements 3245 PropertyChangeListener { 3246 3247 // NOTE: This class exists only for backward compatibility. All 3248 // its functionality has been moved into Handler. If you need to add 3249 // new functionality add it to the Handler, but make sure this 3250 // class calls into the Handler. 3251 3252 public void propertyChange(PropertyChangeEvent event) { 3253 getHandler().propertyChange(event); 3254 } 3255 } // End of BasicTreeUI.SelectionModelPropertyChangeHandler 3256 3257 3258 /** 3259 * <code>TreeTraverseAction</code> is the action used for left/right keys. 3260 * Will toggle the expandedness of a node, as well as potentially 3261 * incrementing the selection. 3262 */ 3263 @SuppressWarnings("serial") // Superclass is not serializable across versions 3264 public class TreeTraverseAction extends AbstractAction { 3265 /** Determines direction to traverse, 1 means expand, -1 means 3266 * collapse. */ 3267 protected int direction; 3268 /** True if the selection is reset, false means only the lead path 3269 * changes. */ 3270 private boolean changeSelection; 3271 3272 /** 3273 * Constructs a new instance of {@code TreeTraverseAction}. 3274 * 3275 * @param direction the direction 3276 * @param name the name of action 3277 */ 3278 public TreeTraverseAction(int direction, String name) { 3279 this(direction, name, true); 3280 } 3281 3282 private TreeTraverseAction(int direction, String name, 3283 boolean changeSelection) { 3284 this.direction = direction; 3285 this.changeSelection = changeSelection; 3286 } 3287 3288 public void actionPerformed(ActionEvent e) { 3289 if (tree != null) { 3290 SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction, 3291 changeSelection); 3292 } 3293 } 3294 3295 public boolean isEnabled() { return (tree != null && 3296 tree.isEnabled()); } 3297 } // BasicTreeUI.TreeTraverseAction 3298 3299 3300 /** TreePageAction handles page up and page down events. 3301 */ 3302 @SuppressWarnings("serial") // Superclass is not serializable across versions 3303 public class TreePageAction extends AbstractAction { 3304 /** Specifies the direction to adjust the selection by. */ 3305 protected int direction; 3306 /** True indicates should set selection from anchor path. */ 3307 private boolean addToSelection; 3308 private boolean changeSelection; 3309 3310 /** 3311 * Constructs a new instance of {@code TreePageAction}. 3312 * 3313 * @param direction the direction 3314 * @param name the name of action 3315 */ 3316 public TreePageAction(int direction, String name) { 3317 this(direction, name, false, true); 3318 } 3319 3320 private TreePageAction(int direction, String name, 3321 boolean addToSelection, 3322 boolean changeSelection) { 3323 this.direction = direction; 3324 this.addToSelection = addToSelection; 3325 this.changeSelection = changeSelection; 3326 } 3327 3328 public void actionPerformed(ActionEvent e) { 3329 if (tree != null) { 3330 SHARED_ACTION.page(tree, BasicTreeUI.this, direction, 3331 addToSelection, changeSelection); 3332 } 3333 } 3334 3335 public boolean isEnabled() { return (tree != null && 3336 tree.isEnabled()); } 3337 3338 } // BasicTreeUI.TreePageAction 3339 3340 3341 /** TreeIncrementAction is used to handle up/down actions. Selection 3342 * is moved up or down based on direction. 3343 */ 3344 @SuppressWarnings("serial") // Superclass is not serializable across versions 3345 public class TreeIncrementAction extends AbstractAction { 3346 /** Specifies the direction to adjust the selection by. */ 3347 protected int direction; 3348 /** If true the new item is added to the selection, if false the 3349 * selection is reset. */ 3350 private boolean addToSelection; 3351 private boolean changeSelection; 3352 3353 /** 3354 * Constructs a new instance of {@code TreeIncrementAction}. 3355 * 3356 * @param direction the direction 3357 * @param name the name of action 3358 */ 3359 public TreeIncrementAction(int direction, String name) { 3360 this(direction, name, false, true); 3361 } 3362 3363 private TreeIncrementAction(int direction, String name, 3364 boolean addToSelection, 3365 boolean changeSelection) { 3366 this.direction = direction; 3367 this.addToSelection = addToSelection; 3368 this.changeSelection = changeSelection; 3369 } 3370 3371 public void actionPerformed(ActionEvent e) { 3372 if (tree != null) { 3373 SHARED_ACTION.increment(tree, BasicTreeUI.this, direction, 3374 addToSelection, changeSelection); 3375 } 3376 } 3377 3378 public boolean isEnabled() { return (tree != null && 3379 tree.isEnabled()); } 3380 3381 } // End of class BasicTreeUI.TreeIncrementAction 3382 3383 /** 3384 * TreeHomeAction is used to handle end/home actions. 3385 * Scrolls either the first or last cell to be visible based on 3386 * direction. 3387 */ 3388 @SuppressWarnings("serial") // Superclass is not serializable across versions 3389 public class TreeHomeAction extends AbstractAction { 3390 /** 3391 * The direction. 3392 */ 3393 protected int direction; 3394 /** Set to true if append to selection. */ 3395 private boolean addToSelection; 3396 private boolean changeSelection; 3397 3398 /** 3399 * Constructs a new instance of {@code TreeHomeAction}. 3400 * 3401 * @param direction the direction 3402 * @param name the name of action 3403 */ 3404 public TreeHomeAction(int direction, String name) { 3405 this(direction, name, false, true); 3406 } 3407 3408 private TreeHomeAction(int direction, String name, 3409 boolean addToSelection, 3410 boolean changeSelection) { 3411 this.direction = direction; 3412 this.changeSelection = changeSelection; 3413 this.addToSelection = addToSelection; 3414 } 3415 3416 public void actionPerformed(ActionEvent e) { 3417 if (tree != null) { 3418 SHARED_ACTION.home(tree, BasicTreeUI.this, direction, 3419 addToSelection, changeSelection); 3420 } 3421 } 3422 3423 public boolean isEnabled() { return (tree != null && 3424 tree.isEnabled()); } 3425 3426 } // End of class BasicTreeUI.TreeHomeAction 3427 3428 3429 /** 3430 * For the first selected row expandedness will be toggled. 3431 */ 3432 @SuppressWarnings("serial") // Superclass is not serializable across versions 3433 public class TreeToggleAction extends AbstractAction { 3434 /** 3435 * Constructs a new instance of {@code TreeToggleAction}. 3436 * 3437 * @param name the name of action 3438 */ 3439 public TreeToggleAction(String name) { 3440 } 3441 3442 public void actionPerformed(ActionEvent e) { 3443 if(tree != null) { 3444 SHARED_ACTION.toggle(tree, BasicTreeUI.this); 3445 } 3446 } 3447 3448 public boolean isEnabled() { return (tree != null && 3449 tree.isEnabled()); } 3450 3451 } // End of class BasicTreeUI.TreeToggleAction 3452 3453 3454 /** 3455 * ActionListener that invokes cancelEditing when action performed. 3456 */ 3457 @SuppressWarnings("serial") // Superclass is not serializable across versions 3458 public class TreeCancelEditingAction extends AbstractAction { 3459 /** 3460 * Constructs a new instance of {@code TreeCancelEditingAction}. 3461 * 3462 * @param name the name of action 3463 */ 3464 public TreeCancelEditingAction(String name) { 3465 } 3466 3467 public void actionPerformed(ActionEvent e) { 3468 if(tree != null) { 3469 SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this); 3470 } 3471 } 3472 3473 public boolean isEnabled() { return (tree != null && 3474 tree.isEnabled() && 3475 isEditing(tree)); } 3476 } // End of class BasicTreeUI.TreeCancelEditingAction 3477 3478 3479 /** 3480 * MouseInputHandler handles passing all mouse events, 3481 * including mouse motion events, until the mouse is released to 3482 * the destination it is constructed with. It is assumed all the 3483 * events are currently target at source. 3484 */ 3485 public class MouseInputHandler extends Object implements 3486 MouseInputListener 3487 { 3488 /** Source that events are coming from. */ 3489 protected Component source; 3490 /** Destination that receives all events. */ 3491 protected Component destination; 3492 private Component focusComponent; 3493 private boolean dispatchedEvent; 3494 3495 /** 3496 * Constructs a new instance of {@code MouseInputHandler}. 3497 * 3498 * @param source a source component 3499 * @param destination a destination component 3500 * @param event a mouse event 3501 */ 3502 public MouseInputHandler(Component source, Component destination, 3503 MouseEvent event){ 3504 this(source, destination, event, null); 3505 } 3506 3507 MouseInputHandler(Component source, Component destination, 3508 MouseEvent event, Component focusComponent) { 3509 this.source = source; 3510 this.destination = destination; 3511 this.source.addMouseListener(this); 3512 this.source.addMouseMotionListener(this); 3513 3514 SwingUtilities2.setSkipClickCount(destination, 3515 event.getClickCount() - 1); 3516 3517 /* Dispatch the editing event! */ 3518 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3519 (source, event, destination)); 3520 this.focusComponent = focusComponent; 3521 } 3522 3523 public void mouseClicked(MouseEvent e) { 3524 if(destination != null) { 3525 dispatchedEvent = true; 3526 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3527 (source, e, destination)); 3528 } 3529 } 3530 3531 public void mousePressed(MouseEvent e) { 3532 } 3533 3534 public void mouseReleased(MouseEvent e) { 3535 if(destination != null) 3536 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3537 (source, e, destination)); 3538 removeFromSource(); 3539 } 3540 3541 public void mouseEntered(MouseEvent e) { 3542 if (!SwingUtilities.isLeftMouseButton(e)) { 3543 removeFromSource(); 3544 } 3545 } 3546 3547 public void mouseExited(MouseEvent e) { 3548 if (!SwingUtilities.isLeftMouseButton(e)) { 3549 removeFromSource(); 3550 } 3551 } 3552 3553 public void mouseDragged(MouseEvent e) { 3554 if(destination != null) { 3555 dispatchedEvent = true; 3556 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3557 (source, e, destination)); 3558 } 3559 } 3560 3561 public void mouseMoved(MouseEvent e) { 3562 removeFromSource(); 3563 } 3564 3565 /** 3566 * Removes an event from the source. 3567 */ 3568 protected void removeFromSource() { 3569 if(source != null) { 3570 source.removeMouseListener(this); 3571 source.removeMouseMotionListener(this); 3572 if (focusComponent != null && 3573 focusComponent == destination && !dispatchedEvent && 3574 (focusComponent instanceof JTextField)) { 3575 ((JTextField)focusComponent).selectAll(); 3576 } 3577 } 3578 source = destination = null; 3579 } 3580 3581 } // End of class BasicTreeUI.MouseInputHandler 3582 3583 private static final TransferHandler defaultTransferHandler = new TreeTransferHandler(); 3584 3585 @SuppressWarnings("serial") // JDK-implementation class 3586 static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator<TreePath> { 3587 3588 private JTree tree; 3589 3590 /** 3591 * Create a Transferable to use as the source for a data transfer. 3592 * 3593 * @param c The component holding the data to be transfered. This 3594 * argument is provided to enable sharing of TransferHandlers by 3595 * multiple components. 3596 * @return The representation of the data to be transfered. 3597 * 3598 */ 3599 protected Transferable createTransferable(JComponent c) { 3600 if (c instanceof JTree) { 3601 tree = (JTree) c; 3602 TreePath[] paths = tree.getSelectionPaths(); 3603 3604 if (paths == null || paths.length == 0) { 3605 return null; 3606 } 3607 3608 StringBuilder plainStr = new StringBuilder(); 3609 StringBuilder htmlStr = new StringBuilder(); 3610 3611 htmlStr.append("<html>\n<body>\n<ul>\n"); 3612 3613 TreeModel model = tree.getModel(); 3614 TreePath lastPath = null; 3615 TreePath[] displayPaths = getDisplayOrderPaths(paths); 3616 3617 for (TreePath path : displayPaths) { 3618 Object node = path.getLastPathComponent(); 3619 boolean leaf = model.isLeaf(node); 3620 String label = getDisplayString(path, true, leaf); 3621 3622 plainStr.append(label + "\n"); 3623 htmlStr.append(" <li>" + label + "\n"); 3624 } 3625 3626 // remove the last newline 3627 plainStr.deleteCharAt(plainStr.length() - 1); 3628 htmlStr.append("</ul>\n</body>\n</html>"); 3629 3630 tree = null; 3631 3632 return new BasicTransferable(plainStr.toString(), htmlStr.toString()); 3633 } 3634 3635 return null; 3636 } 3637 3638 public int compare(TreePath o1, TreePath o2) { 3639 int row1 = tree.getRowForPath(o1); 3640 int row2 = tree.getRowForPath(o2); 3641 return row1 - row2; 3642 } 3643 3644 String getDisplayString(TreePath path, boolean selected, boolean leaf) { 3645 int row = tree.getRowForPath(path); 3646 boolean hasFocus = tree.getLeadSelectionRow() == row; 3647 Object node = path.getLastPathComponent(); 3648 return tree.convertValueToText(node, selected, tree.isExpanded(row), 3649 leaf, row, hasFocus); 3650 } 3651 3652 /** 3653 * Selection paths are in selection order. The conversion to 3654 * HTML requires display order. This method resorts the paths 3655 * to be in the display order. 3656 */ 3657 TreePath[] getDisplayOrderPaths(TreePath[] paths) { 3658 // sort the paths to display order rather than selection order 3659 ArrayList<TreePath> selOrder = new ArrayList<TreePath>(); 3660 for (TreePath path : paths) { 3661 selOrder.add(path); 3662 } 3663 Collections.sort(selOrder, this); 3664 int n = selOrder.size(); 3665 TreePath[] displayPaths = new TreePath[n]; 3666 for (int i = 0; i < n; i++) { 3667 displayPaths[i] = selOrder.get(i); 3668 } 3669 return displayPaths; 3670 } 3671 3672 public int getSourceActions(JComponent c) { 3673 return COPY; 3674 } 3675 3676 } 3677 3678 3679 private class Handler implements CellEditorListener, FocusListener, 3680 KeyListener, MouseListener, MouseMotionListener, 3681 PropertyChangeListener, TreeExpansionListener, 3682 TreeModelListener, TreeSelectionListener, 3683 BeforeDrag { 3684 // 3685 // KeyListener 3686 // 3687 private String prefix = ""; 3688 private String typedString = ""; 3689 private long lastTime = 0L; 3690 3691 /** 3692 * Invoked when a key has been typed. 3693 * 3694 * Moves the keyboard focus to the first element whose prefix matches the 3695 * sequence of alphanumeric keys pressed by the user with delay less 3696 * than value of <code>timeFactor</code> property (or 1000 milliseconds 3697 * if it is not defined). Subsequent same key presses move the keyboard 3698 * focus to the next object that starts with the same letter until another 3699 * key is pressed, then it is treated as the prefix with appropriate number 3700 * of the same letters followed by first typed another letter. 3701 */ 3702 public void keyTyped(KeyEvent e) { 3703 // handle first letter navigation 3704 if(tree != null && tree.getRowCount()>0 && tree.hasFocus() && 3705 tree.isEnabled()) { 3706 if (e.isAltDown() || BasicGraphicsUtils.isMenuShortcutKeyDown(e) || 3707 isNavigationKey(e)) { 3708 return; 3709 } 3710 boolean startingFromSelection = true; 3711 3712 char c = e.getKeyChar(); 3713 3714 long time = e.getWhen(); 3715 int startingRow = tree.getLeadSelectionRow(); 3716 if (time - lastTime < timeFactor) { 3717 typedString += c; 3718 if((prefix.length() == 1) && (c == prefix.charAt(0))) { 3719 // Subsequent same key presses move the keyboard focus to the next 3720 // object that starts with the same letter. 3721 startingRow++; 3722 } else { 3723 prefix = typedString; 3724 } 3725 } else { 3726 startingRow++; 3727 typedString = "" + c; 3728 prefix = typedString; 3729 } 3730 lastTime = time; 3731 3732 if (startingRow < 0 || startingRow >= tree.getRowCount()) { 3733 startingFromSelection = false; 3734 startingRow = 0; 3735 } 3736 TreePath path = tree.getNextMatch(prefix, startingRow, 3737 Position.Bias.Forward); 3738 if (path != null) { 3739 tree.setSelectionPath(path); 3740 int row = getRowForPath(tree, path); 3741 ensureRowsAreVisible(row, row); 3742 } else if (startingFromSelection) { 3743 path = tree.getNextMatch(prefix, 0, 3744 Position.Bias.Forward); 3745 if (path != null) { 3746 tree.setSelectionPath(path); 3747 int row = getRowForPath(tree, path); 3748 ensureRowsAreVisible(row, row); 3749 } 3750 } 3751 } 3752 } 3753 3754 /** 3755 * Invoked when a key has been pressed. 3756 * 3757 * Checks to see if the key event is a navigation key to prevent 3758 * dispatching these keys for the first letter navigation. 3759 */ 3760 public void keyPressed(KeyEvent e) { 3761 if (tree != null && isNavigationKey(e)) { 3762 prefix = ""; 3763 typedString = ""; 3764 lastTime = 0L; 3765 } 3766 } 3767 3768 public void keyReleased(KeyEvent e) { 3769 } 3770 3771 /** 3772 * Returns whether or not the supplied key event maps to a key that is used for 3773 * navigation. This is used for optimizing key input by only passing non- 3774 * navigation keys to the first letter navigation mechanism. 3775 */ 3776 private boolean isNavigationKey(KeyEvent event) { 3777 InputMap inputMap = tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 3778 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event); 3779 3780 return inputMap != null && inputMap.get(key) != null; 3781 } 3782 3783 3784 // 3785 // PropertyChangeListener 3786 // 3787 public void propertyChange(PropertyChangeEvent event) { 3788 if (event.getSource() == treeSelectionModel) { 3789 treeSelectionModel.resetRowSelection(); 3790 } 3791 else if(event.getSource() == tree) { 3792 String changeName = event.getPropertyName(); 3793 3794 if (changeName == JTree.LEAD_SELECTION_PATH_PROPERTY) { 3795 if (!ignoreLAChange) { 3796 updateLeadSelectionRow(); 3797 repaintPath((TreePath)event.getOldValue()); 3798 repaintPath((TreePath)event.getNewValue()); 3799 } 3800 } 3801 else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) { 3802 if (!ignoreLAChange) { 3803 repaintPath((TreePath)event.getOldValue()); 3804 repaintPath((TreePath)event.getNewValue()); 3805 } 3806 } 3807 if(changeName == JTree.CELL_RENDERER_PROPERTY) { 3808 setCellRenderer((TreeCellRenderer)event.getNewValue()); 3809 redoTheLayout(); 3810 } 3811 else if(changeName == JTree.TREE_MODEL_PROPERTY) { 3812 setModel((TreeModel)event.getNewValue()); 3813 } 3814 else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) { 3815 setRootVisible(((Boolean)event.getNewValue()). 3816 booleanValue()); 3817 } 3818 else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) { 3819 setShowsRootHandles(((Boolean)event.getNewValue()). 3820 booleanValue()); 3821 } 3822 else if(changeName == JTree.ROW_HEIGHT_PROPERTY) { 3823 setRowHeight(((Integer)event.getNewValue()). 3824 intValue()); 3825 } 3826 else if(changeName == JTree.CELL_EDITOR_PROPERTY) { 3827 setCellEditor((TreeCellEditor)event.getNewValue()); 3828 } 3829 else if(changeName == JTree.EDITABLE_PROPERTY) { 3830 setEditable(((Boolean)event.getNewValue()).booleanValue()); 3831 } 3832 else if(changeName == JTree.LARGE_MODEL_PROPERTY) { 3833 setLargeModel(tree.isLargeModel()); 3834 } 3835 else if(changeName == JTree.SELECTION_MODEL_PROPERTY) { 3836 setSelectionModel(tree.getSelectionModel()); 3837 } 3838 else if(changeName == "font") { 3839 completeEditing(); 3840 if(treeState != null) 3841 treeState.invalidateSizes(); 3842 updateSize(); 3843 } 3844 else if (changeName == "componentOrientation") { 3845 if (tree != null) { 3846 leftToRight = BasicGraphicsUtils.isLeftToRight(tree); 3847 redoTheLayout(); 3848 tree.treeDidChange(); 3849 3850 InputMap km = getInputMap(JComponent.WHEN_FOCUSED); 3851 SwingUtilities.replaceUIInputMap(tree, 3852 JComponent.WHEN_FOCUSED, km); 3853 } 3854 } else if ("dropLocation" == changeName) { 3855 JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue(); 3856 repaintDropLocation(oldValue); 3857 repaintDropLocation(tree.getDropLocation()); 3858 } 3859 } 3860 } 3861 3862 private void repaintDropLocation(JTree.DropLocation loc) { 3863 if (loc == null) { 3864 return; 3865 } 3866 3867 Rectangle r; 3868 3869 if (isDropLine(loc)) { 3870 r = getDropLineRect(loc); 3871 } else { 3872 r = tree.getPathBounds(loc.getPath()); 3873 } 3874 3875 if (r != null) { 3876 tree.repaint(r); 3877 } 3878 } 3879 3880 // 3881 // MouseListener 3882 // 3883 3884 // Whether or not the mouse press (which is being considered as part 3885 // of a drag sequence) also caused the selection change to be fully 3886 // processed. 3887 private boolean dragPressDidSelection; 3888 3889 // Set to true when a drag gesture has been fully recognized and DnD 3890 // begins. Use this to ignore further mouse events which could be 3891 // delivered if DnD is cancelled (via ESCAPE for example) 3892 private boolean dragStarted; 3893 3894 // The path over which the press occurred and the press event itself 3895 private TreePath pressedPath; 3896 private MouseEvent pressedEvent; 3897 3898 // Used to detect whether the press event causes a selection change. 3899 // If it does, we won't try to start editing on the release. 3900 private boolean valueChangedOnPress; 3901 3902 private boolean isActualPath(TreePath path, int x, int y) { 3903 if (path == null) { 3904 return false; 3905 } 3906 3907 Rectangle bounds = getPathBounds(tree, path); 3908 if (bounds == null || y > (bounds.y + bounds.height)) { 3909 return false; 3910 } 3911 3912 return (x >= bounds.x) && (x <= (bounds.x + bounds.width)); 3913 } 3914 3915 public void mouseClicked(MouseEvent e) { 3916 } 3917 3918 public void mouseEntered(MouseEvent e) { 3919 } 3920 3921 public void mouseExited(MouseEvent e) { 3922 } 3923 3924 /** 3925 * Invoked when a mouse button has been pressed on a component. 3926 */ 3927 public void mousePressed(MouseEvent e) { 3928 if (SwingUtilities2.shouldIgnore(e, tree)) { 3929 return; 3930 } 3931 3932 // if we can't stop any ongoing editing, do nothing 3933 if (isEditing(tree) && tree.getInvokesStopCellEditing() 3934 && !stopEditing(tree)) { 3935 return; 3936 } 3937 3938 completeEditing(); 3939 3940 pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY()); 3941 3942 if (tree.getDragEnabled()) { 3943 mousePressedDND(e); 3944 } else { 3945 SwingUtilities2.adjustFocus(tree); 3946 handleSelection(e); 3947 } 3948 } 3949 3950 private void mousePressedDND(MouseEvent e) { 3951 pressedEvent = e; 3952 boolean grabFocus = true; 3953 dragStarted = false; 3954 valueChangedOnPress = false; 3955 3956 // if we have a valid path and this is a drag initiating event 3957 if (isActualPath(pressedPath, e.getX(), e.getY()) && 3958 DragRecognitionSupport.mousePressed(e)) { 3959 3960 dragPressDidSelection = false; 3961 3962 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) { 3963 // do nothing for control - will be handled on release 3964 // or when drag starts 3965 return; 3966 } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) { 3967 // clicking on something that's already selected 3968 // and need to make it the lead now 3969 setAnchorSelectionPath(pressedPath); 3970 setLeadSelectionPath(pressedPath, true); 3971 return; 3972 } 3973 3974 dragPressDidSelection = true; 3975 3976 // could be a drag initiating event - don't grab focus 3977 grabFocus = false; 3978 } 3979 3980 if (grabFocus) { 3981 SwingUtilities2.adjustFocus(tree); 3982 } 3983 3984 handleSelection(e); 3985 } 3986 3987 void handleSelection(MouseEvent e) { 3988 if(pressedPath != null) { 3989 Rectangle bounds = getPathBounds(tree, pressedPath); 3990 3991 if (bounds == null || e.getY() >= (bounds.y + bounds.height)) { 3992 return; 3993 } 3994 3995 // Preferably checkForClickInExpandControl could take 3996 // the Event to do this it self! 3997 if(SwingUtilities.isLeftMouseButton(e)) { 3998 checkForClickInExpandControl(pressedPath, e.getX(), e.getY()); 3999 } 4000 4001 int x = e.getX(); 4002 4003 // Perhaps they clicked the cell itself. If so, 4004 // select it. 4005 if (x >= bounds.x && x < (bounds.x + bounds.width)) { 4006 if (tree.getDragEnabled() || !startEditing(pressedPath, e)) { 4007 selectPathForEvent(pressedPath, e); 4008 } 4009 } 4010 } 4011 } 4012 4013 public void dragStarting(MouseEvent me) { 4014 dragStarted = true; 4015 4016 if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) { 4017 tree.addSelectionPath(pressedPath); 4018 setAnchorSelectionPath(pressedPath); 4019 setLeadSelectionPath(pressedPath, true); 4020 } 4021 4022 pressedEvent = null; 4023 pressedPath = null; 4024 } 4025 4026 public void mouseDragged(MouseEvent e) { 4027 if (SwingUtilities2.shouldIgnore(e, tree)) { 4028 return; 4029 } 4030 4031 if (tree.getDragEnabled()) { 4032 DragRecognitionSupport.mouseDragged(e, this); 4033 } 4034 } 4035 4036 /** 4037 * Invoked when the mouse button has been moved on a component 4038 * (with no buttons no down). 4039 */ 4040 public void mouseMoved(MouseEvent e) { 4041 } 4042 4043 public void mouseReleased(MouseEvent e) { 4044 if (SwingUtilities2.shouldIgnore(e, tree)) { 4045 return; 4046 } 4047 4048 if (tree.getDragEnabled()) { 4049 mouseReleasedDND(e); 4050 } 4051 4052 pressedEvent = null; 4053 pressedPath = null; 4054 } 4055 4056 private void mouseReleasedDND(MouseEvent e) { 4057 MouseEvent me = DragRecognitionSupport.mouseReleased(e); 4058 if (me != null) { 4059 SwingUtilities2.adjustFocus(tree); 4060 if (!dragPressDidSelection) { 4061 handleSelection(me); 4062 } 4063 } 4064 4065 if (!dragStarted) { 4066 4067 // Note: We don't give the tree a chance to start editing if the 4068 // mouse press caused a selection change. Otherwise the default 4069 // tree cell editor will start editing on EVERY press and 4070 // release. If it turns out that this affects some editors, we 4071 // can always parameterize this with a client property. ex: 4072 // 4073 // if (pressedPath != null && 4074 // (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") || 4075 // !valueChangedOnPress) && ... 4076 if (pressedPath != null && !valueChangedOnPress && 4077 isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) { 4078 4079 startEditingOnRelease(pressedPath, pressedEvent, e); 4080 } 4081 } 4082 } 4083 4084 // 4085 // FocusListener 4086 // 4087 public void focusGained(FocusEvent e) { 4088 if(tree != null) { 4089 Rectangle pBounds; 4090 4091 pBounds = getPathBounds(tree, tree.getLeadSelectionPath()); 4092 if(pBounds != null) 4093 tree.repaint(getRepaintPathBounds(pBounds)); 4094 pBounds = getPathBounds(tree, getLeadSelectionPath()); 4095 if(pBounds != null) 4096 tree.repaint(getRepaintPathBounds(pBounds)); 4097 } 4098 } 4099 4100 public void focusLost(FocusEvent e) { 4101 focusGained(e); 4102 } 4103 4104 // 4105 // CellEditorListener 4106 // 4107 public void editingStopped(ChangeEvent e) { 4108 completeEditing(false, false, true); 4109 } 4110 4111 /** Messaged when editing has been canceled in the tree. */ 4112 public void editingCanceled(ChangeEvent e) { 4113 completeEditing(false, false, false); 4114 } 4115 4116 4117 // 4118 // TreeSelectionListener 4119 // 4120 public void valueChanged(TreeSelectionEvent event) { 4121 valueChangedOnPress = true; 4122 4123 // Stop editing 4124 completeEditing(); 4125 // Make sure all the paths are visible, if necessary. 4126 // PENDING: This should be tweaked when isAdjusting is added 4127 if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) { 4128 TreePath[] paths = treeSelectionModel 4129 .getSelectionPaths(); 4130 4131 if(paths != null) { 4132 for(int counter = paths.length - 1; counter >= 0; 4133 counter--) { 4134 TreePath path = paths[counter].getParentPath(); 4135 boolean expand = true; 4136 4137 while (path != null) { 4138 // Indicates this path isn't valid anymore, 4139 // we shouldn't attempt to expand it then. 4140 if (treeModel.isLeaf(path.getLastPathComponent())){ 4141 expand = false; 4142 path = null; 4143 } 4144 else { 4145 path = path.getParentPath(); 4146 } 4147 } 4148 if (expand) { 4149 tree.makeVisible(paths[counter]); 4150 } 4151 } 4152 } 4153 } 4154 4155 TreePath oldLead = getLeadSelectionPath(); 4156 lastSelectedRow = tree.getMinSelectionRow(); 4157 TreePath lead = tree.getSelectionModel().getLeadSelectionPath(); 4158 setAnchorSelectionPath(lead); 4159 setLeadSelectionPath(lead); 4160 4161 TreePath[] changedPaths = event.getPaths(); 4162 Rectangle nodeBounds; 4163 Rectangle visRect = tree.getVisibleRect(); 4164 boolean paintPaths = true; 4165 int nWidth = tree.getWidth(); 4166 4167 if(changedPaths != null) { 4168 int counter, maxCounter = changedPaths.length; 4169 4170 if(maxCounter > 4) { 4171 tree.repaint(); 4172 paintPaths = false; 4173 } 4174 else { 4175 for (counter = 0; counter < maxCounter; counter++) { 4176 nodeBounds = getPathBounds(tree, 4177 changedPaths[counter]); 4178 if(nodeBounds != null && 4179 visRect.intersects(nodeBounds)) 4180 tree.repaint(0, nodeBounds.y, nWidth, 4181 nodeBounds.height); 4182 } 4183 } 4184 } 4185 if(paintPaths) { 4186 nodeBounds = getPathBounds(tree, oldLead); 4187 if(nodeBounds != null && visRect.intersects(nodeBounds)) 4188 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 4189 nodeBounds = getPathBounds(tree, lead); 4190 if(nodeBounds != null && visRect.intersects(nodeBounds)) 4191 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 4192 } 4193 } 4194 4195 4196 // 4197 // TreeExpansionListener 4198 // 4199 public void treeExpanded(TreeExpansionEvent event) { 4200 if(event != null && tree != null) { 4201 TreePath path = event.getPath(); 4202 4203 updateExpandedDescendants(path); 4204 } 4205 } 4206 4207 public void treeCollapsed(TreeExpansionEvent event) { 4208 if(event != null && tree != null) { 4209 TreePath path = event.getPath(); 4210 4211 completeEditing(); 4212 if(path != null && tree.isVisible(path)) { 4213 treeState.setExpandedState(path, false); 4214 updateLeadSelectionRow(); 4215 updateSize(); 4216 } 4217 } 4218 } 4219 4220 // 4221 // TreeModelListener 4222 // 4223 public void treeNodesChanged(TreeModelEvent e) { 4224 if(treeState != null && e != null) { 4225 TreePath parentPath = SwingUtilities2.getTreePath(e, getModel()); 4226 int[] indices = e.getChildIndices(); 4227 if (indices == null || indices.length == 0) { 4228 // The root has changed 4229 treeState.treeNodesChanged(e); 4230 updateSize(); 4231 } 4232 else if (treeState.isExpanded(parentPath)) { 4233 // Changed nodes are visible 4234 // Find the minimum index, we only need paint from there 4235 // down. 4236 int minIndex = indices[0]; 4237 for (int i = indices.length - 1; i > 0; i--) { 4238 minIndex = Math.min(indices[i], minIndex); 4239 } 4240 Object minChild = treeModel.getChild( 4241 parentPath.getLastPathComponent(), minIndex); 4242 TreePath minPath = parentPath.pathByAddingChild(minChild); 4243 Rectangle minBounds = getPathBounds(tree, minPath); 4244 4245 // Forward to the treestate 4246 treeState.treeNodesChanged(e); 4247 4248 // Mark preferred size as bogus. 4249 updateSize0(); 4250 4251 // And repaint 4252 Rectangle newMinBounds = getPathBounds(tree, minPath); 4253 if (minBounds == null || newMinBounds == null) { 4254 return; 4255 } 4256 4257 if (indices.length == 1 && 4258 newMinBounds.height == minBounds.height) { 4259 tree.repaint(0, minBounds.y, tree.getWidth(), 4260 minBounds.height); 4261 } 4262 else { 4263 tree.repaint(0, minBounds.y, tree.getWidth(), 4264 tree.getHeight() - minBounds.y); 4265 } 4266 } 4267 else { 4268 // Nodes that changed aren't visible. No need to paint 4269 treeState.treeNodesChanged(e); 4270 } 4271 } 4272 } 4273 4274 public void treeNodesInserted(TreeModelEvent e) { 4275 if(treeState != null && e != null) { 4276 treeState.treeNodesInserted(e); 4277 4278 updateLeadSelectionRow(); 4279 4280 TreePath path = SwingUtilities2.getTreePath(e, getModel()); 4281 4282 if(treeState.isExpanded(path)) { 4283 updateSize(); 4284 } 4285 else { 4286 // PENDING(sky): Need a method in TreeModelEvent 4287 // that can return the count, getChildIndices allocs 4288 // a new array! 4289 int[] indices = e.getChildIndices(); 4290 int childCount = treeModel.getChildCount 4291 (path.getLastPathComponent()); 4292 4293 if(indices != null && (childCount - indices.length) == 0) 4294 updateSize(); 4295 } 4296 } 4297 } 4298 4299 public void treeNodesRemoved(TreeModelEvent e) { 4300 if(treeState != null && e != null) { 4301 treeState.treeNodesRemoved(e); 4302 4303 updateLeadSelectionRow(); 4304 4305 TreePath path = SwingUtilities2.getTreePath(e, getModel()); 4306 4307 if(treeState.isExpanded(path) || 4308 treeModel.getChildCount(path.getLastPathComponent()) == 0) 4309 updateSize(); 4310 } 4311 } 4312 4313 public void treeStructureChanged(TreeModelEvent e) { 4314 if(treeState != null && e != null) { 4315 treeState.treeStructureChanged(e); 4316 4317 updateLeadSelectionRow(); 4318 4319 TreePath pPath = SwingUtilities2.getTreePath(e, getModel()); 4320 4321 if (pPath != null) { 4322 pPath = pPath.getParentPath(); 4323 } 4324 if(pPath == null || treeState.isExpanded(pPath)) 4325 updateSize(); 4326 } 4327 } 4328 } 4329 4330 4331 4332 private static class Actions extends UIAction { 4333 private static final String SELECT_PREVIOUS = "selectPrevious"; 4334 private static final String SELECT_PREVIOUS_CHANGE_LEAD = 4335 "selectPreviousChangeLead"; 4336 private static final String SELECT_PREVIOUS_EXTEND_SELECTION = 4337 "selectPreviousExtendSelection"; 4338 private static final String SELECT_NEXT = "selectNext"; 4339 private static final String SELECT_NEXT_CHANGE_LEAD = 4340 "selectNextChangeLead"; 4341 private static final String SELECT_NEXT_EXTEND_SELECTION = 4342 "selectNextExtendSelection"; 4343 private static final String SELECT_CHILD = "selectChild"; 4344 private static final String SELECT_CHILD_CHANGE_LEAD = 4345 "selectChildChangeLead"; 4346 private static final String SELECT_PARENT = "selectParent"; 4347 private static final String SELECT_PARENT_CHANGE_LEAD = 4348 "selectParentChangeLead"; 4349 private static final String SCROLL_UP_CHANGE_SELECTION = 4350 "scrollUpChangeSelection"; 4351 private static final String SCROLL_UP_CHANGE_LEAD = 4352 "scrollUpChangeLead"; 4353 private static final String SCROLL_UP_EXTEND_SELECTION = 4354 "scrollUpExtendSelection"; 4355 private static final String SCROLL_DOWN_CHANGE_SELECTION = 4356 "scrollDownChangeSelection"; 4357 private static final String SCROLL_DOWN_EXTEND_SELECTION = 4358 "scrollDownExtendSelection"; 4359 private static final String SCROLL_DOWN_CHANGE_LEAD = 4360 "scrollDownChangeLead"; 4361 private static final String SELECT_FIRST = "selectFirst"; 4362 private static final String SELECT_FIRST_CHANGE_LEAD = 4363 "selectFirstChangeLead"; 4364 private static final String SELECT_FIRST_EXTEND_SELECTION = 4365 "selectFirstExtendSelection"; 4366 private static final String SELECT_LAST = "selectLast"; 4367 private static final String SELECT_LAST_CHANGE_LEAD = 4368 "selectLastChangeLead"; 4369 private static final String SELECT_LAST_EXTEND_SELECTION = 4370 "selectLastExtendSelection"; 4371 private static final String TOGGLE = "toggle"; 4372 private static final String CANCEL_EDITING = "cancel"; 4373 private static final String START_EDITING = "startEditing"; 4374 private static final String SELECT_ALL = "selectAll"; 4375 private static final String CLEAR_SELECTION = "clearSelection"; 4376 private static final String SCROLL_LEFT = "scrollLeft"; 4377 private static final String SCROLL_RIGHT = "scrollRight"; 4378 private static final String SCROLL_LEFT_EXTEND_SELECTION = 4379 "scrollLeftExtendSelection"; 4380 private static final String SCROLL_RIGHT_EXTEND_SELECTION = 4381 "scrollRightExtendSelection"; 4382 private static final String SCROLL_RIGHT_CHANGE_LEAD = 4383 "scrollRightChangeLead"; 4384 private static final String SCROLL_LEFT_CHANGE_LEAD = 4385 "scrollLeftChangeLead"; 4386 private static final String EXPAND = "expand"; 4387 private static final String COLLAPSE = "collapse"; 4388 private static final String MOVE_SELECTION_TO_PARENT = 4389 "moveSelectionToParent"; 4390 4391 // add the lead item to the selection without changing lead or anchor 4392 private static final String ADD_TO_SELECTION = "addToSelection"; 4393 4394 // toggle the selected state of the lead item and move the anchor to it 4395 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor"; 4396 4397 // extend the selection to the lead item 4398 private static final String EXTEND_TO = "extendTo"; 4399 4400 // move the anchor to the lead and ensure only that item is selected 4401 private static final String MOVE_SELECTION_TO = "moveSelectionTo"; 4402 4403 Actions() { 4404 super(null); 4405 } 4406 4407 Actions(String key) { 4408 super(key); 4409 } 4410 4411 public boolean isEnabled(Object o) { 4412 if (o instanceof JTree) { 4413 if (getName() == CANCEL_EDITING) { 4414 return ((JTree)o).isEditing(); 4415 } 4416 } 4417 return true; 4418 } 4419 4420 public void actionPerformed(ActionEvent e) { 4421 JTree tree = (JTree)e.getSource(); 4422 BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType( 4423 tree.getUI(), BasicTreeUI.class); 4424 if (ui == null) { 4425 return; 4426 } 4427 String key = getName(); 4428 if (key == SELECT_PREVIOUS) { 4429 increment(tree, ui, -1, false, true); 4430 } 4431 else if (key == SELECT_PREVIOUS_CHANGE_LEAD) { 4432 increment(tree, ui, -1, false, false); 4433 } 4434 else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) { 4435 increment(tree, ui, -1, true, true); 4436 } 4437 else if (key == SELECT_NEXT) { 4438 increment(tree, ui, 1, false, true); 4439 } 4440 else if (key == SELECT_NEXT_CHANGE_LEAD) { 4441 increment(tree, ui, 1, false, false); 4442 } 4443 else if (key == SELECT_NEXT_EXTEND_SELECTION) { 4444 increment(tree, ui, 1, true, true); 4445 } 4446 else if (key == SELECT_CHILD) { 4447 traverse(tree, ui, 1, true); 4448 } 4449 else if (key == SELECT_CHILD_CHANGE_LEAD) { 4450 traverse(tree, ui, 1, false); 4451 } 4452 else if (key == SELECT_PARENT) { 4453 traverse(tree, ui, -1, true); 4454 } 4455 else if (key == SELECT_PARENT_CHANGE_LEAD) { 4456 traverse(tree, ui, -1, false); 4457 } 4458 else if (key == SCROLL_UP_CHANGE_SELECTION) { 4459 page(tree, ui, -1, false, true); 4460 } 4461 else if (key == SCROLL_UP_CHANGE_LEAD) { 4462 page(tree, ui, -1, false, false); 4463 } 4464 else if (key == SCROLL_UP_EXTEND_SELECTION) { 4465 page(tree, ui, -1, true, true); 4466 } 4467 else if (key == SCROLL_DOWN_CHANGE_SELECTION) { 4468 page(tree, ui, 1, false, true); 4469 } 4470 else if (key == SCROLL_DOWN_EXTEND_SELECTION) { 4471 page(tree, ui, 1, true, true); 4472 } 4473 else if (key == SCROLL_DOWN_CHANGE_LEAD) { 4474 page(tree, ui, 1, false, false); 4475 } 4476 else if (key == SELECT_FIRST) { 4477 home(tree, ui, -1, false, true); 4478 } 4479 else if (key == SELECT_FIRST_CHANGE_LEAD) { 4480 home(tree, ui, -1, false, false); 4481 } 4482 else if (key == SELECT_FIRST_EXTEND_SELECTION) { 4483 home(tree, ui, -1, true, true); 4484 } 4485 else if (key == SELECT_LAST) { 4486 home(tree, ui, 1, false, true); 4487 } 4488 else if (key == SELECT_LAST_CHANGE_LEAD) { 4489 home(tree, ui, 1, false, false); 4490 } 4491 else if (key == SELECT_LAST_EXTEND_SELECTION) { 4492 home(tree, ui, 1, true, true); 4493 } 4494 else if (key == TOGGLE) { 4495 toggle(tree, ui); 4496 } 4497 else if (key == CANCEL_EDITING) { 4498 cancelEditing(tree, ui); 4499 } 4500 else if (key == START_EDITING) { 4501 startEditing(tree, ui); 4502 } 4503 else if (key == SELECT_ALL) { 4504 selectAll(tree, ui, true); 4505 } 4506 else if (key == CLEAR_SELECTION) { 4507 selectAll(tree, ui, false); 4508 } 4509 else if (key == ADD_TO_SELECTION) { 4510 if (ui.getRowCount(tree) > 0) { 4511 int lead = ui.getLeadSelectionRow(); 4512 if (!tree.isRowSelected(lead)) { 4513 TreePath aPath = ui.getAnchorSelectionPath(); 4514 tree.addSelectionRow(lead); 4515 ui.setAnchorSelectionPath(aPath); 4516 } 4517 } 4518 } 4519 else if (key == TOGGLE_AND_ANCHOR) { 4520 if (ui.getRowCount(tree) > 0) { 4521 int lead = ui.getLeadSelectionRow(); 4522 TreePath lPath = ui.getLeadSelectionPath(); 4523 if (!tree.isRowSelected(lead)) { 4524 tree.addSelectionRow(lead); 4525 } else { 4526 tree.removeSelectionRow(lead); 4527 ui.setLeadSelectionPath(lPath); 4528 } 4529 ui.setAnchorSelectionPath(lPath); 4530 } 4531 } 4532 else if (key == EXTEND_TO) { 4533 extendSelection(tree, ui); 4534 } 4535 else if (key == MOVE_SELECTION_TO) { 4536 if (ui.getRowCount(tree) > 0) { 4537 int lead = ui.getLeadSelectionRow(); 4538 tree.setSelectionInterval(lead, lead); 4539 } 4540 } 4541 else if (key == SCROLL_LEFT) { 4542 scroll(tree, ui, SwingConstants.HORIZONTAL, -10); 4543 } 4544 else if (key == SCROLL_RIGHT) { 4545 scroll(tree, ui, SwingConstants.HORIZONTAL, 10); 4546 } 4547 else if (key == SCROLL_LEFT_EXTEND_SELECTION) { 4548 scrollChangeSelection(tree, ui, -1, true, true); 4549 } 4550 else if (key == SCROLL_RIGHT_EXTEND_SELECTION) { 4551 scrollChangeSelection(tree, ui, 1, true, true); 4552 } 4553 else if (key == SCROLL_RIGHT_CHANGE_LEAD) { 4554 scrollChangeSelection(tree, ui, 1, false, false); 4555 } 4556 else if (key == SCROLL_LEFT_CHANGE_LEAD) { 4557 scrollChangeSelection(tree, ui, -1, false, false); 4558 } 4559 else if (key == EXPAND) { 4560 expand(tree, ui); 4561 } 4562 else if (key == COLLAPSE) { 4563 collapse(tree, ui); 4564 } 4565 else if (key == MOVE_SELECTION_TO_PARENT) { 4566 moveSelectionToParent(tree, ui); 4567 } 4568 } 4569 4570 private void scrollChangeSelection(JTree tree, BasicTreeUI ui, 4571 int direction, boolean addToSelection, 4572 boolean changeSelection) { 4573 int rowCount; 4574 4575 if((rowCount = ui.getRowCount(tree)) > 0 && 4576 ui.treeSelectionModel != null) { 4577 TreePath newPath; 4578 Rectangle visRect = tree.getVisibleRect(); 4579 4580 if (direction == -1) { 4581 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4582 visRect.y); 4583 visRect.x = Math.max(0, visRect.x - visRect.width); 4584 } 4585 else { 4586 visRect.x = Math.min(Math.max(0, tree.getWidth() - 4587 visRect.width), visRect.x + visRect.width); 4588 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4589 visRect.y + visRect.height); 4590 } 4591 // Scroll 4592 tree.scrollRectToVisible(visRect); 4593 // select 4594 if (addToSelection) { 4595 ui.extendSelection(newPath); 4596 } 4597 else if(changeSelection) { 4598 tree.setSelectionPath(newPath); 4599 } 4600 else { 4601 ui.setLeadSelectionPath(newPath, true); 4602 } 4603 } 4604 } 4605 4606 private void scroll(JTree component, BasicTreeUI ui, int direction, 4607 int amount) { 4608 Rectangle visRect = component.getVisibleRect(); 4609 Dimension size = component.getSize(); 4610 if (direction == SwingConstants.HORIZONTAL) { 4611 visRect.x += amount; 4612 visRect.x = Math.max(0, visRect.x); 4613 visRect.x = Math.min(Math.max(0, size.width - visRect.width), 4614 visRect.x); 4615 } 4616 else { 4617 visRect.y += amount; 4618 visRect.y = Math.max(0, visRect.y); 4619 visRect.y = Math.min(Math.max(0, size.width - visRect.height), 4620 visRect.y); 4621 } 4622 component.scrollRectToVisible(visRect); 4623 } 4624 4625 private void extendSelection(JTree tree, BasicTreeUI ui) { 4626 if (ui.getRowCount(tree) > 0) { 4627 int lead = ui.getLeadSelectionRow(); 4628 4629 if (lead != -1) { 4630 TreePath leadP = ui.getLeadSelectionPath(); 4631 TreePath aPath = ui.getAnchorSelectionPath(); 4632 int aRow = ui.getRowForPath(tree, aPath); 4633 4634 if(aRow == -1) 4635 aRow = 0; 4636 tree.setSelectionInterval(aRow, lead); 4637 ui.setLeadSelectionPath(leadP); 4638 ui.setAnchorSelectionPath(aPath); 4639 } 4640 } 4641 } 4642 4643 private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) { 4644 int rowCount = ui.getRowCount(tree); 4645 4646 if(rowCount > 0) { 4647 if(selectAll) { 4648 if (tree.getSelectionModel().getSelectionMode() == 4649 TreeSelectionModel.SINGLE_TREE_SELECTION) { 4650 4651 int lead = ui.getLeadSelectionRow(); 4652 if (lead != -1) { 4653 tree.setSelectionRow(lead); 4654 } else if (tree.getMinSelectionRow() == -1) { 4655 tree.setSelectionRow(0); 4656 ui.ensureRowsAreVisible(0, 0); 4657 } 4658 return; 4659 } 4660 4661 TreePath lastPath = ui.getLeadSelectionPath(); 4662 TreePath aPath = ui.getAnchorSelectionPath(); 4663 4664 if(lastPath != null && !tree.isVisible(lastPath)) { 4665 lastPath = null; 4666 } 4667 tree.setSelectionInterval(0, rowCount - 1); 4668 if(lastPath != null) { 4669 ui.setLeadSelectionPath(lastPath); 4670 } 4671 if(aPath != null && tree.isVisible(aPath)) { 4672 ui.setAnchorSelectionPath(aPath); 4673 } 4674 } 4675 else { 4676 TreePath lastPath = ui.getLeadSelectionPath(); 4677 TreePath aPath = ui.getAnchorSelectionPath(); 4678 4679 tree.clearSelection(); 4680 ui.setAnchorSelectionPath(aPath); 4681 ui.setLeadSelectionPath(lastPath); 4682 } 4683 } 4684 } 4685 4686 private void startEditing(JTree tree, BasicTreeUI ui) { 4687 TreePath lead = ui.getLeadSelectionPath(); 4688 int editRow = (lead != null) ? 4689 ui.getRowForPath(tree, lead) : -1; 4690 4691 if(editRow != -1) { 4692 tree.startEditingAtPath(lead); 4693 } 4694 } 4695 4696 private void cancelEditing(JTree tree, BasicTreeUI ui) { 4697 tree.cancelEditing(); 4698 } 4699 4700 private void toggle(JTree tree, BasicTreeUI ui) { 4701 int selRow = ui.getLeadSelectionRow(); 4702 4703 if(selRow != -1 && !ui.isLeaf(selRow)) { 4704 TreePath aPath = ui.getAnchorSelectionPath(); 4705 TreePath lPath = ui.getLeadSelectionPath(); 4706 4707 ui.toggleExpandState(ui.getPathForRow(tree, selRow)); 4708 ui.setAnchorSelectionPath(aPath); 4709 ui.setLeadSelectionPath(lPath); 4710 } 4711 } 4712 4713 private void expand(JTree tree, BasicTreeUI ui) { 4714 int selRow = ui.getLeadSelectionRow(); 4715 tree.expandRow(selRow); 4716 } 4717 4718 private void collapse(JTree tree, BasicTreeUI ui) { 4719 int selRow = ui.getLeadSelectionRow(); 4720 tree.collapseRow(selRow); 4721 } 4722 4723 private void increment(JTree tree, BasicTreeUI ui, int direction, 4724 boolean addToSelection, 4725 boolean changeSelection) { 4726 4727 // disable moving of lead unless in discontiguous mode 4728 if (!addToSelection && !changeSelection && 4729 tree.getSelectionModel().getSelectionMode() != 4730 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4731 changeSelection = true; 4732 } 4733 4734 int rowCount; 4735 4736 if(ui.treeSelectionModel != null && 4737 (rowCount = tree.getRowCount()) > 0) { 4738 int selIndex = ui.getLeadSelectionRow(); 4739 int newIndex; 4740 4741 if(selIndex == -1) { 4742 if(direction == 1) 4743 newIndex = 0; 4744 else 4745 newIndex = rowCount - 1; 4746 } 4747 else 4748 /* Aparently people don't like wrapping;( */ 4749 newIndex = Math.min(rowCount - 1, Math.max 4750 (0, (selIndex + direction))); 4751 if(addToSelection && ui.treeSelectionModel. 4752 getSelectionMode() != TreeSelectionModel. 4753 SINGLE_TREE_SELECTION) { 4754 ui.extendSelection(tree.getPathForRow(newIndex)); 4755 } 4756 else if(changeSelection) { 4757 tree.setSelectionInterval(newIndex, newIndex); 4758 } 4759 else { 4760 ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true); 4761 } 4762 ui.ensureRowsAreVisible(newIndex, newIndex); 4763 ui.lastSelectedRow = newIndex; 4764 } 4765 } 4766 4767 private void traverse(JTree tree, BasicTreeUI ui, int direction, 4768 boolean changeSelection) { 4769 4770 // disable moving of lead unless in discontiguous mode 4771 if (!changeSelection && 4772 tree.getSelectionModel().getSelectionMode() != 4773 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4774 changeSelection = true; 4775 } 4776 4777 int rowCount; 4778 4779 if((rowCount = tree.getRowCount()) > 0) { 4780 int minSelIndex = ui.getLeadSelectionRow(); 4781 int newIndex; 4782 4783 if(minSelIndex == -1) 4784 newIndex = 0; 4785 else { 4786 /* Try and expand the node, otherwise go to next 4787 node. */ 4788 if(direction == 1) { 4789 TreePath minSelPath = ui.getPathForRow(tree, minSelIndex); 4790 int childCount = tree.getModel(). 4791 getChildCount(minSelPath.getLastPathComponent()); 4792 newIndex = -1; 4793 if (!ui.isLeaf(minSelIndex)) { 4794 if (!tree.isExpanded(minSelIndex)) { 4795 ui.toggleExpandState(minSelPath); 4796 } 4797 else if (childCount > 0) { 4798 newIndex = Math.min(minSelIndex + 1, rowCount - 1); 4799 } 4800 } 4801 } 4802 /* Try to collapse node. */ 4803 else { 4804 if(!ui.isLeaf(minSelIndex) && 4805 tree.isExpanded(minSelIndex)) { 4806 ui.toggleExpandState(ui.getPathForRow 4807 (tree, minSelIndex)); 4808 newIndex = -1; 4809 } 4810 else { 4811 TreePath path = ui.getPathForRow(tree, 4812 minSelIndex); 4813 4814 if(path != null && path.getPathCount() > 1) { 4815 newIndex = ui.getRowForPath(tree, path. 4816 getParentPath()); 4817 } 4818 else 4819 newIndex = -1; 4820 } 4821 } 4822 } 4823 if(newIndex != -1) { 4824 if(changeSelection) { 4825 tree.setSelectionInterval(newIndex, newIndex); 4826 } 4827 else { 4828 ui.setLeadSelectionPath(ui.getPathForRow( 4829 tree, newIndex), true); 4830 } 4831 ui.ensureRowsAreVisible(newIndex, newIndex); 4832 } 4833 } 4834 } 4835 4836 private void moveSelectionToParent(JTree tree, BasicTreeUI ui) { 4837 int selRow = ui.getLeadSelectionRow(); 4838 TreePath path = ui.getPathForRow(tree, selRow); 4839 if (path != null && path.getPathCount() > 1) { 4840 int newIndex = ui.getRowForPath(tree, path.getParentPath()); 4841 if (newIndex != -1) { 4842 tree.setSelectionInterval(newIndex, newIndex); 4843 ui.ensureRowsAreVisible(newIndex, newIndex); 4844 } 4845 } 4846 } 4847 4848 private void page(JTree tree, BasicTreeUI ui, int direction, 4849 boolean addToSelection, boolean changeSelection) { 4850 4851 // disable moving of lead unless in discontiguous mode 4852 if (!addToSelection && !changeSelection && 4853 tree.getSelectionModel().getSelectionMode() != 4854 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4855 changeSelection = true; 4856 } 4857 4858 int rowCount; 4859 4860 if((rowCount = ui.getRowCount(tree)) > 0 && 4861 ui.treeSelectionModel != null) { 4862 Dimension maxSize = tree.getSize(); 4863 TreePath lead = ui.getLeadSelectionPath(); 4864 TreePath newPath; 4865 Rectangle visRect = tree.getVisibleRect(); 4866 4867 if(direction == -1) { 4868 // up. 4869 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4870 visRect.y); 4871 if(newPath.equals(lead)) { 4872 visRect.y = Math.max(0, visRect.y - visRect.height); 4873 newPath = tree.getClosestPathForLocation(visRect.x, 4874 visRect.y); 4875 } 4876 } 4877 else { 4878 // down 4879 visRect.y = Math.min(maxSize.height, visRect.y + 4880 visRect.height - 1); 4881 newPath = tree.getClosestPathForLocation(visRect.x, 4882 visRect.y); 4883 if(newPath.equals(lead)) { 4884 visRect.y = Math.min(maxSize.height, visRect.y + 4885 visRect.height - 1); 4886 newPath = tree.getClosestPathForLocation(visRect.x, 4887 visRect.y); 4888 } 4889 } 4890 Rectangle newRect = ui.getPathBounds(tree, newPath); 4891 if (newRect != null) { 4892 newRect.x = visRect.x; 4893 newRect.width = visRect.width; 4894 if(direction == -1) { 4895 newRect.height = visRect.height; 4896 } 4897 else { 4898 newRect.y -= (visRect.height - newRect.height); 4899 newRect.height = visRect.height; 4900 } 4901 4902 if(addToSelection) { 4903 ui.extendSelection(newPath); 4904 } 4905 else if(changeSelection) { 4906 tree.setSelectionPath(newPath); 4907 } 4908 else { 4909 ui.setLeadSelectionPath(newPath, true); 4910 } 4911 tree.scrollRectToVisible(newRect); 4912 } 4913 } 4914 } 4915 4916 private void home(JTree tree, final BasicTreeUI ui, int direction, 4917 boolean addToSelection, boolean changeSelection) { 4918 4919 // disable moving of lead unless in discontiguous mode 4920 if (!addToSelection && !changeSelection && 4921 tree.getSelectionModel().getSelectionMode() != 4922 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4923 changeSelection = true; 4924 } 4925 4926 final int rowCount = ui.getRowCount(tree); 4927 4928 if (rowCount > 0) { 4929 if(direction == -1) { 4930 ui.ensureRowsAreVisible(0, 0); 4931 if (addToSelection) { 4932 TreePath aPath = ui.getAnchorSelectionPath(); 4933 int aRow = (aPath == null) ? -1 : 4934 ui.getRowForPath(tree, aPath); 4935 4936 if (aRow == -1) { 4937 tree.setSelectionInterval(0, 0); 4938 } 4939 else { 4940 tree.setSelectionInterval(0, aRow); 4941 ui.setAnchorSelectionPath(aPath); 4942 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0)); 4943 } 4944 } 4945 else if(changeSelection) { 4946 tree.setSelectionInterval(0, 0); 4947 } 4948 else { 4949 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0), 4950 true); 4951 } 4952 } 4953 else { 4954 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); 4955 if (addToSelection) { 4956 TreePath aPath = ui.getAnchorSelectionPath(); 4957 int aRow = (aPath == null) ? -1 : 4958 ui.getRowForPath(tree, aPath); 4959 4960 if (aRow == -1) { 4961 tree.setSelectionInterval(rowCount - 1, 4962 rowCount -1); 4963 } 4964 else { 4965 tree.setSelectionInterval(aRow, rowCount - 1); 4966 ui.setAnchorSelectionPath(aPath); 4967 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4968 rowCount -1)); 4969 } 4970 } 4971 else if(changeSelection) { 4972 tree.setSelectionInterval(rowCount - 1, rowCount - 1); 4973 } 4974 else { 4975 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4976 rowCount - 1), true); 4977 } 4978 if (ui.isLargeModel()){ 4979 SwingUtilities.invokeLater(new Runnable() { 4980 public void run() { 4981 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); 4982 } 4983 }); 4984 } 4985 } 4986 } 4987 } 4988 } 4989 } // End of class BasicTreeUI