- /*
- * @(#)BasicTreeUI.java 1.122 00/02/02
- *
- * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * 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.Enumeration;
- import java.util.Hashtable;
- import javax.swing.plaf.ActionMapUIResource;
- 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.113 07/20/99
- * @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;
- /** Last width the tree was at when painted. This is used when
- * !leftToRigth to notice the bounds have changed so that we can instruct
- * the TreeState to relayout. */
- private int lastWidth;
- // Following 4 ivars are only valid when editing.
- /** When editing, this will be the Component that is doing the actual
- * editing. */
- protected Component editingComponent;
- /** Path that is being edited. */
- protected TreePath editingPath;
- /** Row that is being edited. Should only be referenced if
- * editingComponent is not null. */
- protected int editingRow;
- /** Set to true if the editor has a different size than the renderer. */
- protected boolean editorHasDifferentSize;
- /** Row correspondin to lead path. */
- private int leadRow;
- /** If true, the property change event for LEAD_SELECTION_PATH_PROPERTY,
- * or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint. */
- private boolean ignoreLAChange;
- /** Indicates the orientation. */
- private boolean leftToRight;
- // Cached listeners
- private PropertyChangeListener propertyChangeListener;
- private PropertyChangeListener selectionModelPropertyChangeListener;
- private MouseListener mouseListener;
- private FocusListener focusListener;
- private KeyListener keyListener;
- /** Used for large models, listens for moved/resized events and
- * updates the validCachedPreferredSize bit accordingly. */
- private ComponentListener componentListener;
- /** Listens for CellEditor events. */
- private CellEditorListener cellEditorListener;
- /** Updates the display when the selection changes. */
- private TreeSelectionListener treeSelectionListener;
- /** Is responsible for updating the display based on model events. */
- private TreeModelListener treeModelListener;
- /** Updates the treestate as the nodes expand. */
- private TreeExpansionListener treeExpansionListener;
- 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
- leftToRight = BasicGraphicsUtils.isLeftToRight(tree);
- lastWidth = tree.getWidth();
- stopEditingInCompleteEditing = true;
- lastSelectedRow = -1;
- 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());
- }
- 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);
- }
- }
- 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);
- ActionMap am = getActionMap();
- SwingUtilities.replaceUIActionMap(tree, am);
- }
- InputMap getInputMap(int condition) {
- if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
- return (InputMap)UIManager.get("Tree.ancestorInputMap");
- }
- else if (condition == JComponent.WHEN_FOCUSED) {
- return (InputMap)UIManager.get("Tree.focusInputMap");
- }
- return null;
- }
- ActionMap getActionMap() {
- return createActionMap();
- }
- ActionMap createActionMap() {
- ActionMap map = new ActionMapUIResource();
- map.put("selectPrevious", new TreeIncrementAction(-1, "selectPrevious",
- false, true));
- map.put("selectPreviousChangeLead", new TreeIncrementAction
- (-1, "selectPreviousLead", false, false));
- map.put("selectPreviousExtendSelection", new TreeIncrementAction
- (-1, "selectPreviousExtendSelection", true, true));
- map.put("selectNext", new TreeIncrementAction
- (1, "selectNext", false, true));
- map.put("selectNextChangeLead", new TreeIncrementAction
- (1, "selectNextLead", false, false));
- map.put("selectNextExtendSelection", new TreeIncrementAction
- (1, "selectNextExtendSelection", true, true));
- map.put("selectChild", new TreeTraverseAction
- (1, "selectChild", true));
- map.put("selectChildChangeLead", new TreeTraverseAction
- (1, "selectChildLead", false));
- map.put("selectParent", new TreeTraverseAction
- (-1, "selectParent", true));
- map.put("selectParentChangeLead", new TreeTraverseAction
- (-1, "selectParentLead", false));
- map.put("scrollUpChangeSelection", new TreePageAction
- (-1, "scrollUpChangeSelection", false, true));
- map.put("scrollUpChangeLead", new TreePageAction
- (-1, "scrollUpChangeLead", false, false));
- map.put("scrollUpExtendSelection", new TreePageAction
- (-1, "scrollUpExtendSelection", true, true));
- map.put("scrollDownChangeSelection", new TreePageAction
- (1, "scrollDownChangeSelection", false, true));
- map.put("scrollDownExtendSelection", new TreePageAction
- (1, "scrollDownExtendSelection", true, true));
- map.put("scrollDownChangeLead", new TreePageAction
- (1, "scrollDownChangeLead", false, false));
- map.put("selectFirst", new TreeHomeAction
- (-1, "selectFirst", false, true));
- map.put("selectFirstChangeLead", new TreeHomeAction
- (-1, "selectFirst", false, false));
- map.put("selectFirstExtendSelection",new TreeHomeAction
- (-1, "selectFirstExtendSelection", true, true));
- map.put("selectLast", new TreeHomeAction
- (1, "selectLast", false, true));
- map.put("selectLastChangeLead", new TreeHomeAction
- (1, "selectLast", false, false));
- map.put("selectLastExtendSelection", new TreeHomeAction
- (1, "selectLastExtendSelection", true, true));
- map.put("toggle", new TreeToggleAction("toggle"));
- map.put("cancel", new TreeCancelEditingAction("cancel"));
- map.put("startEditing", new TreeEditAction("startEditing"));
- map.put("selectAll", new TreeSelectAllAction("selectAll", true));
- map.put("clearSelection", new TreeSelectAllAction
- ("clearSelection", false));
- map.put("toggleSelectionPreserveAnchor",
- new TreeAddSelectionAction("toggleSelectionPreserveAnchor",
- false));
- map.put("toggleSelection",
- new TreeAddSelectionAction("toggleSelection", true));
- map.put("extendSelection", new TreeExtendSelectionAction
- ("extendSelection"));
- map.put("scrollLeft", new ScrollAction
- (tree, SwingConstants.HORIZONTAL, -10));
- map.put("scrollLeftExtendSelection", new TreeScrollLRAction
- (-1, "scrollLeftExtendSelection", true, true));
- map.put("scrollRight", new ScrollAction
- (tree, SwingConstants.HORIZONTAL, 10));
- map.put("scrollRightExtendSelection", new TreeScrollLRAction
- (1, "scrollRightExtendSelection", true, true));
- map.put("scrollRightChangeLead", new TreeScrollLRAction
- (1, "scrollRightChangeLead", false, false));
- map.put("scrollLeftChangeLead", new TreeScrollLRAction
- (-1, "scrollLeftChangeLead", false, false));
- return map;
- }
- /**
- * 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;
- setSelectionModel(null);
- treeState = 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() {
- SwingUtilities.replaceUIActionMap(tree, null);
- SwingUtilities.replaceUIInputMap(tree, JComponent.
- WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
- null);
- SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, null);
- }
- /**
- * Uninstalls the renderer pane.
- */
- protected void uninstallComponents() {
- if(rendererPane != null) {
- tree.remove(rendererPane);
- }
- }
- /**
- * Recomputes the right margin, and invalidates any tree states
- */
- private void redoTheLayout() {
- if (treeState != null) {
- treeState.invalidateSizes();
- }
- }
- //
- // 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;
- }
- // Update the lastWidth if necessary.
- // This should really come from a ComponentListener installed on
- // the JTree, but for the time being it is here.
- int width = tree.getWidth();
- if (width != lastWidth) {
- lastWidth = width;
- if (!leftToRight) {
- // For RTL when the size changes, we have to refresh the
- // cache as the X position is based off the width.
- redoTheLayout();
- updateSize();
- }
- }
- 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);
- }
- //This is the quick fix for bug 4259260. Somewhere we
- //are out by 4 pixels in the RTL layout. Its probably
- //due to built in right-side padding in some icons. Rather
- //than ferret out problem at the source, this compensates.
- if (!leftToRight) {
- bounds.x +=4;
- }
- paintRow(g, paintBounds, insets, bounds, path,
- row, isExpanded, hasBeenExpanded, isLeaf);
- if((bounds.y + bounds.height) >= endY)
- done = true;
- }
- else {
- done = true;
- }
- row++;
- }
- }
- // Empty out the renderer pane, allowing renderers to be gc'ed.
- rendererPane.removeAll();
- }
- /**
- * 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.
- if (leftToRight) {
- int leftX = bounds.x - getRightChildIndent();
- 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);
- }
- }
- else {
- int leftX = bounds.x + bounds.width + getRightChildIndent();
- int nodeX = bounds.x + bounds.width +
- getHorizontalLegBuffer() - 1;
- if(lineY > clipTop && lineY < clipBottom &&
- leftX > clipLeft && nodeX < clipRight) {
- leftX = Math.min(leftX, clipRight);
- nodeX = Math.max(nodeX, clipLeft);
- g.setColor(getHashColor());
- paintHorizontalLine(g, tree, lineY, nodeX, leftX);
- }
- }
- }
- /**
- * 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;
- if (leftToRight) {
- lineX = ((path.getPathCount() + depthOffset) *
- totalChildIndent) - getRightChildIndent() + insets.left;
- }
- else {
- lineX = lastWidth - ((path.getPathCount() - 1 + depthOffset) *
- totalChildIndent) - 9;
- }
- 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;
- if (leftToRight) {
- middleXOfKnob = bounds.x - (getRightChildIndent() - 1);
- }
- else {
- middleXOfKnob = bounds.x + bounds.width + getRightChildIndent();
- }
- 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 = getLeadSelectionRow();
- }
- else
- leadIndex = -1;
- Component component;
- component = currentCellRenderer.getTreeCellRendererComponent
- (tree, path.getLastPathComponent(),
- tree.isRowSelected(row), isExpanded, isLeaf, row,
- (leadIndex == row));
- rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
- bounds.width, bounds.height, true);
- }
- /**
- * Returns true if the expand (toggle) control should be drawn for
- * the specified row.
- */
- protected boolean shouldPaintExpandControl(TreePath path, int row,
- boolean isExpanded,
- boolean hasBeenExpanded,
- boolean isLeaf) {
- if(isLeaf)
- return false;
- int depth = path.getPathCount() - 1;
- if((depth == 0 || (depth == 1 && !isRootVisible())) &&
- !getShowsRootHandles())
- return false;
- return true;
- }
- /**
- * Paints a vertical line.
- */
- protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
- int bottom) {
- 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();
- // Create a listener to update preferred size when bounds
- // changes, if necessary.
- if(isLargeModel()) {
- if(componentListener == null) {
- componentListener = createComponentListener();
- if(componentListener != null)
- tree.addComponentListener(componentListener);
- }
- }
- else if(componentListener != null) {
- tree.removeComponentListener(componentListener);
- componentListener = null;
- }
- }
- else if(componentListener != null) {
- tree.removeComponentListener(componentListener);
- componentListener = null;
- }
- }
- /**
- * Marks the cached size as being invalid, and messages the
- * tree with <code>treeDidChange</code>.
- */
- protected void updateSize() {
- validCachedPreferredSize = false;
- tree.treeDidChange();
- }
- /**
- * 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(i != null) {
- visRect.x -= i.left;
- visRect.y -= i.top;
- }
- if (leftToRight) {
- preferredSize.width = treeState.getPreferredWidth(visRect);
- }
- else {
- if (getRowCount(tree) == 0) {
- preferredSize.width = 0;
- }
- else {
- preferredSize.width = lastWidth - getMinX(visRect);
- }
- }
- }
- else if (leftToRight) {
- preferredSize.width = treeState.getPreferredWidth(null);
- }
- else {
- Rectangle tempRect = null;
- int rowCount = tree.getRowCount();
- int width = 0;
- for (int counter = 0; counter < rowCount; counter++) {
- tempRect = treeState.getBounds
- (treeState.getPathForRow(counter), tempRect);
- if (tempRect != null) {
- width = Math.max(lastWidth - tempRect.x, width);
- }
- }
- preferredSize.width = width;
- }
- preferredSize.height = treeState.getPreferredHeight();
- if(i != null) {
- preferredSize.width += i.left + i.right;
- preferredSize.height += i.top + i.bottom;
- }
- }
- validCachedPreferredSize = true;
- }
- /**
- * Returns the minimum x location for the nodes in <code>bounds</code>.
- */
- private int getMinX(Rectangle bounds) {
- TreePath firstPath;
- int endY;
- if(bounds == null) {
- firstPath = getPathForRow(tree, 0);
- endY = Integer.MAX_VALUE;
- }
- else {
- firstPath = treeState.getPathClosestTo(bounds.x, bounds.y);
- endY = bounds.height + bounds.y;
- }
- Enumeration paths = treeState.getVisiblePathsFrom(firstPath);
- int minX = 0;
- if(paths != null && paths.hasMoreElements()) {
- Rectangle pBounds = treeState.getBounds
- ((TreePath)paths.nextElement(), null);
- int width;
- if(pBounds != null) {
- minX = pBounds.x + pBounds.width;
- if (pBounds.y >= endY) {
- return minX;
- }
- }
- while (pBounds != null && paths.hasMoreElements()) {
- pBounds = treeState.getBounds((TreePath)paths.nextElement(),
- pBounds);
- if (pBounds != null && pBounds.y < endY) {
- minX = Math.min(minX, pBounds.x);
- }
- else {
- pBounds = null;
- }
- }
- return minX;
- }
- return minX;
- }
- /**
- * Messaged from the VisibleTreeNode after it has been expanded.
- */
- protected void pathWasExpanded(TreePath path) {
- if(tree != null) {
- tree.fireTreeExpanded(path);
- }
- }
- /**
- * Messaged from the VisibleTreeNode after it has collapsed.
- */
- protected void pathWasCollapsed(TreePath path) {
- if(tree != null) {
- tree.fireTreeCollapsed(path);
- }
- }
- /**
- * Ensures that the rows identified by beginRow through endRow are
- * visible.
- */
- protected void ensureRowsAreVisible(int beginRow, int endRow) {
- if(tree != null && beginRow >= 0 && endRow < getRowCount(tree))