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 protected void completeEditing(boolean messageStop, 2367 boolean messageCancel, 2368 boolean messageTree) { 2369 if(stopEditingInCompleteEditing && editingComponent != null) { 2370 Component oldComponent = editingComponent; 2371 TreePath oldPath = editingPath; 2372 TreeCellEditor oldEditor = cellEditor; 2373 Object newValue = oldEditor.getCellEditorValue(); 2374 Rectangle editingBounds = getPathBounds(tree, 2375 editingPath); 2376 boolean requestFocus = (tree != null && 2377 (tree.hasFocus() || SwingUtilities. 2378 findFocusOwner(editingComponent) != null)); 2379 2380 editingComponent = null; 2381 editingPath = null; 2382 if(messageStop) 2383 oldEditor.stopCellEditing(); 2384 else if(messageCancel) 2385 oldEditor.cancelCellEditing(); 2386 tree.remove(oldComponent); 2387 if(editorHasDifferentSize) { 2388 treeState.invalidatePathBounds(oldPath); 2389 updateSize(); 2390 } 2391 else if (editingBounds != null) { 2392 editingBounds.x = 0; 2393 editingBounds.width = tree.getSize().width; 2394 tree.repaint(editingBounds); 2395 } 2396 if(requestFocus) 2397 tree.requestFocus(); 2398 if(messageTree) 2399 treeModel.valueForPathChanged(oldPath, newValue); 2400 } 2401 } 2402 2403 // cover method for startEditing that allows us to pass extra 2404 // information into that method via a class variable 2405 private boolean startEditingOnRelease(TreePath path, 2406 MouseEvent event, 2407 MouseEvent releaseEvent) { 2408 this.releaseEvent = releaseEvent; 2409 try { 2410 return startEditing(path, event); 2411 } finally { 2412 this.releaseEvent = null; 2413 } 2414 } 2415 2416 /** 2417 * Will start editing for node if there is a {@code cellEditor} and 2418 * {@code shouldSelectCell} returns {@code true}.<p> 2419 * This assumes that path is valid and visible. 2420 * 2421 * @param path a tree path 2422 * @param event a mouse event 2423 * @return {@code true} if the editing is successful 2424 */ 2425 protected boolean startEditing(TreePath path, MouseEvent event) { 2426 if (isEditing(tree) && tree.getInvokesStopCellEditing() && 2427 !stopEditing(tree)) { 2428 return false; 2429 } 2430 completeEditing(); 2431 if(cellEditor != null && tree.isPathEditable(path)) { 2432 int row = getRowForPath(tree, path); 2433 2434 if(cellEditor.isCellEditable(event)) { 2435 editingComponent = cellEditor.getTreeCellEditorComponent 2436 (tree, path.getLastPathComponent(), 2437 tree.isPathSelected(path), tree.isExpanded(path), 2438 treeModel.isLeaf(path.getLastPathComponent()), row); 2439 Rectangle nodeBounds = getPathBounds(tree, path); 2440 if (nodeBounds == null) { 2441 return false; 2442 } 2443 2444 editingRow = row; 2445 2446 Dimension editorSize = editingComponent.getPreferredSize(); 2447 2448 // Only allow odd heights if explicitly set. 2449 if(editorSize.height != nodeBounds.height && 2450 getRowHeight() > 0) 2451 editorSize.height = getRowHeight(); 2452 2453 if(editorSize.width != nodeBounds.width || 2454 editorSize.height != nodeBounds.height) { 2455 // Editor wants different width or height, invalidate 2456 // treeState and relayout. 2457 editorHasDifferentSize = true; 2458 treeState.invalidatePathBounds(path); 2459 updateSize(); 2460 // To make sure x/y are updated correctly, fetch 2461 // the bounds again. 2462 nodeBounds = getPathBounds(tree, path); 2463 if (nodeBounds == null) { 2464 return false; 2465 } 2466 } 2467 else 2468 editorHasDifferentSize = false; 2469 tree.add(editingComponent); 2470 editingComponent.setBounds(nodeBounds.x, nodeBounds.y, 2471 nodeBounds.width, 2472 nodeBounds.height); 2473 editingPath = path; 2474 AWTAccessor.getComponentAccessor().revalidateSynchronously(editingComponent); 2475 editingComponent.repaint(); 2476 if(cellEditor.shouldSelectCell(event)) { 2477 stopEditingInCompleteEditing = false; 2478 tree.setSelectionRow(row); 2479 stopEditingInCompleteEditing = true; 2480 } 2481 2482 Component focusedComponent = SwingUtilities2. 2483 compositeRequestFocus(editingComponent); 2484 boolean selectAll = true; 2485 2486 if(event != null) { 2487 /* Find the component that will get forwarded all the 2488 mouse events until mouseReleased. */ 2489 Point componentPoint = SwingUtilities.convertPoint 2490 (tree, new Point(event.getX(), event.getY()), 2491 editingComponent); 2492 2493 /* Create an instance of BasicTreeMouseListener to handle 2494 passing the mouse/motion events to the necessary 2495 component. */ 2496 // We really want similar behavior to getMouseEventTarget, 2497 // but it is package private. 2498 Component activeComponent = SwingUtilities. 2499 getDeepestComponentAt(editingComponent, 2500 componentPoint.x, componentPoint.y); 2501 if (activeComponent != null) { 2502 MouseInputHandler handler = 2503 new MouseInputHandler(tree, activeComponent, 2504 event, focusedComponent); 2505 2506 if (releaseEvent != null) { 2507 handler.mouseReleased(releaseEvent); 2508 } 2509 2510 selectAll = false; 2511 } 2512 } 2513 if (selectAll && focusedComponent instanceof JTextField) { 2514 ((JTextField)focusedComponent).selectAll(); 2515 } 2516 return true; 2517 } 2518 else 2519 editingComponent = null; 2520 } 2521 return false; 2522 } 2523 2524 // 2525 // Following are primarily for handling mouse events. 2526 // 2527 2528 /** 2529 * If the {@code mouseX} and {@code mouseY} are in the 2530 * expand/collapse region of the {@code row}, this will toggle 2531 * the row. 2532 * 2533 * @param path a tree path 2534 * @param mouseX an X coordinate 2535 * @param mouseY an Y coordinate 2536 */ 2537 protected void checkForClickInExpandControl(TreePath path, 2538 int mouseX, int mouseY) { 2539 if (isLocationInExpandControl(path, mouseX, mouseY)) { 2540 handleExpandControlClick(path, mouseX, mouseY); 2541 } 2542 } 2543 2544 /** 2545 * Returns {@code true} if {@code mouseX} and {@code mouseY} fall 2546 * in the area of row that is used to expand/collapse the node and 2547 * the node at {@code row} does not represent a leaf. 2548 * 2549 * @param path a tree path 2550 * @param mouseX an X coordinate 2551 * @param mouseY an Y coordinate 2552 * @return {@code true} if the mouse cursor fall in the area of row that 2553 * is used to expand/collapse the node and the node is not a leaf. 2554 */ 2555 protected boolean isLocationInExpandControl(TreePath path, 2556 int mouseX, int mouseY) { 2557 if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){ 2558 int boxWidth; 2559 Insets i = tree.getInsets(); 2560 2561 if(getExpandedIcon() != null) 2562 boxWidth = getExpandedIcon().getIconWidth(); 2563 else 2564 boxWidth = 8; 2565 2566 int boxLeftX = getRowX(tree.getRowForPath(path), 2567 path.getPathCount() - 1); 2568 2569 if (leftToRight) { 2570 boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1; 2571 } else { 2572 boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1; 2573 } 2574 2575 boxLeftX = findCenteredX(boxLeftX, boxWidth); 2576 2577 return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth)); 2578 } 2579 return false; 2580 } 2581 2582 /** 2583 * Messaged when the user clicks the particular row, this invokes 2584 * {@code toggleExpandState}. 2585 * 2586 * @param path a tree path 2587 * @param mouseX an X coordinate 2588 * @param mouseY an Y coordinate 2589 */ 2590 protected void handleExpandControlClick(TreePath path, int mouseX, 2591 int mouseY) { 2592 toggleExpandState(path); 2593 } 2594 2595 /** 2596 * Expands path if it is not expanded, or collapses row if it is expanded. 2597 * If expanding a path and {@code JTree} scrolls on expand, 2598 * {@code ensureRowsAreVisible} is invoked to scroll as many of the children 2599 * to visible as possible (tries to scroll to last visible descendant of path). 2600 * 2601 * @param path a tree path 2602 */ 2603 protected void toggleExpandState(TreePath path) { 2604 if(!tree.isExpanded(path)) { 2605 int row = getRowForPath(tree, path); 2606 2607 tree.expandPath(path); 2608 updateSize(); 2609 if(row != -1) { 2610 if(tree.getScrollsOnExpand()) 2611 ensureRowsAreVisible(row, row + treeState. 2612 getVisibleChildCount(path)); 2613 else 2614 ensureRowsAreVisible(row, row); 2615 } 2616 } 2617 else { 2618 tree.collapsePath(path); 2619 updateSize(); 2620 } 2621 } 2622 2623 /** 2624 * Returning {@code true} signifies a mouse event on the node should toggle 2625 * the selection of only the row under mouse. 2626 * 2627 * @param event a mouse event 2628 * @return {@code true} if a mouse event on the node should toggle the selection 2629 */ 2630 protected boolean isToggleSelectionEvent(MouseEvent event) { 2631 return (SwingUtilities.isLeftMouseButton(event) && 2632 BasicGraphicsUtils.isMenuShortcutKeyDown(event)); 2633 } 2634 2635 /** 2636 * Returning {@code true} signifies a mouse event on the node should select 2637 * from the anchor point. 2638 * 2639 * @param event a mouse event 2640 * @return {@code true} if a mouse event on the node should select 2641 * from the anchor point 2642 */ 2643 protected boolean isMultiSelectEvent(MouseEvent event) { 2644 return (SwingUtilities.isLeftMouseButton(event) && 2645 event.isShiftDown()); 2646 } 2647 2648 /** 2649 * Returning {@code true} indicates the row under the mouse should be toggled 2650 * based on the event. This is invoked after {@code checkForClickInExpandControl}, 2651 * implying the location is not in the expand (toggle) control. 2652 * 2653 * @param event a mouse event 2654 * @return {@code true} if the row under the mouse should be toggled 2655 */ 2656 protected boolean isToggleEvent(MouseEvent event) { 2657 if(!SwingUtilities.isLeftMouseButton(event)) { 2658 return false; 2659 } 2660 int clickCount = tree.getToggleClickCount(); 2661 2662 if(clickCount <= 0) { 2663 return false; 2664 } 2665 return ((event.getClickCount() % clickCount) == 0); 2666 } 2667 2668 /** 2669 * Messaged to update the selection based on a {@code MouseEvent} over a 2670 * particular row. If the event is a toggle selection event, the 2671 * row is either selected, or deselected. If the event identifies 2672 * a multi selection event, the selection is updated from the 2673 * anchor point. Otherwise the row is selected, and if the event 2674 * specified a toggle event the row is expanded/collapsed. 2675 * 2676 * @param path the selected path 2677 * @param event the mouse event 2678 */ 2679 protected void selectPathForEvent(TreePath path, MouseEvent event) { 2680 /* Adjust from the anchor point. */ 2681 if(isMultiSelectEvent(event)) { 2682 TreePath anchor = getAnchorSelectionPath(); 2683 int anchorRow = (anchor == null) ? -1 : 2684 getRowForPath(tree, anchor); 2685 2686 if(anchorRow == -1 || tree.getSelectionModel(). 2687 getSelectionMode() == TreeSelectionModel. 2688 SINGLE_TREE_SELECTION) { 2689 tree.setSelectionPath(path); 2690 } 2691 else { 2692 int row = getRowForPath(tree, path); 2693 TreePath lastAnchorPath = anchor; 2694 2695 if (isToggleSelectionEvent(event)) { 2696 if (tree.isRowSelected(anchorRow)) { 2697 tree.addSelectionInterval(anchorRow, row); 2698 } else { 2699 tree.removeSelectionInterval(anchorRow, row); 2700 tree.addSelectionInterval(row, row); 2701 } 2702 } else if(row < anchorRow) { 2703 tree.setSelectionInterval(row, anchorRow); 2704 } else { 2705 tree.setSelectionInterval(anchorRow, row); 2706 } 2707 lastSelectedRow = row; 2708 setAnchorSelectionPath(lastAnchorPath); 2709 setLeadSelectionPath(path); 2710 } 2711 } 2712 2713 // Should this event toggle the selection of this row? 2714 /* Control toggles just this node. */ 2715 else if(isToggleSelectionEvent(event)) { 2716 if(tree.isPathSelected(path)) 2717 tree.removeSelectionPath(path); 2718 else 2719 tree.addSelectionPath(path); 2720 lastSelectedRow = getRowForPath(tree, path); 2721 setAnchorSelectionPath(path); 2722 setLeadSelectionPath(path); 2723 } 2724 2725 /* Otherwise set the selection to just this interval. */ 2726 else if(SwingUtilities.isLeftMouseButton(event)) { 2727 tree.setSelectionPath(path); 2728 if(isToggleEvent(event)) { 2729 toggleExpandState(path); 2730 } 2731 } 2732 } 2733 2734 /** 2735 * Returns {@code true} if the node at {@code row} is a leaf. 2736 * 2737 * @param row a row 2738 * @return {@code true} if the node at {@code row} is a leaf 2739 */ 2740 protected boolean isLeaf(int row) { 2741 TreePath path = getPathForRow(tree, row); 2742 2743 if(path != null) 2744 return treeModel.isLeaf(path.getLastPathComponent()); 2745 // Have to return something here... 2746 return true; 2747 } 2748 2749 // 2750 // The following selection methods (lead/anchor) are covers for the 2751 // methods in JTree. 2752 // 2753 private void setAnchorSelectionPath(TreePath newPath) { 2754 ignoreLAChange = true; 2755 try { 2756 tree.setAnchorSelectionPath(newPath); 2757 } finally{ 2758 ignoreLAChange = false; 2759 } 2760 } 2761 2762 private TreePath getAnchorSelectionPath() { 2763 return tree.getAnchorSelectionPath(); 2764 } 2765 2766 private void setLeadSelectionPath(TreePath newPath) { 2767 setLeadSelectionPath(newPath, false); 2768 } 2769 2770 private void setLeadSelectionPath(TreePath newPath, boolean repaint) { 2771 Rectangle bounds = repaint ? 2772 getPathBounds(tree, getLeadSelectionPath()) : null; 2773 2774 ignoreLAChange = true; 2775 try { 2776 tree.setLeadSelectionPath(newPath); 2777 } finally { 2778 ignoreLAChange = false; 2779 } 2780 leadRow = getRowForPath(tree, newPath); 2781 2782 if (repaint) { 2783 if (bounds != null) { 2784 tree.repaint(getRepaintPathBounds(bounds)); 2785 } 2786 bounds = getPathBounds(tree, newPath); 2787 if (bounds != null) { 2788 tree.repaint(getRepaintPathBounds(bounds)); 2789 } 2790 } 2791 } 2792 2793 private Rectangle getRepaintPathBounds(Rectangle bounds) { 2794 if (UIManager.getBoolean("Tree.repaintWholeRow")) { 2795 bounds.x = 0; 2796 bounds.width = tree.getWidth(); 2797 } 2798 return bounds; 2799 } 2800 2801 private TreePath getLeadSelectionPath() { 2802 return tree.getLeadSelectionPath(); 2803 } 2804 2805 /** 2806 * Updates the lead row of the selection. 2807 * @since 1.7 2808 */ 2809 protected void updateLeadSelectionRow() { 2810 leadRow = getRowForPath(tree, getLeadSelectionPath()); 2811 } 2812 2813 /** 2814 * Returns the lead row of the selection. 2815 * 2816 * @return selection lead row 2817 * @since 1.7 2818 */ 2819 protected int getLeadSelectionRow() { 2820 return leadRow; 2821 } 2822 2823 /** 2824 * Extends the selection from the anchor to make <code>newLead</code> 2825 * the lead of the selection. This does not scroll. 2826 */ 2827 private void extendSelection(TreePath newLead) { 2828 TreePath aPath = getAnchorSelectionPath(); 2829 int aRow = (aPath == null) ? -1 : 2830 getRowForPath(tree, aPath); 2831 int newIndex = getRowForPath(tree, newLead); 2832 2833 if(aRow == -1) { 2834 tree.setSelectionRow(newIndex); 2835 } 2836 else { 2837 if(aRow < newIndex) { 2838 tree.setSelectionInterval(aRow, newIndex); 2839 } 2840 else { 2841 tree.setSelectionInterval(newIndex, aRow); 2842 } 2843 setAnchorSelectionPath(aPath); 2844 setLeadSelectionPath(newLead); 2845 } 2846 } 2847 2848 /** 2849 * Invokes <code>repaint</code> on the JTree for the passed in TreePath, 2850 * <code>path</code>. 2851 */ 2852 private void repaintPath(TreePath path) { 2853 if (path != null) { 2854 Rectangle bounds = getPathBounds(tree, path); 2855 if (bounds != null) { 2856 tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height); 2857 } 2858 } 2859 } 2860 2861 /** 2862 * Updates the TreeState in response to nodes expanding/collapsing. 2863 */ 2864 public class TreeExpansionHandler implements TreeExpansionListener { 2865 // NOTE: This class exists only for backward compatibility. All 2866 // its functionality has been moved into Handler. If you need to add 2867 // new functionality add it to the Handler, but make sure this 2868 // class calls into the Handler. 2869 2870 /** 2871 * Called whenever an item in the tree has been expanded. 2872 */ 2873 public void treeExpanded(TreeExpansionEvent event) { 2874 getHandler().treeExpanded(event); 2875 } 2876 2877 /** 2878 * Called whenever an item in the tree has been collapsed. 2879 */ 2880 public void treeCollapsed(TreeExpansionEvent event) { 2881 getHandler().treeCollapsed(event); 2882 } 2883 } // BasicTreeUI.TreeExpansionHandler 2884 2885 2886 /** 2887 * Updates the preferred size when scrolling (if necessary). 2888 */ 2889 public class ComponentHandler extends ComponentAdapter implements 2890 ActionListener { 2891 /** Timer used when inside a scrollpane and the scrollbar is 2892 * adjusting. */ 2893 protected Timer timer; 2894 /** ScrollBar that is being adjusted. */ 2895 protected JScrollBar scrollBar; 2896 2897 public void componentMoved(ComponentEvent e) { 2898 if(timer == null) { 2899 JScrollPane scrollPane = getScrollPane(); 2900 2901 if(scrollPane == null) 2902 updateSize(); 2903 else { 2904 scrollBar = scrollPane.getVerticalScrollBar(); 2905 if(scrollBar == null || 2906 !scrollBar.getValueIsAdjusting()) { 2907 // Try the horizontal scrollbar. 2908 if((scrollBar = scrollPane.getHorizontalScrollBar()) 2909 != null && scrollBar.getValueIsAdjusting()) 2910 startTimer(); 2911 else 2912 updateSize(); 2913 } 2914 else 2915 startTimer(); 2916 } 2917 } 2918 } 2919 2920 /** 2921 * Creates, if necessary, and starts a Timer to check if need to 2922 * resize the bounds. 2923 */ 2924 protected void startTimer() { 2925 if(timer == null) { 2926 timer = new Timer(200, this); 2927 timer.setRepeats(true); 2928 } 2929 timer.start(); 2930 } 2931 2932 /** 2933 * Returns the {@code JScrollPane} housing the {@code JTree}, 2934 * or null if one isn't found. 2935 * 2936 * @return the {@code JScrollPane} housing the {@code JTree} 2937 */ 2938 protected JScrollPane getScrollPane() { 2939 Component c = tree.getParent(); 2940 2941 while(c != null && !(c instanceof JScrollPane)) 2942 c = c.getParent(); 2943 if(c instanceof JScrollPane) 2944 return (JScrollPane)c; 2945 return null; 2946 } 2947 2948 /** 2949 * Public as a result of Timer. If the scrollBar is null, or 2950 * not adjusting, this stops the timer and updates the sizing. 2951 */ 2952 public void actionPerformed(ActionEvent ae) { 2953 if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { 2954 if(timer != null) 2955 timer.stop(); 2956 updateSize(); 2957 timer = null; 2958 scrollBar = null; 2959 } 2960 } 2961 } // End of BasicTreeUI.ComponentHandler 2962 2963 2964 /** 2965 * Forwards all TreeModel events to the TreeState. 2966 */ 2967 public class TreeModelHandler implements TreeModelListener { 2968 2969 // NOTE: This class exists only for backward compatibility. All 2970 // its functionality has been moved into Handler. If you need to add 2971 // new functionality add it to the Handler, but make sure this 2972 // class calls into the Handler. 2973 2974 public void treeNodesChanged(TreeModelEvent e) { 2975 getHandler().treeNodesChanged(e); 2976 } 2977 2978 public void treeNodesInserted(TreeModelEvent e) { 2979 getHandler().treeNodesInserted(e); 2980 } 2981 2982 public void treeNodesRemoved(TreeModelEvent e) { 2983 getHandler().treeNodesRemoved(e); 2984 } 2985 2986 public void treeStructureChanged(TreeModelEvent e) { 2987 getHandler().treeStructureChanged(e); 2988 } 2989 } // End of BasicTreeUI.TreeModelHandler 2990 2991 2992 /** 2993 * Listens for changes in the selection model and updates the display 2994 * accordingly. 2995 */ 2996 public class TreeSelectionHandler implements TreeSelectionListener { 2997 2998 // NOTE: This class exists only for backward compatibility. All 2999 // its functionality has been moved into Handler. If you need to add 3000 // new functionality add it to the Handler, but make sure this 3001 // class calls into the Handler. 3002 3003 /** 3004 * Messaged when the selection changes in the tree we're displaying 3005 * for. Stops editing, messages super and displays the changed paths. 3006 */ 3007 public void valueChanged(TreeSelectionEvent event) { 3008 getHandler().valueChanged(event); 3009 } 3010 }// End of BasicTreeUI.TreeSelectionHandler 3011 3012 3013 /** 3014 * Listener responsible for getting cell editing events and updating 3015 * the tree accordingly. 3016 */ 3017 public class CellEditorHandler implements CellEditorListener { 3018 3019 // NOTE: This class exists only for backward compatibility. All 3020 // its functionality has been moved into Handler. If you need to add 3021 // new functionality add it to the Handler, but make sure this 3022 // class calls into the Handler. 3023 3024 /** Messaged when editing has stopped in the tree. */ 3025 public void editingStopped(ChangeEvent e) { 3026 getHandler().editingStopped(e); 3027 } 3028 3029 /** Messaged when editing has been canceled in the tree. */ 3030 public void editingCanceled(ChangeEvent e) { 3031 getHandler().editingCanceled(e); 3032 } 3033 } // BasicTreeUI.CellEditorHandler 3034 3035 3036 /** 3037 * This is used to get multiple key down events to appropriately generate 3038 * events. 3039 */ 3040 public class KeyHandler extends KeyAdapter { 3041 3042 // NOTE: This class exists only for backward compatibility. All 3043 // its functionality has been moved into Handler. If you need to add 3044 // new functionality add it to the Handler, but make sure this 3045 // class calls into the Handler. 3046 3047 // Also note these fields aren't use anymore, nor does Handler have 3048 // the old functionality. This behavior worked around an old bug 3049 // in JComponent that has long since been fixed. 3050 3051 /** Key code that is being generated for. */ 3052 protected Action repeatKeyAction; 3053 3054 /** Set to true while keyPressed is active. */ 3055 protected boolean isKeyDown; 3056 3057 /** 3058 * Invoked when a key has been typed. 3059 * 3060 * Moves the keyboard focus to the first element 3061 * whose first letter matches the alphanumeric key 3062 * pressed by the user. Subsequent same key presses 3063 * move the keyboard focus to the next object that 3064 * starts with the same letter. 3065 */ 3066 public void keyTyped(KeyEvent e) { 3067 getHandler().keyTyped(e); 3068 } 3069 3070 public void keyPressed(KeyEvent e) { 3071 getHandler().keyPressed(e); 3072 } 3073 3074 public void keyReleased(KeyEvent e) { 3075 getHandler().keyReleased(e); 3076 } 3077 } // End of BasicTreeUI.KeyHandler 3078 3079 3080 /** 3081 * Repaints the lead selection row when focus is lost/gained. 3082 */ 3083 public class FocusHandler implements FocusListener { 3084 // NOTE: This class exists only for backward compatibility. All 3085 // its functionality has been moved into Handler. If you need to add 3086 // new functionality add it to the Handler, but make sure this 3087 // class calls into the Handler. 3088 3089 /** 3090 * Invoked when focus is activated on the tree we're in, redraws the 3091 * lead row. 3092 */ 3093 public void focusGained(FocusEvent e) { 3094 getHandler().focusGained(e); 3095 } 3096 3097 /** 3098 * Invoked when focus is activated on the tree we're in, redraws the 3099 * lead row. 3100 */ 3101 public void focusLost(FocusEvent e) { 3102 getHandler().focusLost(e); 3103 } 3104 } // End of class BasicTreeUI.FocusHandler 3105 3106 3107 /** 3108 * Class responsible for getting size of node, method is forwarded 3109 * to BasicTreeUI method. X location does not include insets, that is 3110 * handled in getPathBounds. 3111 */ 3112 // This returns locations that don't include any Insets. 3113 public class NodeDimensionsHandler extends 3114 AbstractLayoutCache.NodeDimensions { 3115 /** 3116 * Responsible for getting the size of a particular node. 3117 */ 3118 public Rectangle getNodeDimensions(Object value, int row, 3119 int depth, boolean expanded, 3120 Rectangle size) { 3121 // Return size of editing component, if editing and asking 3122 // for editing row. 3123 if(editingComponent != null && editingRow == row) { 3124 Dimension prefSize = editingComponent. 3125 getPreferredSize(); 3126 int rh = getRowHeight(); 3127 3128 if(rh > 0 && rh != prefSize.height) 3129 prefSize.height = rh; 3130 if(size != null) { 3131 size.x = getRowX(row, depth); 3132 size.width = prefSize.width; 3133 size.height = prefSize.height; 3134 } 3135 else { 3136 size = new Rectangle(getRowX(row, depth), 0, 3137 prefSize.width, prefSize.height); 3138 } 3139 return size; 3140 } 3141 // Not editing, use renderer. 3142 if(currentCellRenderer != null) { 3143 Component aComponent; 3144 3145 aComponent = currentCellRenderer.getTreeCellRendererComponent 3146 (tree, value, tree.isRowSelected(row), 3147 expanded, treeModel.isLeaf(value), row, 3148 false); 3149 if(tree != null) { 3150 // Only ever removed when UI changes, this is OK! 3151 rendererPane.add(aComponent); 3152 aComponent.validate(); 3153 } 3154 Dimension prefSize = aComponent.getPreferredSize(); 3155 3156 if(size != null) { 3157 size.x = getRowX(row, depth); 3158 size.width = prefSize.width; 3159 size.height = prefSize.height; 3160 } 3161 else { 3162 size = new Rectangle(getRowX(row, depth), 0, 3163 prefSize.width, prefSize.height); 3164 } 3165 return size; 3166 } 3167 return null; 3168 } 3169 3170 /** 3171 * Returns amount to indent the given row. 3172 * 3173 * @param row a row 3174 * @param depth a depth 3175 * @return amount to indent the given row 3176 */ 3177 protected int getRowX(int row, int depth) { 3178 return BasicTreeUI.this.getRowX(row, depth); 3179 } 3180 3181 } // End of class BasicTreeUI.NodeDimensionsHandler 3182 3183 3184 /** 3185 * TreeMouseListener is responsible for updating the selection 3186 * based on mouse events. 3187 */ 3188 public class MouseHandler extends MouseAdapter implements MouseMotionListener 3189 { 3190 // NOTE: This class exists only for backward compatibility. All 3191 // its functionality has been moved into Handler. If you need to add 3192 // new functionality add it to the Handler, but make sure this 3193 // class calls into the Handler. 3194 3195 /** 3196 * Invoked when a mouse button has been pressed on a component. 3197 */ 3198 public void mousePressed(MouseEvent e) { 3199 getHandler().mousePressed(e); 3200 } 3201 3202 public void mouseDragged(MouseEvent e) { 3203 getHandler().mouseDragged(e); 3204 } 3205 3206 /** 3207 * Invoked when the mouse button has been moved on a component 3208 * (with no buttons no down). 3209 * @since 1.4 3210 */ 3211 public void mouseMoved(MouseEvent e) { 3212 getHandler().mouseMoved(e); 3213 } 3214 3215 public void mouseReleased(MouseEvent e) { 3216 getHandler().mouseReleased(e); 3217 } 3218 } // End of BasicTreeUI.MouseHandler 3219 3220 3221 /** 3222 * PropertyChangeListener for the tree. Updates the appropriate 3223 * variable, or TreeState, based on what changes. 3224 */ 3225 public class PropertyChangeHandler implements 3226 PropertyChangeListener { 3227 3228 // NOTE: This class exists only for backward compatibility. All 3229 // its functionality has been moved into Handler. If you need to add 3230 // new functionality add it to the Handler, but make sure this 3231 // class calls into the Handler. 3232 3233 public void propertyChange(PropertyChangeEvent event) { 3234 getHandler().propertyChange(event); 3235 } 3236 } // End of BasicTreeUI.PropertyChangeHandler 3237 3238 3239 /** 3240 * Listener on the TreeSelectionModel, resets the row selection if 3241 * any of the properties of the model change. 3242 */ 3243 public class SelectionModelPropertyChangeHandler implements 3244 PropertyChangeListener { 3245 3246 // NOTE: This class exists only for backward compatibility. All 3247 // its functionality has been moved into Handler. If you need to add 3248 // new functionality add it to the Handler, but make sure this 3249 // class calls into the Handler. 3250 3251 public void propertyChange(PropertyChangeEvent event) { 3252 getHandler().propertyChange(event); 3253 } 3254 } // End of BasicTreeUI.SelectionModelPropertyChangeHandler 3255 3256 3257 /** 3258 * <code>TreeTraverseAction</code> is the action used for left/right keys. 3259 * Will toggle the expandedness of a node, as well as potentially 3260 * incrementing the selection. 3261 */ 3262 @SuppressWarnings("serial") // Superclass is not serializable across versions 3263 public class TreeTraverseAction extends AbstractAction { 3264 /** Determines direction to traverse, 1 means expand, -1 means 3265 * collapse. */ 3266 protected int direction; 3267 /** True if the selection is reset, false means only the lead path 3268 * changes. */ 3269 private boolean changeSelection; 3270 3271 /** 3272 * Constructs a new instance of {@code TreeTraverseAction}. 3273 * 3274 * @param direction the direction 3275 * @param name the name of action 3276 */ 3277 public TreeTraverseAction(int direction, String name) { 3278 this(direction, name, true); 3279 } 3280 3281 private TreeTraverseAction(int direction, String name, 3282 boolean changeSelection) { 3283 this.direction = direction; 3284 this.changeSelection = changeSelection; 3285 } 3286 3287 public void actionPerformed(ActionEvent e) { 3288 if (tree != null) { 3289 SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction, 3290 changeSelection); 3291 } 3292 } 3293 3294 public boolean isEnabled() { return (tree != null && 3295 tree.isEnabled()); } 3296 } // BasicTreeUI.TreeTraverseAction 3297 3298 3299 /** TreePageAction handles page up and page down events. 3300 */ 3301 @SuppressWarnings("serial") // Superclass is not serializable across versions 3302 public class TreePageAction extends AbstractAction { 3303 /** Specifies the direction to adjust the selection by. */ 3304 protected int direction; 3305 /** True indicates should set selection from anchor path. */ 3306 private boolean addToSelection; 3307 private boolean changeSelection; 3308 3309 /** 3310 * Constructs a new instance of {@code TreePageAction}. 3311 * 3312 * @param direction the direction 3313 * @param name the name of action 3314 */ 3315 public TreePageAction(int direction, String name) { 3316 this(direction, name, false, true); 3317 } 3318 3319 private TreePageAction(int direction, String name, 3320 boolean addToSelection, 3321 boolean changeSelection) { 3322 this.direction = direction; 3323 this.addToSelection = addToSelection; 3324 this.changeSelection = changeSelection; 3325 } 3326 3327 public void actionPerformed(ActionEvent e) { 3328 if (tree != null) { 3329 SHARED_ACTION.page(tree, BasicTreeUI.this, direction, 3330 addToSelection, changeSelection); 3331 } 3332 } 3333 3334 public boolean isEnabled() { return (tree != null && 3335 tree.isEnabled()); } 3336 3337 } // BasicTreeUI.TreePageAction 3338 3339 3340 /** TreeIncrementAction is used to handle up/down actions. Selection 3341 * is moved up or down based on direction. 3342 */ 3343 @SuppressWarnings("serial") // Superclass is not serializable across versions 3344 public class TreeIncrementAction extends AbstractAction { 3345 /** Specifies the direction to adjust the selection by. */ 3346 protected int direction; 3347 /** If true the new item is added to the selection, if false the 3348 * selection is reset. */ 3349 private boolean addToSelection; 3350 private boolean changeSelection; 3351 3352 /** 3353 * Constructs a new instance of {@code TreeIncrementAction}. 3354 * 3355 * @param direction the direction 3356 * @param name the name of action 3357 */ 3358 public TreeIncrementAction(int direction, String name) { 3359 this(direction, name, false, true); 3360 } 3361 3362 private TreeIncrementAction(int direction, String name, 3363 boolean addToSelection, 3364 boolean changeSelection) { 3365 this.direction = direction; 3366 this.addToSelection = addToSelection; 3367 this.changeSelection = changeSelection; 3368 } 3369 3370 public void actionPerformed(ActionEvent e) { 3371 if (tree != null) { 3372 SHARED_ACTION.increment(tree, BasicTreeUI.this, direction, 3373 addToSelection, changeSelection); 3374 } 3375 } 3376 3377 public boolean isEnabled() { return (tree != null && 3378 tree.isEnabled()); } 3379 3380 } // End of class BasicTreeUI.TreeIncrementAction 3381 3382 /** 3383 * TreeHomeAction is used to handle end/home actions. 3384 * Scrolls either the first or last cell to be visible based on 3385 * direction. 3386 */ 3387 @SuppressWarnings("serial") // Superclass is not serializable across versions 3388 public class TreeHomeAction extends AbstractAction { 3389 /** 3390 * The direction. 3391 */ 3392 protected int direction; 3393 /** Set to true if append to selection. */ 3394 private boolean addToSelection; 3395 private boolean changeSelection; 3396 3397 /** 3398 * Constructs a new instance of {@code TreeHomeAction}. 3399 * 3400 * @param direction the direction 3401 * @param name the name of action 3402 */ 3403 public TreeHomeAction(int direction, String name) { 3404 this(direction, name, false, true); 3405 } 3406 3407 private TreeHomeAction(int direction, String name, 3408 boolean addToSelection, 3409 boolean changeSelection) { 3410 this.direction = direction; 3411 this.changeSelection = changeSelection; 3412 this.addToSelection = addToSelection; 3413 } 3414 3415 public void actionPerformed(ActionEvent e) { 3416 if (tree != null) { 3417 SHARED_ACTION.home(tree, BasicTreeUI.this, direction, 3418 addToSelection, changeSelection); 3419 } 3420 } 3421 3422 public boolean isEnabled() { return (tree != null && 3423 tree.isEnabled()); } 3424 3425 } // End of class BasicTreeUI.TreeHomeAction 3426 3427 3428 /** 3429 * For the first selected row expandedness will be toggled. 3430 */ 3431 @SuppressWarnings("serial") // Superclass is not serializable across versions 3432 public class TreeToggleAction extends AbstractAction { 3433 /** 3434 * Constructs a new instance of {@code TreeToggleAction}. 3435 * 3436 * @param name the name of action 3437 */ 3438 public TreeToggleAction(String name) { 3439 } 3440 3441 public void actionPerformed(ActionEvent e) { 3442 if(tree != null) { 3443 SHARED_ACTION.toggle(tree, BasicTreeUI.this); 3444 } 3445 } 3446 3447 public boolean isEnabled() { return (tree != null && 3448 tree.isEnabled()); } 3449 3450 } // End of class BasicTreeUI.TreeToggleAction 3451 3452 3453 /** 3454 * ActionListener that invokes cancelEditing when action performed. 3455 */ 3456 @SuppressWarnings("serial") // Superclass is not serializable across versions 3457 public class TreeCancelEditingAction extends AbstractAction { 3458 /** 3459 * Constructs a new instance of {@code TreeCancelEditingAction}. 3460 * 3461 * @param name the name of action 3462 */ 3463 public TreeCancelEditingAction(String name) { 3464 } 3465 3466 public void actionPerformed(ActionEvent e) { 3467 if(tree != null) { 3468 SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this); 3469 } 3470 } 3471 3472 public boolean isEnabled() { return (tree != null && 3473 tree.isEnabled() && 3474 isEditing(tree)); } 3475 } // End of class BasicTreeUI.TreeCancelEditingAction 3476 3477 3478 /** 3479 * MouseInputHandler handles passing all mouse events, 3480 * including mouse motion events, until the mouse is released to 3481 * the destination it is constructed with. It is assumed all the 3482 * events are currently target at source. 3483 */ 3484 public class MouseInputHandler extends Object implements 3485 MouseInputListener 3486 { 3487 /** Source that events are coming from. */ 3488 protected Component source; 3489 /** Destination that receives all events. */ 3490 protected Component destination; 3491 private Component focusComponent; 3492 private boolean dispatchedEvent; 3493 3494 /** 3495 * Constructs a new instance of {@code MouseInputHandler}. 3496 * 3497 * @param source a source component 3498 * @param destination a destination component 3499 * @param event a mouse event 3500 */ 3501 public MouseInputHandler(Component source, Component destination, 3502 MouseEvent event){ 3503 this(source, destination, event, null); 3504 } 3505 3506 MouseInputHandler(Component source, Component destination, 3507 MouseEvent event, Component focusComponent) { 3508 this.source = source; 3509 this.destination = destination; 3510 this.source.addMouseListener(this); 3511 this.source.addMouseMotionListener(this); 3512 3513 SwingUtilities2.setSkipClickCount(destination, 3514 event.getClickCount() - 1); 3515 3516 /* Dispatch the editing event! */ 3517 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3518 (source, event, destination)); 3519 this.focusComponent = focusComponent; 3520 } 3521 3522 public void mouseClicked(MouseEvent e) { 3523 if(destination != null) { 3524 dispatchedEvent = true; 3525 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3526 (source, e, destination)); 3527 } 3528 } 3529 3530 public void mousePressed(MouseEvent e) { 3531 } 3532 3533 public void mouseReleased(MouseEvent e) { 3534 if(destination != null) 3535 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3536 (source, e, destination)); 3537 removeFromSource(); 3538 } 3539 3540 public void mouseEntered(MouseEvent e) { 3541 if (!SwingUtilities.isLeftMouseButton(e)) { 3542 removeFromSource(); 3543 } 3544 } 3545 3546 public void mouseExited(MouseEvent e) { 3547 if (!SwingUtilities.isLeftMouseButton(e)) { 3548 removeFromSource(); 3549 } 3550 } 3551 3552 public void mouseDragged(MouseEvent e) { 3553 if(destination != null) { 3554 dispatchedEvent = true; 3555 destination.dispatchEvent(SwingUtilities.convertMouseEvent 3556 (source, e, destination)); 3557 } 3558 } 3559 3560 public void mouseMoved(MouseEvent e) { 3561 removeFromSource(); 3562 } 3563 3564 /** 3565 * Removes an event from the source. 3566 */ 3567 protected void removeFromSource() { 3568 if(source != null) { 3569 source.removeMouseListener(this); 3570 source.removeMouseMotionListener(this); 3571 if (focusComponent != null && 3572 focusComponent == destination && !dispatchedEvent && 3573 (focusComponent instanceof JTextField)) { 3574 ((JTextField)focusComponent).selectAll(); 3575 } 3576 } 3577 source = destination = null; 3578 } 3579 3580 } // End of class BasicTreeUI.MouseInputHandler 3581 3582 private static final TransferHandler defaultTransferHandler = new TreeTransferHandler(); 3583 3584 @SuppressWarnings("serial") // JDK-implementation class 3585 static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator<TreePath> { 3586 3587 private JTree tree; 3588 3589 /** 3590 * Create a Transferable to use as the source for a data transfer. 3591 * 3592 * @param c The component holding the data to be transfered. This 3593 * argument is provided to enable sharing of TransferHandlers by 3594 * multiple components. 3595 * @return The representation of the data to be transfered. 3596 * 3597 */ 3598 protected Transferable createTransferable(JComponent c) { 3599 if (c instanceof JTree) { 3600 tree = (JTree) c; 3601 TreePath[] paths = tree.getSelectionPaths(); 3602 3603 if (paths == null || paths.length == 0) { 3604 return null; 3605 } 3606 3607 StringBuilder plainStr = new StringBuilder(); 3608 StringBuilder htmlStr = new StringBuilder(); 3609 3610 htmlStr.append("<html>\n<body>\n<ul>\n"); 3611 3612 TreeModel model = tree.getModel(); 3613 TreePath lastPath = null; 3614 TreePath[] displayPaths = getDisplayOrderPaths(paths); 3615 3616 for (TreePath path : displayPaths) { 3617 Object node = path.getLastPathComponent(); 3618 boolean leaf = model.isLeaf(node); 3619 String label = getDisplayString(path, true, leaf); 3620 3621 plainStr.append(label + "\n"); 3622 htmlStr.append(" <li>" + label + "\n"); 3623 } 3624 3625 // remove the last newline 3626 plainStr.deleteCharAt(plainStr.length() - 1); 3627 htmlStr.append("</ul>\n</body>\n</html>"); 3628 3629 tree = null; 3630 3631 return new BasicTransferable(plainStr.toString(), htmlStr.toString()); 3632 } 3633 3634 return null; 3635 } 3636 3637 public int compare(TreePath o1, TreePath o2) { 3638 int row1 = tree.getRowForPath(o1); 3639 int row2 = tree.getRowForPath(o2); 3640 return row1 - row2; 3641 } 3642 3643 String getDisplayString(TreePath path, boolean selected, boolean leaf) { 3644 int row = tree.getRowForPath(path); 3645 boolean hasFocus = tree.getLeadSelectionRow() == row; 3646 Object node = path.getLastPathComponent(); 3647 return tree.convertValueToText(node, selected, tree.isExpanded(row), 3648 leaf, row, hasFocus); 3649 } 3650 3651 /** 3652 * Selection paths are in selection order. The conversion to 3653 * HTML requires display order. This method resorts the paths 3654 * to be in the display order. 3655 */ 3656 TreePath[] getDisplayOrderPaths(TreePath[] paths) { 3657 // sort the paths to display order rather than selection order 3658 ArrayList<TreePath> selOrder = new ArrayList<TreePath>(); 3659 for (TreePath path : paths) { 3660 selOrder.add(path); 3661 } 3662 Collections.sort(selOrder, this); 3663 int n = selOrder.size(); 3664 TreePath[] displayPaths = new TreePath[n]; 3665 for (int i = 0; i < n; i++) { 3666 displayPaths[i] = selOrder.get(i); 3667 } 3668 return displayPaths; 3669 } 3670 3671 public int getSourceActions(JComponent c) { 3672 return COPY; 3673 } 3674 3675 } 3676 3677 3678 private class Handler implements CellEditorListener, FocusListener, 3679 KeyListener, MouseListener, MouseMotionListener, 3680 PropertyChangeListener, TreeExpansionListener, 3681 TreeModelListener, TreeSelectionListener, 3682 BeforeDrag { 3683 // 3684 // KeyListener 3685 // 3686 private String prefix = ""; 3687 private String typedString = ""; 3688 private long lastTime = 0L; 3689 3690 /** 3691 * Invoked when a key has been typed. 3692 * 3693 * Moves the keyboard focus to the first element whose prefix matches the 3694 * sequence of alphanumeric keys pressed by the user with delay less 3695 * than value of <code>timeFactor</code> property (or 1000 milliseconds 3696 * if it is not defined). Subsequent same key presses move the keyboard 3697 * focus to the next object that starts with the same letter until another 3698 * key is pressed, then it is treated as the prefix with appropriate number 3699 * of the same letters followed by first typed another letter. 3700 */ 3701 public void keyTyped(KeyEvent e) { 3702 // handle first letter navigation 3703 if(tree != null && tree.getRowCount()>0 && tree.hasFocus() && 3704 tree.isEnabled()) { 3705 if (e.isAltDown() || BasicGraphicsUtils.isMenuShortcutKeyDown(e) || 3706 isNavigationKey(e)) { 3707 return; 3708 } 3709 boolean startingFromSelection = true; 3710 3711 char c = e.getKeyChar(); 3712 3713 long time = e.getWhen(); 3714 int startingRow = tree.getLeadSelectionRow(); 3715 if (time - lastTime < timeFactor) { 3716 typedString += c; 3717 if((prefix.length() == 1) && (c == prefix.charAt(0))) { 3718 // Subsequent same key presses move the keyboard focus to the next 3719 // object that starts with the same letter. 3720 startingRow++; 3721 } else { 3722 prefix = typedString; 3723 } 3724 } else { 3725 startingRow++; 3726 typedString = "" + c; 3727 prefix = typedString; 3728 } 3729 lastTime = time; 3730 3731 if (startingRow < 0 || startingRow >= tree.getRowCount()) { 3732 startingFromSelection = false; 3733 startingRow = 0; 3734 } 3735 TreePath path = tree.getNextMatch(prefix, startingRow, 3736 Position.Bias.Forward); 3737 if (path != null) { 3738 tree.setSelectionPath(path); 3739 int row = getRowForPath(tree, path); 3740 ensureRowsAreVisible(row, row); 3741 } else if (startingFromSelection) { 3742 path = tree.getNextMatch(prefix, 0, 3743 Position.Bias.Forward); 3744 if (path != null) { 3745 tree.setSelectionPath(path); 3746 int row = getRowForPath(tree, path); 3747 ensureRowsAreVisible(row, row); 3748 } 3749 } 3750 } 3751 } 3752 3753 /** 3754 * Invoked when a key has been pressed. 3755 * 3756 * Checks to see if the key event is a navigation key to prevent 3757 * dispatching these keys for the first letter navigation. 3758 */ 3759 public void keyPressed(KeyEvent e) { 3760 if (tree != null && isNavigationKey(e)) { 3761 prefix = ""; 3762 typedString = ""; 3763 lastTime = 0L; 3764 } 3765 } 3766 3767 public void keyReleased(KeyEvent e) { 3768 } 3769 3770 /** 3771 * Returns whether or not the supplied key event maps to a key that is used for 3772 * navigation. This is used for optimizing key input by only passing non- 3773 * navigation keys to the first letter navigation mechanism. 3774 */ 3775 private boolean isNavigationKey(KeyEvent event) { 3776 InputMap inputMap = tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 3777 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event); 3778 3779 return inputMap != null && inputMap.get(key) != null; 3780 } 3781 3782 3783 // 3784 // PropertyChangeListener 3785 // 3786 public void propertyChange(PropertyChangeEvent event) { 3787 if (event.getSource() == treeSelectionModel) { 3788 treeSelectionModel.resetRowSelection(); 3789 } 3790 else if(event.getSource() == tree) { 3791 String changeName = event.getPropertyName(); 3792 3793 if (changeName == JTree.LEAD_SELECTION_PATH_PROPERTY) { 3794 if (!ignoreLAChange) { 3795 updateLeadSelectionRow(); 3796 repaintPath((TreePath)event.getOldValue()); 3797 repaintPath((TreePath)event.getNewValue()); 3798 } 3799 } 3800 else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) { 3801 if (!ignoreLAChange) { 3802 repaintPath((TreePath)event.getOldValue()); 3803 repaintPath((TreePath)event.getNewValue()); 3804 } 3805 } 3806 if(changeName == JTree.CELL_RENDERER_PROPERTY) { 3807 setCellRenderer((TreeCellRenderer)event.getNewValue()); 3808 redoTheLayout(); 3809 } 3810 else if(changeName == JTree.TREE_MODEL_PROPERTY) { 3811 setModel((TreeModel)event.getNewValue()); 3812 } 3813 else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) { 3814 setRootVisible(((Boolean)event.getNewValue()). 3815 booleanValue()); 3816 } 3817 else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) { 3818 setShowsRootHandles(((Boolean)event.getNewValue()). 3819 booleanValue()); 3820 } 3821 else if(changeName == JTree.ROW_HEIGHT_PROPERTY) { 3822 setRowHeight(((Integer)event.getNewValue()). 3823 intValue()); 3824 } 3825 else if(changeName == JTree.CELL_EDITOR_PROPERTY) { 3826 setCellEditor((TreeCellEditor)event.getNewValue()); 3827 } 3828 else if(changeName == JTree.EDITABLE_PROPERTY) { 3829 setEditable(((Boolean)event.getNewValue()).booleanValue()); 3830 } 3831 else if(changeName == JTree.LARGE_MODEL_PROPERTY) { 3832 setLargeModel(tree.isLargeModel()); 3833 } 3834 else if(changeName == JTree.SELECTION_MODEL_PROPERTY) { 3835 setSelectionModel(tree.getSelectionModel()); 3836 } 3837 else if(changeName == "font") { 3838 completeEditing(); 3839 if(treeState != null) 3840 treeState.invalidateSizes(); 3841 updateSize(); 3842 } 3843 else if (changeName == "componentOrientation") { 3844 if (tree != null) { 3845 leftToRight = BasicGraphicsUtils.isLeftToRight(tree); 3846 redoTheLayout(); 3847 tree.treeDidChange(); 3848 3849 InputMap km = getInputMap(JComponent.WHEN_FOCUSED); 3850 SwingUtilities.replaceUIInputMap(tree, 3851 JComponent.WHEN_FOCUSED, km); 3852 } 3853 } else if ("dropLocation" == changeName) { 3854 JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue(); 3855 repaintDropLocation(oldValue); 3856 repaintDropLocation(tree.getDropLocation()); 3857 } 3858 } 3859 } 3860 3861 private void repaintDropLocation(JTree.DropLocation loc) { 3862 if (loc == null) { 3863 return; 3864 } 3865 3866 Rectangle r; 3867 3868 if (isDropLine(loc)) { 3869 r = getDropLineRect(loc); 3870 } else { 3871 r = tree.getPathBounds(loc.getPath()); 3872 } 3873 3874 if (r != null) { 3875 tree.repaint(r); 3876 } 3877 } 3878 3879 // 3880 // MouseListener 3881 // 3882 3883 // Whether or not the mouse press (which is being considered as part 3884 // of a drag sequence) also caused the selection change to be fully 3885 // processed. 3886 private boolean dragPressDidSelection; 3887 3888 // Set to true when a drag gesture has been fully recognized and DnD 3889 // begins. Use this to ignore further mouse events which could be 3890 // delivered if DnD is cancelled (via ESCAPE for example) 3891 private boolean dragStarted; 3892 3893 // The path over which the press occurred and the press event itself 3894 private TreePath pressedPath; 3895 private MouseEvent pressedEvent; 3896 3897 // Used to detect whether the press event causes a selection change. 3898 // If it does, we won't try to start editing on the release. 3899 private boolean valueChangedOnPress; 3900 3901 private boolean isActualPath(TreePath path, int x, int y) { 3902 if (path == null) { 3903 return false; 3904 } 3905 3906 Rectangle bounds = getPathBounds(tree, path); 3907 if (bounds == null || y > (bounds.y + bounds.height)) { 3908 return false; 3909 } 3910 3911 return (x >= bounds.x) && (x <= (bounds.x + bounds.width)); 3912 } 3913 3914 public void mouseClicked(MouseEvent e) { 3915 } 3916 3917 public void mouseEntered(MouseEvent e) { 3918 } 3919 3920 public void mouseExited(MouseEvent e) { 3921 } 3922 3923 /** 3924 * Invoked when a mouse button has been pressed on a component. 3925 */ 3926 public void mousePressed(MouseEvent e) { 3927 if (SwingUtilities2.shouldIgnore(e, tree)) { 3928 return; 3929 } 3930 3931 // if we can't stop any ongoing editing, do nothing 3932 if (isEditing(tree) && tree.getInvokesStopCellEditing() 3933 && !stopEditing(tree)) { 3934 return; 3935 } 3936 3937 completeEditing(); 3938 3939 pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY()); 3940 3941 if (tree.getDragEnabled()) { 3942 mousePressedDND(e); 3943 } else { 3944 SwingUtilities2.adjustFocus(tree); 3945 handleSelection(e); 3946 } 3947 } 3948 3949 private void mousePressedDND(MouseEvent e) { 3950 pressedEvent = e; 3951 boolean grabFocus = true; 3952 dragStarted = false; 3953 valueChangedOnPress = false; 3954 3955 // if we have a valid path and this is a drag initiating event 3956 if (isActualPath(pressedPath, e.getX(), e.getY()) && 3957 DragRecognitionSupport.mousePressed(e)) { 3958 3959 dragPressDidSelection = false; 3960 3961 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) { 3962 // do nothing for control - will be handled on release 3963 // or when drag starts 3964 return; 3965 } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) { 3966 // clicking on something that's already selected 3967 // and need to make it the lead now 3968 setAnchorSelectionPath(pressedPath); 3969 setLeadSelectionPath(pressedPath, true); 3970 return; 3971 } 3972 3973 dragPressDidSelection = true; 3974 3975 // could be a drag initiating event - don't grab focus 3976 grabFocus = false; 3977 } 3978 3979 if (grabFocus) { 3980 SwingUtilities2.adjustFocus(tree); 3981 } 3982 3983 handleSelection(e); 3984 } 3985 3986 void handleSelection(MouseEvent e) { 3987 if(pressedPath != null) { 3988 Rectangle bounds = getPathBounds(tree, pressedPath); 3989 3990 if (bounds == null || e.getY() >= (bounds.y + bounds.height)) { 3991 return; 3992 } 3993 3994 // Preferably checkForClickInExpandControl could take 3995 // the Event to do this it self! 3996 if(SwingUtilities.isLeftMouseButton(e)) { 3997 checkForClickInExpandControl(pressedPath, e.getX(), e.getY()); 3998 } 3999 4000 int x = e.getX(); 4001 4002 // Perhaps they clicked the cell itself. If so, 4003 // select it. 4004 if (x >= bounds.x && x < (bounds.x + bounds.width)) { 4005 if (tree.getDragEnabled() || !startEditing(pressedPath, e)) { 4006 selectPathForEvent(pressedPath, e); 4007 } 4008 } 4009 } 4010 } 4011 4012 public void dragStarting(MouseEvent me) { 4013 dragStarted = true; 4014 4015 if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) { 4016 tree.addSelectionPath(pressedPath); 4017 setAnchorSelectionPath(pressedPath); 4018 setLeadSelectionPath(pressedPath, true); 4019 } 4020 4021 pressedEvent = null; 4022 pressedPath = null; 4023 } 4024 4025 public void mouseDragged(MouseEvent e) { 4026 if (SwingUtilities2.shouldIgnore(e, tree)) { 4027 return; 4028 } 4029 4030 if (tree.getDragEnabled()) { 4031 DragRecognitionSupport.mouseDragged(e, this); 4032 } 4033 } 4034 4035 /** 4036 * Invoked when the mouse button has been moved on a component 4037 * (with no buttons no down). 4038 */ 4039 public void mouseMoved(MouseEvent e) { 4040 } 4041 4042 public void mouseReleased(MouseEvent e) { 4043 if (SwingUtilities2.shouldIgnore(e, tree)) { 4044 return; 4045 } 4046 4047 if (tree.getDragEnabled()) { 4048 mouseReleasedDND(e); 4049 } 4050 4051 pressedEvent = null; 4052 pressedPath = null; 4053 } 4054 4055 private void mouseReleasedDND(MouseEvent e) { 4056 MouseEvent me = DragRecognitionSupport.mouseReleased(e); 4057 if (me != null) { 4058 SwingUtilities2.adjustFocus(tree); 4059 if (!dragPressDidSelection) { 4060 handleSelection(me); 4061 } 4062 } 4063 4064 if (!dragStarted) { 4065 4066 // Note: We don't give the tree a chance to start editing if the 4067 // mouse press caused a selection change. Otherwise the default 4068 // tree cell editor will start editing on EVERY press and 4069 // release. If it turns out that this affects some editors, we 4070 // can always parameterize this with a client property. ex: 4071 // 4072 // if (pressedPath != null && 4073 // (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") || 4074 // !valueChangedOnPress) && ... 4075 if (pressedPath != null && !valueChangedOnPress && 4076 isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) { 4077 4078 startEditingOnRelease(pressedPath, pressedEvent, e); 4079 } 4080 } 4081 } 4082 4083 // 4084 // FocusListener 4085 // 4086 public void focusGained(FocusEvent e) { 4087 if(tree != null) { 4088 Rectangle pBounds; 4089 4090 pBounds = getPathBounds(tree, tree.getLeadSelectionPath()); 4091 if(pBounds != null) 4092 tree.repaint(getRepaintPathBounds(pBounds)); 4093 pBounds = getPathBounds(tree, getLeadSelectionPath()); 4094 if(pBounds != null) 4095 tree.repaint(getRepaintPathBounds(pBounds)); 4096 } 4097 } 4098 4099 public void focusLost(FocusEvent e) { 4100 focusGained(e); 4101 } 4102 4103 // 4104 // CellEditorListener 4105 // 4106 public void editingStopped(ChangeEvent e) { 4107 completeEditing(false, false, true); 4108 } 4109 4110 /** Messaged when editing has been canceled in the tree. */ 4111 public void editingCanceled(ChangeEvent e) { 4112 completeEditing(false, false, false); 4113 } 4114 4115 4116 // 4117 // TreeSelectionListener 4118 // 4119 public void valueChanged(TreeSelectionEvent event) { 4120 valueChangedOnPress = true; 4121 4122 // Stop editing 4123 completeEditing(); 4124 // Make sure all the paths are visible, if necessary. 4125 // PENDING: This should be tweaked when isAdjusting is added 4126 if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) { 4127 TreePath[] paths = treeSelectionModel 4128 .getSelectionPaths(); 4129 4130 if(paths != null) { 4131 for(int counter = paths.length - 1; counter >= 0; 4132 counter--) { 4133 TreePath path = paths[counter].getParentPath(); 4134 boolean expand = true; 4135 4136 while (path != null) { 4137 // Indicates this path isn't valid anymore, 4138 // we shouldn't attempt to expand it then. 4139 if (treeModel.isLeaf(path.getLastPathComponent())){ 4140 expand = false; 4141 path = null; 4142 } 4143 else { 4144 path = path.getParentPath(); 4145 } 4146 } 4147 if (expand) { 4148 tree.makeVisible(paths[counter]); 4149 } 4150 } 4151 } 4152 } 4153 4154 TreePath oldLead = getLeadSelectionPath(); 4155 lastSelectedRow = tree.getMinSelectionRow(); 4156 TreePath lead = tree.getSelectionModel().getLeadSelectionPath(); 4157 setAnchorSelectionPath(lead); 4158 setLeadSelectionPath(lead); 4159 4160 TreePath[] changedPaths = event.getPaths(); 4161 Rectangle nodeBounds; 4162 Rectangle visRect = tree.getVisibleRect(); 4163 boolean paintPaths = true; 4164 int nWidth = tree.getWidth(); 4165 4166 if(changedPaths != null) { 4167 int counter, maxCounter = changedPaths.length; 4168 4169 if(maxCounter > 4) { 4170 tree.repaint(); 4171 paintPaths = false; 4172 } 4173 else { 4174 for (counter = 0; counter < maxCounter; counter++) { 4175 nodeBounds = getPathBounds(tree, 4176 changedPaths[counter]); 4177 if(nodeBounds != null && 4178 visRect.intersects(nodeBounds)) 4179 tree.repaint(0, nodeBounds.y, nWidth, 4180 nodeBounds.height); 4181 } 4182 } 4183 } 4184 if(paintPaths) { 4185 nodeBounds = getPathBounds(tree, oldLead); 4186 if(nodeBounds != null && visRect.intersects(nodeBounds)) 4187 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 4188 nodeBounds = getPathBounds(tree, lead); 4189 if(nodeBounds != null && visRect.intersects(nodeBounds)) 4190 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); 4191 } 4192 } 4193 4194 4195 // 4196 // TreeExpansionListener 4197 // 4198 public void treeExpanded(TreeExpansionEvent event) { 4199 if(event != null && tree != null) { 4200 TreePath path = event.getPath(); 4201 4202 updateExpandedDescendants(path); 4203 } 4204 } 4205 4206 public void treeCollapsed(TreeExpansionEvent event) { 4207 if(event != null && tree != null) { 4208 TreePath path = event.getPath(); 4209 4210 completeEditing(); 4211 if(path != null && tree.isVisible(path)) { 4212 treeState.setExpandedState(path, false); 4213 updateLeadSelectionRow(); 4214 updateSize(); 4215 } 4216 } 4217 } 4218 4219 // 4220 // TreeModelListener 4221 // 4222 public void treeNodesChanged(TreeModelEvent e) { 4223 if(treeState != null && e != null) { 4224 TreePath parentPath = SwingUtilities2.getTreePath(e, getModel()); 4225 int[] indices = e.getChildIndices(); 4226 if (indices == null || indices.length == 0) { 4227 // The root has changed 4228 treeState.treeNodesChanged(e); 4229 updateSize(); 4230 } 4231 else if (treeState.isExpanded(parentPath)) { 4232 // Changed nodes are visible 4233 // Find the minimum index, we only need paint from there 4234 // down. 4235 int minIndex = indices[0]; 4236 for (int i = indices.length - 1; i > 0; i--) { 4237 minIndex = Math.min(indices[i], minIndex); 4238 } 4239 Object minChild = treeModel.getChild( 4240 parentPath.getLastPathComponent(), minIndex); 4241 TreePath minPath = parentPath.pathByAddingChild(minChild); 4242 Rectangle minBounds = getPathBounds(tree, minPath); 4243 4244 // Forward to the treestate 4245 treeState.treeNodesChanged(e); 4246 4247 // Mark preferred size as bogus. 4248 updateSize0(); 4249 4250 // And repaint 4251 Rectangle newMinBounds = getPathBounds(tree, minPath); 4252 if (minBounds == null || newMinBounds == null) { 4253 return; 4254 } 4255 4256 if (indices.length == 1 && 4257 newMinBounds.height == minBounds.height) { 4258 tree.repaint(0, minBounds.y, tree.getWidth(), 4259 minBounds.height); 4260 } 4261 else { 4262 tree.repaint(0, minBounds.y, tree.getWidth(), 4263 tree.getHeight() - minBounds.y); 4264 } 4265 } 4266 else { 4267 // Nodes that changed aren't visible. No need to paint 4268 treeState.treeNodesChanged(e); 4269 } 4270 } 4271 } 4272 4273 public void treeNodesInserted(TreeModelEvent e) { 4274 if(treeState != null && e != null) { 4275 treeState.treeNodesInserted(e); 4276 4277 updateLeadSelectionRow(); 4278 4279 TreePath path = SwingUtilities2.getTreePath(e, getModel()); 4280 4281 if(treeState.isExpanded(path)) { 4282 updateSize(); 4283 } 4284 else { 4285 // PENDING(sky): Need a method in TreeModelEvent 4286 // that can return the count, getChildIndices allocs 4287 // a new array! 4288 int[] indices = e.getChildIndices(); 4289 int childCount = treeModel.getChildCount 4290 (path.getLastPathComponent()); 4291 4292 if(indices != null && (childCount - indices.length) == 0) 4293 updateSize(); 4294 } 4295 } 4296 } 4297 4298 public void treeNodesRemoved(TreeModelEvent e) { 4299 if(treeState != null && e != null) { 4300 treeState.treeNodesRemoved(e); 4301 4302 updateLeadSelectionRow(); 4303 4304 TreePath path = SwingUtilities2.getTreePath(e, getModel()); 4305 4306 if(treeState.isExpanded(path) || 4307 treeModel.getChildCount(path.getLastPathComponent()) == 0) 4308 updateSize(); 4309 } 4310 } 4311 4312 public void treeStructureChanged(TreeModelEvent e) { 4313 if(treeState != null && e != null) { 4314 treeState.treeStructureChanged(e); 4315 4316 updateLeadSelectionRow(); 4317 4318 TreePath pPath = SwingUtilities2.getTreePath(e, getModel()); 4319 4320 if (pPath != null) { 4321 pPath = pPath.getParentPath(); 4322 } 4323 if(pPath == null || treeState.isExpanded(pPath)) 4324 updateSize(); 4325 } 4326 } 4327 } 4328 4329 4330 4331 private static class Actions extends UIAction { 4332 private static final String SELECT_PREVIOUS = "selectPrevious"; 4333 private static final String SELECT_PREVIOUS_CHANGE_LEAD = 4334 "selectPreviousChangeLead"; 4335 private static final String SELECT_PREVIOUS_EXTEND_SELECTION = 4336 "selectPreviousExtendSelection"; 4337 private static final String SELECT_NEXT = "selectNext"; 4338 private static final String SELECT_NEXT_CHANGE_LEAD = 4339 "selectNextChangeLead"; 4340 private static final String SELECT_NEXT_EXTEND_SELECTION = 4341 "selectNextExtendSelection"; 4342 private static final String SELECT_CHILD = "selectChild"; 4343 private static final String SELECT_CHILD_CHANGE_LEAD = 4344 "selectChildChangeLead"; 4345 private static final String SELECT_PARENT = "selectParent"; 4346 private static final String SELECT_PARENT_CHANGE_LEAD = 4347 "selectParentChangeLead"; 4348 private static final String SCROLL_UP_CHANGE_SELECTION = 4349 "scrollUpChangeSelection"; 4350 private static final String SCROLL_UP_CHANGE_LEAD = 4351 "scrollUpChangeLead"; 4352 private static final String SCROLL_UP_EXTEND_SELECTION = 4353 "scrollUpExtendSelection"; 4354 private static final String SCROLL_DOWN_CHANGE_SELECTION = 4355 "scrollDownChangeSelection"; 4356 private static final String SCROLL_DOWN_EXTEND_SELECTION = 4357 "scrollDownExtendSelection"; 4358 private static final String SCROLL_DOWN_CHANGE_LEAD = 4359 "scrollDownChangeLead"; 4360 private static final String SELECT_FIRST = "selectFirst"; 4361 private static final String SELECT_FIRST_CHANGE_LEAD = 4362 "selectFirstChangeLead"; 4363 private static final String SELECT_FIRST_EXTEND_SELECTION = 4364 "selectFirstExtendSelection"; 4365 private static final String SELECT_LAST = "selectLast"; 4366 private static final String SELECT_LAST_CHANGE_LEAD = 4367 "selectLastChangeLead"; 4368 private static final String SELECT_LAST_EXTEND_SELECTION = 4369 "selectLastExtendSelection"; 4370 private static final String TOGGLE = "toggle"; 4371 private static final String CANCEL_EDITING = "cancel"; 4372 private static final String START_EDITING = "startEditing"; 4373 private static final String SELECT_ALL = "selectAll"; 4374 private static final String CLEAR_SELECTION = "clearSelection"; 4375 private static final String SCROLL_LEFT = "scrollLeft"; 4376 private static final String SCROLL_RIGHT = "scrollRight"; 4377 private static final String SCROLL_LEFT_EXTEND_SELECTION = 4378 "scrollLeftExtendSelection"; 4379 private static final String SCROLL_RIGHT_EXTEND_SELECTION = 4380 "scrollRightExtendSelection"; 4381 private static final String SCROLL_RIGHT_CHANGE_LEAD = 4382 "scrollRightChangeLead"; 4383 private static final String SCROLL_LEFT_CHANGE_LEAD = 4384 "scrollLeftChangeLead"; 4385 private static final String EXPAND = "expand"; 4386 private static final String COLLAPSE = "collapse"; 4387 private static final String MOVE_SELECTION_TO_PARENT = 4388 "moveSelectionToParent"; 4389 4390 // add the lead item to the selection without changing lead or anchor 4391 private static final String ADD_TO_SELECTION = "addToSelection"; 4392 4393 // toggle the selected state of the lead item and move the anchor to it 4394 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor"; 4395 4396 // extend the selection to the lead item 4397 private static final String EXTEND_TO = "extendTo"; 4398 4399 // move the anchor to the lead and ensure only that item is selected 4400 private static final String MOVE_SELECTION_TO = "moveSelectionTo"; 4401 4402 Actions() { 4403 super(null); 4404 } 4405 4406 Actions(String key) { 4407 super(key); 4408 } 4409 4410 public boolean isEnabled(Object o) { 4411 if (o instanceof JTree) { 4412 if (getName() == CANCEL_EDITING) { 4413 return ((JTree)o).isEditing(); 4414 } 4415 } 4416 return true; 4417 } 4418 4419 public void actionPerformed(ActionEvent e) { 4420 JTree tree = (JTree)e.getSource(); 4421 BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType( 4422 tree.getUI(), BasicTreeUI.class); 4423 if (ui == null) { 4424 return; 4425 } 4426 String key = getName(); 4427 if (key == SELECT_PREVIOUS) { 4428 increment(tree, ui, -1, false, true); 4429 } 4430 else if (key == SELECT_PREVIOUS_CHANGE_LEAD) { 4431 increment(tree, ui, -1, false, false); 4432 } 4433 else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) { 4434 increment(tree, ui, -1, true, true); 4435 } 4436 else if (key == SELECT_NEXT) { 4437 increment(tree, ui, 1, false, true); 4438 } 4439 else if (key == SELECT_NEXT_CHANGE_LEAD) { 4440 increment(tree, ui, 1, false, false); 4441 } 4442 else if (key == SELECT_NEXT_EXTEND_SELECTION) { 4443 increment(tree, ui, 1, true, true); 4444 } 4445 else if (key == SELECT_CHILD) { 4446 traverse(tree, ui, 1, true); 4447 } 4448 else if (key == SELECT_CHILD_CHANGE_LEAD) { 4449 traverse(tree, ui, 1, false); 4450 } 4451 else if (key == SELECT_PARENT) { 4452 traverse(tree, ui, -1, true); 4453 } 4454 else if (key == SELECT_PARENT_CHANGE_LEAD) { 4455 traverse(tree, ui, -1, false); 4456 } 4457 else if (key == SCROLL_UP_CHANGE_SELECTION) { 4458 page(tree, ui, -1, false, true); 4459 } 4460 else if (key == SCROLL_UP_CHANGE_LEAD) { 4461 page(tree, ui, -1, false, false); 4462 } 4463 else if (key == SCROLL_UP_EXTEND_SELECTION) { 4464 page(tree, ui, -1, true, true); 4465 } 4466 else if (key == SCROLL_DOWN_CHANGE_SELECTION) { 4467 page(tree, ui, 1, false, true); 4468 } 4469 else if (key == SCROLL_DOWN_EXTEND_SELECTION) { 4470 page(tree, ui, 1, true, true); 4471 } 4472 else if (key == SCROLL_DOWN_CHANGE_LEAD) { 4473 page(tree, ui, 1, false, false); 4474 } 4475 else if (key == SELECT_FIRST) { 4476 home(tree, ui, -1, false, true); 4477 } 4478 else if (key == SELECT_FIRST_CHANGE_LEAD) { 4479 home(tree, ui, -1, false, false); 4480 } 4481 else if (key == SELECT_FIRST_EXTEND_SELECTION) { 4482 home(tree, ui, -1, true, true); 4483 } 4484 else if (key == SELECT_LAST) { 4485 home(tree, ui, 1, false, true); 4486 } 4487 else if (key == SELECT_LAST_CHANGE_LEAD) { 4488 home(tree, ui, 1, false, false); 4489 } 4490 else if (key == SELECT_LAST_EXTEND_SELECTION) { 4491 home(tree, ui, 1, true, true); 4492 } 4493 else if (key == TOGGLE) { 4494 toggle(tree, ui); 4495 } 4496 else if (key == CANCEL_EDITING) { 4497 cancelEditing(tree, ui); 4498 } 4499 else if (key == START_EDITING) { 4500 startEditing(tree, ui); 4501 } 4502 else if (key == SELECT_ALL) { 4503 selectAll(tree, ui, true); 4504 } 4505 else if (key == CLEAR_SELECTION) { 4506 selectAll(tree, ui, false); 4507 } 4508 else if (key == ADD_TO_SELECTION) { 4509 if (ui.getRowCount(tree) > 0) { 4510 int lead = ui.getLeadSelectionRow(); 4511 if (!tree.isRowSelected(lead)) { 4512 TreePath aPath = ui.getAnchorSelectionPath(); 4513 tree.addSelectionRow(lead); 4514 ui.setAnchorSelectionPath(aPath); 4515 } 4516 } 4517 } 4518 else if (key == TOGGLE_AND_ANCHOR) { 4519 if (ui.getRowCount(tree) > 0) { 4520 int lead = ui.getLeadSelectionRow(); 4521 TreePath lPath = ui.getLeadSelectionPath(); 4522 if (!tree.isRowSelected(lead)) { 4523 tree.addSelectionRow(lead); 4524 } else { 4525 tree.removeSelectionRow(lead); 4526 ui.setLeadSelectionPath(lPath); 4527 } 4528 ui.setAnchorSelectionPath(lPath); 4529 } 4530 } 4531 else if (key == EXTEND_TO) { 4532 extendSelection(tree, ui); 4533 } 4534 else if (key == MOVE_SELECTION_TO) { 4535 if (ui.getRowCount(tree) > 0) { 4536 int lead = ui.getLeadSelectionRow(); 4537 tree.setSelectionInterval(lead, lead); 4538 } 4539 } 4540 else if (key == SCROLL_LEFT) { 4541 scroll(tree, ui, SwingConstants.HORIZONTAL, -10); 4542 } 4543 else if (key == SCROLL_RIGHT) { 4544 scroll(tree, ui, SwingConstants.HORIZONTAL, 10); 4545 } 4546 else if (key == SCROLL_LEFT_EXTEND_SELECTION) { 4547 scrollChangeSelection(tree, ui, -1, true, true); 4548 } 4549 else if (key == SCROLL_RIGHT_EXTEND_SELECTION) { 4550 scrollChangeSelection(tree, ui, 1, true, true); 4551 } 4552 else if (key == SCROLL_RIGHT_CHANGE_LEAD) { 4553 scrollChangeSelection(tree, ui, 1, false, false); 4554 } 4555 else if (key == SCROLL_LEFT_CHANGE_LEAD) { 4556 scrollChangeSelection(tree, ui, -1, false, false); 4557 } 4558 else if (key == EXPAND) { 4559 expand(tree, ui); 4560 } 4561 else if (key == COLLAPSE) { 4562 collapse(tree, ui); 4563 } 4564 else if (key == MOVE_SELECTION_TO_PARENT) { 4565 moveSelectionToParent(tree, ui); 4566 } 4567 } 4568 4569 private void scrollChangeSelection(JTree tree, BasicTreeUI ui, 4570 int direction, boolean addToSelection, 4571 boolean changeSelection) { 4572 int rowCount; 4573 4574 if((rowCount = ui.getRowCount(tree)) > 0 && 4575 ui.treeSelectionModel != null) { 4576 TreePath newPath; 4577 Rectangle visRect = tree.getVisibleRect(); 4578 4579 if (direction == -1) { 4580 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4581 visRect.y); 4582 visRect.x = Math.max(0, visRect.x - visRect.width); 4583 } 4584 else { 4585 visRect.x = Math.min(Math.max(0, tree.getWidth() - 4586 visRect.width), visRect.x + visRect.width); 4587 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4588 visRect.y + visRect.height); 4589 } 4590 // Scroll 4591 tree.scrollRectToVisible(visRect); 4592 // select 4593 if (addToSelection) { 4594 ui.extendSelection(newPath); 4595 } 4596 else if(changeSelection) { 4597 tree.setSelectionPath(newPath); 4598 } 4599 else { 4600 ui.setLeadSelectionPath(newPath, true); 4601 } 4602 } 4603 } 4604 4605 private void scroll(JTree component, BasicTreeUI ui, int direction, 4606 int amount) { 4607 Rectangle visRect = component.getVisibleRect(); 4608 Dimension size = component.getSize(); 4609 if (direction == SwingConstants.HORIZONTAL) { 4610 visRect.x += amount; 4611 visRect.x = Math.max(0, visRect.x); 4612 visRect.x = Math.min(Math.max(0, size.width - visRect.width), 4613 visRect.x); 4614 } 4615 else { 4616 visRect.y += amount; 4617 visRect.y = Math.max(0, visRect.y); 4618 visRect.y = Math.min(Math.max(0, size.width - visRect.height), 4619 visRect.y); 4620 } 4621 component.scrollRectToVisible(visRect); 4622 } 4623 4624 private void extendSelection(JTree tree, BasicTreeUI ui) { 4625 if (ui.getRowCount(tree) > 0) { 4626 int lead = ui.getLeadSelectionRow(); 4627 4628 if (lead != -1) { 4629 TreePath leadP = ui.getLeadSelectionPath(); 4630 TreePath aPath = ui.getAnchorSelectionPath(); 4631 int aRow = ui.getRowForPath(tree, aPath); 4632 4633 if(aRow == -1) 4634 aRow = 0; 4635 tree.setSelectionInterval(aRow, lead); 4636 ui.setLeadSelectionPath(leadP); 4637 ui.setAnchorSelectionPath(aPath); 4638 } 4639 } 4640 } 4641 4642 private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) { 4643 int rowCount = ui.getRowCount(tree); 4644 4645 if(rowCount > 0) { 4646 if(selectAll) { 4647 if (tree.getSelectionModel().getSelectionMode() == 4648 TreeSelectionModel.SINGLE_TREE_SELECTION) { 4649 4650 int lead = ui.getLeadSelectionRow(); 4651 if (lead != -1) { 4652 tree.setSelectionRow(lead); 4653 } else if (tree.getMinSelectionRow() == -1) { 4654 tree.setSelectionRow(0); 4655 ui.ensureRowsAreVisible(0, 0); 4656 } 4657 return; 4658 } 4659 4660 TreePath lastPath = ui.getLeadSelectionPath(); 4661 TreePath aPath = ui.getAnchorSelectionPath(); 4662 4663 if(lastPath != null && !tree.isVisible(lastPath)) { 4664 lastPath = null; 4665 } 4666 tree.setSelectionInterval(0, rowCount - 1); 4667 if(lastPath != null) { 4668 ui.setLeadSelectionPath(lastPath); 4669 } 4670 if(aPath != null && tree.isVisible(aPath)) { 4671 ui.setAnchorSelectionPath(aPath); 4672 } 4673 } 4674 else { 4675 TreePath lastPath = ui.getLeadSelectionPath(); 4676 TreePath aPath = ui.getAnchorSelectionPath(); 4677 4678 tree.clearSelection(); 4679 ui.setAnchorSelectionPath(aPath); 4680 ui.setLeadSelectionPath(lastPath); 4681 } 4682 } 4683 } 4684 4685 private void startEditing(JTree tree, BasicTreeUI ui) { 4686 TreePath lead = ui.getLeadSelectionPath(); 4687 int editRow = (lead != null) ? 4688 ui.getRowForPath(tree, lead) : -1; 4689 4690 if(editRow != -1) { 4691 tree.startEditingAtPath(lead); 4692 } 4693 } 4694 4695 private void cancelEditing(JTree tree, BasicTreeUI ui) { 4696 tree.cancelEditing(); 4697 } 4698 4699 private void toggle(JTree tree, BasicTreeUI ui) { 4700 int selRow = ui.getLeadSelectionRow(); 4701 4702 if(selRow != -1 && !ui.isLeaf(selRow)) { 4703 TreePath aPath = ui.getAnchorSelectionPath(); 4704 TreePath lPath = ui.getLeadSelectionPath(); 4705 4706 ui.toggleExpandState(ui.getPathForRow(tree, selRow)); 4707 ui.setAnchorSelectionPath(aPath); 4708 ui.setLeadSelectionPath(lPath); 4709 } 4710 } 4711 4712 private void expand(JTree tree, BasicTreeUI ui) { 4713 int selRow = ui.getLeadSelectionRow(); 4714 tree.expandRow(selRow); 4715 } 4716 4717 private void collapse(JTree tree, BasicTreeUI ui) { 4718 int selRow = ui.getLeadSelectionRow(); 4719 tree.collapseRow(selRow); 4720 } 4721 4722 private void increment(JTree tree, BasicTreeUI ui, int direction, 4723 boolean addToSelection, 4724 boolean changeSelection) { 4725 4726 // disable moving of lead unless in discontiguous mode 4727 if (!addToSelection && !changeSelection && 4728 tree.getSelectionModel().getSelectionMode() != 4729 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4730 changeSelection = true; 4731 } 4732 4733 int rowCount; 4734 4735 if(ui.treeSelectionModel != null && 4736 (rowCount = tree.getRowCount()) > 0) { 4737 int selIndex = ui.getLeadSelectionRow(); 4738 int newIndex; 4739 4740 if(selIndex == -1) { 4741 if(direction == 1) 4742 newIndex = 0; 4743 else 4744 newIndex = rowCount - 1; 4745 } 4746 else 4747 /* Aparently people don't like wrapping;( */ 4748 newIndex = Math.min(rowCount - 1, Math.max 4749 (0, (selIndex + direction))); 4750 if(addToSelection && ui.treeSelectionModel. 4751 getSelectionMode() != TreeSelectionModel. 4752 SINGLE_TREE_SELECTION) { 4753 ui.extendSelection(tree.getPathForRow(newIndex)); 4754 } 4755 else if(changeSelection) { 4756 tree.setSelectionInterval(newIndex, newIndex); 4757 } 4758 else { 4759 ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true); 4760 } 4761 ui.ensureRowsAreVisible(newIndex, newIndex); 4762 ui.lastSelectedRow = newIndex; 4763 } 4764 } 4765 4766 private void traverse(JTree tree, BasicTreeUI ui, int direction, 4767 boolean changeSelection) { 4768 4769 // disable moving of lead unless in discontiguous mode 4770 if (!changeSelection && 4771 tree.getSelectionModel().getSelectionMode() != 4772 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4773 changeSelection = true; 4774 } 4775 4776 int rowCount; 4777 4778 if((rowCount = tree.getRowCount()) > 0) { 4779 int minSelIndex = ui.getLeadSelectionRow(); 4780 int newIndex; 4781 4782 if(minSelIndex == -1) 4783 newIndex = 0; 4784 else { 4785 /* Try and expand the node, otherwise go to next 4786 node. */ 4787 if(direction == 1) { 4788 TreePath minSelPath = ui.getPathForRow(tree, minSelIndex); 4789 int childCount = tree.getModel(). 4790 getChildCount(minSelPath.getLastPathComponent()); 4791 newIndex = -1; 4792 if (!ui.isLeaf(minSelIndex)) { 4793 if (!tree.isExpanded(minSelIndex)) { 4794 ui.toggleExpandState(minSelPath); 4795 } 4796 else if (childCount > 0) { 4797 newIndex = Math.min(minSelIndex + 1, rowCount - 1); 4798 } 4799 } 4800 } 4801 /* Try to collapse node. */ 4802 else { 4803 if(!ui.isLeaf(minSelIndex) && 4804 tree.isExpanded(minSelIndex)) { 4805 ui.toggleExpandState(ui.getPathForRow 4806 (tree, minSelIndex)); 4807 newIndex = -1; 4808 } 4809 else { 4810 TreePath path = ui.getPathForRow(tree, 4811 minSelIndex); 4812 4813 if(path != null && path.getPathCount() > 1) { 4814 newIndex = ui.getRowForPath(tree, path. 4815 getParentPath()); 4816 } 4817 else 4818 newIndex = -1; 4819 } 4820 } 4821 } 4822 if(newIndex != -1) { 4823 if(changeSelection) { 4824 tree.setSelectionInterval(newIndex, newIndex); 4825 } 4826 else { 4827 ui.setLeadSelectionPath(ui.getPathForRow( 4828 tree, newIndex), true); 4829 } 4830 ui.ensureRowsAreVisible(newIndex, newIndex); 4831 } 4832 } 4833 } 4834 4835 private void moveSelectionToParent(JTree tree, BasicTreeUI ui) { 4836 int selRow = ui.getLeadSelectionRow(); 4837 TreePath path = ui.getPathForRow(tree, selRow); 4838 if (path != null && path.getPathCount() > 1) { 4839 int newIndex = ui.getRowForPath(tree, path.getParentPath()); 4840 if (newIndex != -1) { 4841 tree.setSelectionInterval(newIndex, newIndex); 4842 ui.ensureRowsAreVisible(newIndex, newIndex); 4843 } 4844 } 4845 } 4846 4847 private void page(JTree tree, BasicTreeUI ui, int direction, 4848 boolean addToSelection, boolean changeSelection) { 4849 4850 // disable moving of lead unless in discontiguous mode 4851 if (!addToSelection && !changeSelection && 4852 tree.getSelectionModel().getSelectionMode() != 4853 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4854 changeSelection = true; 4855 } 4856 4857 int rowCount; 4858 4859 if((rowCount = ui.getRowCount(tree)) > 0 && 4860 ui.treeSelectionModel != null) { 4861 Dimension maxSize = tree.getSize(); 4862 TreePath lead = ui.getLeadSelectionPath(); 4863 TreePath newPath; 4864 Rectangle visRect = tree.getVisibleRect(); 4865 4866 if(direction == -1) { 4867 // up. 4868 newPath = ui.getClosestPathForLocation(tree, visRect.x, 4869 visRect.y); 4870 if(newPath.equals(lead)) { 4871 visRect.y = Math.max(0, visRect.y - visRect.height); 4872 newPath = tree.getClosestPathForLocation(visRect.x, 4873 visRect.y); 4874 } 4875 } 4876 else { 4877 // down 4878 visRect.y = Math.min(maxSize.height, visRect.y + 4879 visRect.height - 1); 4880 newPath = tree.getClosestPathForLocation(visRect.x, 4881 visRect.y); 4882 if(newPath.equals(lead)) { 4883 visRect.y = Math.min(maxSize.height, visRect.y + 4884 visRect.height - 1); 4885 newPath = tree.getClosestPathForLocation(visRect.x, 4886 visRect.y); 4887 } 4888 } 4889 Rectangle newRect = ui.getPathBounds(tree, newPath); 4890 if (newRect != null) { 4891 newRect.x = visRect.x; 4892 newRect.width = visRect.width; 4893 if(direction == -1) { 4894 newRect.height = visRect.height; 4895 } 4896 else { 4897 newRect.y -= (visRect.height - newRect.height); 4898 newRect.height = visRect.height; 4899 } 4900 4901 if(addToSelection) { 4902 ui.extendSelection(newPath); 4903 } 4904 else if(changeSelection) { 4905 tree.setSelectionPath(newPath); 4906 } 4907 else { 4908 ui.setLeadSelectionPath(newPath, true); 4909 } 4910 tree.scrollRectToVisible(newRect); 4911 } 4912 } 4913 } 4914 4915 private void home(JTree tree, final BasicTreeUI ui, int direction, 4916 boolean addToSelection, boolean changeSelection) { 4917 4918 // disable moving of lead unless in discontiguous mode 4919 if (!addToSelection && !changeSelection && 4920 tree.getSelectionModel().getSelectionMode() != 4921 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { 4922 changeSelection = true; 4923 } 4924 4925 final int rowCount = ui.getRowCount(tree); 4926 4927 if (rowCount > 0) { 4928 if(direction == -1) { 4929 ui.ensureRowsAreVisible(0, 0); 4930 if (addToSelection) { 4931 TreePath aPath = ui.getAnchorSelectionPath(); 4932 int aRow = (aPath == null) ? -1 : 4933 ui.getRowForPath(tree, aPath); 4934 4935 if (aRow == -1) { 4936 tree.setSelectionInterval(0, 0); 4937 } 4938 else { 4939 tree.setSelectionInterval(0, aRow); 4940 ui.setAnchorSelectionPath(aPath); 4941 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0)); 4942 } 4943 } 4944 else if(changeSelection) { 4945 tree.setSelectionInterval(0, 0); 4946 } 4947 else { 4948 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0), 4949 true); 4950 } 4951 } 4952 else { 4953 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); 4954 if (addToSelection) { 4955 TreePath aPath = ui.getAnchorSelectionPath(); 4956 int aRow = (aPath == null) ? -1 : 4957 ui.getRowForPath(tree, aPath); 4958 4959 if (aRow == -1) { 4960 tree.setSelectionInterval(rowCount - 1, 4961 rowCount -1); 4962 } 4963 else { 4964 tree.setSelectionInterval(aRow, rowCount - 1); 4965 ui.setAnchorSelectionPath(aPath); 4966 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4967 rowCount -1)); 4968 } 4969 } 4970 else if(changeSelection) { 4971 tree.setSelectionInterval(rowCount - 1, rowCount - 1); 4972 } 4973 else { 4974 ui.setLeadSelectionPath(ui.getPathForRow(tree, 4975 rowCount - 1), true); 4976 } 4977 if (ui.isLargeModel()){ 4978 SwingUtilities.invokeLater(new Runnable() { 4979 public void run() { 4980 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); 4981 } 4982 }); 4983 } 4984 } 4985 } 4986 } 4987 } 4988 } // End of class BasicTreeUI