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