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