Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]

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

Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]