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

package javax.swing.plaf.basic;

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

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

The basic L&F for a hierarchical data structure.

Author:Scott Violet, Shannon Hickey (drag and drop)
/** * The basic L&amp;F for a hierarchical data structure. * <p> * * @author Scott Violet * @author Shannon Hickey (drag and drop) */
public class BasicTreeUI extends TreeUI { private static final StringBuilder BASELINE_COMPONENT_KEY = new StringBuilder("Tree.baselineComponent"); // Old actions forward to an instance of this. static private final Actions SHARED_ACTION = new Actions(); transient protected Icon collapsedIcon; transient protected Icon expandedIcon;
Color used to draw hash marks. If null no hash marks will be drawn.
/** * Color used to draw hash marks. If <code>null</code> no hash marks * will be drawn. */
private Color hashColor;
Distance between left margin and where vertical dashes will be drawn.
/** Distance between left margin and where vertical dashes will be * drawn. */
protected int leftChildIndent;
Distance to add to leftChildIndent to determine where cell contents will be drawn.
/** Distance to add to leftChildIndent to determine where cell * contents will be drawn. */
protected int rightChildIndent;
Total distance that will be indented. The sum of leftChildIndent and rightChildIndent.
/** Total distance that will be indented. The sum of leftChildIndent * and rightChildIndent. */
protected int totalChildIndent;
Minimum preferred size.
/** Minimum preferred size. */
protected Dimension preferredMinSize;
Index of the row that was last selected.
/** Index of the row that was last selected. */
protected int lastSelectedRow;
Component that we're going to be drawing into.
/** Component that we're going to be drawing into. */
protected JTree tree;
Renderer that is being used to do the actual cell drawing.
/** Renderer that is being used to do the actual cell drawing. */
transient protected TreeCellRenderer currentCellRenderer;
Set to true if the renderer that is currently in the tree was created by this instance.
/** Set to true if the renderer that is currently in the tree was * created by this instance. */
protected boolean createdRenderer;
Editor for the tree.
/** Editor for the tree. */
transient protected TreeCellEditor cellEditor;
Set to true if editor that is currently in the tree was created by this instance.
/** Set to true if editor that is currently in the tree was * created by this instance. */
protected boolean createdCellEditor;
Set to false when editing and shouldSelectCell() returns true meaning the node should be selected before editing, used in completeEditing.
/** Set to false when editing and shouldSelectCell() returns true meaning * the node should be selected before editing, used in completeEditing. */
protected boolean stopEditingInCompleteEditing;
Used to paint the TreeCellRenderer.
/** Used to paint the TreeCellRenderer. */
protected CellRendererPane rendererPane;
Size needed to completely display all the nodes.
/** Size needed to completely display all the nodes. */
protected Dimension preferredSize;
Is the preferredSize valid?
/** Is the preferredSize valid? */
protected boolean validCachedPreferredSize;
Object responsible for handling sizing and expanded issues.
/** Object responsible for handling sizing and expanded issues. */
// WARNING: Be careful with the bounds held by treeState. They are // always in terms of left-to-right. They get mapped to right-to-left // by the various methods of this class. protected AbstractLayoutCache treeState;
Used for minimizing the drawing of vertical lines.
/** 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.
/** True if doing optimizations for a largeModel. Subclasses that * don't support this may wish to override createLayoutCache to not * return a FixedHeightLayoutCache instance. */
protected boolean largeModel;
Reponsible for telling the TreeState the size needed for a node.
/** Reponsible for telling the TreeState the size needed for a node. */
protected AbstractLayoutCache.NodeDimensions nodeDimensions;
Used to determine what to display.
/** Used to determine what to display. */
protected TreeModel treeModel;
Model maintaining the selection.
/** 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.
/** How much the depth should be offset to properly calculate * x locations. This is based on whether or not the root is visible, * and if the root handles are visible. */
protected int depthOffset; // Following 4 ivars are only valid when editing.
When editing, this will be the Component that is doing the actual editing.
/** When editing, this will be the Component that is doing the actual * editing. */
protected Component editingComponent;
Path that is being edited.
/** Path that is being edited. */
protected TreePath editingPath;
Row that is being edited. Should only be referenced if editingComponent is not null.
/** Row that is being edited. Should only be referenced if * editingComponent is not null. */
protected int editingRow;
Set to true if the editor has a different size than the renderer.
/** Set to true if the editor has a different size than the renderer. */
protected boolean editorHasDifferentSize;
Row correspondin to lead path.
/** Row correspondin to lead path. */
private int leadRow;
If true, the property change event for LEAD_SELECTION_PATH_PROPERTY, or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint.
/** If true, the property change event for LEAD_SELECTION_PATH_PROPERTY, * or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint. */
private boolean ignoreLAChange;
Indicates the orientation.
/** Indicates the orientation. */
private boolean leftToRight; // Cached listeners private PropertyChangeListener propertyChangeListener; private PropertyChangeListener selectionModelPropertyChangeListener; private MouseListener mouseListener; private FocusListener focusListener; private KeyListener keyListener;
Used for large models, listens for moved/resized events and updates the validCachedPreferredSize bit accordingly.
/** Used for large models, listens for moved/resized events and * updates the validCachedPreferredSize bit accordingly. */
private ComponentListener componentListener;
Listens for CellEditor events.
/** Listens for CellEditor events. */
private CellEditorListener cellEditorListener;
Updates the display when the selection changes.
/** Updates the display when the selection changes. */
private TreeSelectionListener treeSelectionListener;
Is responsible for updating the display based on model events.
/** Is responsible for updating the display based on model events. */
private TreeModelListener treeModelListener;
Updates the treestate as the nodes expand.
/** Updates the treestate as the nodes expand. */
private TreeExpansionListener treeExpansionListener;
UI property indicating whether to paint lines
/** UI property indicating whether to paint lines */
private boolean paintLines = true;
UI property for painting dashed lines
/** UI property for painting dashed lines */
private boolean lineTypeDashed;
The time factor to treate the series of typed alphanumeric key as prefix for first letter navigation.
/** * The time factor to treate the series of typed alphanumeric key * as prefix for first letter navigation. */
private long timeFactor = 1000L; private Handler handler;
A temporary variable for communication between startEditingOnRelease and startEditing.
/** * A temporary variable for communication between startEditingOnRelease * and startEditing. */
private MouseEvent releaseEvent; public static ComponentUI createUI(JComponent x) { return new BasicTreeUI(); } static void loadActionMap(LazyActionMap map) { map.put(new Actions(Actions.SELECT_PREVIOUS)); map.put(new Actions(Actions.SELECT_PREVIOUS_CHANGE_LEAD)); map.put(new Actions(Actions.SELECT_PREVIOUS_EXTEND_SELECTION)); map.put(new Actions(Actions.SELECT_NEXT)); map.put(new Actions(Actions.SELECT_NEXT_CHANGE_LEAD)); map.put(new Actions(Actions.SELECT_NEXT_EXTEND_SELECTION)); map.put(new Actions(Actions.SELECT_CHILD)); map.put(new Actions(Actions.SELECT_CHILD_CHANGE_LEAD)); map.put(new Actions(Actions.SELECT_PARENT)); map.put(new Actions(Actions.SELECT_PARENT_CHANGE_LEAD)); map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION)); map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD)); map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION)); map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION)); map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION)); map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD)); map.put(new Actions(Actions.SELECT_FIRST)); map.put(new Actions(Actions.SELECT_FIRST_CHANGE_LEAD)); map.put(new Actions(Actions.SELECT_FIRST_EXTEND_SELECTION)); map.put(new Actions(Actions.SELECT_LAST)); map.put(new Actions(Actions.SELECT_LAST_CHANGE_LEAD)); map.put(new Actions(Actions.SELECT_LAST_EXTEND_SELECTION)); map.put(new Actions(Actions.TOGGLE)); map.put(new Actions(Actions.CANCEL_EDITING)); map.put(new Actions(Actions.START_EDITING)); map.put(new Actions(Actions.SELECT_ALL)); map.put(new Actions(Actions.CLEAR_SELECTION)); map.put(new Actions(Actions.SCROLL_LEFT)); map.put(new Actions(Actions.SCROLL_RIGHT)); map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION)); map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION)); map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_LEAD)); map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_LEAD)); map.put(new Actions(Actions.EXPAND)); map.put(new Actions(Actions.COLLAPSE)); map.put(new Actions(Actions.MOVE_SELECTION_TO_PARENT)); map.put(new Actions(Actions.ADD_TO_SELECTION)); map.put(new Actions(Actions.TOGGLE_AND_ANCHOR)); map.put(new Actions(Actions.EXTEND_TO)); map.put(new Actions(Actions.MOVE_SELECTION_TO)); map.put(TransferHandler.getCutAction()); map.put(TransferHandler.getCopyAction()); map.put(TransferHandler.getPasteAction()); } public BasicTreeUI() { super(); } protected Color getHashColor() { return hashColor; } protected void setHashColor(Color color) { hashColor = color; } public void setLeftChildIndent(int newAmount) { leftChildIndent = newAmount; totalChildIndent = leftChildIndent + rightChildIndent; if(treeState != null) treeState.invalidateSizes(); updateSize(); } public int getLeftChildIndent() { return leftChildIndent; } public void setRightChildIndent(int newAmount) { rightChildIndent = newAmount; totalChildIndent = leftChildIndent + rightChildIndent; if(treeState != null) treeState.invalidateSizes(); updateSize(); } public int getRightChildIndent() { return rightChildIndent; } public void setExpandedIcon(Icon newG) { expandedIcon = newG; } public Icon getExpandedIcon() { return expandedIcon; } public void setCollapsedIcon(Icon newG) { collapsedIcon = newG; } public Icon getCollapsedIcon() { return collapsedIcon; } // // Methods for configuring the behavior of the tree. None of them // push the value to the JTree instance. You should really only // call these methods on the JTree. //
Updates the componentListener, if necessary.
/** * Updates the componentListener, if necessary. */
protected void setLargeModel(boolean largeModel) { if(getRowHeight() < 1) largeModel = false; if(this.largeModel != largeModel) { completeEditing(); this.largeModel = largeModel; treeState = createLayoutCache(); configureLayoutCache(); updateLayoutCacheExpandedNodesIfNecessary(); updateSize(); } } protected boolean isLargeModel() { return largeModel; }
Sets the row height, this is forwarded to the treeState.
/** * Sets the row height, this is forwarded to the treeState. */
protected void setRowHeight(int rowHeight) { completeEditing(); if(treeState != null) { setLargeModel(tree.isLargeModel()); treeState.setRowHeight(rowHeight); updateSize(); } } protected int getRowHeight() { return (tree == null) ? -1 : tree.getRowHeight(); }
Sets the TreeCellRenderer to tcr. This invokes updateRenderer.
/** * Sets the TreeCellRenderer to <code>tcr</code>. This invokes * <code>updateRenderer</code>. */
protected void setCellRenderer(TreeCellRenderer tcr) { completeEditing(); updateRenderer(); if(treeState != null) { treeState.invalidateSizes(); updateSize(); } }
Return currentCellRenderer, which will either be the trees renderer, or defaultCellRenderer, which ever wasn't null.
/** * Return currentCellRenderer, which will either be the trees * renderer, or defaultCellRenderer, which ever wasn't null. */
protected TreeCellRenderer getCellRenderer() { return currentCellRenderer; }
Sets the TreeModel.
/** * Sets the TreeModel. */
protected void setModel(TreeModel model) { completeEditing(); if(treeModel != null && treeModelListener != null) treeModel.removeTreeModelListener(treeModelListener); treeModel = model; if(treeModel != null) { if(treeModelListener != null) treeModel.addTreeModelListener(treeModelListener); } if(treeState != null) { treeState.setModel(model); updateLayoutCacheExpandedNodesIfNecessary(); updateSize(); } } protected TreeModel getModel() { return treeModel; }
Sets the root to being visible.
/** * Sets the root to being visible. */
protected void setRootVisible(boolean newValue) { completeEditing(); updateDepthOffset(); if(treeState != null) { treeState.setRootVisible(newValue); treeState.invalidateSizes(); updateSize(); } } protected boolean isRootVisible() { return (tree != null) ? tree.isRootVisible() : false; }
Determines whether the node handles are to be displayed.
/** * Determines whether the node handles are to be displayed. */
protected void setShowsRootHandles(boolean newValue) { completeEditing(); updateDepthOffset(); if(treeState != null) { treeState.invalidateSizes(); updateSize(); } } protected boolean getShowsRootHandles() { return (tree != null) ? tree.getShowsRootHandles() : false; }
Sets the cell editor.
/** * Sets the cell editor. */
protected void setCellEditor(TreeCellEditor editor) { updateCellEditor(); } protected TreeCellEditor getCellEditor() { return (tree != null) ? tree.getCellEditor() : null; }
Configures the receiver to allow, or not allow, editing.
/** * Configures the receiver to allow, or not allow, editing. */
protected void setEditable(boolean newValue) { updateCellEditor(); } protected boolean isEditable() { return (tree != null) ? tree.isEditable() : false; }
Resets the selection model. The appropriate listener are installed on the model.
/** * Resets the selection model. The appropriate listener are installed * on the model. */
protected void setSelectionModel(TreeSelectionModel newLSM) { completeEditing(); if(selectionModelPropertyChangeListener != null && treeSelectionModel != null) treeSelectionModel.removePropertyChangeListener (selectionModelPropertyChangeListener); if(treeSelectionListener != null && treeSelectionModel != null) treeSelectionModel.removeTreeSelectionListener (treeSelectionListener); treeSelectionModel = newLSM; if(treeSelectionModel != null) { if(selectionModelPropertyChangeListener != null) treeSelectionModel.addPropertyChangeListener (selectionModelPropertyChangeListener); if(treeSelectionListener != null) treeSelectionModel.addTreeSelectionListener (treeSelectionListener); if(treeState != null) treeState.setSelectionModel(treeSelectionModel); } else if(treeState != null) treeState.setSelectionModel(null); if(tree != null) tree.repaint(); } protected TreeSelectionModel getSelectionModel() { return treeSelectionModel; } // // TreeUI methods //
Returns the Rectangle enclosing the label portion that the last item in path will be drawn into. Will return null if any component in path is currently valid.
/** * Returns the Rectangle enclosing the label portion that the * last item in path will be drawn into. Will return null if * any component in path is currently valid. */
public Rectangle getPathBounds(JTree tree, TreePath path) { if(tree != null && treeState != null) { return getPathBounds(path, tree.getInsets(), new Rectangle()); } return null; } private Rectangle getPathBounds(TreePath path, Insets insets, Rectangle bounds) { bounds = treeState.getBounds(path, bounds); if (bounds != null) { if (leftToRight) { bounds.x += insets.left; } else { bounds.x = tree.getWidth() - (bounds.x + bounds.width) - insets.right; } bounds.y += insets.top; } return bounds; }
Returns the path for passed in row. If row is not visible null is returned.
/** * Returns the path for passed in row. If row is not visible * null is returned. */
public TreePath getPathForRow(JTree tree, int row) { return (treeState != null) ? treeState.getPathForRow(row) : null; }
Returns the row that the last item identified in path is visible at. Will return -1 if any of the elements in path are not currently visible.
/** * Returns the row that the last item identified in path is visible * at. Will return -1 if any of the elements in path are not * currently visible. */
public int getRowForPath(JTree tree, TreePath path) { return (treeState != null) ? treeState.getRowForPath(path) : -1; }
Returns the number of rows that are being displayed.
/** * Returns the number of rows that are being displayed. */
public int getRowCount(JTree tree) { return (treeState != null) ? treeState.getRowCount() : 0; }
Returns the path to the node that is closest to x,y. If there is nothing currently visible this will return null, otherwise it'll always return a valid path. If you need to test if the returned object is exactly at x, y you should get the bounds for the returned path and test x, y against that.
/** * Returns the path to the node that is closest to x,y. If * there is nothing currently visible this will return null, otherwise * it'll always return a valid path. If you need to test if the * returned object is exactly at x, y you should get the bounds for * the returned path and test x, y against that. */
public TreePath getClosestPathForLocation(JTree tree, int x, int y) { if(tree != null && treeState != null) { // TreeState doesn't care about the x location, hence it isn't // adjusted y -= tree.getInsets().top; return treeState.getPathClosestTo(x, y); } return null; }
Returns true if the tree is being edited. The item that is being edited can be returned by getEditingPath().
/** * Returns true if the tree is being edited. The item that is being * edited can be returned by getEditingPath(). */
public boolean isEditing(JTree tree) { return (editingComponent != null); }
Stops the current editing session. This has no effect if the tree isn't being edited. Returns true if the editor allows the editing session to stop.
/** * Stops the current editing session. This has no effect if the * tree isn't being edited. Returns true if the editor allows the * editing session to stop. */
public boolean stopEditing(JTree tree) { if(editingComponent != null && cellEditor.stopCellEditing()) { completeEditing(false, false, true); return true; } return false; }
Cancels the current editing session.
/** * Cancels the current editing session. */
public void cancelEditing(JTree tree) { if(editingComponent != null) { completeEditing(false, true, false); } }
Selects the last item in path and tries to edit it. Editing will fail if the CellEditor won't allow it for the selected item.
/** * Selects the last item in path and tries to edit it. Editing will * fail if the CellEditor won't allow it for the selected item. */
public void startEditingAtPath(JTree tree, TreePath path) { tree.scrollPathToVisible(path); if(path != null && tree.isVisible(path)) startEditing(path, null); }
Returns the path to the element that is being edited.
/** * Returns the path to the element that is being edited. */
public TreePath getEditingPath(JTree tree) { return editingPath; } // // Install methods // public void installUI(JComponent c) { if ( c == null ) { throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" ); } tree = (JTree)c; prepareForUIInstall(); // Boilerplate install block installDefaults(); installKeyboardActions(); installComponents(); installListeners(); completeUIInstall(); }
Invoked after the tree instance variable has been set, but before any defaults/listeners have been installed.
/** * Invoked after the <code>tree</code> instance variable has been * set, but before any defaults/listeners have been installed. */
protected void prepareForUIInstall() { drawingCache = new Hashtable<TreePath,Boolean>(7); // Data member initializations leftToRight = BasicGraphicsUtils.isLeftToRight(tree); stopEditingInCompleteEditing = true; lastSelectedRow = -1; leadRow = -1; preferredSize = new Dimension(); largeModel = tree.isLargeModel(); if(getRowHeight() <= 0) largeModel = false; setModel(tree.getModel()); }
Invoked from installUI after all the defaults/listeners have been installed.
/** * Invoked from installUI after all the defaults/listeners have been * installed. */
protected void completeUIInstall() { // Custom install code this.setShowsRootHandles(tree.getShowsRootHandles()); updateRenderer(); updateDepthOffset(); setSelectionModel(tree.getSelectionModel()); // Create, if necessary, the TreeState instance. treeState = createLayoutCache(); configureLayoutCache(); updateSize(); } protected void installDefaults() { if(tree.getBackground() == null || tree.getBackground() instanceof UIResource) { tree.setBackground(UIManager.getColor("Tree.background")); } if(getHashColor() == null || getHashColor() instanceof UIResource) { setHashColor(UIManager.getColor("Tree.hash")); } if (tree.getFont() == null || tree.getFont() instanceof UIResource) tree.setFont( UIManager.getFont("Tree.font") ); // JTree's original row height is 16. To correctly display the // contents on Linux we should have set it to 18, Windows 19 and // Solaris 20. As these values vary so much it's too hard to // be backward compatable and try to update the row height, we're // therefor NOT going to adjust the row height based on font. If the // developer changes the font, it's there responsibility to update // the row height. setExpandedIcon( (Icon)UIManager.get( "Tree.expandedIcon" ) ); setCollapsedIcon( (Icon)UIManager.get( "Tree.collapsedIcon" ) ); setLeftChildIndent(((Integer)UIManager.get("Tree.leftChildIndent")). intValue()); setRightChildIndent(((Integer)UIManager.get("Tree.rightChildIndent")). intValue()); LookAndFeel.installProperty(tree, "rowHeight", UIManager.get("Tree.rowHeight")); largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0); Object scrollsOnExpand = UIManager.get("Tree.scrollsOnExpand"); if (scrollsOnExpand != null) { LookAndFeel.installProperty(tree, "scrollsOnExpand", scrollsOnExpand); } paintLines = UIManager.getBoolean("Tree.paintLines"); lineTypeDashed = UIManager.getBoolean("Tree.lineTypeDashed"); Long l = (Long)UIManager.get("Tree.timeFactor"); timeFactor = (l!=null) ? l.longValue() : 1000L; Object showsRootHandles = UIManager.get("Tree.showsRootHandles"); if (showsRootHandles != null) { LookAndFeel.installProperty(tree, JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles); } } protected void installListeners() { if ( (propertyChangeListener = createPropertyChangeListener()) != null ) { tree.addPropertyChangeListener(propertyChangeListener); } if ( (mouseListener = createMouseListener()) != null ) { tree.addMouseListener(mouseListener); if (mouseListener instanceof MouseMotionListener) { tree.addMouseMotionListener((MouseMotionListener)mouseListener); } } if ((focusListener = createFocusListener()) != null ) { tree.addFocusListener(focusListener); } if ((keyListener = createKeyListener()) != null) { tree.addKeyListener(keyListener); } if((treeExpansionListener = createTreeExpansionListener()) != null) { tree.addTreeExpansionListener(treeExpansionListener); } if((treeModelListener = createTreeModelListener()) != null && treeModel != null) { treeModel.addTreeModelListener(treeModelListener); } if((selectionModelPropertyChangeListener = createSelectionModelPropertyChangeListener()) != null && treeSelectionModel != null) { treeSelectionModel.addPropertyChangeListener (selectionModelPropertyChangeListener); } if((treeSelectionListener = createTreeSelectionListener()) != null && treeSelectionModel != null) { treeSelectionModel.addTreeSelectionListener(treeSelectionListener); } TransferHandler th = tree.getTransferHandler(); if (th == null || th instanceof UIResource) { tree.setTransferHandler(defaultTransferHandler); // default TransferHandler doesn't support drop // so we don't want drop handling if (tree.getDropTarget() instanceof UIResource) { tree.setDropTarget(null); } } LookAndFeel.installProperty(tree, "opaque", Boolean.TRUE); } protected void installKeyboardActions() { InputMap km = getInputMap(JComponent. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); SwingUtilities.replaceUIInputMap(tree, JComponent. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km); km = getInputMap(JComponent.WHEN_FOCUSED); SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, km); LazyActionMap.installLazyActionMap(tree, BasicTreeUI.class, "Tree.actionMap"); } InputMap getInputMap(int condition) { if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { return (InputMap)DefaultLookup.get(tree, this, "Tree.ancestorInputMap"); } else if (condition == JComponent.WHEN_FOCUSED) { InputMap keyMap = (InputMap)DefaultLookup.get(tree, this, "Tree.focusInputMap"); InputMap rtlKeyMap; if (tree.getComponentOrientation().isLeftToRight() || ((rtlKeyMap = (InputMap)DefaultLookup.get(tree, this, "Tree.focusInputMap.RightToLeft")) == null)) { return keyMap; } else { rtlKeyMap.setParent(keyMap); return rtlKeyMap; } } return null; }
Intalls the subcomponents of the tree, which is the renderer pane.
/** * Intalls the subcomponents of the tree, which is the renderer pane. */
protected void installComponents() { if ((rendererPane = createCellRendererPane()) != null) { tree.add( rendererPane ); } } // // Create methods. //
Creates an instance of NodeDimensions that is able to determine the size of a given node in the tree.
/** * Creates an instance of NodeDimensions that is able to determine * the size of a given node in the tree. */
protected AbstractLayoutCache.NodeDimensions createNodeDimensions() { return new NodeDimensionsHandler(); }
Creates a listener that is responsible that updates the UI based on how the tree changes.
/** * Creates a listener that is responsible that updates the UI based on * how the tree changes. */
protected PropertyChangeListener createPropertyChangeListener() { return getHandler(); } private Handler getHandler() { if (handler == null) { handler = new Handler(); } return handler; }
Creates the listener responsible for updating the selection based on mouse events.
/** * Creates the listener responsible for updating the selection based on * mouse events. */
protected MouseListener createMouseListener() { return getHandler(); }
Creates a listener that is responsible for updating the display when focus is lost/gained.
/** * Creates a listener that is responsible for updating the display * when focus is lost/gained. */
protected FocusListener createFocusListener() { return getHandler(); }
Creates the listener reponsible for getting key events from the tree.
/** * Creates the listener reponsible for getting key events from * the tree. */
protected KeyListener createKeyListener() { return getHandler(); }
Creates the listener responsible for getting property change events from the selection model.
/** * Creates the listener responsible for getting property change * events from the selection model. */
protected PropertyChangeListener createSelectionModelPropertyChangeListener() { return getHandler(); }
Creates the listener that updates the display based on selection change methods.
/** * Creates the listener that updates the display based on selection change * methods. */
protected TreeSelectionListener createTreeSelectionListener() { return getHandler(); }
Creates a listener to handle events from the current editor.
/** * Creates a listener to handle events from the current editor. */
protected CellEditorListener createCellEditorListener() { return getHandler(); }
Creates and returns a new ComponentHandler. This is used for the large model to mark the validCachedPreferredSize as invalid when the component moves.
/** * Creates and returns a new ComponentHandler. This is used for * the large model to mark the validCachedPreferredSize as invalid * when the component moves. */
protected ComponentListener createComponentListener() { return new ComponentHandler(); }
Creates and returns the object responsible for updating the treestate when nodes expanded state changes.
/** * Creates and returns the object responsible for updating the treestate * when nodes expanded state changes. */
protected TreeExpansionListener createTreeExpansionListener() { return getHandler(); }
Creates the object responsible for managing what is expanded, as well as the size of nodes.
/** * Creates the object responsible for managing what is expanded, as * well as the size of nodes. */
protected AbstractLayoutCache createLayoutCache() { if(isLargeModel() && getRowHeight() > 0) { return new FixedHeightLayoutCache(); } return new VariableHeightLayoutCache(); }
Returns the renderer pane that renderer components are placed in.
/** * Returns the renderer pane that renderer components are placed in. */
protected CellRendererPane createCellRendererPane() { return new CellRendererPane(); }
Creates a default cell editor.
/** * Creates a default cell editor. */
protected TreeCellEditor createDefaultCellEditor() { if(currentCellRenderer != null && (currentCellRenderer instanceof DefaultTreeCellRenderer)) { DefaultTreeCellEditor editor = new DefaultTreeCellEditor (tree, (DefaultTreeCellRenderer)currentCellRenderer); return editor; } return new DefaultTreeCellEditor(tree, null); }
Returns the default cell renderer that is used to do the stamping of each node.
/** * Returns the default cell renderer that is used to do the * stamping of each node. */
protected TreeCellRenderer createDefaultCellRenderer() { return new DefaultTreeCellRenderer(); }
Returns a listener that can update the tree when the model changes.
/** * Returns a listener that can update the tree when the model changes. */
protected TreeModelListener createTreeModelListener() { return getHandler(); } // // Uninstall methods // public void uninstallUI(JComponent c) { completeEditing(); prepareForUIUninstall(); uninstallDefaults(); uninstallListeners(); uninstallKeyboardActions(); uninstallComponents(); completeUIUninstall(); } protected void prepareForUIUninstall() { } protected void completeUIUninstall() { if(createdRenderer) { tree.setCellRenderer(null); } if(createdCellEditor) { tree.setCellEditor(null); } cellEditor = null; currentCellRenderer = null; rendererPane = null; componentListener = null; propertyChangeListener = null; mouseListener = null; focusListener = null; keyListener = null; setSelectionModel(null); treeState = null; drawingCache = null; selectionModelPropertyChangeListener = null; tree = null; treeModel = null; treeSelectionModel = null; treeSelectionListener = null; treeExpansionListener = null; } protected void uninstallDefaults() { if (tree.getTransferHandler() instanceof UIResource) { tree.setTransferHandler(null); } } protected void uninstallListeners() { if(componentListener != null) { tree.removeComponentListener(componentListener); } if (propertyChangeListener != null) { tree.removePropertyChangeListener(propertyChangeListener); } if (mouseListener != null) { tree.removeMouseListener(mouseListener); if (mouseListener instanceof MouseMotionListener) { tree.removeMouseMotionListener((MouseMotionListener)mouseListener); } } if (focusListener != null) { tree.removeFocusListener(focusListener); } if (keyListener != null) { tree.removeKeyListener(keyListener); } if(treeExpansionListener != null) { tree.removeTreeExpansionListener(treeExpansionListener); } if(treeModel != null && treeModelListener != null) { treeModel.removeTreeModelListener(treeModelListener); } if(selectionModelPropertyChangeListener != null && treeSelectionModel != null) { treeSelectionModel.removePropertyChangeListener (selectionModelPropertyChangeListener); } if(treeSelectionListener != null && treeSelectionModel != null) { treeSelectionModel.removeTreeSelectionListener (treeSelectionListener); } handler = null; } protected void uninstallKeyboardActions() { SwingUtilities.replaceUIActionMap(tree, null); SwingUtilities.replaceUIInputMap(tree, JComponent. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, null); }
Uninstalls the renderer pane.
/** * Uninstalls the renderer pane. */
protected void uninstallComponents() { if(rendererPane != null) { tree.remove(rendererPane); } }
Recomputes the right margin, and invalidates any tree states
/** * Recomputes the right margin, and invalidates any tree states */
private void redoTheLayout() { if (treeState != null) { treeState.invalidateSizes(); } }
Returns the baseline.
Throws:
See Also:
Since:1.6
/** * Returns the baseline. * * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} * @see javax.swing.JComponent#getBaseline(int, int) * @since 1.6 */
public int getBaseline(JComponent c, int width, int height) { super.getBaseline(c, width, height); UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults(); Component renderer = (Component)lafDefaults.get( BASELINE_COMPONENT_KEY); if (renderer == null) { TreeCellRenderer tcr = createDefaultCellRenderer(); renderer = tcr.getTreeCellRendererComponent( tree, "a", false, false, false, -1, false); lafDefaults.put(BASELINE_COMPONENT_KEY, renderer); } int rowHeight = tree.getRowHeight(); int baseline; if (rowHeight > 0) { baseline = renderer.getBaseline(Integer.MAX_VALUE, rowHeight); } else { Dimension pref = renderer.getPreferredSize(); baseline = renderer.getBaseline(pref.width, pref.height); } return baseline + tree.getInsets().top; }
Returns an enum indicating how the baseline of the component changes as the size changes.
Throws:
  • NullPointerException – {@inheritDoc}
See Also:
Since:1.6
/** * Returns an enum indicating how the baseline of the component * changes as the size changes. * * @throws NullPointerException {@inheritDoc} * @see javax.swing.JComponent#getBaseline(int, int) * @since 1.6 */
public Component.BaselineResizeBehavior getBaselineResizeBehavior( JComponent c) { super.getBaselineResizeBehavior(c); return Component.BaselineResizeBehavior.CONSTANT_ASCENT; } // // Painting routines. // public void paint(Graphics g, JComponent c) { if (tree != c) { throw new InternalError("incorrect component"); } // Should never happen if installed for a UI if(treeState == null) { return; } Rectangle paintBounds = g.getClipBounds(); Insets insets = tree.getInsets(); TreePath initialPath = getClosestPathForLocation (tree, 0, paintBounds.y); Enumeration paintingEnumerator = treeState.getVisiblePathsFrom (initialPath); int row = treeState.getRowForPath(initialPath); int endY = paintBounds.y + paintBounds.height; drawingCache.clear(); if(initialPath != null && paintingEnumerator != null) { TreePath parentPath = initialPath; // Draw the lines, knobs, and rows // Find each parent and have them draw a line to their last child parentPath = parentPath.getParentPath(); while(parentPath != null) { paintVerticalPartOfLeg(g, paintBounds, insets, parentPath); drawingCache.put(parentPath, Boolean.TRUE); parentPath = parentPath.getParentPath(); } boolean done = false; // Information for the node being rendered. boolean isExpanded; boolean hasBeenExpanded; boolean isLeaf; Rectangle boundsBuffer = new Rectangle(); Rectangle bounds; TreePath path; boolean rootVisible = isRootVisible(); while(!done && paintingEnumerator.hasMoreElements()) { path = (TreePath)paintingEnumerator.nextElement(); if(path != null) { isLeaf = treeModel.isLeaf(path.getLastPathComponent()); if(isLeaf) isExpanded = hasBeenExpanded = false; else { isExpanded = treeState.getExpandedState(path); hasBeenExpanded = tree.hasBeenExpanded(path); } bounds = getPathBounds(path, insets, boundsBuffer); if(bounds == null) // This will only happen if the model changes out // from under us (usually in another thread). // Swing isn't multithreaded, but I'll put this // check in anyway. return; // See if the vertical line to the parent has been drawn. parentPath = path.getParentPath(); if(parentPath != null) { if(drawingCache.get(parentPath) == null) { paintVerticalPartOfLeg(g, paintBounds, insets, parentPath); drawingCache.put(parentPath, Boolean.TRUE); } paintHorizontalPartOfLeg(g, paintBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); } else if(rootVisible && row == 0) { paintHorizontalPartOfLeg(g, paintBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); } if(shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) { paintExpandControl(g, paintBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); } paintRow(g, paintBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); if((bounds.y + bounds.height) >= endY) done = true; } else { done = true; } row++; } } paintDropLine(g); // Empty out the renderer pane, allowing renderers to be gc'ed. rendererPane.removeAll(); drawingCache.clear(); }
Tells if a DropLocation should be indicated by a line between nodes. This is meant for javax.swing.DropMode.INSERT and javax.swing.DropMode.ON_OR_INSERT drop modes.
Params:
  • loc – a DropLocation
Returns:true if the drop location should be shown as a line
Since:1.7
/** * Tells if a {@code DropLocation} should be indicated by a line between * nodes. This is meant for {@code javax.swing.DropMode.INSERT} and * {@code javax.swing.DropMode.ON_OR_INSERT} drop modes. * * @param loc a {@code DropLocation} * @return {@code true} if the drop location should be shown as a line * @since 1.7 */
protected boolean isDropLine(JTree.DropLocation loc) { return loc != null && loc.getPath() != null && loc.getChildIndex() != -1; }
Paints the drop line.
Params:
  • g – Graphics object to draw on
Since:1.7
/** * Paints the drop line. * * @param g {@code Graphics} object to draw on * @since 1.7 */
protected void paintDropLine(Graphics g) { JTree.DropLocation loc = tree.getDropLocation(); if (!isDropLine(loc)) { return; } Color c = UIManager.getColor("Tree.dropLineColor"); if (c != null) { g.setColor(c); Rectangle rect = getDropLineRect(loc); g.fillRect(rect.x, rect.y, rect.width, rect.height); } }
Returns a unbounding box for the drop line.
Params:
  • loc – a DropLocation
Returns:bounding box for the drop line
Since:1.7
/** * Returns a unbounding box for the drop line. * * @param loc a {@code DropLocation} * @return bounding box for the drop line * @since 1.7 */
protected Rectangle getDropLineRect(JTree.DropLocation loc) { Rectangle rect; TreePath path = loc.getPath(); int index = loc.getChildIndex(); boolean ltr = leftToRight; Insets insets = tree.getInsets(); if (tree.getRowCount() == 0) { rect = new Rectangle(insets.left, insets.top, tree.getWidth() - insets.left - insets.right, 0); } else { TreeModel model = getModel(); Object root = model.getRoot(); if (path.getLastPathComponent() == root && index >= model.getChildCount(root)) { rect = tree.getRowBounds(tree.getRowCount() - 1); rect.y = rect.y + rect.height; Rectangle xRect; if (!tree.isRootVisible()) { xRect = tree.getRowBounds(0); } else if (model.getChildCount(root) == 0){ xRect = tree.getRowBounds(0); xRect.x += totalChildIndent; xRect.width -= totalChildIndent + totalChildIndent; } else { TreePath lastChildPath = path.pathByAddingChild( model.getChild(root, model.getChildCount(root) - 1)); xRect = tree.getPathBounds(lastChildPath); } rect.x = xRect.x; rect.width = xRect.width; } else { rect = tree.getPathBounds(path.pathByAddingChild( model.getChild(path.getLastPathComponent(), index))); } } if (rect.y != 0) { rect.y--; } if (!ltr) { rect.x = rect.x + rect.width - 100; } rect.width = 100; rect.height = 2; return rect; }
Paints the horizontal part of the leg. The receiver should NOT modify clipBounds, or insets.

NOTE: parentRow can be -1 if the root is not visible.

/** * Paints the horizontal part of the leg. The receiver should * NOT modify <code>clipBounds</code>, or <code>insets</code>.<p> * NOTE: <code>parentRow</code> can be -1 if the root is not visible. */
protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) { if (!paintLines) { return; } // Don't paint the legs for the root'ish node if the int depth = path.getPathCount() - 1; if((depth == 0 || (depth == 1 && !isRootVisible())) && !getShowsRootHandles()) { return; } int clipLeft = clipBounds.x; int clipRight = clipBounds.x + clipBounds.width; int clipTop = clipBounds.y; int clipBottom = clipBounds.y + clipBounds.height; int lineY = bounds.y + bounds.height / 2; if (leftToRight) { int leftX = bounds.x - getRightChildIndent(); int nodeX = bounds.x - getHorizontalLegBuffer(); if(lineY >= clipTop && lineY < clipBottom && nodeX >= clipLeft && leftX < clipRight && leftX < nodeX) { g.setColor(getHashColor()); paintHorizontalLine(g, tree, lineY, leftX, nodeX - 1); } } else { int nodeX = bounds.x + bounds.width + getHorizontalLegBuffer(); int rightX = bounds.x + bounds.width + getRightChildIndent(); if(lineY >= clipTop && lineY < clipBottom && rightX >= clipLeft && nodeX < clipRight && nodeX < rightX) { g.setColor(getHashColor()); paintHorizontalLine(g, tree, lineY, nodeX, rightX - 1); } } }
Paints the vertical part of the leg. The receiver should NOT modify clipBounds, insets.
/** * Paints the vertical part of the leg. The receiver should * NOT modify <code>clipBounds</code>, <code>insets</code>. */
protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path) { if (!paintLines) { return; } int depth = path.getPathCount() - 1; if (depth == 0 && !getShowsRootHandles() && !isRootVisible()) { return; } int lineX = getRowX(-1, depth + 1); if (leftToRight) { lineX = lineX - getRightChildIndent() + insets.left; } else { lineX = tree.getWidth() - lineX - insets.right + getRightChildIndent() - 1; } int clipLeft = clipBounds.x; int clipRight = clipBounds.x + (clipBounds.width - 1); if (lineX >= clipLeft && lineX <= clipRight) { int clipTop = clipBounds.y; int clipBottom = clipBounds.y + clipBounds.height; Rectangle parentBounds = getPathBounds(tree, path); Rectangle lastChildBounds = getPathBounds(tree, getLastChildPath(path)); if(lastChildBounds == null) // This shouldn't happen, but if the model is modified // in another thread it is possible for this to happen. // Swing isn't multithreaded, but I'll add this check in // anyway. return; int top; if(parentBounds == null) { top = Math.max(insets.top + getVerticalLegBuffer(), clipTop); } else top = Math.max(parentBounds.y + parentBounds.height + getVerticalLegBuffer(), clipTop); if(depth == 0 && !isRootVisible()) { TreeModel model = getModel(); if(model != null) { Object root = model.getRoot(); if(model.getChildCount(root) > 0) { parentBounds = getPathBounds(tree, path. pathByAddingChild(model.getChild(root, 0))); if(parentBounds != null) top = Math.max(insets.top + getVerticalLegBuffer(), parentBounds.y + parentBounds.height / 2); } } } int bottom = Math.min(lastChildBounds.y + (lastChildBounds.height / 2), clipBottom); if (top <= bottom) { g.setColor(getHashColor()); paintVerticalLine(g, tree, lineX, top, bottom); } } }
Paints the expand (toggle) part of a row. The receiver should NOT modify clipBounds, or insets.
/** * Paints the expand (toggle) part of a row. The receiver should * NOT modify <code>clipBounds</code>, or <code>insets</code>. */
protected void paintExpandControl(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) { Object value = path.getLastPathComponent(); // Draw icons if not a leaf and either hasn't been loaded, // or the model child count is > 0. if (!isLeaf && (!hasBeenExpanded || treeModel.getChildCount(value) > 0)) { int middleXOfKnob; if (leftToRight) { middleXOfKnob = bounds.x - getRightChildIndent() + 1; } else { middleXOfKnob = bounds.x + bounds.width + getRightChildIndent() - 1; } int middleYOfKnob = bounds.y + (bounds.height / 2); if (isExpanded) { Icon expandedIcon = getExpandedIcon(); if(expandedIcon != null) drawCentered(tree, g, expandedIcon, middleXOfKnob, middleYOfKnob ); } else { Icon collapsedIcon = getCollapsedIcon(); if(collapsedIcon != null) drawCentered(tree, g, collapsedIcon, middleXOfKnob, middleYOfKnob); } } }
Paints the renderer part of a row. The receiver should NOT modify clipBounds, or insets.
/** * Paints the renderer part of a row. The receiver should * NOT modify <code>clipBounds</code>, or <code>insets</code>. */
protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) { // Don't paint the renderer if editing this row. if(editingComponent != null && editingRow == row) return; int leadIndex; if(tree.hasFocus()) { leadIndex = getLeadSelectionRow(); } else leadIndex = -1; Component component; component = currentCellRenderer.getTreeCellRendererComponent (tree, path.getLastPathComponent(), tree.isRowSelected(row), isExpanded, isLeaf, row, (leadIndex == row)); rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y, bounds.width, bounds.height, true); }
Returns true if the expand (toggle) control should be drawn for the specified row.
/** * Returns true if the expand (toggle) control should be drawn for * the specified row. */
protected boolean shouldPaintExpandControl(TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) { if(isLeaf) return false; int depth = path.getPathCount() - 1; if((depth == 0 || (depth == 1 && !isRootVisible())) && !getShowsRootHandles()) return false; return true; }
Paints a vertical line.
/** * Paints a vertical line. */
protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom) { if (lineTypeDashed) { drawDashedVerticalLine(g, x, top, bottom); } else { g.drawLine(x, top, x, bottom); } }
Paints a horizontal line.
/** * Paints a horizontal line. */
protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right) { if (lineTypeDashed) { drawDashedHorizontalLine(g, y, left, right); } else { g.drawLine(left, y, right, y); } }
The vertical element of legs between nodes starts at the bottom of the parent node by default. This method makes the leg start below that.
/** * The vertical element of legs between nodes starts at the bottom of the * parent node by default. This method makes the leg start below that. */
protected int getVerticalLegBuffer() { return 0; }
The horizontal element of legs between nodes starts at the right of the left-hand side of the child node by default. This method makes the leg end before that.
/** * The horizontal element of legs between nodes starts at the * right of the left-hand side of the child node by default. This * method makes the leg end before that. */
protected int getHorizontalLegBuffer() { return 0; } private int findCenteredX(int x, int iconWidth) { return leftToRight ? x - (int)Math.ceil(iconWidth / 2.0) : x - (int)Math.floor(iconWidth / 2.0); } // // Generic painting methods // // Draws the icon centered at (x,y) protected void drawCentered(Component c, Graphics graphics, Icon icon, int x, int y) { icon.paintIcon(c, graphics, findCenteredX(x, icon.getIconWidth()), y - icon.getIconHeight() / 2); } // This method is slow -- revisit when Java2D is ready. // assumes x1 <= x2 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2){ // Drawing only even coordinates helps join line segments so they // appear as one line. This can be defeated by translating the // Graphics by an odd amount. x1 += (x1 % 2); for (int x = x1; x <= x2; x+=2) { g.drawLine(x, y, x, y); } } // This method is slow -- revisit when Java2D is ready. // assumes y1 <= y2 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) { // Drawing only even coordinates helps join line segments so they // appear as one line. This can be defeated by translating the // Graphics by an odd amount. y1 += (y1 % 2); for (int y = y1; y <= y2; y+=2) { g.drawLine(x, y, x, y); } } // // Various local methods //
Returns the location, along the x-axis, to render a particular row at. The return value does not include any Insets specified on the JTree. This does not check for the validity of the row or depth, it is assumed to be correct and will not throw an Exception if the row or depth doesn't match that of the tree.
Params:
  • row – Row to return x location for
  • depth – Depth of the row
Returns:amount to indent the given row.
Since:1.5
/** * Returns the location, along the x-axis, to render a particular row * at. The return value does not include any Insets specified on the JTree. * This does not check for the validity of the row or depth, it is assumed * to be correct and will not throw an Exception if the row or depth * doesn't match that of the tree. * * @param row Row to return x location for * @param depth Depth of the row * @return amount to indent the given row. * @since 1.5 */
protected int getRowX(int row, int depth) { return totalChildIndent * (depth + depthOffset); }
Makes all the nodes that are expanded in JTree expanded in LayoutCache. This invokes updateExpandedDescendants with the root path.
/** * Makes all the nodes that are expanded in JTree expanded in LayoutCache. * This invokes updateExpandedDescendants with the root path. */
protected void updateLayoutCacheExpandedNodes() { if(treeModel != null && treeModel.getRoot() != null) updateExpandedDescendants(new TreePath(treeModel.getRoot())); } private void updateLayoutCacheExpandedNodesIfNecessary() { if (treeModel != null && treeModel.getRoot() != null) { TreePath rootPath = new TreePath(treeModel.getRoot()); if (tree.isExpanded(rootPath)) { updateLayoutCacheExpandedNodes(); } else { treeState.setExpandedState(rootPath, false); } } }
Updates the expanded state of all the descendants of path by getting the expanded descendants from the tree and forwarding to the tree state.
/** * Updates the expanded state of all the descendants of <code>path</code> * by getting the expanded descendants from the tree and forwarding * to the tree state. */
protected void updateExpandedDescendants(TreePath path) { completeEditing(); if(treeState != null) { treeState.setExpandedState(path, true); Enumeration descendants = tree.getExpandedDescendants(path); if(descendants != null) { while(descendants.hasMoreElements()) { path = (TreePath)descendants.nextElement(); treeState.setExpandedState(path, true); } } updateLeadSelectionRow(); updateSize(); } }
Returns a path to the last child of parent.
/** * Returns a path to the last child of <code>parent</code>. */
protected TreePath getLastChildPath(TreePath parent) { if(treeModel != null) { int childCount = treeModel.getChildCount (parent.getLastPathComponent()); if(childCount > 0) return parent.pathByAddingChild(treeModel.getChild (parent.getLastPathComponent(), childCount - 1)); } return null; }
Updates how much each depth should be offset by.
/** * Updates how much each depth should be offset by. */
protected void updateDepthOffset() { if(isRootVisible()) { if(getShowsRootHandles()) depthOffset = 1; else depthOffset = 0; } else if(!getShowsRootHandles()) depthOffset = -1; else depthOffset = 0; }
Updates the cellEditor based on the editability of the JTree that we're contained in. If the tree is editable but doesn't have a cellEditor, a basic one will be used.
/** * Updates the cellEditor based on the editability of the JTree that * we're contained in. If the tree is editable but doesn't have a * cellEditor, a basic one will be used. */
protected void updateCellEditor() { TreeCellEditor newEditor; completeEditing(); if(tree == null) newEditor = null; else { if(tree.isEditable()) { newEditor = tree.getCellEditor(); if(newEditor == null) { newEditor = createDefaultCellEditor(); if(newEditor != null) { tree.setCellEditor(newEditor); createdCellEditor = true; } } } else newEditor = null; } if(newEditor != cellEditor) { if(cellEditor != null && cellEditorListener != null) cellEditor.removeCellEditorListener(cellEditorListener); cellEditor = newEditor; if(cellEditorListener == null) cellEditorListener = createCellEditorListener(); if(newEditor != null && cellEditorListener != null) newEditor.addCellEditorListener(cellEditorListener); createdCellEditor = false; } }
Messaged from the tree we're in when the renderer has changed.
/** * Messaged from the tree we're in when the renderer has changed. */
protected void updateRenderer() { if(tree != null) { TreeCellRenderer newCellRenderer; newCellRenderer = tree.getCellRenderer(); if(newCellRenderer == null) { tree.setCellRenderer(createDefaultCellRenderer()); createdRenderer = true; } else { createdRenderer = false; currentCellRenderer = newCellRenderer; if(createdCellEditor) { tree.setCellEditor(null); } } } else { createdRenderer = false; currentCellRenderer = null; } updateCellEditor(); }
Resets the TreeState instance based on the tree we're providing the look and feel for.
/** * Resets the TreeState instance based on the tree we're providing the * look and feel for. */
protected void configureLayoutCache() { if(treeState != null && tree != null) { if(nodeDimensions == null) nodeDimensions = createNodeDimensions(); treeState.setNodeDimensions(nodeDimensions); treeState.setRootVisible(tree.isRootVisible()); treeState.setRowHeight(tree.getRowHeight()); treeState.setSelectionModel(getSelectionModel()); // Only do this if necessary, may loss state if call with // same model as it currently has. if(treeState.getModel() != tree.getModel()) treeState.setModel(tree.getModel()); updateLayoutCacheExpandedNodesIfNecessary(); // Create a listener to update preferred size when bounds // changes, if necessary. if(isLargeModel()) { if(componentListener == null) { componentListener = createComponentListener(); if(componentListener != null) tree.addComponentListener(componentListener); } } else if(componentListener != null) { tree.removeComponentListener(componentListener); componentListener = null; } } else if(componentListener != null) { tree.removeComponentListener(componentListener); componentListener = null; } }
Marks the cached size as being invalid, and messages the tree with treeDidChange.
/** * Marks the cached size as being invalid, and messages the * tree with <code>treeDidChange</code>. */
protected void updateSize() { validCachedPreferredSize = false; tree.treeDidChange(); } private void updateSize0() { validCachedPreferredSize = false; tree.revalidate(); }
Updates the preferredSize instance variable, which is returned from getPreferredSize().

For left to right orientations, the size is determined from the current AbstractLayoutCache. For RTL orientations, the preferred size becomes the width minus the minimum x position.

/** * Updates the <code>preferredSize</code> instance variable, * which is returned from <code>getPreferredSize()</code>.<p> * For left to right orientations, the size is determined from the * current AbstractLayoutCache. For RTL orientations, the preferred size * becomes the width minus the minimum x position. */
protected void updateCachedPreferredSize() { if(treeState != null) { Insets i = tree.getInsets(); if(isLargeModel()) { Rectangle visRect = tree.getVisibleRect(); if (visRect.x == 0 && visRect.y == 0 && visRect.width == 0 && visRect.height == 0 && tree.getVisibleRowCount() > 0) { // The tree doesn't have a valid bounds yet. Calculate // based on visible row count. visRect.width = 1; visRect.height = tree.getRowHeight() * tree.getVisibleRowCount(); } else { visRect.x -= i.left; visRect.y -= i.top; } // we should consider a non-visible area above Component component = SwingUtilities.getUnwrappedParent(tree); if (component instanceof JViewport) { component = component.getParent(); if (component instanceof JScrollPane) { JScrollPane pane = (JScrollPane) component; JScrollBar bar = pane.getHorizontalScrollBar(); if ((bar != null) && bar.isVisible()) { int height = bar.getHeight(); visRect.y -= height; visRect.height += height; } } } preferredSize.width = treeState.getPreferredWidth(visRect); } else { preferredSize.width = treeState.getPreferredWidth(null); } preferredSize.height = treeState.getPreferredHeight(); preferredSize.width += i.left + i.right; preferredSize.height += i.top + i.bottom; } validCachedPreferredSize = true; }
Messaged from the VisibleTreeNode after it has been expanded.
/** * Messaged from the VisibleTreeNode after it has been expanded. */
protected void pathWasExpanded(TreePath path) { if(tree != null) { tree.fireTreeExpanded(path); } }
Messaged from the VisibleTreeNode after it has collapsed.
/** * Messaged from the VisibleTreeNode after it has collapsed. */
protected void pathWasCollapsed(TreePath path) { if(tree != null) { tree.fireTreeCollapsed(path); } }
Ensures that the rows identified by beginRow through endRow are visible.
/** * Ensures that the rows identified by beginRow through endRow are * visible. */
protected void ensureRowsAreVisible(int beginRow, int endRow) { if(tree != null && beginRow >= 0 && endRow < getRowCount(tree)) { boolean scrollVert = DefaultLookup.getBoolean(tree, this, "Tree.scrollsHorizontallyAndVertically", false); if(beginRow == endRow) { Rectangle scrollBounds = getPathBounds(tree, getPathForRow (tree, beginRow)); if(scrollBounds != null) { if (!scrollVert) { scrollBounds.x = tree.getVisibleRect().x; scrollBounds.width = 1; } tree.scrollRectToVisible(scrollBounds); } } else { Rectangle beginRect = getPathBounds(tree, getPathForRow (tree, beginRow)); if (beginRect != null) { Rectangle visRect = tree.getVisibleRect(); Rectangle testRect = beginRect; int beginY = beginRect.y; int maxY = beginY + visRect.height; for(int counter = beginRow + 1; counter <= endRow; counter++) { testRect = getPathBounds(tree, getPathForRow(tree, counter)); if (testRect == null) { return; } if((testRect.y + testRect.height) > maxY) counter = endRow; } tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1, testRect.y + testRect.height- beginY)); } } } }
Sets the preferred minimum size.
/** Sets the preferred minimum size. */
public void setPreferredMinSize(Dimension newSize) { preferredMinSize = newSize; }
Returns the minimum preferred size.
/** Returns the minimum preferred size. */
public Dimension getPreferredMinSize() { if(preferredMinSize == null) return null; return new Dimension(preferredMinSize); }
Returns the preferred size to properly display the tree, this is a cover method for getPreferredSize(c, true).
/** Returns the preferred size to properly display the tree, * this is a cover method for getPreferredSize(c, true). */
public Dimension getPreferredSize(JComponent c) { return getPreferredSize(c, true); }
Returns the preferred size to represent the tree in c. If checkConsistency is true checkConsistency is messaged first.
/** Returns the preferred size to represent the tree in * <I>c</I>. If <I>checkConsistency</I> is true * <b>checkConsistency</b> is messaged first. */
public Dimension getPreferredSize(JComponent c, boolean checkConsistency) { Dimension pSize = this.getPreferredMinSize(); if(!validCachedPreferredSize) updateCachedPreferredSize(); if(tree != null) { if(pSize != null) return new Dimension(Math.max(pSize.width, preferredSize.width), Math.max(pSize.height, preferredSize.height)); return new Dimension(preferredSize.width, preferredSize.height); } else if(pSize != null) return pSize; else return new Dimension(0, 0); }
Returns the minimum size for this component. Which will be the min preferred size or 0, 0.
/** * Returns the minimum size for this component. Which will be * the min preferred size or 0, 0. */
public Dimension getMinimumSize(JComponent c) { if(this.getPreferredMinSize() != null) return this.getPreferredMinSize(); return new Dimension(0, 0); }
Returns the maximum size for this component, which will be the preferred size if the instance is currently in a JTree, or 0, 0.
/** * Returns the maximum size for this component, which will be the * preferred size if the instance is currently in a JTree, or 0, 0. */
public Dimension getMaximumSize(JComponent c) { if(tree != null) return getPreferredSize(tree); if(this.getPreferredMinSize() != null) return this.getPreferredMinSize(); return new Dimension(0, 0); }
Messages to stop the editing session. If the UI the receiver is providing the look and feel for returns true from getInvokesStopCellEditing, stopCellEditing will invoked on the current editor. Then completeEditing will be messaged with false, true, false to cancel any lingering editing.
/** * Messages to stop the editing session. If the UI the receiver * is providing the look and feel for returns true from * <code>getInvokesStopCellEditing</code>, stopCellEditing will * invoked on the current editor. Then completeEditing will * be messaged with false, true, false to cancel any lingering * editing. */
protected void completeEditing() { /* If should invoke stopCellEditing, try that */ if(tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing && editingComponent != null) { cellEditor.stopCellEditing(); } /* Invoke cancelCellEditing, this will do nothing if stopCellEditing was successful. */ completeEditing(false, true, false); }
Stops the editing session. If messageStop is true the editor is messaged with stopEditing, if messageCancel is true the editor is messaged with cancelEditing. If messageTree is true the treeModel is messaged with valueForPathChanged.
/** * Stops the editing session. If messageStop is true the editor * is messaged with stopEditing, if messageCancel is true the * editor is messaged with cancelEditing. If messageTree is true * the treeModel is messaged with valueForPathChanged. */
protected void completeEditing(boolean messageStop, boolean messageCancel, boolean messageTree) { if(stopEditingInCompleteEditing && editingComponent != null) { Component oldComponent = editingComponent; TreePath oldPath = editingPath; TreeCellEditor oldEditor = cellEditor; Object newValue = oldEditor.getCellEditorValue(); Rectangle editingBounds = getPathBounds(tree, editingPath); boolean requestFocus = (tree != null && (tree.hasFocus() || SwingUtilities. findFocusOwner(editingComponent) != null)); editingComponent = null; editingPath = null; if(messageStop) oldEditor.stopCellEditing(); else if(messageCancel) oldEditor.cancelCellEditing(); tree.remove(oldComponent); if(editorHasDifferentSize) { treeState.invalidatePathBounds(oldPath); updateSize(); } else if (editingBounds != null) { editingBounds.x = 0; editingBounds.width = tree.getSize().width; tree.repaint(editingBounds); } if(requestFocus) tree.requestFocus(); if(messageTree) treeModel.valueForPathChanged(oldPath, newValue); } } // cover method for startEditing that allows us to pass extra // information into that method via a class variable private boolean startEditingOnRelease(TreePath path, MouseEvent event, MouseEvent releaseEvent) { this.releaseEvent = releaseEvent; try { return startEditing(path, event); } finally { this.releaseEvent = null; } }
Will start editing for node if there is a cellEditor and shouldSelectCell returns true.

This assumes that path is valid and visible.

/** * Will start editing for node if there is a cellEditor and * shouldSelectCell returns true.<p> * This assumes that path is valid and visible. */
protected boolean startEditing(TreePath path, MouseEvent event) { if (isEditing(tree) && tree.getInvokesStopCellEditing() && !stopEditing(tree)) { return false; } completeEditing(); if(cellEditor != null && tree.isPathEditable(path)) { int row = getRowForPath(tree, path); if(cellEditor.isCellEditable(event)) { editingComponent = cellEditor.getTreeCellEditorComponent (tree, path.getLastPathComponent(), tree.isPathSelected(path), tree.isExpanded(path), treeModel.isLeaf(path.getLastPathComponent()), row); Rectangle nodeBounds = getPathBounds(tree, path); if (nodeBounds == null) { return false; } editingRow = row; Dimension editorSize = editingComponent.getPreferredSize(); // Only allow odd heights if explicitly set. if(editorSize.height != nodeBounds.height && getRowHeight() > 0) editorSize.height = getRowHeight(); if(editorSize.width != nodeBounds.width || editorSize.height != nodeBounds.height) { // Editor wants different width or height, invalidate // treeState and relayout. editorHasDifferentSize = true; treeState.invalidatePathBounds(path); updateSize(); // To make sure x/y are updated correctly, fetch // the bounds again. nodeBounds = getPathBounds(tree, path); if (nodeBounds == null) { return false; } } else editorHasDifferentSize = false; tree.add(editingComponent); editingComponent.setBounds(nodeBounds.x, nodeBounds.y, nodeBounds.width, nodeBounds.height); editingPath = path; AWTAccessor.getComponentAccessor().revalidateSynchronously(editingComponent); editingComponent.repaint(); if(cellEditor.shouldSelectCell(event)) { stopEditingInCompleteEditing = false; tree.setSelectionRow(row); stopEditingInCompleteEditing = true; } Component focusedComponent = SwingUtilities2. compositeRequestFocus(editingComponent); boolean selectAll = true; if(event != null) { /* Find the component that will get forwarded all the mouse events until mouseReleased. */ Point componentPoint = SwingUtilities.convertPoint (tree, new Point(event.getX(), event.getY()), editingComponent); /* Create an instance of BasicTreeMouseListener to handle passing the mouse/motion events to the necessary component. */ // We really want similar behavior to getMouseEventTarget, // but it is package private. Component activeComponent = SwingUtilities. getDeepestComponentAt(editingComponent, componentPoint.x, componentPoint.y); if (activeComponent != null) { MouseInputHandler handler = new MouseInputHandler(tree, activeComponent, event, focusedComponent); if (releaseEvent != null) { handler.mouseReleased(releaseEvent); } selectAll = false; } } if (selectAll && focusedComponent instanceof JTextField) { ((JTextField)focusedComponent).selectAll(); } return true; } else editingComponent = null; } return false; } // // Following are primarily for handling mouse events. //
If the mouseX and mouseY are in the expand/collapse region of the row, this will toggle the row.
/** * If the <code>mouseX</code> and <code>mouseY</code> are in the * expand/collapse region of the <code>row</code>, this will toggle * the row. */
protected void checkForClickInExpandControl(TreePath path, int mouseX, int mouseY) { if (isLocationInExpandControl(path, mouseX, mouseY)) { handleExpandControlClick(path, mouseX, mouseY); } }
Returns true if mouseX and mouseY fall in the area of row that is used to expand/collapse the node and the node at row does not represent a leaf.
/** * Returns true if <code>mouseX</code> and <code>mouseY</code> fall * in the area of row that is used to expand/collapse the node and * the node at <code>row</code> does not represent a leaf. */
protected boolean isLocationInExpandControl(TreePath path, int mouseX, int mouseY) { if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){ int boxWidth; Insets i = tree.getInsets(); if(getExpandedIcon() != null) boxWidth = getExpandedIcon().getIconWidth(); else boxWidth = 8; int boxLeftX = getRowX(tree.getRowForPath(path), path.getPathCount() - 1); if (leftToRight) { boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1; } else { boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1; } boxLeftX = findCenteredX(boxLeftX, boxWidth); return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth)); } return false; }
Messaged when the user clicks the particular row, this invokes toggleExpandState.
/** * Messaged when the user clicks the particular row, this invokes * toggleExpandState. */
protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY) { toggleExpandState(path); }
Expands path if it is not expanded, or collapses row if it is expanded. If expanding a path and JTree scrolls on expand, ensureRowsAreVisible is invoked to scroll as many of the children to visible as possible (tries to scroll to last visible descendant of path).
/** * Expands path if it is not expanded, or collapses row if it is expanded. * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible * is invoked to scroll as many of the children to visible as possible * (tries to scroll to last visible descendant of path). */
protected void toggleExpandState(TreePath path) { if(!tree.isExpanded(path)) { int row = getRowForPath(tree, path); tree.expandPath(path); updateSize(); if(row != -1) { if(tree.getScrollsOnExpand()) ensureRowsAreVisible(row, row + treeState. getVisibleChildCount(path)); else ensureRowsAreVisible(row, row); } } else { tree.collapsePath(path); updateSize(); } }
Returning true signifies a mouse event on the node should toggle the selection of only the row under mouse.
/** * Returning true signifies a mouse event on the node should toggle * the selection of only the row under mouse. */
protected boolean isToggleSelectionEvent(MouseEvent event) { return (SwingUtilities.isLeftMouseButton(event) && BasicGraphicsUtils.isMenuShortcutKeyDown(event)); }
Returning true signifies a mouse event on the node should select from the anchor point.
/** * Returning true signifies a mouse event on the node should select * from the anchor point. */
protected boolean isMultiSelectEvent(MouseEvent event) { return (SwingUtilities.isLeftMouseButton(event) && event.isShiftDown()); }
Returning true indicates the row under the mouse should be toggled based on the event. This is invoked after checkForClickInExpandControl, implying the location is not in the expand (toggle) control
/** * Returning true indicates the row under the mouse should be toggled * based on the event. This is invoked after checkForClickInExpandControl, * implying the location is not in the expand (toggle) control */
protected boolean isToggleEvent(MouseEvent event) { if(!SwingUtilities.isLeftMouseButton(event)) { return false; } int clickCount = tree.getToggleClickCount(); if(clickCount <= 0) { return false; } return ((event.getClickCount() % clickCount) == 0); }
Messaged to update the selection based on a MouseEvent over a particular row. If the event is a toggle selection event, the row is either selected, or deselected. If the event identifies a multi selection event, the selection is updated from the anchor point. Otherwise the row is selected, and if the event specified a toggle event the row is expanded/collapsed.
/** * Messaged to update the selection based on a MouseEvent over a * particular row. If the event is a toggle selection event, the * row is either selected, or deselected. If the event identifies * a multi selection event, the selection is updated from the * anchor point. Otherwise the row is selected, and if the event * specified a toggle event the row is expanded/collapsed. */
protected void selectPathForEvent(TreePath path, MouseEvent event) { /* Adjust from the anchor point. */ if(isMultiSelectEvent(event)) { TreePath anchor = getAnchorSelectionPath(); int anchorRow = (anchor == null) ? -1 : getRowForPath(tree, anchor); if(anchorRow == -1 || tree.getSelectionModel(). getSelectionMode() == TreeSelectionModel. SINGLE_TREE_SELECTION) { tree.setSelectionPath(path); } else { int row = getRowForPath(tree, path); TreePath lastAnchorPath = anchor; if (isToggleSelectionEvent(event)) { if (tree.isRowSelected(anchorRow)) { tree.addSelectionInterval(anchorRow, row); } else { tree.removeSelectionInterval(anchorRow, row); tree.addSelectionInterval(row, row); } } else if(row < anchorRow) { tree.setSelectionInterval(row, anchorRow); } else { tree.setSelectionInterval(anchorRow, row); } lastSelectedRow = row; setAnchorSelectionPath(lastAnchorPath); setLeadSelectionPath(path); } } // Should this event toggle the selection of this row? /* Control toggles just this node. */ else if(isToggleSelectionEvent(event)) { if(tree.isPathSelected(path)) tree.removeSelectionPath(path); else tree.addSelectionPath(path); lastSelectedRow = getRowForPath(tree, path); setAnchorSelectionPath(path); setLeadSelectionPath(path); } /* Otherwise set the selection to just this interval. */ else if(SwingUtilities.isLeftMouseButton(event)) { tree.setSelectionPath(path); if(isToggleEvent(event)) { toggleExpandState(path); } } }
Returns:true if the node at row is a leaf.
/** * @return true if the node at <code>row</code> is a leaf. */
protected boolean isLeaf(int row) { TreePath path = getPathForRow(tree, row); if(path != null) return treeModel.isLeaf(path.getLastPathComponent()); // Have to return something here... return true; } // // The following selection methods (lead/anchor) are covers for the // methods in JTree. // private void setAnchorSelectionPath(TreePath newPath) { ignoreLAChange = true; try { tree.setAnchorSelectionPath(newPath); } finally{ ignoreLAChange = false; } } private TreePath getAnchorSelectionPath() { return tree.getAnchorSelectionPath(); } private void setLeadSelectionPath(TreePath newPath) { setLeadSelectionPath(newPath, false); } private void setLeadSelectionPath(TreePath newPath, boolean repaint) { Rectangle bounds = repaint ? getPathBounds(tree, getLeadSelectionPath()) : null; ignoreLAChange = true; try { tree.setLeadSelectionPath(newPath); } finally { ignoreLAChange = false; } leadRow = getRowForPath(tree, newPath); if (repaint) { if (bounds != null) { tree.repaint(getRepaintPathBounds(bounds)); } bounds = getPathBounds(tree, newPath); if (bounds != null) { tree.repaint(getRepaintPathBounds(bounds)); } } } private Rectangle getRepaintPathBounds(Rectangle bounds) { if (UIManager.getBoolean("Tree.repaintWholeRow")) { bounds.x = 0; bounds.width = tree.getWidth(); } return bounds; } private TreePath getLeadSelectionPath() { return tree.getLeadSelectionPath(); }
Updates the lead row of the selection.
Since:1.7
/** * Updates the lead row of the selection. * @since 1.7 */
protected void updateLeadSelectionRow() { leadRow = getRowForPath(tree, getLeadSelectionPath()); }
Returns the lead row of the selection.
Returns:selection lead row
Since:1.7
/** * Returns the lead row of the selection. * * @return selection lead row * @since 1.7 */
protected int getLeadSelectionRow() { return leadRow; }
Extends the selection from the anchor to make newLead the lead of the selection. This does not scroll.
/** * Extends the selection from the anchor to make <code>newLead</code> * the lead of the selection. This does not scroll. */
private void extendSelection(TreePath newLead) { TreePath aPath = getAnchorSelectionPath(); int aRow = (aPath == null) ? -1 : getRowForPath(tree, aPath); int newIndex = getRowForPath(tree, newLead); if(aRow == -1) { tree.setSelectionRow(newIndex); } else { if(aRow < newIndex) { tree.setSelectionInterval(aRow, newIndex); } else { tree.setSelectionInterval(newIndex, aRow); } setAnchorSelectionPath(aPath); setLeadSelectionPath(newLead); } }
Invokes repaint on the JTree for the passed in TreePath, path.
/** * Invokes <code>repaint</code> on the JTree for the passed in TreePath, * <code>path</code>. */
private void repaintPath(TreePath path) { if (path != null) { Rectangle bounds = getPathBounds(tree, path); if (bounds != null) { tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height); } } }
Updates the TreeState in response to nodes expanding/collapsing.
/** * Updates the TreeState in response to nodes expanding/collapsing. */
public class TreeExpansionHandler implements TreeExpansionListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler.
Called whenever an item in the tree has been expanded.
/** * Called whenever an item in the tree has been expanded. */
public void treeExpanded(TreeExpansionEvent event) { getHandler().treeExpanded(event); }
Called whenever an item in the tree has been collapsed.
/** * Called whenever an item in the tree has been collapsed. */
public void treeCollapsed(TreeExpansionEvent event) { getHandler().treeCollapsed(event); } } // BasicTreeUI.TreeExpansionHandler
Updates the preferred size when scrolling (if necessary).
/** * 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.
/** Timer used when inside a scrollpane and the scrollbar is * adjusting. */
protected Timer timer;
ScrollBar that is being adjusted.
/** ScrollBar that is being adjusted. */
protected JScrollBar scrollBar; public void componentMoved(ComponentEvent e) { if(timer == null) { JScrollPane scrollPane = getScrollPane(); if(scrollPane == null) updateSize(); else { scrollBar = scrollPane.getVerticalScrollBar(); if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { // Try the horizontal scrollbar. if((scrollBar = scrollPane.getHorizontalScrollBar()) != null && scrollBar.getValueIsAdjusting()) startTimer(); else updateSize(); } else startTimer(); } } }
Creates, if necessary, and starts a Timer to check if need to resize the bounds.
/** * Creates, if necessary, and starts a Timer to check if need to * resize the bounds. */
protected void startTimer() { if(timer == null) { timer = new Timer(200, this); timer.setRepeats(true); } timer.start(); }
Returns the JScrollPane housing the JTree, or null if one isn't found.
/** * Returns the JScrollPane housing the JTree, or null if one isn't * found. */
protected JScrollPane getScrollPane() { Component c = tree.getParent(); while(c != null && !(c instanceof JScrollPane)) c = c.getParent(); if(c instanceof JScrollPane) return (JScrollPane)c; return null; }
Public as a result of Timer. If the scrollBar is null, or not adjusting, this stops the timer and updates the sizing.
/** * Public as a result of Timer. If the scrollBar is null, or * not adjusting, this stops the timer and updates the sizing. */
public void actionPerformed(ActionEvent ae) { if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { if(timer != null) timer.stop(); updateSize(); timer = null; scrollBar = null; } } } // End of BasicTreeUI.ComponentHandler
Forwards all TreeModel events to the TreeState.
/** * Forwards all TreeModel events to the TreeState. */
public class TreeModelHandler implements TreeModelListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void treeNodesChanged(TreeModelEvent e) { getHandler().treeNodesChanged(e); } public void treeNodesInserted(TreeModelEvent e) { getHandler().treeNodesInserted(e); } public void treeNodesRemoved(TreeModelEvent e) { getHandler().treeNodesRemoved(e); } public void treeStructureChanged(TreeModelEvent e) { getHandler().treeStructureChanged(e); } } // End of BasicTreeUI.TreeModelHandler
Listens for changes in the selection model and updates the display accordingly.
/** * Listens for changes in the selection model and updates the display * accordingly. */
public class TreeSelectionHandler implements TreeSelectionListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler.
Messaged when the selection changes in the tree we're displaying for. Stops editing, messages super and displays the changed paths.
/** * Messaged when the selection changes in the tree we're displaying * for. Stops editing, messages super and displays the changed paths. */
public void valueChanged(TreeSelectionEvent event) { getHandler().valueChanged(event); } }// End of BasicTreeUI.TreeSelectionHandler
Listener responsible for getting cell editing events and updating the tree accordingly.
/** * Listener responsible for getting cell editing events and updating * the tree accordingly. */
public class CellEditorHandler implements CellEditorListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler.
Messaged when editing has stopped in the tree.
/** Messaged when editing has stopped in the tree. */
public void editingStopped(ChangeEvent e) { getHandler().editingStopped(e); }
Messaged when editing has been canceled in the tree.
/** Messaged when editing has been canceled in the tree. */
public void editingCanceled(ChangeEvent e) { getHandler().editingCanceled(e); } } // BasicTreeUI.CellEditorHandler
This is used to get multiple key down events to appropriately generate events.
/** * This is used to get multiple key down events to appropriately generate * events. */
public class KeyHandler extends KeyAdapter { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. // Also note these fields aren't use anymore, nor does Handler have // the old functionality. This behavior worked around an old bug // in JComponent that has long since been fixed.
Key code that is being generated for.
/** Key code that is being generated for. */
protected Action repeatKeyAction;
Set to true while keyPressed is active.
/** Set to true while keyPressed is active. */
protected boolean isKeyDown;
Invoked when a key has been typed. Moves the keyboard focus to the first element whose first letter matches the alphanumeric key pressed by the user. Subsequent same key presses move the keyboard focus to the next object that starts with the same letter.
/** * Invoked when a key has been typed. * * Moves the keyboard focus to the first element * whose first letter matches the alphanumeric key * pressed by the user. Subsequent same key presses * move the keyboard focus to the next object that * starts with the same letter. */
public void keyTyped(KeyEvent e) { getHandler().keyTyped(e); } public void keyPressed(KeyEvent e) { getHandler().keyPressed(e); } public void keyReleased(KeyEvent e) { getHandler().keyReleased(e); } } // End of BasicTreeUI.KeyHandler
Repaints the lead selection row when focus is lost/gained.
/** * Repaints the lead selection row when focus is lost/gained. */
public class FocusHandler implements FocusListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler.
Invoked when focus is activated on the tree we're in, redraws the lead row.
/** * Invoked when focus is activated on the tree we're in, redraws the * lead row. */
public void focusGained(FocusEvent e) { getHandler().focusGained(e); }
Invoked when focus is activated on the tree we're in, redraws the lead row.
/** * Invoked when focus is activated on the tree we're in, redraws the * lead row. */
public void focusLost(FocusEvent e) { getHandler().focusLost(e); } } // End of class BasicTreeUI.FocusHandler
Class responsible for getting size of node, method is forwarded to BasicTreeUI method. X location does not include insets, that is handled in getPathBounds.
/** * Class responsible for getting size of node, method is forwarded * to BasicTreeUI method. X location does not include insets, that is * handled in getPathBounds. */
// This returns locations that don't include any Insets. public class NodeDimensionsHandler extends AbstractLayoutCache.NodeDimensions {
Responsible for getting the size of a particular node.
/** * Responsible for getting the size of a particular node. */
public Rectangle getNodeDimensions(Object value, int row, int depth, boolean expanded, Rectangle size) { // Return size of editing component, if editing and asking // for editing row. if(editingComponent != null && editingRow == row) { Dimension prefSize = editingComponent. getPreferredSize(); int rh = getRowHeight(); if(rh > 0 && rh != prefSize.height) prefSize.height = rh; if(size != null) { size.x = getRowX(row, depth); size.width = prefSize.width; size.height = prefSize.height; } else { size = new Rectangle(getRowX(row, depth), 0, prefSize.width, prefSize.height); } return size; } // Not editing, use renderer. if(currentCellRenderer != null) { Component aComponent; aComponent = currentCellRenderer.getTreeCellRendererComponent (tree, value, tree.isRowSelected(row), expanded, treeModel.isLeaf(value), row, false); if(tree != null) { // Only ever removed when UI changes, this is OK! rendererPane.add(aComponent); aComponent.validate(); } Dimension prefSize = aComponent.getPreferredSize(); if(size != null) { size.x = getRowX(row, depth); size.width = prefSize.width; size.height = prefSize.height; } else { size = new Rectangle(getRowX(row, depth), 0, prefSize.width, prefSize.height); } return size; } return null; }
Returns: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); } } // End of class BasicTreeUI.NodeDimensionsHandler
TreeMouseListener is responsible for updating the selection based on mouse events.
/** * TreeMouseListener is responsible for updating the selection * based on mouse events. */
public class MouseHandler extends MouseAdapter implements MouseMotionListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler.
Invoked when a mouse button has been pressed on a component.
/** * Invoked when a mouse button has been pressed on a component. */
public void mousePressed(MouseEvent e) { getHandler().mousePressed(e); } public void mouseDragged(MouseEvent e) { getHandler().mouseDragged(e); }
Invoked when the mouse button has been moved on a component (with no buttons no down).
Since:1.4
/** * Invoked when the mouse button has been moved on a component * (with no buttons no down). * @since 1.4 */
public void mouseMoved(MouseEvent e) { getHandler().mouseMoved(e); } public void mouseReleased(MouseEvent e) { getHandler().mouseReleased(e); } } // End of BasicTreeUI.MouseHandler
PropertyChangeListener for the tree. Updates the appropriate variable, or TreeState, based on what changes.
/** * PropertyChangeListener for the tree. Updates the appropriate * variable, or TreeState, based on what changes. */
public class PropertyChangeHandler implements PropertyChangeListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void propertyChange(PropertyChangeEvent event) { getHandler().propertyChange(event); } } // End of BasicTreeUI.PropertyChangeHandler
Listener on the TreeSelectionModel, resets the row selection if any of the properties of the model change.
/** * Listener on the TreeSelectionModel, resets the row selection if * any of the properties of the model change. */
public class SelectionModelPropertyChangeHandler implements PropertyChangeListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void propertyChange(PropertyChangeEvent event) { getHandler().propertyChange(event); } } // End of BasicTreeUI.SelectionModelPropertyChangeHandler
TreeTraverseAction is the action used for left/right keys. Will toggle the expandedness of a node, as well as potentially incrementing the selection.
/** * <code>TreeTraverseAction</code> is the action used for left/right keys. * Will toggle the expandedness of a node, as well as potentially * incrementing the selection. */
public class TreeTraverseAction extends AbstractAction {
Determines direction to traverse, 1 means expand, -1 means collapse.
/** Determines direction to traverse, 1 means expand, -1 means * collapse. */
protected int direction;
True if the selection is reset, false means only the lead path changes.
/** True if the selection is reset, false means only the lead path * changes. */
private boolean changeSelection; public TreeTraverseAction(int direction, String name) { this(direction, name, true); } private TreeTraverseAction(int direction, String name, boolean changeSelection) { this.direction = direction; this.changeSelection = changeSelection; } public void actionPerformed(ActionEvent e) { if (tree != null) { SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction, changeSelection); } } public boolean isEnabled() { return (tree != null && tree.isEnabled()); } } // BasicTreeUI.TreeTraverseAction
TreePageAction handles page up and page down events.
/** TreePageAction handles page up and page down events. */
public class TreePageAction extends AbstractAction {
Specifies the direction to adjust the selection by.
/** Specifies the direction to adjust the selection by. */
protected int direction;
True indicates should set selection from anchor path.
/** True indicates should set selection from anchor path. */
private boolean addToSelection; private boolean changeSelection; public TreePageAction(int direction, String name) { this(direction, name, false, true); } private TreePageAction(int direction, String name, boolean addToSelection, boolean changeSelection) { this.direction = direction; this.addToSelection = addToSelection; this.changeSelection = changeSelection; } public void actionPerformed(ActionEvent e) { if (tree != null) { SHARED_ACTION.page(tree, BasicTreeUI.this, direction, addToSelection, changeSelection); } } public boolean isEnabled() { return (tree != null && tree.isEnabled()); } } // BasicTreeUI.TreePageAction
TreeIncrementAction is used to handle up/down actions. Selection is moved up or down based on direction.
/** 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.
/** Specifies the direction to adjust the selection by. */
protected int direction;
If true the new item is added to the selection, if false the selection is reset.
/** If true the new item is added to the selection, if false the * selection is reset. */
private boolean addToSelection; private boolean changeSelection; public TreeIncrementAction(int direction, String name) { this(direction, name, false, true); } private TreeIncrementAction(int direction, String name, boolean addToSelection, boolean changeSelection) { this.direction = direction; this.addToSelection = addToSelection; this.changeSelection = changeSelection; } public void actionPerformed(ActionEvent e) { if (tree != null) { SHARED_ACTION.increment(tree, BasicTreeUI.this, direction, addToSelection, changeSelection); } } public boolean isEnabled() { return (tree != null && tree.isEnabled()); } } // End of class BasicTreeUI.TreeIncrementAction
TreeHomeAction is used to handle end/home actions. Scrolls either the first or last cell to be visible based on direction.
/** * TreeHomeAction is used to handle end/home actions. * Scrolls either the first or last cell to be visible based on * direction. */
public class TreeHomeAction extends AbstractAction { protected int direction;
Set to true if append to selection.
/** Set to true if append to selection. */
private boolean addToSelection; private boolean changeSelection; public TreeHomeAction(int direction, String name) { this(direction, name, false, true); } private TreeHomeAction(int direction, String name, boolean addToSelection, boolean changeSelection) { this.direction = direction; this.changeSelection = changeSelection; this.addToSelection = addToSelection; } public void actionPerformed(ActionEvent e) { if (tree != null) { SHARED_ACTION.home(tree, BasicTreeUI.this, direction, addToSelection, changeSelection); } } public boolean isEnabled() { return (tree != null && tree.isEnabled()); } } // End of class BasicTreeUI.TreeHomeAction
For the first selected row expandedness will be toggled.
/** * For the first selected row expandedness will be toggled. */
public class TreeToggleAction extends AbstractAction { public TreeToggleAction(String name) { } public void actionPerformed(ActionEvent e) { if(tree != null) { SHARED_ACTION.toggle(tree, BasicTreeUI.this); } } public boolean isEnabled() { return (tree != null && tree.isEnabled()); } } // End of class BasicTreeUI.TreeToggleAction
ActionListener that invokes cancelEditing when action performed.
/** * ActionListener that invokes cancelEditing when action performed. */
public class TreeCancelEditingAction extends AbstractAction { public TreeCancelEditingAction(String name) { } public void actionPerformed(ActionEvent e) { if(tree != null) { SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this); } } public boolean isEnabled() { return (tree != null && tree.isEnabled() && isEditing(tree)); } } // End of class BasicTreeUI.TreeCancelEditingAction
MouseInputHandler handles passing all mouse events, including mouse motion events, until the mouse is released to the destination it is constructed with. It is assumed all the events are currently target at source.
/** * MouseInputHandler handles passing all mouse events, * including mouse motion events, until the mouse is released to * the destination it is constructed with. It is assumed all the * events are currently target at source. */
public class MouseInputHandler extends Object implements MouseInputListener {
Source that events are coming from.
/** Source that events are coming from. */
protected Component source;
Destination that receives all events.
/** Destination that receives all events. */
protected Component destination; private Component focusComponent; private boolean dispatchedEvent; public MouseInputHandler(Component source, Component destination, MouseEvent event){ this(source, destination, event, null); } MouseInputHandler(Component source, Component destination, MouseEvent event, Component focusComponent) { this.source = source; this.destination = destination; this.source.addMouseListener(this); this.source.addMouseMotionListener(this); SwingUtilities2.setSkipClickCount(destination, event.getClickCount() - 1); /* Dispatch the editing event! */ destination.dispatchEvent(SwingUtilities.convertMouseEvent (source, event, destination)); this.focusComponent = focusComponent; } public void mouseClicked(MouseEvent e) { if(destination != null) { dispatchedEvent = true; destination.dispatchEvent(SwingUtilities.convertMouseEvent (source, e, destination)); } } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { if(destination != null) destination.dispatchEvent(SwingUtilities.convertMouseEvent (source, e, destination)); removeFromSource(); } public void mouseEntered(MouseEvent e) { if (!SwingUtilities.isLeftMouseButton(e)) { removeFromSource(); } } public void mouseExited(MouseEvent e) { if (!SwingUtilities.isLeftMouseButton(e)) { removeFromSource(); } } public void mouseDragged(MouseEvent e) { if(destination != null) { dispatchedEvent = true; destination.dispatchEvent(SwingUtilities.convertMouseEvent (source, e, destination)); } } public void mouseMoved(MouseEvent e) { removeFromSource(); } protected void removeFromSource() { if(source != null) { source.removeMouseListener(this); source.removeMouseMotionListener(this); if (focusComponent != null && focusComponent == destination && !dispatchedEvent && (focusComponent instanceof JTextField)) { ((JTextField)focusComponent).selectAll(); } } source = destination = null; } } // End of class BasicTreeUI.MouseInputHandler private static final TransferHandler defaultTransferHandler = new TreeTransferHandler(); static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator<TreePath> { private JTree tree;
Create a Transferable to use as the source for a data transfer.
Params:
  • c – The component holding the data to be transfered. This argument is provided to enable sharing of TransferHandlers by multiple components.
Returns: The representation of the data to be transfered.
/** * Create a Transferable to use as the source for a data transfer. * * @param c The component holding the data to be transfered. This * argument is provided to enable sharing of TransferHandlers by * multiple components. * @return The representation of the data to be transfered. * */
protected Transferable createTransferable(JComponent c) { if (c instanceof JTree) { tree = (JTree) c; TreePath[] paths = tree.getSelectionPaths(); if (paths == null || paths.length == 0) { return null; } StringBuffer plainBuf = new StringBuffer(); StringBuffer htmlBuf = new StringBuffer(); htmlBuf.append("<html>\n<body>\n<ul>\n"); TreeModel model = tree.getModel(); TreePath lastPath = null; TreePath[] displayPaths = getDisplayOrderPaths(paths); for (TreePath path : displayPaths) { Object node = path.getLastPathComponent(); boolean leaf = model.isLeaf(node); String label = getDisplayString(path, true, leaf); plainBuf.append(label + "\n"); htmlBuf.append(" <li>" + label + "\n"); } // remove the last newline plainBuf.deleteCharAt(plainBuf.length() - 1); htmlBuf.append("</ul>\n</body>\n</html>"); tree = null; return new BasicTransferable(plainBuf.toString(), htmlBuf.toString()); } return null; } public int compare(TreePath o1, TreePath o2) { int row1 = tree.getRowForPath(o1); int row2 = tree.getRowForPath(o2); return row1 - row2; } String getDisplayString(TreePath path, boolean selected, boolean leaf) { int row = tree.getRowForPath(path); boolean hasFocus = tree.getLeadSelectionRow() == row; Object node = path.getLastPathComponent(); return tree.convertValueToText(node, selected, tree.isExpanded(row), leaf, row, hasFocus); }
Selection paths are in selection order. The conversion to HTML requires display order. This method resorts the paths to be in the display order.
/** * Selection paths are in selection order. The conversion to * HTML requires display order. This method resorts the paths * to be in the display order. */
TreePath[] getDisplayOrderPaths(TreePath[] paths) { // sort the paths to display order rather than selection order ArrayList<TreePath> selOrder = new ArrayList<TreePath>(); for (TreePath path : paths) { selOrder.add(path); } Collections.sort(selOrder, this); int n = selOrder.size(); TreePath[] displayPaths = new TreePath[n]; for (int i = 0; i < n; i++) { displayPaths[i] = selOrder.get(i); } return displayPaths; } public int getSourceActions(JComponent c) { return COPY; } } private class Handler implements CellEditorListener, FocusListener, KeyListener, MouseListener, MouseMotionListener, PropertyChangeListener, TreeExpansionListener, TreeModelListener, TreeSelectionListener, BeforeDrag { // // KeyListener // private String prefix = ""; private String typedString = ""; private long lastTime = 0L;
Invoked when a key has been typed. Moves the keyboard focus to the first element whose prefix matches the sequence of alphanumeric keys pressed by the user with delay less than value of timeFactor property (or 1000 milliseconds if it is not defined). Subsequent same key presses move the keyboard focus to the next object that starts with the same letter until another key is pressed, then it is treated as the prefix with appropriate number of the same letters followed by first typed another letter.
/** * Invoked when a key has been typed. * * Moves the keyboard focus to the first element whose prefix matches the * sequence of alphanumeric keys pressed by the user with delay less * than value of <code>timeFactor</code> property (or 1000 milliseconds * if it is not defined). Subsequent same key presses move the keyboard * focus to the next object that starts with the same letter until another * key is pressed, then it is treated as the prefix with appropriate number * of the same letters followed by first typed another letter. */
public void keyTyped(KeyEvent e) { // handle first letter navigation if(tree != null && tree.getRowCount()>0 && tree.hasFocus() && tree.isEnabled()) { if (e.isAltDown() || BasicGraphicsUtils.isMenuShortcutKeyDown(e) || isNavigationKey(e)) { return; } boolean startingFromSelection = true; char c = e.getKeyChar(); long time = e.getWhen(); int startingRow = tree.getLeadSelectionRow(); if (time - lastTime < timeFactor) { typedString += c; if((prefix.length() == 1) && (c == prefix.charAt(0))) { // Subsequent same key presses move the keyboard focus to the next // object that starts with the same letter. startingRow++; } else { prefix = typedString; } } else { startingRow++; typedString = "" + c; prefix = typedString; } lastTime = time; if (startingRow < 0 || startingRow >= tree.getRowCount()) { startingFromSelection = false; startingRow = 0; } TreePath path = tree.getNextMatch(prefix, startingRow, Position.Bias.Forward); if (path != null) { tree.setSelectionPath(path); int row = getRowForPath(tree, path); ensureRowsAreVisible(row, row); } else if (startingFromSelection) { path = tree.getNextMatch(prefix, 0, Position.Bias.Forward); if (path != null) { tree.setSelectionPath(path); int row = getRowForPath(tree, path); ensureRowsAreVisible(row, row); } } } }
Invoked when a key has been pressed. Checks to see if the key event is a navigation key to prevent dispatching these keys for the first letter navigation.
/** * Invoked when a key has been pressed. * * Checks to see if the key event is a navigation key to prevent * dispatching these keys for the first letter navigation. */
public void keyPressed(KeyEvent e) { if (tree != null && isNavigationKey(e)) { prefix = ""; typedString = ""; lastTime = 0L; } } public void keyReleased(KeyEvent e) { }
Returns whether or not the supplied key event maps to a key that is used for navigation. This is used for optimizing key input by only passing non- navigation keys to the first letter navigation mechanism.
/** * Returns whether or not the supplied key event maps to a key that is used for * navigation. This is used for optimizing key input by only passing non- * navigation keys to the first letter navigation mechanism. */
private boolean isNavigationKey(KeyEvent event) { InputMap inputMap = tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); KeyStroke key = KeyStroke.getKeyStrokeForEvent(event); return inputMap != null && inputMap.get(key) != null; } // // PropertyChangeListener // public void propertyChange(PropertyChangeEvent event) { if (event.getSource() == treeSelectionModel) { treeSelectionModel.resetRowSelection(); } else if(event.getSource() == tree) { String changeName = event.getPropertyName(); if (changeName == JTree.LEAD_SELECTION_PATH_PROPERTY) { if (!ignoreLAChange) { updateLeadSelectionRow(); repaintPath((TreePath)event.getOldValue()); repaintPath((TreePath)event.getNewValue()); } } else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) { if (!ignoreLAChange) { repaintPath((TreePath)event.getOldValue()); repaintPath((TreePath)event.getNewValue()); } } if(changeName == JTree.CELL_RENDERER_PROPERTY) { setCellRenderer((TreeCellRenderer)event.getNewValue()); redoTheLayout(); } else if(changeName == JTree.TREE_MODEL_PROPERTY) { setModel((TreeModel)event.getNewValue()); } else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) { setRootVisible(((Boolean)event.getNewValue()). booleanValue()); } else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) { setShowsRootHandles(((Boolean)event.getNewValue()). booleanValue()); } else if(changeName == JTree.ROW_HEIGHT_PROPERTY) { setRowHeight(((Integer)event.getNewValue()). intValue()); } else if(changeName == JTree.CELL_EDITOR_PROPERTY) { setCellEditor((TreeCellEditor)event.getNewValue()); } else if(changeName == JTree.EDITABLE_PROPERTY) { setEditable(((Boolean)event.getNewValue()).booleanValue()); } else if(changeName == JTree.LARGE_MODEL_PROPERTY) { setLargeModel(tree.isLargeModel()); } else if(changeName == JTree.SELECTION_MODEL_PROPERTY) { setSelectionModel(tree.getSelectionModel()); } else if(changeName == "font") { completeEditing(); if(treeState != null) treeState.invalidateSizes(); updateSize(); } else if (changeName == "componentOrientation") { if (tree != null) { leftToRight = BasicGraphicsUtils.isLeftToRight(tree); redoTheLayout(); tree.treeDidChange(); InputMap km = getInputMap(JComponent.WHEN_FOCUSED); SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, km); } } else if ("dropLocation" == changeName) { JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue(); repaintDropLocation(oldValue); repaintDropLocation(tree.getDropLocation()); } } } private void repaintDropLocation(JTree.DropLocation loc) { if (loc == null) { return; } Rectangle r; if (isDropLine(loc)) { r = getDropLineRect(loc); } else { r = tree.getPathBounds(loc.getPath()); } if (r != null) { tree.repaint(r); } } // // MouseListener // // Whether or not the mouse press (which is being considered as part // of a drag sequence) also caused the selection change to be fully // processed. private boolean dragPressDidSelection; // Set to true when a drag gesture has been fully recognized and DnD // begins. Use this to ignore further mouse events which could be // delivered if DnD is cancelled (via ESCAPE for example) private boolean dragStarted; // The path over which the press occurred and the press event itself private TreePath pressedPath; private MouseEvent pressedEvent; // Used to detect whether the press event causes a selection change. // If it does, we won't try to start editing on the release. private boolean valueChangedOnPress; private boolean isActualPath(TreePath path, int x, int y) { if (path == null) { return false; } Rectangle bounds = getPathBounds(tree, path); if (bounds == null || y > (bounds.y + bounds.height)) { return false; } return (x >= bounds.x) && (x <= (bounds.x + bounds.width)); } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { }
Invoked when a mouse button has been pressed on a component.
/** * Invoked when a mouse button has been pressed on a component. */
public void mousePressed(MouseEvent e) { if (SwingUtilities2.shouldIgnore(e, tree)) { return; } // if we can't stop any ongoing editing, do nothing if (isEditing(tree) && tree.getInvokesStopCellEditing() && !stopEditing(tree)) { return; } completeEditing(); pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY()); if (tree.getDragEnabled()) { mousePressedDND(e); } else { SwingUtilities2.adjustFocus(tree); handleSelection(e); } } private void mousePressedDND(MouseEvent e) { pressedEvent = e; boolean grabFocus = true; dragStarted = false; valueChangedOnPress = false; // if we have a valid path and this is a drag initiating event if (isActualPath(pressedPath, e.getX(), e.getY()) && DragRecognitionSupport.mousePressed(e)) { dragPressDidSelection = false; if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) { // do nothing for control - will be handled on release // or when drag starts return; } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) { // clicking on something that's already selected // and need to make it the lead now setAnchorSelectionPath(pressedPath); setLeadSelectionPath(pressedPath, true); return; } dragPressDidSelection = true; // could be a drag initiating event - don't grab focus grabFocus = false; } if (grabFocus) { SwingUtilities2.adjustFocus(tree); } handleSelection(e); } void handleSelection(MouseEvent e) { if(pressedPath != null) { Rectangle bounds = getPathBounds(tree, pressedPath); if (bounds == null || e.getY() >= (bounds.y + bounds.height)) { return; } // Preferably checkForClickInExpandControl could take // the Event to do this it self! if(SwingUtilities.isLeftMouseButton(e)) { checkForClickInExpandControl(pressedPath, e.getX(), e.getY()); } int x = e.getX(); // Perhaps they clicked the cell itself. If so, // select it. if (x >= bounds.x && x < (bounds.x + bounds.width)) { if (tree.getDragEnabled() || !startEditing(pressedPath, e)) { selectPathForEvent(pressedPath, e); } } } } public void dragStarting(MouseEvent me) { dragStarted = true; if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) { tree.addSelectionPath(pressedPath); setAnchorSelectionPath(pressedPath); setLeadSelectionPath(pressedPath, true); } pressedEvent = null; pressedPath = null; } public void mouseDragged(MouseEvent e) { if (SwingUtilities2.shouldIgnore(e, tree)) { return; } if (tree.getDragEnabled()) { DragRecognitionSupport.mouseDragged(e, this); } }
Invoked when the mouse button has been moved on a component (with no buttons no down).
/** * Invoked when the mouse button has been moved on a component * (with no buttons no down). */
public void mouseMoved(MouseEvent e) { } public void mouseReleased(MouseEvent e) { if (SwingUtilities2.shouldIgnore(e, tree)) { return; } if (tree.getDragEnabled()) { mouseReleasedDND(e); } pressedEvent = null; pressedPath = null; } private void mouseReleasedDND(MouseEvent e) { MouseEvent me = DragRecognitionSupport.mouseReleased(e); if (me != null) { SwingUtilities2.adjustFocus(tree); if (!dragPressDidSelection) { handleSelection(me); } } if (!dragStarted) { // Note: We don't give the tree a chance to start editing if the // mouse press caused a selection change. Otherwise the default // tree cell editor will start editing on EVERY press and // release. If it turns out that this affects some editors, we // can always parameterize this with a client property. ex: // // if (pressedPath != null && // (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") || // !valueChangedOnPress) && ... if (pressedPath != null && !valueChangedOnPress && isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) { startEditingOnRelease(pressedPath, pressedEvent, e); } } } // // FocusListener // public void focusGained(FocusEvent e) { if(tree != null) { Rectangle pBounds; pBounds = getPathBounds(tree, tree.getLeadSelectionPath()); if(pBounds != null) tree.repaint(getRepaintPathBounds(pBounds)); pBounds = getPathBounds(tree, getLeadSelectionPath()); if(pBounds != null) tree.repaint(getRepaintPathBounds(pBounds)); } } public void focusLost(FocusEvent e) { focusGained(e); } // // CellEditorListener // public void editingStopped(ChangeEvent e) { completeEditing(false, false, true); }
Messaged when editing has been canceled in the tree.
/** Messaged when editing has been canceled in the tree. */
public void editingCanceled(ChangeEvent e) { completeEditing(false, false, false); } // // TreeSelectionListener // public void valueChanged(TreeSelectionEvent event) { valueChangedOnPress = true; // Stop editing completeEditing(); // Make sure all the paths are visible, if necessary. // PENDING: This should be tweaked when isAdjusting is added if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) { TreePath[] paths = treeSelectionModel .getSelectionPaths(); if(paths != null) { for(int counter = paths.length - 1; counter >= 0; counter--) { TreePath path = paths[counter].getParentPath(); boolean expand = true; while (path != null) { // Indicates this path isn't valid anymore, // we shouldn't attempt to expand it then. if (treeModel.isLeaf(path.getLastPathComponent())){ expand = false; path = null; } else { path = path.getParentPath(); } } if (expand) { tree.makeVisible(paths[counter]); } } } } TreePath oldLead = getLeadSelectionPath(); lastSelectedRow = tree.getMinSelectionRow(); TreePath lead = tree.getSelectionModel().getLeadSelectionPath(); setAnchorSelectionPath(lead); setLeadSelectionPath(lead); TreePath[] changedPaths = event.getPaths(); Rectangle nodeBounds; Rectangle visRect = tree.getVisibleRect(); boolean paintPaths = true; int nWidth = tree.getWidth(); if(changedPaths != null) { int counter, maxCounter = changedPaths.length; if(maxCounter > 4) { tree.repaint(); paintPaths = false; } else { for (counter = 0; counter < maxCounter; counter++) { nodeBounds = getPathBounds(tree, changedPaths[counter]); if(nodeBounds != null && visRect.intersects(nodeBounds)) tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); } } } if(paintPaths) { nodeBounds = getPathBounds(tree, oldLead); if(nodeBounds != null && visRect.intersects(nodeBounds)) tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); nodeBounds = getPathBounds(tree, lead); if(nodeBounds != null && visRect.intersects(nodeBounds)) tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); } } // // TreeExpansionListener // public void treeExpanded(TreeExpansionEvent event) { if(event != null && tree != null) { TreePath path = event.getPath(); updateExpandedDescendants(path); } } public void treeCollapsed(TreeExpansionEvent event) { if(event != null && tree != null) { TreePath path = event.getPath(); completeEditing(); if(path != null && tree.isVisible(path)) { treeState.setExpandedState(path, false); updateLeadSelectionRow(); updateSize(); } } } // // TreeModelListener // public void treeNodesChanged(TreeModelEvent e) { if(treeState != null && e != null) { TreePath parentPath = SwingUtilities2.getTreePath(e, getModel()); int[] indices = e.getChildIndices(); if (indices == null || indices.length == 0) { // The root has changed treeState.treeNodesChanged(e); updateSize(); } else if (treeState.isExpanded(parentPath)) { // Changed nodes are visible // Find the minimum index, we only need paint from there // down. int minIndex = indices[0]; for (int i = indices.length - 1; i > 0; i--) { minIndex = Math.min(indices[i], minIndex); } Object minChild = treeModel.getChild( parentPath.getLastPathComponent(), minIndex); TreePath minPath = parentPath.pathByAddingChild(minChild); Rectangle minBounds = getPathBounds(tree, minPath); // Forward to the treestate treeState.treeNodesChanged(e); // Mark preferred size as bogus. updateSize0(); // And repaint Rectangle newMinBounds = getPathBounds(tree, minPath); if (minBounds == null || newMinBounds == null) { return; } if (indices.length == 1 && newMinBounds.height == minBounds.height) { tree.repaint(0, minBounds.y, tree.getWidth(), minBounds.height); } else { tree.repaint(0, minBounds.y, tree.getWidth(), tree.getHeight() - minBounds.y); } } else { // Nodes that changed aren't visible. No need to paint treeState.treeNodesChanged(e); } } } public void treeNodesInserted(TreeModelEvent e) { if(treeState != null && e != null) { treeState.treeNodesInserted(e); updateLeadSelectionRow(); TreePath path = SwingUtilities2.getTreePath(e, getModel()); if(treeState.isExpanded(path)) { updateSize(); } else { // PENDING(sky): Need a method in TreeModelEvent // that can return the count, getChildIndices allocs // a new array! int[] indices = e.getChildIndices(); int childCount = treeModel.getChildCount (path.getLastPathComponent()); if(indices != null && (childCount - indices.length) == 0) updateSize(); } } } public void treeNodesRemoved(TreeModelEvent e) { if(treeState != null && e != null) { treeState.treeNodesRemoved(e); updateLeadSelectionRow(); TreePath path = SwingUtilities2.getTreePath(e, getModel()); if(treeState.isExpanded(path) || treeModel.getChildCount(path.getLastPathComponent()) == 0) updateSize(); } } public void treeStructureChanged(TreeModelEvent e) { if(treeState != null && e != null) { treeState.treeStructureChanged(e); updateLeadSelectionRow(); TreePath pPath = SwingUtilities2.getTreePath(e, getModel()); if (pPath != null) { pPath = pPath.getParentPath(); } if(pPath == null || treeState.isExpanded(pPath)) updateSize(); } } } private static class Actions extends UIAction { private static final String SELECT_PREVIOUS = "selectPrevious"; private static final String SELECT_PREVIOUS_CHANGE_LEAD = "selectPreviousChangeLead"; private static final String SELECT_PREVIOUS_EXTEND_SELECTION = "selectPreviousExtendSelection"; private static final String SELECT_NEXT = "selectNext"; private static final String SELECT_NEXT_CHANGE_LEAD = "selectNextChangeLead"; private static final String SELECT_NEXT_EXTEND_SELECTION = "selectNextExtendSelection"; private static final String SELECT_CHILD = "selectChild"; private static final String SELECT_CHILD_CHANGE_LEAD = "selectChildChangeLead"; private static final String SELECT_PARENT = "selectParent"; private static final String SELECT_PARENT_CHANGE_LEAD = "selectParentChangeLead"; private static final String SCROLL_UP_CHANGE_SELECTION = "scrollUpChangeSelection"; private static final String SCROLL_UP_CHANGE_LEAD = "scrollUpChangeLead"; private static final String SCROLL_UP_EXTEND_SELECTION = "scrollUpExtendSelection"; private static final String SCROLL_DOWN_CHANGE_SELECTION = "scrollDownChangeSelection"; private static final String SCROLL_DOWN_EXTEND_SELECTION = "scrollDownExtendSelection"; private static final String SCROLL_DOWN_CHANGE_LEAD = "scrollDownChangeLead"; private static final String SELECT_FIRST = "selectFirst"; private static final String SELECT_FIRST_CHANGE_LEAD = "selectFirstChangeLead"; private static final String SELECT_FIRST_EXTEND_SELECTION = "selectFirstExtendSelection"; private static final String SELECT_LAST = "selectLast"; private static final String SELECT_LAST_CHANGE_LEAD = "selectLastChangeLead"; private static final String SELECT_LAST_EXTEND_SELECTION = "selectLastExtendSelection"; private static final String TOGGLE = "toggle"; private static final String CANCEL_EDITING = "cancel"; private static final String START_EDITING = "startEditing"; private static final String SELECT_ALL = "selectAll"; private static final String CLEAR_SELECTION = "clearSelection"; private static final String SCROLL_LEFT = "scrollLeft"; private static final String SCROLL_RIGHT = "scrollRight"; private static final String SCROLL_LEFT_EXTEND_SELECTION = "scrollLeftExtendSelection"; private static final String SCROLL_RIGHT_EXTEND_SELECTION = "scrollRightExtendSelection"; private static final String SCROLL_RIGHT_CHANGE_LEAD = "scrollRightChangeLead"; private static final String SCROLL_LEFT_CHANGE_LEAD = "scrollLeftChangeLead"; private static final String EXPAND = "expand"; private static final String COLLAPSE = "collapse"; private static final String MOVE_SELECTION_TO_PARENT = "moveSelectionToParent"; // add the lead item to the selection without changing lead or anchor private static final String ADD_TO_SELECTION = "addToSelection"; // toggle the selected state of the lead item and move the anchor to it private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor"; // extend the selection to the lead item private static final String EXTEND_TO = "extendTo"; // move the anchor to the lead and ensure only that item is selected private static final String MOVE_SELECTION_TO = "moveSelectionTo"; Actions() { super(null); } Actions(String key) { super(key); } public boolean isEnabled(Object o) { if (o instanceof JTree) { if (getName() == CANCEL_EDITING) { return ((JTree)o).isEditing(); } } return true; } public void actionPerformed(ActionEvent e) { JTree tree = (JTree)e.getSource(); BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType( tree.getUI(), BasicTreeUI.class); if (ui == null) { return; } String key = getName(); if (key == SELECT_PREVIOUS) { increment(tree, ui, -1, false, true); } else if (key == SELECT_PREVIOUS_CHANGE_LEAD) { increment(tree, ui, -1, false, false); } else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) { increment(tree, ui, -1, true, true); } else if (key == SELECT_NEXT) { increment(tree, ui, 1, false, true); } else if (key == SELECT_NEXT_CHANGE_LEAD) { increment(tree, ui, 1, false, false); } else if (key == SELECT_NEXT_EXTEND_SELECTION) { increment(tree, ui, 1, true, true); } else if (key == SELECT_CHILD) { traverse(tree, ui, 1, true); } else if (key == SELECT_CHILD_CHANGE_LEAD) { traverse(tree, ui, 1, false); } else if (key == SELECT_PARENT) { traverse(tree, ui, -1, true); } else if (key == SELECT_PARENT_CHANGE_LEAD) { traverse(tree, ui, -1, false); } else if (key == SCROLL_UP_CHANGE_SELECTION) { page(tree, ui, -1, false, true); } else if (key == SCROLL_UP_CHANGE_LEAD) { page(tree, ui, -1, false, false); } else if (key == SCROLL_UP_EXTEND_SELECTION) { page(tree, ui, -1, true, true); } else if (key == SCROLL_DOWN_CHANGE_SELECTION) { page(tree, ui, 1, false, true); } else if (key == SCROLL_DOWN_EXTEND_SELECTION) { page(tree, ui, 1, true, true); } else if (key == SCROLL_DOWN_CHANGE_LEAD) { page(tree, ui, 1, false, false); } else if (key == SELECT_FIRST) { home(tree, ui, -1, false, true); } else if (key == SELECT_FIRST_CHANGE_LEAD) { home(tree, ui, -1, false, false); } else if (key == SELECT_FIRST_EXTEND_SELECTION) { home(tree, ui, -1, true, true); } else if (key == SELECT_LAST) { home(tree, ui, 1, false, true); } else if (key == SELECT_LAST_CHANGE_LEAD) { home(tree, ui, 1, false, false); } else if (key == SELECT_LAST_EXTEND_SELECTION) { home(tree, ui, 1, true, true); } else if (key == TOGGLE) { toggle(tree, ui); } else if (key == CANCEL_EDITING) { cancelEditing(tree, ui); } else if (key == START_EDITING) { startEditing(tree, ui); } else if (key == SELECT_ALL) { selectAll(tree, ui, true); } else if (key == CLEAR_SELECTION) { selectAll(tree, ui, false); } else if (key == ADD_TO_SELECTION) { if (ui.getRowCount(tree) > 0) { int lead = ui.getLeadSelectionRow(); if (!tree.isRowSelected(lead)) { TreePath aPath = ui.getAnchorSelectionPath(); tree.addSelectionRow(lead); ui.setAnchorSelectionPath(aPath); } } } else if (key == TOGGLE_AND_ANCHOR) { if (ui.getRowCount(tree) > 0) { int lead = ui.getLeadSelectionRow(); TreePath lPath = ui.getLeadSelectionPath(); if (!tree.isRowSelected(lead)) { tree.addSelectionRow(lead); } else { tree.removeSelectionRow(lead); ui.setLeadSelectionPath(lPath); } ui.setAnchorSelectionPath(lPath); } } else if (key == EXTEND_TO) { extendSelection(tree, ui); } else if (key == MOVE_SELECTION_TO) { if (ui.getRowCount(tree) > 0) { int lead = ui.getLeadSelectionRow(); tree.setSelectionInterval(lead, lead); } } else if (key == SCROLL_LEFT) { scroll(tree, ui, SwingConstants.HORIZONTAL, -10); } else if (key == SCROLL_RIGHT) { scroll(tree, ui, SwingConstants.HORIZONTAL, 10); } else if (key == SCROLL_LEFT_EXTEND_SELECTION) { scrollChangeSelection(tree, ui, -1, true, true); } else if (key == SCROLL_RIGHT_EXTEND_SELECTION) { scrollChangeSelection(tree, ui, 1, true, true); } else if (key == SCROLL_RIGHT_CHANGE_LEAD) { scrollChangeSelection(tree, ui, 1, false, false); } else if (key == SCROLL_LEFT_CHANGE_LEAD) { scrollChangeSelection(tree, ui, -1, false, false); } else if (key == EXPAND) { expand(tree, ui); } else if (key == COLLAPSE) { collapse(tree, ui); } else if (key == MOVE_SELECTION_TO_PARENT) { moveSelectionToParent(tree, ui); } } private void scrollChangeSelection(JTree tree, BasicTreeUI ui, int direction, boolean addToSelection, boolean changeSelection) { int rowCount; if((rowCount = ui.getRowCount(tree)) > 0 && ui.treeSelectionModel != null) { TreePath newPath; Rectangle visRect = tree.getVisibleRect(); if (direction == -1) { newPath = ui.getClosestPathForLocation(tree, visRect.x, visRect.y); visRect.x = Math.max(0, visRect.x - visRect.width); } else { visRect.x = Math.min(Math.max(0, tree.getWidth() - visRect.width), visRect.x + visRect.width); newPath = ui.getClosestPathForLocation(tree, visRect.x, visRect.y + visRect.height); } // Scroll tree.scrollRectToVisible(visRect); // select if (addToSelection) { ui.extendSelection(newPath); } else if(changeSelection) { tree.setSelectionPath(newPath); } else { ui.setLeadSelectionPath(newPath, true); } } } private void scroll(JTree component, BasicTreeUI ui, int direction, int amount) { Rectangle visRect = component.getVisibleRect(); Dimension size = component.getSize(); if (direction == SwingConstants.HORIZONTAL) { visRect.x += amount; visRect.x = Math.max(0, visRect.x); visRect.x = Math.min(Math.max(0, size.width - visRect.width), visRect.x); } else { visRect.y += amount; visRect.y = Math.max(0, visRect.y); visRect.y = Math.min(Math.max(0, size.width - visRect.height), visRect.y); } component.scrollRectToVisible(visRect); } private void extendSelection(JTree tree, BasicTreeUI ui) { if (ui.getRowCount(tree) > 0) { int lead = ui.getLeadSelectionRow(); if (lead != -1) { TreePath leadP = ui.getLeadSelectionPath(); TreePath aPath = ui.getAnchorSelectionPath(); int aRow = ui.getRowForPath(tree, aPath); if(aRow == -1) aRow = 0; tree.setSelectionInterval(aRow, lead); ui.setLeadSelectionPath(leadP); ui.setAnchorSelectionPath(aPath); } } } private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) { int rowCount = ui.getRowCount(tree); if(rowCount > 0) { if(selectAll) { if (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION) { int lead = ui.getLeadSelectionRow(); if (lead != -1) { tree.setSelectionRow(lead); } else if (tree.getMinSelectionRow() == -1) { tree.setSelectionRow(0); ui.ensureRowsAreVisible(0, 0); } return; } TreePath lastPath = ui.getLeadSelectionPath(); TreePath aPath = ui.getAnchorSelectionPath(); if(lastPath != null && !tree.isVisible(lastPath)) { lastPath = null; } tree.setSelectionInterval(0, rowCount - 1); if(lastPath != null) { ui.setLeadSelectionPath(lastPath); } if(aPath != null && tree.isVisible(aPath)) { ui.setAnchorSelectionPath(aPath); } } else { TreePath lastPath = ui.getLeadSelectionPath(); TreePath aPath = ui.getAnchorSelectionPath(); tree.clearSelection(); ui.setAnchorSelectionPath(aPath); ui.setLeadSelectionPath(lastPath); } } } private void startEditing(JTree tree, BasicTreeUI ui) { TreePath lead = ui.getLeadSelectionPath(); int editRow = (lead != null) ? ui.getRowForPath(tree, lead) : -1; if(editRow != -1) { tree.startEditingAtPath(lead); } } private void cancelEditing(JTree tree, BasicTreeUI ui) { tree.cancelEditing(); } private void toggle(JTree tree, BasicTreeUI ui) { int selRow = ui.getLeadSelectionRow(); if(selRow != -1 && !ui.isLeaf(selRow)) { TreePath aPath = ui.getAnchorSelectionPath(); TreePath lPath = ui.getLeadSelectionPath(); ui.toggleExpandState(ui.getPathForRow(tree, selRow)); ui.setAnchorSelectionPath(aPath); ui.setLeadSelectionPath(lPath); } } private void expand(JTree tree, BasicTreeUI ui) { int selRow = ui.getLeadSelectionRow(); tree.expandRow(selRow); } private void collapse(JTree tree, BasicTreeUI ui) { int selRow = ui.getLeadSelectionRow(); tree.collapseRow(selRow); } private void increment(JTree tree, BasicTreeUI ui, int direction, boolean addToSelection, boolean changeSelection) { // disable moving of lead unless in discontiguous mode if (!addToSelection && !changeSelection && tree.getSelectionModel().getSelectionMode() != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { changeSelection = true; } int rowCount; if(ui.treeSelectionModel != null && (rowCount = tree.getRowCount()) > 0) { int selIndex = ui.getLeadSelectionRow(); int newIndex; if(selIndex == -1) { if(direction == 1) newIndex = 0; else newIndex = rowCount - 1; } else /* Aparently people don't like wrapping;( */ newIndex = Math.min(rowCount - 1, Math.max (0, (selIndex + direction))); if(addToSelection && ui.treeSelectionModel. getSelectionMode() != TreeSelectionModel. SINGLE_TREE_SELECTION) { ui.extendSelection(tree.getPathForRow(newIndex)); } else if(changeSelection) { tree.setSelectionInterval(newIndex, newIndex); } else { ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true); } ui.ensureRowsAreVisible(newIndex, newIndex); ui.lastSelectedRow = newIndex; } } private void traverse(JTree tree, BasicTreeUI ui, int direction, boolean changeSelection) { // disable moving of lead unless in discontiguous mode if (!changeSelection && tree.getSelectionModel().getSelectionMode() != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { changeSelection = true; } int rowCount; if((rowCount = tree.getRowCount()) > 0) { int minSelIndex = ui.getLeadSelectionRow(); int newIndex; if(minSelIndex == -1) newIndex = 0; else { /* Try and expand the node, otherwise go to next node. */ if(direction == 1) { TreePath minSelPath = ui.getPathForRow(tree, minSelIndex); int childCount = tree.getModel(). getChildCount(minSelPath.getLastPathComponent()); newIndex = -1; if (!ui.isLeaf(minSelIndex)) { if (!tree.isExpanded(minSelIndex)) { ui.toggleExpandState(minSelPath); } else if (childCount > 0) { newIndex = Math.min(minSelIndex + 1, rowCount - 1); } } } /* Try to collapse node. */ else { if(!ui.isLeaf(minSelIndex) && tree.isExpanded(minSelIndex)) { ui.toggleExpandState(ui.getPathForRow (tree, minSelIndex)); newIndex = -1; } else { TreePath path = ui.getPathForRow(tree, minSelIndex); if(path != null && path.getPathCount() > 1) { newIndex = ui.getRowForPath(tree, path. getParentPath()); } else newIndex = -1; } } } if(newIndex != -1) { if(changeSelection) { tree.setSelectionInterval(newIndex, newIndex); } else { ui.setLeadSelectionPath(ui.getPathForRow( tree, newIndex), true); } ui.ensureRowsAreVisible(newIndex, newIndex); } } } private void moveSelectionToParent(JTree tree, BasicTreeUI ui) { int selRow = ui.getLeadSelectionRow(); TreePath path = ui.getPathForRow(tree, selRow); if (path != null && path.getPathCount() > 1) { int newIndex = ui.getRowForPath(tree, path.getParentPath()); if (newIndex != -1) { tree.setSelectionInterval(newIndex, newIndex); ui.ensureRowsAreVisible(newIndex, newIndex); } } } private void page(JTree tree, BasicTreeUI ui, int direction, boolean addToSelection, boolean changeSelection) { // disable moving of lead unless in discontiguous mode if (!addToSelection && !changeSelection && tree.getSelectionModel().getSelectionMode() != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { changeSelection = true; } int rowCount; if((rowCount = ui.getRowCount(tree)) > 0 && ui.treeSelectionModel != null) { Dimension maxSize = tree.getSize(); TreePath lead = ui.getLeadSelectionPath(); TreePath newPath; Rectangle visRect = tree.getVisibleRect(); if(direction == -1) { // up. newPath = ui.getClosestPathForLocation(tree, visRect.x, visRect.y); if(newPath.equals(lead)) { visRect.y = Math.max(0, visRect.y - visRect.height); newPath = tree.getClosestPathForLocation(visRect.x, visRect.y); } } else { // down visRect.y = Math.min(maxSize.height, visRect.y + visRect.height - 1); newPath = tree.getClosestPathForLocation(visRect.x, visRect.y); if(newPath.equals(lead)) { visRect.y = Math.min(maxSize.height, visRect.y + visRect.height - 1); newPath = tree.getClosestPathForLocation(visRect.x, visRect.y); } } Rectangle newRect = ui.getPathBounds(tree, newPath); if (newRect != null) { newRect.x = visRect.x; newRect.width = visRect.width; if(direction == -1) { newRect.height = visRect.height; } else { newRect.y -= (visRect.height - newRect.height); newRect.height = visRect.height; } if(addToSelection) { ui.extendSelection(newPath); } else if(changeSelection) { tree.setSelectionPath(newPath); } else { ui.setLeadSelectionPath(newPath, true); } tree.scrollRectToVisible(newRect); } } } private void home(JTree tree, final BasicTreeUI ui, int direction, boolean addToSelection, boolean changeSelection) { // disable moving of lead unless in discontiguous mode if (!addToSelection && !changeSelection && tree.getSelectionModel().getSelectionMode() != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { changeSelection = true; } final int rowCount = ui.getRowCount(tree); if (rowCount > 0) { if(direction == -1) { ui.ensureRowsAreVisible(0, 0); if (addToSelection) { TreePath aPath = ui.getAnchorSelectionPath(); int aRow = (aPath == null) ? -1 : ui.getRowForPath(tree, aPath); if (aRow == -1) { tree.setSelectionInterval(0, 0); } else { tree.setSelectionInterval(0, aRow); ui.setAnchorSelectionPath(aPath); ui.setLeadSelectionPath(ui.getPathForRow(tree, 0)); } } else if(changeSelection) { tree.setSelectionInterval(0, 0); } else { ui.setLeadSelectionPath(ui.getPathForRow(tree, 0), true); } } else { ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); if (addToSelection) { TreePath aPath = ui.getAnchorSelectionPath(); int aRow = (aPath == null) ? -1 : ui.getRowForPath(tree, aPath); if (aRow == -1) { tree.setSelectionInterval(rowCount - 1, rowCount -1); } else { tree.setSelectionInterval(aRow, rowCount - 1); ui.setAnchorSelectionPath(aPath); ui.setLeadSelectionPath(ui.getPathForRow(tree, rowCount -1)); } } else if(changeSelection) { tree.setSelectionInterval(rowCount - 1, rowCount - 1); } else { ui.setLeadSelectionPath(ui.getPathForRow(tree, rowCount - 1), true); } if (ui.isLargeModel()){ SwingUtilities.invokeLater(new Runnable() { public void run() { ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); } }); } } } } } } // End of class BasicTreeUI