Use Tree Navigation
public class

BasicTreeUI.TreeModelHandler

extends Object
implements TreeModelListener
/*
 * Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package javax.swing.plaf.basic;

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import java.beans.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.TreeUI;
import javax.swing.tree.*;
import javax.swing.text.Position;
import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
import sun.swing.SwingUtilities2;

import sun.swing.DefaultLookup;
import sun.swing.UIAction;

/**
 * The basic L&F for a hierarchical data structure.
 * <p>
 *
 * @author Scott Violet
 * @author Shannon Hickey (drag and drop)
 */


public class BasicTreeUI extends TreeUI
{
   
private static final Object BASELINE_COMPONENT_KEY = new Object(); // Tree.baselineComponent

   
// Old actions forward to an instance of this.
   
static private final Actions SHARED_ACTION = new Actions();

   
transient protected Icon        collapsedIcon;
   
transient protected Icon        expandedIcon;

   
/**
      * Color used to draw hash marks.  If <code>null</code> no hash marks
      * will be drawn.
      */

   
private Color hashColor;

   
/** Distance between left margin and where vertical dashes will be
      * drawn. */

   
protected int               leftChildIndent;
   
/** Distance to add to leftChildIndent to determine where cell
      * contents will be drawn. */

   
protected int               rightChildIndent;
   
/** Total distance that will be indented.  The sum of leftChildIndent
      * and rightChildIndent. */

   
protected int               totalChildIndent;

   
/** Minimum preferred size. */
   
protected Dimension         preferredMinSize;

   
/** Index of the row that was last selected. */
   
protected int               lastSelectedRow;

   
/** Component that we're going to be drawing into. */
   
protected JTree             tree;

   
/** Renderer that is being used to do the actual cell drawing. */
   
transient protected TreeCellRenderer   currentCellRenderer;

   
/** Set to true if the renderer that is currently in the tree was
     * created by this instance. */

   
protected boolean           createdRenderer;

   
/** Editor for the tree. */
   
transient protected TreeCellEditor     cellEditor;

   
/** Set to true if editor that is currently in the tree was
     * created by this instance. */

   
protected boolean           createdCellEditor;

   
/** Set to false when editing and shouldSelectCell() returns true meaning
      * the node should be selected before editing, used in completeEditing. */

   
protected boolean           stopEditingInCompleteEditing;

   
/** Used to paint the TreeCellRenderer. */
   
protected CellRendererPane  rendererPane;

   
/** Size needed to completely display all the nodes. */
   
protected Dimension         preferredSize;

   
/** Is the preferredSize valid? */
   
protected boolean           validCachedPreferredSize;

   
/** Object responsible for handling sizing and expanded issues. */
   
// WARNING: Be careful with the bounds held by treeState. They are
   
// always in terms of left-to-right. They get mapped to right-to-left
   
// by the various methods of this class.
   
protected AbstractLayoutCache  treeState;


   
/** Used for minimizing the drawing of vertical lines. */
   
protected Hashtable<TreePath,Boolean> drawingCache;

   
/** True if doing optimizations for a largeModel. Subclasses that
     * don't support this may wish to override createLayoutCache to not
     * return a FixedHeightLayoutCache instance. */

   
protected boolean           largeModel;

   
/** Reponsible for telling the TreeState the size needed for a node. */
   
protected AbstractLayoutCache.NodeDimensions     nodeDimensions;

   
/** Used to determine what to display. */
   
protected TreeModel         treeModel;

   
/** Model maintaing the selection. */
   
protected TreeSelectionModel treeSelectionModel;

   
/** How much the depth should be offset to properly calculate
     * x locations. This is based on whether or not the root is visible,
     * and if the root handles are visible. */

   
protected int               depthOffset;

   
// Following 4 ivars are only valid when editing.

   
/** When editing, this will be the Component that is doing the actual
      * editing. */

   
protected Component         editingComponent;

   
/** Path that is being edited. */
   
protected TreePath          editingPath;

   
/** Row that is being edited. Should only be referenced if
     * editingComponent is not null. */

   
protected int               editingRow;

   
/** Set to true if the editor has a different size than the renderer. */
   
protected boolean           editorHasDifferentSize;

   
/** Row correspondin to lead path. */
   
private int                 leadRow;
   
/** If true, the property change event for LEAD_SELECTION_PATH_PROPERTY,
     * or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint. */

   
private boolean             ignoreLAChange;

   
/** Indicates the orientation. */
   
private boolean             leftToRight;

   
// Cached listeners
   
private PropertyChangeListener propertyChangeListener;
   
private PropertyChangeListener selectionModelPropertyChangeListener;
   
private MouseListener mouseListener;
   
private FocusListener focusListener;
   
private KeyListener keyListener;
   
/** Used for large models, listens for moved/resized events and
     * updates the validCachedPreferredSize bit accordingly. */

   
private ComponentListener   componentListener;
   
/** Listens for CellEditor events. */
   
private CellEditorListener  cellEditorListener;
   
/** Updates the display when the selection changes. */
   
private TreeSelectionListener treeSelectionListener;
   
/** Is responsible for updating the display based on model events. */
   
private TreeModelListener treeModelListener;
   
/** Updates the treestate as the nodes expand. */
   
private TreeExpansionListener treeExpansionListener;

   
/** UI property indicating whether to paint lines */
   
private boolean paintLines = true;

   
/** UI property for painting dashed lines */
   
private boolean lineTypeDashed;

   
/**
     * The time factor to treate the series of typed alphanumeric key
     * as prefix for first letter navigation.
     */

   
private long timeFactor = 1000L;

   
private Handler handler;

   
/**
     * A temporary variable for communication between startEditingOnRelease
     * and startEditing.
     */

   
private MouseEvent releaseEvent;

   
public static ComponentUI createUI(JComponent x) {
       
return new BasicTreeUI();
   
}


   
static void loadActionMap(LazyActionMap map) {
        map
.put(new Actions(Actions.SELECT_PREVIOUS));
        map
.put(new Actions(Actions.SELECT_PREVIOUS_CHANGE_LEAD));
        map
.put(new Actions(Actions.SELECT_PREVIOUS_EXTEND_SELECTION));

        map
.put(new Actions(Actions.SELECT_NEXT));
        map
.put(new Actions(Actions.SELECT_NEXT_CHANGE_LEAD));
        map
.put(new Actions(Actions.SELECT_NEXT_EXTEND_SELECTION));

        map
.put(new Actions(Actions.SELECT_CHILD));
        map
.put(new Actions(Actions.SELECT_CHILD_CHANGE_LEAD));

        map
.put(new Actions(Actions.SELECT_PARENT));
        map
.put(new Actions(Actions.SELECT_PARENT_CHANGE_LEAD));

        map
.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION));
        map
.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
        map
.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION));

        map
.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION));
        map
.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION));
        map
.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));

        map
.put(new Actions(Actions.SELECT_FIRST));
        map
.put(new Actions(Actions.SELECT_FIRST_CHANGE_LEAD));
        map
.put(new Actions(Actions.SELECT_FIRST_EXTEND_SELECTION));

        map
.put(new Actions(Actions.SELECT_LAST));
        map
.put(new Actions(Actions.SELECT_LAST_CHANGE_LEAD));
        map
.put(new Actions(Actions.SELECT_LAST_EXTEND_SELECTION));

        map
.put(new Actions(Actions.TOGGLE));

        map
.put(new Actions(Actions.CANCEL_EDITING));

        map
.put(new Actions(Actions.START_EDITING));

        map
.put(new Actions(Actions.SELECT_ALL));

        map
.put(new Actions(Actions.CLEAR_SELECTION));

        map
.put(new Actions(Actions.SCROLL_LEFT));
        map
.put(new Actions(Actions.SCROLL_RIGHT));

        map
.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION));
        map
.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION));

        map
.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_LEAD));
        map
.put(new Actions(Actions.SCROLL_LEFT_CHANGE_LEAD));

        map
.put(new Actions(Actions.EXPAND));
        map
.put(new Actions(Actions.COLLAPSE));
        map
.put(new Actions(Actions.MOVE_SELECTION_TO_PARENT));

        map
.put(new Actions(Actions.ADD_TO_SELECTION));
        map
.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
        map
.put(new Actions(Actions.EXTEND_TO));
        map
.put(new Actions(Actions.MOVE_SELECTION_TO));

        map
.put(TransferHandler.getCutAction());
        map
.put(TransferHandler.getCopyAction());
        map
.put(TransferHandler.getPasteAction());
   
}


   
public BasicTreeUI() {
       
super();
   
}

   
protected Color getHashColor() {
       
return hashColor;
   
}

   
protected void setHashColor(Color color) {
        hashColor
= color;
   
}

   
public void setLeftChildIndent(int newAmount) {
        leftChildIndent
= newAmount;
        totalChildIndent
= leftChildIndent + rightChildIndent;
       
if(treeState != null)
            treeState
.invalidateSizes();
        updateSize
();
   
}

   
public int getLeftChildIndent() {
       
return leftChildIndent;
   
}

   
public void setRightChildIndent(int newAmount) {
        rightChildIndent
= newAmount;
        totalChildIndent
= leftChildIndent + rightChildIndent;
       
if(treeState != null)
            treeState
.invalidateSizes();
        updateSize
();
   
}

   
public int getRightChildIndent() {
       
return rightChildIndent;
   
}

   
public void setExpandedIcon(Icon newG) {
        expandedIcon
= newG;
   
}

   
public Icon getExpandedIcon() {
       
return expandedIcon;
   
}

   
public void setCollapsedIcon(Icon newG) {
        collapsedIcon
= newG;
   
}

   
public Icon getCollapsedIcon() {
       
return collapsedIcon;
   
}

   
//
   
// Methods for configuring the behavior of the tree. None of them
   
// push the value to the JTree instance. You should really only
   
// call these methods on the JTree.
   
//

   
/**
     * Updates the componentListener, if necessary.
     */

   
protected void setLargeModel(boolean largeModel) {
       
if(getRowHeight() < 1)
            largeModel
= false;
       
if(this.largeModel != largeModel) {
            completeEditing
();
           
this.largeModel = largeModel;
            treeState
= createLayoutCache();
            configureLayoutCache
();
            updateLayoutCacheExpandedNodesIfNecessary
();
            updateSize
();
       
}
   
}

   
protected boolean isLargeModel() {
       
return largeModel;
   
}

   
/**
     * Sets the row height, this is forwarded to the treeState.
     */

   
protected void setRowHeight(int rowHeight) {
        completeEditing
();
       
if(treeState != null) {
            setLargeModel
(tree.isLargeModel());
            treeState
.setRowHeight(rowHeight);
            updateSize
();
       
}
   
}

   
protected int getRowHeight() {
       
return (tree == null) ? -1 : tree.getRowHeight();
   
}

   
/**
     * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
     * <code>updateRenderer</code>.
     */

   
protected void setCellRenderer(TreeCellRenderer tcr) {
        completeEditing
();
        updateRenderer
();
       
if(treeState != null) {
            treeState
.invalidateSizes();
            updateSize
();
       
}
   
}

   
/**
     * Return currentCellRenderer, which will either be the trees
     * renderer, or defaultCellRenderer, which ever wasn't null.
     */

   
protected TreeCellRenderer getCellRenderer() {
       
return currentCellRenderer;
   
}

   
/**
     * Sets the TreeModel.
     */

   
protected void setModel(TreeModel model) {
        completeEditing
();
       
if(treeModel != null && treeModelListener != null)
            treeModel
.removeTreeModelListener(treeModelListener);
        treeModel
= model;
       
if(treeModel != null) {
           
if(treeModelListener != null)
                treeModel
.addTreeModelListener(treeModelListener);
       
}
       
if(treeState != null) {
            treeState
.setModel(model);
            updateLayoutCacheExpandedNodesIfNecessary
();
            updateSize
();
       
}
   
}

   
protected TreeModel getModel() {
       
return treeModel;
   
}

   
/**
     * Sets the root to being visible.
     */

   
protected void setRootVisible(boolean newValue) {
        completeEditing
();
        updateDepthOffset
();
       
if(treeState != null) {
            treeState
.setRootVisible(newValue);
            treeState
.invalidateSizes();
            updateSize
();
       
}
   
}

   
protected boolean isRootVisible() {
       
return (tree != null) ? tree.isRootVisible() : false;
   
}

   
/**
     * Determines whether the node handles are to be displayed.
     */

   
protected void setShowsRootHandles(boolean newValue) {
        completeEditing
();
        updateDepthOffset
();
       
if(treeState != null) {
            treeState
.invalidateSizes();
            updateSize
();
       
}
   
}

   
protected boolean getShowsRootHandles() {
       
return (tree != null) ? tree.getShowsRootHandles() : false;
   
}

   
/**
     * Sets the cell editor.
     */

   
protected void setCellEditor(TreeCellEditor editor) {
        updateCellEditor
();
   
}

   
protected TreeCellEditor getCellEditor() {
       
return (tree != null) ? tree.getCellEditor() : null;
   
}

   
/**
     * Configures the receiver to allow, or not allow, editing.
     */

   
protected void setEditable(boolean newValue) {
        updateCellEditor
();
   
}

   
protected boolean isEditable() {
       
return (tree != null) ? tree.isEditable() : false;
   
}

   
/**
     * Resets the selection model. The appropriate listener are installed
     * on the model.
     */

   
protected void setSelectionModel(TreeSelectionModel newLSM) {
        completeEditing
();
       
if(selectionModelPropertyChangeListener != null &&
           treeSelectionModel
!= null)
            treeSelectionModel
.removePropertyChangeListener
                             
(selectionModelPropertyChangeListener);
       
if(treeSelectionListener != null && treeSelectionModel != null)
            treeSelectionModel
.removeTreeSelectionListener
                               
(treeSelectionListener);
        treeSelectionModel
= newLSM;
       
if(treeSelectionModel != null) {
           
if(selectionModelPropertyChangeListener != null)
                treeSelectionModel
.addPropertyChangeListener
                             
(selectionModelPropertyChangeListener);
           
if(treeSelectionListener != null)
                treeSelectionModel
.addTreeSelectionListener
                                   
(treeSelectionListener);
           
if(treeState != null)
                treeState
.setSelectionModel(treeSelectionModel);
       
}
       
else if(treeState != null)
            treeState
.setSelectionModel(null);
       
if(tree != null)
            tree
.repaint();
   
}

   
protected TreeSelectionModel getSelectionModel() {
       
return treeSelectionModel;
   
}

   
//
   
// TreeUI methods
   
//

   
/**
      * Returns the Rectangle enclosing the label portion that the
      * last item in path will be drawn into.  Will return null if
      * any component in path is currently valid.
      */

   
public Rectangle getPathBounds(JTree tree, TreePath path) {
       
if(tree != null && treeState != null) {
           
return getPathBounds(path, tree.getInsets(), new Rectangle());
       
}
       
return null;
   
}

   
private Rectangle getPathBounds(TreePath path, Insets insets,
                                   
Rectangle bounds) {
        bounds
= treeState.getBounds(path, bounds);
       
if (bounds != null) {
           
if (leftToRight) {
                bounds
.x += insets.left;
           
} else {
                bounds
.x = tree.getWidth() - (bounds.x + bounds.width) -
                        insets
.right;
           
}
            bounds
.y += insets.top;
       
}
       
return bounds;
   
}

   
/**
      * Returns the path for passed in row.  If row is not visible
      * null is returned.
      */

   
public TreePath getPathForRow(JTree tree, int row) {
       
return (treeState != null) ? treeState.getPathForRow(row) : null;
   
}

   
/**
      * Returns the row that the last item identified in path is visible
      * at.  Will return -1 if any of the elements in path are not
      * currently visible.
      */

   
public int getRowForPath(JTree tree, TreePath path) {
       
return (treeState != null) ? treeState.getRowForPath(path) : -1;
   
}

   
/**
      * Returns the number of rows that are being displayed.
      */

   
public int getRowCount(JTree tree) {
       
return (treeState != null) ? treeState.getRowCount() : 0;
   
}

   
/**
      * Returns the path to the node that is closest to x,y.  If
      * there is nothing currently visible this will return null, otherwise
      * it'll always return a valid path.  If you need to test if the
      * returned object is exactly at x, y you should get the bounds for
      * the returned path and test x, y against that.
      */

   
public TreePath getClosestPathForLocation(JTree tree, int x, int y) {
       
if(tree != null && treeState != null) {
           
// TreeState doesn't care about the x location, hence it isn't
           
// adjusted
            y
-= tree.getInsets().top;
           
return treeState.getPathClosestTo(x, y);
       
}
       
return null;
   
}

   
/**
      * Returns true if the tree is being edited.  The item that is being
      * edited can be returned by getEditingPath().
      */

   
public boolean isEditing(JTree tree) {
       
return (editingComponent != null);
   
}

   
/**
      * Stops the current editing session.  This has no effect if the
      * tree isn't being edited.  Returns true if the editor allows the
      * editing session to stop.
      */

   
public boolean stopEditing(JTree tree) {
       
if(editingComponent != null && cellEditor.stopCellEditing()) {
            completeEditing
(false, false, true);
           
return true;
       
}
       
return false;
   
}

   
/**
      * Cancels the current editing session.
      */

   
public void cancelEditing(JTree tree) {
       
if(editingComponent != null) {
            completeEditing
(false, true, false);
       
}
   
}

   
/**
      * Selects the last item in path and tries to edit it.  Editing will
      * fail if the CellEditor won't allow it for the selected item.
      */

   
public void startEditingAtPath(JTree tree, TreePath path) {
        tree
.scrollPathToVisible(path);
       
if(path != null && tree.isVisible(path))
            startEditing
(path, null);
   
}

   
/**
     * Returns the path to the element that is being edited.
     */

   
public TreePath getEditingPath(JTree tree) {
       
return editingPath;
   
}

   
//
   
// Install methods
   
//

   
public void installUI(JComponent c) {
       
if ( c == null ) {
           
throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" );
       
}

        tree
= (JTree)c;

        prepareForUIInstall
();

       
// Boilerplate install block
        installDefaults
();
        installKeyboardActions
();
        installComponents
();
        installListeners
();

        completeUIInstall
();
   
}

   
/**
     * Invoked after the <code>tree</code> instance variable has been
     * set, but before any defaults/listeners have been installed.
     */

   
protected void prepareForUIInstall() {
        drawingCache
= new Hashtable<TreePath,Boolean>(7);

       
// Data member initializations
        leftToRight
= BasicGraphicsUtils.isLeftToRight(tree);
        stopEditingInCompleteEditing
= true;
        lastSelectedRow
= -1;
        leadRow
= -1;
        preferredSize
= new Dimension();

        largeModel
= tree.isLargeModel();
       
if(getRowHeight() <= 0)
            largeModel
= false;
        setModel
(tree.getModel());
   
}

   
/**
     * Invoked from installUI after all the defaults/listeners have been
     * installed.
     */

   
protected void completeUIInstall() {
       
// Custom install code

       
this.setShowsRootHandles(tree.getShowsRootHandles());

        updateRenderer
();

        updateDepthOffset
();

        setSelectionModel
(tree.getSelectionModel());

       
// Create, if necessary, the TreeState instance.
        treeState
= createLayoutCache();
        configureLayoutCache
();

        updateSize
();
   
}

   
protected void installDefaults() {
       
if(tree.getBackground() == null ||
           tree
.getBackground() instanceof UIResource) {
            tree
.setBackground(UIManager.getColor("Tree.background"));
       
}
       
if(getHashColor() == null || getHashColor() instanceof UIResource) {
            setHashColor
(UIManager.getColor("Tree.hash"));
       
}
       
if (tree.getFont() == null || tree.getFont() instanceof UIResource)
            tree
.setFont( UIManager.getFont("Tree.font") );
       
// JTree's original row height is 16.  To correctly display the
       
// contents on Linux we should have set it to 18, Windows 19 and
       
// Solaris 20.  As these values vary so much it's too hard to
       
// be backward compatable and try to update the row height, we're
       
// therefor NOT going to adjust the row height based on font.  If the
       
// developer changes the font, it's there responsibility to update
       
// the row height.

        setExpandedIcon
( (Icon)UIManager.get( "Tree.expandedIcon" ) );
        setCollapsedIcon
( (Icon)UIManager.get( "Tree.collapsedIcon" ) );

        setLeftChildIndent
(((Integer)UIManager.get("Tree.leftChildIndent")).
                           intValue
());
        setRightChildIndent
(((Integer)UIManager.get("Tree.rightChildIndent")).
                           intValue
());

       
LookAndFeel.installProperty(tree, "rowHeight",
                                   
UIManager.get("Tree.rowHeight"));

        largeModel
= (tree.isLargeModel() && tree.getRowHeight() > 0);

       
Object scrollsOnExpand = UIManager.get("Tree.scrollsOnExpand");
       
if (scrollsOnExpand != null) {
           
LookAndFeel.installProperty(tree, "scrollsOnExpand", scrollsOnExpand);
       
}

        paintLines
= UIManager.getBoolean("Tree.paintLines");
        lineTypeDashed
= UIManager.getBoolean("Tree.lineTypeDashed");

       
Long l = (Long)UIManager.get("Tree.timeFactor");
        timeFactor
= (l!=null) ? l.longValue() : 1000L;

       
Object showsRootHandles = UIManager.get("Tree.showsRootHandles");
       
if (showsRootHandles != null) {
           
LookAndFeel.installProperty(tree,
                   
JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles);
       
}
   
}

   
protected void installListeners() {
       
if ( (propertyChangeListener = createPropertyChangeListener())
             
!= null ) {
            tree
.addPropertyChangeListener(propertyChangeListener);
       
}
       
if ( (mouseListener = createMouseListener()) != null ) {
            tree
.addMouseListener(mouseListener);
           
if (mouseListener instanceof MouseMotionListener) {
                tree
.addMouseMotionListener((MouseMotionListener)mouseListener);
           
}
       
}
       
if ((focusListener = createFocusListener()) != null ) {
            tree
.addFocusListener(focusListener);
       
}
       
if ((keyListener = createKeyListener()) != null) {
            tree
.addKeyListener(keyListener);
       
}
       
if((treeExpansionListener = createTreeExpansionListener()) != null) {
            tree
.addTreeExpansionListener(treeExpansionListener);
       
}
       
if((treeModelListener = createTreeModelListener()) != null &&
           treeModel
!= null) {
            treeModel
.addTreeModelListener(treeModelListener);
       
}
       
if((selectionModelPropertyChangeListener =
            createSelectionModelPropertyChangeListener
()) != null &&
           treeSelectionModel
!= null) {
            treeSelectionModel
.addPropertyChangeListener
               
(selectionModelPropertyChangeListener);
       
}
       
if((treeSelectionListener = createTreeSelectionListener()) != null &&
           treeSelectionModel
!= null) {
            treeSelectionModel
.addTreeSelectionListener(treeSelectionListener);
       
}

       
TransferHandler th = tree.getTransferHandler();
       
if (th == null || th instanceof UIResource) {
            tree
.setTransferHandler(defaultTransferHandler);
           
// default TransferHandler doesn't support drop
           
// so we don't want drop handling
           
if (tree.getDropTarget() instanceof UIResource) {
                tree
.setDropTarget(null);
           
}
       
}

       
LookAndFeel.installProperty(tree, "opaque", Boolean.TRUE);
   
}

   
protected void installKeyboardActions() {
       
InputMap km = getInputMap(JComponent.
                                  WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
);

       
SwingUtilities.replaceUIInputMap(tree, JComponent.
                                         WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
,
                                         km
);
        km
= getInputMap(JComponent.WHEN_FOCUSED);
       
SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, km);

       
LazyActionMap.installLazyActionMap(tree, BasicTreeUI.class,
                                           
"Tree.actionMap");
   
}

   
InputMap getInputMap(int condition) {
       
if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
           
return (InputMap)DefaultLookup.get(tree, this,
                                               
"Tree.ancestorInputMap");
       
}
       
else if (condition == JComponent.WHEN_FOCUSED) {
           
InputMap keyMap = (InputMap)DefaultLookup.get(tree, this,
                                                     
"Tree.focusInputMap");
           
InputMap rtlKeyMap;

           
if (tree.getComponentOrientation().isLeftToRight() ||
                 
((rtlKeyMap = (InputMap)DefaultLookup.get(tree, this,
                 
"Tree.focusInputMap.RightToLeft")) == null)) {
               
return keyMap;
           
} else {
                rtlKeyMap
.setParent(keyMap);
               
return rtlKeyMap;
           
}
       
}
       
return null;
   
}

   
/**
     * Intalls the subcomponents of the tree, which is the renderer pane.
     */

   
protected void installComponents() {
       
if ((rendererPane = createCellRendererPane()) != null) {
            tree
.add( rendererPane );
       
}
   
}

   
//
   
// Create methods.
   
//

   
/**
     * Creates an instance of NodeDimensions that is able to determine
     * the size of a given node in the tree.
     */

   
protected AbstractLayoutCache.NodeDimensions createNodeDimensions() {
       
return new NodeDimensionsHandler();
   
}

   
/**
     * Creates a listener that is responsible that updates the UI based on
     * how the tree changes.
     */

   
protected PropertyChangeListener createPropertyChangeListener() {
       
return getHandler();
   
}

   
private Handler getHandler() {
       
if (handler == null) {
            handler
= new Handler();
       
}
       
return handler;
   
}

   
/**
     * Creates the listener responsible for updating the selection based on
     * mouse events.
     */

   
protected MouseListener createMouseListener() {
       
return getHandler();
   
}

   
/**
     * Creates a listener that is responsible for updating the display
     * when focus is lost/gained.
     */

   
protected FocusListener createFocusListener() {
       
return getHandler();
   
}

   
/**
     * Creates the listener reponsible for getting key events from
     * the tree.
     */

   
protected KeyListener createKeyListener() {
       
return getHandler();
   
}

   
/**
     * Creates the listener responsible for getting property change
     * events from the selection model.
     */

   
protected PropertyChangeListener createSelectionModelPropertyChangeListener() {
       
return getHandler();
   
}

   
/**
     * Creates the listener that updates the display based on selection change
     * methods.
     */

   
protected TreeSelectionListener createTreeSelectionListener() {
       
return getHandler();
   
}

   
/**
     * Creates a listener to handle events from the current editor.
     */

   
protected CellEditorListener createCellEditorListener() {
       
return getHandler();
   
}

   
/**
     * Creates and returns a new ComponentHandler. This is used for
     * the large model to mark the validCachedPreferredSize as invalid
     * when the component moves.
     */

   
protected ComponentListener createComponentListener() {
       
return new ComponentHandler();
   
}

   
/**
     * Creates and returns the object responsible for updating the treestate
     * when nodes expanded state changes.
     */

   
protected TreeExpansionListener createTreeExpansionListener() {
       
return getHandler();
   
}

   
/**
     * Creates the object responsible for managing what is expanded, as
     * well as the size of nodes.
     */

   
protected AbstractLayoutCache createLayoutCache() {
       
if(isLargeModel() && getRowHeight() > 0) {
           
return new FixedHeightLayoutCache();
       
}
       
return new VariableHeightLayoutCache();
   
}

   
/**
     * Returns the renderer pane that renderer components are placed in.
     */

   
protected CellRendererPane createCellRendererPane() {
       
return new CellRendererPane();
   
}

   
/**
      * Creates a default cell editor.
      */

   
protected TreeCellEditor createDefaultCellEditor() {
       
if(currentCellRenderer != null &&
           
(currentCellRenderer instanceof DefaultTreeCellRenderer)) {
           
DefaultTreeCellEditor editor = new DefaultTreeCellEditor
                       
(tree, (DefaultTreeCellRenderer)currentCellRenderer);

           
return editor;
       
}
       
return new DefaultTreeCellEditor(tree, null);
   
}

   
/**
      * Returns the default cell renderer that is used to do the
      * stamping of each node.
      */

   
protected TreeCellRenderer createDefaultCellRenderer() {
       
return new DefaultTreeCellRenderer();
   
}

   
/**
     * Returns a listener that can update the tree when the model changes.
     */

   
protected TreeModelListener createTreeModelListener() {
       
return getHandler();
   
}

   
//
   
// Uninstall methods
   
//

   
public void uninstallUI(JComponent c) {
        completeEditing
();

        prepareForUIUninstall
();

        uninstallDefaults
();
        uninstallListeners
();
        uninstallKeyboardActions
();
        uninstallComponents
();

        completeUIUninstall
();
   
}

   
protected void prepareForUIUninstall() {
   
}

   
protected void completeUIUninstall() {
       
if(createdRenderer) {
            tree
.setCellRenderer(null);
       
}
       
if(createdCellEditor) {
            tree
.setCellEditor(null);
       
}
        cellEditor
= null;
        currentCellRenderer
= null;
        rendererPane
= null;
        componentListener
= null;
        propertyChangeListener
= null;
        mouseListener
= null;
        focusListener
= null;
        keyListener
= null;
        setSelectionModel
(null);
        treeState
= null;
        drawingCache
= null;
        selectionModelPropertyChangeListener
= null;
        tree
= null;
        treeModel
= null;
        treeSelectionModel
= null;
        treeSelectionListener
= null;
        treeExpansionListener
= null;
   
}

   
protected void uninstallDefaults() {
       
if (tree.getTransferHandler() instanceof UIResource) {
            tree
.setTransferHandler(null);
       
}
   
}

   
protected void uninstallListeners() {
       
if(componentListener != null) {
            tree
.removeComponentListener(componentListener);
       
}
       
if (propertyChangeListener != null) {
            tree
.removePropertyChangeListener(propertyChangeListener);
       
}
       
if (mouseListener != null) {
            tree
.removeMouseListener(mouseListener);
           
if (mouseListener instanceof MouseMotionListener) {
                tree
.removeMouseMotionListener((MouseMotionListener)mouseListener);
           
}
       
}
       
if (focusListener != null) {
            tree
.removeFocusListener(focusListener);
       
}
       
if (keyListener != null) {
            tree
.removeKeyListener(keyListener);
       
}
       
if(treeExpansionListener != null) {
            tree
.removeTreeExpansionListener(treeExpansionListener);
       
}
       
if(treeModel != null && treeModelListener != null) {
            treeModel
.removeTreeModelListener(treeModelListener);
       
}
       
if(selectionModelPropertyChangeListener != null &&
           treeSelectionModel
!= null) {
            treeSelectionModel
.removePropertyChangeListener
               
(selectionModelPropertyChangeListener);
       
}
       
if(treeSelectionListener != null && treeSelectionModel != null) {
            treeSelectionModel
.removeTreeSelectionListener
                               
(treeSelectionListener);
       
}
        handler
= null;
   
}

   
protected void uninstallKeyboardActions() {
       
SwingUtilities.replaceUIActionMap(tree, null);
       
SwingUtilities.replaceUIInputMap(tree, JComponent.
                                         WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
,
                                         
null);
       
SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, null);
   
}

   
/**
     * Uninstalls the renderer pane.
     */

   
protected void uninstallComponents() {
       
if(rendererPane != null) {
            tree
.remove(rendererPane);
       
}
   
}

   
/**
     * Recomputes the right margin, and invalidates any tree states
     */

   
private void redoTheLayout() {
       
if (treeState != null) {
            treeState
.invalidateSizes();
       
}
   
}

   
/**
     * Returns the baseline.
     *
     * @throws NullPointerException {@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @see javax.swing.JComponent#getBaseline(int, int)
     * @since 1.6
     */

   
public int getBaseline(JComponent c, int width, int height) {
       
super.getBaseline(c, width, height);
       
UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
       
Component renderer = (Component)lafDefaults.get(
                BASELINE_COMPONENT_KEY
);
       
if (renderer == null) {
           
TreeCellRenderer tcr = createDefaultCellRenderer();
            renderer
= tcr.getTreeCellRendererComponent(
                    tree
, "a", false, false, false, -1, false);
            lafDefaults
.put(BASELINE_COMPONENT_KEY, renderer);
       
}
       
int rowHeight = tree.getRowHeight();
       
int baseline;
       
if (rowHeight > 0) {
            baseline
= renderer.getBaseline(Integer.MAX_VALUE, rowHeight);
       
}
       
else {
           
Dimension pref = renderer.getPreferredSize();
            baseline
= renderer.getBaseline(pref.width, pref.height);
       
}
       
return baseline + tree.getInsets().top;
   
}

   
/**
     * Returns an enum indicating how the baseline of the component
     * changes as the size changes.
     *
     * @throws NullPointerException {@inheritDoc}
     * @see javax.swing.JComponent#getBaseline(int, int)
     * @since 1.6
     */

   
public Component.BaselineResizeBehavior getBaselineResizeBehavior(
           
JComponent c) {
       
super.getBaselineResizeBehavior(c);
       
return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
   
}

   
//
   
// Painting routines.
   
//

   
public void paint(Graphics g, JComponent c) {
       
if (tree != c) {
           
throw new InternalError("incorrect component");
       
}

       
// Should never happen if installed for a UI
       
if(treeState == null) {
           
return;
       
}

       
Rectangle        paintBounds = g.getClipBounds();
       
Insets           insets = tree.getInsets();
       
TreePath         initialPath = getClosestPathForLocation
                                       
(tree, 0, paintBounds.y);
       
Enumeration      paintingEnumerator = treeState.getVisiblePathsFrom
                                             
(initialPath);
       
int              row = treeState.getRowForPath(initialPath);
       
int              endY = paintBounds.y + paintBounds.height;

        drawingCache
.clear();

       
if(initialPath != null && paintingEnumerator != null) {
           
TreePath   parentPath = initialPath;

           
// Draw the lines, knobs, and rows

           
// Find each parent and have them draw a line to their last child
            parentPath
= parentPath.getParentPath();
           
while(parentPath != null) {
                paintVerticalPartOfLeg
(g, paintBounds, insets, parentPath);
                drawingCache
.put(parentPath, Boolean.TRUE);
                parentPath
= parentPath.getParentPath();
           
}

           
boolean         done = false;
           
// Information for the node being rendered.
           
boolean         isExpanded;
           
boolean         hasBeenExpanded;
           
boolean         isLeaf;
           
Rectangle       boundsBuffer = new Rectangle();
           
Rectangle       bounds;
           
TreePath        path;
           
boolean         rootVisible = isRootVisible();

           
while(!done && paintingEnumerator.hasMoreElements()) {
                path
= (TreePath)paintingEnumerator.nextElement();
               
if(path != null) {
                    isLeaf
= treeModel.isLeaf(path.getLastPathComponent());
                   
if(isLeaf)
                        isExpanded
= hasBeenExpanded = false;
                   
else {
                        isExpanded
= treeState.getExpandedState(path);
                        hasBeenExpanded
= tree.hasBeenExpanded(path);
                   
}
                    bounds
= getPathBounds(path, insets, boundsBuffer);
                   
if(bounds == null)
                       
// This will only happen if the model changes out
                       
// from under us (usually in another thread).
                       
// Swing isn't multithreaded, but I'll put this
                       
// check in anyway.
                       
return;
                   
// See if the vertical line to the parent has been drawn.
                    parentPath
= path.getParentPath();
                   
if(parentPath != null) {
                       
if(drawingCache.get(parentPath) == null) {
                            paintVerticalPartOfLeg
(g, paintBounds,
                                                   insets
, parentPath);
                            drawingCache
.put(parentPath, Boolean.TRUE);
                       
}
                        paintHorizontalPartOfLeg
(g, paintBounds, insets,
                                                 bounds
, path, row,
                                                 isExpanded
,
                                                 hasBeenExpanded
, isLeaf);
                   
}
                   
else if(rootVisible && row == 0) {
                        paintHorizontalPartOfLeg
(g, paintBounds, insets,
                                                 bounds
, path, row,
                                                 isExpanded
,
                                                 hasBeenExpanded
, isLeaf);
                   
}
                   
if(shouldPaintExpandControl(path, row, isExpanded,
                                                hasBeenExpanded
, isLeaf)) {
                        paintExpandControl
(g, paintBounds, insets, bounds,
                                           path
, row, isExpanded,
                                           hasBeenExpanded
, isLeaf);
                   
}
                    paintRow
(g, paintBounds, insets, bounds, path,
                                 row
, isExpanded, hasBeenExpanded, isLeaf);
                   
if((bounds.y + bounds.height) >= endY)
                       
done = true;
               
}
               
else {
                   
done = true;
               
}
                row
++;
           
}
       
}

        paintDropLine
(g);

       
// Empty out the renderer pane, allowing renderers to be gc'ed.
        rendererPane
.removeAll();

        drawingCache
.clear();
   
}

   
private boolean isDropLine(JTree.DropLocation loc) {
       
return loc != null && loc.getPath() != null && loc.getChildIndex() != -1;
   
}

   
private void paintDropLine(Graphics g) {
       
JTree.DropLocation loc = tree.getDropLocation();
       
if (!isDropLine(loc)) {
           
return;
       
}

       
Color c = UIManager.getColor("Tree.dropLineColor");
       
if (c != null) {
            g
.setColor(c);
           
Rectangle rect = getDropLineRect(loc);
            g
.fillRect(rect.x, rect.y, rect.width, rect.height);
       
}
   
}

   
private Rectangle getDropLineRect(JTree.DropLocation loc) {
       
Rectangle rect = null;
       
TreePath path = loc.getPath();
       
int index = loc.getChildIndex();
       
boolean ltr = leftToRight;

       
Insets insets = tree.getInsets();

       
if (tree.getRowCount() == 0) {
            rect
= new Rectangle(insets.left,
                                 insets
.top,
                                 tree
.getWidth() - insets.left - insets.right,
                                 
0);
       
} else {
           
TreeModel model = getModel();
           
Object root = model.getRoot();

           
if (path.getLastPathComponent() == root
                   
&& index >= model.getChildCount(root)) {

                rect
= tree.getRowBounds(tree.getRowCount() - 1);
                rect
.y = rect.y + rect.height;
               
Rectangle xRect;

               
if (!tree.isRootVisible()) {
                    xRect
= tree.getRowBounds(0);
               
} else if (model.getChildCount(root) == 0){
                    xRect
= tree.getRowBounds(0);
                    xRect
.x += totalChildIndent;
                    xRect
.width -= totalChildIndent + totalChildIndent;
               
} else {
                   
TreePath lastChildPath = path.pathByAddingChild(
                        model
.getChild(root, model.getChildCount(root) - 1));
                    xRect
= tree.getPathBounds(lastChildPath);
               
}

                rect
.x = xRect.x;
                rect
.width = xRect.width;
           
} else {
                rect
= tree.getPathBounds(path.pathByAddingChild(
                    model
.getChild(path.getLastPathComponent(), index)));
           
}
       
}

       
if (rect.y != 0) {
            rect
.y--;
       
}

       
if (!ltr) {
            rect
.x = rect.x + rect.width - 100;
       
}

        rect
.width = 100;
        rect
.height = 2;

       
return rect;
   
}

   
/**
     * Paints the horizontal part of the leg. The receiver should
     * NOT modify <code>clipBounds</code>, or <code>insets</code>.<p>
     * NOTE: <code>parentRow</code> can be -1 if the root is not visible.
     */

   
protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
                                           
Insets insets, Rectangle bounds,
                                           
TreePath path, int row,
                                           
boolean isExpanded,
                                           
boolean hasBeenExpanded, boolean
                                            isLeaf
) {
       
if (!paintLines) {
           
return;
       
}

       
// Don't paint the legs for the root'ish node if the
       
int depth = path.getPathCount() - 1;
       
if((depth == 0 || (depth == 1 && !isRootVisible())) &&
           
!getShowsRootHandles()) {
           
return;
       
}

       
int clipLeft = clipBounds.x;
       
int clipRight = clipBounds.x + clipBounds.width;
       
int clipTop = clipBounds.y;
       
int clipBottom = clipBounds.y + clipBounds.height;
       
int lineY = bounds.y + bounds.height / 2;

       
if (leftToRight) {
           
int leftX = bounds.x - getRightChildIndent();
           
int nodeX = bounds.x - getHorizontalLegBuffer();

           
if(lineY >= clipTop
                   
&& lineY < clipBottom
                   
&& nodeX >= clipLeft
                   
&& leftX < clipRight
                   
&& leftX < nodeX) {

                g
.setColor(getHashColor());
                paintHorizontalLine
(g, tree, lineY, leftX, nodeX - 1);
           
}
       
} else {
           
int nodeX = bounds.x + bounds.width + getHorizontalLegBuffer();
           
int rightX = bounds.x + bounds.width + getRightChildIndent();

           
if(lineY >= clipTop
                   
&& lineY < clipBottom
                   
&& rightX >= clipLeft
                   
&& nodeX < clipRight
                   
&& nodeX < rightX) {

                g
.setColor(getHashColor());
                paintHorizontalLine
(g, tree, lineY, nodeX, rightX - 1);
           
}
       
}
   
}

   
/**
     * Paints the vertical part of the leg. The receiver should
     * NOT modify <code>clipBounds</code>, <code>insets</code>.<p>
     */

   
protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
                                         
Insets insets, TreePath path) {
       
if (!paintLines) {
           
return;
       
}

       
int depth = path.getPathCount() - 1;
       
if (depth == 0 && !getShowsRootHandles() && !isRootVisible()) {
           
return;
       
}
       
int lineX = getRowX(-1, depth + 1);
       
if (leftToRight) {
            lineX
= lineX - getRightChildIndent() + insets.left;
       
}
       
else {
            lineX
= tree.getWidth() - lineX - insets.right +
                    getRightChildIndent
() - 1;
       
}
       
int clipLeft = clipBounds.x;
       
int clipRight = clipBounds.x + (clipBounds.width - 1);

       
if (lineX >= clipLeft && lineX <= clipRight) {
           
int clipTop = clipBounds.y;
           
int clipBottom = clipBounds.y + clipBounds.height;
           
Rectangle parentBounds = getPathBounds(tree, path);
           
Rectangle lastChildBounds = getPathBounds(tree,
                                                     getLastChildPath
(path));

           
if(lastChildBounds == null)
               
// This shouldn't happen, but if the model is modified
               
// in another thread it is possible for this to happen.
               
// Swing isn't multithreaded, but I'll add this check in
               
// anyway.
               
return;

           
int       top;

           
if(parentBounds == null) {
                top
= Math.max(insets.top + getVerticalLegBuffer(),
                               clipTop
);
           
}
           
else
                top
= Math.max(parentBounds.y + parentBounds.height +
                               getVerticalLegBuffer
(), clipTop);
           
if(depth == 0 && !isRootVisible()) {
               
TreeModel      model = getModel();

               
if(model != null) {
                   
Object        root = model.getRoot();

                   
if(model.getChildCount(root) > 0) {
                        parentBounds
= getPathBounds(tree, path.
                                  pathByAddingChild
(model.getChild(root, 0)));
                       
if(parentBounds != null)
                            top
= Math.max(insets.top + getVerticalLegBuffer(),
                                           parentBounds
.y +
                                           parentBounds
.height / 2);
                   
}
               
}
           
}

           
int bottom = Math.min(lastChildBounds.y +
                                 
(lastChildBounds.height / 2), clipBottom);

           
if (top <= bottom) {
                g
.setColor(getHashColor());
                paintVerticalLine
(g, tree, lineX, top, bottom);
           
}
       
}
   
}

   
/**
     * Paints the expand (toggle) part of a row. The receiver should
     * NOT modify <code>clipBounds</code>, or <code>insets</code>.
     */

   
protected void paintExpandControl(Graphics g,
                                     
Rectangle clipBounds, Insets insets,
                                     
Rectangle bounds, TreePath path,
                                     
int row, boolean isExpanded,
                                     
boolean hasBeenExpanded,
                                     
boolean isLeaf) {
       
Object       value = path.getLastPathComponent();

       
// Draw icons if not a leaf and either hasn't been loaded,
       
// or the model child count is > 0.
       
if (!isLeaf && (!hasBeenExpanded ||
                        treeModel
.getChildCount(value) > 0)) {
           
int middleXOfKnob;
           
if (leftToRight) {
                middleXOfKnob
= bounds.x - getRightChildIndent() + 1;
           
} else {
                middleXOfKnob
= bounds.x + bounds.width + getRightChildIndent() - 1;
           
}
           
int middleYOfKnob = bounds.y + (bounds.height / 2);

           
if (isExpanded) {
               
Icon expandedIcon = getExpandedIcon();
               
if(expandedIcon != null)
                  drawCentered
(tree, g, expandedIcon, middleXOfKnob,
                               middleYOfKnob
);
           
}
           
else {
               
Icon collapsedIcon = getCollapsedIcon();
               
if(collapsedIcon != null)
                  drawCentered
(tree, g, collapsedIcon, middleXOfKnob,
                               middleYOfKnob
);
           
}
       
}
   
}

   
/**
     * Paints the renderer part of a row. The receiver should
     * NOT modify <code>clipBounds</code>, or <code>insets</code>.
     */

   
protected void paintRow(Graphics g, Rectangle clipBounds,
                           
Insets insets, Rectangle bounds, TreePath path,
                           
int row, boolean isExpanded,
                           
boolean hasBeenExpanded, boolean isLeaf) {
       
// Don't paint the renderer if editing this row.
       
if(editingComponent != null && editingRow == row)
           
return;

       
int leadIndex;

       
if(tree.hasFocus()) {
            leadIndex
= getLeadSelectionRow();
       
}
       
else
            leadIndex
= -1;

       
Component component;

        component
= currentCellRenderer.getTreeCellRendererComponent
                     
(tree, path.getLastPathComponent(),
                       tree
.isRowSelected(row), isExpanded, isLeaf, row,
                       
(leadIndex == row));

        rendererPane
.paintComponent(g, component, tree, bounds.x, bounds.y,
                                    bounds
.width, bounds.height, true);
   
}

   
/**
     * Returns true if the expand (toggle) control should be drawn for
     * the specified row.
     */

   
protected boolean shouldPaintExpandControl(TreePath path, int row,
                                               
boolean isExpanded,
                                               
boolean hasBeenExpanded,
                                               
boolean isLeaf) {
       
if(isLeaf)
           
return false;

       
int              depth = path.getPathCount() - 1;

       
if((depth == 0 || (depth == 1 && !isRootVisible())) &&
           
!getShowsRootHandles())
           
return false;
       
return true;
   
}

   
/**
     * Paints a vertical line.
     */

   
protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
                                   
int bottom) {
       
if (lineTypeDashed) {
            drawDashedVerticalLine
(g, x, top, bottom);
       
} else {
            g
.drawLine(x, top, x, bottom);
       
}
   
}

   
/**
     * Paints a horizontal line.
     */

   
protected void paintHorizontalLine(Graphics g, JComponent c, int y,
                                     
int left, int right) {
       
if (lineTypeDashed) {
            drawDashedHorizontalLine
(g, y, left, right);
       
} else {
            g
.drawLine(left, y, right, y);
       
}
   
}

   
/**
     * The vertical element of legs between nodes starts at the bottom of the
     * parent node by default.  This method makes the leg start below that.
     */

   
protected int getVerticalLegBuffer() {
       
return 0;
   
}

   
/**
     * The horizontal element of legs between nodes starts at the
     * right of the left-hand side of the child node by default.  This
     * method makes the leg end before that.
     */

   
protected int getHorizontalLegBuffer() {
       
return 0;
   
}

   
private int findCenteredX(int x, int iconWidth) {
       
return leftToRight
               
? x - (int)Math.ceil(iconWidth / 2.0)
               
: x - (int)Math.floor(iconWidth / 2.0);
   
}

   
//
   
// Generic painting methods
   
//

   
// Draws the icon centered at (x,y)
   
protected void drawCentered(Component c, Graphics graphics, Icon icon,
                               
int x, int y) {
        icon
.paintIcon(c, graphics,
                      findCenteredX
(x, icon.getIconWidth()),
                      y
- icon.getIconHeight() / 2);
   
}

   
// This method is slow -- revisit when Java2D is ready.
   
// assumes x1 <= x2
   
protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2){
       
// Drawing only even coordinates helps join line segments so they
       
// appear as one line.  This can be defeated by translating the
       
// Graphics by an odd amount.
        x1
+= (x1 % 2);

       
for (int x = x1; x <= x2; x+=2) {
            g
.drawLine(x, y, x, y);
       
}
   
}

   
// This method is slow -- revisit when Java2D is ready.
   
// assumes y1 <= y2
   
protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) {
       
// Drawing only even coordinates helps join line segments so they
       
// appear as one line.  This can be defeated by translating the
       
// Graphics by an odd amount.
        y1
+= (y1 % 2);

       
for (int y = y1; y <= y2; y+=2) {
            g
.drawLine(x, y, x, y);
       
}
   
}

   
//
   
// Various local methods
   
//

   
/**
     * Returns the location, along the x-axis, to render a particular row
     * at. The return value does not include any Insets specified on the JTree.
     * This does not check for the validity of the row or depth, it is assumed
     * to be correct and will not throw an Exception if the row or depth
     * doesn't match that of the tree.
     *
     * @param row Row to return x location for
     * @param depth Depth of the row
     * @return amount to indent the given row.
     * @since 1.5
     */

   
protected int getRowX(int row, int depth) {
       
return totalChildIndent * (depth + depthOffset);
   
}

   
/**
     * Makes all the nodes that are expanded in JTree expanded in LayoutCache.
     * This invokes updateExpandedDescendants with the root path.
     */

   
protected void updateLayoutCacheExpandedNodes() {
       
if(treeModel != null && treeModel.getRoot() != null)
            updateExpandedDescendants
(new TreePath(treeModel.getRoot()));
   
}

   
private void updateLayoutCacheExpandedNodesIfNecessary() {
       
if (treeModel != null && treeModel.getRoot() != null) {
           
TreePath rootPath = new TreePath(treeModel.getRoot());
           
if (tree.isExpanded(rootPath)) {
                updateLayoutCacheExpandedNodes
();
           
} else {
                treeState
.setExpandedState(rootPath, false);
           
}
       
}
   
}

   
/**
     * Updates the expanded state of all the descendants of <code>path</code>
     * by getting the expanded descendants from the tree and forwarding
     * to the tree state.
     */

   
protected void updateExpandedDescendants(TreePath path) {
        completeEditing
();
       
if(treeState != null) {
            treeState
.setExpandedState(path, true);

           
Enumeration   descendants = tree.getExpandedDescendants(path);

           
if(descendants != null) {
               
while(descendants.hasMoreElements()) {
                    path
= (TreePath)descendants.nextElement();
                    treeState
.setExpandedState(path, true);
               
}
           
}
            updateLeadRow
();
            updateSize
();
       
}
   
}

   
/**
     * Returns a path to the last child of <code>parent</code>.
     */

   
protected TreePath getLastChildPath(TreePath parent) {
       
if(treeModel != null) {
           
int         childCount = treeModel.getChildCount
               
(parent.getLastPathComponent());

           
if(childCount > 0)
               
return parent.pathByAddingChild(treeModel.getChild
                           
(parent.getLastPathComponent(), childCount - 1));
       
}
       
return null;
   
}

   
/**
     * Updates how much each depth should be offset by.
     */

   
protected void updateDepthOffset() {
       
if(isRootVisible()) {
           
if(getShowsRootHandles())
                depthOffset
= 1;
           
else
                depthOffset
= 0;
       
}
       
else if(!getShowsRootHandles())
            depthOffset
= -1;
       
else
            depthOffset
= 0;
   
}

   
/**
      * Updates the cellEditor based on the editability of the JTree that
      * we're contained in.  If the tree is editable but doesn't have a
      * cellEditor, a basic one will be used.
      */

   
protected void updateCellEditor() {
       
TreeCellEditor        newEditor;

        completeEditing
();
       
if(tree == null)
            newEditor
= null;
       
else {
           
if(tree.isEditable()) {
                newEditor
= tree.getCellEditor();
               
if(newEditor == null) {
                    newEditor
= createDefaultCellEditor();
                   
if(newEditor != null) {
                        tree
.setCellEditor(newEditor);
                        createdCellEditor
= true;
                   
}
               
}
           
}
           
else
                newEditor
= null;
       
}
       
if(newEditor != cellEditor) {
           
if(cellEditor != null && cellEditorListener != null)
                cellEditor
.removeCellEditorListener(cellEditorListener);
            cellEditor
= newEditor;
           
if(cellEditorListener == null)
                cellEditorListener
= createCellEditorListener();
           
if(newEditor != null && cellEditorListener != null)
                newEditor
.addCellEditorListener(cellEditorListener);
            createdCellEditor
= false;
       
}
   
}

   
/**
      * Messaged from the tree we're in when the renderer has changed.
      */

   
protected void updateRenderer() {
       
if(tree != null) {
           
TreeCellRenderer      newCellRenderer;

            newCellRenderer
= tree.getCellRenderer();
           
if(newCellRenderer == null) {
                tree
.setCellRenderer(createDefaultCellRenderer());
                createdRenderer
= true;
           
}
           
else {
                createdRenderer
= false;
                currentCellRenderer
= newCellRenderer;
               
if(createdCellEditor) {
                    tree
.setCellEditor(null);
               
}
           
}
       
}
       
else {
            createdRenderer
= false;
            currentCellRenderer
= null;
       
}
        updateCellEditor
();
   
}

   
/**
     * Resets the TreeState instance based on the tree we're providing the
     * look and feel for.
     */

   
protected void configureLayoutCache() {
       
if(treeState != null && tree != null) {
           
if(nodeDimensions == null)
                nodeDimensions
= createNodeDimensions();
            treeState
.setNodeDimensions(nodeDimensions);
            treeState
.setRootVisible(tree.isRootVisible());
            treeState
.setRowHeight(tree.getRowHeight());
            treeState
.setSelectionModel(getSelectionModel());
           
// Only do this if necessary, may loss state if call with
           
// same model as it currently has.
           
if(treeState.getModel() != tree.getModel())
                treeState
.setModel(tree.getModel());
            updateLayoutCacheExpandedNodesIfNecessary
();
           
// Create a listener to update preferred size when bounds
           
// changes, if necessary.
           
if(isLargeModel()) {
               
if(componentListener == null) {
                    componentListener
= createComponentListener();
                   
if(componentListener != null)
                        tree
.addComponentListener(componentListener);
               
}
           
}
           
else if(componentListener != null) {
                tree
.removeComponentListener(componentListener);
                componentListener
= null;
           
}
       
}
       
else if(componentListener != null) {
            tree
.removeComponentListener(componentListener);
            componentListener
= null;
       
}
   
}

   
/**
     * Marks the cached size as being invalid, and messages the
     * tree with <code>treeDidChange</code>.
     */

   
protected void updateSize() {
        validCachedPreferredSize
= false;
        tree
.treeDidChange();
   
}

   
private void updateSize0() {
        validCachedPreferredSize
= false;
        tree
.revalidate();
   
}

   
/**
     * Updates the <code>preferredSize</code> instance variable,
     * which is returned from <code>getPreferredSize()</code>.<p>
     * For left to right orientations, the size is determined from the
     * current AbstractLayoutCache. For RTL orientations, the preferred size
     * becomes the width minus the minimum x position.
     */

   
protected void updateCachedPreferredSize() {
       
if(treeState != null) {
           
Insets               i = tree.getInsets();

           
if(isLargeModel()) {
               
Rectangle            visRect = tree.getVisibleRect();

               
if (visRect.x == 0 && visRect.y == 0 &&
                        visRect
.width == 0 && visRect.height == 0 &&
                        tree
.getVisibleRowCount() > 0) {
                   
// The tree doesn't have a valid bounds yet. Calculate
                   
// based on visible row count.
                    visRect
.width = 1;
                    visRect
.height = tree.getRowHeight() *
                            tree
.getVisibleRowCount();
               
} else {
                    visRect
.x -= i.left;
                    visRect
.y -= i.top;
               
}
                preferredSize
.width = treeState.getPreferredWidth(visRect);
           
}
           
else {
                preferredSize
.width = treeState.getPreferredWidth(null);
           
}
            preferredSize
.height = treeState.getPreferredHeight();
            preferredSize
.width += i.left + i.right;
            preferredSize
.height += i.top + i.bottom;
       
}
        validCachedPreferredSize
= true;
   
}

   
/**
      * Messaged from the VisibleTreeNode after it has been expanded.
      */

   
protected void pathWasExpanded(TreePath path) {
       
if(tree != null) {
            tree
.fireTreeExpanded(path);
       
}
   
}

   
/**
      * Messaged from the VisibleTreeNode after it has collapsed.
      */

   
protected void pathWasCollapsed(TreePath path) {
       
if(tree != null) {
            tree
.fireTreeCollapsed(path);
       
}
   
}

   
/**
      * Ensures that the rows identified by beginRow through endRow are
      * visible.
      */

   
protected void ensureRowsAreVisible(int beginRow, int endRow) {
       
if(tree != null && beginRow >= 0 && endRow < getRowCount(tree)) {
           
boolean scrollVert = DefaultLookup.getBoolean(tree, this,
                             
"Tree.scrollsHorizontallyAndVertically", false);
           
if(beginRow == endRow) {
               
Rectangle     scrollBounds = getPathBounds(tree, getPathForRow
                                                           
(tree, beginRow));

               
if(scrollBounds != null) {
                   
if (!scrollVert) {
                        scrollBounds
.x = tree.getVisibleRect().x;
                        scrollBounds
.width = 1;
                   
}
                    tree
.scrollRectToVisible(scrollBounds);
               
}
           
}
           
else {
               
Rectangle   beginRect = getPathBounds(tree, getPathForRow
                                                     
(tree, beginRow));
               
Rectangle   visRect = tree.getVisibleRect();
               
Rectangle   testRect = beginRect;
               
int         beginY = beginRect.y;
               
int         maxY = beginY + visRect.height;

               
for(int counter = beginRow + 1; counter <= endRow; counter++) {
                    testRect
= getPathBounds(tree,
                                             getPathForRow
(tree, counter));
                   
if((testRect.y + testRect.height) > maxY)
                        counter
= endRow;
               
}
                tree
.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1,
                                                  testRect
.y + testRect.height-
                                                  beginY
));
           
}
       
}
   
}

   
/** Sets the preferred minimum size.
      */

   
public void setPreferredMinSize(Dimension newSize) {
        preferredMinSize
= newSize;
   
}

   
/** Returns the minimum preferred size.
      */

   
public Dimension getPreferredMinSize() {
       
if(preferredMinSize == null)
           
return null;
       
return new Dimension(preferredMinSize);
   
}

   
/** Returns the preferred size to properly display the tree,
      * this is a cover method for getPreferredSize(c, false).
      */

   
public Dimension getPreferredSize(JComponent c) {
       
return getPreferredSize(c, true);
   
}

   
/** Returns the preferred size to represent the tree in
      * <I>c</I>.  If <I>checkConsistancy</I> is true
      * <b>checkConsistancy</b> is messaged first.
      */

   
public Dimension getPreferredSize(JComponent c,
                                     
boolean checkConsistancy) {
       
Dimension       pSize = this.getPreferredMinSize();

       
if(!validCachedPreferredSize)
            updateCachedPreferredSize
();
       
if(tree != null) {
           
if(pSize != null)
               
return new Dimension(Math.max(pSize.width,
                                              preferredSize
.width),
                             
Math.max(pSize.height, preferredSize.height));
           
return new Dimension(preferredSize.width, preferredSize.height);
       
}
       
else if(pSize != null)
           
return pSize;
       
else
           
return new Dimension(0, 0);
   
}

   
/**
      * Returns the minimum size for this component.  Which will be
      * the min preferred size or 0, 0.
      */

   
public Dimension getMinimumSize(JComponent c) {
       
if(this.getPreferredMinSize() != null)
           
return this.getPreferredMinSize();
       
return new Dimension(0, 0);
   
}

   
/**
      * Returns the maximum size for this component, which will be the
      * preferred size if the instance is currently in a JTree, or 0, 0.
      */

   
public Dimension getMaximumSize(JComponent c) {
       
if(tree != null)
           
return getPreferredSize(tree);
       
if(this.getPreferredMinSize() != null)
           
return this.getPreferredMinSize();
       
return new Dimension(0, 0);
   
}


   
/**
     * Messages to stop the editing session. If the UI the receiver
     * is providing the look and feel for returns true from
     * <code>getInvokesStopCellEditing</code>, stopCellEditing will
     * invoked on the current editor. Then completeEditing will
     * be messaged with false, true, false to cancel any lingering
     * editing.
     */

   
protected void completeEditing() {
       
/* If should invoke stopCellEditing, try that */
       
if(tree.getInvokesStopCellEditing() &&
           stopEditingInCompleteEditing
&& editingComponent != null) {
            cellEditor
.stopCellEditing();
       
}
       
/* Invoke cancelCellEditing, this will do nothing if stopCellEditing
           was successful. */

        completeEditing
(false, true, false);
   
}

   
/**
      * Stops the editing session.  If messageStop is true the editor
      * is messaged with stopEditing, if messageCancel is true the
      * editor is messaged with cancelEditing. If messageTree is true
      * the treeModel is messaged with valueForPathChanged.
      */

   
protected void completeEditing(boolean messageStop,
                                   
boolean messageCancel,
                                   
boolean messageTree) {
       
if(stopEditingInCompleteEditing && editingComponent != null) {
           
Component             oldComponent = editingComponent;
           
TreePath              oldPath = editingPath;
           
TreeCellEditor        oldEditor = cellEditor;
           
Object                newValue = oldEditor.getCellEditorValue();
           
Rectangle             editingBounds = getPathBounds(tree,
                                                                editingPath
);
           
boolean               requestFocus = (tree != null &&
                                   
(tree.hasFocus() || SwingUtilities.
                                    findFocusOwner
(editingComponent) != null));

            editingComponent
= null;
            editingPath
= null;
           
if(messageStop)
                oldEditor
.stopCellEditing();
           
else if(messageCancel)
                oldEditor
.cancelCellEditing();
            tree
.remove(oldComponent);
           
if(editorHasDifferentSize) {
                treeState
.invalidatePathBounds(oldPath);
                updateSize
();
           
}
           
else {
                editingBounds
.x = 0;
                editingBounds
.width = tree.getSize().width;
                tree
.repaint(editingBounds);
           
}
           
if(requestFocus)
                tree
.requestFocus();
           
if(messageTree)
                treeModel
.valueForPathChanged(oldPath, newValue);
       
}
   
}

   
// cover method for startEditing that allows us to pass extra
   
// information into that method via a class variable
   
private boolean startEditingOnRelease(TreePath path,
                                         
MouseEvent event,
                                         
MouseEvent releaseEvent) {
       
this.releaseEvent = releaseEvent;
       
try {
           
return startEditing(path, event);
       
} finally {
           
this.releaseEvent = null;
       
}
   
}

   
/**
      * Will start editing for node if there is a cellEditor and
      * shouldSelectCell returns true.<p>
      * This assumes that path is valid and visible.
      */

   
protected boolean startEditing(TreePath path, MouseEvent event) {
       
if (isEditing(tree) && tree.getInvokesStopCellEditing() &&
                               
!stopEditing(tree)) {
           
return false;
       
}
        completeEditing
();
       
if(cellEditor != null && tree.isPathEditable(path)) {
           
int           row = getRowForPath(tree, path);

           
if(cellEditor.isCellEditable(event)) {
                editingComponent
= cellEditor.getTreeCellEditorComponent
                     
(tree, path.getLastPathComponent(),
                       tree
.isPathSelected(path), tree.isExpanded(path),
                       treeModel
.isLeaf(path.getLastPathComponent()), row);
               
Rectangle           nodeBounds = getPathBounds(tree, path);

                editingRow
= row;

               
Dimension editorSize = editingComponent.getPreferredSize();

               
// Only allow odd heights if explicitly set.
               
if(editorSize.height != nodeBounds.height &&
                   getRowHeight
() > 0)
                    editorSize
.height = getRowHeight();

               
if(editorSize.width != nodeBounds.width ||
                   editorSize
.height != nodeBounds.height) {
                   
// Editor wants different width or height, invalidate
                   
// treeState and relayout.
                    editorHasDifferentSize
= true;
                    treeState
.invalidatePathBounds(path);
                    updateSize
();
                   
// To make sure x/y are updated correctly, fetch
                   
// the bounds again.
                    nodeBounds
= getPathBounds(tree, path);
               
}
               
else
                    editorHasDifferentSize
= false;
                tree
.add(editingComponent);
                editingComponent
.setBounds(nodeBounds.x, nodeBounds.y,
                                           nodeBounds
.width,
                                           nodeBounds
.height);
                editingPath
= path;
               
if (editingComponent instanceof JComponent) {
                   
((JComponent)editingComponent).revalidate();
               
} else {
                    editingComponent
.validate();
               
}
                editingComponent
.repaint();
               
if(cellEditor.shouldSelectCell(event)) {
                    stopEditingInCompleteEditing
= false;
                    tree
.setSelectionRow(row);
                    stopEditingInCompleteEditing
= true;
               
}

               
Component focusedComponent = SwingUtilities2.
                                 compositeRequestFocus
(editingComponent);
               
boolean selectAll = true;

               
if(event != null && event instanceof MouseEvent) {
                   
/* Find the component that will get forwarded all the
                       mouse events until mouseReleased. */

                   
Point          componentPoint = SwingUtilities.convertPoint
                       
(tree, new Point(event.getX(), event.getY()),
                         editingComponent
);

                   
/* Create an instance of BasicTreeMouseListener to handle
                       passing the mouse/motion events to the necessary
                       component. */

                   
// We really want similar behavior to getMouseEventTarget,
                   
// but it is package private.
                   
Component activeComponent = SwingUtilities.
                                    getDeepestComponentAt
(editingComponent,
                                       componentPoint
.x, componentPoint.y);
                   
if (activeComponent != null) {
                       
MouseInputHandler handler =
                           
new MouseInputHandler(tree, activeComponent,
                                                 
event, focusedComponent);

                       
if (releaseEvent != null) {
                            handler
.mouseReleased(releaseEvent);
                       
}

                        selectAll
= false;
                   
}
               
}
               
if (selectAll && focusedComponent instanceof JTextField) {
                   
((JTextField)focusedComponent).selectAll();
               
}
               
return true;
           
}
           
else
                editingComponent
= null;
       
}
       
return false;
   
}

   
//
   
// Following are primarily for handling mouse events.
   
//

   
/**
     * If the <code>mouseX</code> and <code>mouseY</code> are in the
     * expand/collapse region of the <code>row</code>, this will toggle
     * the row.
     */

   
protected void checkForClickInExpandControl(TreePath path,
                                               
int mouseX, int mouseY) {
     
if (isLocationInExpandControl(path, mouseX, mouseY)) {
          handleExpandControlClick
(path, mouseX, mouseY);
       
}
   
}

   
/**
     * Returns true if <code>mouseX</code> and <code>mouseY</code> fall
     * in the area of row that is used to expand/collapse the node and
     * the node at <code>row</code> does not represent a leaf.
     */

   
protected boolean isLocationInExpandControl(TreePath path,
                                               
int mouseX, int mouseY) {
       
if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){
           
int                     boxWidth;
           
Insets                  i = tree.getInsets();

           
if(getExpandedIcon() != null)
                boxWidth
= getExpandedIcon().getIconWidth();
           
else
                boxWidth
= 8;

           
int boxLeftX = getRowX(tree.getRowForPath(path),
                                   path
.getPathCount() - 1);

           
if (leftToRight) {
                boxLeftX
= boxLeftX + i.left - getRightChildIndent() + 1;
           
} else {
                boxLeftX
= tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1;
           
}

            boxLeftX
= findCenteredX(boxLeftX, boxWidth);

           
return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth));
       
}
       
return false;
   
}

   
/**
     * Messaged when the user clicks the particular row, this invokes
     * toggleExpandState.
     */

   
protected void handleExpandControlClick(TreePath path, int mouseX,
                                           
int mouseY) {
        toggleExpandState
(path);
   
}

   
/**
     * Expands path if it is not expanded, or collapses row if it is expanded.
     * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible
     * is invoked to scroll as many of the children to visible as possible
     * (tries to scroll to last visible descendant of path).
     */

   
protected void toggleExpandState(TreePath path) {
       
if(!tree.isExpanded(path)) {
           
int       row = getRowForPath(tree, path);

            tree
.expandPath(path);
            updateSize
();
           
if(row != -1) {
               
if(tree.getScrollsOnExpand())
                    ensureRowsAreVisible
(row, row + treeState.
                                         getVisibleChildCount
(path));
               
else
                    ensureRowsAreVisible
(row, row);
           
}
       
}
       
else {
            tree
.collapsePath(path);
            updateSize
();
       
}
   
}

   
/**
     * Returning true signifies a mouse event on the node should toggle
     * the selection of only the row under mouse.
     */

   
protected boolean isToggleSelectionEvent(MouseEvent event) {
       
return (SwingUtilities.isLeftMouseButton(event) &&
               
event.isControlDown());
   
}

   
/**
     * Returning true signifies a mouse event on the node should select
     * from the anchor point.
     */

   
protected boolean isMultiSelectEvent(MouseEvent event) {
       
return (SwingUtilities.isLeftMouseButton(event) &&
               
event.isShiftDown());
   
}

   
/**
     * Returning true indicates the row under the mouse should be toggled
     * based on the event. This is invoked after checkForClickInExpandControl,
     * implying the location is not in the expand (toggle) control
     */

   
protected boolean isToggleEvent(MouseEvent event) {
       
if(!SwingUtilities.isLeftMouseButton(event)) {
           
return false;
       
}
       
int           clickCount = tree.getToggleClickCount();

       
if(clickCount <= 0) {
           
return false;
       
}
       
return ((event.getClickCount() % clickCount) == 0);
   
}

   
/**
     * Messaged to update the selection based on a MouseEvent over a
     * particular row. If the event is a toggle selection event, the
     * row is either selected, or deselected. If the event identifies
     * a multi selection event, the selection is updated from the
     * anchor point. Otherwise the row is selected, and if the event
     * specified a toggle event the row is expanded/collapsed.
     */

   
protected void selectPathForEvent(TreePath path, MouseEvent event) {
       
/* Adjust from the anchor point. */
       
if(isMultiSelectEvent(event)) {
           
TreePath    anchor = getAnchorSelectionPath();
           
int         anchorRow = (anchor == null) ? -1 :
                                    getRowForPath
(tree, anchor);

           
if(anchorRow == -1 || tree.getSelectionModel().
                      getSelectionMode
() == TreeSelectionModel.
                      SINGLE_TREE_SELECTION
) {
                tree
.setSelectionPath(path);
           
}
           
else {
               
int          row = getRowForPath(tree, path);
               
TreePath     lastAnchorPath = anchor;

               
if (isToggleSelectionEvent(event)) {
                   
if (tree.isRowSelected(anchorRow)) {
                        tree
.addSelectionInterval(anchorRow, row);
                   
} else {
                        tree
.removeSelectionInterval(anchorRow, row);
                        tree
.addSelectionInterval(row, row);
                   
}
               
} else if(row < anchorRow) {
                    tree
.setSelectionInterval(row, anchorRow);
               
} else {
                    tree
.setSelectionInterval(anchorRow, row);
               
}
                lastSelectedRow
= row;
                setAnchorSelectionPath
(lastAnchorPath);
                setLeadSelectionPath
(path);
           
}
       
}

       
// Should this event toggle the selection of this row?
       
/* Control toggles just this node. */
       
else if(isToggleSelectionEvent(event)) {
           
if(tree.isPathSelected(path))
                tree
.removeSelectionPath(path);
           
else
                tree
.addSelectionPath(path);
            lastSelectedRow
= getRowForPath(tree, path);
            setAnchorSelectionPath
(path);
            setLeadSelectionPath
(path);
       
}

       
/* Otherwise set the selection to just this interval. */
       
else if(SwingUtilities.isLeftMouseButton(event)) {
            tree
.setSelectionPath(path);
           
if(isToggleEvent(event)) {
                toggleExpandState
(path);
           
}
       
}
   
}

   
/**
     * @return true if the node at <code>row</code> is a leaf.
     */

   
protected boolean isLeaf(int row) {
       
TreePath          path = getPathForRow(tree, row);

       
if(path != null)
           
return treeModel.isLeaf(path.getLastPathComponent());
       
// Have to return something here...
       
return true;
   
}

   
//
   
// The following selection methods (lead/anchor) are covers for the
   
// methods in JTree.
   
//
   
private void setAnchorSelectionPath(TreePath newPath) {
        ignoreLAChange
= true;
       
try {
            tree
.setAnchorSelectionPath(newPath);
       
} finally{
            ignoreLAChange
= false;
       
}
   
}

   
private TreePath getAnchorSelectionPath() {
       
return tree.getAnchorSelectionPath();
   
}

   
private void setLeadSelectionPath(TreePath newPath) {
        setLeadSelectionPath
(newPath, false);
   
}

   
private void setLeadSelectionPath(TreePath newPath, boolean repaint) {
       
Rectangle       bounds = repaint ?
                            getPathBounds
(tree, getLeadSelectionPath()) : null;

        ignoreLAChange
= true;
       
try {
            tree
.setLeadSelectionPath(newPath);
       
} finally {
            ignoreLAChange
= false;
       
}
        leadRow
= getRowForPath(tree, newPath);

       
if (repaint) {
           
if (bounds != null) {
                tree
.repaint(getRepaintPathBounds(bounds));
           
}
            bounds
= getPathBounds(tree, newPath);
           
if (bounds != null) {
                tree
.repaint(getRepaintPathBounds(bounds));
           
}
       
}
   
}

   
private Rectangle getRepaintPathBounds(Rectangle bounds) {
       
if (UIManager.getBoolean("Tree.repaintWholeRow")) {
           bounds
.x = 0;
           bounds
.width = tree.getWidth();
       
}
       
return bounds;
   
}

   
private TreePath getLeadSelectionPath() {
       
return tree.getLeadSelectionPath();
   
}

   
private void updateLeadRow() {
        leadRow
= getRowForPath(tree, getLeadSelectionPath());
   
}

   
private int getLeadSelectionRow() {
       
return leadRow;
   
}

   
/**
     * Extends the selection from the anchor to make <code>newLead</code>
     * the lead of the selection. This does not scroll.
     */

   
private void extendSelection(TreePath newLead) {
       
TreePath           aPath = getAnchorSelectionPath();
       
int                aRow = (aPath == null) ? -1 :
                                  getRowForPath
(tree, aPath);
       
int                newIndex = getRowForPath(tree, newLead);

       
if(aRow == -1) {
            tree
.setSelectionRow(newIndex);
       
}
       
else {
           
if(aRow < newIndex) {
                tree
.setSelectionInterval(aRow, newIndex);
           
}
           
else {
                tree
.setSelectionInterval(newIndex, aRow);
           
}
            setAnchorSelectionPath
(aPath);
            setLeadSelectionPath
(newLead);
       
}
   
}

   
/**
     * Invokes <code>repaint</code> on the JTree for the passed in TreePath,
     * <code>path</code>.
     */

   
private void repaintPath(TreePath path) {
       
if (path != null) {
           
Rectangle bounds = getPathBounds(tree, path);
           
if (bounds != null) {
                tree
.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
           
}
       
}
   
}

   
/**
     * Updates the TreeState in response to nodes expanding/collapsing.
     */

   
public class TreeExpansionHandler implements TreeExpansionListener {
       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
/**
         * Called whenever an item in the tree has been expanded.
         */

       
public void treeExpanded(TreeExpansionEvent event) {
            getHandler
().treeExpanded(event);
       
}

       
/**
         * Called whenever an item in the tree has been collapsed.
         */

       
public void treeCollapsed(TreeExpansionEvent event) {
            getHandler
().treeCollapsed(event);
       
}
   
} // BasicTreeUI.TreeExpansionHandler


   
/**
     * Updates the preferred size when scrolling (if necessary).
     */

   
public class ComponentHandler extends ComponentAdapter implements
                 
ActionListener {
       
/** Timer used when inside a scrollpane and the scrollbar is
         * adjusting. */

       
protected Timer                timer;
       
/** ScrollBar that is being adjusted. */
       
protected JScrollBar           scrollBar;

       
public void componentMoved(ComponentEvent e) {
           
if(timer == null) {
               
JScrollPane   scrollPane = getScrollPane();

               
if(scrollPane == null)
                    updateSize
();
               
else {
                    scrollBar
= scrollPane.getVerticalScrollBar();
                   
if(scrollBar == null ||
                       
!scrollBar.getValueIsAdjusting()) {
                       
// Try the horizontal scrollbar.
                       
if((scrollBar = scrollPane.getHorizontalScrollBar())
                           
!= null && scrollBar.getValueIsAdjusting())
                            startTimer
();
                       
else
                            updateSize
();
                   
}
                   
else
                        startTimer
();
               
}
           
}
       
}

       
/**
         * Creates, if necessary, and starts a Timer to check if need to
         * resize the bounds.
         */

       
protected void startTimer() {
           
if(timer == null) {
                timer
= new Timer(200, this);
                timer
.setRepeats(true);
           
}
            timer
.start();
       
}

       
/**
         * Returns the JScrollPane housing the JTree, or null if one isn't
         * found.
         */

       
protected JScrollPane getScrollPane() {
           
Component       c = tree.getParent();

           
while(c != null && !(c instanceof JScrollPane))
                c
= c.getParent();
           
if(c instanceof JScrollPane)
               
return (JScrollPane)c;
           
return null;
       
}

       
/**
         * Public as a result of Timer. If the scrollBar is null, or
         * not adjusting, this stops the timer and updates the sizing.
         */

       
public void actionPerformed(ActionEvent ae) {
           
if(scrollBar == null || !scrollBar.getValueIsAdjusting()) {
               
if(timer != null)
                    timer
.stop();
                updateSize
();
                timer
= null;
                scrollBar
= null;
           
}
       
}
   
} // End of BasicTreeUI.ComponentHandler


   
/**
     * Forwards all TreeModel events to the TreeState.
     */

   
public class TreeModelHandler implements TreeModelListener {

       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
public void treeNodesChanged(TreeModelEvent e) {
            getHandler
().treeNodesChanged(e);
       
}

       
public void treeNodesInserted(TreeModelEvent e) {
            getHandler
().treeNodesInserted(e);
       
}

       
public void treeNodesRemoved(TreeModelEvent e) {
            getHandler
().treeNodesRemoved(e);
       
}

       
public void treeStructureChanged(TreeModelEvent e) {
            getHandler
().treeStructureChanged(e);
       
}
   
} // End of BasicTreeUI.TreeModelHandler


   
/**
     * Listens for changes in the selection model and updates the display
     * accordingly.
     */

   
public class TreeSelectionHandler implements TreeSelectionListener {

       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
/**
         * Messaged when the selection changes in the tree we're displaying
         * for.  Stops editing, messages super and displays the changed paths.
         */

       
public void valueChanged(TreeSelectionEvent event) {
            getHandler
().valueChanged(event);
       
}
   
}// End of BasicTreeUI.TreeSelectionHandler


   
/**
     * Listener responsible for getting cell editing events and updating
     * the tree accordingly.
     */

   
public class CellEditorHandler implements CellEditorListener {

       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
/** Messaged when editing has stopped in the tree. */
       
public void editingStopped(ChangeEvent e) {
            getHandler
().editingStopped(e);
       
}

       
/** Messaged when editing has been canceled in the tree. */
       
public void editingCanceled(ChangeEvent e) {
            getHandler
().editingCanceled(e);
       
}
   
} // BasicTreeUI.CellEditorHandler


   
/**
     * This is used to get mutliple key down events to appropriately generate
     * events.
     */

   
public class KeyHandler extends KeyAdapter {

       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
// Also note these fields aren't use anymore, nor does Handler have
       
// the old functionality. This behavior worked around an old bug
       
// in JComponent that has long since been fixed.

       
/** Key code that is being generated for. */
       
protected Action              repeatKeyAction;

       
/** Set to true while keyPressed is active. */
       
protected boolean            isKeyDown;

       
/**
         * Invoked when a key has been typed.
         *
         * Moves the keyboard focus to the first element
         * whose first letter matches the alphanumeric key
         * pressed by the user. Subsequent same key presses
         * move the keyboard focus to the next object that
         * starts with the same letter.
         */

       
public void keyTyped(KeyEvent e) {
            getHandler
().keyTyped(e);
       
}

       
public void keyPressed(KeyEvent e) {
            getHandler
().keyPressed(e);
       
}

       
public void keyReleased(KeyEvent e) {
            getHandler
().keyReleased(e);
       
}
   
} // End of BasicTreeUI.KeyHandler


   
/**
     * Repaints the lead selection row when focus is lost/gained.
     */

   
public class FocusHandler implements FocusListener {
       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
/**
         * Invoked when focus is activated on the tree we're in, redraws the
         * lead row.
         */

       
public void focusGained(FocusEvent e) {
            getHandler
().focusGained(e);
       
}

       
/**
         * Invoked when focus is activated on the tree we're in, redraws the
         * lead row.
         */

       
public void focusLost(FocusEvent e) {
            getHandler
().focusLost(e);
       
}
   
} // End of class BasicTreeUI.FocusHandler


   
/**
     * Class responsible for getting size of node, method is forwarded
     * to BasicTreeUI method. X location does not include insets, that is
     * handled in getPathBounds.
     */

   
// This returns locations that don't include any Insets.
   
public class NodeDimensionsHandler extends
                 
AbstractLayoutCache.NodeDimensions {
       
/**
         * Responsible for getting the size of a particular node.
         */

       
public Rectangle getNodeDimensions(Object value, int row,
                                           
int depth, boolean expanded,
                                           
Rectangle size) {
           
// Return size of editing component, if editing and asking
           
// for editing row.
           
if(editingComponent != null && editingRow == row) {
               
Dimension        prefSize = editingComponent.
                                              getPreferredSize
();
               
int              rh = getRowHeight();

               
if(rh > 0 && rh != prefSize.height)
                    prefSize
.height = rh;
               
if(size != null) {
                    size
.x = getRowX(row, depth);
                    size
.width = prefSize.width;
                    size
.height = prefSize.height;
               
}
               
else {
                    size
= new Rectangle(getRowX(row, depth), 0,
                                         prefSize
.width, prefSize.height);
               
}
               
return size;
           
}
           
// Not editing, use renderer.
           
if(currentCellRenderer != null) {
               
Component          aComponent;

                aComponent
= currentCellRenderer.getTreeCellRendererComponent
                   
(tree, value, tree.isRowSelected(row),
                     expanded
, treeModel.isLeaf(value), row,
                     
false);
               
if(tree != null) {
                   
// Only ever removed when UI changes, this is OK!
                    rendererPane
.add(aComponent);
                    aComponent
.validate();
               
}
               
Dimension        prefSize = aComponent.getPreferredSize();

               
if(size != null) {
                    size
.x = getRowX(row, depth);
                    size
.width = prefSize.width;
                    size
.height = prefSize.height;
               
}
               
else {
                    size
= new Rectangle(getRowX(row, depth), 0,
                                         prefSize
.width, prefSize.height);
               
}
               
return size;
           
}
           
return null;
       
}

       
/**
         * @return amount to indent the given row.
         */

       
protected int getRowX(int row, int depth) {
           
return BasicTreeUI.this.getRowX(row, depth);
       
}

   
} // End of class BasicTreeUI.NodeDimensionsHandler


   
/**
     * TreeMouseListener is responsible for updating the selection
     * based on mouse events.
     */

   
public class MouseHandler extends MouseAdapter implements MouseMotionListener
 
{
       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
/**
         * Invoked when a mouse button has been pressed on a component.
         */

       
public void mousePressed(MouseEvent e) {
            getHandler
().mousePressed(e);
       
}

       
public void mouseDragged(MouseEvent e) {
            getHandler
().mouseDragged(e);
       
}

       
/**
         * Invoked when the mouse button has been moved on a component
         * (with no buttons no down).
         * @since 1.4
         */

       
public void mouseMoved(MouseEvent e) {
            getHandler
().mouseMoved(e);
       
}

       
public void mouseReleased(MouseEvent e) {
            getHandler
().mouseReleased(e);
       
}
   
} // End of BasicTreeUI.MouseHandler


   
/**
     * PropertyChangeListener for the tree. Updates the appropriate
     * varaible, or TreeState, based on what changes.
     */

   
public class PropertyChangeHandler implements
                       
PropertyChangeListener {

       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
public void propertyChange(PropertyChangeEvent event) {
            getHandler
().propertyChange(event);
       
}
   
} // End of BasicTreeUI.PropertyChangeHandler


   
/**
     * Listener on the TreeSelectionModel, resets the row selection if
     * any of the properties of the model change.
     */

   
public class SelectionModelPropertyChangeHandler implements
                     
PropertyChangeListener {

       
// NOTE: This class exists only for backward compatability. All
       
// its functionality has been moved into Handler. If you need to add
       
// new functionality add it to the Handler, but make sure this
       
// class calls into the Handler.

       
public void propertyChange(PropertyChangeEvent event) {
            getHandler
().propertyChange(event);
       
}
   
} // End of BasicTreeUI.SelectionModelPropertyChangeHandler


   
/**
     * <code>TreeTraverseAction</code> is the action used for left/right keys.
     * Will toggle the expandedness of a node, as well as potentially
     * incrementing the selection.
     */

   
public class TreeTraverseAction extends AbstractAction {
       
/** Determines direction to traverse, 1 means expand, -1 means
          * collapse. */

       
protected int direction;
       
/** True if the selection is reset, false means only the lead path
         * changes. */

       
private boolean changeSelection;

       
public TreeTraverseAction(int direction, String name) {
           
this(direction, name, true);
       
}

       
private TreeTraverseAction(int direction, String name,
                                   
boolean changeSelection) {
           
this.direction = direction;
           
this.changeSelection = changeSelection;
       
}

       
public void actionPerformed(ActionEvent e) {
           
if (tree != null) {
                SHARED_ACTION
.traverse(tree, BasicTreeUI.this, direction,
                                       changeSelection
);
           
}
       
}

       
public boolean isEnabled() { return (tree != null &&
                                             tree
.isEnabled()); }
   
} // BasicTreeUI.TreeTraverseAction


   
/** TreePageAction handles page up and page down events.
      */

   
public class TreePageAction extends AbstractAction {
       
/** Specifies the direction to adjust the selection by. */
       
protected int         direction;
       
/** True indicates should set selection from anchor path. */
       
private boolean       addToSelection;
       
private boolean       changeSelection;

       
public TreePageAction(int direction, String name) {
           
this(direction, name, false, true);
       
}

       
private TreePageAction(int direction, String name,
                               
boolean addToSelection,
                               
boolean changeSelection) {
           
this.direction = direction;
           
this.addToSelection = addToSelection;
           
this.changeSelection = changeSelection;
       
}

       
public void actionPerformed(ActionEvent e) {
           
if (tree != null) {
                SHARED_ACTION
.page(tree, BasicTreeUI.this, direction,
                                   addToSelection
, changeSelection);
           
}
       
}

       
public boolean isEnabled() { return (tree != null &&
                                             tree
.isEnabled()); }

   
} // BasicTreeUI.TreePageAction


   
/** TreeIncrementAction is used to handle up/down actions.  Selection
      * is moved up or down based on direction.
      */

   
public class TreeIncrementAction extends AbstractAction  {
       
/** Specifies the direction to adjust the selection by. */
       
protected int         direction;
       
/** If true the new item is added to the selection, if false the
         * selection is reset. */

       
private boolean       addToSelection;
       
private boolean       changeSelection;

       
public TreeIncrementAction(int direction, String name) {
           
this(direction, name, false, true);
       
}

       
private TreeIncrementAction(int direction, String name,
                                   
boolean addToSelection,
                                   
boolean changeSelection) {
           
this.direction = direction;
           
this.addToSelection = addToSelection;
           
this.changeSelection = changeSelection;
       
}

       
public void actionPerformed(ActionEvent e) {
           
if (tree != null) {
                SHARED_ACTION
.increment(tree, BasicTreeUI.this, direction,
                                        addToSelection
, changeSelection);
           
}
       
}

       
public boolean isEnabled() { return (tree != null &&
                                             tree
.isEnabled()); }

   
} // End of class BasicTreeUI.TreeIncrementAction

   
/**
      * TreeHomeAction is used to handle end/home actions.
      * Scrolls either the first or last cell to be visible based on
      * direction.
      */

   
public class TreeHomeAction extends AbstractAction {
       
protected int            direction;
       
/** Set to true if append to selection. */
       
private boolean          addToSelection;
       
private boolean          changeSelection;

       
public TreeHomeAction(int direction, String name) {
           
this(direction, name, false, true);
       
}

       
private TreeHomeAction(int direction, String name,
                               
boolean addToSelection,
                               
boolean changeSelection) {
           
this.direction = direction;
           
this.changeSelection = changeSelection;
           
this.addToSelection = addToSelection;
       
}

       
public void actionPerformed(ActionEvent e) {
           
if (tree != null) {
                SHARED_ACTION
.home(tree, BasicTreeUI.this, direction,
                                   addToSelection
, changeSelection);
           
}
       
}

       
public boolean isEnabled() { return (tree != null &&
                                             tree
.isEnabled()); }

   
} // End of class BasicTreeUI.TreeHomeAction


   
/**
      * For the first selected row expandedness will be toggled.
      */

   
public class TreeToggleAction extends AbstractAction {
       
public TreeToggleAction(String name) {
       
}

       
public void actionPerformed(ActionEvent e) {
           
if(tree != null) {
                SHARED_ACTION
.toggle(tree, BasicTreeUI.this);
           
}
       
}

       
public boolean isEnabled() { return (tree != null &&
                                             tree
.isEnabled()); }

   
} // End of class BasicTreeUI.TreeToggleAction


   
/**
     * ActionListener that invokes cancelEditing when action performed.
     */

   
public class TreeCancelEditingAction extends AbstractAction {
       
public TreeCancelEditingAction(String name) {
       
}

       
public void actionPerformed(ActionEvent e) {
           
if(tree != null) {
                SHARED_ACTION
.cancelEditing(tree, BasicTreeUI.this);
           
}
       
}

       
public boolean isEnabled() { return (tree != null &&
                                             tree
.isEnabled() &&
                                             isEditing
(tree)); }
   
} // End of class BasicTreeUI.TreeCancelEditingAction


   
/**
      * MouseInputHandler handles passing all mouse events,
      * including mouse motion events, until the mouse is released to
      * the destination it is constructed with. It is assumed all the
      * events are currently target at source.
      */

   
public class MouseInputHandler extends Object implements
                     
MouseInputListener
   
{
       
/** Source that events are coming from. */
       
protected Component        source;
       
/** Destination that receives all events. */
       
protected Component        destination;
       
private Component          focusComponent;
       
private boolean            dispatchedEvent;

       
public MouseInputHandler(Component source, Component destination,
                                     
MouseEvent event){
           
this(source, destination, event, null);
       
}

       
MouseInputHandler(Component source, Component destination,
                         
MouseEvent event, Component focusComponent) {
           
this.source = source;
           
this.destination = destination;
           
this.source.addMouseListener(this);
           
this.source.addMouseMotionListener(this);

           
SwingUtilities2.setSkipClickCount(destination,
                                             
event.getClickCount() - 1);

           
/* Dispatch the editing event! */
            destination
.dispatchEvent(SwingUtilities.convertMouseEvent
                                         
(source, event, destination));
           
this.focusComponent = focusComponent;
       
}

       
public void mouseClicked(MouseEvent e) {
           
if(destination != null) {
                dispatchedEvent
= true;
                destination
.dispatchEvent(SwingUtilities.convertMouseEvent
                                         
(source, e, destination));
           
}
       
}

       
public void mousePressed(MouseEvent e) {
       
}

       
public void mouseReleased(MouseEvent e) {
           
if(destination != null)
                destination
.dispatchEvent(SwingUtilities.convertMouseEvent
                                         
(source, e, destination));
            removeFromSource
();
       
}

       
public void mouseEntered(MouseEvent e) {
           
if (!SwingUtilities.isLeftMouseButton(e)) {
                removeFromSource
();
           
}
       
}

       
public void mouseExited(MouseEvent e) {
           
if (!SwingUtilities.isLeftMouseButton(e)) {
                removeFromSource
();
           
}
       
}

       
public void mouseDragged(MouseEvent e) {
           
if(destination != null) {
                dispatchedEvent
= true;
                destination
.dispatchEvent(SwingUtilities.convertMouseEvent
                                         
(source, e, destination));
           
}
       
}

       
public void mouseMoved(MouseEvent e) {
            removeFromSource
();
       
}

       
protected void removeFromSource() {
           
if(source != null) {
                source
.removeMouseListener(this);
                source
.removeMouseMotionListener(this);
               
if (focusComponent != null &&
                      focusComponent
== destination && !dispatchedEvent &&
                     
(focusComponent instanceof JTextField)) {
                   
((JTextField)focusComponent).selectAll();
               
}
           
}
            source
= destination = null;
       
}

   
} // End of class BasicTreeUI.MouseInputHandler

   
private static final TransferHandler defaultTransferHandler = new TreeTransferHandler();

   
static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator {

       
private JTree tree;

       
/**
         * Create a Transferable to use as the source for a data transfer.
         *
         * @param c  The component holding the data to be transfered.  This
         *  argument is provided to enable sharing of TransferHandlers by
         *  multiple components.
         * @return  The representation of the data to be transfered.
         *
         */

       
protected Transferable createTransferable(JComponent c) {
           
if (c instanceof JTree) {
                tree
= (JTree) c;
               
TreePath[] paths = tree.getSelectionPaths();

               
if (paths == null || paths.length == 0) {
                   
return null;
               
}

               
StringBuffer plainBuf = new StringBuffer();
               
StringBuffer htmlBuf = new StringBuffer();

                htmlBuf
.append("<html>\n<body>\n<ul>\n");

               
TreeModel model = tree.getModel();
               
TreePath lastPath = null;
               
TreePath[] displayPaths = getDisplayOrderPaths(paths);

               
for (int i = 0; i < displayPaths.length; i++) {
                   
TreePath path = displayPaths[i];

                   
Object node = path.getLastPathComponent();
                   
boolean leaf = model.isLeaf(node);
                   
String label = getDisplayString(path, true, leaf);

                    plainBuf
.append(label + "\n");
                    htmlBuf
.append("  <li>" + label + "\n");
               
}

               
// remove the last newline
                plainBuf
.deleteCharAt(plainBuf.length() - 1);
                htmlBuf
.append("</ul>\n</body>\n</html>");

                tree
= null;

               
return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
           
}

           
return null;
       
}

       
public int compare(Object o1, Object o2) {
           
int row1 = tree.getRowForPath((TreePath)o1);
           
int row2 = tree.getRowForPath((TreePath)o2);
           
return row1 - row2;
       
}

       
String getDisplayString(TreePath path, boolean selected, boolean leaf) {
           
int row = tree.getRowForPath(path);
           
boolean hasFocus = tree.getLeadSelectionRow() == row;
           
Object node = path.getLastPathComponent();
           
return tree.convertValueToText(node, selected, tree.isExpanded(row),
                                           leaf
, row, hasFocus);
       
}

       
/**
         * Selection paths are in selection order.  The conversion to
         * HTML requires display order.  This method resorts the paths
         * to be in the display order.
         */

       
TreePath[] getDisplayOrderPaths(TreePath[] paths) {
           
// sort the paths to display order rather than selection order
           
ArrayList selOrder = new ArrayList();
           
for (int i = 0; i < paths.length; i++) {
                selOrder
.add(paths[i]);
           
}
           
Collections.sort(selOrder, this);
           
int n = selOrder.size();
           
TreePath[] displayPaths = new TreePath[n];
           
for (int i = 0; i < n; i++) {
                displayPaths
[i] = (TreePath) selOrder.get(i);
           
}
           
return displayPaths;
       
}

       
public int getSourceActions(JComponent c) {
           
return COPY;
       
}

   
}


   
private class Handler implements CellEditorListener, FocusListener,
                 
KeyListener, MouseListener, MouseMotionListener,
                 
PropertyChangeListener, TreeExpansionListener,
                 
TreeModelListener, TreeSelectionListener,
                 
BeforeDrag {
       
//
       
// KeyListener
       
//
       
private String prefix = "";
       
private String typedString = "";
       
private long lastTime = 0L;

       
/**
         * Invoked when a key has been typed.
         *
         * Moves the keyboard focus to the first element whose prefix matches the
         * sequence of alphanumeric keys pressed by the user with delay less
         * than value of <code>timeFactor</code> property (or 1000 milliseconds
         * if it is not defined). Subsequent same key presses move the keyboard
         * focus to the next object that starts with the same letter until another
         * key is pressed, then it is treated as the prefix with appropriate number
         * of the same letters followed by first typed another letter.
         */

       
public void keyTyped(KeyEvent e) {
           
// handle first letter navigation
           
if(tree != null && tree.getRowCount()>0 && tree.hasFocus() &&
               tree
.isEnabled()) {
               
if (e.isAltDown() || e.isControlDown() || e.isMetaDown() ||
                    isNavigationKey
(e)) {
                   
return;
               
}
               
boolean startingFromSelection = true;

               
char c = e.getKeyChar();

               
long time = e.getWhen();
               
int startingRow = tree.getLeadSelectionRow();
               
if (time - lastTime < timeFactor) {
                    typedString
+= c;
                   
if((prefix.length() == 1) && (c == prefix.charAt(0))) {
                       
// Subsequent same key presses move the keyboard focus to the next
                       
// object that starts with the same letter.
                        startingRow
++;
                   
} else {
                        prefix
= typedString;
                   
}
               
} else {
                    startingRow
++;
                    typedString
= "" + c;
                    prefix
= typedString;
               
}
                lastTime
= time;

               
if (startingRow < 0 || startingRow >= tree.getRowCount()) {
                    startingFromSelection
= false;
                    startingRow
= 0;
               
}
               
TreePath path = tree.getNextMatch(prefix, startingRow,
                                                 
Position.Bias.Forward);
               
if (path != null) {
                    tree
.setSelectionPath(path);
                   
int row = getRowForPath(tree, path);
                    ensureRowsAreVisible
(row, row);
               
} else if (startingFromSelection) {
                    path
= tree.getNextMatch(prefix, 0,
                                             
Position.Bias.Forward);
                   
if (path != null) {
                        tree
.setSelectionPath(path);
                       
int row = getRowForPath(tree, path);
                        ensureRowsAreVisible
(row, row);
                   
}
               
}
           
}
       
}

       
/**
         * Invoked when a key has been pressed.
         *
         * Checks to see if the key event is a navigation key to prevent
         * dispatching these keys for the first letter navigation.
         */

       
public void keyPressed(KeyEvent e) {
           
if ( isNavigationKey(e) ) {
                prefix
= "";
                typedString
= "";
                lastTime
= 0L;
           
}
       
}

       
public void keyReleased(KeyEvent e) {
       
}

       
/**
         * Returns whether or not the supplied key event maps to a key that is used for
         * navigation.  This is used for optimizing key input by only passing non-
         * navigation keys to the first letter navigation mechanism.
         */

       
private boolean isNavigationKey(KeyEvent event) {
           
InputMap inputMap = tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
           
KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);

           
if (inputMap != null && inputMap.get(key) != null) {
               
return true;
           
}
           
return false;
       
}


       
//
       
// PropertyChangeListener
       
//
       
public void propertyChange(PropertyChangeEvent event) {
           
if (event.getSource() == treeSelectionModel) {
                treeSelectionModel
.resetRowSelection();
           
}
           
else if(event.getSource() == tree) {
               
String              changeName = event.getPropertyName();

               
if (changeName == JTree.LEAD_SELECTION_PATH_PROPERTY) {
                   
if (!ignoreLAChange) {
                        updateLeadRow
();
                        repaintPath
((TreePath)event.getOldValue());
                        repaintPath
((TreePath)event.getNewValue());
                   
}
               
}
               
else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) {
                   
if (!ignoreLAChange) {
                        repaintPath
((TreePath)event.getOldValue());
                        repaintPath
((TreePath)event.getNewValue());
                   
}
               
}
               
if(changeName == JTree.CELL_RENDERER_PROPERTY) {
                    setCellRenderer
((TreeCellRenderer)event.getNewValue());
                    redoTheLayout
();
               
}
               
else if(changeName == JTree.TREE_MODEL_PROPERTY) {
                    setModel
((TreeModel)event.getNewValue());
               
}
               
else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) {
                    setRootVisible
(((Boolean)event.getNewValue()).
                                   booleanValue
());
               
}
               
else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) {
                    setShowsRootHandles
(((Boolean)event.getNewValue()).
                                        booleanValue
());
               
}
               
else if(changeName == JTree.ROW_HEIGHT_PROPERTY) {
                    setRowHeight
(((Integer)event.getNewValue()).
                                 intValue
());
               
}
               
else if(changeName == JTree.CELL_EDITOR_PROPERTY) {
                    setCellEditor
((TreeCellEditor)event.getNewValue());
               
}
               
else if(changeName == JTree.EDITABLE_PROPERTY) {
                    setEditable
(((Boolean)event.getNewValue()).booleanValue());
               
}
               
else if(changeName == JTree.LARGE_MODEL_PROPERTY) {
                    setLargeModel
(tree.isLargeModel());
               
}
               
else if(changeName == JTree.SELECTION_MODEL_PROPERTY) {
                    setSelectionModel
(tree.getSelectionModel());
               
}
               
else if(changeName == "font") {
                    completeEditing
();
                   
if(treeState != null)
                        treeState
.invalidateSizes();
                    updateSize
();
               
}
               
else if (changeName == "componentOrientation") {
                   
if (tree != null) {
                        leftToRight
= BasicGraphicsUtils.isLeftToRight(tree);
                        redoTheLayout
();
                        tree
.treeDidChange();

                       
InputMap km = getInputMap(JComponent.WHEN_FOCUSED);
                       
SwingUtilities.replaceUIInputMap(tree,
                                               
JComponent.WHEN_FOCUSED, km);
                   
}
               
} else if ("dropLocation" == changeName) {
                   
JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue();
                    repaintDropLocation
(oldValue);
                    repaintDropLocation
(tree.getDropLocation());
               
}
           
}
       
}

       
private void repaintDropLocation(JTree.DropLocation loc) {
           
if (loc == null) {
               
return;
           
}

           
Rectangle r;

           
if (isDropLine(loc)) {
                r
= getDropLineRect(loc);
           
} else {
                r
= tree.getPathBounds(loc.getPath());
           
}

           
if (r != null) {
                tree
.repaint(r);
           
}
       
}

       
//
       
// MouseListener
       
//

       
// Whether or not the mouse press (which is being considered as part
       
// of a drag sequence) also caused the selection change to be fully
       
// processed.
       
private boolean dragPressDidSelection;

       
// Set to true when a drag gesture has been fully recognized and DnD
       
// begins. Use this to ignore further mouse events which could be
       
// delivered if DnD is cancelled (via ESCAPE for example)
       
private boolean dragStarted;

       
// The path over which the press occurred and the press event itself
       
private TreePath pressedPath;
       
private MouseEvent pressedEvent;

       
// Used to detect whether the press event causes a selection change.
       
// If it does, we won't try to start editing on the release.
       
private boolean valueChangedOnPress;

       
private boolean isActualPath(TreePath path, int x, int y) {
           
if (path == null) {
               
return false;
           
}

           
Rectangle bounds = getPathBounds(tree, path);
           
if (y > (bounds.y + bounds.height)) {
               
return false;
           
}

           
return (x >= bounds.x) && (x <= (bounds.x + bounds.width));
       
}

       
public void mouseClicked(MouseEvent e) {
       
}

       
public void mouseEntered(MouseEvent e) {
       
}

       
public void mouseExited(MouseEvent e) {
       
}

       
/**
         * Invoked when a mouse button has been pressed on a component.
         */

       
public void mousePressed(MouseEvent e) {
           
if (SwingUtilities2.shouldIgnore(e, tree)) {
               
return;
           
}

           
// if we can't stop any ongoing editing, do nothing
           
if (isEditing(tree) && tree.getInvokesStopCellEditing()
                               
&& !stopEditing(tree)) {
               
return;
           
}

            completeEditing
();

            pressedPath
= getClosestPathForLocation(tree, e.getX(), e.getY());

           
if (tree.getDragEnabled()) {
                mousePressedDND
(e);
           
} else {
               
SwingUtilities2.adjustFocus(tree);
                handleSelection
(e);
           
}
       
}

       
private void mousePressedDND(MouseEvent e) {
            pressedEvent
= e;
           
boolean grabFocus = true;
            dragStarted
= false;
            valueChangedOnPress
= false;

           
// if we have a valid path and this is a drag initiating event
           
if (isActualPath(pressedPath, e.getX(), e.getY()) &&
                   
DragRecognitionSupport.mousePressed(e)) {

                dragPressDidSelection
= false;

               
if (e.isControlDown()) {
                   
// do nothing for control - will be handled on release
                   
// or when drag starts
                   
return;
               
} else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) {
                   
// clicking on something that's already selected
                   
// and need to make it the lead now
                    setAnchorSelectionPath
(pressedPath);
                    setLeadSelectionPath
(pressedPath, true);
                   
return;
               
}

                dragPressDidSelection
= true;

               
// could be a drag initiating event - don't grab focus
                grabFocus
= false;
           
}

           
if (grabFocus) {
               
SwingUtilities2.adjustFocus(tree);
           
}

            handleSelection
(e);
       
}

       
void handleSelection(MouseEvent e) {
           
if(pressedPath != null) {
               
Rectangle bounds = getPathBounds(tree, pressedPath);

               
if(e.getY() >= (bounds.y + bounds.height)) {
                   
return;
               
}

               
// Preferably checkForClickInExpandControl could take
               
// the Event to do this it self!
               
if(SwingUtilities.isLeftMouseButton(e)) {
                    checkForClickInExpandControl
(pressedPath, e.getX(), e.getY());
               
}

               
int x = e.getX();

               
// Perhaps they clicked the cell itself. If so,
               
// select it.
               
if (x >= bounds.x && x < (bounds.x + bounds.width)) {
                   
if (tree.getDragEnabled() || !startEditing(pressedPath, e)) {
                        selectPathForEvent
(pressedPath, e);
                   
}
               
}
           
}
       
}

       
public void dragStarting(MouseEvent me) {
            dragStarted
= true;

           
if (me.isControlDown()) {
                tree
.addSelectionPath(pressedPath);
                setAnchorSelectionPath
(pressedPath);
                setLeadSelectionPath
(pressedPath, true);
           
}

            pressedEvent
= null;
            pressedPath
= null;
       
}

       
public void mouseDragged(MouseEvent e) {
           
if (SwingUtilities2.shouldIgnore(e, tree)) {
               
return;
           
}

           
if (tree.getDragEnabled()) {
               
DragRecognitionSupport.mouseDragged(e, this);
           
}
       
}

       
/**
         * Invoked when the mouse button has been moved on a component
         * (with no buttons no down).
         */

       
public void mouseMoved(MouseEvent e) {
       
}

       
public void mouseReleased(MouseEvent e) {
           
if (SwingUtilities2.shouldIgnore(e, tree)) {
               
return;
           
}

           
if (tree.getDragEnabled()) {
                mouseReleasedDND
(e);
           
}

            pressedEvent
= null;
            pressedPath
= null;
       
}

       
private void mouseReleasedDND(MouseEvent e) {
           
MouseEvent me = DragRecognitionSupport.mouseReleased(e);
           
if (me != null) {
               
SwingUtilities2.adjustFocus(tree);
               
if (!dragPressDidSelection) {
                    handleSelection
(me);
               
}
           
}

           
if (!dragStarted) {

               
// Note: We don't give the tree a chance to start editing if the
               
// mouse press caused a selection change. Otherwise the default
               
// tree cell editor will start editing on EVERY press and
               
// release. If it turns out that this affects some editors, we
               
// can always parameterize this with a client property. ex:
               
//
               
// if (pressedPath != null &&
               
//         (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") ||
               
//          !valueChangedOnPress) && ...
               
if (pressedPath != null && !valueChangedOnPress &&
                        isActualPath
(pressedPath, pressedEvent.getX(), pressedEvent.getY())) {

                    startEditingOnRelease
(pressedPath, pressedEvent, e);
               
}
           
}
       
}

       
//
       
// FocusListener
       
//
       
public void focusGained(FocusEvent e) {
           
if(tree != null) {
               
Rectangle                 pBounds;

                pBounds
= getPathBounds(tree, tree.getLeadSelectionPath());
               
if(pBounds != null)
                    tree
.repaint(getRepaintPathBounds(pBounds));
                pBounds
= getPathBounds(tree, getLeadSelectionPath());
               
if(pBounds != null)
                    tree
.repaint(getRepaintPathBounds(pBounds));
           
}
       
}

       
public void focusLost(FocusEvent e) {
            focusGained
(e);
       
}

       
//
       
// CellEditorListener
       
//
       
public void editingStopped(ChangeEvent e) {
            completeEditing
(false, false, true);
       
}

       
/** Messaged when editing has been canceled in the tree. */
       
public void editingCanceled(ChangeEvent e) {
            completeEditing
(false, false, false);
       
}


       
//
       
// TreeSelectionListener
       
//
       
public void valueChanged(TreeSelectionEvent event) {
            valueChangedOnPress
= true;

           
// Stop editing
            completeEditing
();
           
// Make sure all the paths are visible, if necessary.
           
// PENDING: This should be tweaked when isAdjusting is added
           
if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) {
               
TreePath[]           paths = treeSelectionModel
                                         
.getSelectionPaths();

               
if(paths != null) {
                   
for(int counter = paths.length - 1; counter >= 0;
                        counter
--) {
                       
TreePath path = paths[counter].getParentPath();
                       
boolean expand = true;

                       
while (path != null) {
                           
// Indicates this path isn't valid anymore,
                           
// we shouldn't attempt to expand it then.
                           
if (treeModel.isLeaf(path.getLastPathComponent())){
                                expand
= false;
                                path
= null;
                           
}
                           
else {
                                path
= path.getParentPath();
                           
}
                       
}
                       
if (expand) {
                            tree
.makeVisible(paths[counter]);
                       
}
                   
}
               
}
           
}

           
TreePath oldLead = getLeadSelectionPath();
            lastSelectedRow
= tree.getMinSelectionRow();
           
TreePath lead = tree.getSelectionModel().getLeadSelectionPath();
            setAnchorSelectionPath
(lead);
            setLeadSelectionPath
(lead);

           
TreePath[]       changedPaths = event.getPaths();
           
Rectangle        nodeBounds;
           
Rectangle        visRect = tree.getVisibleRect();
           
boolean          paintPaths = true;
           
int              nWidth = tree.getWidth();

           
if(changedPaths != null) {
               
int              counter, maxCounter = changedPaths.length;

               
if(maxCounter > 4) {
                    tree
.repaint();
                    paintPaths
= false;
               
}
               
else {
                   
for (counter = 0; counter < maxCounter; counter++) {
                        nodeBounds
= getPathBounds(tree,
                                                   changedPaths
[counter]);
                       
if(nodeBounds != null &&
                           visRect
.intersects(nodeBounds))
                            tree
.repaint(0, nodeBounds.y, nWidth,
                                         nodeBounds
.height);
                   
}
               
}
           
}
           
if(paintPaths) {
                nodeBounds
= getPathBounds(tree, oldLead);
               
if(nodeBounds != null && visRect.intersects(nodeBounds))
                    tree
.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
                nodeBounds
= getPathBounds(tree, lead);
               
if(nodeBounds != null && visRect.intersects(nodeBounds))
                    tree
.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
           
}
       
}


       
//
       
// TreeExpansionListener
       
//
       
public void treeExpanded(TreeExpansionEvent event) {
           
if(event != null && tree != null) {
               
TreePath      path = event.getPath();

                updateExpandedDescendants
(path);
           
}
       
}

       
public void treeCollapsed(TreeExpansionEvent event) {
           
if(event != null && tree != null) {
               
TreePath        path = event.getPath();

                completeEditing
();
               
if(path != null && tree.isVisible(path)) {
                    treeState
.setExpandedState(path, false);
                    updateLeadRow
();
                    updateSize
();
               
}
           
}
       
}

       
//
       
// TreeModelListener
       
//
       
public void treeNodesChanged(TreeModelEvent e) {
           
if(treeState != null && e != null) {
               
TreePath parentPath = e.getTreePath();
               
int[] indices = e.getChildIndices();
               
if (indices == null || indices.length == 0) {
                   
// The root has changed
                    treeState
.treeNodesChanged(e);
                    updateSize
();
               
}
               
else if (treeState.isExpanded(parentPath)) {
                   
// Changed nodes are visible
                   
// Find the minimum index, we only need paint from there
                   
// down.
                   
int minIndex = indices[0];
                   
for (int i = indices.length - 1; i > 0; i--) {
                        minIndex
= Math.min(indices[i], minIndex);
                   
}
                   
Object minChild = treeModel.getChild(
                            parentPath
.getLastPathComponent(), minIndex);
                   
TreePath minPath = parentPath.pathByAddingChild(minChild);
                   
Rectangle minBounds = getPathBounds(tree, minPath);

                   
// Forward to the treestate
                    treeState
.treeNodesChanged(e);

                   
// Mark preferred size as bogus.
                    updateSize0
();

                   
// And repaint
                   
Rectangle newMinBounds = getPathBounds(tree, minPath);
                   
if (indices.length == 1 &&
                            newMinBounds
.height == minBounds.height) {
                        tree
.repaint(0, minBounds.y, tree.getWidth(),
                                     minBounds
.height);
                   
}
                   
else {
                        tree
.repaint(0, minBounds.y, tree.getWidth(),
                                     tree
.getHeight() - minBounds.y);
                   
}
               
}
               
else {
                   
// Nodes that changed aren't visible.  No need to paint
                    treeState
.treeNodesChanged(e);
               
}
           
}
       
}

       
public void treeNodesInserted(TreeModelEvent e) {
           
if(treeState != null && e != null) {
                treeState
.treeNodesInserted(e);

                updateLeadRow
();

               
TreePath       path = e.getTreePath();

               
if(treeState.isExpanded(path)) {
                    updateSize
();
               
}
               
else {
                   
// PENDING(sky): Need a method in TreeModelEvent
                   
// that can return the count, getChildIndices allocs
                   
// a new array!
                   
int[]      indices = e.getChildIndices();
                   
int        childCount = treeModel.getChildCount
                                           
(path.getLastPathComponent());

                   
if(indices != null && (childCount - indices.length) == 0)
                        updateSize
();
               
}
           
}
       
}

       
public void treeNodesRemoved(TreeModelEvent e) {
           
if(treeState != null && e != null) {
                treeState
.treeNodesRemoved(e);

                updateLeadRow
();

               
TreePath       path = e.getTreePath();

               
if(treeState.isExpanded(path) ||
                   treeModel
.getChildCount(path.getLastPathComponent()) == 0)
                    updateSize
();
           
}
       
}

       
public void treeStructureChanged(TreeModelEvent e) {
           
if(treeState != null && e != null) {
                treeState
.treeStructureChanged(e);

                updateLeadRow
();

               
TreePath       pPath = e.getTreePath();

               
if (pPath != null) {
                    pPath
= pPath.getParentPath();
               
}
               
if(pPath == null || treeState.isExpanded(pPath))
                    updateSize
();
           
}
       
}
   
}



   
private static class Actions extends UIAction {
       
private static final String SELECT_PREVIOUS = "selectPrevious";
       
private static final String SELECT_PREVIOUS_CHANGE_LEAD =
                             
"selectPreviousChangeLead";
       
private static final String SELECT_PREVIOUS_EXTEND_SELECTION =
                             
"selectPreviousExtendSelection";
       
private static final String SELECT_NEXT = "selectNext";
       
private static final String SELECT_NEXT_CHANGE_LEAD =
                                   
"selectNextChangeLead";
       
private static final String SELECT_NEXT_EXTEND_SELECTION =
                                   
"selectNextExtendSelection";
       
private static final String SELECT_CHILD = "selectChild";
       
private static final String SELECT_CHILD_CHANGE_LEAD =
                                   
"selectChildChangeLead";
       
private static final String SELECT_PARENT = "selectParent";
       
private static final String SELECT_PARENT_CHANGE_LEAD =
                                   
"selectParentChangeLead";
       
private static final String SCROLL_UP_CHANGE_SELECTION =
                                   
"scrollUpChangeSelection";
       
private static final String SCROLL_UP_CHANGE_LEAD =
                                   
"scrollUpChangeLead";
       
private static final String SCROLL_UP_EXTEND_SELECTION =
                                   
"scrollUpExtendSelection";
       
private static final String SCROLL_DOWN_CHANGE_SELECTION =
                                   
"scrollDownChangeSelection";
       
private static final String SCROLL_DOWN_EXTEND_SELECTION =
                                   
"scrollDownExtendSelection";
       
private static final String SCROLL_DOWN_CHANGE_LEAD =
                                   
"scrollDownChangeLead";
       
private static final String SELECT_FIRST = "selectFirst";
       
private static final String SELECT_FIRST_CHANGE_LEAD =
                                   
"selectFirstChangeLead";
       
private static final String SELECT_FIRST_EXTEND_SELECTION =
                                   
"selectFirstExtendSelection";
       
private static final String SELECT_LAST = "selectLast";
       
private static final String SELECT_LAST_CHANGE_LEAD =
                                   
"selectLastChangeLead";
       
private static final String SELECT_LAST_EXTEND_SELECTION =
                                   
"selectLastExtendSelection";
       
private static final String TOGGLE = "toggle";
       
private static final String CANCEL_EDITING = "cancel";
       
private static final String START_EDITING = "startEditing";
       
private static final String SELECT_ALL = "selectAll";
       
private static final String CLEAR_SELECTION = "clearSelection";
       
private static final String SCROLL_LEFT = "scrollLeft";
       
private static final String SCROLL_RIGHT = "scrollRight";
       
private static final String SCROLL_LEFT_EXTEND_SELECTION =
                                   
"scrollLeftExtendSelection";
       
private static final String SCROLL_RIGHT_EXTEND_SELECTION =
                                   
"scrollRightExtendSelection";
       
private static final String SCROLL_RIGHT_CHANGE_LEAD =
                                   
"scrollRightChangeLead";
       
private static final String SCROLL_LEFT_CHANGE_LEAD =
                                   
"scrollLeftChangeLead";
       
private static final String EXPAND = "expand";
       
private static final String COLLAPSE = "collapse";
       
private static final String MOVE_SELECTION_TO_PARENT =
                                   
"moveSelectionToParent";

       
// add the lead item to the selection without changing lead or anchor
       
private static final String ADD_TO_SELECTION = "addToSelection";

       
// toggle the selected state of the lead item and move the anchor to it
       
private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";

       
// extend the selection to the lead item
       
private static final String EXTEND_TO = "extendTo";

       
// move the anchor to the lead and ensure only that item is selected
       
private static final String MOVE_SELECTION_TO = "moveSelectionTo";

       
Actions() {
           
super(null);
       
}

       
Actions(String key) {
           
super(key);
       
}

       
public boolean isEnabled(Object o) {
           
if (o instanceof JTree) {
               
if (getName() == CANCEL_EDITING) {
                   
return ((JTree)o).isEditing();
               
}
           
}
           
return true;
       
}

       
public void actionPerformed(ActionEvent e) {
           
JTree tree = (JTree)e.getSource();
           
BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType(
                             tree
.getUI(), BasicTreeUI.class);
           
if (ui == null) {
               
return;
           
}
           
String key = getName();
           
if (key == SELECT_PREVIOUS) {
                increment
(tree, ui, -1, false, true);
           
}
           
else if (key == SELECT_PREVIOUS_CHANGE_LEAD) {
                increment
(tree, ui, -1, false, false);
           
}
           
else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) {
                increment
(tree, ui, -1, true, true);
           
}
           
else if (key == SELECT_NEXT) {
                increment
(tree, ui, 1, false, true);
           
}
           
else if (key == SELECT_NEXT_CHANGE_LEAD) {
                increment
(tree, ui, 1, false, false);
           
}
           
else if (key == SELECT_NEXT_EXTEND_SELECTION) {
                increment
(tree, ui, 1, true, true);
           
}
           
else if (key == SELECT_CHILD) {
                traverse
(tree, ui, 1, true);
           
}
           
else if (key == SELECT_CHILD_CHANGE_LEAD) {
                traverse
(tree, ui, 1, false);
           
}
           
else if (key == SELECT_PARENT) {
                traverse
(tree, ui, -1, true);
           
}
           
else if (key == SELECT_PARENT_CHANGE_LEAD) {
                traverse
(tree, ui, -1, false);
           
}
           
else if (key == SCROLL_UP_CHANGE_SELECTION) {
                page
(tree, ui, -1, false, true);
           
}
           
else if (key == SCROLL_UP_CHANGE_LEAD) {
                page
(tree, ui, -1, false, false);
           
}
           
else if (key == SCROLL_UP_EXTEND_SELECTION) {
                page
(tree, ui, -1, true, true);
           
}
           
else if (key == SCROLL_DOWN_CHANGE_SELECTION) {
                page
(tree, ui, 1, false, true);
           
}
           
else if (key == SCROLL_DOWN_EXTEND_SELECTION) {
                page
(tree, ui, 1, true, true);
           
}
           
else if (key == SCROLL_DOWN_CHANGE_LEAD) {
                page
(tree, ui, 1, false, false);
           
}
           
else if (key == SELECT_FIRST) {
                home
(tree, ui, -1, false, true);
           
}
           
else if (key == SELECT_FIRST_CHANGE_LEAD) {
                home
(tree, ui, -1, false, false);
           
}
           
else if (key == SELECT_FIRST_EXTEND_SELECTION) {
                home
(tree, ui, -1, true, true);
           
}
           
else if (key == SELECT_LAST) {
                home
(tree, ui, 1, false, true);
           
}
           
else if (key == SELECT_LAST_CHANGE_LEAD) {
                home
(tree, ui, 1, false, false);
           
}
           
else if (key == SELECT_LAST_EXTEND_SELECTION) {
                home
(tree, ui, 1, true, true);
           
}
           
else if (key == TOGGLE) {
                toggle
(tree, ui);
           
}
           
else if (key == CANCEL_EDITING) {
                cancelEditing
(tree, ui);
           
}
           
else if (key == START_EDITING) {
                startEditing
(tree, ui);
           
}
           
else if (key == SELECT_ALL) {
                selectAll
(tree, ui, true);
           
}
           
else if (key == CLEAR_SELECTION) {
                selectAll
(tree, ui, false);
           
}
           
else if (key == ADD_TO_SELECTION) {
               
if (ui.getRowCount(tree) > 0) {
                   
int lead = ui.getLeadSelectionRow();
                   
if (!tree.isRowSelected(lead)) {
                       
TreePath aPath = ui.getAnchorSelectionPath();
                        tree
.addSelectionRow(lead);
                        ui
.setAnchorSelectionPath(aPath);
                   
}
               
}
           
}
           
else if (key == TOGGLE_AND_ANCHOR) {
               
if (ui.getRowCount(tree) > 0) {
                   
int lead = ui.getLeadSelectionRow();
                   
TreePath lPath = ui.getLeadSelectionPath();
                   
if (!tree.isRowSelected(lead)) {
                        tree
.addSelectionRow(lead);
                   
} else {
                        tree
.removeSelectionRow(lead);
                        ui
.setLeadSelectionPath(lPath);
                   
}
                    ui
.setAnchorSelectionPath(lPath);
               
}
           
}
           
else if (key == EXTEND_TO) {
                extendSelection
(tree, ui);
           
}
           
else if (key == MOVE_SELECTION_TO) {
               
if (ui.getRowCount(tree) > 0) {
                   
int lead = ui.getLeadSelectionRow();
                    tree
.setSelectionInterval(lead, lead);
               
}
           
}
           
else if (key == SCROLL_LEFT) {
                scroll
(tree, ui, SwingConstants.HORIZONTAL, -10);
           
}
           
else if (key == SCROLL_RIGHT) {
                scroll
(tree, ui, SwingConstants.HORIZONTAL, 10);
           
}
           
else if (key == SCROLL_LEFT_EXTEND_SELECTION) {
                scrollChangeSelection
(tree, ui, -1, true, true);
           
}
           
else if (key == SCROLL_RIGHT_EXTEND_SELECTION) {
                scrollChangeSelection
(tree, ui, 1, true, true);
           
}
           
else if (key == SCROLL_RIGHT_CHANGE_LEAD) {
                scrollChangeSelection
(tree, ui, 1, false, false);
           
}
           
else if (key == SCROLL_LEFT_CHANGE_LEAD) {
                scrollChangeSelection
(tree, ui, -1, false, false);
           
}
           
else if (key == EXPAND) {
                expand
(tree, ui);
           
}
           
else if (key == COLLAPSE) {
                collapse
(tree, ui);
           
}
           
else if (key == MOVE_SELECTION_TO_PARENT) {
                moveSelectionToParent
(tree, ui);
           
}
       
}

       
private void scrollChangeSelection(JTree tree, BasicTreeUI ui,
                           
int direction, boolean addToSelection,
                           
boolean changeSelection) {
           
int           rowCount;

           
if((rowCount = ui.getRowCount(tree)) > 0 &&
                ui
.treeSelectionModel != null) {
               
TreePath          newPath;
               
Rectangle         visRect = tree.getVisibleRect();

               
if (direction == -1) {
                    newPath
= ui.getClosestPathForLocation(tree, visRect.x,
                                                        visRect
.y);
                    visRect
.x = Math.max(0, visRect.x - visRect.width);
               
}
               
else {
                    visRect
.x = Math.min(Math.max(0, tree.getWidth() -
                                   visRect
.width), visRect.x + visRect.width);
                    newPath
= ui.getClosestPathForLocation(tree, visRect.x,
                                                 visRect
.y + visRect.height);
               
}
               
// Scroll
                tree
.scrollRectToVisible(visRect);
               
// select
               
if (addToSelection) {
                    ui
.extendSelection(newPath);
               
}
               
else if(changeSelection) {
                    tree
.setSelectionPath(newPath);
               
}
               
else {
                    ui
.setLeadSelectionPath(newPath, true);
               
}
           
}
       
}

       
private void scroll(JTree component, BasicTreeUI ui, int direction,
                           
int amount) {
           
Rectangle visRect = component.getVisibleRect();
           
Dimension size = component.getSize();
           
if (direction == SwingConstants.HORIZONTAL) {
                visRect
.x += amount;
                visRect
.x = Math.max(0, visRect.x);
                visRect
.x = Math.min(Math.max(0, size.width - visRect.width),
                                     visRect
.x);
           
}
           
else {
                visRect
.y += amount;
                visRect
.y = Math.max(0, visRect.y);
                visRect
.y = Math.min(Math.max(0, size.width - visRect.height),
                                     visRect
.y);
           
}
            component
.scrollRectToVisible(visRect);
       
}

       
private void extendSelection(JTree tree, BasicTreeUI ui) {
           
if (ui.getRowCount(tree) > 0) {
               
int       lead = ui.getLeadSelectionRow();

               
if (lead != -1) {
                   
TreePath      leadP = ui.getLeadSelectionPath();
                   
TreePath      aPath = ui.getAnchorSelectionPath();
                   
int           aRow = ui.getRowForPath(tree, aPath);

                   
if(aRow == -1)
                        aRow
= 0;
                    tree
.setSelectionInterval(aRow, lead);
                    ui
.setLeadSelectionPath(leadP);
                    ui
.setAnchorSelectionPath(aPath);
               
}
           
}
       
}

       
private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) {
           
int                   rowCount = ui.getRowCount(tree);

           
if(rowCount > 0) {
               
if(selectAll) {
                   
if (tree.getSelectionModel().getSelectionMode() ==
                           
TreeSelectionModel.SINGLE_TREE_SELECTION) {

                       
int lead = ui.getLeadSelectionRow();
                       
if (lead != -1) {
                            tree
.setSelectionRow(lead);
                       
} else if (tree.getMinSelectionRow() == -1) {
                            tree
.setSelectionRow(0);
                            ui
.ensureRowsAreVisible(0, 0);
                       
}
                       
return;
                   
}

                   
TreePath      lastPath = ui.getLeadSelectionPath();
                   
TreePath      aPath = ui.getAnchorSelectionPath();

                   
if(lastPath != null && !tree.isVisible(lastPath)) {
                        lastPath
= null;
                   
}
                    tree
.setSelectionInterval(0, rowCount - 1);
                   
if(lastPath != null) {
                        ui
.setLeadSelectionPath(lastPath);
                   
}
                   
if(aPath != null && tree.isVisible(aPath)) {
                        ui
.setAnchorSelectionPath(aPath);
                   
}
               
}
               
else {
                   
TreePath      lastPath = ui.getLeadSelectionPath();
                   
TreePath      aPath = ui.getAnchorSelectionPath();

                    tree
.clearSelection();
                    ui
.setAnchorSelectionPath(aPath);
                    ui
.setLeadSelectionPath(lastPath);
               
}
           
}
       
}

       
private void startEditing(JTree tree, BasicTreeUI ui) {
           
TreePath   lead = ui.getLeadSelectionPath();
           
int        editRow = (lead != null) ?
                                     ui
.getRowForPath(tree, lead) : -1;

           
if(editRow != -1) {
                tree
.startEditingAtPath(lead);
           
}
       
}

       
private void cancelEditing(JTree tree, BasicTreeUI ui) {
            tree
.cancelEditing();
       
}

       
private void toggle(JTree tree, BasicTreeUI ui) {
           
int            selRow = ui.getLeadSelectionRow();

           
if(selRow != -1 && !ui.isLeaf(selRow)) {
               
TreePath aPath = ui.getAnchorSelectionPath();
               
TreePath lPath = ui.getLeadSelectionPath();

                ui
.toggleExpandState(ui.getPathForRow(tree, selRow));
                ui
.setAnchorSelectionPath(aPath);
                ui
.setLeadSelectionPath(lPath);
           
}
       
}

       
private void expand(JTree tree, BasicTreeUI ui) {
           
int selRow = ui.getLeadSelectionRow();
            tree
.expandRow(selRow);
       
}

       
private void collapse(JTree tree, BasicTreeUI ui) {
           
int selRow = ui.getLeadSelectionRow();
            tree
.collapseRow(selRow);
       
}

       
private void increment(JTree tree, BasicTreeUI ui, int direction,
                               
boolean addToSelection,
                               
boolean changeSelection) {

           
// disable moving of lead unless in discontiguous mode
           
if (!addToSelection && !changeSelection &&
                    tree
.getSelectionModel().getSelectionMode() !=
                       
TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
                changeSelection
= true;
           
}

           
int              rowCount;

           
if(ui.treeSelectionModel != null &&
                 
(rowCount = tree.getRowCount()) > 0) {
               
int                  selIndex = ui.getLeadSelectionRow();
               
int                  newIndex;

               
if(selIndex == -1) {
                   
if(direction == 1)
                        newIndex
= 0;
                   
else
                        newIndex
= rowCount - 1;
               
}
               
else
                   
/* Aparently people don't like wrapping;( */
                    newIndex
= Math.min(rowCount - 1, Math.max
                                       
(0, (selIndex + direction)));
               
if(addToSelection && ui.treeSelectionModel.
                        getSelectionMode
() != TreeSelectionModel.
                        SINGLE_TREE_SELECTION
) {
                    ui
.extendSelection(tree.getPathForRow(newIndex));
               
}
               
else if(changeSelection) {
                    tree
.setSelectionInterval(newIndex, newIndex);
               
}
               
else {
                    ui
.setLeadSelectionPath(tree.getPathForRow(newIndex),true);
               
}
                ui
.ensureRowsAreVisible(newIndex, newIndex);
                ui
.lastSelectedRow = newIndex;
           
}
       
}

       
private void traverse(JTree tree, BasicTreeUI ui, int direction,
                             
boolean changeSelection) {

           
// disable moving of lead unless in discontiguous mode
           
if (!changeSelection &&
                    tree
.getSelectionModel().getSelectionMode() !=
                       
TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
                changeSelection
= true;
           
}

           
int                rowCount;

           
if((rowCount = tree.getRowCount()) > 0) {
               
int               minSelIndex = ui.getLeadSelectionRow();
               
int               newIndex;

               
if(minSelIndex == -1)
                    newIndex
= 0;
               
else {
                   
/* Try and expand the node, otherwise go to next
                       node. */

                   
if(direction == 1) {
                       
TreePath minSelPath = ui.getPathForRow(tree, minSelIndex);
                       
int childCount = tree.getModel().
                            getChildCount
(minSelPath.getLastPathComponent());
                        newIndex
= -1;
                       
if (!ui.isLeaf(minSelIndex)) {
                           
if (!tree.isExpanded(minSelIndex)) {
                                ui
.toggleExpandState(minSelPath);
                           
}
                           
else if (childCount > 0) {
                                newIndex
= Math.min(minSelIndex + 1, rowCount - 1);
                           
}
                       
}
                   
}
                   
/* Try to collapse node. */
                   
else {
                       
if(!ui.isLeaf(minSelIndex) &&
                           tree
.isExpanded(minSelIndex)) {
                            ui
.toggleExpandState(ui.getPathForRow
                                             
(tree, minSelIndex));
                            newIndex
= -1;
                       
}
                       
else {
                           
TreePath         path = ui.getPathForRow(tree,
                                                                  minSelIndex
);

                           
if(path != null && path.getPathCount() > 1) {
                                newIndex
= ui.getRowForPath(tree, path.
                                                         getParentPath
());
                           
}
                           
else
                                newIndex
= -1;
                       
}
                   
}
               
}
               
if(newIndex != -1) {
                   
if(changeSelection) {
                        tree
.setSelectionInterval(newIndex, newIndex);
                   
}
                   
else {
                        ui
.setLeadSelectionPath(ui.getPathForRow(
                                                    tree
, newIndex), true);
                   
}
                    ui
.ensureRowsAreVisible(newIndex, newIndex);
               
}
           
}
       
}

       
private void moveSelectionToParent(JTree tree, BasicTreeUI ui) {
           
int selRow = ui.getLeadSelectionRow();
           
TreePath path = ui.getPathForRow(tree, selRow);
           
if (path != null && path.getPathCount() > 1) {
               
int  newIndex = ui.getRowForPath(tree, path.getParentPath());
               
if (newIndex != -1) {
                    tree
.setSelectionInterval(newIndex, newIndex);
                    ui
.ensureRowsAreVisible(newIndex, newIndex);
               
}
           
}
       
}

       
private void page(JTree tree, BasicTreeUI ui, int direction,
                         
boolean addToSelection, boolean changeSelection) {

           
// disable moving of lead unless in discontiguous mode
           
if (!addToSelection && !changeSelection &&
                    tree
.getSelectionModel().getSelectionMode() !=
                       
TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
                changeSelection
= true;
           
}

           
int           rowCount;

           
if((rowCount = ui.getRowCount(tree)) > 0 &&
                           ui
.treeSelectionModel != null) {
               
Dimension         maxSize = tree.getSize();
               
TreePath          lead = ui.getLeadSelectionPath();
               
TreePath          newPath;
               
Rectangle         visRect = tree.getVisibleRect();

               
if(direction == -1) {
                   
// up.
                    newPath
= ui.getClosestPathForLocation(tree, visRect.x,
                                                         visRect
.y);
                   
if(newPath.equals(lead)) {
                        visRect
.y = Math.max(0, visRect.y - visRect.height);
                        newPath
= tree.getClosestPathForLocation(visRect.x,
                                                                 visRect
.y);
                   
}
               
}
               
else {
                   
// down
                    visRect
.y = Math.min(maxSize.height, visRect.y +
                                         visRect
.height - 1);
                    newPath
= tree.getClosestPathForLocation(visRect.x,
                                                             visRect
.y);
                   
if(newPath.equals(lead)) {
                        visRect
.y = Math.min(maxSize.height, visRect.y +
                                             visRect
.height - 1);
                        newPath
= tree.getClosestPathForLocation(visRect.x,
                                                                 visRect
.y);
                   
}
               
}
               
Rectangle            newRect = ui.getPathBounds(tree, newPath);

                newRect
.x = visRect.x;
                newRect
.width = visRect.width;
               
if(direction == -1) {
                    newRect
.height = visRect.height;
               
}
               
else {
                    newRect
.y -= (visRect.height - newRect.height);
                    newRect
.height = visRect.height;
               
}

               
if(addToSelection) {
                    ui
.extendSelection(newPath);
               
}
               
else if(changeSelection) {
                    tree
.setSelectionPath(newPath);
               
}
               
else {
                    ui
.setLeadSelectionPath(newPath, true);
               
}
                tree
.scrollRectToVisible(newRect);
           
}
       
}

       
private void home(JTree tree, BasicTreeUI ui, int direction,
                         
boolean addToSelection, boolean changeSelection) {

           
// disable moving of lead unless in discontiguous mode
           
if (!addToSelection && !changeSelection &&
                    tree
.getSelectionModel().getSelectionMode() !=
                       
TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
                changeSelection
= true;
           
}

           
int rowCount = ui.getRowCount(tree);

           
if (rowCount > 0) {
               
if(direction == -1) {
                    ui
.ensureRowsAreVisible(0, 0);
                   
if (addToSelection) {
                       
TreePath        aPath = ui.getAnchorSelectionPath();
                       
int             aRow = (aPath == null) ? -1 :
                                        ui
.getRowForPath(tree, aPath);

                       
if (aRow == -1) {
                            tree
.setSelectionInterval(0, 0);
                       
}
                       
else {
                            tree
.setSelectionInterval(0, aRow);
                            ui
.setAnchorSelectionPath(aPath);
                            ui
.setLeadSelectionPath(ui.getPathForRow(tree, 0));
                       
}
                   
}
                   
else if(changeSelection) {
                        tree
.setSelectionInterval(0, 0);
                   
}
                   
else {
                        ui
.setLeadSelectionPath(ui.getPathForRow(tree, 0),
                                               
true);
                   
}
               
}
               
else {
                    ui
.ensureRowsAreVisible(rowCount - 1, rowCount - 1);
                   
if (addToSelection) {
                       
TreePath        aPath = ui.getAnchorSelectionPath();
                       
int             aRow = (aPath == null) ? -1 :
                                        ui
.getRowForPath(tree, aPath);

                       
if (aRow == -1) {
                            tree
.setSelectionInterval(rowCount - 1,
                                                      rowCount
-1);
                       
}
                       
else {
                            tree
.setSelectionInterval(aRow, rowCount - 1);
                            ui
.setAnchorSelectionPath(aPath);
                            ui
.setLeadSelectionPath(ui.getPathForRow(tree,
                                                               rowCount
-1));
                       
}
                   
}
                   
else if(changeSelection) {
                        tree
.setSelectionInterval(rowCount - 1, rowCount - 1);
                   
}
                   
else {
                        ui
.setLeadSelectionPath(ui.getPathForRow(tree,
                                                          rowCount
- 1), true);
                   
}
               
}
           
}
       
}
   
}
} // End of class BasicTreeUI