- /*
- * @(#)BasicTreeUI.java 1.107 01/11/29
- *
- * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing.plaf.basic;
- import javax.swing.*;
- import javax.swing.event.*;
- import javax.swing.text.DefaultTextUI;
- import java.awt.*;
- import java.awt.event.*;
- import java.beans.*;
- import java.io.*;
- import java.util.*;
- import javax.swing.plaf.ComponentUI;
- import javax.swing.plaf.UIResource;
- import javax.swing.plaf.TreeUI;
- import javax.swing.tree.*;
- /**
- * The basic L&F for a hierarchical data structure.
- * <p>
- *
- * @version 1.107 11/29/01
- * @author Scott Violet
- */
- public class BasicTreeUI extends TreeUI
- {
- static private final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
- transient protected Icon collapsedIcon;
- transient protected Icon expandedIcon;
- /** Color used to draw hash marks. If null no hash marks will be
- * drawn. */
- private Color hashColor;
- /** Distance between left margin and where verical dashes will be
- * drawn. */
- protected int leftChildIndent;
- /** Distance to add to leftChildIndent to determine where cell
- * contents will be drawn. */
- protected int rightChildIndent;
- /** Total distance that will be indented. The sum of leftChildIndent
- * and rightChildIndent. */
- protected int totalChildIndent;
- /** Minimum preferred size. */
- protected Dimension preferredMinSize;
- /** Index of the row that was last selected. */
- protected int lastSelectedRow;
- /** Component that we're going to be drawing into. */
- protected JTree tree;
- /** Renderer that is being used to do the actual cell drawing. */
- transient protected TreeCellRenderer currentCellRenderer;
- /** Set to true if the renderer that is currently in the tree was
- * created by this instance. */
- protected boolean createdRenderer;
- /** Editor for the tree. */
- transient protected TreeCellEditor cellEditor;
- /** Set to true if editor that is currently in the tree was
- * created by this instance. */
- protected boolean createdCellEditor;
- /** Set to false when editing and shouldSelectCell() returns true meaning
- * the node should be selected before editing, used in completeEditing. */
- protected boolean stopEditingInCompleteEditing;
- /** Used to paint the TreeCellRenderer. */
- protected CellRendererPane rendererPane;
- /** Size needed to completely display all the nodes. */
- protected Dimension preferredSize;
- /** Is the preferredSize valid? */
- protected boolean validCachedPreferredSize;
- /** Object responsible for handling sizing and expanded issues. */
- protected AbstractLayoutCache treeState;
- /** Used for minimizing the drawing of vertical lines. */
- protected Hashtable drawingCache;
- /** True if doing optimizations for a largeModel. Subclasses that
- * don't support this may wish to override createLayoutCache to not
- * return a FixedHeightLayoutCache instance. */
- protected boolean largeModel;
- /** Reponsible for telling the TreeState the size needed for a node. */
- protected AbstractLayoutCache.NodeDimensions nodeDimensions;
- /** Used to determine what to display. */
- protected TreeModel treeModel;
- /** Model maintaing the selection. */
- protected TreeSelectionModel treeSelectionModel;
- /** How much the depth should be offset to properly calculate
- * x locations. This is based on whether or not the root is visible,
- * and if the root handles are visible. */
- protected int depthOffset;
- // Following 4 ivars are only valid when editing.
- /** When editing, this will be the Component that is doing the actual
- * editing. */
- protected Component editingComponent;
- /** Path that is being edited. */
- protected TreePath editingPath;
- /** Row that is being edited. Should only be referenced if
- * editingComponent is not null. */
- protected int editingRow;
- /** Set to true if the editor has a different size than the renderer. */
- protected boolean editorHasDifferentSize;
- /** Updated when selection changes. This really needs to be in JTree, and
- * public at some point. */
- private TreePath anchorPath;
- /** Path of last selected item. */
- private TreePath leadPath;
- /** Row correspondin to lead path. */
- private int leadRow;
- /** If true, the selection will be changed with the focus, otherwise
- * both the selection and focus will change at the same time. This comes
- * from the property Tree.changeSelectionWithFocus. */
- private boolean changeSelectionWithFocus;
- /** KeyStrokes registered actions under. */
- private Vector keyActions;
- // Cached listeners
- private PropertyChangeListener propertyChangeListener;
- private PropertyChangeListener selectionModelPropertyChangeListener;
- private MouseListener mouseListener;
- private FocusListener focusListener;
- private KeyListener keyListener;
- /** Used for large models, listens for moved/resized events and
- * updates the validCachedPreferredSize bit accordingly. */
- private ComponentListener componentListener;
- /** Listens for CellEditor events. */
- private CellEditorListener cellEditorListener;
- /** Updates the display when the selection changes. */
- private TreeSelectionListener treeSelectionListener;
- /** Is responsible for updating the display based on model events. */
- private TreeModelListener treeModelListener;
- /** Updates the treestate as the nodes expand. */
- private TreeExpansionListener treeExpansionListener;
- public static ComponentUI createUI(JComponent x) {
- return new BasicTreeUI();
- }
- public BasicTreeUI() {
- super();
- }
- protected Color getHashColor() {
- return hashColor;
- }
- protected void setHashColor(Color color) {
- hashColor = color;
- }
- public void setLeftChildIndent(int newAmount) {
- leftChildIndent = newAmount;
- totalChildIndent = leftChildIndent + rightChildIndent;
- if(treeState != null)
- treeState.invalidateSizes();
- updateSize();
- }
- public int getLeftChildIndent() {
- return leftChildIndent;
- }
- public void setRightChildIndent(int newAmount) {
- rightChildIndent = newAmount;
- totalChildIndent = leftChildIndent + rightChildIndent;
- if(treeState != null)
- treeState.invalidateSizes();
- updateSize();
- }
- public int getRightChildIndent() {
- return rightChildIndent;
- }
- public void setExpandedIcon(Icon newG) {
- expandedIcon = newG;
- }
- public Icon getExpandedIcon() {
- return expandedIcon;
- }
- public void setCollapsedIcon(Icon newG) {
- collapsedIcon = newG;
- }
- public Icon getCollapsedIcon() {
- return collapsedIcon;
- }
- //
- // Methods for configuring the behavior of the tree. None of them
- // push the value to the JTree instance. You should really only
- // call these methods on the JTree.
- //
- /**
- * Updates the componentListener, if necessary.
- */
- protected void setLargeModel(boolean largeModel) {
- if(getRowHeight() < 1)
- largeModel = false;
- if(this.largeModel != largeModel) {
- completeEditing();
- this.largeModel = largeModel;
- treeState = createLayoutCache();
- configureLayoutCache();
- updateLayoutCacheExpandedNodes();
- updateSize();
- }
- }
- protected boolean isLargeModel() {
- return largeModel;
- }
- /**
- * Sets the row height, this is forwarded to the treeState.
- */
- protected void setRowHeight(int rowHeight) {
- completeEditing();
- if(treeState != null) {
- setLargeModel(tree.isLargeModel());
- treeState.setRowHeight(rowHeight);
- updateSize();
- }
- }
- protected int getRowHeight() {
- return (tree == null) ? -1 : tree.getRowHeight();
- }
- /**
- * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
- * <code>updateRenderer</code>.
- */
- protected void setCellRenderer(TreeCellRenderer tcr) {
- completeEditing();
- updateRenderer();
- if(treeState != null) {
- treeState.invalidateSizes();
- updateSize();
- }
- }
- /**
- * Return currentCellRenderer, which will either be the trees
- * renderer, or defaultCellRenderer, which ever wasn't null.
- */
- protected TreeCellRenderer getCellRenderer() {
- return currentCellRenderer;
- }
- /**
- * Sets the TreeModel.
- */
- protected void setModel(TreeModel model) {
- completeEditing();
- if(treeModel != null && treeModelListener != null)
- treeModel.removeTreeModelListener(treeModelListener);
- treeModel = model;
- if(treeModel != null) {
- if(treeModelListener != null)
- treeModel.addTreeModelListener(treeModelListener);
- }
- if(treeState != null) {
- treeState.setModel(model);
- updateLayoutCacheExpandedNodes();
- updateSize();
- }
- }
- protected TreeModel getModel() {
- return treeModel;
- }
- /**
- * Sets the root to being visible.
- */
- protected void setRootVisible(boolean newValue) {
- completeEditing();
- updateDepthOffset();
- if(treeState != null) {
- treeState.setRootVisible(newValue);
- treeState.invalidateSizes();
- updateSize();
- }
- }
- protected boolean isRootVisible() {
- return (tree != null) ? tree.isRootVisible() : false;
- }
- /**
- * Determines whether the node handles are to be displayed.
- */
- protected void setShowsRootHandles(boolean newValue) {
- completeEditing();
- updateDepthOffset();
- if(treeState != null) {
- treeState.invalidateSizes();
- updateSize();
- }
- }
- protected boolean getShowsRootHandles() {
- return (tree != null) ? tree.getShowsRootHandles() : false;
- }
- /**
- * Sets the cell editor.
- */
- protected void setCellEditor(TreeCellEditor editor) {
- updateCellEditor();
- }
- protected TreeCellEditor getCellEditor() {
- return (tree != null) ? tree.getCellEditor() : null;
- }
- /**
- * Configures the receiver to allow, or not allow, editing.
- */
- protected void setEditable(boolean newValue) {
- updateCellEditor();
- }
- protected boolean isEditable() {
- return (tree != null) ? tree.isEditable() : false;
- }
- /**
- * Resets the selection model. The appropriate listener are installed
- * on the model.
- */
- protected void setSelectionModel(TreeSelectionModel newLSM) {
- completeEditing();
- if(selectionModelPropertyChangeListener != null &&
- treeSelectionModel != null)
- treeSelectionModel.removePropertyChangeListener
- (selectionModelPropertyChangeListener);
- if(treeSelectionListener != null && treeSelectionModel != null)
- treeSelectionModel.removeTreeSelectionListener
- (treeSelectionListener);
- treeSelectionModel = newLSM;
- if(treeSelectionModel != null) {
- if(selectionModelPropertyChangeListener != null)
- treeSelectionModel.addPropertyChangeListener
- (selectionModelPropertyChangeListener);
- if(treeSelectionListener != null)
- treeSelectionModel.addTreeSelectionListener
- (treeSelectionListener);
- if(treeState != null)
- treeState.setSelectionModel(treeSelectionModel);
- }
- else if(treeState != null)
- treeState.setSelectionModel(null);
- if(tree != null)
- tree.repaint();
- }
- protected TreeSelectionModel getSelectionModel() {
- return treeSelectionModel;
- }
- //
- // TreeUI methods
- //
- /**
- * Returns the Rectangle enclosing the label portion that the
- * last item in path will be drawn into. Will return null if
- * any component in path is currently valid.
- */
- public Rectangle getPathBounds(JTree tree, TreePath path) {
- if(tree != null && treeState != null) {
- Insets i = tree.getInsets();
- Rectangle bounds = treeState.getBounds(path, null);
- if(bounds != null && i != null) {
- bounds.x += i.left;
- bounds.y += i.top;
- }
- return bounds;
- }
- return null;
- }
- /**
- * Returns the path for passed in row. If row is not visible
- * null is returned.
- */
- public TreePath getPathForRow(JTree tree, int row) {
- return (treeState != null) ? treeState.getPathForRow(row) : null;
- }
- /**
- * Returns the row that the last item identified in path is visible
- * at. Will return -1 if any of the elements in path are not
- * currently visible.
- */
- public int getRowForPath(JTree tree, TreePath path) {
- return (treeState != null) ? treeState.getRowForPath(path) : -1;
- }
- /**
- * Returns the number of rows that are being displayed.
- */
- public int getRowCount(JTree tree) {
- return (treeState != null) ? treeState.getRowCount() : 0;
- }
- /**
- * Returns the path to the node that is closest to x,y. If
- * there is nothing currently visible this will return null, otherwise
- * it'll always return a valid path. If you need to test if the
- * returned object is exactly at x, y you should get the bounds for
- * the returned path and test x, y against that.
- */
- public TreePath getClosestPathForLocation(JTree tree, int x, int y) {
- if(tree != null && treeState != null) {
- Insets i = tree.getInsets();
- if(i == null)
- i = EMPTY_INSETS;
- return treeState.getPathClosestTo(x - i.left, y - i.top);
- }
- return null;
- }
- /**
- * Returns true if the tree is being edited. The item that is being
- * edited can be returned by getEditingPath().
- */
- public boolean isEditing(JTree tree) {
- return (editingComponent != null);
- }
- /**
- * Stops the current editing session. This has no effect if the
- * tree isn't being edited. Returns true if the editor allows the
- * editing session to stop.
- */
- public boolean stopEditing(JTree tree) {
- if(editingComponent != null && cellEditor.stopCellEditing()) {
- completeEditing(false, false, true);
- return true;
- }
- return false;
- }
- /**
- * Cancels the current editing session.
- */
- public void cancelEditing(JTree tree) {
- if(editingComponent != null) {
- completeEditing(false, true, false);
- }
- }
- /**
- * Selects the last item in path and tries to edit it. Editing will
- * fail if the CellEditor won't allow it for the selected item.
- */
- public void startEditingAtPath(JTree tree, TreePath path) {
- tree.scrollPathToVisible(path);
- if(path != null && tree.isVisible(path))
- startEditing(path, null);
- }
- /**
- * Returns the path to the element that is being edited.
- */
- public TreePath getEditingPath(JTree tree) {
- return editingPath;
- }
- //
- // Install methods
- //
- public void installUI(JComponent c) {
- if ( c == null ) {
- throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" );
- }
- tree = (JTree)c;
- prepareForUIInstall();
- // Boilerplate install block
- installDefaults();
- installListeners();
- installKeyboardActions();
- installComponents();
- completeUIInstall();
- }
- /**
- * Invoked after the <code>tree</code> instance variable has been
- * set, but before any defaults/listeners have been installed.
- */
- protected void prepareForUIInstall() {
- drawingCache = new Hashtable(7);
- // Data member initializations
- stopEditingInCompleteEditing = true;
- lastSelectedRow = -1;
- leadPath = anchorPath = null;
- leadRow = -1;
- preferredSize = new Dimension();
- tree.setRowHeight(UIManager.getInt("Tree.rowHeight"));
- Object b = UIManager.get("Tree.scrollsOnExpand");
- if(b != null)
- tree.setScrollsOnExpand(((Boolean)b).booleanValue());
- largeModel = tree.isLargeModel();
- if(getRowHeight() <= 0)
- largeModel = false;
- setModel(tree.getModel());
- }
- /**
- * Invoked from installUI after all the defaults/listeners have been
- * installed.
- */
- protected void completeUIInstall() {
- // Custom install code
- this.setShowsRootHandles(tree.getShowsRootHandles());
- updateRenderer();
- updateDepthOffset();
- setSelectionModel(tree.getSelectionModel());
- // Create, if necessary, the TreeState instance.
- treeState = createLayoutCache();
- configureLayoutCache();
- updateSize();
- }
- protected void installDefaults() {
- if(tree.getBackground() == null ||
- tree.getBackground() instanceof UIResource) {
- tree.setBackground(UIManager.getColor("Tree.background"));
- }
- if(getHashColor() == null || getHashColor() instanceof UIResource) {
- setHashColor(UIManager.getColor("Tree.hash"));
- }
- if (tree.getFont() == null || tree.getFont() instanceof UIResource)
- tree.setFont( UIManager.getFont("Tree.font") );
- 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());
- changeSelectionWithFocus = ((Boolean)UIManager.get
- ("Tree.changeSelectionWithFocus")).booleanValue();
- }
- protected void installListeners() {
- if ( (propertyChangeListener = createPropertyChangeListener())
- != null ) {
- tree.addPropertyChangeListener(propertyChangeListener);
- }
- if ( (mouseListener = createMouseListener()) != null ) {
- tree.addMouseListener(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);
- }
- }
- private void registerKeyAction(ActionListener action, KeyStroke ks) {
- keyActions.addElement(ks);
- tree.registerKeyboardAction(action, ks, JComponent.WHEN_FOCUSED);
- }
- protected void installKeyboardActions() {
- // PENDING(sky): All these names need to be rethought, and the values
- // need to come from the defaults manager.
- boolean changeSelection = changeSelectionWithFocus();
- // YES! 33 key actions, yeesh!
- // hania: This became 43 when I added the keypad arrow key actions.
- // hania: is counding the number of calls below really the best way
- // to determine the size this vector needs to be initialized to?
- keyActions = new Vector(43);
- registerKeyAction(new TreeIncrementAction(-1, "UP", false,
- changeSelection), KeyStroke.getKeyStroke(KeyEvent.VK_UP,0));
- registerKeyAction(new TreeIncrementAction(-1, "UP", false,
- changeSelection), KeyStroke.getKeyStroke("KP_UP"));
- registerKeyAction(new TreeIncrementAction(-1, "SELECT_UP", true,
- true), KeyStroke.getKeyStroke(KeyEvent.VK_UP,
- InputEvent.SHIFT_MASK));
- registerKeyAction(new TreeIncrementAction(-1, "SELECT_UP", true,
- true), KeyStroke.getKeyStroke("shift KP_UP"));
- registerKeyAction(new TreeIncrementAction(1, "DOWN", false,
- changeSelection), KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0));
- registerKeyAction(new TreeIncrementAction(1, "DOWN", false,
- changeSelection), KeyStroke.getKeyStroke("KP_DOWN" ));
- registerKeyAction(new TreeIncrementAction(1, "SELECT_DOWN",
- true, true), KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,
- InputEvent.SHIFT_MASK));
- registerKeyAction(new TreeIncrementAction(1, "SELECT_DOWN",
- true, true), KeyStroke.getKeyStroke("shift KP_DOWN"));
- registerKeyAction(new TreeTraverseAction(1, "RIGHT",
- changeSelection),
- KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0));
- registerKeyAction(new TreeTraverseAction(1, "RIGHT",
- changeSelection),
- KeyStroke.getKeyStroke("KP_RIGHT"));
- registerKeyAction(new TreeTraverseAction(-1, "LEFT",
- changeSelection),
- KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0));
- registerKeyAction(new TreeTraverseAction(-1, "LEFT",
- changeSelection),
- KeyStroke.getKeyStroke("KP_LEFT"));
- registerKeyAction(new TreePageAction(-1, "P_UP", false,
- changeSelection),
- KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,0));
- registerKeyAction(new TreePageAction(-1, "SELECT_P_UP",
- true, true),
- KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,
- InputEvent.SHIFT_MASK));
- registerKeyAction(new TreePageAction(1, "P_DOWN", false,
- changeSelection),
- KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0));
- registerKeyAction(new TreePageAction(1, "SELECT_P_DOWN",
- true, true), KeyStroke.getKeyStroke(KeyEvent.
- VK_PAGE_DOWN, InputEvent.SHIFT_MASK));
- registerKeyAction(new TreeHomeAction(-1, "HOME", false,
- changeSelection), KeyStroke.getKeyStroke(KeyEvent.VK_HOME,0));
- registerKeyAction(new TreeHomeAction(-1, "SELECT_HOME",true,
- true), KeyStroke.getKeyStroke(KeyEvent.VK_HOME,
- InputEvent.SHIFT_MASK));
- registerKeyAction(new TreeHomeAction(1, "END", false,
- changeSelection),KeyStroke.getKeyStroke
- (KeyEvent.VK_END,0));
- registerKeyAction(new TreeHomeAction(1, "SELECT_END", true,
- true), KeyStroke.getKeyStroke(KeyEvent.VK_END,
- InputEvent.SHIFT_MASK));
- registerKeyAction(new TreeToggleAction("TOGGLE"),
- KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0));
- KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
- tree.registerKeyboardAction(new TreeCancelEditingAction("ESCAPE"), ks,
- JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
- keyActions.addElement(ks);
- registerKeyAction(new TreeEditAction("EDIT_NODE"),
- KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
- ActionListener saAction = new TreeSelectAllAction("SELECT_ALL",true);
- registerKeyAction(saAction, KeyStroke.getKeyStroke
- (KeyEvent.VK_A, InputEvent.CTRL_MASK));
- registerKeyAction(saAction, KeyStroke.getKeyStroke
- (KeyEvent.VK_SLASH, InputEvent.CTRL_MASK));
- registerKeyAction(new TreeSelectAllAction("UNSELECT_ALL",
- false), KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SLASH,
- InputEvent.CTRL_MASK));
- registerKeyAction(new TreeAddSelectionAction("SELECT_NODE", false),
- KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,
- InputEvent.CTRL_MASK));
- registerKeyAction(new TreeExtendSelectionAction
- ("EXTEND_SELECTION"),
- KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,
- InputEvent.SHIFT_MASK));
- if(changeSelection) {
- registerKeyAction(new TreeHomeAction(-1, "HOME", false,
- false), KeyStroke.getKeyStroke(KeyEvent.VK_HOME,
- InputEvent.CTRL_MASK));
- registerKeyAction(new TreeHomeAction(1, "END", false,
- false),KeyStroke.getKeyStroke(KeyEvent.VK_END,
- InputEvent.CTRL_MASK));
- registerKeyAction(new TreeIncrementAction(-1, "UP",
- false, false), KeyStroke.getKeyStroke(KeyEvent.VK_UP,
- InputEvent.CTRL_MASK));
- registerKeyAction(new TreeIncrementAction(-1, "UP",
- false, false), KeyStroke.getKeyStroke("ctrl KP_UP"));
- registerKeyAction(new TreeIncrementAction(1, "DOWN",
- false, false), KeyStroke.getKeyStroke
- (KeyEvent.VK_DOWN, InputEvent.CTRL_MASK));
- registerKeyAction(new TreeIncrementAction(1, "DOWN",
- false, false), KeyStroke.getKeyStroke
- ("ctrl KP_DOWN"));
- registerKeyAction(new TreePageAction(1, "LEAD_P_DOWN", false,
- false), KeyStroke.getKeyStroke
- (KeyEvent.VK_PAGE_DOWN, InputEvent.CTRL_MASK));
- registerKeyAction(new TreePageAction(1, "SELECT_P_DOWN", true,
- true), KeyStroke.getKeyStroke
- (KeyEvent.VK_PAGE_DOWN, InputEvent.CTRL_MASK |
- InputEvent.SHIFT_MASK));
- registerKeyAction(new TreePageAction(-1, "LEAD_P_UP", false,
- false), KeyStroke.getKeyStroke
- (KeyEvent.VK_PAGE_UP, InputEvent.CTRL_MASK));
- registerKeyAction(new TreePageAction(-1, "SELECT_P_UP", true,
- true), KeyStroke.getKeyStroke
- (KeyEvent.VK_PAGE_UP, InputEvent.CTRL_MASK |
- InputEvent.SHIFT_MASK));
- registerKeyAction(new ScrollAction(tree, SwingConstants.
- HORIZONTAL, -10), KeyStroke.getKeyStroke
- (KeyEvent.VK_LEFT, InputEvent.CTRL_MASK));
- registerKeyAction(new ScrollAction(tree, SwingConstants.
- HORIZONTAL, -10), KeyStroke.getKeyStroke
- ("ctrl KP_LEFT"));
- registerKeyAction(new ScrollAction(tree, SwingConstants.
- HORIZONTAL, 10), KeyStroke.getKeyStroke
- (KeyEvent.VK_RIGHT, InputEvent.CTRL_MASK));
- registerKeyAction(new ScrollAction(tree, SwingConstants.
- HORIZONTAL, 10), KeyStroke.getKeyStroke
- ("ctrl KP_RIGHT"));
- registerKeyAction(new TreeAddSelectionAction("SELECT_NODE", false),
- KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0));
- }
- else {
- registerKeyAction(new TreeScrollLRAction(1, "CTRL_P_DOWN",
- false), KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,
- InputEvent.CTRL_MASK));
- registerKeyAction(new TreeScrollLRAction(-1, "CTRL_P_UP",
- false), KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,
- InputEvent.CTRL_MASK));
- registerKeyAction(new TreeScrollLRAction(-1,
- "SELECT_CTRL_P_UP", true), KeyStroke.
- getKeyStroke(KeyEvent.VK_PAGE_UP,
- InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK));
- registerKeyAction(new TreeScrollLRAction(1,
- "SELECT_CTRL_P_DOWN", true), KeyStroke.
- getKeyStroke(KeyEvent.VK_PAGE_DOWN,
- InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK));
- registerKeyAction(new TreeAddSelectionAction("SELECT_NODE", true),
- KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0));
- }
- }
- /**
- * Intalls the subcomponents of the tree, which is the renderer pane.
- */
- protected void installComponents() {
- if ((rendererPane = createCellRendererPane()) != null) {
- tree.add( rendererPane );
- }
- }
- //
- // Create methods.
- //
- /**
- * Creates an instance of NodeDimensions that is able to determine
- * the size of a given node in the tree.
- */
- protected AbstractLayoutCache.NodeDimensions createNodeDimensions() {
- return new NodeDimensionsHandler();
- }
- /**
- * Creates a listener that is responsible that updates the UI based on
- * how the tree changes.
- */
- protected PropertyChangeListener createPropertyChangeListener() {
- return new PropertyChangeHandler();
- }
- /**
- * Creates the listener responsible for updating the selection based on
- * mouse events.
- */
- protected MouseListener createMouseListener() {
- return new MouseHandler();
- }
- /**
- * Creates a listener that is responsible for updating the display
- * when focus is lost/gained.
- */
- protected FocusListener createFocusListener() {
- return new FocusHandler();
- }
- /**
- * Creates the listener reponsible for getting key events from
- * the tree.
- */
- protected KeyListener createKeyListener() {
- return new KeyHandler();
- }
- /**
- * Creates the listener responsible for getting property change
- * events from the selection model.
- */
- protected PropertyChangeListener createSelectionModelPropertyChangeListener() {
- return new SelectionModelPropertyChangeHandler();
- }
- /**
- * Creates the listener that updates the display based on selection change
- * methods.
- */
- protected TreeSelectionListener createTreeSelectionListener() {
- return new TreeSelectionHandler();
- }
- /**
- * Creates a listener to handle events from the current editor.
- */
- protected CellEditorListener createCellEditorListener() {
- return new CellEditorHandler();
- }
- /**
- * Creates and returns a new ComponentHandler. This is used for
- * the large model to mark the validCachedPreferredSize as invalid
- * when the component moves.
- */
- protected ComponentListener createComponentListener() {
- return new ComponentHandler();
- }
- /**
- * Creates and returns the object responsible for updating the treestate
- * when nodes expanded state changes.
- */
- protected TreeExpansionListener createTreeExpansionListener() {
- return new TreeExpansionHandler();
- }
- /**
- * Creates the object responsible for managing what is expanded, as
- * well as the size of nodes.
- */
- protected AbstractLayoutCache createLayoutCache() {
- if(isLargeModel() && getRowHeight() > 0) {
- return new FixedHeightLayoutCache();
- }
- return new VariableHeightLayoutCache();
- }
- /**
- * Returns the renderer pane that renderer components are placed in.
- */
- protected CellRendererPane createCellRendererPane() {
- return new CellRendererPane();
- }
- /**
- * Creates a default cell editor.
- */
- protected TreeCellEditor createDefaultCellEditor() {
- if(currentCellRenderer != null &&
- (currentCellRenderer instanceof DefaultTreeCellRenderer)) {
- DefaultTreeCellEditor editor = new DefaultTreeCellEditor
- (tree, (DefaultTreeCellRenderer)currentCellRenderer);
- return editor;
- }
- return new DefaultTreeCellEditor(tree, null);
- }
- /**
- * Returns the default cell renderer that is used to do the
- * stamping of each node.
- */
- protected TreeCellRenderer createDefaultCellRenderer() {
- return new DefaultTreeCellRenderer();
- }
- /**
- * Returns a listener that can update the tree when the model changes.
- */
- protected TreeModelListener createTreeModelListener() {
- return new TreeModelHandler();
- }
- //
- // 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;
- treeState = null;
- setSelectionModel(null);
- drawingCache = null;
- selectionModelPropertyChangeListener = null;
- tree = null;
- treeModel = null;
- treeSelectionModel = null;
- treeSelectionListener = null;
- treeExpansionListener = null;
- }
- protected void uninstallDefaults() {
- }
- protected void uninstallListeners() {
- if(componentListener != null) {
- tree.removeComponentListener(componentListener);
- }
- if (propertyChangeListener != null) {
- tree.removePropertyChangeListener(propertyChangeListener);
- }
- if (mouseListener != null) {
- tree.removeMouseListener(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);
- }
- }
- protected void uninstallKeyboardActions() {
- for(int counter = keyActions.size() - 1; counter >= 0; counter--) {
- tree.unregisterKeyboardAction((KeyStroke)keyActions.
- elementAt(counter));
- }
- keyActions = null;
- }
- /**
- * Uninstalls the renderer pane.
- */
- protected void uninstallComponents() {
- if(rendererPane != null) {
- tree.remove(rendererPane);
- }
- }
- //
- // 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();
- if(insets == null)
- insets = EMPTY_INSETS;
- 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 = treeState.getBounds(path, 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;
- bounds.x += insets.left;
- bounds.y += insets.top;
- // 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++;
- }
- }
- }
- /**
- * Paints the horizontal part of the leg. The reciever 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) {
- int clipLeft = clipBounds.x;
- int clipRight = clipBounds.x + (clipBounds.width - 1);
- int clipTop = clipBounds.y;
- int clipBottom = clipBounds.y + (clipBounds.height - 1);
- int lineY = bounds.y + bounds.height / 2;
- // Offset leftX from parents indent.
- int leftX = bounds.x - totalChildIndent + 8;
- int nodeX = bounds.x - getHorizontalLegBuffer();
- if(lineY > clipTop && lineY < clipBottom && nodeX > clipLeft &&
- leftX < clipRight ) {
- leftX = Math.max(leftX, clipLeft);
- nodeX = Math.min(nodeX, clipRight);
- g.setColor(getHashColor());
- paintHorizontalLine(g, tree, lineY, leftX, nodeX);
- }
- }
- /**
- * Paints the vertical part of the leg. The reciever should
- * NOT modify <code>clipBounds</code>, <code>insets</code>.<p>
- */
- protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
- Insets insets, TreePath path) {
- int lineX = ((path.getPathCount() - 1 + depthOffset) *
- totalChildIndent) + 8 + insets.left;
- 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(path.getPathCount() == 1 && !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);
- g.setColor(getHashColor());
- paintVerticalLine(g, tree, lineX, top, bottom);
- }
- }
- /**
- * Paints the expand (toggle) part of a row. The reciever 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 = bounds.x - (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 reciever 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 = getLeadRow();
- }
- else
- leadIndex = -1;
- Component component;
- component = currentCellRenderer.getTreeCellRendererComponent
- (tree, path.getLastPathComponent(),
- tree.isRowSelected(row), isExpanded, isLeaf, row,
- (leadIndex == row));
- rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
- bounds.width, bounds.height, true);
- }
- /**
- * Returns true if the expand (toggle) control should be drawn for
- * the specified row.
- */
- protected boolean shouldPaintExpandControl(TreePath path, int row,
- boolean isExpanded,
- boolean hasBeenExpanded,
- boolean isLeaf) {
- if(isLeaf)
- return false;
- int depth = path.getPathCount() - 1;
- if((depth == 0 || (depth == 1 && !isRootVisible())) &&
- !getShowsRootHandles())
- return false;
- return true;
- }
- /**
- * Paints a vertical line.
- */
- protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
- int bottom) {
- g.drawLine(x, top, x, bottom);
- }
- /**
- * Paints a horizontal line.
- */
- protected void paintHorizontalLine(Graphics g, JComponent c, int y,
- int left, int right) {
- g.drawLine(left, y, right, y);
- }
- /**
- * The vertical element of legs between nodes starts at the bottom of the
- * parent node by default. This method makes the leg start below that.
- */
- protected int getVerticalLegBuffer() {
- return 0;
- }
- /**
- * The horizontal element of legs between nodes starts at the
- * right of the left-hand side of the child node by default. This
- * method makes the leg end before that.
- */
- protected int getHorizontalLegBuffer() {
- return 0;
- }
- //
- // 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, x - icon.getIconWidth()/2, 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
- //
- /**
- * 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)
- updateExpandedDescendants(new TreePath(treeModel.getRoot()));
- }
- /**
- * Updates the expanded state of all the descendants of <code>path</code>
- * by getting the expanded descendants from the tree and forwarding
- * to the tree state.
- */
- protected void updateExpandedDescendants(TreePath path) {
- completeEditing();
- if(treeState != null) {
- treeState.setExpandedState(path, true);
- Enumeration descendants = tree.getExpandedDescendants(path);
- if(descendants != null) {
- while(descendants.hasMoreElements()) {
- path = (TreePath)descendants.nextElement();
- treeState.setExpandedState(path, true);
- }
- }
- updateLeadRow();
- updateSize();
- }
- }
- /**
- * Returns a path to the last child of <code>parent</code>.
- */
- protected TreePath getLastChildPath(TreePath parent) {
- if(treeModel != null) {
- int childCount = treeModel.getChildCount
- (parent.getLastPathComponent());
- if(childCount > 0)
- return parent.pathByAddingChild(treeModel.getChild
- (parent.getLastPathComponent(), childCount - 1));
- }
- return null;
- }
- /**
- * Updates how much each depth should be offset by.
- */
- protected void updateDepthOffset() {
- if(isRootVisible()) {
- if(getShowsRootHandles())
- depthOffset = 1;
- else
- depthOffset = 0;
- }
- else if(!getShowsRootHandles())
- depthOffset = -1;
- else
- depthOffset = 0;
- }
- /**
- * Updates the cellEditor based on the editability of the JTree that
- * we're contained in. If the tree is editable but doesn't have a
- * cellEditor, a basic one will be used.
- */
- protected void updateCellEditor() {
- TreeCellEditor newEditor;
- completeEditing();
- if(tree == null)
- newEditor = null;
- else {
- if(tree.isEditable()) {
- newEditor = tree.getCellEditor();
- if(newEditor == null) {
- newEditor = createDefaultCellEditor();
- if(newEditor != null) {
- tree.setCellEditor(newEditor);
- createdCellEditor = true;
- }
- }
- }
- else
- newEditor = null;
- }
- if(newEditor != cellEditor) {
- if(cellEditor != null && cellEditorListener != null)
- cellEditor.removeCellEditorListener(cellEditorListener);
- cellEditor = newEditor;
- if(cellEditorListener == null)
- cellEditorListener = createCellEditorListener();
- if(newEditor != null && cellEditorListener != null)
- newEditor.addCellEditorListener(cellEditorListener);
- createdCellEditor = false;
- }
- }
- /**
- * Messaged from the tree we're in when the renderer has changed.
- */
- protected void updateRenderer() {
- if(tree != null) {
- TreeCellRenderer newCellRenderer;
- newCellRenderer = tree.getCellRenderer();
- if(newCellRenderer == null) {
- tree.setCellRenderer(createDefaultCellRenderer());
- createdRenderer = true;
- }
- else {
- createdRenderer = false;
- currentCellRenderer = newCellRenderer;
- if(createdCellEditor) {
- tree.setCellEditor(null);
- }
- }
- }
- else {
- createdRenderer = false;
- currentCellRenderer = null;
- }
- updateCellEditor();
- }
- /**
- * Resets the TreeState instance based on the tree we're providing the
- * look and feel for.
- */
- protected void configureLayoutCache() {
- if(treeState != null && tree != null) {
- if(nodeDimensions == null)
- nodeDimensions = createNodeDimensions();
- treeState.setNodeDimensions(nodeDimensions);
- treeState.setRootVisible(tree.isRootVisible());
- treeState.setRowHeight(tree.getRowHeight());
- treeState.setSelectionModel(getSelectionModel());
- // Only do this if necessary, may loss state if call with
- // same model as it currently has.
- if(treeState.getModel() != tree.getModel())
- treeState.setModel(tree.getModel());
- updateLayoutCacheExpandedNodes();