mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			3940 lines
		
	
	
		
			112 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			3940 lines
		
	
	
		
			112 KiB
		
	
	
	
		
			Java
		
	
	
	
/* BasicTreeUI.java --
 | 
						|
 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
 | 
						|
 | 
						|
 This file is part of GNU Classpath.
 | 
						|
 | 
						|
 GNU Classpath is free software; you can redistribute it and/or modify
 | 
						|
 it under the terms of the GNU General Public License as published by
 | 
						|
 the Free Software Foundation; either version 2, or (at your option)
 | 
						|
 any later version.
 | 
						|
 | 
						|
 GNU Classpath 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 for more details.
 | 
						|
 | 
						|
 You should have received a copy of the GNU General Public License
 | 
						|
 along with GNU Classpath; see the file COPYING.  If not, write to the
 | 
						|
 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | 
						|
 02110-1301 USA.
 | 
						|
 | 
						|
 Linking this library statically or dynamically with other modules is
 | 
						|
 making a combined work based on this library.  Thus, the terms and
 | 
						|
 conditions of the GNU General Public License cover the whole
 | 
						|
 combination.
 | 
						|
 | 
						|
 As a special exception, the copyright holders of this library give you
 | 
						|
 permission to link this library with independent modules to produce an
 | 
						|
 executable, regardless of the license terms of these independent
 | 
						|
 modules, and to copy and distribute the resulting executable under
 | 
						|
 terms of your choice, provided that you also meet, for each linked
 | 
						|
 independent module, the terms and conditions of the license of that
 | 
						|
 module.  An independent module is a module which is not derived from
 | 
						|
 or based on this library.  If you modify this library, you may extend
 | 
						|
 this exception to your version of the library, but you are not
 | 
						|
 obligated to do so.  If you do not wish to do so, delete this
 | 
						|
 exception statement from your version. */
 | 
						|
 | 
						|
 | 
						|
package javax.swing.plaf.basic;
 | 
						|
 | 
						|
import gnu.javax.swing.tree.GnuPath;
 | 
						|
 | 
						|
import java.awt.Color;
 | 
						|
import java.awt.Component;
 | 
						|
import java.awt.Container;
 | 
						|
import java.awt.Dimension;
 | 
						|
import java.awt.Graphics;
 | 
						|
import java.awt.Insets;
 | 
						|
import java.awt.Label;
 | 
						|
import java.awt.Point;
 | 
						|
import java.awt.Rectangle;
 | 
						|
import java.awt.event.ActionEvent;
 | 
						|
import java.awt.event.ActionListener;
 | 
						|
import java.awt.event.ComponentAdapter;
 | 
						|
import java.awt.event.ComponentEvent;
 | 
						|
import java.awt.event.ComponentListener;
 | 
						|
import java.awt.event.FocusEvent;
 | 
						|
import java.awt.event.FocusListener;
 | 
						|
import java.awt.event.InputEvent;
 | 
						|
import java.awt.event.KeyAdapter;
 | 
						|
import java.awt.event.KeyEvent;
 | 
						|
import java.awt.event.KeyListener;
 | 
						|
import java.awt.event.MouseAdapter;
 | 
						|
import java.awt.event.MouseEvent;
 | 
						|
import java.awt.event.MouseListener;
 | 
						|
import java.awt.event.MouseMotionListener;
 | 
						|
import java.beans.PropertyChangeEvent;
 | 
						|
import java.beans.PropertyChangeListener;
 | 
						|
import java.util.Enumeration;
 | 
						|
import java.util.Hashtable;
 | 
						|
 | 
						|
import javax.swing.AbstractAction;
 | 
						|
import javax.swing.Action;
 | 
						|
import javax.swing.ActionMap;
 | 
						|
import javax.swing.CellRendererPane;
 | 
						|
import javax.swing.Icon;
 | 
						|
import javax.swing.InputMap;
 | 
						|
import javax.swing.JComponent;
 | 
						|
import javax.swing.JScrollBar;
 | 
						|
import javax.swing.JScrollPane;
 | 
						|
import javax.swing.JTree;
 | 
						|
import javax.swing.LookAndFeel;
 | 
						|
import javax.swing.SwingUtilities;
 | 
						|
import javax.swing.Timer;
 | 
						|
import javax.swing.UIManager;
 | 
						|
import javax.swing.event.CellEditorListener;
 | 
						|
import javax.swing.event.ChangeEvent;
 | 
						|
import javax.swing.event.MouseInputListener;
 | 
						|
import javax.swing.event.TreeExpansionEvent;
 | 
						|
import javax.swing.event.TreeExpansionListener;
 | 
						|
import javax.swing.event.TreeModelEvent;
 | 
						|
import javax.swing.event.TreeModelListener;
 | 
						|
import javax.swing.event.TreeSelectionEvent;
 | 
						|
import javax.swing.event.TreeSelectionListener;
 | 
						|
import javax.swing.plaf.ActionMapUIResource;
 | 
						|
import javax.swing.plaf.ComponentUI;
 | 
						|
import javax.swing.plaf.TreeUI;
 | 
						|
import javax.swing.tree.AbstractLayoutCache;
 | 
						|
import javax.swing.tree.DefaultTreeCellEditor;
 | 
						|
import javax.swing.tree.DefaultTreeCellRenderer;
 | 
						|
import javax.swing.tree.TreeCellEditor;
 | 
						|
import javax.swing.tree.TreeCellRenderer;
 | 
						|
import javax.swing.tree.TreeModel;
 | 
						|
import javax.swing.tree.TreeNode;
 | 
						|
import javax.swing.tree.TreePath;
 | 
						|
import javax.swing.tree.TreeSelectionModel;
 | 
						|
import javax.swing.tree.VariableHeightLayoutCache;
 | 
						|
 | 
						|
/**
 | 
						|
 * A delegate providing the user interface for <code>JTree</code> according to
 | 
						|
 * the Basic look and feel.
 | 
						|
 *
 | 
						|
 * @see javax.swing.JTree
 | 
						|
 * @author Lillian Angel (langel@redhat.com)
 | 
						|
 * @author Sascha Brawer (brawer@dandelis.ch)
 | 
						|
 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
 | 
						|
 */
 | 
						|
public class BasicTreeUI
 | 
						|
  extends TreeUI
 | 
						|
{
 | 
						|
  /**
 | 
						|
   * The tree cell editing may be started by the single mouse click on the
 | 
						|
   * selected cell. To separate it from the double mouse click, the editing
 | 
						|
   * session starts after this time (in ms) after that single click, and only no
 | 
						|
   * other clicks were performed during that time.
 | 
						|
   */
 | 
						|
  static int WAIT_TILL_EDITING = 900;
 | 
						|
 | 
						|
  /** Collapse Icon for the tree. */
 | 
						|
  protected transient Icon collapsedIcon;
 | 
						|
 | 
						|
  /** Expanded Icon for the tree. */
 | 
						|
  protected transient Icon expandedIcon;
 | 
						|
 | 
						|
  /** Distance between left margin and where vertical dashes will be drawn. */
 | 
						|
  protected int leftChildIndent;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Distance between leftChildIndent and where cell contents will be drawn.
 | 
						|
   */
 | 
						|
  protected int rightChildIndent;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Total fistance that will be indented. The sum of leftChildIndent and
 | 
						|
   * rightChildIndent .
 | 
						|
   */
 | 
						|
  protected int totalChildIndent;
 | 
						|
 | 
						|
  /** Index of the row that was last selected. */
 | 
						|
  protected int lastSelectedRow;
 | 
						|
 | 
						|
  /** Component that we're going to be drawing onto. */
 | 
						|
  protected JTree tree;
 | 
						|
 | 
						|
  /** Renderer that is being used to do the actual cell drawing. */
 | 
						|
  protected transient 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. */
 | 
						|
  protected transient 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 shouldSelectCall() returns true meaning the
 | 
						|
   * node should be selected before editing, used in completeEditing.
 | 
						|
   * GNU Classpath editing is implemented differently, so this value is not
 | 
						|
   * actually read anywhere. However it is always set correctly to maintain
 | 
						|
   * interoperability with the derived classes that read this field.
 | 
						|
   */
 | 
						|
  protected boolean stopEditingInCompleteEditing;
 | 
						|
 | 
						|
  /** Used to paint the TreeCellRenderer. */
 | 
						|
  protected CellRendererPane rendererPane;
 | 
						|
 | 
						|
  /** Size needed to completely display all the nodes. */
 | 
						|
  protected Dimension preferredSize;
 | 
						|
 | 
						|
  /** Minimum size needed to completely display all the nodes. */
 | 
						|
  protected Dimension preferredMinSize;
 | 
						|
 | 
						|
  /** Is the preferredSize valid? */
 | 
						|
  protected boolean validCachedPreferredSize;
 | 
						|
 | 
						|
  /** Object responsible for handling sizing and expanded issues. */
 | 
						|
  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;
 | 
						|
 | 
						|
  /** Responsible for telling the TreeState the size needed for a node. */
 | 
						|
  protected AbstractLayoutCache.NodeDimensions nodeDimensions;
 | 
						|
 | 
						|
  /** Used to determine what to display. */
 | 
						|
  protected TreeModel treeModel;
 | 
						|
 | 
						|
  /** Model maintaining 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;
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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
 | 
						|
   * null.
 | 
						|
   */
 | 
						|
  protected int editingRow;
 | 
						|
 | 
						|
  /** Set to true if the editor has a different size than the renderer. */
 | 
						|
  protected boolean editorHasDifferentSize;
 | 
						|
 | 
						|
  /** Boolean to keep track of editing. */
 | 
						|
  boolean isEditing;
 | 
						|
 | 
						|
  /** The current path of the visible nodes in the tree. */
 | 
						|
  TreePath currentVisiblePath;
 | 
						|
 | 
						|
  /** The gap between the icon and text. */
 | 
						|
  int gap = 4;
 | 
						|
 | 
						|
  /** The max height of the nodes in the tree. */
 | 
						|
  int maxHeight;
 | 
						|
 | 
						|
  /** The hash color. */
 | 
						|
  Color hashColor;
 | 
						|
 | 
						|
  /** Listeners */
 | 
						|
  PropertyChangeListener propertyChangeListener;
 | 
						|
 | 
						|
  FocusListener focusListener;
 | 
						|
 | 
						|
  TreeSelectionListener treeSelectionListener;
 | 
						|
 | 
						|
  MouseListener mouseListener;
 | 
						|
 | 
						|
  KeyListener keyListener;
 | 
						|
 | 
						|
  PropertyChangeListener selectionModelPropertyChangeListener;
 | 
						|
 | 
						|
  ComponentListener componentListener;
 | 
						|
 | 
						|
  CellEditorListener cellEditorListener;
 | 
						|
 | 
						|
  TreeExpansionListener treeExpansionListener;
 | 
						|
 | 
						|
  TreeModelListener treeModelListener;
 | 
						|
 | 
						|
  /**
 | 
						|
   * The zero size icon, used for expand controls, if they are not visible.
 | 
						|
   */
 | 
						|
  static Icon nullIcon;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a new BasicTreeUI object.
 | 
						|
   */
 | 
						|
  public BasicTreeUI()
 | 
						|
  {
 | 
						|
    validCachedPreferredSize = false;
 | 
						|
    drawingCache = new Hashtable();
 | 
						|
    nodeDimensions = createNodeDimensions();
 | 
						|
    configureLayoutCache();
 | 
						|
 | 
						|
    editingRow = - 1;
 | 
						|
    lastSelectedRow = - 1;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns an instance of the UI delegate for the specified component.
 | 
						|
   *
 | 
						|
   * @param c the <code>JComponent</code> for which we need a UI delegate for.
 | 
						|
   * @return the <code>ComponentUI</code> for c.
 | 
						|
   */
 | 
						|
  public static ComponentUI createUI(JComponent c)
 | 
						|
  {
 | 
						|
    return new BasicTreeUI();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the Hash color.
 | 
						|
   *
 | 
						|
   * @return the <code>Color</code> of the Hash.
 | 
						|
   */
 | 
						|
  protected Color getHashColor()
 | 
						|
  {
 | 
						|
    return hashColor;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the Hash color.
 | 
						|
   *
 | 
						|
   * @param color the <code>Color</code> to set the Hash to.
 | 
						|
   */
 | 
						|
  protected void setHashColor(Color color)
 | 
						|
  {
 | 
						|
    hashColor = color;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the left child's indent value.
 | 
						|
   *
 | 
						|
   * @param newAmount is the new indent value for the left child.
 | 
						|
   */
 | 
						|
  public void setLeftChildIndent(int newAmount)
 | 
						|
  {
 | 
						|
    leftChildIndent = newAmount;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the indent value for the left child.
 | 
						|
   *
 | 
						|
   * @return the indent value for the left child.
 | 
						|
   */
 | 
						|
  public int getLeftChildIndent()
 | 
						|
  {
 | 
						|
    return leftChildIndent;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the right child's indent value.
 | 
						|
   *
 | 
						|
   * @param newAmount is the new indent value for the right child.
 | 
						|
   */
 | 
						|
  public void setRightChildIndent(int newAmount)
 | 
						|
  {
 | 
						|
    rightChildIndent = newAmount;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the indent value for the right child.
 | 
						|
   *
 | 
						|
   * @return the indent value for the right child.
 | 
						|
   */
 | 
						|
  public int getRightChildIndent()
 | 
						|
  {
 | 
						|
    return rightChildIndent;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the expanded icon.
 | 
						|
   *
 | 
						|
   * @param newG is the new expanded icon.
 | 
						|
   */
 | 
						|
  public void setExpandedIcon(Icon newG)
 | 
						|
  {
 | 
						|
    expandedIcon = newG;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the current expanded icon.
 | 
						|
   *
 | 
						|
   * @return the current expanded icon.
 | 
						|
   */
 | 
						|
  public Icon getExpandedIcon()
 | 
						|
  {
 | 
						|
    return expandedIcon;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the collapsed icon.
 | 
						|
   *
 | 
						|
   * @param newG is the new collapsed icon.
 | 
						|
   */
 | 
						|
  public void setCollapsedIcon(Icon newG)
 | 
						|
  {
 | 
						|
    collapsedIcon = newG;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the current collapsed icon.
 | 
						|
   *
 | 
						|
   * @return the current collapsed icon.
 | 
						|
   */
 | 
						|
  public Icon getCollapsedIcon()
 | 
						|
  {
 | 
						|
    return collapsedIcon;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates the componentListener, if necessary.
 | 
						|
   *
 | 
						|
   * @param largeModel sets this.largeModel to it.
 | 
						|
   */
 | 
						|
  protected void setLargeModel(boolean largeModel)
 | 
						|
  {
 | 
						|
    if (largeModel != this.largeModel)
 | 
						|
      {
 | 
						|
        completeEditing();
 | 
						|
        tree.removeComponentListener(componentListener);
 | 
						|
        this.largeModel = largeModel;
 | 
						|
        tree.addComponentListener(componentListener);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if largeModel is set
 | 
						|
   *
 | 
						|
   * @return true if largeModel is set, otherwise false.
 | 
						|
   */
 | 
						|
  protected boolean isLargeModel()
 | 
						|
  {
 | 
						|
    return largeModel;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the row height.
 | 
						|
   *
 | 
						|
   * @param rowHeight is the height to set this.rowHeight to.
 | 
						|
   */
 | 
						|
  protected void setRowHeight(int rowHeight)
 | 
						|
  {
 | 
						|
    completeEditing();
 | 
						|
    if (rowHeight == 0)
 | 
						|
      rowHeight = getMaxHeight(tree);
 | 
						|
    treeState.setRowHeight(rowHeight);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the current row height.
 | 
						|
   *
 | 
						|
   * @return current row height.
 | 
						|
   */
 | 
						|
  protected int getRowHeight()
 | 
						|
  {
 | 
						|
    return tree.getRowHeight();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
 | 
						|
   * <code>updateRenderer</code>.
 | 
						|
   *
 | 
						|
   * @param tcr is the new TreeCellRenderer.
 | 
						|
   */
 | 
						|
  protected void setCellRenderer(TreeCellRenderer tcr)
 | 
						|
  {
 | 
						|
    // Finish editing before changing the renderer.
 | 
						|
    completeEditing();
 | 
						|
 | 
						|
    // The renderer is set in updateRenderer.
 | 
						|
    updateRenderer();
 | 
						|
 | 
						|
    // Refresh the layout if necessary.
 | 
						|
    if (treeState != null)
 | 
						|
      {
 | 
						|
        treeState.invalidateSizes();
 | 
						|
        updateSize();
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Return currentCellRenderer, which will either be the trees renderer, or
 | 
						|
   * defaultCellRenderer, which ever was not null.
 | 
						|
   *
 | 
						|
   * @return the current Cell Renderer
 | 
						|
   */
 | 
						|
  protected TreeCellRenderer getCellRenderer()
 | 
						|
  {
 | 
						|
    if (currentCellRenderer != null)
 | 
						|
      return currentCellRenderer;
 | 
						|
 | 
						|
    return createDefaultCellRenderer();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the tree's model.
 | 
						|
   *
 | 
						|
   * @param model to set the treeModel to.
 | 
						|
   */
 | 
						|
  protected void setModel(TreeModel model)
 | 
						|
  {
 | 
						|
    completeEditing();
 | 
						|
 | 
						|
    if (treeModel != null && treeModelListener != null)
 | 
						|
      treeModel.removeTreeModelListener(treeModelListener);
 | 
						|
 | 
						|
    treeModel = tree.getModel();
 | 
						|
 | 
						|
    if (treeModel != null && treeModelListener != null)
 | 
						|
      treeModel.addTreeModelListener(treeModelListener);
 | 
						|
 | 
						|
    if (treeState != null)
 | 
						|
      {
 | 
						|
        treeState.setModel(treeModel);
 | 
						|
        updateLayoutCacheExpandedNodes();
 | 
						|
        updateSize();
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the tree's model
 | 
						|
   *
 | 
						|
   * @return treeModel
 | 
						|
   */
 | 
						|
  protected TreeModel getModel()
 | 
						|
  {
 | 
						|
    return treeModel;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the root to being visible.
 | 
						|
   *
 | 
						|
   * @param newValue sets the visibility of the root
 | 
						|
   */
 | 
						|
  protected void setRootVisible(boolean newValue)
 | 
						|
  {
 | 
						|
    completeEditing();
 | 
						|
    tree.setRootVisible(newValue);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if the root is visible.
 | 
						|
   *
 | 
						|
   * @return true if the root is visible.
 | 
						|
   */
 | 
						|
  protected boolean isRootVisible()
 | 
						|
  {
 | 
						|
    return tree.isRootVisible();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Determines whether the node handles are to be displayed.
 | 
						|
   *
 | 
						|
   * @param newValue sets whether or not node handles should be displayed.
 | 
						|
   */
 | 
						|
  protected void setShowsRootHandles(boolean newValue)
 | 
						|
  {
 | 
						|
    completeEditing();
 | 
						|
    updateDepthOffset();
 | 
						|
    if (treeState != null)
 | 
						|
      {
 | 
						|
        treeState.invalidateSizes();
 | 
						|
        updateSize();
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if the node handles are to be displayed.
 | 
						|
   *
 | 
						|
   * @return true if the node handles are to be displayed.
 | 
						|
   */
 | 
						|
  protected boolean getShowsRootHandles()
 | 
						|
  {
 | 
						|
    return tree.getShowsRootHandles();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the cell editor.
 | 
						|
   *
 | 
						|
   * @param editor to set the cellEditor to.
 | 
						|
   */
 | 
						|
  protected void setCellEditor(TreeCellEditor editor)
 | 
						|
  {
 | 
						|
    updateCellEditor();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the <code>TreeCellEditor</code> for this tree.
 | 
						|
   *
 | 
						|
   * @return the cellEditor for this tree.
 | 
						|
   */
 | 
						|
  protected TreeCellEditor getCellEditor()
 | 
						|
  {
 | 
						|
    return cellEditor;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Configures the receiver to allow, or not allow, editing.
 | 
						|
   *
 | 
						|
   * @param newValue sets the receiver to allow editing if true.
 | 
						|
   */
 | 
						|
  protected void setEditable(boolean newValue)
 | 
						|
  {
 | 
						|
    updateCellEditor();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if the receiver allows editing.
 | 
						|
   *
 | 
						|
   * @return true if the receiver allows editing.
 | 
						|
   */
 | 
						|
  protected boolean isEditable()
 | 
						|
  {
 | 
						|
    return tree.isEditable();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Resets the selection model. The appropriate listeners are installed on the
 | 
						|
   * model.
 | 
						|
   *
 | 
						|
   * @param newLSM resets the selection model.
 | 
						|
   */
 | 
						|
  protected void setSelectionModel(TreeSelectionModel newLSM)
 | 
						|
  {
 | 
						|
    completeEditing();
 | 
						|
    if (newLSM != null)
 | 
						|
      {
 | 
						|
        treeSelectionModel = newLSM;
 | 
						|
        tree.setSelectionModel(treeSelectionModel);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the current selection model.
 | 
						|
   *
 | 
						|
   * @return the current selection model.
 | 
						|
   */
 | 
						|
  protected TreeSelectionModel getSelectionModel()
 | 
						|
  {
 | 
						|
    return treeSelectionModel;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the Rectangle enclosing the label portion that the last item in
 | 
						|
   * path will be drawn to. Will return null if any component in path is
 | 
						|
   * currently valid.
 | 
						|
   *
 | 
						|
   * @param tree is the current tree the path will be drawn to.
 | 
						|
   * @param path is the current path the tree to draw to.
 | 
						|
   * @return the Rectangle enclosing the label portion that the last item in the
 | 
						|
   *         path will be drawn to.
 | 
						|
   */
 | 
						|
  public Rectangle getPathBounds(JTree tree, TreePath path)
 | 
						|
  {
 | 
						|
    Rectangle bounds = null;
 | 
						|
    if (tree != null && treeState != null)
 | 
						|
      {
 | 
						|
        bounds = treeState.getBounds(path, null);
 | 
						|
        Insets i = tree.getInsets();
 | 
						|
        if (bounds != null && i != null)
 | 
						|
          {
 | 
						|
            bounds.x += i.left;
 | 
						|
            bounds.y += i.top;
 | 
						|
          }
 | 
						|
      }
 | 
						|
    return bounds;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the max height of all the nodes in the tree.
 | 
						|
   *
 | 
						|
   * @param tree - the current tree
 | 
						|
   * @return the max height.
 | 
						|
   */
 | 
						|
  int getMaxHeight(JTree tree)
 | 
						|
  {
 | 
						|
    if (maxHeight != 0)
 | 
						|
      return maxHeight;
 | 
						|
 | 
						|
    Icon e = UIManager.getIcon("Tree.openIcon");
 | 
						|
    Icon c = UIManager.getIcon("Tree.closedIcon");
 | 
						|
    Icon l = UIManager.getIcon("Tree.leafIcon");
 | 
						|
    int rc = getRowCount(tree);
 | 
						|
    int iconHeight = 0;
 | 
						|
 | 
						|
    for (int row = 0; row < rc; row++)
 | 
						|
      {
 | 
						|
        if (isLeaf(row))
 | 
						|
          iconHeight = l.getIconHeight();
 | 
						|
        else if (tree.isExpanded(row))
 | 
						|
          iconHeight = e.getIconHeight();
 | 
						|
        else
 | 
						|
          iconHeight = c.getIconHeight();
 | 
						|
 | 
						|
        maxHeight = Math.max(maxHeight, iconHeight + gap);
 | 
						|
      }
 | 
						|
 | 
						|
    treeState.setRowHeight(maxHeight);
 | 
						|
    return maxHeight;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Get the tree node icon.
 | 
						|
   */
 | 
						|
  Icon getNodeIcon(TreePath path)
 | 
						|
  {
 | 
						|
    Object node = path.getLastPathComponent();
 | 
						|
    if (treeModel.isLeaf(node))
 | 
						|
      return UIManager.getIcon("Tree.leafIcon");
 | 
						|
    else if (treeState.getExpandedState(path))
 | 
						|
      return UIManager.getIcon("Tree.openIcon");
 | 
						|
    else
 | 
						|
      return UIManager.getIcon("Tree.closedIcon");
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the path for passed in row. If row is not visible null is returned.
 | 
						|
   *
 | 
						|
   * @param tree is the current tree to return path for.
 | 
						|
   * @param row is the row number of the row to return.
 | 
						|
   * @return the path for passed in row. If row is not visible null is returned.
 | 
						|
   */
 | 
						|
  public TreePath getPathForRow(JTree tree, int row)
 | 
						|
  {
 | 
						|
    return treeState.getPathForRow(row);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the row that the last item identified in path is visible at. Will
 | 
						|
   * return -1 if any of the elments in the path are not currently visible.
 | 
						|
   *
 | 
						|
   * @param tree is the current tree to return the row for.
 | 
						|
   * @param path is the path used to find the row.
 | 
						|
   * @return the row that the last item identified in path is visible at. Will
 | 
						|
   *         return -1 if any of the elments in the path are not currently
 | 
						|
   *         visible.
 | 
						|
   */
 | 
						|
  public int getRowForPath(JTree tree, TreePath path)
 | 
						|
  {
 | 
						|
    return treeState.getRowForPath(path);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the number of rows that are being displayed.
 | 
						|
   *
 | 
						|
   * @param tree is the current tree to return the number of rows for.
 | 
						|
   * @return the number of rows being displayed.
 | 
						|
   */
 | 
						|
  public int getRowCount(JTree tree)
 | 
						|
  {
 | 
						|
    return treeState.getRowCount();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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.
 | 
						|
   *
 | 
						|
   * @param tree the tree to search for the closest path
 | 
						|
   * @param x is the x coordinate of the location to search
 | 
						|
   * @param y is the y coordinate of the location to search
 | 
						|
   * @return the tree path closes to x,y.
 | 
						|
   */
 | 
						|
  public TreePath getClosestPathForLocation(JTree tree, int x, int y)
 | 
						|
  {
 | 
						|
    return treeState.getPathClosestTo(x, y);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if the tree is being edited. The item that is being edited can
 | 
						|
   * be returned by getEditingPath().
 | 
						|
   *
 | 
						|
   * @param tree is the tree to check for editing.
 | 
						|
   * @return true if the tree is being edited.
 | 
						|
   */
 | 
						|
  public boolean isEditing(JTree tree)
 | 
						|
  {
 | 
						|
    return isEditing;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Stops the current editing session. This has no effect if the tree is not
 | 
						|
   * being edited. Returns true if the editor allows the editing session to
 | 
						|
   * stop.
 | 
						|
   *
 | 
						|
   * @param tree is the tree to stop the editing on
 | 
						|
   * @return true if the editor allows the editing session to stop.
 | 
						|
   */
 | 
						|
  public boolean stopEditing(JTree tree)
 | 
						|
  {
 | 
						|
    boolean ret = false;
 | 
						|
    if (editingComponent != null && cellEditor.stopCellEditing())
 | 
						|
      {
 | 
						|
        completeEditing(false, false, true);
 | 
						|
        ret = true;
 | 
						|
      }
 | 
						|
    return ret;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Cancels the current editing session.
 | 
						|
   *
 | 
						|
   * @param tree is the tree to cancel the editing session on.
 | 
						|
   */
 | 
						|
  public void cancelEditing(JTree tree)
 | 
						|
  {
 | 
						|
    // There is no need to send the cancel message to the editor,
 | 
						|
    // as the cancellation event itself arrives from it. This would
 | 
						|
    // only be necessary when cancelling the editing programatically.
 | 
						|
    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.
 | 
						|
   *
 | 
						|
   * @param tree is the tree to edit on.
 | 
						|
   * @param path is the path in tree to edit on.
 | 
						|
   */
 | 
						|
  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 editted.
 | 
						|
   *
 | 
						|
   * @param tree is the tree to get the editing path from.
 | 
						|
   * @return the path that is being edited.
 | 
						|
   */
 | 
						|
  public TreePath getEditingPath(JTree tree)
 | 
						|
  {
 | 
						|
    return editingPath;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Invoked after the tree instance variable has been set, but before any
 | 
						|
   * default/listeners have been installed.
 | 
						|
   */
 | 
						|
  protected void prepareForUIInstall()
 | 
						|
  {
 | 
						|
    lastSelectedRow = -1;
 | 
						|
    preferredSize = new Dimension();
 | 
						|
    largeModel = tree.isLargeModel();
 | 
						|
    preferredSize = new Dimension();
 | 
						|
    stopEditingInCompleteEditing = true;
 | 
						|
    setModel(tree.getModel());
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Invoked from installUI after all the defaults/listeners have been
 | 
						|
   * installed.
 | 
						|
   */
 | 
						|
  protected void completeUIInstall()
 | 
						|
  {
 | 
						|
    setShowsRootHandles(tree.getShowsRootHandles());
 | 
						|
    updateRenderer();
 | 
						|
    updateDepthOffset();
 | 
						|
    setSelectionModel(tree.getSelectionModel());
 | 
						|
    configureLayoutCache();
 | 
						|
    treeState.setRootVisible(tree.isRootVisible());
 | 
						|
    treeSelectionModel.setRowMapper(treeState);
 | 
						|
    updateSize();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Invoked from uninstallUI after all the defaults/listeners have been
 | 
						|
   * uninstalled.
 | 
						|
   */
 | 
						|
  protected void completeUIUninstall()
 | 
						|
  {
 | 
						|
    tree = null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Installs the subcomponents of the tree, which is the renderer pane.
 | 
						|
   */
 | 
						|
  protected void installComponents()
 | 
						|
  {
 | 
						|
    currentCellRenderer = createDefaultCellRenderer();
 | 
						|
    rendererPane = createCellRendererPane();
 | 
						|
    createdRenderer = true;
 | 
						|
    setCellRenderer(currentCellRenderer);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates an instance of NodeDimensions that is able to determine the size of
 | 
						|
   * a given node in the tree. The node dimensions must be created before
 | 
						|
   * configuring the layout cache.
 | 
						|
   *
 | 
						|
   * @return the NodeDimensions of a given node in the tree
 | 
						|
   */
 | 
						|
  protected AbstractLayoutCache.NodeDimensions createNodeDimensions()
 | 
						|
  {
 | 
						|
    return new NodeDimensionsHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a listener that is reponsible for the updates the UI based on how
 | 
						|
   * the tree changes.
 | 
						|
   *
 | 
						|
   * @return the PropertyChangeListener that is reposnsible for the updates
 | 
						|
   */
 | 
						|
  protected PropertyChangeListener createPropertyChangeListener()
 | 
						|
  {
 | 
						|
    return new PropertyChangeHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates the listener responsible for updating the selection based on mouse
 | 
						|
   * events.
 | 
						|
   *
 | 
						|
   * @return the MouseListener responsible for updating.
 | 
						|
   */
 | 
						|
  protected MouseListener createMouseListener()
 | 
						|
  {
 | 
						|
    return new MouseHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates the listener that is responsible for updating the display when
 | 
						|
   * focus is lost/grained.
 | 
						|
   *
 | 
						|
   * @return the FocusListener responsible for updating.
 | 
						|
   */
 | 
						|
  protected FocusListener createFocusListener()
 | 
						|
  {
 | 
						|
    return new FocusHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates the listener reponsible for getting key events from the tree.
 | 
						|
   *
 | 
						|
   * @return the KeyListener responsible for getting key events.
 | 
						|
   */
 | 
						|
  protected KeyListener createKeyListener()
 | 
						|
  {
 | 
						|
    return new KeyHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates the listener responsible for getting property change events from
 | 
						|
   * the selection model.
 | 
						|
   *
 | 
						|
   * @returns the PropertyChangeListener reponsible for getting property change
 | 
						|
   *          events from the selection model.
 | 
						|
   */
 | 
						|
  protected PropertyChangeListener createSelectionModelPropertyChangeListener()
 | 
						|
  {
 | 
						|
    return new SelectionModelPropertyChangeHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates the listener that updates the display based on selection change
 | 
						|
   * methods.
 | 
						|
   *
 | 
						|
   * @return the TreeSelectionListener responsible for updating.
 | 
						|
   */
 | 
						|
  protected TreeSelectionListener createTreeSelectionListener()
 | 
						|
  {
 | 
						|
    return new TreeSelectionHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a listener to handle events from the current editor
 | 
						|
   *
 | 
						|
   * @return the CellEditorListener that handles events from the current editor
 | 
						|
   */
 | 
						|
  protected CellEditorListener createCellEditorListener()
 | 
						|
  {
 | 
						|
    return new CellEditorHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates and returns a new ComponentHandler. This is used for the large
 | 
						|
   * model to mark the validCachedPreferredSize as invalid when the component
 | 
						|
   * moves.
 | 
						|
   *
 | 
						|
   * @return a new ComponentHandler.
 | 
						|
   */
 | 
						|
  protected ComponentListener createComponentListener()
 | 
						|
  {
 | 
						|
    return new ComponentHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates and returns the object responsible for updating the treestate when
 | 
						|
   * a nodes expanded state changes.
 | 
						|
   *
 | 
						|
   * @return the TreeExpansionListener responsible for updating the treestate
 | 
						|
   */
 | 
						|
  protected TreeExpansionListener createTreeExpansionListener()
 | 
						|
  {
 | 
						|
    return new TreeExpansionHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates the object responsible for managing what is expanded, as well as
 | 
						|
   * the size of nodes.
 | 
						|
   *
 | 
						|
   * @return the object responsible for managing what is expanded.
 | 
						|
   */
 | 
						|
  protected AbstractLayoutCache createLayoutCache()
 | 
						|
  {
 | 
						|
    return new VariableHeightLayoutCache();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the renderer pane that renderer components are placed in.
 | 
						|
   *
 | 
						|
   * @return the rendererpane that render components are placed in.
 | 
						|
   */
 | 
						|
  protected CellRendererPane createCellRendererPane()
 | 
						|
  {
 | 
						|
    return new CellRendererPane();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a default cell editor.
 | 
						|
   *
 | 
						|
   * @return the default cell editor.
 | 
						|
   */
 | 
						|
  protected TreeCellEditor createDefaultCellEditor()
 | 
						|
  {
 | 
						|
    DefaultTreeCellEditor ed;
 | 
						|
    if (currentCellRenderer != null
 | 
						|
        && currentCellRenderer instanceof DefaultTreeCellRenderer)
 | 
						|
      ed = new DefaultTreeCellEditor(tree,
 | 
						|
                                (DefaultTreeCellRenderer) currentCellRenderer);
 | 
						|
    else
 | 
						|
      ed = new DefaultTreeCellEditor(tree, null);
 | 
						|
    return ed;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the default cell renderer that is used to do the stamping of each
 | 
						|
   * node.
 | 
						|
   *
 | 
						|
   * @return 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.
 | 
						|
   *
 | 
						|
   * @return a listener that can update the tree when the model changes.
 | 
						|
   */
 | 
						|
  protected TreeModelListener createTreeModelListener()
 | 
						|
  {
 | 
						|
    return new TreeModelHandler();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Uninstall all registered listeners
 | 
						|
   */
 | 
						|
  protected void uninstallListeners()
 | 
						|
  {
 | 
						|
    tree.removePropertyChangeListener(propertyChangeListener);
 | 
						|
    tree.removeFocusListener(focusListener);
 | 
						|
    tree.removeTreeSelectionListener(treeSelectionListener);
 | 
						|
    tree.removeMouseListener(mouseListener);
 | 
						|
    tree.removeKeyListener(keyListener);
 | 
						|
    tree.removePropertyChangeListener(selectionModelPropertyChangeListener);
 | 
						|
    tree.removeComponentListener(componentListener);
 | 
						|
    tree.removeTreeExpansionListener(treeExpansionListener);
 | 
						|
 | 
						|
    TreeCellEditor tce = tree.getCellEditor();
 | 
						|
    if (tce != null)
 | 
						|
      tce.removeCellEditorListener(cellEditorListener);
 | 
						|
    if (treeModel != null)
 | 
						|
      treeModel.removeTreeModelListener(treeModelListener);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Uninstall all keyboard actions.
 | 
						|
   */
 | 
						|
  protected void uninstallKeyboardActions()
 | 
						|
  {
 | 
						|
    tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
 | 
						|
                                                                              null);
 | 
						|
    tree.getActionMap().setParent(null);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Uninstall the rendererPane.
 | 
						|
   */
 | 
						|
  protected void uninstallComponents()
 | 
						|
  {
 | 
						|
    currentCellRenderer = null;
 | 
						|
    rendererPane = null;
 | 
						|
    createdRenderer = false;
 | 
						|
    setCellRenderer(currentCellRenderer);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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.
 | 
						|
   *
 | 
						|
   * @return the vertical leg buffer
 | 
						|
   */
 | 
						|
  protected int getVerticalLegBuffer()
 | 
						|
  {
 | 
						|
    return getRowHeight() / 2;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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.
 | 
						|
   *
 | 
						|
   * @return the horizontal leg buffer
 | 
						|
   */
 | 
						|
  protected int getHorizontalLegBuffer()
 | 
						|
  {
 | 
						|
    return rightChildIndent / 2;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Make 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()));
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates the expanded state of all the descendants of the <code>path</code>
 | 
						|
   * by getting the expanded descendants from the tree and forwarding to the
 | 
						|
   * tree state.
 | 
						|
   *
 | 
						|
   * @param path the path used to update the expanded states
 | 
						|
   */
 | 
						|
  protected void updateExpandedDescendants(TreePath path)
 | 
						|
  {
 | 
						|
    completeEditing();
 | 
						|
    Enumeration expanded = tree.getExpandedDescendants(path);
 | 
						|
    while (expanded.hasMoreElements())
 | 
						|
      treeState.setExpandedState((TreePath) expanded.nextElement(), true);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns a path to the last child of <code>parent</code>
 | 
						|
   *
 | 
						|
   * @param parent is the topmost path to specified
 | 
						|
   * @return a path to the last child of parent
 | 
						|
   */
 | 
						|
  protected TreePath getLastChildPath(TreePath parent)
 | 
						|
  {
 | 
						|
    return (TreePath) parent.getLastPathComponent();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates how much each depth should be offset by.
 | 
						|
   */
 | 
						|
  protected void updateDepthOffset()
 | 
						|
  {
 | 
						|
    depthOffset += getVerticalLegBuffer();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates the cellEditor based on 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()
 | 
						|
  {
 | 
						|
    completeEditing();
 | 
						|
    TreeCellEditor newEd = null;
 | 
						|
    if (tree != null && tree.isEditable())
 | 
						|
      {
 | 
						|
        newEd = tree.getCellEditor();
 | 
						|
        if (newEd == null)
 | 
						|
          {
 | 
						|
            newEd = createDefaultCellEditor();
 | 
						|
            if (newEd != null)
 | 
						|
              {
 | 
						|
                tree.setCellEditor(newEd);
 | 
						|
                createdCellEditor = true;
 | 
						|
              }
 | 
						|
          }
 | 
						|
      }
 | 
						|
    // Update listeners.
 | 
						|
    if (newEd != cellEditor)
 | 
						|
      {
 | 
						|
        if (cellEditor != null && cellEditorListener != null)
 | 
						|
          cellEditor.removeCellEditorListener(cellEditorListener);
 | 
						|
        cellEditor = newEd;
 | 
						|
        if (cellEditorListener == null)
 | 
						|
          cellEditorListener = createCellEditorListener();
 | 
						|
        if (cellEditor != null && cellEditorListener != null)
 | 
						|
          cellEditor.addCellEditorListener(cellEditorListener);
 | 
						|
        createdCellEditor = false;
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Messaged from the tree we're in when the renderer has changed.
 | 
						|
   */
 | 
						|
  protected void updateRenderer()
 | 
						|
  {
 | 
						|
    if (tree != null)
 | 
						|
      {
 | 
						|
        TreeCellRenderer rend = tree.getCellRenderer();
 | 
						|
        if (rend != null)
 | 
						|
          {
 | 
						|
            createdRenderer = false;
 | 
						|
            currentCellRenderer = rend;
 | 
						|
            if (createdCellEditor)
 | 
						|
              tree.setCellEditor(null);
 | 
						|
          }
 | 
						|
        else
 | 
						|
          {
 | 
						|
            tree.setCellRenderer(createDefaultCellRenderer());
 | 
						|
            createdRenderer = true;
 | 
						|
          }
 | 
						|
      }
 | 
						|
    else
 | 
						|
      {
 | 
						|
        currentCellRenderer = null;
 | 
						|
        createdRenderer = false;
 | 
						|
      }
 | 
						|
 | 
						|
    updateCellEditor();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Resets the treeState instance based on the tree we're providing the look
 | 
						|
   * and feel for. The node dimensions handler is required and must be created
 | 
						|
   * in advance.
 | 
						|
   */
 | 
						|
  protected void configureLayoutCache()
 | 
						|
  {
 | 
						|
    treeState = createLayoutCache();
 | 
						|
    treeState.setNodeDimensions(nodeDimensions);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Marks the cached size as being invalid, and messages the tree with
 | 
						|
   * <code>treeDidChange</code>.
 | 
						|
   */
 | 
						|
  protected void updateSize()
 | 
						|
  {
 | 
						|
    preferredSize = null;
 | 
						|
    updateCachedPreferredSize();
 | 
						|
    tree.treeDidChange();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates the <code>preferredSize</code> instance variable, which is
 | 
						|
   * returned from <code>getPreferredSize()</code>.
 | 
						|
   */
 | 
						|
  protected void updateCachedPreferredSize()
 | 
						|
  {
 | 
						|
    validCachedPreferredSize = false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Messaged from the VisibleTreeNode after it has been expanded.
 | 
						|
   *
 | 
						|
   * @param path is the path that has been expanded.
 | 
						|
   */
 | 
						|
  protected void pathWasExpanded(TreePath path)
 | 
						|
  {
 | 
						|
    validCachedPreferredSize = false;
 | 
						|
    treeState.setExpandedState(path, true);
 | 
						|
    tree.repaint();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Messaged from the VisibleTreeNode after it has collapsed
 | 
						|
   */
 | 
						|
  protected void pathWasCollapsed(TreePath path)
 | 
						|
  {
 | 
						|
    validCachedPreferredSize = false;
 | 
						|
    treeState.setExpandedState(path, false);
 | 
						|
    tree.repaint();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Install all defaults for the tree.
 | 
						|
   */
 | 
						|
  protected void installDefaults()
 | 
						|
  {
 | 
						|
    LookAndFeel.installColorsAndFont(tree, "Tree.background",
 | 
						|
                                     "Tree.foreground", "Tree.font");
 | 
						|
 | 
						|
    hashColor = UIManager.getColor("Tree.hash");
 | 
						|
    if (hashColor == null)
 | 
						|
      hashColor = Color.black;
 | 
						|
 | 
						|
    tree.setOpaque(true);
 | 
						|
 | 
						|
    rightChildIndent = UIManager.getInt("Tree.rightChildIndent");
 | 
						|
    leftChildIndent = UIManager.getInt("Tree.leftChildIndent");
 | 
						|
    totalChildIndent = rightChildIndent + leftChildIndent;
 | 
						|
    setRowHeight(UIManager.getInt("Tree.rowHeight"));
 | 
						|
    tree.setRowHeight(getRowHeight());
 | 
						|
    tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand"));
 | 
						|
    setExpandedIcon(UIManager.getIcon("Tree.expandedIcon"));
 | 
						|
    setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon"));
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Install all keyboard actions for this
 | 
						|
   */
 | 
						|
  protected void installKeyboardActions()
 | 
						|
  {
 | 
						|
    InputMap focusInputMap =
 | 
						|
      (InputMap) SharedUIDefaults.get("Tree.focusInputMap");
 | 
						|
    SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED,
 | 
						|
                                     focusInputMap);
 | 
						|
    InputMap ancestorInputMap =
 | 
						|
      (InputMap) SharedUIDefaults.get("Tree.ancestorInputMap");
 | 
						|
    SwingUtilities.replaceUIInputMap(tree,
 | 
						|
                                 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
 | 
						|
                                 ancestorInputMap);
 | 
						|
 | 
						|
    SwingUtilities.replaceUIActionMap(tree, getActionMap());
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates and returns the shared action map for JTrees.
 | 
						|
   *
 | 
						|
   * @return the shared action map for JTrees
 | 
						|
   */
 | 
						|
  private ActionMap getActionMap()
 | 
						|
  {
 | 
						|
    ActionMap am = (ActionMap) UIManager.get("Tree.actionMap");
 | 
						|
    if (am == null)
 | 
						|
      {
 | 
						|
        am = createDefaultActions();
 | 
						|
        UIManager.getLookAndFeelDefaults().put("Tree.actionMap", am);
 | 
						|
      }
 | 
						|
    return am;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates the default actions when there are none specified by the L&F.
 | 
						|
   *
 | 
						|
   * @return the default actions
 | 
						|
   */
 | 
						|
  private ActionMap createDefaultActions()
 | 
						|
  {
 | 
						|
    ActionMapUIResource am = new ActionMapUIResource();
 | 
						|
    Action action;
 | 
						|
 | 
						|
    // TreeHomeAction.
 | 
						|
    action = new TreeHomeAction(-1, "selectFirst");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeHomeAction(-1, "selectFirstChangeLead");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeHomeAction(-1, "selectFirstExtendSelection");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeHomeAction(1, "selectLast");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeHomeAction(1, "selectLastChangeLead");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeHomeAction(1, "selectLastExtendSelection");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
 | 
						|
    // TreeIncrementAction.
 | 
						|
    action = new TreeIncrementAction(-1, "selectPrevious");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeIncrementAction(-1, "selectPreviousExtendSelection");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeIncrementAction(-1, "selectPreviousChangeLead");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeIncrementAction(1, "selectNext");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeIncrementAction(1, "selectNextExtendSelection");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeIncrementAction(1, "selectNextChangeLead");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
 | 
						|
    // TreeTraverseAction.
 | 
						|
    action = new TreeTraverseAction(-1, "selectParent");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeTraverseAction(1, "selectChild");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
 | 
						|
    // TreeToggleAction.
 | 
						|
    action = new TreeToggleAction("toggleAndAnchor");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
 | 
						|
    // TreePageAction.
 | 
						|
    action = new TreePageAction(-1, "scrollUpChangeSelection");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreePageAction(-1, "scrollUpExtendSelection");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreePageAction(-1, "scrollUpChangeLead");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreePageAction(1, "scrollDownChangeSelection");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreePageAction(1, "scrollDownExtendSelection");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreePageAction(1, "scrollDownChangeLead");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
 | 
						|
    // Tree editing actions
 | 
						|
    action = new TreeStartEditingAction("startEditing");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
    action = new TreeCancelEditingAction("cancel");
 | 
						|
    am.put(action.getValue(Action.NAME), action);
 | 
						|
 | 
						|
 | 
						|
    return am;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Converts the modifiers.
 | 
						|
   *
 | 
						|
   * @param mod - modifier to convert
 | 
						|
   * @returns the new modifier
 | 
						|
   */
 | 
						|
  private int convertModifiers(int mod)
 | 
						|
  {
 | 
						|
    if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
 | 
						|
      {
 | 
						|
        mod |= KeyEvent.SHIFT_MASK;
 | 
						|
        mod &= ~ KeyEvent.SHIFT_DOWN_MASK;
 | 
						|
      }
 | 
						|
    if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
 | 
						|
      {
 | 
						|
        mod |= KeyEvent.CTRL_MASK;
 | 
						|
        mod &= ~ KeyEvent.CTRL_DOWN_MASK;
 | 
						|
      }
 | 
						|
    if ((mod & KeyEvent.META_DOWN_MASK) != 0)
 | 
						|
      {
 | 
						|
        mod |= KeyEvent.META_MASK;
 | 
						|
        mod &= ~ KeyEvent.META_DOWN_MASK;
 | 
						|
      }
 | 
						|
    if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
 | 
						|
      {
 | 
						|
        mod |= KeyEvent.ALT_MASK;
 | 
						|
        mod &= ~ KeyEvent.ALT_DOWN_MASK;
 | 
						|
      }
 | 
						|
    if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
 | 
						|
      {
 | 
						|
        mod |= KeyEvent.ALT_GRAPH_MASK;
 | 
						|
        mod &= ~ KeyEvent.ALT_GRAPH_DOWN_MASK;
 | 
						|
      }
 | 
						|
    return mod;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Install all listeners for this
 | 
						|
   */
 | 
						|
  protected void installListeners()
 | 
						|
  {
 | 
						|
    propertyChangeListener = createPropertyChangeListener();
 | 
						|
    tree.addPropertyChangeListener(propertyChangeListener);
 | 
						|
 | 
						|
    focusListener = createFocusListener();
 | 
						|
    tree.addFocusListener(focusListener);
 | 
						|
 | 
						|
    treeSelectionListener = createTreeSelectionListener();
 | 
						|
    tree.addTreeSelectionListener(treeSelectionListener);
 | 
						|
 | 
						|
    mouseListener = createMouseListener();
 | 
						|
    tree.addMouseListener(mouseListener);
 | 
						|
 | 
						|
    keyListener = createKeyListener();
 | 
						|
    tree.addKeyListener(keyListener);
 | 
						|
 | 
						|
    selectionModelPropertyChangeListener =
 | 
						|
      createSelectionModelPropertyChangeListener();
 | 
						|
    if (treeSelectionModel != null
 | 
						|
        && selectionModelPropertyChangeListener != null)
 | 
						|
      {
 | 
						|
        treeSelectionModel.addPropertyChangeListener(
 | 
						|
            selectionModelPropertyChangeListener);
 | 
						|
      }
 | 
						|
 | 
						|
    componentListener = createComponentListener();
 | 
						|
    tree.addComponentListener(componentListener);
 | 
						|
 | 
						|
    treeExpansionListener = createTreeExpansionListener();
 | 
						|
    tree.addTreeExpansionListener(treeExpansionListener);
 | 
						|
 | 
						|
    treeModelListener = createTreeModelListener();
 | 
						|
    if (treeModel != null)
 | 
						|
      treeModel.addTreeModelListener(treeModelListener);
 | 
						|
 | 
						|
    cellEditorListener = createCellEditorListener();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Install the UI for the component
 | 
						|
   *
 | 
						|
   * @param c the component to install UI for
 | 
						|
   */
 | 
						|
  public void installUI(JComponent c)
 | 
						|
  {
 | 
						|
    tree = (JTree) c;
 | 
						|
 | 
						|
    prepareForUIInstall();
 | 
						|
    installDefaults();
 | 
						|
    installComponents();
 | 
						|
    installKeyboardActions();
 | 
						|
    installListeners();
 | 
						|
    completeUIInstall();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Uninstall the defaults for the tree
 | 
						|
   */
 | 
						|
  protected void uninstallDefaults()
 | 
						|
  {
 | 
						|
    tree.setFont(null);
 | 
						|
    tree.setForeground(null);
 | 
						|
    tree.setBackground(null);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Uninstall the UI for the component
 | 
						|
   *
 | 
						|
   * @param c the component to uninstall UI for
 | 
						|
   */
 | 
						|
  public void uninstallUI(JComponent c)
 | 
						|
  {
 | 
						|
    completeEditing();
 | 
						|
 | 
						|
    prepareForUIUninstall();
 | 
						|
    uninstallDefaults();
 | 
						|
    uninstallKeyboardActions();
 | 
						|
    uninstallListeners();
 | 
						|
    uninstallComponents();
 | 
						|
    completeUIUninstall();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Paints the specified component appropriate for the look and feel. This
 | 
						|
   * method is invoked from the ComponentUI.update method when the specified
 | 
						|
   * component is being painted. Subclasses should override this method and use
 | 
						|
   * the specified Graphics object to render the content of the component.
 | 
						|
   *
 | 
						|
   * @param g the Graphics context in which to paint
 | 
						|
   * @param c the component being painted; this argument is often ignored, but
 | 
						|
   *          might be used if the UI object is stateless and shared by multiple
 | 
						|
   *          components
 | 
						|
   */
 | 
						|
  public void paint(Graphics g, JComponent c)
 | 
						|
  {
 | 
						|
    JTree tree = (JTree) c;
 | 
						|
 | 
						|
    int rows = treeState.getRowCount();
 | 
						|
 | 
						|
    if (rows == 0)
 | 
						|
      // There is nothing to do if the tree is empty.
 | 
						|
      return;
 | 
						|
 | 
						|
    Rectangle clip = g.getClipBounds();
 | 
						|
 | 
						|
    Insets insets = tree.getInsets();
 | 
						|
 | 
						|
    if (clip != null && treeModel != null)
 | 
						|
      {
 | 
						|
        int startIndex = tree.getClosestRowForLocation(clip.x, clip.y);
 | 
						|
        int endIndex = tree.getClosestRowForLocation(clip.x + clip.width,
 | 
						|
                                                     clip.y + clip.height);
 | 
						|
        // Also paint dashes to the invisible nodes below.
 | 
						|
        // These should be painted first, otherwise they may cover
 | 
						|
        // the control icons.
 | 
						|
        if (endIndex < rows)
 | 
						|
          for (int i = endIndex + 1; i < rows; i++)
 | 
						|
            {
 | 
						|
              TreePath path = treeState.getPathForRow(i);
 | 
						|
              if (isLastChild(path))
 | 
						|
                paintVerticalPartOfLeg(g, clip, insets, path);
 | 
						|
            }
 | 
						|
 | 
						|
        // The two loops are required to ensure that the lines are not
 | 
						|
        // painted over the other tree components.
 | 
						|
 | 
						|
        int n = endIndex - startIndex + 1;
 | 
						|
        Rectangle[] bounds = new Rectangle[n];
 | 
						|
        boolean[] isLeaf = new boolean[n];
 | 
						|
        boolean[] isExpanded = new boolean[n];
 | 
						|
        TreePath[] path = new TreePath[n];
 | 
						|
        int k;
 | 
						|
 | 
						|
        k = 0;
 | 
						|
        for (int i = startIndex; i <= endIndex; i++, k++)
 | 
						|
          {
 | 
						|
            path[k] = treeState.getPathForRow(i);
 | 
						|
            if (path[k] != null)
 | 
						|
              {
 | 
						|
                isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent());
 | 
						|
                isExpanded[k] = tree.isExpanded(path[k]);
 | 
						|
                bounds[k] = getPathBounds(tree, path[k]);
 | 
						|
 | 
						|
                paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k],
 | 
						|
                                         i, isExpanded[k], false, isLeaf[k]);
 | 
						|
              }
 | 
						|
            if (isLastChild(path[k]))
 | 
						|
              paintVerticalPartOfLeg(g, clip, insets, path[k]);
 | 
						|
          }
 | 
						|
 | 
						|
        k = 0;
 | 
						|
        for (int i = startIndex; i <= endIndex; i++, k++)
 | 
						|
          {
 | 
						|
            if (path[k] != null)
 | 
						|
              paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k],
 | 
						|
                       false, isLeaf[k]);
 | 
						|
          }
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Check if the path is referring to the last child of some parent.
 | 
						|
   */
 | 
						|
  private boolean isLastChild(TreePath path)
 | 
						|
  {
 | 
						|
    if (path == null)
 | 
						|
      return false;
 | 
						|
    else if (path instanceof GnuPath)
 | 
						|
      {
 | 
						|
        // Except the seldom case when the layout cache is changed, this
 | 
						|
        // optimized code will be executed.
 | 
						|
        return ((GnuPath) path).isLastChild;
 | 
						|
      }
 | 
						|
    else
 | 
						|
      {
 | 
						|
        // Non optimized general case.
 | 
						|
        TreePath parent = path.getParentPath();
 | 
						|
        if (parent == null)
 | 
						|
          return false;
 | 
						|
        int childCount = treeState.getVisibleChildCount(parent);
 | 
						|
        int p = treeModel.getIndexOfChild(parent, path.getLastPathComponent());
 | 
						|
        return p == childCount - 1;
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Ensures that the rows identified by beginRow through endRow are visible.
 | 
						|
   *
 | 
						|
   * @param beginRow is the first row
 | 
						|
   * @param endRow is the last row
 | 
						|
   */
 | 
						|
  protected void ensureRowsAreVisible(int beginRow, int endRow)
 | 
						|
  {
 | 
						|
    if (beginRow < endRow)
 | 
						|
      {
 | 
						|
        int temp = endRow;
 | 
						|
        endRow = beginRow;
 | 
						|
        beginRow = temp;
 | 
						|
      }
 | 
						|
 | 
						|
    for (int i = beginRow; i < endRow; i++)
 | 
						|
      {
 | 
						|
        TreePath path = getPathForRow(tree, i);
 | 
						|
        if (! tree.isVisible(path))
 | 
						|
          tree.makeVisible(path);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the preferred minimum size.
 | 
						|
   *
 | 
						|
   * @param newSize is the new preferred minimum size.
 | 
						|
   */
 | 
						|
  public void setPreferredMinSize(Dimension newSize)
 | 
						|
  {
 | 
						|
    preferredMinSize = newSize;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the preferred minimum size.
 | 
						|
   *
 | 
						|
   * @returns the preferred minimum size.
 | 
						|
   */
 | 
						|
  public Dimension getPreferredMinSize()
 | 
						|
  {
 | 
						|
    if (preferredMinSize == null)
 | 
						|
      return getPreferredSize(tree);
 | 
						|
    else
 | 
						|
      return preferredMinSize;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the preferred size to properly display the tree, this is a cover
 | 
						|
   * method for getPreferredSize(c, false).
 | 
						|
   *
 | 
						|
   * @param c the component whose preferred size is being queried; this argument
 | 
						|
   *          is often ignored but might be used if the UI object is stateless
 | 
						|
   *          and shared by multiple components
 | 
						|
   * @return the preferred size
 | 
						|
   */
 | 
						|
  public Dimension getPreferredSize(JComponent c)
 | 
						|
  {
 | 
						|
    return getPreferredSize(c, false);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the preferred size to represent the tree in c. If checkConsistancy
 | 
						|
   * is true, checkConsistancy is messaged first.
 | 
						|
   *
 | 
						|
   * @param c the component whose preferred size is being queried.
 | 
						|
   * @param checkConsistancy if true must check consistancy
 | 
						|
   * @return the preferred size
 | 
						|
   */
 | 
						|
  public Dimension getPreferredSize(JComponent c, boolean checkConsistancy)
 | 
						|
  {
 | 
						|
    if (! validCachedPreferredSize)
 | 
						|
      {
 | 
						|
        Rectangle size = tree.getBounds();
 | 
						|
        // Add the scrollbar dimensions to the preferred size.
 | 
						|
        preferredSize = new Dimension(treeState.getPreferredWidth(size),
 | 
						|
                                      treeState.getPreferredHeight());
 | 
						|
        validCachedPreferredSize = true;
 | 
						|
      }
 | 
						|
    return preferredSize;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the minimum size for this component. Which will be the min
 | 
						|
   * preferred size or (0,0).
 | 
						|
   *
 | 
						|
   * @param c the component whose min size is being queried.
 | 
						|
   * @returns the preferred size or null
 | 
						|
   */
 | 
						|
  public Dimension getMinimumSize(JComponent c)
 | 
						|
  {
 | 
						|
    return preferredMinSize = getPreferredSize(c);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the maximum size for the component, which will be the preferred
 | 
						|
   * size if the instance is currently in JTree or (0,0).
 | 
						|
   *
 | 
						|
   * @param c the component whose preferred size is being queried
 | 
						|
   * @return the max size or null
 | 
						|
   */
 | 
						|
  public Dimension getMaximumSize(JComponent c)
 | 
						|
  {
 | 
						|
    return getPreferredSize(c);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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 be invoked
 | 
						|
   * on the current editor. Then completeEditing will be messaged with false,
 | 
						|
   * true, false to cancel any lingering editing.
 | 
						|
   */
 | 
						|
  protected void completeEditing()
 | 
						|
  {
 | 
						|
    if (tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing
 | 
						|
        && editingComponent != null)
 | 
						|
      cellEditor.stopCellEditing();
 | 
						|
 | 
						|
    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.
 | 
						|
   *
 | 
						|
   * @param messageStop message to stop editing
 | 
						|
   * @param messageCancel message to cancel editing
 | 
						|
   * @param messageTree message to treeModel
 | 
						|
   */
 | 
						|
  protected void completeEditing(boolean messageStop, boolean messageCancel,
 | 
						|
                                 boolean messageTree)
 | 
						|
  {
 | 
						|
    // Make no attempt to complete the non existing editing session.
 | 
						|
    if (stopEditingInCompleteEditing && editingComponent != null)
 | 
						|
      {
 | 
						|
        Component comp = editingComponent;
 | 
						|
        TreePath p = editingPath;
 | 
						|
        editingComponent = null;
 | 
						|
        editingPath = null;
 | 
						|
        if (messageStop)
 | 
						|
          cellEditor.stopCellEditing();
 | 
						|
        else if (messageCancel)
 | 
						|
          cellEditor.cancelCellEditing();
 | 
						|
 | 
						|
        tree.remove(comp);
 | 
						|
 | 
						|
        if (editorHasDifferentSize)
 | 
						|
          {
 | 
						|
            treeState.invalidatePathBounds(p);
 | 
						|
            updateSize();
 | 
						|
          }
 | 
						|
        else
 | 
						|
          {
 | 
						|
            // Need to refresh the tree.
 | 
						|
            Rectangle b = getPathBounds(tree, p);
 | 
						|
            tree.repaint(0, b.y, tree.getWidth(), b.height);
 | 
						|
          }
 | 
						|
 | 
						|
        if (messageTree)
 | 
						|
          {
 | 
						|
            Object value = cellEditor.getCellEditorValue();
 | 
						|
            treeModel.valueForPathChanged(p, value);
 | 
						|
          }
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Will start editing for node if there is a cellEditor and shouldSelectCall
 | 
						|
   * returns true. This assumes that path is valid and visible.
 | 
						|
   *
 | 
						|
   * @param path is the path to start editing
 | 
						|
   * @param event is the MouseEvent performed on the path
 | 
						|
   * @return true if successful
 | 
						|
   */
 | 
						|
  protected boolean startEditing(TreePath path, MouseEvent event)
 | 
						|
  {
 | 
						|
    // Maybe cancel editing.
 | 
						|
    if (isEditing(tree) && tree.getInvokesStopCellEditing()
 | 
						|
        && ! stopEditing(tree))
 | 
						|
      return false;
 | 
						|
 | 
						|
    completeEditing();
 | 
						|
    TreeCellEditor ed = cellEditor;
 | 
						|
    if (ed != null && tree.isPathEditable(path))
 | 
						|
      {
 | 
						|
        if (ed.isCellEditable(event))
 | 
						|
          {
 | 
						|
            editingRow = getRowForPath(tree, path);
 | 
						|
            Object value = path.getLastPathComponent();
 | 
						|
            boolean isSelected = tree.isPathSelected(path);
 | 
						|
            boolean isExpanded = tree.isExpanded(editingPath);
 | 
						|
            boolean isLeaf = treeModel.isLeaf(value);
 | 
						|
            editingComponent = ed.getTreeCellEditorComponent(tree, value,
 | 
						|
                                                             isSelected,
 | 
						|
                                                             isExpanded,
 | 
						|
                                                             isLeaf,
 | 
						|
                                                             editingRow);
 | 
						|
 | 
						|
            Rectangle bounds = getPathBounds(tree, path);
 | 
						|
 | 
						|
            Dimension size = editingComponent.getPreferredSize();
 | 
						|
            int rowHeight = getRowHeight();
 | 
						|
            if (size.height != bounds.height && rowHeight > 0)
 | 
						|
              size.height = rowHeight;
 | 
						|
 | 
						|
            if (size.width != bounds.width || size.height != bounds.height)
 | 
						|
              {
 | 
						|
                editorHasDifferentSize = true;
 | 
						|
                treeState.invalidatePathBounds(path);
 | 
						|
                updateSize();
 | 
						|
              }
 | 
						|
            else
 | 
						|
              editorHasDifferentSize = false;
 | 
						|
 | 
						|
            // The editing component must be added to its container. We add the
 | 
						|
            // container, not the editing component itself.
 | 
						|
            tree.add(editingComponent);
 | 
						|
            editingComponent.setBounds(bounds.x, bounds.y, size.width,
 | 
						|
                                       size.height);
 | 
						|
            editingComponent.validate();
 | 
						|
            editingPath = path;
 | 
						|
 | 
						|
            if (ed.shouldSelectCell(event))
 | 
						|
              {
 | 
						|
                stopEditingInCompleteEditing = false;
 | 
						|
                tree.setSelectionRow(editingRow);
 | 
						|
                stopEditingInCompleteEditing = true;
 | 
						|
              }
 | 
						|
 | 
						|
            editorRequestFocus(editingComponent);
 | 
						|
            // Register MouseInputHandler to redispatch initial mouse events
 | 
						|
            // correctly.
 | 
						|
            if (event instanceof MouseEvent)
 | 
						|
              {
 | 
						|
                Point p = SwingUtilities.convertPoint(tree, event.getX(), event.getY(),
 | 
						|
                                                      editingComponent);
 | 
						|
                Component active =
 | 
						|
                  SwingUtilities.getDeepestComponentAt(editingComponent, p.x, p.y);
 | 
						|
                if (active != null)
 | 
						|
                  {
 | 
						|
                    MouseInputHandler ih = new MouseInputHandler(tree, active, event);
 | 
						|
 | 
						|
                  }
 | 
						|
              }
 | 
						|
 | 
						|
            return true;
 | 
						|
          }
 | 
						|
        else
 | 
						|
          editingComponent = null;
 | 
						|
      }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Requests focus on the editor. The method is necessary since the
 | 
						|
   * DefaultTreeCellEditor returns a container that contains the
 | 
						|
   * actual editor, and we want to request focus on the editor, not the
 | 
						|
   * container.
 | 
						|
   */
 | 
						|
  private void editorRequestFocus(Component c)
 | 
						|
  {
 | 
						|
    if (c instanceof Container)
 | 
						|
      {
 | 
						|
        // TODO: Maybe do something more reasonable here, like queriying the
 | 
						|
        // FocusTraversalPolicy.
 | 
						|
        Container cont = (Container) c;
 | 
						|
        if (cont.getComponentCount() > 0)
 | 
						|
          cont.getComponent(0).requestFocus();
 | 
						|
      }
 | 
						|
    else if (c.isFocusable())
 | 
						|
      c.requestFocus();
 | 
						|
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or
 | 
						|
   * collapse region of the row, this will toggle the row.
 | 
						|
   *
 | 
						|
   * @param path the path we are concerned with
 | 
						|
   * @param mouseX is the cursor's x position
 | 
						|
   * @param mouseY is the cursor's y position
 | 
						|
   */
 | 
						|
  protected void checkForClickInExpandControl(TreePath path, int mouseX,
 | 
						|
                                              int mouseY)
 | 
						|
  {
 | 
						|
    if (isLocationInExpandControl(path, mouseX, mouseY))
 | 
						|
      handleExpandControlClick(path, mouseX, mouseY);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in
 | 
						|
   * the area of row that is used to expand/collpse the node and the node at row
 | 
						|
   * does not represent a leaf.
 | 
						|
   *
 | 
						|
   * @param path the path we are concerned with
 | 
						|
   * @param mouseX is the cursor's x position
 | 
						|
   * @param mouseY is the cursor's y position
 | 
						|
   * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in
 | 
						|
   *         the area of row that is used to expand/collpse the node and the
 | 
						|
   *         node at row does not represent a leaf.
 | 
						|
   */
 | 
						|
  protected boolean isLocationInExpandControl(TreePath path, int mouseX,
 | 
						|
                                              int mouseY)
 | 
						|
  {
 | 
						|
    boolean cntlClick = false;
 | 
						|
    if (! treeModel.isLeaf(path.getLastPathComponent()))
 | 
						|
      {
 | 
						|
        int width;
 | 
						|
        Icon expandedIcon = getExpandedIcon();
 | 
						|
        if (expandedIcon != null)
 | 
						|
          width = expandedIcon.getIconWidth();
 | 
						|
        else
 | 
						|
          // Only guessing. This is the width of
 | 
						|
          // the tree control icon in Metal L&F.
 | 
						|
          width = 18;
 | 
						|
 | 
						|
        Insets i = tree.getInsets();
 | 
						|
 | 
						|
        int depth;
 | 
						|
        if (isRootVisible())
 | 
						|
          depth = path.getPathCount()-1;
 | 
						|
        else
 | 
						|
          depth = path.getPathCount()-2;
 | 
						|
 | 
						|
        int left = getRowX(tree.getRowForPath(path), depth)
 | 
						|
                   - width + i.left;
 | 
						|
        cntlClick = mouseX >= left && mouseX <= left + width;
 | 
						|
      }
 | 
						|
    return cntlClick;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Messaged when the user clicks the particular row, this invokes
 | 
						|
   * toggleExpandState.
 | 
						|
   *
 | 
						|
   * @param path the path we are concerned with
 | 
						|
   * @param mouseX is the cursor's x position
 | 
						|
   * @param mouseY is the cursor's y position
 | 
						|
   */
 | 
						|
  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 scroll 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).
 | 
						|
   *
 | 
						|
   * @param path the path we are concerned with
 | 
						|
   */
 | 
						|
  protected void toggleExpandState(TreePath path)
 | 
						|
  {
 | 
						|
    // tree.isExpanded(path) would do the same, but treeState knows faster.
 | 
						|
    if (treeState.isExpanded(path))
 | 
						|
      tree.collapsePath(path);
 | 
						|
    else
 | 
						|
      tree.expandPath(path);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returning true signifies a mouse event on the node should toggle the
 | 
						|
   * selection of only the row under the mouse. The BasisTreeUI treats the
 | 
						|
   * event as "toggle selection event" if the CTRL button was pressed while
 | 
						|
   * clicking. The event is not counted as toggle event if the associated
 | 
						|
   * tree does not support the multiple selection.
 | 
						|
   *
 | 
						|
   * @param event is the MouseEvent performed on the row.
 | 
						|
   * @return true signifies a mouse event on the node should toggle the
 | 
						|
   *         selection of only the row under the mouse.
 | 
						|
   */
 | 
						|
  protected boolean isToggleSelectionEvent(MouseEvent event)
 | 
						|
  {
 | 
						|
    return
 | 
						|
      (tree.getSelectionModel().getSelectionMode() !=
 | 
						|
        TreeSelectionModel.SINGLE_TREE_SELECTION) &&
 | 
						|
      ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returning true signifies a mouse event on the node should select from the
 | 
						|
   * anchor point. The BasisTreeUI treats the event as "multiple selection
 | 
						|
   * event" if the SHIFT button was pressed while clicking. The event is not
 | 
						|
   * counted as multiple selection event if the associated tree does not support
 | 
						|
   * the multiple selection.
 | 
						|
   *
 | 
						|
   * @param event is the MouseEvent performed on the node.
 | 
						|
   * @return true signifies a mouse event on the node should select from the
 | 
						|
   *         anchor point.
 | 
						|
   */
 | 
						|
  protected boolean isMultiSelectEvent(MouseEvent event)
 | 
						|
  {
 | 
						|
    return
 | 
						|
      (tree.getSelectionModel().getSelectionMode() !=
 | 
						|
        TreeSelectionModel.SINGLE_TREE_SELECTION) &&
 | 
						|
      ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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.
 | 
						|
   *
 | 
						|
   * @param event is the MouseEvent performed on the row.
 | 
						|
   * @return true indicates the row under the mouse should be toggled based on
 | 
						|
   *         the event.
 | 
						|
   */
 | 
						|
  protected boolean isToggleEvent(MouseEvent event)
 | 
						|
  {
 | 
						|
    boolean toggle = false;
 | 
						|
    if (SwingUtilities.isLeftMouseButton(event))
 | 
						|
      {
 | 
						|
        int clickCount = tree.getToggleClickCount();
 | 
						|
        if (clickCount > 0 && event.getClickCount() == clickCount)
 | 
						|
          toggle = true;
 | 
						|
      }
 | 
						|
    return toggle;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Messaged to update the selection based on a MouseEvent over a particular
 | 
						|
   * row. If the even 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 the previous selection is cleared.</p>
 | 
						|
   *
 | 
						|
   * @param path is the path selected for an event
 | 
						|
   * @param event is the MouseEvent performed on the path.
 | 
						|
   *
 | 
						|
   * @see #isToggleSelectionEvent(MouseEvent)
 | 
						|
   * @see #isMultiSelectEvent(MouseEvent)
 | 
						|
   */
 | 
						|
  protected void selectPathForEvent(TreePath path, MouseEvent event)
 | 
						|
  {
 | 
						|
    if (isToggleSelectionEvent(event))
 | 
						|
      {
 | 
						|
        // The event selects or unselects the clicked row.
 | 
						|
        if (tree.isPathSelected(path))
 | 
						|
          tree.removeSelectionPath(path);
 | 
						|
        else
 | 
						|
          {
 | 
						|
            tree.addSelectionPath(path);
 | 
						|
            tree.setAnchorSelectionPath(path);
 | 
						|
          }
 | 
						|
      }
 | 
						|
    else if (isMultiSelectEvent(event))
 | 
						|
      {
 | 
						|
        // The event extends selection form anchor till the clicked row.
 | 
						|
        TreePath anchor = tree.getAnchorSelectionPath();
 | 
						|
        if (anchor != null)
 | 
						|
          {
 | 
						|
            int aRow = getRowForPath(tree, anchor);
 | 
						|
            tree.addSelectionInterval(aRow, getRowForPath(tree, path));
 | 
						|
          }
 | 
						|
        else
 | 
						|
          tree.addSelectionPath(path);
 | 
						|
      }
 | 
						|
    else
 | 
						|
      {
 | 
						|
        // This is an ordinary event that just selects the clicked row.
 | 
						|
        tree.setSelectionPath(path);
 | 
						|
        if (isToggleEvent(event))
 | 
						|
          toggleExpandState(path);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if the node at <code>row</code> is a leaf.
 | 
						|
   *
 | 
						|
   * @param row is the row we are concerned with.
 | 
						|
   * @return true if the node at <code>row</code> is a leaf.
 | 
						|
   */
 | 
						|
  protected boolean isLeaf(int row)
 | 
						|
  {
 | 
						|
    TreePath pathForRow = getPathForRow(tree, row);
 | 
						|
    if (pathForRow == null)
 | 
						|
      return true;
 | 
						|
 | 
						|
    Object node = pathForRow.getLastPathComponent();
 | 
						|
    return treeModel.isLeaf(node);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * The action to start editing at the current lead selection path.
 | 
						|
   */
 | 
						|
  class TreeStartEditingAction
 | 
						|
      extends AbstractAction
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Creates the new tree cancel editing action.
 | 
						|
     *
 | 
						|
     * @param name the name of the action (used in toString).
 | 
						|
     */
 | 
						|
    public TreeStartEditingAction(String name)
 | 
						|
    {
 | 
						|
      super(name);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Start editing at the current lead selection path.
 | 
						|
     *
 | 
						|
     * @param e the ActionEvent that caused this action.
 | 
						|
     */
 | 
						|
    public void actionPerformed(ActionEvent e)
 | 
						|
    {
 | 
						|
      TreePath lead = tree.getLeadSelectionPath();
 | 
						|
      if (!tree.isEditing())
 | 
						|
        tree.startEditingAtPath(lead);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public ComponentHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when the component's position changes.
 | 
						|
     *
 | 
						|
     * @param e the event that occurs when moving the component
 | 
						|
     */
 | 
						|
    public void componentMoved(ComponentEvent e)
 | 
						|
    {
 | 
						|
      if (timer == null)
 | 
						|
        {
 | 
						|
          JScrollPane scrollPane = getScrollPane();
 | 
						|
          if (scrollPane == null)
 | 
						|
            updateSize();
 | 
						|
          else
 | 
						|
            {
 | 
						|
              // Determine the scrollbar that is adjusting, if any, and
 | 
						|
              // start the timer for that. If no scrollbar is adjusting,
 | 
						|
              // we simply call updateSize().
 | 
						|
              scrollBar = scrollPane.getVerticalScrollBar();
 | 
						|
              if (scrollBar == null || !scrollBar.getValueIsAdjusting())
 | 
						|
                {
 | 
						|
                  // It's not the vertical scrollbar, try the horizontal one.
 | 
						|
                  scrollBar = scrollPane.getHorizontalScrollBar();
 | 
						|
                  if (scrollBar != null && scrollBar.getValueIsAdjusting())
 | 
						|
                    startTimer();
 | 
						|
                  else
 | 
						|
                    updateSize();
 | 
						|
                }
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  startTimer();
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates, if necessary, and starts a Timer to check if needed 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.
 | 
						|
     *
 | 
						|
     * @return JScrollPane housing the JTree, or null if one isn't found.
 | 
						|
     */
 | 
						|
    protected JScrollPane getScrollPane()
 | 
						|
    {
 | 
						|
      JScrollPane found = null;
 | 
						|
      Component p = tree.getParent();
 | 
						|
      while (p != null && !(p instanceof JScrollPane))
 | 
						|
        p = p.getParent();
 | 
						|
      if (p instanceof JScrollPane)
 | 
						|
        found = (JScrollPane) p;
 | 
						|
      return found;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Public as a result of Timer. If the scrollBar is null, or not adjusting,
 | 
						|
     * this stops the timer and updates the sizing.
 | 
						|
     *
 | 
						|
     * @param ae is the action performed
 | 
						|
     */
 | 
						|
    public void actionPerformed(ActionEvent ae)
 | 
						|
    {
 | 
						|
      if (scrollBar == null || !scrollBar.getValueIsAdjusting())
 | 
						|
        {
 | 
						|
          if (timer != null)
 | 
						|
            timer.stop();
 | 
						|
          updateSize();
 | 
						|
          timer = null;
 | 
						|
          scrollBar = null;
 | 
						|
        }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Listener responsible for getting cell editing events and updating the tree
 | 
						|
   * accordingly.
 | 
						|
   */
 | 
						|
  public class CellEditorHandler
 | 
						|
      implements CellEditorListener
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public CellEditorHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Messaged when editing has stopped in the tree. Tells the listeners
 | 
						|
     * editing has stopped.
 | 
						|
     *
 | 
						|
     * @param e is the notification event
 | 
						|
     */
 | 
						|
    public void editingStopped(ChangeEvent e)
 | 
						|
    {
 | 
						|
      completeEditing(false, false, true);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Messaged when editing has been canceled in the tree. This tells the
 | 
						|
     * listeners the editor has canceled editing.
 | 
						|
     *
 | 
						|
     * @param e is the notification event
 | 
						|
     */
 | 
						|
    public void editingCanceled(ChangeEvent e)
 | 
						|
    {
 | 
						|
      completeEditing(false, false, false);
 | 
						|
    }
 | 
						|
  } // CellEditorHandler
 | 
						|
 | 
						|
  /**
 | 
						|
   * Repaints the lead selection row when focus is lost/grained.
 | 
						|
   */
 | 
						|
  public class FocusHandler
 | 
						|
      implements FocusListener
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public FocusHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when focus is activated on the tree we're in, redraws the lead
 | 
						|
     * row. Invoked when a component gains the keyboard focus. The method
 | 
						|
     * repaints the lead row that is shown differently when the tree is in
 | 
						|
     * focus.
 | 
						|
     *
 | 
						|
     * @param e is the focus event that is activated
 | 
						|
     */
 | 
						|
    public void focusGained(FocusEvent e)
 | 
						|
    {
 | 
						|
      repaintLeadRow();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when focus is deactivated on the tree we're in, redraws the lead
 | 
						|
     * row. Invoked when a component loses the keyboard focus. The method
 | 
						|
     * repaints the lead row that is shown differently when the tree is in
 | 
						|
     * focus.
 | 
						|
     *
 | 
						|
     * @param e is the focus event that is deactivated
 | 
						|
     */
 | 
						|
    public void focusLost(FocusEvent e)
 | 
						|
    {
 | 
						|
      repaintLeadRow();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Repaint the lead row.
 | 
						|
     */
 | 
						|
    void repaintLeadRow()
 | 
						|
    {
 | 
						|
      TreePath lead = tree.getLeadSelectionPath();
 | 
						|
      if (lead != null)
 | 
						|
        tree.repaint(tree.getPathBounds(lead));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * This is used to get multiple key down events to appropriately genereate
 | 
						|
   * events.
 | 
						|
   */
 | 
						|
  public class KeyHandler
 | 
						|
      extends KeyAdapter
 | 
						|
  {
 | 
						|
    /** Key code that is being generated for. */
 | 
						|
    protected Action repeatKeyAction;
 | 
						|
 | 
						|
    /** Set to true while keyPressed is active */
 | 
						|
    protected boolean isKeyDown;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public KeyHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 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.
 | 
						|
     *
 | 
						|
     * @param e the key typed
 | 
						|
     */
 | 
						|
    public void keyTyped(KeyEvent e)
 | 
						|
    {
 | 
						|
      char typed = Character.toLowerCase(e.getKeyChar());
 | 
						|
      for (int row = tree.getLeadSelectionRow() + 1;
 | 
						|
        row < tree.getRowCount(); row++)
 | 
						|
        {
 | 
						|
           if (checkMatch(row, typed))
 | 
						|
             {
 | 
						|
               tree.setSelectionRow(row);
 | 
						|
               tree.scrollRowToVisible(row);
 | 
						|
               return;
 | 
						|
             }
 | 
						|
        }
 | 
						|
 | 
						|
      // Not found below, search above:
 | 
						|
      for (int row = 0; row < tree.getLeadSelectionRow(); row++)
 | 
						|
        {
 | 
						|
           if (checkMatch(row, typed))
 | 
						|
             {
 | 
						|
               tree.setSelectionRow(row);
 | 
						|
               tree.scrollRowToVisible(row);
 | 
						|
               return;
 | 
						|
             }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Check if the given tree row starts with this character
 | 
						|
     *
 | 
						|
     * @param row the tree row
 | 
						|
     * @param typed the typed char, must be converted to lowercase
 | 
						|
     * @return true if the given tree row starts with this character
 | 
						|
     */
 | 
						|
    boolean checkMatch(int row, char typed)
 | 
						|
    {
 | 
						|
      TreePath path = treeState.getPathForRow(row);
 | 
						|
      String node = path.getLastPathComponent().toString();
 | 
						|
      if (node.length() > 0)
 | 
						|
        {
 | 
						|
          char x = node.charAt(0);
 | 
						|
          if (typed == Character.toLowerCase(x))
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when a key has been pressed.
 | 
						|
     *
 | 
						|
     * @param e the key pressed
 | 
						|
     */
 | 
						|
    public void keyPressed(KeyEvent e)
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when a key has been released
 | 
						|
     *
 | 
						|
     * @param e the key released
 | 
						|
     */
 | 
						|
    public void keyReleased(KeyEvent e)
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * MouseListener is responsible for updating the selection based on mouse
 | 
						|
   * events.
 | 
						|
   */
 | 
						|
  public class MouseHandler
 | 
						|
    extends MouseAdapter
 | 
						|
    implements MouseMotionListener
 | 
						|
  {
 | 
						|
 | 
						|
    /**
 | 
						|
     * If the cell has been selected on mouse press.
 | 
						|
     */
 | 
						|
    private boolean selectedOnPress;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public MouseHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when a mouse button has been pressed on a component.
 | 
						|
     *
 | 
						|
     * @param e is the mouse event that occured
 | 
						|
     */
 | 
						|
    public void mousePressed(MouseEvent e)
 | 
						|
    {
 | 
						|
      if (! e.isConsumed())
 | 
						|
        {
 | 
						|
          handleEvent(e);
 | 
						|
          selectedOnPress = true;
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          selectedOnPress = false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when a mouse button is pressed on a component and then dragged.
 | 
						|
     * MOUSE_DRAGGED events will continue to be delivered to the component where
 | 
						|
     * the drag originated until the mouse button is released (regardless of
 | 
						|
     * whether the mouse position is within the bounds of the component).
 | 
						|
     *
 | 
						|
     * @param e is the mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseDragged(MouseEvent e)
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when the mouse button has been moved on a component (with no
 | 
						|
     * buttons no down).
 | 
						|
     *
 | 
						|
     * @param e the mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseMoved(MouseEvent e)
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when a mouse button has been released on a component.
 | 
						|
     *
 | 
						|
     * @param e is the mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseReleased(MouseEvent e)
 | 
						|
    {
 | 
						|
      if (! e.isConsumed() && ! selectedOnPress)
 | 
						|
        handleEvent(e);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Handles press and release events.
 | 
						|
     *
 | 
						|
     * @param e the mouse event
 | 
						|
     */
 | 
						|
    private void handleEvent(MouseEvent e)
 | 
						|
    {
 | 
						|
      if (tree != null && tree.isEnabled())
 | 
						|
        {
 | 
						|
          // Maybe stop editing.
 | 
						|
          if (isEditing(tree) && tree.getInvokesStopCellEditing()
 | 
						|
              && ! stopEditing(tree))
 | 
						|
            return;
 | 
						|
 | 
						|
          // Explicitly request focus.
 | 
						|
          tree.requestFocusInWindow();
 | 
						|
 | 
						|
          int x = e.getX();
 | 
						|
          int y = e.getY();
 | 
						|
          TreePath path = getClosestPathForLocation(tree, x, y);
 | 
						|
          if (path != null)
 | 
						|
            {
 | 
						|
              Rectangle b = getPathBounds(tree, path);
 | 
						|
              if (y <= b.y + b.height)
 | 
						|
                {
 | 
						|
                  if (SwingUtilities.isLeftMouseButton(e))
 | 
						|
                    checkForClickInExpandControl(path, x, y);
 | 
						|
                  if (x > b.x && x <= b.x + b.width)
 | 
						|
                    {
 | 
						|
                      if (! startEditing(path, e))
 | 
						|
                        selectPathForEvent(path, e);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * MouseInputHandler handles passing all mouse events, including mouse motion
 | 
						|
   * events, until the mouse is released to the destination it is constructed
 | 
						|
   * with.
 | 
						|
   */
 | 
						|
  public class MouseInputHandler
 | 
						|
      implements MouseInputListener
 | 
						|
  {
 | 
						|
    /** Source that events are coming from */
 | 
						|
    protected Component source;
 | 
						|
 | 
						|
    /** Destination that receives all events. */
 | 
						|
    protected Component destination;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     *
 | 
						|
     * @param source that events are coming from
 | 
						|
     * @param destination that receives all events
 | 
						|
     * @param e is the event received
 | 
						|
     */
 | 
						|
    public MouseInputHandler(Component source, Component destination,
 | 
						|
                             MouseEvent e)
 | 
						|
    {
 | 
						|
      this.source = source;
 | 
						|
      this.destination = destination;
 | 
						|
      source.addMouseListener(this);
 | 
						|
      source.addMouseMotionListener(this);
 | 
						|
      dispatch(e);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when the mouse button has been clicked (pressed and released) on
 | 
						|
     * a component.
 | 
						|
     *
 | 
						|
     * @param e mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseClicked(MouseEvent e)
 | 
						|
    {
 | 
						|
      dispatch(e);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when a mouse button has been pressed on a component.
 | 
						|
     *
 | 
						|
     * @param e mouse event that occured
 | 
						|
     */
 | 
						|
    public void mousePressed(MouseEvent e)
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when a mouse button has been released on a component.
 | 
						|
     *
 | 
						|
     * @param e mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseReleased(MouseEvent e)
 | 
						|
    {
 | 
						|
      dispatch(e);
 | 
						|
      removeFromSource();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when the mouse enters a component.
 | 
						|
     *
 | 
						|
     * @param e mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseEntered(MouseEvent e)
 | 
						|
    {
 | 
						|
      if (! SwingUtilities.isLeftMouseButton(e))
 | 
						|
        removeFromSource();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when the mouse exits a component.
 | 
						|
     *
 | 
						|
     * @param e mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseExited(MouseEvent e)
 | 
						|
    {
 | 
						|
      if (! SwingUtilities.isLeftMouseButton(e))
 | 
						|
        removeFromSource();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when a mouse button is pressed on a component and then dragged.
 | 
						|
     * MOUSE_DRAGGED events will continue to be delivered to the component where
 | 
						|
     * the drag originated until the mouse button is released (regardless of
 | 
						|
     * whether the mouse position is within the bounds of the component).
 | 
						|
     *
 | 
						|
     * @param e mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseDragged(MouseEvent e)
 | 
						|
    {
 | 
						|
      dispatch(e);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when the mouse cursor has been moved onto a component but no
 | 
						|
     * buttons have been pushed.
 | 
						|
     *
 | 
						|
     * @param e mouse event that occured
 | 
						|
     */
 | 
						|
    public void mouseMoved(MouseEvent e)
 | 
						|
    {
 | 
						|
      removeFromSource();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Removes event from the source
 | 
						|
     */
 | 
						|
    protected void removeFromSource()
 | 
						|
    {
 | 
						|
      if (source != null)
 | 
						|
        {
 | 
						|
          source.removeMouseListener(this);
 | 
						|
          source.removeMouseMotionListener(this);
 | 
						|
        }
 | 
						|
      source = null;
 | 
						|
      destination = null;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Redispatches mouse events to the destination.
 | 
						|
     *
 | 
						|
     * @param e the mouse event to redispatch
 | 
						|
     */
 | 
						|
    private void dispatch(MouseEvent e)
 | 
						|
    {
 | 
						|
      if (destination != null)
 | 
						|
        {
 | 
						|
          MouseEvent e2 = SwingUtilities.convertMouseEvent(source, e,
 | 
						|
                                                           destination);
 | 
						|
          destination.dispatchEvent(e2);
 | 
						|
        }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Class responsible for getting size of node, method is forwarded to
 | 
						|
   * BasicTreeUI method. X location does not include insets, that is handled in
 | 
						|
   * getPathBounds.
 | 
						|
   */
 | 
						|
  public class NodeDimensionsHandler
 | 
						|
      extends AbstractLayoutCache.NodeDimensions
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public NodeDimensionsHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns, by reference in bounds, the size and x origin to place value at.
 | 
						|
     * The calling method is responsible for determining the Y location. If
 | 
						|
     * bounds is null, a newly created Rectangle should be returned, otherwise
 | 
						|
     * the value should be placed in bounds and returned.
 | 
						|
     *
 | 
						|
     * @param cell the value to be represented
 | 
						|
     * @param row row being queried
 | 
						|
     * @param depth the depth of the row
 | 
						|
     * @param expanded true if row is expanded
 | 
						|
     * @param size a Rectangle containing the size needed to represent value
 | 
						|
     * @return containing the node dimensions, or null if node has no dimension
 | 
						|
     */
 | 
						|
    public Rectangle getNodeDimensions(Object cell, int row, int depth,
 | 
						|
                                       boolean expanded, Rectangle size)
 | 
						|
    {
 | 
						|
      Dimension prefSize;
 | 
						|
      if (editingComponent != null && editingRow == row)
 | 
						|
        {
 | 
						|
          // Editing, ask editor for preferred size.
 | 
						|
          prefSize = editingComponent.getPreferredSize();
 | 
						|
          int rowHeight = getRowHeight();
 | 
						|
          if (rowHeight > 0 && rowHeight != prefSize.height)
 | 
						|
            prefSize.height = rowHeight;
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          // Not editing, ask renderer for preferred size.
 | 
						|
          Component rend =
 | 
						|
            currentCellRenderer.getTreeCellRendererComponent(tree, cell,
 | 
						|
                                                       tree.isRowSelected(row),
 | 
						|
                                                       expanded,
 | 
						|
                                                       treeModel.isLeaf(cell),
 | 
						|
                                                       row, false);
 | 
						|
          // Make sure the layout is valid.
 | 
						|
          rendererPane.add(rend);
 | 
						|
          rend.validate();
 | 
						|
          prefSize = rend.getPreferredSize();
 | 
						|
        }
 | 
						|
      if (size != null)
 | 
						|
        {
 | 
						|
          size.x = getRowX(row, depth);
 | 
						|
          // FIXME: This should be handled by the layout cache.
 | 
						|
          size.y = prefSize.height * row;
 | 
						|
          size.width =  prefSize.width;
 | 
						|
          size.height = prefSize.height;
 | 
						|
        }
 | 
						|
      else
 | 
						|
        // FIXME: The y should be handled by the layout cache.
 | 
						|
        size = new Rectangle(getRowX(row, depth), prefSize.height * row, prefSize.width,
 | 
						|
                             prefSize.height);
 | 
						|
 | 
						|
      return size;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the amount to indent the given row
 | 
						|
     *
 | 
						|
     * @return amount to indent the given row.
 | 
						|
     */
 | 
						|
    protected int getRowX(int row, int depth)
 | 
						|
    {
 | 
						|
      return BasicTreeUI.this.getRowX(row, depth);
 | 
						|
    }
 | 
						|
  } // NodeDimensionsHandler
 | 
						|
 | 
						|
  /**
 | 
						|
   * PropertyChangeListener for the tree. Updates the appropriate variable, or
 | 
						|
   * TreeState, based on what changes.
 | 
						|
   */
 | 
						|
  public class PropertyChangeHandler
 | 
						|
      implements PropertyChangeListener
 | 
						|
  {
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public PropertyChangeHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * This method gets called when a bound property is changed.
 | 
						|
     *
 | 
						|
     * @param event A PropertyChangeEvent object describing the event source and
 | 
						|
     *          the property that has changed.
 | 
						|
     */
 | 
						|
    public void propertyChange(PropertyChangeEvent event)
 | 
						|
    {
 | 
						|
      String property = event.getPropertyName();
 | 
						|
      if (property.equals(JTree.ROOT_VISIBLE_PROPERTY))
 | 
						|
        {
 | 
						|
          validCachedPreferredSize = false;
 | 
						|
          treeState.setRootVisible(tree.isRootVisible());
 | 
						|
          tree.repaint();
 | 
						|
        }
 | 
						|
      else if (property.equals(JTree.SELECTION_MODEL_PROPERTY))
 | 
						|
        {
 | 
						|
          treeSelectionModel = tree.getSelectionModel();
 | 
						|
          treeSelectionModel.setRowMapper(treeState);
 | 
						|
        }
 | 
						|
      else if (property.equals(JTree.TREE_MODEL_PROPERTY))
 | 
						|
        {
 | 
						|
          setModel(tree.getModel());
 | 
						|
        }
 | 
						|
      else if (property.equals(JTree.CELL_RENDERER_PROPERTY))
 | 
						|
        {
 | 
						|
          setCellRenderer(tree.getCellRenderer());
 | 
						|
          // Update layout.
 | 
						|
          if (treeState != null)
 | 
						|
            treeState.invalidateSizes();
 | 
						|
        }
 | 
						|
      else if (property.equals(JTree.EDITABLE_PROPERTY))
 | 
						|
        setEditable(((Boolean) event.getNewValue()).booleanValue());
 | 
						|
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Listener on the TreeSelectionModel, resets the row selection if any of the
 | 
						|
   * properties of the model change.
 | 
						|
   */
 | 
						|
  public class SelectionModelPropertyChangeHandler
 | 
						|
    implements PropertyChangeListener
 | 
						|
  {
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public SelectionModelPropertyChangeHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * This method gets called when a bound property is changed.
 | 
						|
     *
 | 
						|
     * @param event A PropertyChangeEvent object describing the event source and
 | 
						|
     *          the property that has changed.
 | 
						|
     */
 | 
						|
    public void propertyChange(PropertyChangeEvent event)
 | 
						|
    {
 | 
						|
      treeSelectionModel.resetRowSelection();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * The action to cancel editing on this tree.
 | 
						|
   */
 | 
						|
  public class TreeCancelEditingAction
 | 
						|
      extends AbstractAction
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Creates the new tree cancel editing action.
 | 
						|
     *
 | 
						|
     * @param name the name of the action (used in toString).
 | 
						|
     */
 | 
						|
    public TreeCancelEditingAction(String name)
 | 
						|
    {
 | 
						|
      super(name);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when an action occurs, cancels the cell editing (if the
 | 
						|
     * tree cell is being edited).
 | 
						|
     *
 | 
						|
     * @param e event that occured
 | 
						|
     */
 | 
						|
    public void actionPerformed(ActionEvent e)
 | 
						|
    {
 | 
						|
      if (isEnabled() && tree.isEditing())
 | 
						|
        tree.cancelEditing();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates the TreeState in response to nodes expanding/collapsing.
 | 
						|
   */
 | 
						|
  public class TreeExpansionHandler
 | 
						|
      implements TreeExpansionListener
 | 
						|
  {
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public TreeExpansionHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Called whenever an item in the tree has been expanded.
 | 
						|
     *
 | 
						|
     * @param event is the event that occured
 | 
						|
     */
 | 
						|
    public void treeExpanded(TreeExpansionEvent event)
 | 
						|
    {
 | 
						|
      validCachedPreferredSize = false;
 | 
						|
      treeState.setExpandedState(event.getPath(), true);
 | 
						|
      // The maximal cell height may change
 | 
						|
      maxHeight = 0;
 | 
						|
      tree.revalidate();
 | 
						|
      tree.repaint();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Called whenever an item in the tree has been collapsed.
 | 
						|
     *
 | 
						|
     * @param event is the event that occured
 | 
						|
     */
 | 
						|
    public void treeCollapsed(TreeExpansionEvent event)
 | 
						|
    {
 | 
						|
      completeEditing();
 | 
						|
      validCachedPreferredSize = false;
 | 
						|
      treeState.setExpandedState(event.getPath(), false);
 | 
						|
      // The maximal cell height may change
 | 
						|
      maxHeight = 0;
 | 
						|
      tree.revalidate();
 | 
						|
      tree.repaint();
 | 
						|
    }
 | 
						|
  } // TreeExpansionHandler
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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
 | 
						|
  {
 | 
						|
 | 
						|
    /** The direction, either home or end */
 | 
						|
    protected int direction;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates a new TreeHomeAction instance.
 | 
						|
     *
 | 
						|
     * @param dir the direction to go to, <code>-1</code> for home,
 | 
						|
     *        <code>1</code> for end
 | 
						|
     * @param name the name of the action
 | 
						|
     */
 | 
						|
    public TreeHomeAction(int dir, String name)
 | 
						|
    {
 | 
						|
      direction = dir;
 | 
						|
      putValue(Action.NAME, name);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when an action occurs.
 | 
						|
     *
 | 
						|
     * @param e is the event that occured
 | 
						|
     */
 | 
						|
    public void actionPerformed(ActionEvent e)
 | 
						|
    {
 | 
						|
      if (tree != null)
 | 
						|
        {
 | 
						|
          String command = (String) getValue(Action.NAME);
 | 
						|
          if (command.equals("selectFirst"))
 | 
						|
            {
 | 
						|
              ensureRowsAreVisible(0, 0);
 | 
						|
              tree.setSelectionInterval(0, 0);
 | 
						|
            }
 | 
						|
          if (command.equals("selectFirstChangeLead"))
 | 
						|
            {
 | 
						|
              ensureRowsAreVisible(0, 0);
 | 
						|
              tree.setLeadSelectionPath(getPathForRow(tree, 0));
 | 
						|
            }
 | 
						|
          if (command.equals("selectFirstExtendSelection"))
 | 
						|
            {
 | 
						|
              ensureRowsAreVisible(0, 0);
 | 
						|
              TreePath anchorPath = tree.getAnchorSelectionPath();
 | 
						|
              if (anchorPath == null)
 | 
						|
                tree.setSelectionInterval(0, 0);
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  int anchorRow = getRowForPath(tree, anchorPath);
 | 
						|
                  tree.setSelectionInterval(0, anchorRow);
 | 
						|
                  tree.setAnchorSelectionPath(anchorPath);
 | 
						|
                  tree.setLeadSelectionPath(getPathForRow(tree, 0));
 | 
						|
                }
 | 
						|
            }
 | 
						|
          else if (command.equals("selectLast"))
 | 
						|
            {
 | 
						|
              int end = getRowCount(tree) - 1;
 | 
						|
              ensureRowsAreVisible(end, end);
 | 
						|
              tree.setSelectionInterval(end, end);
 | 
						|
            }
 | 
						|
          else if (command.equals("selectLastChangeLead"))
 | 
						|
            {
 | 
						|
              int end = getRowCount(tree) - 1;
 | 
						|
              ensureRowsAreVisible(end, end);
 | 
						|
              tree.setLeadSelectionPath(getPathForRow(tree, end));
 | 
						|
            }
 | 
						|
          else if (command.equals("selectLastExtendSelection"))
 | 
						|
            {
 | 
						|
              int end = getRowCount(tree) - 1;
 | 
						|
              ensureRowsAreVisible(end, end);
 | 
						|
              TreePath anchorPath = tree.getAnchorSelectionPath();
 | 
						|
              if (anchorPath == null)
 | 
						|
                tree.setSelectionInterval(end, end);
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  int anchorRow = getRowForPath(tree, anchorPath);
 | 
						|
                  tree.setSelectionInterval(end, anchorRow);
 | 
						|
                  tree.setAnchorSelectionPath(anchorPath);
 | 
						|
                  tree.setLeadSelectionPath(getPathForRow(tree, end));
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
      // Ensure that the lead path is visible after the increment action.
 | 
						|
      tree.scrollPathToVisible(tree.getLeadSelectionPath());
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns true if the action is enabled.
 | 
						|
     *
 | 
						|
     * @return true if the action is enabled.
 | 
						|
     */
 | 
						|
    public boolean isEnabled()
 | 
						|
    {
 | 
						|
      return (tree != null) && tree.isEnabled();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates a new TreeIncrementAction.
 | 
						|
     *
 | 
						|
     * @param dir up or down, <code>-1</code> for up, <code>1</code> for down
 | 
						|
     * @param name is the name of the direction
 | 
						|
     */
 | 
						|
    public TreeIncrementAction(int dir, String name)
 | 
						|
    {
 | 
						|
      direction = dir;
 | 
						|
      putValue(Action.NAME, name);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when an action occurs.
 | 
						|
     *
 | 
						|
     * @param e is the event that occured
 | 
						|
     */
 | 
						|
    public void actionPerformed(ActionEvent e)
 | 
						|
    {
 | 
						|
      TreePath currentPath = tree.getLeadSelectionPath();
 | 
						|
      int currentRow;
 | 
						|
 | 
						|
      if (currentPath != null)
 | 
						|
        currentRow = treeState.getRowForPath(currentPath);
 | 
						|
      else
 | 
						|
        currentRow = 0;
 | 
						|
 | 
						|
      int rows = treeState.getRowCount();
 | 
						|
 | 
						|
      int nextRow = currentRow + 1;
 | 
						|
      int prevRow = currentRow - 1;
 | 
						|
      boolean hasNext = nextRow < rows;
 | 
						|
      boolean hasPrev = prevRow >= 0 && rows > 0;
 | 
						|
      TreePath newPath;
 | 
						|
      String command = (String) getValue(Action.NAME);
 | 
						|
 | 
						|
      if (command.equals("selectPreviousChangeLead") && hasPrev)
 | 
						|
        {
 | 
						|
          newPath = treeState.getPathForRow(prevRow);
 | 
						|
          tree.setSelectionPath(newPath);
 | 
						|
          tree.setAnchorSelectionPath(newPath);
 | 
						|
          tree.setLeadSelectionPath(newPath);
 | 
						|
        }
 | 
						|
      else if (command.equals("selectPreviousExtendSelection") && hasPrev)
 | 
						|
        {
 | 
						|
          newPath = treeState.getPathForRow(prevRow);
 | 
						|
 | 
						|
          // If the new path is already selected, the selection shrinks,
 | 
						|
          // unselecting the previously current path.
 | 
						|
          if (tree.isPathSelected(newPath))
 | 
						|
            tree.getSelectionModel().removeSelectionPath(currentPath);
 | 
						|
 | 
						|
          // This must be called in any case because it updates the model
 | 
						|
          // lead selection index.
 | 
						|
          tree.addSelectionPath(newPath);
 | 
						|
          tree.setLeadSelectionPath(newPath);
 | 
						|
        }
 | 
						|
      else if (command.equals("selectPrevious") && hasPrev)
 | 
						|
        {
 | 
						|
          newPath = treeState.getPathForRow(prevRow);
 | 
						|
          tree.setSelectionPath(newPath);
 | 
						|
        }
 | 
						|
      else if (command.equals("selectNext") && hasNext)
 | 
						|
        {
 | 
						|
          newPath = treeState.getPathForRow(nextRow);
 | 
						|
          tree.setSelectionPath(newPath);
 | 
						|
        }
 | 
						|
      else if (command.equals("selectNextExtendSelection") && hasNext)
 | 
						|
        {
 | 
						|
          newPath = treeState.getPathForRow(nextRow);
 | 
						|
 | 
						|
          // If the new path is already selected, the selection shrinks,
 | 
						|
          // unselecting the previously current path.
 | 
						|
          if (tree.isPathSelected(newPath))
 | 
						|
            tree.getSelectionModel().removeSelectionPath(currentPath);
 | 
						|
 | 
						|
          // This must be called in any case because it updates the model
 | 
						|
          // lead selection index.
 | 
						|
          tree.addSelectionPath(newPath);
 | 
						|
 | 
						|
          tree.setLeadSelectionPath(newPath);
 | 
						|
        }
 | 
						|
      else if (command.equals("selectNextChangeLead") && hasNext)
 | 
						|
        {
 | 
						|
          newPath = treeState.getPathForRow(nextRow);
 | 
						|
          tree.setSelectionPath(newPath);
 | 
						|
          tree.setAnchorSelectionPath(newPath);
 | 
						|
          tree.setLeadSelectionPath(newPath);
 | 
						|
        }
 | 
						|
 | 
						|
      // Ensure that the lead path is visible after the increment action.
 | 
						|
      tree.scrollPathToVisible(tree.getLeadSelectionPath());
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns true if the action is enabled.
 | 
						|
     *
 | 
						|
     * @return true if the action is enabled.
 | 
						|
     */
 | 
						|
    public boolean isEnabled()
 | 
						|
    {
 | 
						|
      return (tree != null) && tree.isEnabled();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Forwards all TreeModel events to the TreeState.
 | 
						|
   */
 | 
						|
  public class TreeModelHandler
 | 
						|
      implements TreeModelListener
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public TreeModelHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked after a node (or a set of siblings) has changed in some way. The
 | 
						|
     * node(s) have not changed locations in the tree or altered their children
 | 
						|
     * arrays, but other attributes have changed and may affect presentation.
 | 
						|
     * Example: the name of a file has changed, but it is in the same location
 | 
						|
     * in the file system. To indicate the root has changed, childIndices and
 | 
						|
     * children will be null. Use e.getPath() to get the parent of the changed
 | 
						|
     * node(s). e.getChildIndices() returns the index(es) of the changed
 | 
						|
     * node(s).
 | 
						|
     *
 | 
						|
     * @param e is the event that occured
 | 
						|
     */
 | 
						|
    public void treeNodesChanged(TreeModelEvent e)
 | 
						|
    {
 | 
						|
      validCachedPreferredSize = false;
 | 
						|
      treeState.treeNodesChanged(e);
 | 
						|
      tree.repaint();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked after nodes have been inserted into the tree. Use e.getPath() to
 | 
						|
     * get the parent of the new node(s). e.getChildIndices() returns the
 | 
						|
     * index(es) of the new node(s) in ascending order.
 | 
						|
     *
 | 
						|
     * @param e is the event that occured
 | 
						|
     */
 | 
						|
    public void treeNodesInserted(TreeModelEvent e)
 | 
						|
    {
 | 
						|
      validCachedPreferredSize = false;
 | 
						|
      treeState.treeNodesInserted(e);
 | 
						|
      tree.repaint();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked after nodes have been removed from the tree. Note that if a
 | 
						|
     * subtree is removed from the tree, this method may only be invoked once
 | 
						|
     * for the root of the removed subtree, not once for each individual set of
 | 
						|
     * siblings removed. Use e.getPath() to get the former parent of the deleted
 | 
						|
     * node(s). e.getChildIndices() returns, in ascending order, the index(es)
 | 
						|
     * the node(s) had before being deleted.
 | 
						|
     *
 | 
						|
     * @param e is the event that occured
 | 
						|
     */
 | 
						|
    public void treeNodesRemoved(TreeModelEvent e)
 | 
						|
    {
 | 
						|
      validCachedPreferredSize = false;
 | 
						|
      treeState.treeNodesRemoved(e);
 | 
						|
      tree.repaint();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked after the tree has drastically changed structure from a given
 | 
						|
     * node down. If the path returned by e.getPath() is of length one and the
 | 
						|
     * first element does not identify the current root node the first element
 | 
						|
     * should become the new root of the tree. Use e.getPath() to get the path
 | 
						|
     * to the node. e.getChildIndices() returns null.
 | 
						|
     *
 | 
						|
     * @param e is the event that occured
 | 
						|
     */
 | 
						|
    public void treeStructureChanged(TreeModelEvent e)
 | 
						|
    {
 | 
						|
      if (e.getPath().length == 1
 | 
						|
          && ! e.getPath()[0].equals(treeModel.getRoot()))
 | 
						|
        tree.expandPath(new TreePath(treeModel.getRoot()));
 | 
						|
      validCachedPreferredSize = false;
 | 
						|
      treeState.treeStructureChanged(e);
 | 
						|
      tree.repaint();
 | 
						|
    }
 | 
						|
  } // TreeModelHandler
 | 
						|
 | 
						|
  /**
 | 
						|
   * TreePageAction handles page up and page down events.
 | 
						|
   */
 | 
						|
  public class TreePageAction
 | 
						|
      extends AbstractAction
 | 
						|
  {
 | 
						|
    /** Specifies the direction to adjust the selection by. */
 | 
						|
    protected int direction;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     *
 | 
						|
     * @param direction up or down
 | 
						|
     * @param name is the name of the direction
 | 
						|
     */
 | 
						|
    public TreePageAction(int direction, String name)
 | 
						|
    {
 | 
						|
      this.direction = direction;
 | 
						|
      putValue(Action.NAME, name);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when an action occurs.
 | 
						|
     *
 | 
						|
     * @param e is the event that occured
 | 
						|
     */
 | 
						|
    public void actionPerformed(ActionEvent e)
 | 
						|
    {
 | 
						|
      String command = (String) getValue(Action.NAME);
 | 
						|
      boolean extendSelection = command.equals("scrollUpExtendSelection")
 | 
						|
                                || command.equals("scrollDownExtendSelection");
 | 
						|
      boolean changeSelection = command.equals("scrollUpChangeSelection")
 | 
						|
                                || command.equals("scrollDownChangeSelection");
 | 
						|
 | 
						|
      // Disable change lead, unless we are in discontinuous mode.
 | 
						|
      if (!extendSelection && !changeSelection
 | 
						|
          && tree.getSelectionModel().getSelectionMode() !=
 | 
						|
            TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 | 
						|
        {
 | 
						|
          changeSelection = true;
 | 
						|
        }
 | 
						|
 | 
						|
      int rowCount = getRowCount(tree);
 | 
						|
      if (rowCount > 0 && treeSelectionModel != null)
 | 
						|
        {
 | 
						|
          Dimension maxSize = tree.getSize();
 | 
						|
          TreePath lead = tree.getLeadSelectionPath();
 | 
						|
          TreePath newPath = null;
 | 
						|
          Rectangle visible = tree.getVisibleRect();
 | 
						|
          if (direction == -1) // The RI handles -1 as up.
 | 
						|
            {
 | 
						|
              newPath = getClosestPathForLocation(tree, visible.x, visible.y);
 | 
						|
              if (newPath.equals(lead)) // Corner case, adjust one page up.
 | 
						|
                {
 | 
						|
                  visible.y = Math.max(0, visible.y - visible.height);
 | 
						|
                  newPath = getClosestPathForLocation(tree, visible.x,
 | 
						|
                                                      visible.y);
 | 
						|
                }
 | 
						|
            }
 | 
						|
          else // +1 is down.
 | 
						|
            {
 | 
						|
              visible.y = Math.min(maxSize.height,
 | 
						|
                                   visible.y + visible.height - 1);
 | 
						|
              newPath = getClosestPathForLocation(tree, visible.x, visible.y);
 | 
						|
              if (newPath.equals(lead)) // Corner case, adjust one page down.
 | 
						|
                {
 | 
						|
                  visible.y = Math.min(maxSize.height,
 | 
						|
                                       visible.y + visible.height - 1);
 | 
						|
                  newPath = getClosestPathForLocation(tree, visible.x,
 | 
						|
                                                      visible.y);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
          // Determine new visible rect.
 | 
						|
          Rectangle newVisible = getPathBounds(tree, newPath);
 | 
						|
          newVisible.x = visible.x;
 | 
						|
          newVisible.width = visible.width;
 | 
						|
          if (direction == -1)
 | 
						|
            {
 | 
						|
              newVisible.height = visible.height;
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              newVisible.y -= visible.height - newVisible.height;
 | 
						|
              newVisible.height = visible.height;
 | 
						|
            }
 | 
						|
 | 
						|
          if (extendSelection)
 | 
						|
            {
 | 
						|
              // Extend selection.
 | 
						|
              TreePath anchorPath = tree.getAnchorSelectionPath();
 | 
						|
              if (anchorPath == null)
 | 
						|
                {
 | 
						|
                  tree.setSelectionPath(newPath);
 | 
						|
                }
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  int newIndex = getRowForPath(tree, newPath);
 | 
						|
                  int anchorIndex = getRowForPath(tree, anchorPath);
 | 
						|
                  tree.setSelectionInterval(Math.min(anchorIndex, newIndex),
 | 
						|
                                            Math.max(anchorIndex, newIndex));
 | 
						|
                  tree.setAnchorSelectionPath(anchorPath);
 | 
						|
                  tree.setLeadSelectionPath(newPath);
 | 
						|
                }
 | 
						|
            }
 | 
						|
          else if (changeSelection)
 | 
						|
            {
 | 
						|
              tree.setSelectionPath(newPath);
 | 
						|
            }
 | 
						|
          else // Change lead.
 | 
						|
            {
 | 
						|
              tree.setLeadSelectionPath(newPath);
 | 
						|
            }
 | 
						|
 | 
						|
          tree.scrollRectToVisible(newVisible);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns true if the action is enabled.
 | 
						|
     *
 | 
						|
     * @return true if the action is enabled.
 | 
						|
     */
 | 
						|
    public boolean isEnabled()
 | 
						|
    {
 | 
						|
      return (tree != null) && tree.isEnabled();
 | 
						|
    }
 | 
						|
  } // TreePageAction
 | 
						|
 | 
						|
  /**
 | 
						|
   * Listens for changes in the selection model and updates the display
 | 
						|
   * accordingly.
 | 
						|
   */
 | 
						|
  public class TreeSelectionHandler
 | 
						|
      implements TreeSelectionListener
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     */
 | 
						|
    public TreeSelectionHandler()
 | 
						|
    {
 | 
						|
      // Nothing to do here.
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Messaged when the selection changes in the tree we're displaying for.
 | 
						|
     * Stops editing, messages super and displays the changed paths.
 | 
						|
     *
 | 
						|
     * @param event the event that characterizes the change.
 | 
						|
     */
 | 
						|
    public void valueChanged(TreeSelectionEvent event)
 | 
						|
    {
 | 
						|
      completeEditing();
 | 
						|
 | 
						|
      TreePath op = event.getOldLeadSelectionPath();
 | 
						|
      TreePath np = event.getNewLeadSelectionPath();
 | 
						|
 | 
						|
      // Repaint of the changed lead selection path.
 | 
						|
      if (op != np)
 | 
						|
        {
 | 
						|
          Rectangle o = treeState.getBounds(event.getOldLeadSelectionPath(),
 | 
						|
                                           new Rectangle());
 | 
						|
          Rectangle n = treeState.getBounds(event.getNewLeadSelectionPath(),
 | 
						|
                                           new Rectangle());
 | 
						|
 | 
						|
          if (o != null)
 | 
						|
            tree.repaint(o);
 | 
						|
          if (n != null)
 | 
						|
            tree.repaint(n);
 | 
						|
        }
 | 
						|
    }
 | 
						|
  } // TreeSelectionHandler
 | 
						|
 | 
						|
  /**
 | 
						|
   * For the first selected row expandedness will be toggled.
 | 
						|
   */
 | 
						|
  public class TreeToggleAction
 | 
						|
      extends AbstractAction
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Creates a new TreeToggleAction.
 | 
						|
     *
 | 
						|
     * @param name is the name of <code>Action</code> field
 | 
						|
     */
 | 
						|
    public TreeToggleAction(String name)
 | 
						|
    {
 | 
						|
      putValue(Action.NAME, name);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when an action occurs.
 | 
						|
     *
 | 
						|
     * @param e the event that occured
 | 
						|
     */
 | 
						|
    public void actionPerformed(ActionEvent e)
 | 
						|
    {
 | 
						|
      int selected = tree.getLeadSelectionRow();
 | 
						|
      if (selected != -1 && isLeaf(selected))
 | 
						|
        {
 | 
						|
          TreePath anchorPath = tree.getAnchorSelectionPath();
 | 
						|
          TreePath leadPath = tree.getLeadSelectionPath();
 | 
						|
          toggleExpandState(getPathForRow(tree, selected));
 | 
						|
          // Need to do this, so that the toggling doesn't mess up the lead
 | 
						|
          // and anchor.
 | 
						|
          tree.setLeadSelectionPath(leadPath);
 | 
						|
          tree.setAnchorSelectionPath(anchorPath);
 | 
						|
 | 
						|
          // Ensure that the lead path is visible after the increment action.
 | 
						|
          tree.scrollPathToVisible(tree.getLeadSelectionPath());
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns true if the action is enabled.
 | 
						|
     *
 | 
						|
     * @return true if the action is enabled, false otherwise
 | 
						|
     */
 | 
						|
    public boolean isEnabled()
 | 
						|
    {
 | 
						|
      return (tree != null) && tree.isEnabled();
 | 
						|
    }
 | 
						|
  } // TreeToggleAction
 | 
						|
 | 
						|
  /**
 | 
						|
   * TreeTraverseAction 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;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     *
 | 
						|
     * @param direction to traverse
 | 
						|
     * @param name is the name of the direction
 | 
						|
     */
 | 
						|
    public TreeTraverseAction(int direction, String name)
 | 
						|
    {
 | 
						|
      this.direction = direction;
 | 
						|
      putValue(Action.NAME, name);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Invoked when an action occurs.
 | 
						|
     *
 | 
						|
     * @param e the event that occured
 | 
						|
     */
 | 
						|
    public void actionPerformed(ActionEvent e)
 | 
						|
    {
 | 
						|
      TreePath current = tree.getLeadSelectionPath();
 | 
						|
      if (current == null)
 | 
						|
        return;
 | 
						|
 | 
						|
      String command = (String) getValue(Action.NAME);
 | 
						|
      if (command.equals("selectParent"))
 | 
						|
        {
 | 
						|
          if (current == null)
 | 
						|
            return;
 | 
						|
 | 
						|
          if (tree.isExpanded(current))
 | 
						|
            {
 | 
						|
              tree.collapsePath(current);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              // If the node is not expanded (also, if it is a leaf node),
 | 
						|
              // we just select the parent. We do not select the root if it
 | 
						|
              // is not visible.
 | 
						|
              TreePath parent = current.getParentPath();
 | 
						|
              if (parent != null &&
 | 
						|
                  ! (parent.getPathCount() == 1 && ! tree.isRootVisible()))
 | 
						|
                tree.setSelectionPath(parent);
 | 
						|
            }
 | 
						|
        }
 | 
						|
      else if (command.equals("selectChild"))
 | 
						|
        {
 | 
						|
          Object node = current.getLastPathComponent();
 | 
						|
          int nc = treeModel.getChildCount(node);
 | 
						|
          if (nc == 0 || treeState.isExpanded(current))
 | 
						|
            {
 | 
						|
              // If the node is leaf or it is already expanded,
 | 
						|
              // we just select the next row.
 | 
						|
              int nextRow = tree.getLeadSelectionRow() + 1;
 | 
						|
              if (nextRow <= tree.getRowCount())
 | 
						|
                tree.setSelectionRow(nextRow);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              tree.expandPath(current);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
      // Ensure that the lead path is visible after the increment action.
 | 
						|
      tree.scrollPathToVisible(tree.getLeadSelectionPath());
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns true if the action is enabled.
 | 
						|
     *
 | 
						|
     * @return true if the action is enabled, false otherwise
 | 
						|
     */
 | 
						|
    public boolean isEnabled()
 | 
						|
    {
 | 
						|
      return (tree != null) && tree.isEnabled();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if the LookAndFeel implements the control icons. Package
 | 
						|
   * private for use in inner classes.
 | 
						|
   *
 | 
						|
   * @returns true if there are control icons
 | 
						|
   */
 | 
						|
  boolean hasControlIcons()
 | 
						|
  {
 | 
						|
    if (expandedIcon != null || collapsedIcon != null)
 | 
						|
      return true;
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns control icon. It is null if the LookAndFeel does not implements the
 | 
						|
   * control icons. Package private for use in inner classes.
 | 
						|
   *
 | 
						|
   * @return control icon if it exists.
 | 
						|
   */
 | 
						|
  Icon getCurrentControlIcon(TreePath path)
 | 
						|
  {
 | 
						|
    if (hasControlIcons())
 | 
						|
      {
 | 
						|
        if (tree.isExpanded(path))
 | 
						|
          return expandedIcon;
 | 
						|
        else
 | 
						|
          return collapsedIcon;
 | 
						|
      }
 | 
						|
    else
 | 
						|
      {
 | 
						|
        if (nullIcon == null)
 | 
						|
          nullIcon = new Icon()
 | 
						|
          {
 | 
						|
            public int getIconHeight()
 | 
						|
            {
 | 
						|
              return 0;
 | 
						|
            }
 | 
						|
 | 
						|
            public int getIconWidth()
 | 
						|
            {
 | 
						|
              return 0;
 | 
						|
            }
 | 
						|
 | 
						|
            public void paintIcon(Component c, Graphics g, int x, int y)
 | 
						|
            {
 | 
						|
              // No action here.
 | 
						|
            }
 | 
						|
          };
 | 
						|
        return nullIcon;
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the parent of the current node
 | 
						|
   *
 | 
						|
   * @param root is the root of the tree
 | 
						|
   * @param node is the current node
 | 
						|
   * @return is the parent of the current node
 | 
						|
   */
 | 
						|
  Object getParent(Object root, Object node)
 | 
						|
  {
 | 
						|
    if (root == null || node == null || root.equals(node))
 | 
						|
      return null;
 | 
						|
 | 
						|
    if (node instanceof TreeNode)
 | 
						|
      return ((TreeNode) node).getParent();
 | 
						|
    return findNode(root, node);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Recursively checks the tree for the specified node, starting at the root.
 | 
						|
   *
 | 
						|
   * @param root is starting node to start searching at.
 | 
						|
   * @param node is the node to search for
 | 
						|
   * @return the parent node of node
 | 
						|
   */
 | 
						|
  private Object findNode(Object root, Object node)
 | 
						|
  {
 | 
						|
    if (! treeModel.isLeaf(root) && ! root.equals(node))
 | 
						|
      {
 | 
						|
        int size = treeModel.getChildCount(root);
 | 
						|
        for (int j = 0; j < size; j++)
 | 
						|
          {
 | 
						|
            Object child = treeModel.getChild(root, j);
 | 
						|
            if (node.equals(child))
 | 
						|
              return root;
 | 
						|
 | 
						|
            Object n = findNode(child, node);
 | 
						|
            if (n != null)
 | 
						|
              return n;
 | 
						|
          }
 | 
						|
      }
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Selects the specified path in the tree depending on modes. Package private
 | 
						|
   * for use in inner classes.
 | 
						|
   *
 | 
						|
   * @param tree is the tree we are selecting the path in
 | 
						|
   * @param path is the path we are selecting
 | 
						|
   */
 | 
						|
  void selectPath(JTree tree, TreePath path)
 | 
						|
  {
 | 
						|
    if (path != null)
 | 
						|
      {
 | 
						|
        tree.setSelectionPath(path);
 | 
						|
        tree.setLeadSelectionPath(path);
 | 
						|
        tree.makeVisible(path);
 | 
						|
        tree.scrollPathToVisible(path);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the path from node to the root. Package private for use in inner
 | 
						|
   * classes.
 | 
						|
   *
 | 
						|
   * @param node the node to get the path to
 | 
						|
   * @param depth the depth of the tree to return a path for
 | 
						|
   * @return an array of tree nodes that represent the path to node.
 | 
						|
   */
 | 
						|
  Object[] getPathToRoot(Object node, int depth)
 | 
						|
  {
 | 
						|
    if (node == null)
 | 
						|
      {
 | 
						|
        if (depth == 0)
 | 
						|
          return null;
 | 
						|
 | 
						|
        return new Object[depth];
 | 
						|
      }
 | 
						|
 | 
						|
    Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node),
 | 
						|
                                  depth + 1);
 | 
						|
    path[path.length - depth - 1] = node;
 | 
						|
    return path;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Draws a vertical line using the given graphic context
 | 
						|
   *
 | 
						|
   * @param g is the graphic context
 | 
						|
   * @param c is the component the new line will belong to
 | 
						|
   * @param x is the horizonal position
 | 
						|
   * @param top specifies the top of the line
 | 
						|
   * @param bottom specifies the bottom of the line
 | 
						|
   */
 | 
						|
  protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
 | 
						|
                                   int bottom)
 | 
						|
  {
 | 
						|
    // FIXME: Check if drawing a dashed line or not.
 | 
						|
    g.setColor(getHashColor());
 | 
						|
    g.drawLine(x, top, x, bottom);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Draws a horizontal line using the given graphic context
 | 
						|
   *
 | 
						|
   * @param g is the graphic context
 | 
						|
   * @param c is the component the new line will belong to
 | 
						|
   * @param y is the vertical position
 | 
						|
   * @param left specifies the left point of the line
 | 
						|
   * @param right specifies the right point of the line
 | 
						|
   */
 | 
						|
  protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left,
 | 
						|
                                     int right)
 | 
						|
  {
 | 
						|
    // FIXME: Check if drawing a dashed line or not.
 | 
						|
    g.setColor(getHashColor());
 | 
						|
    g.drawLine(left, y, right, y);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Draws an icon at around a specific position
 | 
						|
   *
 | 
						|
   * @param c is the component the new line will belong to
 | 
						|
   * @param g is the graphic context
 | 
						|
   * @param icon is the icon which will be drawn
 | 
						|
   * @param x is the center position in x-direction
 | 
						|
   * @param y is the center position in y-direction
 | 
						|
   */
 | 
						|
  protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y)
 | 
						|
  {
 | 
						|
    x -= icon.getIconWidth() / 2;
 | 
						|
    y -= icon.getIconHeight() / 2;
 | 
						|
 | 
						|
    if (x < 0)
 | 
						|
      x = 0;
 | 
						|
    if (y < 0)
 | 
						|
      y = 0;
 | 
						|
 | 
						|
    icon.paintIcon(c, g, x, y);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Draws a dashed horizontal line.
 | 
						|
   *
 | 
						|
   * @param g - the graphics configuration.
 | 
						|
   * @param y - the y location to start drawing at
 | 
						|
   * @param x1 - the x location to start drawing at
 | 
						|
   * @param x2 - the x location to finish drawing at
 | 
						|
   */
 | 
						|
  protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)
 | 
						|
  {
 | 
						|
    g.setColor(getHashColor());
 | 
						|
    for (int i = x1; i < x2; i += 2)
 | 
						|
      g.drawLine(i, y, i + 1, y);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Draws a dashed vertical line.
 | 
						|
   *
 | 
						|
   * @param g - the graphics configuration.
 | 
						|
   * @param x - the x location to start drawing at
 | 
						|
   * @param y1 - the y location to start drawing at
 | 
						|
   * @param y2 - the y location to finish drawing at
 | 
						|
   */
 | 
						|
  protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2)
 | 
						|
  {
 | 
						|
    g.setColor(getHashColor());
 | 
						|
    for (int i = y1; i < y2; i += 2)
 | 
						|
      g.drawLine(x, i, x, i + 1);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Paints the expand (toggle) part of a row. The receiver should NOT modify
 | 
						|
   * clipBounds, or insets.
 | 
						|
   *
 | 
						|
   * @param g - the graphics configuration
 | 
						|
   * @param clipBounds -
 | 
						|
   * @param insets -
 | 
						|
   * @param bounds - bounds of expand control
 | 
						|
   * @param path - path to draw control for
 | 
						|
   * @param row - row to draw control for
 | 
						|
   * @param isExpanded - is the row expanded
 | 
						|
   * @param hasBeenExpanded - has the row already been expanded
 | 
						|
   * @param isLeaf - is the path a leaf
 | 
						|
   */
 | 
						|
  protected void paintExpandControl(Graphics g, Rectangle clipBounds,
 | 
						|
                                    Insets insets, Rectangle bounds,
 | 
						|
                                    TreePath path, int row, boolean isExpanded,
 | 
						|
                                    boolean hasBeenExpanded, boolean isLeaf)
 | 
						|
  {
 | 
						|
    if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf))
 | 
						|
      {
 | 
						|
        Icon icon = getCurrentControlIcon(path);
 | 
						|
        int iconW = icon.getIconWidth();
 | 
						|
        int x = bounds.x - iconW - gap;
 | 
						|
        icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2
 | 
						|
                                   - icon.getIconHeight() / 2);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Paints the horizontal part of the leg. The receiver should NOT modify
 | 
						|
   * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not
 | 
						|
   * visible.
 | 
						|
   *
 | 
						|
   * @param g - the graphics configuration
 | 
						|
   * @param clipBounds -
 | 
						|
   * @param insets -
 | 
						|
   * @param bounds - bounds of the cell
 | 
						|
   * @param path - path to draw leg for
 | 
						|
   * @param row - row to start drawing at
 | 
						|
   * @param isExpanded - is the row expanded
 | 
						|
   * @param hasBeenExpanded - has the row already been expanded
 | 
						|
   * @param isLeaf - is the path a leaf
 | 
						|
   */
 | 
						|
  protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
 | 
						|
                                          Insets insets, Rectangle bounds,
 | 
						|
                                          TreePath path, int row,
 | 
						|
                                          boolean isExpanded,
 | 
						|
                                          boolean hasBeenExpanded,
 | 
						|
                                          boolean isLeaf)
 | 
						|
  {
 | 
						|
    if (row != 0)
 | 
						|
      {
 | 
						|
        paintHorizontalLine(g, tree, bounds.y + bounds.height / 2,
 | 
						|
                            bounds.x - leftChildIndent - gap, bounds.x - gap);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Paints the vertical part of the leg. The receiver should NOT modify
 | 
						|
   * clipBounds, insets.
 | 
						|
   *
 | 
						|
   * @param g - the graphics configuration.
 | 
						|
   * @param clipBounds -
 | 
						|
   * @param insets -
 | 
						|
   * @param path - the path to draw the vertical part for.
 | 
						|
   */
 | 
						|
  protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
 | 
						|
                                        Insets insets, TreePath path)
 | 
						|
  {
 | 
						|
    Rectangle bounds = getPathBounds(tree, path);
 | 
						|
    TreePath parent = path.getParentPath();
 | 
						|
 | 
						|
    boolean paintLine;
 | 
						|
    if (isRootVisible())
 | 
						|
      paintLine = parent != null;
 | 
						|
    else
 | 
						|
      paintLine = parent != null && parent.getPathCount() > 1;
 | 
						|
    if (paintLine)
 | 
						|
      {
 | 
						|
        Rectangle parentBounds = getPathBounds(tree, parent);
 | 
						|
        paintVerticalLine(g, tree, parentBounds.x + 2 * gap,
 | 
						|
                          parentBounds.y + parentBounds.height / 2,
 | 
						|
                          bounds.y + bounds.height / 2);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Paints the renderer part of a row. The receiver should NOT modify
 | 
						|
   * clipBounds, or insets.
 | 
						|
   *
 | 
						|
   * @param g - the graphics configuration
 | 
						|
   * @param clipBounds -
 | 
						|
   * @param insets -
 | 
						|
   * @param bounds - bounds of expand control
 | 
						|
   * @param path - path to draw control for
 | 
						|
   * @param row - row to draw control for
 | 
						|
   * @param isExpanded - is the row expanded
 | 
						|
   * @param hasBeenExpanded - has the row already been expanded
 | 
						|
   * @param isLeaf - is the path a leaf
 | 
						|
   */
 | 
						|
  protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
 | 
						|
                          Rectangle bounds, TreePath path, int row,
 | 
						|
                          boolean isExpanded, boolean hasBeenExpanded,
 | 
						|
                          boolean isLeaf)
 | 
						|
  {
 | 
						|
    boolean selected = tree.isPathSelected(path);
 | 
						|
    boolean hasIcons = false;
 | 
						|
    Object node = path.getLastPathComponent();
 | 
						|
 | 
						|
    paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded,
 | 
						|
                       hasBeenExpanded, isLeaf);
 | 
						|
 | 
						|
    TreeCellRenderer dtcr = currentCellRenderer;
 | 
						|
 | 
						|
    boolean focused = false;
 | 
						|
    if (treeSelectionModel != null)
 | 
						|
      focused = treeSelectionModel.getLeadSelectionRow() == row
 | 
						|
                && tree.isFocusOwner();
 | 
						|
 | 
						|
    Component c = dtcr.getTreeCellRendererComponent(tree, node, selected,
 | 
						|
                                                    isExpanded, isLeaf, row,
 | 
						|
                                                    focused);
 | 
						|
 | 
						|
    rendererPane.paintComponent(g, c, c.getParent(), bounds);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Prepares for the UI to uninstall.
 | 
						|
   */
 | 
						|
  protected void prepareForUIUninstall()
 | 
						|
  {
 | 
						|
    // Nothing to do here yet.
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if the expand (toggle) control should be drawn for the
 | 
						|
   * specified row.
 | 
						|
   *
 | 
						|
   * @param path - current path to check for.
 | 
						|
   * @param row - current row to check for.
 | 
						|
   * @param isExpanded - true if the path is expanded
 | 
						|
   * @param hasBeenExpanded - true if the path has been expanded already
 | 
						|
   * @param isLeaf - true if the row is a lead
 | 
						|
   */
 | 
						|
  protected boolean shouldPaintExpandControl(TreePath path, int row,
 | 
						|
                                             boolean isExpanded,
 | 
						|
                                             boolean hasBeenExpanded,
 | 
						|
                                             boolean isLeaf)
 | 
						|
  {
 | 
						|
    Object node = path.getLastPathComponent();
 | 
						|
    return ! isLeaf && hasControlIcons();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the amount to indent the given row
 | 
						|
   *
 | 
						|
   * @return amount to indent the given row.
 | 
						|
   */
 | 
						|
  protected int getRowX(int row, int depth)
 | 
						|
  {
 | 
						|
    return depth * totalChildIndent;
 | 
						|
  }
 | 
						|
} // BasicTreeUI
 |