001 /* BasicTreeUI.java -- 002 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.plaf.basic; 040 041 import gnu.javax.swing.tree.GnuPath; 042 043 import java.awt.Color; 044 import java.awt.Component; 045 import java.awt.Container; 046 import java.awt.Dimension; 047 import java.awt.Graphics; 048 import java.awt.Insets; 049 import java.awt.Label; 050 import java.awt.Point; 051 import java.awt.Rectangle; 052 import java.awt.event.ActionEvent; 053 import java.awt.event.ActionListener; 054 import java.awt.event.ComponentAdapter; 055 import java.awt.event.ComponentEvent; 056 import java.awt.event.ComponentListener; 057 import java.awt.event.FocusEvent; 058 import java.awt.event.FocusListener; 059 import java.awt.event.InputEvent; 060 import java.awt.event.KeyAdapter; 061 import java.awt.event.KeyEvent; 062 import java.awt.event.KeyListener; 063 import java.awt.event.MouseAdapter; 064 import java.awt.event.MouseEvent; 065 import java.awt.event.MouseListener; 066 import java.awt.event.MouseMotionListener; 067 import java.beans.PropertyChangeEvent; 068 import java.beans.PropertyChangeListener; 069 import java.util.Enumeration; 070 import java.util.Hashtable; 071 072 import javax.swing.AbstractAction; 073 import javax.swing.Action; 074 import javax.swing.ActionMap; 075 import javax.swing.CellRendererPane; 076 import javax.swing.Icon; 077 import javax.swing.InputMap; 078 import javax.swing.JComponent; 079 import javax.swing.JScrollBar; 080 import javax.swing.JScrollPane; 081 import javax.swing.JTree; 082 import javax.swing.LookAndFeel; 083 import javax.swing.SwingUtilities; 084 import javax.swing.Timer; 085 import javax.swing.UIManager; 086 import javax.swing.event.CellEditorListener; 087 import javax.swing.event.ChangeEvent; 088 import javax.swing.event.MouseInputListener; 089 import javax.swing.event.TreeExpansionEvent; 090 import javax.swing.event.TreeExpansionListener; 091 import javax.swing.event.TreeModelEvent; 092 import javax.swing.event.TreeModelListener; 093 import javax.swing.event.TreeSelectionEvent; 094 import javax.swing.event.TreeSelectionListener; 095 import javax.swing.plaf.ActionMapUIResource; 096 import javax.swing.plaf.ComponentUI; 097 import javax.swing.plaf.TreeUI; 098 import javax.swing.tree.AbstractLayoutCache; 099 import javax.swing.tree.DefaultTreeCellEditor; 100 import javax.swing.tree.DefaultTreeCellRenderer; 101 import javax.swing.tree.TreeCellEditor; 102 import javax.swing.tree.TreeCellRenderer; 103 import javax.swing.tree.TreeModel; 104 import javax.swing.tree.TreeNode; 105 import javax.swing.tree.TreePath; 106 import javax.swing.tree.TreeSelectionModel; 107 import javax.swing.tree.VariableHeightLayoutCache; 108 109 /** 110 * A delegate providing the user interface for <code>JTree</code> according to 111 * the Basic look and feel. 112 * 113 * @see javax.swing.JTree 114 * @author Lillian Angel (langel@redhat.com) 115 * @author Sascha Brawer (brawer@dandelis.ch) 116 * @author Audrius Meskauskas (audriusa@bioinformatics.org) 117 */ 118 public class BasicTreeUI 119 extends TreeUI 120 { 121 /** 122 * The tree cell editing may be started by the single mouse click on the 123 * selected cell. To separate it from the double mouse click, the editing 124 * session starts after this time (in ms) after that single click, and only no 125 * other clicks were performed during that time. 126 */ 127 static int WAIT_TILL_EDITING = 900; 128 129 /** Collapse Icon for the tree. */ 130 protected transient Icon collapsedIcon; 131 132 /** Expanded Icon for the tree. */ 133 protected transient Icon expandedIcon; 134 135 /** Distance between left margin and where vertical dashes will be drawn. */ 136 protected int leftChildIndent; 137 138 /** 139 * Distance between leftChildIndent and where cell contents will be drawn. 140 */ 141 protected int rightChildIndent; 142 143 /** 144 * Total fistance that will be indented. The sum of leftChildIndent and 145 * rightChildIndent . 146 */ 147 protected int totalChildIndent; 148 149 /** Index of the row that was last selected. */ 150 protected int lastSelectedRow; 151 152 /** Component that we're going to be drawing onto. */ 153 protected JTree tree; 154 155 /** Renderer that is being used to do the actual cell drawing. */ 156 protected transient TreeCellRenderer currentCellRenderer; 157 158 /** 159 * Set to true if the renderer that is currently in the tree was created by 160 * this instance. 161 */ 162 protected boolean createdRenderer; 163 164 /** Editor for the tree. */ 165 protected transient TreeCellEditor cellEditor; 166 167 /** 168 * Set to true if editor that is currently in the tree was created by this 169 * instance. 170 */ 171 protected boolean createdCellEditor; 172 173 /** 174 * Set to false when editing and shouldSelectCall() returns true meaning the 175 * node should be selected before editing, used in completeEditing. 176 * GNU Classpath editing is implemented differently, so this value is not 177 * actually read anywhere. However it is always set correctly to maintain 178 * interoperability with the derived classes that read this field. 179 */ 180 protected boolean stopEditingInCompleteEditing; 181 182 /** Used to paint the TreeCellRenderer. */ 183 protected CellRendererPane rendererPane; 184 185 /** Size needed to completely display all the nodes. */ 186 protected Dimension preferredSize; 187 188 /** Minimum size needed to completely display all the nodes. */ 189 protected Dimension preferredMinSize; 190 191 /** Is the preferredSize valid? */ 192 protected boolean validCachedPreferredSize; 193 194 /** Object responsible for handling sizing and expanded issues. */ 195 protected AbstractLayoutCache treeState; 196 197 /** Used for minimizing the drawing of vertical lines. */ 198 protected Hashtable<TreePath, Boolean> drawingCache; 199 200 /** 201 * True if doing optimizations for a largeModel. Subclasses that don't support 202 * this may wish to override createLayoutCache to not return a 203 * FixedHeightLayoutCache instance. 204 */ 205 protected boolean largeModel; 206 207 /** Responsible for telling the TreeState the size needed for a node. */ 208 protected AbstractLayoutCache.NodeDimensions nodeDimensions; 209 210 /** Used to determine what to display. */ 211 protected TreeModel treeModel; 212 213 /** Model maintaining the selection. */ 214 protected TreeSelectionModel treeSelectionModel; 215 216 /** 217 * How much the depth should be offset to properly calculate x locations. This 218 * is based on whether or not the root is visible, and if the root handles are 219 * visible. 220 */ 221 protected int depthOffset; 222 223 /** 224 * When editing, this will be the Component that is doing the actual editing. 225 */ 226 protected Component editingComponent; 227 228 /** Path that is being edited. */ 229 protected TreePath editingPath; 230 231 /** 232 * Row that is being edited. Should only be referenced if editingComponent is 233 * null. 234 */ 235 protected int editingRow; 236 237 /** Set to true if the editor has a different size than the renderer. */ 238 protected boolean editorHasDifferentSize; 239 240 /** Boolean to keep track of editing. */ 241 boolean isEditing; 242 243 /** The current path of the visible nodes in the tree. */ 244 TreePath currentVisiblePath; 245 246 /** The gap between the icon and text. */ 247 int gap = 4; 248 249 /** The max height of the nodes in the tree. */ 250 int maxHeight; 251 252 /** The hash color. */ 253 Color hashColor; 254 255 /** Listeners */ 256 PropertyChangeListener propertyChangeListener; 257 258 FocusListener focusListener; 259 260 TreeSelectionListener treeSelectionListener; 261 262 MouseListener mouseListener; 263 264 KeyListener keyListener; 265 266 PropertyChangeListener selectionModelPropertyChangeListener; 267 268 ComponentListener componentListener; 269 270 CellEditorListener cellEditorListener; 271 272 TreeExpansionListener treeExpansionListener; 273 274 TreeModelListener treeModelListener; 275 276 /** 277 * The zero size icon, used for expand controls, if they are not visible. 278 */ 279 static Icon nullIcon; 280 281 /** 282 * The special value of the mouse event is sent indicating that this is not 283 * just the mouse click, but the mouse click on the selected node. Sending 284 * such event forces to start the cell editing session. 285 */ 286 static final MouseEvent EDIT = new MouseEvent(new Label(), 7, 7, 7, 7, 7, 7, 287 false); 288 289 /** 290 * Creates a new BasicTreeUI object. 291 */ 292 public BasicTreeUI() 293 { 294 validCachedPreferredSize = false; 295 drawingCache = new Hashtable(); 296 nodeDimensions = createNodeDimensions(); 297 configureLayoutCache(); 298 299 editingRow = - 1; 300 lastSelectedRow = - 1; 301 } 302 303 /** 304 * Returns an instance of the UI delegate for the specified component. 305 * 306 * @param c the <code>JComponent</code> for which we need a UI delegate for. 307 * @return the <code>ComponentUI</code> for c. 308 */ 309 public static ComponentUI createUI(JComponent c) 310 { 311 return new BasicTreeUI(); 312 } 313 314 /** 315 * Returns the Hash color. 316 * 317 * @return the <code>Color</code> of the Hash. 318 */ 319 protected Color getHashColor() 320 { 321 return hashColor; 322 } 323 324 /** 325 * Sets the Hash color. 326 * 327 * @param color the <code>Color</code> to set the Hash to. 328 */ 329 protected void setHashColor(Color color) 330 { 331 hashColor = color; 332 } 333 334 /** 335 * Sets the left child's indent value. 336 * 337 * @param newAmount is the new indent value for the left child. 338 */ 339 public void setLeftChildIndent(int newAmount) 340 { 341 leftChildIndent = newAmount; 342 } 343 344 /** 345 * Returns the indent value for the left child. 346 * 347 * @return the indent value for the left child. 348 */ 349 public int getLeftChildIndent() 350 { 351 return leftChildIndent; 352 } 353 354 /** 355 * Sets the right child's indent value. 356 * 357 * @param newAmount is the new indent value for the right child. 358 */ 359 public void setRightChildIndent(int newAmount) 360 { 361 rightChildIndent = newAmount; 362 } 363 364 /** 365 * Returns the indent value for the right child. 366 * 367 * @return the indent value for the right child. 368 */ 369 public int getRightChildIndent() 370 { 371 return rightChildIndent; 372 } 373 374 /** 375 * Sets the expanded icon. 376 * 377 * @param newG is the new expanded icon. 378 */ 379 public void setExpandedIcon(Icon newG) 380 { 381 expandedIcon = newG; 382 } 383 384 /** 385 * Returns the current expanded icon. 386 * 387 * @return the current expanded icon. 388 */ 389 public Icon getExpandedIcon() 390 { 391 return expandedIcon; 392 } 393 394 /** 395 * Sets the collapsed icon. 396 * 397 * @param newG is the new collapsed icon. 398 */ 399 public void setCollapsedIcon(Icon newG) 400 { 401 collapsedIcon = newG; 402 } 403 404 /** 405 * Returns the current collapsed icon. 406 * 407 * @return the current collapsed icon. 408 */ 409 public Icon getCollapsedIcon() 410 { 411 return collapsedIcon; 412 } 413 414 /** 415 * Updates the componentListener, if necessary. 416 * 417 * @param largeModel sets this.largeModel to it. 418 */ 419 protected void setLargeModel(boolean largeModel) 420 { 421 if (largeModel != this.largeModel) 422 { 423 completeEditing(); 424 tree.removeComponentListener(componentListener); 425 this.largeModel = largeModel; 426 tree.addComponentListener(componentListener); 427 } 428 } 429 430 /** 431 * Returns true if largeModel is set 432 * 433 * @return true if largeModel is set, otherwise false. 434 */ 435 protected boolean isLargeModel() 436 { 437 return largeModel; 438 } 439 440 /** 441 * Sets the row height. 442 * 443 * @param rowHeight is the height to set this.rowHeight to. 444 */ 445 protected void setRowHeight(int rowHeight) 446 { 447 completeEditing(); 448 if (rowHeight == 0) 449 rowHeight = getMaxHeight(tree); 450 treeState.setRowHeight(rowHeight); 451 } 452 453 /** 454 * Returns the current row height. 455 * 456 * @return current row height. 457 */ 458 protected int getRowHeight() 459 { 460 return tree.getRowHeight(); 461 } 462 463 /** 464 * Sets the TreeCellRenderer to <code>tcr</code>. This invokes 465 * <code>updateRenderer</code>. 466 * 467 * @param tcr is the new TreeCellRenderer. 468 */ 469 protected void setCellRenderer(TreeCellRenderer tcr) 470 { 471 // Finish editing before changing the renderer. 472 completeEditing(); 473 474 // The renderer is set in updateRenderer. 475 updateRenderer(); 476 477 // Refresh the layout if necessary. 478 if (treeState != null) 479 { 480 treeState.invalidateSizes(); 481 updateSize(); 482 } 483 } 484 485 /** 486 * Return currentCellRenderer, which will either be the trees renderer, or 487 * defaultCellRenderer, which ever was not null. 488 * 489 * @return the current Cell Renderer 490 */ 491 protected TreeCellRenderer getCellRenderer() 492 { 493 if (currentCellRenderer != null) 494 return currentCellRenderer; 495 496 return createDefaultCellRenderer(); 497 } 498 499 /** 500 * Sets the tree's model. 501 * 502 * @param model to set the treeModel to. 503 */ 504 protected void setModel(TreeModel model) 505 { 506 completeEditing(); 507 508 if (treeModel != null && treeModelListener != null) 509 treeModel.removeTreeModelListener(treeModelListener); 510 511 treeModel = tree.getModel(); 512 513 if (treeModel != null && treeModelListener != null) 514 treeModel.addTreeModelListener(treeModelListener); 515 516 if (treeState != null) 517 { 518 treeState.setModel(treeModel); 519 updateLayoutCacheExpandedNodes(); 520 updateSize(); 521 } 522 } 523 524 /** 525 * Returns the tree's model 526 * 527 * @return treeModel 528 */ 529 protected TreeModel getModel() 530 { 531 return treeModel; 532 } 533 534 /** 535 * Sets the root to being visible. 536 * 537 * @param newValue sets the visibility of the root 538 */ 539 protected void setRootVisible(boolean newValue) 540 { 541 completeEditing(); 542 tree.setRootVisible(newValue); 543 } 544 545 /** 546 * Returns true if the root is visible. 547 * 548 * @return true if the root is visible. 549 */ 550 protected boolean isRootVisible() 551 { 552 return tree.isRootVisible(); 553 } 554 555 /** 556 * Determines whether the node handles are to be displayed. 557 * 558 * @param newValue sets whether or not node handles should be displayed. 559 */ 560 protected void setShowsRootHandles(boolean newValue) 561 { 562 completeEditing(); 563 updateDepthOffset(); 564 if (treeState != null) 565 { 566 treeState.invalidateSizes(); 567 updateSize(); 568 } 569 } 570 571 /** 572 * Returns true if the node handles are to be displayed. 573 * 574 * @return true if the node handles are to be displayed. 575 */ 576 protected boolean getShowsRootHandles() 577 { 578 return tree.getShowsRootHandles(); 579 } 580 581 /** 582 * Sets the cell editor. 583 * 584 * @param editor to set the cellEditor to. 585 */ 586 protected void setCellEditor(TreeCellEditor editor) 587 { 588 updateCellEditor(); 589 } 590 591 /** 592 * Returns the <code>TreeCellEditor</code> for this tree. 593 * 594 * @return the cellEditor for this tree. 595 */ 596 protected TreeCellEditor getCellEditor() 597 { 598 return cellEditor; 599 } 600 601 /** 602 * Configures the receiver to allow, or not allow, editing. 603 * 604 * @param newValue sets the receiver to allow editing if true. 605 */ 606 protected void setEditable(boolean newValue) 607 { 608 updateCellEditor(); 609 } 610 611 /** 612 * Returns true if the receiver allows editing. 613 * 614 * @return true if the receiver allows editing. 615 */ 616 protected boolean isEditable() 617 { 618 return tree.isEditable(); 619 } 620 621 /** 622 * Resets the selection model. The appropriate listeners are installed on the 623 * model. 624 * 625 * @param newLSM resets the selection model. 626 */ 627 protected void setSelectionModel(TreeSelectionModel newLSM) 628 { 629 completeEditing(); 630 if (newLSM != null) 631 { 632 treeSelectionModel = newLSM; 633 tree.setSelectionModel(treeSelectionModel); 634 } 635 } 636 637 /** 638 * Returns the current selection model. 639 * 640 * @return the current selection model. 641 */ 642 protected TreeSelectionModel getSelectionModel() 643 { 644 return treeSelectionModel; 645 } 646 647 /** 648 * Returns the Rectangle enclosing the label portion that the last item in 649 * path will be drawn to. Will return null if any component in path is 650 * currently valid. 651 * 652 * @param tree is the current tree the path will be drawn to. 653 * @param path is the current path the tree to draw to. 654 * @return the Rectangle enclosing the label portion that the last item in the 655 * path will be drawn to. 656 */ 657 public Rectangle getPathBounds(JTree tree, TreePath path) 658 { 659 Rectangle bounds = null; 660 if (tree != null && treeState != null) 661 { 662 bounds = treeState.getBounds(path, null); 663 Insets i = tree.getInsets(); 664 if (bounds != null && i != null) 665 { 666 bounds.x += i.left; 667 bounds.y += i.top; 668 } 669 } 670 return bounds; 671 } 672 673 /** 674 * Returns the max height of all the nodes in the tree. 675 * 676 * @param tree - the current tree 677 * @return the max height. 678 */ 679 int getMaxHeight(JTree tree) 680 { 681 if (maxHeight != 0) 682 return maxHeight; 683 684 Icon e = UIManager.getIcon("Tree.openIcon"); 685 Icon c = UIManager.getIcon("Tree.closedIcon"); 686 Icon l = UIManager.getIcon("Tree.leafIcon"); 687 int rc = getRowCount(tree); 688 int iconHeight = 0; 689 690 for (int row = 0; row < rc; row++) 691 { 692 if (isLeaf(row)) 693 iconHeight = l.getIconHeight(); 694 else if (tree.isExpanded(row)) 695 iconHeight = e.getIconHeight(); 696 else 697 iconHeight = c.getIconHeight(); 698 699 maxHeight = Math.max(maxHeight, iconHeight + gap); 700 } 701 702 treeState.setRowHeight(maxHeight); 703 return maxHeight; 704 } 705 706 /** 707 * Get the tree node icon. 708 */ 709 Icon getNodeIcon(TreePath path) 710 { 711 Object node = path.getLastPathComponent(); 712 if (treeModel.isLeaf(node)) 713 return UIManager.getIcon("Tree.leafIcon"); 714 else if (treeState.getExpandedState(path)) 715 return UIManager.getIcon("Tree.openIcon"); 716 else 717 return UIManager.getIcon("Tree.closedIcon"); 718 } 719 720 /** 721 * Returns the path for passed in row. If row is not visible null is returned. 722 * 723 * @param tree is the current tree to return path for. 724 * @param row is the row number of the row to return. 725 * @return the path for passed in row. If row is not visible null is returned. 726 */ 727 public TreePath getPathForRow(JTree tree, int row) 728 { 729 return treeState.getPathForRow(row); 730 } 731 732 /** 733 * Returns the row that the last item identified in path is visible at. Will 734 * return -1 if any of the elments in the path are not currently visible. 735 * 736 * @param tree is the current tree to return the row for. 737 * @param path is the path used to find the row. 738 * @return the row that the last item identified in path is visible at. Will 739 * return -1 if any of the elments in the path are not currently 740 * visible. 741 */ 742 public int getRowForPath(JTree tree, TreePath path) 743 { 744 return treeState.getRowForPath(path); 745 } 746 747 /** 748 * Returns the number of rows that are being displayed. 749 * 750 * @param tree is the current tree to return the number of rows for. 751 * @return the number of rows being displayed. 752 */ 753 public int getRowCount(JTree tree) 754 { 755 return treeState.getRowCount(); 756 } 757 758 /** 759 * Returns the path to the node that is closest to x,y. If there is nothing 760 * currently visible this will return null, otherwise it'll always return a 761 * valid path. If you need to test if the returned object is exactly at x,y 762 * you should get the bounds for the returned path and test x,y against that. 763 * 764 * @param tree the tree to search for the closest path 765 * @param x is the x coordinate of the location to search 766 * @param y is the y coordinate of the location to search 767 * @return the tree path closes to x,y. 768 */ 769 public TreePath getClosestPathForLocation(JTree tree, int x, int y) 770 { 771 return treeState.getPathClosestTo(x, y); 772 } 773 774 /** 775 * Returns true if the tree is being edited. The item that is being edited can 776 * be returned by getEditingPath(). 777 * 778 * @param tree is the tree to check for editing. 779 * @return true if the tree is being edited. 780 */ 781 public boolean isEditing(JTree tree) 782 { 783 return isEditing; 784 } 785 786 /** 787 * Stops the current editing session. This has no effect if the tree is not 788 * being edited. Returns true if the editor allows the editing session to 789 * stop. 790 * 791 * @param tree is the tree to stop the editing on 792 * @return true if the editor allows the editing session to stop. 793 */ 794 public boolean stopEditing(JTree tree) 795 { 796 boolean ret = false; 797 if (editingComponent != null && cellEditor.stopCellEditing()) 798 { 799 completeEditing(false, false, true); 800 ret = true; 801 } 802 return ret; 803 } 804 805 /** 806 * Cancels the current editing session. 807 * 808 * @param tree is the tree to cancel the editing session on. 809 */ 810 public void cancelEditing(JTree tree) 811 { 812 // There is no need to send the cancel message to the editor, 813 // as the cancellation event itself arrives from it. This would 814 // only be necessary when cancelling the editing programatically. 815 if (editingComponent != null) 816 completeEditing(false, true, false); 817 } 818 819 /** 820 * Selects the last item in path and tries to edit it. Editing will fail if 821 * the CellEditor won't allow it for the selected item. 822 * 823 * @param tree is the tree to edit on. 824 * @param path is the path in tree to edit on. 825 */ 826 public void startEditingAtPath(JTree tree, TreePath path) 827 { 828 tree.scrollPathToVisible(path); 829 if (path != null && tree.isVisible(path)) 830 startEditing(path, null); 831 } 832 833 /** 834 * Returns the path to the element that is being editted. 835 * 836 * @param tree is the tree to get the editing path from. 837 * @return the path that is being edited. 838 */ 839 public TreePath getEditingPath(JTree tree) 840 { 841 return editingPath; 842 } 843 844 /** 845 * Invoked after the tree instance variable has been set, but before any 846 * default/listeners have been installed. 847 */ 848 protected void prepareForUIInstall() 849 { 850 lastSelectedRow = -1; 851 preferredSize = new Dimension(); 852 largeModel = tree.isLargeModel(); 853 preferredSize = new Dimension(); 854 stopEditingInCompleteEditing = true; 855 setModel(tree.getModel()); 856 } 857 858 /** 859 * Invoked from installUI after all the defaults/listeners have been 860 * installed. 861 */ 862 protected void completeUIInstall() 863 { 864 setShowsRootHandles(tree.getShowsRootHandles()); 865 updateRenderer(); 866 updateDepthOffset(); 867 setSelectionModel(tree.getSelectionModel()); 868 configureLayoutCache(); 869 treeState.setRootVisible(tree.isRootVisible()); 870 treeSelectionModel.setRowMapper(treeState); 871 updateSize(); 872 } 873 874 /** 875 * Invoked from uninstallUI after all the defaults/listeners have been 876 * uninstalled. 877 */ 878 protected void completeUIUninstall() 879 { 880 tree = null; 881 } 882 883 /** 884 * Installs the subcomponents of the tree, which is the renderer pane. 885 */ 886 protected void installComponents() 887 { 888 currentCellRenderer = createDefaultCellRenderer(); 889 rendererPane = createCellRendererPane(); 890 createdRenderer = true; 891 setCellRenderer(currentCellRenderer); 892 } 893 894 /** 895 * Creates an instance of NodeDimensions that is able to determine the size of 896 * a given node in the tree. The node dimensions must be created before 897 * configuring the layout cache. 898 * 899 * @return the NodeDimensions of a given node in the tree 900 */ 901 protected AbstractLayoutCache.NodeDimensions createNodeDimensions() 902 { 903 return new NodeDimensionsHandler(); 904 } 905 906 /** 907 * Creates a listener that is reponsible for the updates the UI based on how 908 * the tree changes. 909 * 910 * @return the PropertyChangeListener that is reposnsible for the updates 911 */ 912 protected PropertyChangeListener createPropertyChangeListener() 913 { 914 return new PropertyChangeHandler(); 915 } 916 917 /** 918 * Creates the listener responsible for updating the selection based on mouse 919 * events. 920 * 921 * @return the MouseListener responsible for updating. 922 */ 923 protected MouseListener createMouseListener() 924 { 925 return new MouseHandler(); 926 } 927 928 /** 929 * Creates the listener that is responsible for updating the display when 930 * focus is lost/grained. 931 * 932 * @return the FocusListener responsible for updating. 933 */ 934 protected FocusListener createFocusListener() 935 { 936 return new FocusHandler(); 937 } 938 939 /** 940 * Creates the listener reponsible for getting key events from the tree. 941 * 942 * @return the KeyListener responsible for getting key events. 943 */ 944 protected KeyListener createKeyListener() 945 { 946 return new KeyHandler(); 947 } 948 949 /** 950 * Creates the listener responsible for getting property change events from 951 * the selection model. 952 * 953 * @returns the PropertyChangeListener reponsible for getting property change 954 * events from the selection model. 955 */ 956 protected PropertyChangeListener createSelectionModelPropertyChangeListener() 957 { 958 return new SelectionModelPropertyChangeHandler(); 959 } 960 961 /** 962 * Creates the listener that updates the display based on selection change 963 * methods. 964 * 965 * @return the TreeSelectionListener responsible for updating. 966 */ 967 protected TreeSelectionListener createTreeSelectionListener() 968 { 969 return new TreeSelectionHandler(); 970 } 971 972 /** 973 * Creates a listener to handle events from the current editor 974 * 975 * @return the CellEditorListener that handles events from the current editor 976 */ 977 protected CellEditorListener createCellEditorListener() 978 { 979 return new CellEditorHandler(); 980 } 981 982 /** 983 * Creates and returns a new ComponentHandler. This is used for the large 984 * model to mark the validCachedPreferredSize as invalid when the component 985 * moves. 986 * 987 * @return a new ComponentHandler. 988 */ 989 protected ComponentListener createComponentListener() 990 { 991 return new ComponentHandler(); 992 } 993 994 /** 995 * Creates and returns the object responsible for updating the treestate when 996 * a nodes expanded state changes. 997 * 998 * @return the TreeExpansionListener responsible for updating the treestate 999 */ 1000 protected TreeExpansionListener createTreeExpansionListener() 1001 { 1002 return new TreeExpansionHandler(); 1003 } 1004 1005 /** 1006 * Creates the object responsible for managing what is expanded, as well as 1007 * the size of nodes. 1008 * 1009 * @return the object responsible for managing what is expanded. 1010 */ 1011 protected AbstractLayoutCache createLayoutCache() 1012 { 1013 return new VariableHeightLayoutCache(); 1014 } 1015 1016 /** 1017 * Returns the renderer pane that renderer components are placed in. 1018 * 1019 * @return the rendererpane that render components are placed in. 1020 */ 1021 protected CellRendererPane createCellRendererPane() 1022 { 1023 return new CellRendererPane(); 1024 } 1025 1026 /** 1027 * Creates a default cell editor. 1028 * 1029 * @return the default cell editor. 1030 */ 1031 protected TreeCellEditor createDefaultCellEditor() 1032 { 1033 DefaultTreeCellEditor ed; 1034 if (currentCellRenderer != null 1035 && currentCellRenderer instanceof DefaultTreeCellRenderer) 1036 ed = new DefaultTreeCellEditor(tree, 1037 (DefaultTreeCellRenderer) currentCellRenderer); 1038 else 1039 ed = new DefaultTreeCellEditor(tree, null); 1040 return ed; 1041 } 1042 1043 /** 1044 * Returns the default cell renderer that is used to do the stamping of each 1045 * node. 1046 * 1047 * @return the default cell renderer that is used to do the stamping of each 1048 * node. 1049 */ 1050 protected TreeCellRenderer createDefaultCellRenderer() 1051 { 1052 return new DefaultTreeCellRenderer(); 1053 } 1054 1055 /** 1056 * Returns a listener that can update the tree when the model changes. 1057 * 1058 * @return a listener that can update the tree when the model changes. 1059 */ 1060 protected TreeModelListener createTreeModelListener() 1061 { 1062 return new TreeModelHandler(); 1063 } 1064 1065 /** 1066 * Uninstall all registered listeners 1067 */ 1068 protected void uninstallListeners() 1069 { 1070 tree.removePropertyChangeListener(propertyChangeListener); 1071 tree.removeFocusListener(focusListener); 1072 tree.removeTreeSelectionListener(treeSelectionListener); 1073 tree.removeMouseListener(mouseListener); 1074 tree.removeKeyListener(keyListener); 1075 tree.removePropertyChangeListener(selectionModelPropertyChangeListener); 1076 tree.removeComponentListener(componentListener); 1077 tree.removeTreeExpansionListener(treeExpansionListener); 1078 1079 TreeCellEditor tce = tree.getCellEditor(); 1080 if (tce != null) 1081 tce.removeCellEditorListener(cellEditorListener); 1082 if (treeModel != null) 1083 treeModel.removeTreeModelListener(treeModelListener); 1084 } 1085 1086 /** 1087 * Uninstall all keyboard actions. 1088 */ 1089 protected void uninstallKeyboardActions() 1090 { 1091 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent( 1092 null); 1093 tree.getActionMap().setParent(null); 1094 } 1095 1096 /** 1097 * Uninstall the rendererPane. 1098 */ 1099 protected void uninstallComponents() 1100 { 1101 currentCellRenderer = null; 1102 rendererPane = null; 1103 createdRenderer = false; 1104 setCellRenderer(currentCellRenderer); 1105 } 1106 1107 /** 1108 * The vertical element of legs between nodes starts at the bottom of the 1109 * parent node by default. This method makes the leg start below that. 1110 * 1111 * @return the vertical leg buffer 1112 */ 1113 protected int getVerticalLegBuffer() 1114 { 1115 return getRowHeight() / 2; 1116 } 1117 1118 /** 1119 * The horizontal element of legs between nodes starts at the right of the 1120 * left-hand side of the child node by default. This method makes the leg end 1121 * before that. 1122 * 1123 * @return the horizontal leg buffer 1124 */ 1125 protected int getHorizontalLegBuffer() 1126 { 1127 return rightChildIndent / 2; 1128 } 1129 1130 /** 1131 * Make all the nodes that are expanded in JTree expanded in LayoutCache. This 1132 * invokes updateExpandedDescendants with the root path. 1133 */ 1134 protected void updateLayoutCacheExpandedNodes() 1135 { 1136 if (treeModel != null && treeModel.getRoot() != null) 1137 updateExpandedDescendants(new TreePath(treeModel.getRoot())); 1138 } 1139 1140 /** 1141 * Updates the expanded state of all the descendants of the <code>path</code> 1142 * by getting the expanded descendants from the tree and forwarding to the 1143 * tree state. 1144 * 1145 * @param path the path used to update the expanded states 1146 */ 1147 protected void updateExpandedDescendants(TreePath path) 1148 { 1149 completeEditing(); 1150 Enumeration expanded = tree.getExpandedDescendants(path); 1151 while (expanded.hasMoreElements()) 1152 treeState.setExpandedState((TreePath) expanded.nextElement(), true); 1153 } 1154 1155 /** 1156 * Returns a path to the last child of <code>parent</code> 1157 * 1158 * @param parent is the topmost path to specified 1159 * @return a path to the last child of parent 1160 */ 1161 protected TreePath getLastChildPath(TreePath parent) 1162 { 1163 return (TreePath) parent.getLastPathComponent(); 1164 } 1165 1166 /** 1167 * Updates how much each depth should be offset by. 1168 */ 1169 protected void updateDepthOffset() 1170 { 1171 depthOffset += getVerticalLegBuffer(); 1172 } 1173 1174 /** 1175 * Updates the cellEditor based on editability of the JTree that we're 1176 * contained in. If the tree is editable but doesn't have a cellEditor, a 1177 * basic one will be used. 1178 */ 1179 protected void updateCellEditor() 1180 { 1181 completeEditing(); 1182 TreeCellEditor newEd = null; 1183 if (tree != null && tree.isEditable()) 1184 { 1185 newEd = tree.getCellEditor(); 1186 if (newEd == null) 1187 { 1188 newEd = createDefaultCellEditor(); 1189 if (newEd != null) 1190 { 1191 tree.setCellEditor(newEd); 1192 createdCellEditor = true; 1193 } 1194 } 1195 } 1196 // Update listeners. 1197 if (newEd != cellEditor) 1198 { 1199 if (cellEditor != null && cellEditorListener != null) 1200 cellEditor.removeCellEditorListener(cellEditorListener); 1201 cellEditor = newEd; 1202 if (cellEditorListener == null) 1203 cellEditorListener = createCellEditorListener(); 1204 if (cellEditor != null && cellEditorListener != null) 1205 cellEditor.addCellEditorListener(cellEditorListener); 1206 createdCellEditor = false; 1207 } 1208 } 1209 1210 /** 1211 * Messaged from the tree we're in when the renderer has changed. 1212 */ 1213 protected void updateRenderer() 1214 { 1215 if (tree != null) 1216 { 1217 TreeCellRenderer rend = tree.getCellRenderer(); 1218 if (rend != null) 1219 { 1220 createdRenderer = false; 1221 currentCellRenderer = rend; 1222 if (createdCellEditor) 1223 tree.setCellEditor(null); 1224 } 1225 else 1226 { 1227 tree.setCellRenderer(createDefaultCellRenderer()); 1228 createdRenderer = true; 1229 } 1230 } 1231 else 1232 { 1233 currentCellRenderer = null; 1234 createdRenderer = false; 1235 } 1236 1237 updateCellEditor(); 1238 } 1239 1240 /** 1241 * Resets the treeState instance based on the tree we're providing the look 1242 * and feel for. The node dimensions handler is required and must be created 1243 * in advance. 1244 */ 1245 protected void configureLayoutCache() 1246 { 1247 treeState = createLayoutCache(); 1248 treeState.setNodeDimensions(nodeDimensions); 1249 } 1250 1251 /** 1252 * Marks the cached size as being invalid, and messages the tree with 1253 * <code>treeDidChange</code>. 1254 */ 1255 protected void updateSize() 1256 { 1257 preferredSize = null; 1258 updateCachedPreferredSize(); 1259 tree.treeDidChange(); 1260 } 1261 1262 /** 1263 * Updates the <code>preferredSize</code> instance variable, which is 1264 * returned from <code>getPreferredSize()</code>. 1265 */ 1266 protected void updateCachedPreferredSize() 1267 { 1268 validCachedPreferredSize = false; 1269 } 1270 1271 /** 1272 * Messaged from the VisibleTreeNode after it has been expanded. 1273 * 1274 * @param path is the path that has been expanded. 1275 */ 1276 protected void pathWasExpanded(TreePath path) 1277 { 1278 validCachedPreferredSize = false; 1279 treeState.setExpandedState(path, true); 1280 tree.repaint(); 1281 } 1282 1283 /** 1284 * Messaged from the VisibleTreeNode after it has collapsed 1285 */ 1286 protected void pathWasCollapsed(TreePath path) 1287 { 1288 validCachedPreferredSize = false; 1289 treeState.setExpandedState(path, false); 1290 tree.repaint(); 1291 } 1292 1293 /** 1294 * Install all defaults for the tree. 1295 */ 1296 protected void installDefaults() 1297 { 1298 LookAndFeel.installColorsAndFont(tree, "Tree.background", 1299 "Tree.foreground", "Tree.font"); 1300 1301 hashColor = UIManager.getColor("Tree.hash"); 1302 if (hashColor == null) 1303 hashColor = Color.black; 1304 1305 tree.setOpaque(true); 1306 1307 rightChildIndent = UIManager.getInt("Tree.rightChildIndent"); 1308 leftChildIndent = UIManager.getInt("Tree.leftChildIndent"); 1309 totalChildIndent = rightChildIndent + leftChildIndent; 1310 setRowHeight(UIManager.getInt("Tree.rowHeight")); 1311 tree.setRowHeight(getRowHeight()); 1312 tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand")); 1313 setExpandedIcon(UIManager.getIcon("Tree.expandedIcon")); 1314 setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon")); 1315 } 1316 1317 /** 1318 * Install all keyboard actions for this 1319 */ 1320 protected void installKeyboardActions() 1321 { 1322 InputMap focusInputMap = 1323 (InputMap) SharedUIDefaults.get("Tree.focusInputMap"); 1324 SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, 1325 focusInputMap); 1326 InputMap ancestorInputMap = 1327 (InputMap) SharedUIDefaults.get("Tree.ancestorInputMap"); 1328 SwingUtilities.replaceUIInputMap(tree, 1329 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 1330 ancestorInputMap); 1331 1332 SwingUtilities.replaceUIActionMap(tree, getActionMap()); 1333 } 1334 1335 /** 1336 * Creates and returns the shared action map for JTrees. 1337 * 1338 * @return the shared action map for JTrees 1339 */ 1340 private ActionMap getActionMap() 1341 { 1342 ActionMap am = (ActionMap) UIManager.get("Tree.actionMap"); 1343 if (am == null) 1344 { 1345 am = createDefaultActions(); 1346 UIManager.getLookAndFeelDefaults().put("Tree.actionMap", am); 1347 } 1348 return am; 1349 } 1350 1351 /** 1352 * Creates the default actions when there are none specified by the L&F. 1353 * 1354 * @return the default actions 1355 */ 1356 private ActionMap createDefaultActions() 1357 { 1358 ActionMapUIResource am = new ActionMapUIResource(); 1359 Action action; 1360 1361 // TreeHomeAction. 1362 action = new TreeHomeAction(-1, "selectFirst"); 1363 am.put(action.getValue(Action.NAME), action); 1364 action = new TreeHomeAction(-1, "selectFirstChangeLead"); 1365 am.put(action.getValue(Action.NAME), action); 1366 action = new TreeHomeAction(-1, "selectFirstExtendSelection"); 1367 am.put(action.getValue(Action.NAME), action); 1368 action = new TreeHomeAction(1, "selectLast"); 1369 am.put(action.getValue(Action.NAME), action); 1370 action = new TreeHomeAction(1, "selectLastChangeLead"); 1371 am.put(action.getValue(Action.NAME), action); 1372 action = new TreeHomeAction(1, "selectLastExtendSelection"); 1373 am.put(action.getValue(Action.NAME), action); 1374 1375 // TreeIncrementAction. 1376 action = new TreeIncrementAction(-1, "selectPrevious"); 1377 am.put(action.getValue(Action.NAME), action); 1378 action = new TreeIncrementAction(-1, "selectPreviousExtendSelection"); 1379 am.put(action.getValue(Action.NAME), action); 1380 action = new TreeIncrementAction(-1, "selectPreviousChangeLead"); 1381 am.put(action.getValue(Action.NAME), action); 1382 action = new TreeIncrementAction(1, "selectNext"); 1383 am.put(action.getValue(Action.NAME), action); 1384 action = new TreeIncrementAction(1, "selectNextExtendSelection"); 1385 am.put(action.getValue(Action.NAME), action); 1386 action = new TreeIncrementAction(1, "selectNextChangeLead"); 1387 am.put(action.getValue(Action.NAME), action); 1388 1389 // TreeTraverseAction. 1390 action = new TreeTraverseAction(-1, "selectParent"); 1391 am.put(action.getValue(Action.NAME), action); 1392 action = new TreeTraverseAction(1, "selectChild"); 1393 am.put(action.getValue(Action.NAME), action); 1394 1395 // TreeToggleAction. 1396 action = new TreeToggleAction("toggleAndAnchor"); 1397 am.put(action.getValue(Action.NAME), action); 1398 1399 // TreePageAction. 1400 action = new TreePageAction(-1, "scrollUpChangeSelection"); 1401 am.put(action.getValue(Action.NAME), action); 1402 action = new TreePageAction(-1, "scrollUpExtendSelection"); 1403 am.put(action.getValue(Action.NAME), action); 1404 action = new TreePageAction(-1, "scrollUpChangeLead"); 1405 am.put(action.getValue(Action.NAME), action); 1406 action = new TreePageAction(1, "scrollDownChangeSelection"); 1407 am.put(action.getValue(Action.NAME), action); 1408 action = new TreePageAction(1, "scrollDownExtendSelection"); 1409 am.put(action.getValue(Action.NAME), action); 1410 action = new TreePageAction(1, "scrollDownChangeLead"); 1411 am.put(action.getValue(Action.NAME), action); 1412 1413 // Tree editing actions 1414 action = new TreeStartEditingAction("startEditing"); 1415 am.put(action.getValue(Action.NAME), action); 1416 action = new TreeCancelEditingAction("cancel"); 1417 am.put(action.getValue(Action.NAME), action); 1418 1419 1420 return am; 1421 } 1422 1423 /** 1424 * Converts the modifiers. 1425 * 1426 * @param mod - modifier to convert 1427 * @returns the new modifier 1428 */ 1429 private int convertModifiers(int mod) 1430 { 1431 if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0) 1432 { 1433 mod |= KeyEvent.SHIFT_MASK; 1434 mod &= ~ KeyEvent.SHIFT_DOWN_MASK; 1435 } 1436 if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0) 1437 { 1438 mod |= KeyEvent.CTRL_MASK; 1439 mod &= ~ KeyEvent.CTRL_DOWN_MASK; 1440 } 1441 if ((mod & KeyEvent.META_DOWN_MASK) != 0) 1442 { 1443 mod |= KeyEvent.META_MASK; 1444 mod &= ~ KeyEvent.META_DOWN_MASK; 1445 } 1446 if ((mod & KeyEvent.ALT_DOWN_MASK) != 0) 1447 { 1448 mod |= KeyEvent.ALT_MASK; 1449 mod &= ~ KeyEvent.ALT_DOWN_MASK; 1450 } 1451 if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0) 1452 { 1453 mod |= KeyEvent.ALT_GRAPH_MASK; 1454 mod &= ~ KeyEvent.ALT_GRAPH_DOWN_MASK; 1455 } 1456 return mod; 1457 } 1458 1459 /** 1460 * Install all listeners for this 1461 */ 1462 protected void installListeners() 1463 { 1464 propertyChangeListener = createPropertyChangeListener(); 1465 tree.addPropertyChangeListener(propertyChangeListener); 1466 1467 focusListener = createFocusListener(); 1468 tree.addFocusListener(focusListener); 1469 1470 treeSelectionListener = createTreeSelectionListener(); 1471 tree.addTreeSelectionListener(treeSelectionListener); 1472 1473 mouseListener = createMouseListener(); 1474 tree.addMouseListener(mouseListener); 1475 1476 keyListener = createKeyListener(); 1477 tree.addKeyListener(keyListener); 1478 1479 selectionModelPropertyChangeListener = 1480 createSelectionModelPropertyChangeListener(); 1481 if (treeSelectionModel != null 1482 && selectionModelPropertyChangeListener != null) 1483 { 1484 treeSelectionModel.addPropertyChangeListener( 1485 selectionModelPropertyChangeListener); 1486 } 1487 1488 componentListener = createComponentListener(); 1489 tree.addComponentListener(componentListener); 1490 1491 treeExpansionListener = createTreeExpansionListener(); 1492 tree.addTreeExpansionListener(treeExpansionListener); 1493 1494 treeModelListener = createTreeModelListener(); 1495 if (treeModel != null) 1496 treeModel.addTreeModelListener(treeModelListener); 1497 1498 cellEditorListener = createCellEditorListener(); 1499 } 1500 1501 /** 1502 * Install the UI for the component 1503 * 1504 * @param c the component to install UI for 1505 */ 1506 public void installUI(JComponent c) 1507 { 1508 tree = (JTree) c; 1509 1510 prepareForUIInstall(); 1511 installDefaults(); 1512 installComponents(); 1513 installKeyboardActions(); 1514 installListeners(); 1515 completeUIInstall(); 1516 } 1517 1518 /** 1519 * Uninstall the defaults for the tree 1520 */ 1521 protected void uninstallDefaults() 1522 { 1523 tree.setFont(null); 1524 tree.setForeground(null); 1525 tree.setBackground(null); 1526 } 1527 1528 /** 1529 * Uninstall the UI for the component 1530 * 1531 * @param c the component to uninstall UI for 1532 */ 1533 public void uninstallUI(JComponent c) 1534 { 1535 completeEditing(); 1536 1537 prepareForUIUninstall(); 1538 uninstallDefaults(); 1539 uninstallKeyboardActions(); 1540 uninstallListeners(); 1541 uninstallComponents(); 1542 completeUIUninstall(); 1543 } 1544 1545 /** 1546 * Paints the specified component appropriate for the look and feel. This 1547 * method is invoked from the ComponentUI.update method when the specified 1548 * component is being painted. Subclasses should override this method and use 1549 * the specified Graphics object to render the content of the component. 1550 * 1551 * @param g the Graphics context in which to paint 1552 * @param c the component being painted; this argument is often ignored, but 1553 * might be used if the UI object is stateless and shared by multiple 1554 * components 1555 */ 1556 public void paint(Graphics g, JComponent c) 1557 { 1558 JTree tree = (JTree) c; 1559 1560 int rows = treeState.getRowCount(); 1561 1562 if (rows == 0) 1563 // There is nothing to do if the tree is empty. 1564 return; 1565 1566 Rectangle clip = g.getClipBounds(); 1567 1568 Insets insets = tree.getInsets(); 1569 1570 if (clip != null && treeModel != null) 1571 { 1572 int startIndex = tree.getClosestRowForLocation(clip.x, clip.y); 1573 int endIndex = tree.getClosestRowForLocation(clip.x + clip.width, 1574 clip.y + clip.height); 1575 // Also paint dashes to the invisible nodes below. 1576 // These should be painted first, otherwise they may cover 1577 // the control icons. 1578 if (endIndex < rows) 1579 for (int i = endIndex + 1; i < rows; i++) 1580 { 1581 TreePath path = treeState.getPathForRow(i); 1582 if (isLastChild(path)) 1583 paintVerticalPartOfLeg(g, clip, insets, path); 1584 } 1585 1586 // The two loops are required to ensure that the lines are not 1587 // painted over the other tree components. 1588 1589 int n = endIndex - startIndex + 1; 1590 Rectangle[] bounds = new Rectangle[n]; 1591 boolean[] isLeaf = new boolean[n]; 1592 boolean[] isExpanded = new boolean[n]; 1593 TreePath[] path = new TreePath[n]; 1594 int k; 1595 1596 k = 0; 1597 for (int i = startIndex; i <= endIndex; i++, k++) 1598 { 1599 path[k] = treeState.getPathForRow(i); 1600 if (path[k] != null) 1601 { 1602 isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent()); 1603 isExpanded[k] = tree.isExpanded(path[k]); 1604 bounds[k] = getPathBounds(tree, path[k]); 1605 1606 paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k], 1607 i, isExpanded[k], false, isLeaf[k]); 1608 } 1609 if (isLastChild(path[k])) 1610 paintVerticalPartOfLeg(g, clip, insets, path[k]); 1611 } 1612 1613 k = 0; 1614 for (int i = startIndex; i <= endIndex; i++, k++) 1615 { 1616 if (path[k] != null) 1617 paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k], 1618 false, isLeaf[k]); 1619 } 1620 } 1621 } 1622 1623 /** 1624 * Check if the path is referring to the last child of some parent. 1625 */ 1626 private boolean isLastChild(TreePath path) 1627 { 1628 if (path == null) 1629 return false; 1630 else if (path instanceof GnuPath) 1631 { 1632 // Except the seldom case when the layout cache is changed, this 1633 // optimized code will be executed. 1634 return ((GnuPath) path).isLastChild; 1635 } 1636 else 1637 { 1638 // Non optimized general case. 1639 TreePath parent = path.getParentPath(); 1640 if (parent == null) 1641 return false; 1642 int childCount = treeState.getVisibleChildCount(parent); 1643 int p = treeModel.getIndexOfChild(parent, path.getLastPathComponent()); 1644 return p == childCount - 1; 1645 } 1646 } 1647 1648 /** 1649 * Ensures that the rows identified by beginRow through endRow are visible. 1650 * 1651 * @param beginRow is the first row 1652 * @param endRow is the last row 1653 */ 1654 protected void ensureRowsAreVisible(int beginRow, int endRow) 1655 { 1656 if (beginRow < endRow) 1657 { 1658 int temp = endRow; 1659 endRow = beginRow; 1660 beginRow = temp; 1661 } 1662 1663 for (int i = beginRow; i < endRow; i++) 1664 { 1665 TreePath path = getPathForRow(tree, i); 1666 if (! tree.isVisible(path)) 1667 tree.makeVisible(path); 1668 } 1669 } 1670 1671 /** 1672 * Sets the preferred minimum size. 1673 * 1674 * @param newSize is the new preferred minimum size. 1675 */ 1676 public void setPreferredMinSize(Dimension newSize) 1677 { 1678 preferredMinSize = newSize; 1679 } 1680 1681 /** 1682 * Gets the preferred minimum size. 1683 * 1684 * @returns the preferred minimum size. 1685 */ 1686 public Dimension getPreferredMinSize() 1687 { 1688 if (preferredMinSize == null) 1689 return getPreferredSize(tree); 1690 else 1691 return preferredMinSize; 1692 } 1693 1694 /** 1695 * Returns the preferred size to properly display the tree, this is a cover 1696 * method for getPreferredSize(c, false). 1697 * 1698 * @param c the component whose preferred size is being queried; this argument 1699 * is often ignored but might be used if the UI object is stateless 1700 * and shared by multiple components 1701 * @return the preferred size 1702 */ 1703 public Dimension getPreferredSize(JComponent c) 1704 { 1705 return getPreferredSize(c, false); 1706 } 1707 1708 /** 1709 * Returns the preferred size to represent the tree in c. If checkConsistancy 1710 * is true, checkConsistancy is messaged first. 1711 * 1712 * @param c the component whose preferred size is being queried. 1713 * @param checkConsistancy if true must check consistancy 1714 * @return the preferred size 1715 */ 1716 public Dimension getPreferredSize(JComponent c, boolean checkConsistancy) 1717 { 1718 if (! validCachedPreferredSize) 1719 { 1720 Rectangle size = tree.getBounds(); 1721 // Add the scrollbar dimensions to the preferred size. 1722 preferredSize = new Dimension(treeState.getPreferredWidth(size), 1723 treeState.getPreferredHeight()); 1724 validCachedPreferredSize = true; 1725 } 1726 return preferredSize; 1727 } 1728 1729 /** 1730 * Returns the minimum size for this component. Which will be the min 1731 * preferred size or (0,0). 1732 * 1733 * @param c the component whose min size is being queried. 1734 * @returns the preferred size or null 1735 */ 1736 public Dimension getMinimumSize(JComponent c) 1737 { 1738 return preferredMinSize = getPreferredSize(c); 1739 } 1740 1741 /** 1742 * Returns the maximum size for the component, which will be the preferred 1743 * size if the instance is currently in JTree or (0,0). 1744 * 1745 * @param c the component whose preferred size is being queried 1746 * @return the max size or null 1747 */ 1748 public Dimension getMaximumSize(JComponent c) 1749 { 1750 return getPreferredSize(c); 1751 } 1752 1753 /** 1754 * Messages to stop the editing session. If the UI the receiver is providing 1755 * the look and feel for returns true from 1756 * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked 1757 * on the current editor. Then completeEditing will be messaged with false, 1758 * true, false to cancel any lingering editing. 1759 */ 1760 protected void completeEditing() 1761 { 1762 if (tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing 1763 && editingComponent != null) 1764 cellEditor.stopCellEditing(); 1765 1766 completeEditing(false, true, false); 1767 } 1768 1769 /** 1770 * Stops the editing session. If messageStop is true, the editor is messaged 1771 * with stopEditing, if messageCancel is true the editor is messaged with 1772 * cancelEditing. If messageTree is true, the treeModel is messaged with 1773 * valueForPathChanged. 1774 * 1775 * @param messageStop message to stop editing 1776 * @param messageCancel message to cancel editing 1777 * @param messageTree message to treeModel 1778 */ 1779 protected void completeEditing(boolean messageStop, boolean messageCancel, 1780 boolean messageTree) 1781 { 1782 // Make no attempt to complete the non existing editing session. 1783 if (stopEditingInCompleteEditing && editingComponent != null) 1784 { 1785 Component comp = editingComponent; 1786 TreePath p = editingPath; 1787 editingComponent = null; 1788 editingPath = null; 1789 if (messageStop) 1790 cellEditor.stopCellEditing(); 1791 else if (messageCancel) 1792 cellEditor.cancelCellEditing(); 1793 1794 tree.remove(comp); 1795 1796 if (editorHasDifferentSize) 1797 { 1798 treeState.invalidatePathBounds(p); 1799 updateSize(); 1800 } 1801 else 1802 { 1803 // Need to refresh the tree. 1804 Rectangle b = getPathBounds(tree, p); 1805 tree.repaint(0, b.y, tree.getWidth(), b.height); 1806 } 1807 1808 if (messageTree) 1809 { 1810 Object value = cellEditor.getCellEditorValue(); 1811 treeModel.valueForPathChanged(p, value); 1812 } 1813 } 1814 } 1815 1816 /** 1817 * Will start editing for node if there is a cellEditor and shouldSelectCall 1818 * returns true. This assumes that path is valid and visible. 1819 * 1820 * @param path is the path to start editing 1821 * @param event is the MouseEvent performed on the path 1822 * @return true if successful 1823 */ 1824 protected boolean startEditing(TreePath path, MouseEvent event) 1825 { 1826 // Maybe cancel editing. 1827 if (isEditing(tree) && tree.getInvokesStopCellEditing() 1828 && ! stopEditing(tree)) 1829 return false; 1830 1831 completeEditing(); 1832 TreeCellEditor ed = cellEditor; 1833 if (ed != null && tree.isPathEditable(path)) 1834 { 1835 if (ed.isCellEditable(event)) 1836 { 1837 editingRow = getRowForPath(tree, path); 1838 Object value = path.getLastPathComponent(); 1839 boolean isSelected = tree.isPathSelected(path); 1840 boolean isExpanded = tree.isExpanded(editingPath); 1841 boolean isLeaf = treeModel.isLeaf(value); 1842 editingComponent = ed.getTreeCellEditorComponent(tree, value, 1843 isSelected, 1844 isExpanded, 1845 isLeaf, 1846 editingRow); 1847 1848 Rectangle bounds = getPathBounds(tree, path); 1849 1850 Dimension size = editingComponent.getPreferredSize(); 1851 int rowHeight = getRowHeight(); 1852 if (size.height != bounds.height && rowHeight > 0) 1853 size.height = rowHeight; 1854 1855 if (size.width != bounds.width || size.height != bounds.height) 1856 { 1857 editorHasDifferentSize = true; 1858 treeState.invalidatePathBounds(path); 1859 updateSize(); 1860 } 1861 else 1862 editorHasDifferentSize = false; 1863 1864 // The editing component must be added to its container. We add the 1865 // container, not the editing component itself. 1866 tree.add(editingComponent); 1867 editingComponent.setBounds(bounds.x, bounds.y, size.width, 1868 size.height); 1869 editingComponent.validate(); 1870 editingPath = path; 1871 1872 if (ed.shouldSelectCell(event)) 1873 { 1874 stopEditingInCompleteEditing = false; 1875 tree.setSelectionRow(editingRow); 1876 stopEditingInCompleteEditing = true; 1877 } 1878 1879 editorRequestFocus(editingComponent); 1880 // Register MouseInputHandler to redispatch initial mouse events 1881 // correctly. 1882 if (event instanceof MouseEvent) 1883 { 1884 Point p = SwingUtilities.convertPoint(tree, event.getX(), event.getY(), 1885 editingComponent); 1886 Component active = 1887 SwingUtilities.getDeepestComponentAt(editingComponent, p.x, p.y); 1888 if (active != null) 1889 { 1890 MouseInputHandler ih = new MouseInputHandler(tree, active, event); 1891 1892 } 1893 } 1894 1895 return true; 1896 } 1897 else 1898 editingComponent = null; 1899 } 1900 return false; 1901 } 1902 1903 /** 1904 * Requests focus on the editor. The method is necessary since the 1905 * DefaultTreeCellEditor returns a container that contains the 1906 * actual editor, and we want to request focus on the editor, not the 1907 * container. 1908 */ 1909 private void editorRequestFocus(Component c) 1910 { 1911 if (c instanceof Container) 1912 { 1913 // TODO: Maybe do something more reasonable here, like queriying the 1914 // FocusTraversalPolicy. 1915 Container cont = (Container) c; 1916 if (cont.getComponentCount() > 0) 1917 cont.getComponent(0).requestFocus(); 1918 } 1919 else if (c.isFocusable()) 1920 c.requestFocus(); 1921 1922 } 1923 1924 /** 1925 * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or 1926 * collapse region of the row, this will toggle the row. 1927 * 1928 * @param path the path we are concerned with 1929 * @param mouseX is the cursor's x position 1930 * @param mouseY is the cursor's y position 1931 */ 1932 protected void checkForClickInExpandControl(TreePath path, int mouseX, 1933 int mouseY) 1934 { 1935 if (isLocationInExpandControl(path, mouseX, mouseY)) 1936 handleExpandControlClick(path, mouseX, mouseY); 1937 } 1938 1939 /** 1940 * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in 1941 * the area of row that is used to expand/collpse the node and the node at row 1942 * does not represent a leaf. 1943 * 1944 * @param path the path we are concerned with 1945 * @param mouseX is the cursor's x position 1946 * @param mouseY is the cursor's y position 1947 * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in 1948 * the area of row that is used to expand/collpse the node and the 1949 * node at row does not represent a leaf. 1950 */ 1951 protected boolean isLocationInExpandControl(TreePath path, int mouseX, 1952 int mouseY) 1953 { 1954 boolean cntlClick = false; 1955 if (! treeModel.isLeaf(path.getLastPathComponent())) 1956 { 1957 int width; 1958 Icon expandedIcon = getExpandedIcon(); 1959 if (expandedIcon != null) 1960 width = expandedIcon.getIconWidth(); 1961 else 1962 // Only guessing. This is the width of 1963 // the tree control icon in Metal L&F. 1964 width = 18; 1965 1966 Insets i = tree.getInsets(); 1967 1968 int depth; 1969 if (isRootVisible()) 1970 depth = path.getPathCount()-1; 1971 else 1972 depth = path.getPathCount()-2; 1973 1974 int left = getRowX(tree.getRowForPath(path), depth) 1975 - width + i.left; 1976 cntlClick = mouseX >= left && mouseX <= left + width; 1977 } 1978 return cntlClick; 1979 } 1980 1981 /** 1982 * Messaged when the user clicks the particular row, this invokes 1983 * toggleExpandState. 1984 * 1985 * @param path the path we are concerned with 1986 * @param mouseX is the cursor's x position 1987 * @param mouseY is the cursor's y position 1988 */ 1989 protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY) 1990 { 1991 toggleExpandState(path); 1992 } 1993 1994 /** 1995 * Expands path if it is not expanded, or collapses row if it is expanded. If 1996 * expanding a path and JTree scroll on expand, ensureRowsAreVisible is 1997 * invoked to scroll as many of the children to visible as possible (tries to 1998 * scroll to last visible descendant of path). 1999 * 2000 * @param path the path we are concerned with 2001 */ 2002 protected void toggleExpandState(TreePath path) 2003 { 2004 // tree.isExpanded(path) would do the same, but treeState knows faster. 2005 if (treeState.isExpanded(path)) 2006 tree.collapsePath(path); 2007 else 2008 tree.expandPath(path); 2009 } 2010 2011 /** 2012 * Returning true signifies a mouse event on the node should toggle the 2013 * selection of only the row under the mouse. The BasisTreeUI treats the 2014 * event as "toggle selection event" if the CTRL button was pressed while 2015 * clicking. The event is not counted as toggle event if the associated 2016 * tree does not support the multiple selection. 2017 * 2018 * @param event is the MouseEvent performed on the row. 2019 * @return true signifies a mouse event on the node should toggle the 2020 * selection of only the row under the mouse. 2021 */ 2022 protected boolean isToggleSelectionEvent(MouseEvent event) 2023 { 2024 return 2025 (tree.getSelectionModel().getSelectionMode() != 2026 TreeSelectionModel.SINGLE_TREE_SELECTION) && 2027 ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0); 2028 } 2029 2030 /** 2031 * Returning true signifies a mouse event on the node should select from the 2032 * anchor point. The BasisTreeUI treats the event as "multiple selection 2033 * event" if the SHIFT button was pressed while clicking. The event is not 2034 * counted as multiple selection event if the associated tree does not support 2035 * the multiple selection. 2036 * 2037 * @param event is the MouseEvent performed on the node. 2038 * @return true signifies a mouse event on the node should select from the 2039 * anchor point. 2040 */ 2041 protected boolean isMultiSelectEvent(MouseEvent event) 2042 { 2043 return 2044 (tree.getSelectionModel().getSelectionMode() != 2045 TreeSelectionModel.SINGLE_TREE_SELECTION) && 2046 ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0); 2047 } 2048 2049 /** 2050 * Returning true indicates the row under the mouse should be toggled based on 2051 * the event. This is invoked after checkForClickInExpandControl, implying the 2052 * location is not in the expand (toggle) control. 2053 * 2054 * @param event is the MouseEvent performed on the row. 2055 * @return true indicates the row under the mouse should be toggled based on 2056 * the event. 2057 */ 2058 protected boolean isToggleEvent(MouseEvent event) 2059 { 2060 boolean toggle = false; 2061 if (SwingUtilities.isLeftMouseButton(event)) 2062 { 2063 int clickCount = tree.getToggleClickCount(); 2064 if (clickCount > 0 && event.getClickCount() == clickCount) 2065 toggle = true; 2066 } 2067 return toggle; 2068 } 2069 2070 /** 2071 * Messaged to update the selection based on a MouseEvent over a particular 2072 * row. If the even is a toggle selection event, the row is either selected, 2073 * or deselected. If the event identifies a multi selection event, the 2074 * selection is updated from the anchor point. Otherwise, the row is selected, 2075 * and the previous selection is cleared.</p> 2076 * 2077 * @param path is the path selected for an event 2078 * @param event is the MouseEvent performed on the path. 2079 * 2080 * @see #isToggleSelectionEvent(MouseEvent) 2081 * @see #isMultiSelectEvent(MouseEvent) 2082 */ 2083 protected void selectPathForEvent(TreePath path, MouseEvent event) 2084 { 2085 if (isToggleSelectionEvent(event)) 2086 { 2087 // The event selects or unselects the clicked row. 2088 if (tree.isPathSelected(path)) 2089 tree.removeSelectionPath(path); 2090 else 2091 { 2092 tree.addSelectionPath(path); 2093 tree.setAnchorSelectionPath(path); 2094 } 2095 } 2096 else if (isMultiSelectEvent(event)) 2097 { 2098 // The event extends selection form anchor till the clicked row. 2099 TreePath anchor = tree.getAnchorSelectionPath(); 2100 if (anchor != null) 2101 { 2102 int aRow = getRowForPath(tree, anchor); 2103 tree.addSelectionInterval(aRow, getRowForPath(tree, path)); 2104 } 2105 else 2106 tree.addSelectionPath(path); 2107 } 2108 else 2109 { 2110 // This is an ordinary event that just selects the clicked row. 2111 tree.setSelectionPath(path); 2112 if (isToggleEvent(event)) 2113 toggleExpandState(path); 2114 } 2115 } 2116 2117 /** 2118 * Returns true if the node at <code>row</code> is a leaf. 2119 * 2120 * @param row is the row we are concerned with. 2121 * @return true if the node at <code>row</code> is a leaf. 2122 */ 2123 protected boolean isLeaf(int row) 2124 { 2125 TreePath pathForRow = getPathForRow(tree, row); 2126 if (pathForRow == null) 2127 return true; 2128 2129 Object node = pathForRow.getLastPathComponent(); 2130 return treeModel.isLeaf(node); 2131 } 2132 2133 /** 2134 * The action to start editing at the current lead selection path. 2135 */ 2136 class TreeStartEditingAction 2137 extends AbstractAction 2138 { 2139 /** 2140 * Creates the new tree cancel editing action. 2141 * 2142 * @param name the name of the action (used in toString). 2143 */ 2144 public TreeStartEditingAction(String name) 2145 { 2146 super(name); 2147 } 2148 2149 /** 2150 * Start editing at the current lead selection path. 2151 * 2152 * @param e the ActionEvent that caused this action. 2153 */ 2154 public void actionPerformed(ActionEvent e) 2155 { 2156 TreePath lead = tree.getLeadSelectionPath(); 2157 if (!tree.isEditing()) 2158 tree.startEditingAtPath(lead); 2159 } 2160 } 2161 2162 /** 2163 * Updates the preferred size when scrolling, if necessary. 2164 */ 2165 public class ComponentHandler 2166 extends ComponentAdapter 2167 implements ActionListener 2168 { 2169 /** 2170 * Timer used when inside a scrollpane and the scrollbar is adjusting 2171 */ 2172 protected Timer timer; 2173 2174 /** ScrollBar that is being adjusted */ 2175 protected JScrollBar scrollBar; 2176 2177 /** 2178 * Constructor 2179 */ 2180 public ComponentHandler() 2181 { 2182 // Nothing to do here. 2183 } 2184 2185 /** 2186 * Invoked when the component's position changes. 2187 * 2188 * @param e the event that occurs when moving the component 2189 */ 2190 public void componentMoved(ComponentEvent e) 2191 { 2192 if (timer == null) 2193 { 2194 JScrollPane scrollPane = getScrollPane(); 2195 if (scrollPane == null) 2196 updateSize(); 2197 else 2198 { 2199 // Determine the scrollbar that is adjusting, if any, and 2200 // start the timer for that. If no scrollbar is adjusting, 2201 // we simply call updateSize(). 2202 scrollBar = scrollPane.getVerticalScrollBar(); 2203 if (scrollBar == null || !scrollBar.getValueIsAdjusting()) 2204 { 2205 // It's not the vertical scrollbar, try the horizontal one. 2206 scrollBar = scrollPane.getHorizontalScrollBar(); 2207 if (scrollBar != null && scrollBar.getValueIsAdjusting()) 2208 startTimer(); 2209 else 2210 updateSize(); 2211 } 2212 else 2213 { 2214 startTimer(); 2215 } 2216 } 2217 } 2218 } 2219 2220 /** 2221 * Creates, if necessary, and starts a Timer to check if needed to resize 2222 * the bounds 2223 */ 2224 protected void startTimer() 2225 { 2226 if (timer == null) 2227 { 2228 timer = new Timer(200, this); 2229 timer.setRepeats(true); 2230 } 2231 timer.start(); 2232 } 2233 2234 /** 2235 * Returns the JScrollPane housing the JTree, or null if one isn't found. 2236 * 2237 * @return JScrollPane housing the JTree, or null if one isn't found. 2238 */ 2239 protected JScrollPane getScrollPane() 2240 { 2241 JScrollPane found = null; 2242 Component p = tree.getParent(); 2243 while (p != null && !(p instanceof JScrollPane)) 2244 p = p.getParent(); 2245 if (p instanceof JScrollPane) 2246 found = (JScrollPane) p; 2247 return found; 2248 } 2249 2250 /** 2251 * Public as a result of Timer. If the scrollBar is null, or not adjusting, 2252 * this stops the timer and updates the sizing. 2253 * 2254 * @param ae is the action performed 2255 */ 2256 public void actionPerformed(ActionEvent ae) 2257 { 2258 if (scrollBar == null || !scrollBar.getValueIsAdjusting()) 2259 { 2260 if (timer != null) 2261 timer.stop(); 2262 updateSize(); 2263 timer = null; 2264 scrollBar = null; 2265 } 2266 } 2267 } 2268 2269 /** 2270 * Listener responsible for getting cell editing events and updating the tree 2271 * accordingly. 2272 */ 2273 public class CellEditorHandler 2274 implements CellEditorListener 2275 { 2276 /** 2277 * Constructor 2278 */ 2279 public CellEditorHandler() 2280 { 2281 // Nothing to do here. 2282 } 2283 2284 /** 2285 * Messaged when editing has stopped in the tree. Tells the listeners 2286 * editing has stopped. 2287 * 2288 * @param e is the notification event 2289 */ 2290 public void editingStopped(ChangeEvent e) 2291 { 2292 completeEditing(false, false, true); 2293 } 2294 2295 /** 2296 * Messaged when editing has been canceled in the tree. This tells the 2297 * listeners the editor has canceled editing. 2298 * 2299 * @param e is the notification event 2300 */ 2301 public void editingCanceled(ChangeEvent e) 2302 { 2303 completeEditing(false, false, false); 2304 } 2305 } // CellEditorHandler 2306 2307 /** 2308 * Repaints the lead selection row when focus is lost/grained. 2309 */ 2310 public class FocusHandler 2311 implements FocusListener 2312 { 2313 /** 2314 * Constructor 2315 */ 2316 public FocusHandler() 2317 { 2318 // Nothing to do here. 2319 } 2320 2321 /** 2322 * Invoked when focus is activated on the tree we're in, redraws the lead 2323 * row. Invoked when a component gains the keyboard focus. The method 2324 * repaints the lead row that is shown differently when the tree is in 2325 * focus. 2326 * 2327 * @param e is the focus event that is activated 2328 */ 2329 public void focusGained(FocusEvent e) 2330 { 2331 repaintLeadRow(); 2332 } 2333 2334 /** 2335 * Invoked when focus is deactivated on the tree we're in, redraws the lead 2336 * row. Invoked when a component loses the keyboard focus. The method 2337 * repaints the lead row that is shown differently when the tree is in 2338 * focus. 2339 * 2340 * @param e is the focus event that is deactivated 2341 */ 2342 public void focusLost(FocusEvent e) 2343 { 2344 repaintLeadRow(); 2345 } 2346 2347 /** 2348 * Repaint the lead row. 2349 */ 2350 void repaintLeadRow() 2351 { 2352 TreePath lead = tree.getLeadSelectionPath(); 2353 if (lead != null) 2354 tree.repaint(tree.getPathBounds(lead)); 2355 } 2356 } 2357 2358 /** 2359 * This is used to get multiple key down events to appropriately genereate 2360 * events. 2361 */ 2362 public class KeyHandler 2363 extends KeyAdapter 2364 { 2365 /** Key code that is being generated for. */ 2366 protected Action repeatKeyAction; 2367 2368 /** Set to true while keyPressed is active */ 2369 protected boolean isKeyDown; 2370 2371 /** 2372 * Constructor 2373 */ 2374 public KeyHandler() 2375 { 2376 // Nothing to do here. 2377 } 2378 2379 /** 2380 * Invoked when a key has been typed. Moves the keyboard focus to the first 2381 * element whose first letter matches the alphanumeric key pressed by the 2382 * user. Subsequent same key presses move the keyboard focus to the next 2383 * object that starts with the same letter. 2384 * 2385 * @param e the key typed 2386 */ 2387 public void keyTyped(KeyEvent e) 2388 { 2389 char typed = Character.toLowerCase(e.getKeyChar()); 2390 for (int row = tree.getLeadSelectionRow() + 1; 2391 row < tree.getRowCount(); row++) 2392 { 2393 if (checkMatch(row, typed)) 2394 { 2395 tree.setSelectionRow(row); 2396 tree.scrollRowToVisible(row); 2397 return; 2398 } 2399 } 2400 2401 // Not found below, search above: 2402 for (int row = 0; row < tree.getLeadSelectionRow(); row++) 2403 { 2404 if (checkMatch(row, typed)) 2405 { 2406 tree.setSelectionRow(row); 2407 tree.scrollRowToVisible(row); 2408 return; 2409 } 2410 } 2411 } 2412 2413 /** 2414 * Check if the given tree row starts with this character 2415 * 2416 * @param row the tree row 2417 * @param typed the typed char, must be converted to lowercase 2418 * @return true if the given tree row starts with this character 2419 */ 2420 boolean checkMatch(int row, char typed) 2421 { 2422 TreePath path = treeState.getPathForRow(row); 2423 String node = path.getLastPathComponent().toString(); 2424 if (node.length() > 0) 2425 { 2426 char x = node.charAt(0); 2427 if (typed == Character.toLowerCase(x)) 2428 return true; 2429 } 2430 return false; 2431 } 2432 2433 /** 2434 * Invoked when a key has been pressed. 2435 * 2436 * @param e the key pressed 2437 */ 2438 public void keyPressed(KeyEvent e) 2439 { 2440 // Nothing to do here. 2441 } 2442 2443 /** 2444 * Invoked when a key has been released 2445 * 2446 * @param e the key released 2447 */ 2448 public void keyReleased(KeyEvent e) 2449 { 2450 // Nothing to do here. 2451 } 2452 } 2453 2454 /** 2455 * MouseListener is responsible for updating the selection based on mouse 2456 * events. 2457 */ 2458 public class MouseHandler 2459 extends MouseAdapter 2460 implements MouseMotionListener 2461 { 2462 2463 /** 2464 * If the cell has been selected on mouse press. 2465 */ 2466 private boolean selectedOnPress; 2467 2468 /** 2469 * Constructor 2470 */ 2471 public MouseHandler() 2472 { 2473 // Nothing to do here. 2474 } 2475 2476 /** 2477 * Invoked when a mouse button has been pressed on a component. 2478 * 2479 * @param e is the mouse event that occured 2480 */ 2481 public void mousePressed(MouseEvent e) 2482 { 2483 if (! e.isConsumed()) 2484 { 2485 handleEvent(e); 2486 selectedOnPress = true; 2487 } 2488 else 2489 { 2490 selectedOnPress = false; 2491 } 2492 } 2493 2494 /** 2495 * Invoked when a mouse button is pressed on a component and then dragged. 2496 * MOUSE_DRAGGED events will continue to be delivered to the component where 2497 * the drag originated until the mouse button is released (regardless of 2498 * whether the mouse position is within the bounds of the component). 2499 * 2500 * @param e is the mouse event that occured 2501 */ 2502 public void mouseDragged(MouseEvent e) 2503 { 2504 // Nothing to do here. 2505 } 2506 2507 /** 2508 * Invoked when the mouse button has been moved on a component (with no 2509 * buttons no down). 2510 * 2511 * @param e the mouse event that occured 2512 */ 2513 public void mouseMoved(MouseEvent e) 2514 { 2515 // Nothing to do here. 2516 } 2517 2518 /** 2519 * Invoked when a mouse button has been released on a component. 2520 * 2521 * @param e is the mouse event that occured 2522 */ 2523 public void mouseReleased(MouseEvent e) 2524 { 2525 if (! e.isConsumed() && ! selectedOnPress) 2526 handleEvent(e); 2527 } 2528 2529 /** 2530 * Handles press and release events. 2531 * 2532 * @param e the mouse event 2533 */ 2534 private void handleEvent(MouseEvent e) 2535 { 2536 if (tree != null && tree.isEnabled()) 2537 { 2538 // Maybe stop editing. 2539 if (isEditing(tree) && tree.getInvokesStopCellEditing() 2540 && ! stopEditing(tree)) 2541 return; 2542 2543 // Explicitly request focus. 2544 tree.requestFocusInWindow(); 2545 2546 int x = e.getX(); 2547 int y = e.getY(); 2548 TreePath path = getClosestPathForLocation(tree, x, y); 2549 if (path != null) 2550 { 2551 Rectangle b = getPathBounds(tree, path); 2552 if (y <= b.y + b.height) 2553 { 2554 if (SwingUtilities.isLeftMouseButton(e)) 2555 checkForClickInExpandControl(path, x, y); 2556 if (x > b.x && x <= b.x + b.width) 2557 { 2558 if (! startEditing(path, e)) 2559 selectPathForEvent(path, e); 2560 } 2561 } 2562 } 2563 } 2564 } 2565 } 2566 2567 /** 2568 * MouseInputHandler handles passing all mouse events, including mouse motion 2569 * events, until the mouse is released to the destination it is constructed 2570 * with. 2571 */ 2572 public class MouseInputHandler 2573 implements MouseInputListener 2574 { 2575 /** Source that events are coming from */ 2576 protected Component source; 2577 2578 /** Destination that receives all events. */ 2579 protected Component destination; 2580 2581 /** 2582 * Constructor 2583 * 2584 * @param source that events are coming from 2585 * @param destination that receives all events 2586 * @param e is the event received 2587 */ 2588 public MouseInputHandler(Component source, Component destination, 2589 MouseEvent e) 2590 { 2591 this.source = source; 2592 this.destination = destination; 2593 source.addMouseListener(this); 2594 source.addMouseMotionListener(this); 2595 dispatch(e); 2596 } 2597 2598 /** 2599 * Invoked when the mouse button has been clicked (pressed and released) on 2600 * a component. 2601 * 2602 * @param e mouse event that occured 2603 */ 2604 public void mouseClicked(MouseEvent e) 2605 { 2606 dispatch(e); 2607 } 2608 2609 /** 2610 * Invoked when a mouse button has been pressed on a component. 2611 * 2612 * @param e mouse event that occured 2613 */ 2614 public void mousePressed(MouseEvent e) 2615 { 2616 // Nothing to do here. 2617 } 2618 2619 /** 2620 * Invoked when a mouse button has been released on a component. 2621 * 2622 * @param e mouse event that occured 2623 */ 2624 public void mouseReleased(MouseEvent e) 2625 { 2626 dispatch(e); 2627 removeFromSource(); 2628 } 2629 2630 /** 2631 * Invoked when the mouse enters a component. 2632 * 2633 * @param e mouse event that occured 2634 */ 2635 public void mouseEntered(MouseEvent e) 2636 { 2637 if (! SwingUtilities.isLeftMouseButton(e)) 2638 removeFromSource(); 2639 } 2640 2641 /** 2642 * Invoked when the mouse exits a component. 2643 * 2644 * @param e mouse event that occured 2645 */ 2646 public void mouseExited(MouseEvent e) 2647 { 2648 if (! SwingUtilities.isLeftMouseButton(e)) 2649 removeFromSource(); 2650 } 2651 2652 /** 2653 * Invoked when a mouse button is pressed on a component and then dragged. 2654 * MOUSE_DRAGGED events will continue to be delivered to the component where 2655 * the drag originated until the mouse button is released (regardless of 2656 * whether the mouse position is within the bounds of the component). 2657 * 2658 * @param e mouse event that occured 2659 */ 2660 public void mouseDragged(MouseEvent e) 2661 { 2662 dispatch(e); 2663 } 2664 2665 /** 2666 * Invoked when the mouse cursor has been moved onto a component but no 2667 * buttons have been pushed. 2668 * 2669 * @param e mouse event that occured 2670 */ 2671 public void mouseMoved(MouseEvent e) 2672 { 2673 removeFromSource(); 2674 } 2675 2676 /** 2677 * Removes event from the source 2678 */ 2679 protected void removeFromSource() 2680 { 2681 if (source != null) 2682 { 2683 source.removeMouseListener(this); 2684 source.removeMouseMotionListener(this); 2685 } 2686 source = null; 2687 destination = null; 2688 } 2689 2690 /** 2691 * Redispatches mouse events to the destination. 2692 * 2693 * @param e the mouse event to redispatch 2694 */ 2695 private void dispatch(MouseEvent e) 2696 { 2697 if (destination != null) 2698 { 2699 MouseEvent e2 = SwingUtilities.convertMouseEvent(source, e, 2700 destination); 2701 destination.dispatchEvent(e2); 2702 } 2703 } 2704 } 2705 2706 /** 2707 * Class responsible for getting size of node, method is forwarded to 2708 * BasicTreeUI method. X location does not include insets, that is handled in 2709 * getPathBounds. 2710 */ 2711 public class NodeDimensionsHandler 2712 extends AbstractLayoutCache.NodeDimensions 2713 { 2714 /** 2715 * Constructor 2716 */ 2717 public NodeDimensionsHandler() 2718 { 2719 // Nothing to do here. 2720 } 2721 2722 /** 2723 * Returns, by reference in bounds, the size and x origin to place value at. 2724 * The calling method is responsible for determining the Y location. If 2725 * bounds is null, a newly created Rectangle should be returned, otherwise 2726 * the value should be placed in bounds and returned. 2727 * 2728 * @param cell the value to be represented 2729 * @param row row being queried 2730 * @param depth the depth of the row 2731 * @param expanded true if row is expanded 2732 * @param size a Rectangle containing the size needed to represent value 2733 * @return containing the node dimensions, or null if node has no dimension 2734 */ 2735 public Rectangle getNodeDimensions(Object cell, int row, int depth, 2736 boolean expanded, Rectangle size) 2737 { 2738 Dimension prefSize; 2739 if (editingComponent != null && editingRow == row) 2740 { 2741 // Editing, ask editor for preferred size. 2742 prefSize = editingComponent.getPreferredSize(); 2743 int rowHeight = getRowHeight(); 2744 if (rowHeight > 0 && rowHeight != prefSize.height) 2745 prefSize.height = rowHeight; 2746 } 2747 else 2748 { 2749 // Not editing, ask renderer for preferred size. 2750 Component rend = 2751 currentCellRenderer.getTreeCellRendererComponent(tree, cell, 2752 tree.isRowSelected(row), 2753 expanded, 2754 treeModel.isLeaf(cell), 2755 row, false); 2756 // Make sure the layout is valid. 2757 rendererPane.add(rend); 2758 rend.validate(); 2759 prefSize = rend.getPreferredSize(); 2760 } 2761 if (size != null) 2762 { 2763 size.x = getRowX(row, depth); 2764 // FIXME: This should be handled by the layout cache. 2765 size.y = prefSize.height * row; 2766 size.width = prefSize.width; 2767 size.height = prefSize.height; 2768 } 2769 else 2770 // FIXME: The y should be handled by the layout cache. 2771 size = new Rectangle(getRowX(row, depth), prefSize.height * row, prefSize.width, 2772 prefSize.height); 2773 2774 return size; 2775 } 2776 2777 /** 2778 * Returns the amount to indent the given row 2779 * 2780 * @return amount to indent the given row. 2781 */ 2782 protected int getRowX(int row, int depth) 2783 { 2784 return BasicTreeUI.this.getRowX(row, depth); 2785 } 2786 } // NodeDimensionsHandler 2787 2788 /** 2789 * PropertyChangeListener for the tree. Updates the appropriate variable, or 2790 * TreeState, based on what changes. 2791 */ 2792 public class PropertyChangeHandler 2793 implements PropertyChangeListener 2794 { 2795 2796 /** 2797 * Constructor 2798 */ 2799 public PropertyChangeHandler() 2800 { 2801 // Nothing to do here. 2802 } 2803 2804 /** 2805 * This method gets called when a bound property is changed. 2806 * 2807 * @param event A PropertyChangeEvent object describing the event source and 2808 * the property that has changed. 2809 */ 2810 public void propertyChange(PropertyChangeEvent event) 2811 { 2812 String property = event.getPropertyName(); 2813 if (property.equals(JTree.ROOT_VISIBLE_PROPERTY)) 2814 { 2815 validCachedPreferredSize = false; 2816 treeState.setRootVisible(tree.isRootVisible()); 2817 tree.repaint(); 2818 } 2819 else if (property.equals(JTree.SELECTION_MODEL_PROPERTY)) 2820 { 2821 treeSelectionModel = tree.getSelectionModel(); 2822 treeSelectionModel.setRowMapper(treeState); 2823 } 2824 else if (property.equals(JTree.TREE_MODEL_PROPERTY)) 2825 { 2826 setModel(tree.getModel()); 2827 } 2828 else if (property.equals(JTree.CELL_RENDERER_PROPERTY)) 2829 { 2830 setCellRenderer(tree.getCellRenderer()); 2831 // Update layout. 2832 if (treeState != null) 2833 treeState.invalidateSizes(); 2834 } 2835 else if (property.equals(JTree.EDITABLE_PROPERTY)) 2836 setEditable(((Boolean) event.getNewValue()).booleanValue()); 2837 2838 } 2839 } 2840 2841 /** 2842 * Listener on the TreeSelectionModel, resets the row selection if any of the 2843 * properties of the model change. 2844 */ 2845 public class SelectionModelPropertyChangeHandler 2846 implements PropertyChangeListener 2847 { 2848 2849 /** 2850 * Constructor 2851 */ 2852 public SelectionModelPropertyChangeHandler() 2853 { 2854 // Nothing to do here. 2855 } 2856 2857 /** 2858 * This method gets called when a bound property is changed. 2859 * 2860 * @param event A PropertyChangeEvent object describing the event source and 2861 * the property that has changed. 2862 */ 2863 public void propertyChange(PropertyChangeEvent event) 2864 { 2865 treeSelectionModel.resetRowSelection(); 2866 } 2867 } 2868 2869 /** 2870 * The action to cancel editing on this tree. 2871 */ 2872 public class TreeCancelEditingAction 2873 extends AbstractAction 2874 { 2875 /** 2876 * Creates the new tree cancel editing action. 2877 * 2878 * @param name the name of the action (used in toString). 2879 */ 2880 public TreeCancelEditingAction(String name) 2881 { 2882 super(name); 2883 } 2884 2885 /** 2886 * Invoked when an action occurs, cancels the cell editing (if the 2887 * tree cell is being edited). 2888 * 2889 * @param e event that occured 2890 */ 2891 public void actionPerformed(ActionEvent e) 2892 { 2893 if (isEnabled() && tree.isEditing()) 2894 tree.cancelEditing(); 2895 } 2896 } 2897 2898 /** 2899 * Updates the TreeState in response to nodes expanding/collapsing. 2900 */ 2901 public class TreeExpansionHandler 2902 implements TreeExpansionListener 2903 { 2904 2905 /** 2906 * Constructor 2907 */ 2908 public TreeExpansionHandler() 2909 { 2910 // Nothing to do here. 2911 } 2912 2913 /** 2914 * Called whenever an item in the tree has been expanded. 2915 * 2916 * @param event is the event that occured 2917 */ 2918 public void treeExpanded(TreeExpansionEvent event) 2919 { 2920 validCachedPreferredSize = false; 2921 treeState.setExpandedState(event.getPath(), true); 2922 // The maximal cell height may change 2923 maxHeight = 0; 2924 tree.revalidate(); 2925 tree.repaint(); 2926 } 2927 2928 /** 2929 * Called whenever an item in the tree has been collapsed. 2930 * 2931 * @param event is the event that occured 2932 */ 2933 public void treeCollapsed(TreeExpansionEvent event) 2934 { 2935 completeEditing(); 2936 validCachedPreferredSize = false; 2937 treeState.setExpandedState(event.getPath(), false); 2938 // The maximal cell height may change 2939 maxHeight = 0; 2940 tree.revalidate(); 2941 tree.repaint(); 2942 } 2943 } // TreeExpansionHandler 2944 2945 /** 2946 * TreeHomeAction is used to handle end/home actions. Scrolls either the first 2947 * or last cell to be visible based on direction. 2948 */ 2949 public class TreeHomeAction 2950 extends AbstractAction 2951 { 2952 2953 /** The direction, either home or end */ 2954 protected int direction; 2955 2956 /** 2957 * Creates a new TreeHomeAction instance. 2958 * 2959 * @param dir the direction to go to, <code>-1</code> for home, 2960 * <code>1</code> for end 2961 * @param name the name of the action 2962 */ 2963 public TreeHomeAction(int dir, String name) 2964 { 2965 direction = dir; 2966 putValue(Action.NAME, name); 2967 } 2968 2969 /** 2970 * Invoked when an action occurs. 2971 * 2972 * @param e is the event that occured 2973 */ 2974 public void actionPerformed(ActionEvent e) 2975 { 2976 if (tree != null) 2977 { 2978 String command = (String) getValue(Action.NAME); 2979 if (command.equals("selectFirst")) 2980 { 2981 ensureRowsAreVisible(0, 0); 2982 tree.setSelectionInterval(0, 0); 2983 } 2984 if (command.equals("selectFirstChangeLead")) 2985 { 2986 ensureRowsAreVisible(0, 0); 2987 tree.setLeadSelectionPath(getPathForRow(tree, 0)); 2988 } 2989 if (command.equals("selectFirstExtendSelection")) 2990 { 2991 ensureRowsAreVisible(0, 0); 2992 TreePath anchorPath = tree.getAnchorSelectionPath(); 2993 if (anchorPath == null) 2994 tree.setSelectionInterval(0, 0); 2995 else 2996 { 2997 int anchorRow = getRowForPath(tree, anchorPath); 2998 tree.setSelectionInterval(0, anchorRow); 2999 tree.setAnchorSelectionPath(anchorPath); 3000 tree.setLeadSelectionPath(getPathForRow(tree, 0)); 3001 } 3002 } 3003 else if (command.equals("selectLast")) 3004 { 3005 int end = getRowCount(tree) - 1; 3006 ensureRowsAreVisible(end, end); 3007 tree.setSelectionInterval(end, end); 3008 } 3009 else if (command.equals("selectLastChangeLead")) 3010 { 3011 int end = getRowCount(tree) - 1; 3012 ensureRowsAreVisible(end, end); 3013 tree.setLeadSelectionPath(getPathForRow(tree, end)); 3014 } 3015 else if (command.equals("selectLastExtendSelection")) 3016 { 3017 int end = getRowCount(tree) - 1; 3018 ensureRowsAreVisible(end, end); 3019 TreePath anchorPath = tree.getAnchorSelectionPath(); 3020 if (anchorPath == null) 3021 tree.setSelectionInterval(end, end); 3022 else 3023 { 3024 int anchorRow = getRowForPath(tree, anchorPath); 3025 tree.setSelectionInterval(end, anchorRow); 3026 tree.setAnchorSelectionPath(anchorPath); 3027 tree.setLeadSelectionPath(getPathForRow(tree, end)); 3028 } 3029 } 3030 } 3031 3032 // Ensure that the lead path is visible after the increment action. 3033 tree.scrollPathToVisible(tree.getLeadSelectionPath()); 3034 } 3035 3036 /** 3037 * Returns true if the action is enabled. 3038 * 3039 * @return true if the action is enabled. 3040 */ 3041 public boolean isEnabled() 3042 { 3043 return (tree != null) && tree.isEnabled(); 3044 } 3045 } 3046 3047 /** 3048 * TreeIncrementAction is used to handle up/down actions. Selection is moved 3049 * up or down based on direction. 3050 */ 3051 public class TreeIncrementAction 3052 extends AbstractAction 3053 { 3054 3055 /** 3056 * Specifies the direction to adjust the selection by. 3057 */ 3058 protected int direction; 3059 3060 /** 3061 * Creates a new TreeIncrementAction. 3062 * 3063 * @param dir up or down, <code>-1</code> for up, <code>1</code> for down 3064 * @param name is the name of the direction 3065 */ 3066 public TreeIncrementAction(int dir, String name) 3067 { 3068 direction = dir; 3069 putValue(Action.NAME, name); 3070 } 3071 3072 /** 3073 * Invoked when an action occurs. 3074 * 3075 * @param e is the event that occured 3076 */ 3077 public void actionPerformed(ActionEvent e) 3078 { 3079 TreePath currentPath = tree.getLeadSelectionPath(); 3080 int currentRow; 3081 3082 if (currentPath != null) 3083 currentRow = treeState.getRowForPath(currentPath); 3084 else 3085 currentRow = 0; 3086 3087 int rows = treeState.getRowCount(); 3088 3089 int nextRow = currentRow + 1; 3090 int prevRow = currentRow - 1; 3091 boolean hasNext = nextRow < rows; 3092 boolean hasPrev = prevRow >= 0 && rows > 0; 3093 TreePath newPath; 3094 String command = (String) getValue(Action.NAME); 3095 3096 if (command.equals("selectPreviousChangeLead") && hasPrev) 3097 { 3098 newPath = treeState.getPathForRow(prevRow); 3099 tree.setSelectionPath(newPath); 3100 tree.setAnchorSelectionPath(newPath); 3101 tree.setLeadSelectionPath(newPath); 3102 } 3103 else if (command.equals("selectPreviousExtendSelection") && hasPrev) 3104 { 3105 newPath = treeState.getPathForRow(prevRow); 3106 3107 // If the new path is already selected, the selection shrinks, 3108 // unselecting the previously current path. 3109 if (tree.isPathSelected(newPath)) 3110 tree.getSelectionModel().removeSelectionPath(currentPath); 3111 3112 // This must be called in any case because it updates the model 3113 // lead selection index. 3114 tree.addSelectionPath(newPath); 3115 tree.setLeadSelectionPath(newPath); 3116 } 3117 else if (command.equals("selectPrevious") && hasPrev) 3118 { 3119 newPath = treeState.getPathForRow(prevRow); 3120 tree.setSelectionPath(newPath); 3121 } 3122 else if (command.equals("selectNext") && hasNext) 3123 { 3124 newPath = treeState.getPathForRow(nextRow); 3125 tree.setSelectionPath(newPath); 3126 } 3127 else if (command.equals("selectNextExtendSelection") && hasNext) 3128 { 3129 newPath = treeState.getPathForRow(nextRow); 3130 3131 // If the new path is already selected, the selection shrinks, 3132 // unselecting the previously current path. 3133 if (tree.isPathSelected(newPath)) 3134 tree.getSelectionModel().removeSelectionPath(currentPath); 3135 3136 // This must be called in any case because it updates the model 3137 // lead selection index. 3138 tree.addSelectionPath(newPath); 3139 3140 tree.setLeadSelectionPath(newPath); 3141 } 3142 else if (command.equals("selectNextChangeLead") && hasNext) 3143 { 3144 newPath = treeState.getPathForRow(nextRow); 3145 tree.setSelectionPath(newPath); 3146 tree.setAnchorSelectionPath(newPath); 3147 tree.setLeadSelectionPath(newPath); 3148 } 3149 3150 // Ensure that the lead path is visible after the increment action. 3151 tree.scrollPathToVisible(tree.getLeadSelectionPath()); 3152 } 3153 3154 /** 3155 * Returns true if the action is enabled. 3156 * 3157 * @return true if the action is enabled. 3158 */ 3159 public boolean isEnabled() 3160 { 3161 return (tree != null) && tree.isEnabled(); 3162 } 3163 } 3164 3165 /** 3166 * Forwards all TreeModel events to the TreeState. 3167 */ 3168 public class TreeModelHandler 3169 implements TreeModelListener 3170 { 3171 /** 3172 * Constructor 3173 */ 3174 public TreeModelHandler() 3175 { 3176 // Nothing to do here. 3177 } 3178 3179 /** 3180 * Invoked after a node (or a set of siblings) has changed in some way. The 3181 * node(s) have not changed locations in the tree or altered their children 3182 * arrays, but other attributes have changed and may affect presentation. 3183 * Example: the name of a file has changed, but it is in the same location 3184 * in the file system. To indicate the root has changed, childIndices and 3185 * children will be null. Use e.getPath() to get the parent of the changed 3186 * node(s). e.getChildIndices() returns the index(es) of the changed 3187 * node(s). 3188 * 3189 * @param e is the event that occured 3190 */ 3191 public void treeNodesChanged(TreeModelEvent e) 3192 { 3193 validCachedPreferredSize = false; 3194 treeState.treeNodesChanged(e); 3195 tree.repaint(); 3196 } 3197 3198 /** 3199 * Invoked after nodes have been inserted into the tree. Use e.getPath() to 3200 * get the parent of the new node(s). e.getChildIndices() returns the 3201 * index(es) of the new node(s) in ascending order. 3202 * 3203 * @param e is the event that occured 3204 */ 3205 public void treeNodesInserted(TreeModelEvent e) 3206 { 3207 validCachedPreferredSize = false; 3208 treeState.treeNodesInserted(e); 3209 tree.repaint(); 3210 } 3211 3212 /** 3213 * Invoked after nodes have been removed from the tree. Note that if a 3214 * subtree is removed from the tree, this method may only be invoked once 3215 * for the root of the removed subtree, not once for each individual set of 3216 * siblings removed. Use e.getPath() to get the former parent of the deleted 3217 * node(s). e.getChildIndices() returns, in ascending order, the index(es) 3218 * the node(s) had before being deleted. 3219 * 3220 * @param e is the event that occured 3221 */ 3222 public void treeNodesRemoved(TreeModelEvent e) 3223 { 3224 validCachedPreferredSize = false; 3225 treeState.treeNodesRemoved(e); 3226 tree.repaint(); 3227 } 3228 3229 /** 3230 * Invoked after the tree has drastically changed structure from a given 3231 * node down. If the path returned by e.getPath() is of length one and the 3232 * first element does not identify the current root node the first element 3233 * should become the new root of the tree. Use e.getPath() to get the path 3234 * to the node. e.getChildIndices() returns null. 3235 * 3236 * @param e is the event that occured 3237 */ 3238 public void treeStructureChanged(TreeModelEvent e) 3239 { 3240 if (e.getPath().length == 1 3241 && ! e.getPath()[0].equals(treeModel.getRoot())) 3242 tree.expandPath(new TreePath(treeModel.getRoot())); 3243 validCachedPreferredSize = false; 3244 treeState.treeStructureChanged(e); 3245 tree.repaint(); 3246 } 3247 } // TreeModelHandler 3248 3249 /** 3250 * TreePageAction handles page up and page down events. 3251 */ 3252 public class TreePageAction 3253 extends AbstractAction 3254 { 3255 /** Specifies the direction to adjust the selection by. */ 3256 protected int direction; 3257 3258 /** 3259 * Constructor 3260 * 3261 * @param direction up or down 3262 * @param name is the name of the direction 3263 */ 3264 public TreePageAction(int direction, String name) 3265 { 3266 this.direction = direction; 3267 putValue(Action.NAME, name); 3268 } 3269 3270 /** 3271 * Invoked when an action occurs. 3272 * 3273 * @param e is the event that occured 3274 */ 3275 public void actionPerformed(ActionEvent e) 3276 { 3277 String command = (String) getValue(Action.NAME); 3278 boolean extendSelection = command.equals("scrollUpExtendSelection") 3279 || command.equals("scrollDownExtendSelection"); 3280 boolean changeSelection = command.equals("scrollUpChangeSelection") 3281 || command.equals("scrollDownChangeSelection"); 3282 3283 // Disable change lead, unless we are in discontinuous mode. 3284 if (!extendSelection && !changeSelection 3285 && tree.getSelectionModel().getSelectionMode() != 3286 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) 3287 { 3288 changeSelection = true; 3289 } 3290 3291 int rowCount = getRowCount(tree); 3292 if (rowCount > 0 && treeSelectionModel != null) 3293 { 3294 Dimension maxSize = tree.getSize(); 3295 TreePath lead = tree.getLeadSelectionPath(); 3296 TreePath newPath = null; 3297 Rectangle visible = tree.getVisibleRect(); 3298 if (direction == -1) // The RI handles -1 as up. 3299 { 3300 newPath = getClosestPathForLocation(tree, visible.x, visible.y); 3301 if (newPath.equals(lead)) // Corner case, adjust one page up. 3302 { 3303 visible.y = Math.max(0, visible.y - visible.height); 3304 newPath = getClosestPathForLocation(tree, visible.x, 3305 visible.y); 3306 } 3307 } 3308 else // +1 is down. 3309 { 3310 visible.y = Math.min(maxSize.height, 3311 visible.y + visible.height - 1); 3312 newPath = getClosestPathForLocation(tree, visible.x, visible.y); 3313 if (newPath.equals(lead)) // Corner case, adjust one page down. 3314 { 3315 visible.y = Math.min(maxSize.height, 3316 visible.y + visible.height - 1); 3317 newPath = getClosestPathForLocation(tree, visible.x, 3318 visible.y); 3319 } 3320 } 3321 3322 // Determine new visible rect. 3323 Rectangle newVisible = getPathBounds(tree, newPath); 3324 newVisible.x = visible.x; 3325 newVisible.width = visible.width; 3326 if (direction == -1) 3327 { 3328 newVisible.height = visible.height; 3329 } 3330 else 3331 { 3332 newVisible.y -= visible.height - newVisible.height; 3333 newVisible.height = visible.height; 3334 } 3335 3336 if (extendSelection) 3337 { 3338 // Extend selection. 3339 TreePath anchorPath = tree.getAnchorSelectionPath(); 3340 if (anchorPath == null) 3341 { 3342 tree.setSelectionPath(newPath); 3343 } 3344 else 3345 { 3346 int newIndex = getRowForPath(tree, newPath); 3347 int anchorIndex = getRowForPath(tree, anchorPath); 3348 tree.setSelectionInterval(Math.min(anchorIndex, newIndex), 3349 Math.max(anchorIndex, newIndex)); 3350 tree.setAnchorSelectionPath(anchorPath); 3351 tree.setLeadSelectionPath(newPath); 3352 } 3353 } 3354 else if (changeSelection) 3355 { 3356 tree.setSelectionPath(newPath); 3357 } 3358 else // Change lead. 3359 { 3360 tree.setLeadSelectionPath(newPath); 3361 } 3362 3363 tree.scrollRectToVisible(newVisible); 3364 } 3365 } 3366 3367 /** 3368 * Returns true if the action is enabled. 3369 * 3370 * @return true if the action is enabled. 3371 */ 3372 public boolean isEnabled() 3373 { 3374 return (tree != null) && tree.isEnabled(); 3375 } 3376 } // TreePageAction 3377 3378 /** 3379 * Listens for changes in the selection model and updates the display 3380 * accordingly. 3381 */ 3382 public class TreeSelectionHandler 3383 implements TreeSelectionListener 3384 { 3385 /** 3386 * Constructor 3387 */ 3388 public TreeSelectionHandler() 3389 { 3390 // Nothing to do here. 3391 } 3392 3393 /** 3394 * Messaged when the selection changes in the tree we're displaying for. 3395 * Stops editing, messages super and displays the changed paths. 3396 * 3397 * @param event the event that characterizes the change. 3398 */ 3399 public void valueChanged(TreeSelectionEvent event) 3400 { 3401 completeEditing(); 3402 3403 TreePath op = event.getOldLeadSelectionPath(); 3404 TreePath np = event.getNewLeadSelectionPath(); 3405 3406 // Repaint of the changed lead selection path. 3407 if (op != np) 3408 { 3409 Rectangle o = treeState.getBounds(event.getOldLeadSelectionPath(), 3410 new Rectangle()); 3411 Rectangle n = treeState.getBounds(event.getNewLeadSelectionPath(), 3412 new Rectangle()); 3413 3414 if (o != null) 3415 tree.repaint(o); 3416 if (n != null) 3417 tree.repaint(n); 3418 } 3419 } 3420 } // TreeSelectionHandler 3421 3422 /** 3423 * For the first selected row expandedness will be toggled. 3424 */ 3425 public class TreeToggleAction 3426 extends AbstractAction 3427 { 3428 /** 3429 * Creates a new TreeToggleAction. 3430 * 3431 * @param name is the name of <code>Action</code> field 3432 */ 3433 public TreeToggleAction(String name) 3434 { 3435 putValue(Action.NAME, name); 3436 } 3437 3438 /** 3439 * Invoked when an action occurs. 3440 * 3441 * @param e the event that occured 3442 */ 3443 public void actionPerformed(ActionEvent e) 3444 { 3445 int selected = tree.getLeadSelectionRow(); 3446 if (selected != -1 && isLeaf(selected)) 3447 { 3448 TreePath anchorPath = tree.getAnchorSelectionPath(); 3449 TreePath leadPath = tree.getLeadSelectionPath(); 3450 toggleExpandState(getPathForRow(tree, selected)); 3451 // Need to do this, so that the toggling doesn't mess up the lead 3452 // and anchor. 3453 tree.setLeadSelectionPath(leadPath); 3454 tree.setAnchorSelectionPath(anchorPath); 3455 3456 // Ensure that the lead path is visible after the increment action. 3457 tree.scrollPathToVisible(tree.getLeadSelectionPath()); 3458 } 3459 } 3460 3461 /** 3462 * Returns true if the action is enabled. 3463 * 3464 * @return true if the action is enabled, false otherwise 3465 */ 3466 public boolean isEnabled() 3467 { 3468 return (tree != null) && tree.isEnabled(); 3469 } 3470 } // TreeToggleAction 3471 3472 /** 3473 * TreeTraverseAction is the action used for left/right keys. Will toggle the 3474 * expandedness of a node, as well as potentially incrementing the selection. 3475 */ 3476 public class TreeTraverseAction 3477 extends AbstractAction 3478 { 3479 /** 3480 * Determines direction to traverse, 1 means expand, -1 means collapse. 3481 */ 3482 protected int direction; 3483 3484 /** 3485 * Constructor 3486 * 3487 * @param direction to traverse 3488 * @param name is the name of the direction 3489 */ 3490 public TreeTraverseAction(int direction, String name) 3491 { 3492 this.direction = direction; 3493 putValue(Action.NAME, name); 3494 } 3495 3496 /** 3497 * Invoked when an action occurs. 3498 * 3499 * @param e the event that occured 3500 */ 3501 public void actionPerformed(ActionEvent e) 3502 { 3503 TreePath current = tree.getLeadSelectionPath(); 3504 if (current == null) 3505 return; 3506 3507 String command = (String) getValue(Action.NAME); 3508 if (command.equals("selectParent")) 3509 { 3510 if (current == null) 3511 return; 3512 3513 if (tree.isExpanded(current)) 3514 { 3515 tree.collapsePath(current); 3516 } 3517 else 3518 { 3519 // If the node is not expanded (also, if it is a leaf node), 3520 // we just select the parent. We do not select the root if it 3521 // is not visible. 3522 TreePath parent = current.getParentPath(); 3523 if (parent != null && 3524 ! (parent.getPathCount() == 1 && ! tree.isRootVisible())) 3525 tree.setSelectionPath(parent); 3526 } 3527 } 3528 else if (command.equals("selectChild")) 3529 { 3530 Object node = current.getLastPathComponent(); 3531 int nc = treeModel.getChildCount(node); 3532 if (nc == 0 || treeState.isExpanded(current)) 3533 { 3534 // If the node is leaf or it is already expanded, 3535 // we just select the next row. 3536 int nextRow = tree.getLeadSelectionRow() + 1; 3537 if (nextRow <= tree.getRowCount()) 3538 tree.setSelectionRow(nextRow); 3539 } 3540 else 3541 { 3542 tree.expandPath(current); 3543 } 3544 } 3545 3546 // Ensure that the lead path is visible after the increment action. 3547 tree.scrollPathToVisible(tree.getLeadSelectionPath()); 3548 } 3549 3550 /** 3551 * Returns true if the action is enabled. 3552 * 3553 * @return true if the action is enabled, false otherwise 3554 */ 3555 public boolean isEnabled() 3556 { 3557 return (tree != null) && tree.isEnabled(); 3558 } 3559 } 3560 3561 /** 3562 * Returns true if the LookAndFeel implements the control icons. Package 3563 * private for use in inner classes. 3564 * 3565 * @returns true if there are control icons 3566 */ 3567 boolean hasControlIcons() 3568 { 3569 if (expandedIcon != null || collapsedIcon != null) 3570 return true; 3571 return false; 3572 } 3573 3574 /** 3575 * Returns control icon. It is null if the LookAndFeel does not implements the 3576 * control icons. Package private for use in inner classes. 3577 * 3578 * @return control icon if it exists. 3579 */ 3580 Icon getCurrentControlIcon(TreePath path) 3581 { 3582 if (hasControlIcons()) 3583 { 3584 if (tree.isExpanded(path)) 3585 return expandedIcon; 3586 else 3587 return collapsedIcon; 3588 } 3589 else 3590 { 3591 if (nullIcon == null) 3592 nullIcon = new Icon() 3593 { 3594 public int getIconHeight() 3595 { 3596 return 0; 3597 } 3598 3599 public int getIconWidth() 3600 { 3601 return 0; 3602 } 3603 3604 public void paintIcon(Component c, Graphics g, int x, int y) 3605 { 3606 // No action here. 3607 } 3608 }; 3609 return nullIcon; 3610 } 3611 } 3612 3613 /** 3614 * Returns the parent of the current node 3615 * 3616 * @param root is the root of the tree 3617 * @param node is the current node 3618 * @return is the parent of the current node 3619 */ 3620 Object getParent(Object root, Object node) 3621 { 3622 if (root == null || node == null || root.equals(node)) 3623 return null; 3624 3625 if (node instanceof TreeNode) 3626 return ((TreeNode) node).getParent(); 3627 return findNode(root, node); 3628 } 3629 3630 /** 3631 * Recursively checks the tree for the specified node, starting at the root. 3632 * 3633 * @param root is starting node to start searching at. 3634 * @param node is the node to search for 3635 * @return the parent node of node 3636 */ 3637 private Object findNode(Object root, Object node) 3638 { 3639 if (! treeModel.isLeaf(root) && ! root.equals(node)) 3640 { 3641 int size = treeModel.getChildCount(root); 3642 for (int j = 0; j < size; j++) 3643 { 3644 Object child = treeModel.getChild(root, j); 3645 if (node.equals(child)) 3646 return root; 3647 3648 Object n = findNode(child, node); 3649 if (n != null) 3650 return n; 3651 } 3652 } 3653 return null; 3654 } 3655 3656 /** 3657 * Selects the specified path in the tree depending on modes. Package private 3658 * for use in inner classes. 3659 * 3660 * @param tree is the tree we are selecting the path in 3661 * @param path is the path we are selecting 3662 */ 3663 void selectPath(JTree tree, TreePath path) 3664 { 3665 if (path != null) 3666 { 3667 tree.setSelectionPath(path); 3668 tree.setLeadSelectionPath(path); 3669 tree.makeVisible(path); 3670 tree.scrollPathToVisible(path); 3671 } 3672 } 3673 3674 /** 3675 * Returns the path from node to the root. Package private for use in inner 3676 * classes. 3677 * 3678 * @param node the node to get the path to 3679 * @param depth the depth of the tree to return a path for 3680 * @return an array of tree nodes that represent the path to node. 3681 */ 3682 Object[] getPathToRoot(Object node, int depth) 3683 { 3684 if (node == null) 3685 { 3686 if (depth == 0) 3687 return null; 3688 3689 return new Object[depth]; 3690 } 3691 3692 Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node), 3693 depth + 1); 3694 path[path.length - depth - 1] = node; 3695 return path; 3696 } 3697 3698 /** 3699 * Draws a vertical line using the given graphic context 3700 * 3701 * @param g is the graphic context 3702 * @param c is the component the new line will belong to 3703 * @param x is the horizonal position 3704 * @param top specifies the top of the line 3705 * @param bottom specifies the bottom of the line 3706 */ 3707 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, 3708 int bottom) 3709 { 3710 // FIXME: Check if drawing a dashed line or not. 3711 g.setColor(getHashColor()); 3712 g.drawLine(x, top, x, bottom); 3713 } 3714 3715 /** 3716 * Draws a horizontal line using the given graphic context 3717 * 3718 * @param g is the graphic context 3719 * @param c is the component the new line will belong to 3720 * @param y is the vertical position 3721 * @param left specifies the left point of the line 3722 * @param right specifies the right point of the line 3723 */ 3724 protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, 3725 int right) 3726 { 3727 // FIXME: Check if drawing a dashed line or not. 3728 g.setColor(getHashColor()); 3729 g.drawLine(left, y, right, y); 3730 } 3731 3732 /** 3733 * Draws an icon at around a specific position 3734 * 3735 * @param c is the component the new line will belong to 3736 * @param g is the graphic context 3737 * @param icon is the icon which will be drawn 3738 * @param x is the center position in x-direction 3739 * @param y is the center position in y-direction 3740 */ 3741 protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y) 3742 { 3743 x -= icon.getIconWidth() / 2; 3744 y -= icon.getIconHeight() / 2; 3745 3746 if (x < 0) 3747 x = 0; 3748 if (y < 0) 3749 y = 0; 3750 3751 icon.paintIcon(c, g, x, y); 3752 } 3753 3754 /** 3755 * Draws a dashed horizontal line. 3756 * 3757 * @param g - the graphics configuration. 3758 * @param y - the y location to start drawing at 3759 * @param x1 - the x location to start drawing at 3760 * @param x2 - the x location to finish drawing at 3761 */ 3762 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2) 3763 { 3764 g.setColor(getHashColor()); 3765 for (int i = x1; i < x2; i += 2) 3766 g.drawLine(i, y, i + 1, y); 3767 } 3768 3769 /** 3770 * Draws a dashed vertical line. 3771 * 3772 * @param g - the graphics configuration. 3773 * @param x - the x location to start drawing at 3774 * @param y1 - the y location to start drawing at 3775 * @param y2 - the y location to finish drawing at 3776 */ 3777 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) 3778 { 3779 g.setColor(getHashColor()); 3780 for (int i = y1; i < y2; i += 2) 3781 g.drawLine(x, i, x, i + 1); 3782 } 3783 3784 /** 3785 * Paints the expand (toggle) part of a row. The receiver should NOT modify 3786 * clipBounds, or insets. 3787 * 3788 * @param g - the graphics configuration 3789 * @param clipBounds - 3790 * @param insets - 3791 * @param bounds - bounds of expand control 3792 * @param path - path to draw control for 3793 * @param row - row to draw control for 3794 * @param isExpanded - is the row expanded 3795 * @param hasBeenExpanded - has the row already been expanded 3796 * @param isLeaf - is the path a leaf 3797 */ 3798 protected void paintExpandControl(Graphics g, Rectangle clipBounds, 3799 Insets insets, Rectangle bounds, 3800 TreePath path, int row, boolean isExpanded, 3801 boolean hasBeenExpanded, boolean isLeaf) 3802 { 3803 if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) 3804 { 3805 Icon icon = getCurrentControlIcon(path); 3806 int iconW = icon.getIconWidth(); 3807 int x = bounds.x - iconW - gap; 3808 icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2 3809 - icon.getIconHeight() / 2); 3810 } 3811 } 3812 3813 /** 3814 * Paints the horizontal part of the leg. The receiver should NOT modify 3815 * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not 3816 * visible. 3817 * 3818 * @param g - the graphics configuration 3819 * @param clipBounds - 3820 * @param insets - 3821 * @param bounds - bounds of the cell 3822 * @param path - path to draw leg for 3823 * @param row - row to start drawing at 3824 * @param isExpanded - is the row expanded 3825 * @param hasBeenExpanded - has the row already been expanded 3826 * @param isLeaf - is the path a leaf 3827 */ 3828 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, 3829 Insets insets, Rectangle bounds, 3830 TreePath path, int row, 3831 boolean isExpanded, 3832 boolean hasBeenExpanded, 3833 boolean isLeaf) 3834 { 3835 if (row != 0) 3836 { 3837 paintHorizontalLine(g, tree, bounds.y + bounds.height / 2, 3838 bounds.x - leftChildIndent - gap, bounds.x - gap); 3839 } 3840 } 3841 3842 /** 3843 * Paints the vertical part of the leg. The receiver should NOT modify 3844 * clipBounds, insets. 3845 * 3846 * @param g - the graphics configuration. 3847 * @param clipBounds - 3848 * @param insets - 3849 * @param path - the path to draw the vertical part for. 3850 */ 3851 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, 3852 Insets insets, TreePath path) 3853 { 3854 Rectangle bounds = getPathBounds(tree, path); 3855 TreePath parent = path.getParentPath(); 3856 3857 boolean paintLine; 3858 if (isRootVisible()) 3859 paintLine = parent != null; 3860 else 3861 paintLine = parent != null && parent.getPathCount() > 1; 3862 if (paintLine) 3863 { 3864 Rectangle parentBounds = getPathBounds(tree, parent); 3865 paintVerticalLine(g, tree, parentBounds.x + 2 * gap, 3866 parentBounds.y + parentBounds.height / 2, 3867 bounds.y + bounds.height / 2); 3868 } 3869 } 3870 3871 /** 3872 * Paints the renderer part of a row. The receiver should NOT modify 3873 * clipBounds, or insets. 3874 * 3875 * @param g - the graphics configuration 3876 * @param clipBounds - 3877 * @param insets - 3878 * @param bounds - bounds of expand control 3879 * @param path - path to draw control for 3880 * @param row - row to draw control for 3881 * @param isExpanded - is the row expanded 3882 * @param hasBeenExpanded - has the row already been expanded 3883 * @param isLeaf - is the path a leaf 3884 */ 3885 protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets, 3886 Rectangle bounds, TreePath path, int row, 3887 boolean isExpanded, boolean hasBeenExpanded, 3888 boolean isLeaf) 3889 { 3890 boolean selected = tree.isPathSelected(path); 3891 boolean hasIcons = false; 3892 Object node = path.getLastPathComponent(); 3893 3894 paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded, 3895 hasBeenExpanded, isLeaf); 3896 3897 TreeCellRenderer dtcr = currentCellRenderer; 3898 3899 boolean focused = false; 3900 if (treeSelectionModel != null) 3901 focused = treeSelectionModel.getLeadSelectionRow() == row 3902 && tree.isFocusOwner(); 3903 3904 Component c = dtcr.getTreeCellRendererComponent(tree, node, selected, 3905 isExpanded, isLeaf, row, 3906 focused); 3907 3908 rendererPane.paintComponent(g, c, c.getParent(), bounds); 3909 } 3910 3911 /** 3912 * Prepares for the UI to uninstall. 3913 */ 3914 protected void prepareForUIUninstall() 3915 { 3916 // Nothing to do here yet. 3917 } 3918 3919 /** 3920 * Returns true if the expand (toggle) control should be drawn for the 3921 * specified row. 3922 * 3923 * @param path - current path to check for. 3924 * @param row - current row to check for. 3925 * @param isExpanded - true if the path is expanded 3926 * @param hasBeenExpanded - true if the path has been expanded already 3927 * @param isLeaf - true if the row is a lead 3928 */ 3929 protected boolean shouldPaintExpandControl(TreePath path, int row, 3930 boolean isExpanded, 3931 boolean hasBeenExpanded, 3932 boolean isLeaf) 3933 { 3934 Object node = path.getLastPathComponent(); 3935 return ! isLeaf && hasControlIcons(); 3936 } 3937 3938 /** 3939 * Returns the amount to indent the given row 3940 * 3941 * @return amount to indent the given row. 3942 */ 3943 protected int getRowX(int row, int depth) 3944 { 3945 return depth * totalChildIndent; 3946 } 3947 } // BasicTreeUI