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