| /* |
| * Copyright 1997-2007 Sun Microsystems, Inc. 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. Sun designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.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.awt.dnd.*; |
| import java.beans.*; |
| import java.io.*; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.TooManyListenersException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import javax.swing.plaf.ActionMapUIResource; |
| import javax.swing.plaf.ComponentUI; |
| import javax.swing.plaf.UIResource; |
| import javax.swing.plaf.TreeUI; |
| import javax.swing.tree.*; |
| import javax.swing.text.Position; |
| import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag; |
| import sun.swing.SwingUtilities2; |
| |
| import sun.swing.DefaultLookup; |
| import sun.swing.UIAction; |
| |
| /** |
| * The basic L&F for a hierarchical data structure. |
| * <p> |
| * |
| * @author Scott Violet |
| * @author Shannon Hickey (drag and drop) |
| */ |
| |
| public class BasicTreeUI extends TreeUI |
| { |
| private static final 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 <code>null</code> no hash marks |
| * will be drawn. |
| */ |
| private Color hashColor; |
| |
| /** Distance between left margin and where vertical dashes will be |
| * drawn. */ |
| protected int leftChildIndent; |
| /** Distance to add to leftChildIndent to determine where cell |
| * contents will be drawn. */ |
| protected int rightChildIndent; |
| /** Total distance that will be indented. The sum of leftChildIndent |
| * and rightChildIndent. */ |
| protected int totalChildIndent; |
| |
| /** Minimum preferred size. */ |
| protected Dimension preferredMinSize; |
| |
| /** Index of the row that was last selected. */ |
| protected int lastSelectedRow; |
| |
| /** Component that we're going to be drawing into. */ |
| protected JTree tree; |
| |
| /** Renderer that is being used to do the actual cell drawing. */ |
| transient protected TreeCellRenderer currentCellRenderer; |
| |
| /** Set to true if the renderer that is currently in the tree was |
| * created by this instance. */ |
| protected boolean createdRenderer; |
| |
| /** Editor for the tree. */ |
| transient protected TreeCellEditor cellEditor; |
| |
| /** Set to true if editor that is currently in the tree was |
| * created by this instance. */ |
| protected boolean createdCellEditor; |
| |
| /** Set to false when editing and shouldSelectCell() returns true meaning |
| * the node should be selected before editing, used in completeEditing. */ |
| protected boolean stopEditingInCompleteEditing; |
| |
| /** Used to paint the TreeCellRenderer. */ |
| protected CellRendererPane rendererPane; |
| |
| /** Size needed to completely display all the nodes. */ |
| protected Dimension preferredSize; |
| |
| /** Is the preferredSize valid? */ |
| protected boolean validCachedPreferredSize; |
| |
| /** Object responsible for handling sizing and expanded issues. */ |
| // WARNING: Be careful with the bounds held by treeState. They are |
| // always in terms of left-to-right. They get mapped to right-to-left |
| // by the various methods of this class. |
| protected AbstractLayoutCache treeState; |
| |
| |
| /** Used for minimizing the drawing of vertical lines. */ |
| protected Hashtable<TreePath,Boolean> drawingCache; |
| |
| /** True if doing optimizations for a largeModel. Subclasses that |
| * don't support this may wish to override createLayoutCache to not |
| * return a FixedHeightLayoutCache instance. */ |
| protected boolean largeModel; |
| |
| /** Reponsible for telling the TreeState the size needed for a node. */ |
| protected AbstractLayoutCache.NodeDimensions nodeDimensions; |
| |
| /** Used to determine what to display. */ |
| protected TreeModel treeModel; |
| |
| /** Model maintaing the selection. */ |
| protected TreeSelectionModel treeSelectionModel; |
| |
| /** How much the depth should be offset to properly calculate |
| * x locations. This is based on whether or not the root is visible, |
| * and if the root handles are visible. */ |
| protected int depthOffset; |
| |
| // Following 4 ivars are only valid when editing. |
| |
| /** When editing, this will be the Component that is doing the actual |
| * editing. */ |
| protected Component editingComponent; |
| |
| /** Path that is being edited. */ |
| protected TreePath editingPath; |
| |
| /** Row that is being edited. Should only be referenced if |
| * editingComponent is not null. */ |
| protected int editingRow; |
| |
| /** Set to true if the editor has a different size than the renderer. */ |
| protected boolean editorHasDifferentSize; |
| |
| /** Row correspondin to lead path. */ |
| private int leadRow; |
| /** If true, the property change event for LEAD_SELECTION_PATH_PROPERTY, |
| * or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint. */ |
| private boolean ignoreLAChange; |
| |
| /** Indicates the orientation. */ |
| private boolean leftToRight; |
| |
| // Cached listeners |
| private PropertyChangeListener propertyChangeListener; |
| private PropertyChangeListener selectionModelPropertyChangeListener; |
| private MouseListener mouseListener; |
| private FocusListener focusListener; |
| private KeyListener keyListener; |
| /** Used for large models, listens for moved/resized events and |
| * updates the validCachedPreferredSize bit accordingly. */ |
| private ComponentListener componentListener; |
| /** Listens for CellEditor events. */ |
| private CellEditorListener cellEditorListener; |
| /** Updates the display when the selection changes. */ |
| private TreeSelectionListener treeSelectionListener; |
| /** Is responsible for updating the display based on model events. */ |
| private TreeModelListener treeModelListener; |
| /** Updates the treestate as the nodes expand. */ |
| private TreeExpansionListener treeExpansionListener; |
| |
| /** UI property indicating whether to paint lines */ |
| private boolean paintLines = true; |
| |
| /** UI property for painting dashed lines */ |
| private boolean lineTypeDashed; |
| |
| /** |
| * The time factor to treate the series of typed alphanumeric key |
| * as prefix for first letter navigation. |
| */ |
| private long timeFactor = 1000L; |
| |
| private Handler handler; |
| |
| /** |
| * A temporary variable for communication between startEditingOnRelease |
| * and startEditing. |
| */ |
| private MouseEvent releaseEvent; |
| |
| public static ComponentUI createUI(JComponent x) { |
| return new BasicTreeUI(); |
| } |
| |
| |
| static void loadActionMap(LazyActionMap map) { |
| map.put(new Actions(Actions.SELECT_PREVIOUS)); |
| map.put(new Actions(Actions.SELECT_PREVIOUS_CHANGE_LEAD)); |
| map.put(new Actions(Actions.SELECT_PREVIOUS_EXTEND_SELECTION)); |
| |
| map.put(new Actions(Actions.SELECT_NEXT)); |
| map.put(new Actions(Actions.SELECT_NEXT_CHANGE_LEAD)); |
| map.put(new Actions(Actions.SELECT_NEXT_EXTEND_SELECTION)); |
| |
| map.put(new Actions(Actions.SELECT_CHILD)); |
| map.put(new Actions(Actions.SELECT_CHILD_CHANGE_LEAD)); |
| |
| map.put(new Actions(Actions.SELECT_PARENT)); |
| map.put(new Actions(Actions.SELECT_PARENT_CHANGE_LEAD)); |
| |
| map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION)); |
| map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD)); |
| map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION)); |
| |
| map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION)); |
| map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION)); |
| map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD)); |
| |
| map.put(new Actions(Actions.SELECT_FIRST)); |
| map.put(new Actions(Actions.SELECT_FIRST_CHANGE_LEAD)); |
| map.put(new Actions(Actions.SELECT_FIRST_EXTEND_SELECTION)); |
| |
| map.put(new Actions(Actions.SELECT_LAST)); |
| map.put(new Actions(Actions.SELECT_LAST_CHANGE_LEAD)); |
| map.put(new Actions(Actions.SELECT_LAST_EXTEND_SELECTION)); |
| |
| map.put(new Actions(Actions.TOGGLE)); |
| |
| map.put(new Actions(Actions.CANCEL_EDITING)); |
| |
| map.put(new Actions(Actions.START_EDITING)); |
| |
| map.put(new Actions(Actions.SELECT_ALL)); |
| |
| map.put(new Actions(Actions.CLEAR_SELECTION)); |
| |
| map.put(new Actions(Actions.SCROLL_LEFT)); |
| map.put(new Actions(Actions.SCROLL_RIGHT)); |
| |
| map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION)); |
| map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION)); |
| |
| map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_LEAD)); |
| map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_LEAD)); |
| |
| map.put(new Actions(Actions.EXPAND)); |
| map.put(new Actions(Actions.COLLAPSE)); |
| map.put(new Actions(Actions.MOVE_SELECTION_TO_PARENT)); |
| |
| map.put(new Actions(Actions.ADD_TO_SELECTION)); |
| map.put(new Actions(Actions.TOGGLE_AND_ANCHOR)); |
| map.put(new Actions(Actions.EXTEND_TO)); |
| map.put(new Actions(Actions.MOVE_SELECTION_TO)); |
| |
| map.put(TransferHandler.getCutAction()); |
| map.put(TransferHandler.getCopyAction()); |
| map.put(TransferHandler.getPasteAction()); |
| } |
| |
| |
| public BasicTreeUI() { |
| super(); |
| } |
| |
| protected Color getHashColor() { |
| return hashColor; |
| } |
| |
| protected void setHashColor(Color color) { |
| hashColor = color; |
| } |
| |
| public void setLeftChildIndent(int newAmount) { |
| leftChildIndent = newAmount; |
| totalChildIndent = leftChildIndent + rightChildIndent; |
| if(treeState != null) |
| treeState.invalidateSizes(); |
| updateSize(); |
| } |
| |
| public int getLeftChildIndent() { |
| return leftChildIndent; |
| } |
| |
| public void setRightChildIndent(int newAmount) { |
| rightChildIndent = newAmount; |
| totalChildIndent = leftChildIndent + rightChildIndent; |
| if(treeState != null) |
| treeState.invalidateSizes(); |
| updateSize(); |
| } |
| |
| public int getRightChildIndent() { |
| return rightChildIndent; |
| } |
| |
| public void setExpandedIcon(Icon newG) { |
| expandedIcon = newG; |
| } |
| |
| public Icon getExpandedIcon() { |
| return expandedIcon; |
| } |
| |
| public void setCollapsedIcon(Icon newG) { |
| collapsedIcon = newG; |
| } |
| |
| public Icon getCollapsedIcon() { |
| return collapsedIcon; |
| } |
| |
| // |
| // Methods for configuring the behavior of the tree. None of them |
| // push the value to the JTree instance. You should really only |
| // call these methods on the JTree. |
| // |
| |
| /** |
| * Updates the componentListener, if necessary. |
| */ |
| protected void setLargeModel(boolean largeModel) { |
| if(getRowHeight() < 1) |
| largeModel = false; |
| if(this.largeModel != largeModel) { |
| completeEditing(); |
| this.largeModel = largeModel; |
| treeState = createLayoutCache(); |
| configureLayoutCache(); |
| updateLayoutCacheExpandedNodesIfNecessary(); |
| updateSize(); |
| } |
| } |
| |
| protected boolean isLargeModel() { |
| return largeModel; |
| } |
| |
| /** |
| * Sets the row height, this is forwarded to the treeState. |
| */ |
| protected void setRowHeight(int rowHeight) { |
| completeEditing(); |
| if(treeState != null) { |
| setLargeModel(tree.isLargeModel()); |
| treeState.setRowHeight(rowHeight); |
| updateSize(); |
| } |
| } |
| |
| protected int getRowHeight() { |
| return (tree == null) ? -1 : tree.getRowHeight(); |
| } |
| |
| /** |
| * Sets the TreeCellRenderer to <code>tcr</code>. This invokes |
| * <code>updateRenderer</code>. |
| */ |
| protected void setCellRenderer(TreeCellRenderer tcr) { |
| completeEditing(); |
| updateRenderer(); |
| if(treeState != null) { |
| treeState.invalidateSizes(); |
| updateSize(); |
| } |
| } |
| |
| /** |
| * Return currentCellRenderer, which will either be the trees |
| * renderer, or defaultCellRenderer, which ever wasn't null. |
| */ |
| protected TreeCellRenderer getCellRenderer() { |
| return currentCellRenderer; |
| } |
| |
| /** |
| * Sets the TreeModel. |
| */ |
| protected void setModel(TreeModel model) { |
| completeEditing(); |
| if(treeModel != null && treeModelListener != null) |
| treeModel.removeTreeModelListener(treeModelListener); |
| treeModel = model; |
| if(treeModel != null) { |
| if(treeModelListener != null) |
| treeModel.addTreeModelListener(treeModelListener); |
| } |
| if(treeState != null) { |
| treeState.setModel(model); |
| updateLayoutCacheExpandedNodesIfNecessary(); |
| updateSize(); |
| } |
| } |
| |
| protected TreeModel getModel() { |
| return treeModel; |
| } |
| |
| /** |
| * Sets the root to being visible. |
| */ |
| protected void setRootVisible(boolean newValue) { |
| completeEditing(); |
| updateDepthOffset(); |
| if(treeState != null) { |
| treeState.setRootVisible(newValue); |
| treeState.invalidateSizes(); |
| updateSize(); |
| } |
| } |
| |
| protected boolean isRootVisible() { |
| return (tree != null) ? tree.isRootVisible() : false; |
| } |
| |
| /** |
| * Determines whether the node handles are to be displayed. |
| */ |
| protected void setShowsRootHandles(boolean newValue) { |
| completeEditing(); |
| updateDepthOffset(); |
| if(treeState != null) { |
| treeState.invalidateSizes(); |
| updateSize(); |
| } |
| } |
| |
| protected boolean getShowsRootHandles() { |
| return (tree != null) ? tree.getShowsRootHandles() : false; |
| } |
| |
| /** |
| * Sets the cell editor. |
| */ |
| protected void setCellEditor(TreeCellEditor editor) { |
| updateCellEditor(); |
| } |
| |
| protected TreeCellEditor getCellEditor() { |
| return (tree != null) ? tree.getCellEditor() : null; |
| } |
| |
| /** |
| * Configures the receiver to allow, or not allow, editing. |
| */ |
| protected void setEditable(boolean newValue) { |
| updateCellEditor(); |
| } |
| |
| protected boolean isEditable() { |
| return (tree != null) ? tree.isEditable() : false; |
| } |
| |
| /** |
| * Resets the selection model. The appropriate listener are installed |
| * on the model. |
| */ |
| protected void setSelectionModel(TreeSelectionModel newLSM) { |
| completeEditing(); |
| if(selectionModelPropertyChangeListener != null && |
| treeSelectionModel != null) |
| treeSelectionModel.removePropertyChangeListener |
| (selectionModelPropertyChangeListener); |
| if(treeSelectionListener != null && treeSelectionModel != null) |
| treeSelectionModel.removeTreeSelectionListener |
| (treeSelectionListener); |
| treeSelectionModel = newLSM; |
| if(treeSelectionModel != null) { |
| if(selectionModelPropertyChangeListener != null) |
| treeSelectionModel.addPropertyChangeListener |
| (selectionModelPropertyChangeListener); |
| if(treeSelectionListener != null) |
| treeSelectionModel.addTreeSelectionListener |
| (treeSelectionListener); |
| if(treeState != null) |
| treeState.setSelectionModel(treeSelectionModel); |
| } |
| else if(treeState != null) |
| treeState.setSelectionModel(null); |
| if(tree != null) |
| tree.repaint(); |
| } |
| |
| protected TreeSelectionModel getSelectionModel() { |
| return treeSelectionModel; |
| } |
| |
| // |
| // TreeUI methods |
| // |
| |
| /** |
| * Returns the Rectangle enclosing the label portion that the |
| * last item in path will be drawn into. Will return null if |
| * any component in path is currently valid. |
| */ |
| public Rectangle getPathBounds(JTree tree, TreePath path) { |
| if(tree != null && treeState != null) { |
| return getPathBounds(path, tree.getInsets(), new Rectangle()); |
| } |
| return null; |
| } |
| |
| private Rectangle getPathBounds(TreePath path, Insets insets, |
| Rectangle bounds) { |
| bounds = treeState.getBounds(path, bounds); |
| if (bounds != null) { |
| if (leftToRight) { |
| bounds.x += insets.left; |
| } else { |
| bounds.x = tree.getWidth() - (bounds.x + bounds.width) - |
| insets.right; |
| } |
| bounds.y += insets.top; |
| } |
| return bounds; |
| } |
| |
| /** |
| * Returns the path for passed in row. If row is not visible |
| * null is returned. |
| */ |
| public TreePath getPathForRow(JTree tree, int row) { |
| return (treeState != null) ? treeState.getPathForRow(row) : null; |
| } |
| |
| /** |
| * Returns the row that the last item identified in path is visible |
| * at. Will return -1 if any of the elements in path are not |
| * currently visible. |
| */ |
| public int getRowForPath(JTree tree, TreePath path) { |
| return (treeState != null) ? treeState.getRowForPath(path) : -1; |
| } |
| |
| /** |
| * Returns the number of rows that are being displayed. |
| */ |
| public int getRowCount(JTree tree) { |
| return (treeState != null) ? treeState.getRowCount() : 0; |
| } |
| |
| /** |
| * Returns the path to the node that is closest to x,y. If |
| * there is nothing currently visible this will return null, otherwise |
| * it'll always return a valid path. If you need to test if the |
| * returned object is exactly at x, y you should get the bounds for |
| * the returned path and test x, y against that. |
| */ |
| public TreePath getClosestPathForLocation(JTree tree, int x, int y) { |
| if(tree != null && treeState != null) { |
| // TreeState doesn't care about the x location, hence it isn't |
| // adjusted |
| y -= tree.getInsets().top; |
| return treeState.getPathClosestTo(x, y); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns true if the tree is being edited. The item that is being |
| * edited can be returned by getEditingPath(). |
| */ |
| public boolean isEditing(JTree tree) { |
| return (editingComponent != null); |
| } |
| |
| /** |
| * Stops the current editing session. This has no effect if the |
| * tree isn't being edited. Returns true if the editor allows the |
| * editing session to stop. |
| */ |
| public boolean stopEditing(JTree tree) { |
| if(editingComponent != null && cellEditor.stopCellEditing()) { |
| completeEditing(false, false, true); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Cancels the current editing session. |
| */ |
| public void cancelEditing(JTree tree) { |
| if(editingComponent != null) { |
| completeEditing(false, true, false); |
| } |
| } |
| |
| /** |
| * Selects the last item in path and tries to edit it. Editing will |
| * fail if the CellEditor won't allow it for the selected item. |
| */ |
| public void startEditingAtPath(JTree tree, TreePath path) { |
| tree.scrollPathToVisible(path); |
| if(path != null && tree.isVisible(path)) |
| startEditing(path, null); |
| } |
| |
| /** |
| * Returns the path to the element that is being edited. |
| */ |
| public TreePath getEditingPath(JTree tree) { |
| return editingPath; |
| } |
| |
| // |
| // Install methods |
| // |
| |
| public void installUI(JComponent c) { |
| if ( c == null ) { |
| throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" ); |
| } |
| |
| tree = (JTree)c; |
| |
| prepareForUIInstall(); |
| |
| // Boilerplate install block |
| installDefaults(); |
| installKeyboardActions(); |
| installComponents(); |
| installListeners(); |
| |
| completeUIInstall(); |
| } |
| |
| /** |
| * Invoked after the <code>tree</code> instance variable has been |
| * set, but before any defaults/listeners have been installed. |
| */ |
| protected void prepareForUIInstall() { |
| drawingCache = new Hashtable<TreePath,Boolean>(7); |
| |
| // Data member initializations |
| leftToRight = BasicGraphicsUtils.isLeftToRight(tree); |
| stopEditingInCompleteEditing = true; |
| lastSelectedRow = -1; |
| leadRow = -1; |
| preferredSize = new Dimension(); |
| |
| largeModel = tree.isLargeModel(); |
| if(getRowHeight() <= 0) |
| largeModel = false; |
| setModel(tree.getModel()); |
| } |
| |
| /** |
| * Invoked from installUI after all the defaults/listeners have been |
| * installed. |
| */ |
| protected void completeUIInstall() { |
| // Custom install code |
| |
| this.setShowsRootHandles(tree.getShowsRootHandles()); |
| |
| updateRenderer(); |
| |
| updateDepthOffset(); |
| |
| setSelectionModel(tree.getSelectionModel()); |
| |
| // Create, if necessary, the TreeState instance. |
| treeState = createLayoutCache(); |
| configureLayoutCache(); |
| |
| updateSize(); |
| } |
| |
| protected void installDefaults() { |
| if(tree.getBackground() == null || |
| tree.getBackground() instanceof UIResource) { |
| tree.setBackground(UIManager.getColor("Tree.background")); |
| } |
| if(getHashColor() == null || getHashColor() instanceof UIResource) { |
| setHashColor(UIManager.getColor("Tree.hash")); |
| } |
| if (tree.getFont() == null || tree.getFont() instanceof UIResource) |
| tree.setFont( UIManager.getFont("Tree.font") ); |
| // JTree's original row height is 16. To correctly display the |
| // contents on Linux we should have set it to 18, Windows 19 and |
| // Solaris 20. As these values vary so much it's too hard to |
| // be backward compatable and try to update the row height, we're |
| // therefor NOT going to adjust the row height based on font. If the |
| // developer changes the font, it's there responsibility to update |
| // the row height. |
| |
| setExpandedIcon( (Icon)UIManager.get( "Tree.expandedIcon" ) ); |
| setCollapsedIcon( (Icon)UIManager.get( "Tree.collapsedIcon" ) ); |
| |
| setLeftChildIndent(((Integer)UIManager.get("Tree.leftChildIndent")). |
| intValue()); |
| setRightChildIndent(((Integer)UIManager.get("Tree.rightChildIndent")). |
| intValue()); |
| |
| LookAndFeel.installProperty(tree, "rowHeight", |
| UIManager.get("Tree.rowHeight")); |
| |
| largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0); |
| |
| Object scrollsOnExpand = UIManager.get("Tree.scrollsOnExpand"); |
| if (scrollsOnExpand != null) { |
| LookAndFeel.installProperty(tree, "scrollsOnExpand", scrollsOnExpand); |
| } |
| |
| paintLines = UIManager.getBoolean("Tree.paintLines"); |
| lineTypeDashed = UIManager.getBoolean("Tree.lineTypeDashed"); |
| |
| Long l = (Long)UIManager.get("Tree.timeFactor"); |
| timeFactor = (l!=null) ? l.longValue() : 1000L; |
| |
| Object showsRootHandles = UIManager.get("Tree.showsRootHandles"); |
| if (showsRootHandles != null) { |
| LookAndFeel.installProperty(tree, |
| JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles); |
| } |
| } |
| |
| protected void installListeners() { |
| if ( (propertyChangeListener = createPropertyChangeListener()) |
| != null ) { |
| tree.addPropertyChangeListener(propertyChangeListener); |
| } |
| if ( (mouseListener = createMouseListener()) != null ) { |
| tree.addMouseListener(mouseListener); |
| if (mouseListener instanceof MouseMotionListener) { |
| tree.addMouseMotionListener((MouseMotionListener)mouseListener); |
| } |
| } |
| if ((focusListener = createFocusListener()) != null ) { |
| tree.addFocusListener(focusListener); |
| } |
| if ((keyListener = createKeyListener()) != null) { |
| tree.addKeyListener(keyListener); |
| } |
| if((treeExpansionListener = createTreeExpansionListener()) != null) { |
| tree.addTreeExpansionListener(treeExpansionListener); |
| } |
| if((treeModelListener = createTreeModelListener()) != null && |
| treeModel != null) { |
| treeModel.addTreeModelListener(treeModelListener); |
| } |
| if((selectionModelPropertyChangeListener = |
| createSelectionModelPropertyChangeListener()) != null && |
| treeSelectionModel != null) { |
| treeSelectionModel.addPropertyChangeListener |
| (selectionModelPropertyChangeListener); |
| } |
| if((treeSelectionListener = createTreeSelectionListener()) != null && |
| treeSelectionModel != null) { |
| treeSelectionModel.addTreeSelectionListener(treeSelectionListener); |
| } |
| |
| TransferHandler th = tree.getTransferHandler(); |
| if (th == null || th instanceof UIResource) { |
| tree.setTransferHandler(defaultTransferHandler); |
| // default TransferHandler doesn't support drop |
| // so we don't want drop handling |
| if (tree.getDropTarget() instanceof UIResource) { |
| tree.setDropTarget(null); |
| } |
| } |
| |
| LookAndFeel.installProperty(tree, "opaque", Boolean.TRUE); |
| } |
| |
| protected void installKeyboardActions() { |
| InputMap km = getInputMap(JComponent. |
| WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); |
| |
| SwingUtilities.replaceUIInputMap(tree, JComponent. |
| WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, |
| km); |
| km = getInputMap(JComponent.WHEN_FOCUSED); |
| SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, km); |
| |
| LazyActionMap.installLazyActionMap(tree, BasicTreeUI.class, |
| "Tree.actionMap"); |
| } |
| |
| InputMap getInputMap(int condition) { |
| if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { |
| return (InputMap)DefaultLookup.get(tree, this, |
| "Tree.ancestorInputMap"); |
| } |
| else if (condition == JComponent.WHEN_FOCUSED) { |
| InputMap keyMap = (InputMap)DefaultLookup.get(tree, this, |
| "Tree.focusInputMap"); |
| InputMap rtlKeyMap; |
| |
| if (tree.getComponentOrientation().isLeftToRight() || |
| ((rtlKeyMap = (InputMap)DefaultLookup.get(tree, this, |
| "Tree.focusInputMap.RightToLeft")) == null)) { |
| return keyMap; |
| } else { |
| rtlKeyMap.setParent(keyMap); |
| return rtlKeyMap; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Intalls the subcomponents of the tree, which is the renderer pane. |
| */ |
| protected void installComponents() { |
| if ((rendererPane = createCellRendererPane()) != null) { |
| tree.add( rendererPane ); |
| } |
| } |
| |
| // |
| // Create methods. |
| // |
| |
| /** |
| * Creates an instance of NodeDimensions that is able to determine |
| * the size of a given node in the tree. |
| */ |
| protected AbstractLayoutCache.NodeDimensions createNodeDimensions() { |
| return new NodeDimensionsHandler(); |
| } |
| |
| /** |
| * Creates a listener that is responsible that updates the UI based on |
| * how the tree changes. |
| */ |
| protected PropertyChangeListener createPropertyChangeListener() { |
| return getHandler(); |
| } |
| |
| private Handler getHandler() { |
| if (handler == null) { |
| handler = new Handler(); |
| } |
| return handler; |
| } |
| |
| /** |
| * Creates the listener responsible for updating the selection based on |
| * mouse events. |
| */ |
| protected MouseListener createMouseListener() { |
| return getHandler(); |
| } |
| |
| /** |
| * Creates a listener that is responsible for updating the display |
| * when focus is lost/gained. |
| */ |
| protected FocusListener createFocusListener() { |
| return getHandler(); |
| } |
| |
| /** |
| * Creates the listener reponsible for getting key events from |
| * the tree. |
| */ |
| protected KeyListener createKeyListener() { |
| return getHandler(); |
| } |
| |
| /** |
| * Creates the listener responsible for getting property change |
| * events from the selection model. |
| */ |
| protected PropertyChangeListener createSelectionModelPropertyChangeListener() { |
| return getHandler(); |
| } |
| |
| /** |
| * Creates the listener that updates the display based on selection change |
| * methods. |
| */ |
| protected TreeSelectionListener createTreeSelectionListener() { |
| return getHandler(); |
| } |
| |
| /** |
| * Creates a listener to handle events from the current editor. |
| */ |
| protected CellEditorListener createCellEditorListener() { |
| return getHandler(); |
| } |
| |
| /** |
| * Creates and returns a new ComponentHandler. This is used for |
| * the large model to mark the validCachedPreferredSize as invalid |
| * when the component moves. |
| */ |
| protected ComponentListener createComponentListener() { |
| return new ComponentHandler(); |
| } |
| |
| /** |
| * Creates and returns the object responsible for updating the treestate |
| * when nodes expanded state changes. |
| */ |
| protected TreeExpansionListener createTreeExpansionListener() { |
| return getHandler(); |
| } |
| |
| /** |
| * Creates the object responsible for managing what is expanded, as |
| * well as the size of nodes. |
| */ |
| protected AbstractLayoutCache createLayoutCache() { |
| if(isLargeModel() && getRowHeight() > 0) { |
| return new FixedHeightLayoutCache(); |
| } |
| return new VariableHeightLayoutCache(); |
| } |
| |
| /** |
| * Returns the renderer pane that renderer components are placed in. |
| */ |
| protected CellRendererPane createCellRendererPane() { |
| return new CellRendererPane(); |
| } |
| |
| /** |
| * Creates a default cell editor. |
| */ |
| protected TreeCellEditor createDefaultCellEditor() { |
| if(currentCellRenderer != null && |
| (currentCellRenderer instanceof DefaultTreeCellRenderer)) { |
| DefaultTreeCellEditor editor = new DefaultTreeCellEditor |
| (tree, (DefaultTreeCellRenderer)currentCellRenderer); |
| |
| return editor; |
| } |
| return new DefaultTreeCellEditor(tree, null); |
| } |
| |
| /** |
| * Returns the default cell renderer that is used to do the |
| * stamping of each node. |
| */ |
| protected TreeCellRenderer createDefaultCellRenderer() { |
| return new DefaultTreeCellRenderer(); |
| } |
| |
| /** |
| * Returns a listener that can update the tree when the model changes. |
| */ |
| protected TreeModelListener createTreeModelListener() { |
| return getHandler(); |
| } |
| |
| // |
| // Uninstall methods |
| // |
| |
| public void uninstallUI(JComponent c) { |
| completeEditing(); |
| |
| prepareForUIUninstall(); |
| |
| uninstallDefaults(); |
| uninstallListeners(); |
| uninstallKeyboardActions(); |
| uninstallComponents(); |
| |
| completeUIUninstall(); |
| } |
| |
| protected void prepareForUIUninstall() { |
| } |
| |
| protected void completeUIUninstall() { |
| if(createdRenderer) { |
| tree.setCellRenderer(null); |
| } |
| if(createdCellEditor) { |
| tree.setCellEditor(null); |
| } |
| cellEditor = null; |
| currentCellRenderer = null; |
| rendererPane = null; |
| componentListener = null; |
| propertyChangeListener = null; |
| mouseListener = null; |
| focusListener = null; |
| keyListener = null; |
| setSelectionModel(null); |
| treeState = null; |
| drawingCache = null; |
| selectionModelPropertyChangeListener = null; |
| tree = null; |
| treeModel = null; |
| treeSelectionModel = null; |
| treeSelectionListener = null; |
| treeExpansionListener = null; |
| } |
| |
| protected void uninstallDefaults() { |
| if (tree.getTransferHandler() instanceof UIResource) { |
| tree.setTransferHandler(null); |
| } |
| } |
| |
| protected void uninstallListeners() { |
| if(componentListener != null) { |
| tree.removeComponentListener(componentListener); |
| } |
| if (propertyChangeListener != null) { |
| tree.removePropertyChangeListener(propertyChangeListener); |
| } |
| if (mouseListener != null) { |
| tree.removeMouseListener(mouseListener); |
| if (mouseListener instanceof MouseMotionListener) { |
| tree.removeMouseMotionListener((MouseMotionListener)mouseListener); |
| } |
| } |
| if (focusListener != null) { |
| tree.removeFocusListener(focusListener); |
| } |
| if (keyListener != null) { |
| tree.removeKeyListener(keyListener); |
| } |
| if(treeExpansionListener != null) { |
| tree.removeTreeExpansionListener(treeExpansionListener); |
| } |
| if(treeModel != null && treeModelListener != null) { |
| treeModel.removeTreeModelListener(treeModelListener); |
| } |
| if(selectionModelPropertyChangeListener != null && |
| treeSelectionModel != null) { |
| treeSelectionModel.removePropertyChangeListener |
| (selectionModelPropertyChangeListener); |
| } |
| if(treeSelectionListener != null && treeSelectionModel != null) { |
| treeSelectionModel.removeTreeSelectionListener |
| (treeSelectionListener); |
| } |
| handler = null; |
| } |
| |
| protected void uninstallKeyboardActions() { |
| SwingUtilities.replaceUIActionMap(tree, null); |
| SwingUtilities.replaceUIInputMap(tree, JComponent. |
| WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, |
| null); |
| SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, null); |
| } |
| |
| /** |
| * Uninstalls the renderer pane. |
| */ |
| protected void uninstallComponents() { |
| if(rendererPane != null) { |
| tree.remove(rendererPane); |
| } |
| } |
| |
| /** |
| * Recomputes the right margin, and invalidates any tree states |
| */ |
| private void redoTheLayout() { |
| if (treeState != null) { |
| treeState.invalidateSizes(); |
| } |
| } |
| |
| /** |
| * Returns the baseline. |
| * |
| * @throws NullPointerException {@inheritDoc} |
| * @throws IllegalArgumentException {@inheritDoc} |
| * @see javax.swing.JComponent#getBaseline(int, int) |
| * @since 1.6 |
| */ |
| public int getBaseline(JComponent c, int width, int height) { |
| super.getBaseline(c, width, height); |
| UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults(); |
| Component renderer = (Component)lafDefaults.get( |
| BASELINE_COMPONENT_KEY); |
| if (renderer == null) { |
| TreeCellRenderer tcr = createDefaultCellRenderer(); |
| renderer = tcr.getTreeCellRendererComponent( |
| tree, "a", false, false, false, -1, false); |
| lafDefaults.put(BASELINE_COMPONENT_KEY, renderer); |
| } |
| int rowHeight = tree.getRowHeight(); |
| int baseline; |
| if (rowHeight > 0) { |
| baseline = renderer.getBaseline(Integer.MAX_VALUE, rowHeight); |
| } |
| else { |
| Dimension pref = renderer.getPreferredSize(); |
| baseline = renderer.getBaseline(pref.width, pref.height); |
| } |
| return baseline + tree.getInsets().top; |
| } |
| |
| /** |
| * Returns an enum indicating how the baseline of the component |
| * changes as the size changes. |
| * |
| * @throws NullPointerException {@inheritDoc} |
| * @see javax.swing.JComponent#getBaseline(int, int) |
| * @since 1.6 |
| */ |
| public Component.BaselineResizeBehavior getBaselineResizeBehavior( |
| JComponent c) { |
| super.getBaselineResizeBehavior(c); |
| return Component.BaselineResizeBehavior.CONSTANT_ASCENT; |
| } |
| |
| // |
| // Painting routines. |
| // |
| |
| public void paint(Graphics g, JComponent c) { |
| if (tree != c) { |
| throw new InternalError("incorrect component"); |
| } |
| |
| // Should never happen if installed for a UI |
| if(treeState == null) { |
| return; |
| } |
| |
| Rectangle paintBounds = g.getClipBounds(); |
| Insets insets = tree.getInsets(); |
| TreePath initialPath = getClosestPathForLocation |
| (tree, 0, paintBounds.y); |
| Enumeration paintingEnumerator = treeState.getVisiblePathsFrom |
| (initialPath); |
| int row = treeState.getRowForPath(initialPath); |
| int endY = paintBounds.y + paintBounds.height; |
| |
| drawingCache.clear(); |
| |
| if(initialPath != null && paintingEnumerator != null) { |
| TreePath parentPath = initialPath; |
| |
| // Draw the lines, knobs, and rows |
| |
| // Find each parent and have them draw a line to their last child |
| parentPath = parentPath.getParentPath(); |
| while(parentPath != null) { |
| paintVerticalPartOfLeg(g, paintBounds, insets, parentPath); |
| drawingCache.put(parentPath, Boolean.TRUE); |
| parentPath = parentPath.getParentPath(); |
| } |
| |
| boolean done = false; |
| // Information for the node being rendered. |
| boolean isExpanded; |
| boolean hasBeenExpanded; |
| boolean isLeaf; |
| Rectangle boundsBuffer = new Rectangle(); |
| Rectangle bounds; |
| TreePath path; |
| boolean rootVisible = isRootVisible(); |
| |
| while(!done && paintingEnumerator.hasMoreElements()) { |
| path = (TreePath)paintingEnumerator.nextElement(); |
| if(path != null) { |
| isLeaf = treeModel.isLeaf(path.getLastPathComponent()); |
| if(isLeaf) |
| isExpanded = hasBeenExpanded = false; |
| else { |
| isExpanded = treeState.getExpandedState(path); |
| hasBeenExpanded = tree.hasBeenExpanded(path); |
| } |
| bounds = getPathBounds(path, insets, boundsBuffer); |
| if(bounds == null) |
| // This will only happen if the model changes out |
| // from under us (usually in another thread). |
| // Swing isn't multithreaded, but I'll put this |
| // check in anyway. |
| return; |
| // See if the vertical line to the parent has been drawn. |
| parentPath = path.getParentPath(); |
| if(parentPath != null) { |
| if(drawingCache.get(parentPath) == null) { |
| paintVerticalPartOfLeg(g, paintBounds, |
| insets, parentPath); |
| drawingCache.put(parentPath, Boolean.TRUE); |
| } |
| paintHorizontalPartOfLeg(g, paintBounds, insets, |
| bounds, path, row, |
| isExpanded, |
| hasBeenExpanded, isLeaf); |
| } |
| else if(rootVisible && row == 0) { |
| paintHorizontalPartOfLeg(g, paintBounds, insets, |
| bounds, path, row, |
| isExpanded, |
| hasBeenExpanded, isLeaf); |
| } |
| if(shouldPaintExpandControl(path, row, isExpanded, |
| hasBeenExpanded, isLeaf)) { |
| paintExpandControl(g, paintBounds, insets, bounds, |
| path, row, isExpanded, |
| hasBeenExpanded, isLeaf); |
| } |
| paintRow(g, paintBounds, insets, bounds, path, |
| row, isExpanded, hasBeenExpanded, isLeaf); |
| if((bounds.y + bounds.height) >= endY) |
| done = true; |
| } |
| else { |
| done = true; |
| } |
| row++; |
| } |
| } |
| |
| paintDropLine(g); |
| |
| // Empty out the renderer pane, allowing renderers to be gc'ed. |
| rendererPane.removeAll(); |
| |
| drawingCache.clear(); |
| } |
| |
| private boolean isDropLine(JTree.DropLocation loc) { |
| return loc != null && loc.getPath() != null && loc.getChildIndex() != -1; |
| } |
| |
| private void paintDropLine(Graphics g) { |
| JTree.DropLocation loc = tree.getDropLocation(); |
| if (!isDropLine(loc)) { |
| return; |
| } |
| |
| Color c = UIManager.getColor("Tree.dropLineColor"); |
| if (c != null) { |
| g.setColor(c); |
| Rectangle rect = getDropLineRect(loc); |
| g.fillRect(rect.x, rect.y, rect.width, rect.height); |
| } |
| } |
| |
| private Rectangle getDropLineRect(JTree.DropLocation loc) { |
| Rectangle rect; |
| TreePath path = loc.getPath(); |
| int index = loc.getChildIndex(); |
| boolean ltr = leftToRight; |
| |
| Insets insets = tree.getInsets(); |
| |
| if (tree.getRowCount() == 0) { |
| rect = new Rectangle(insets.left, |
| insets.top, |
| tree.getWidth() - insets.left - insets.right, |
| 0); |
| } else { |
| TreeModel model = getModel(); |
| Object root = model.getRoot(); |
| |
| if (path.getLastPathComponent() == root |
| && index >= model.getChildCount(root)) { |
| |
| rect = tree.getRowBounds(tree.getRowCount() - 1); |
| rect.y = rect.y + rect.height; |
| Rectangle xRect; |
| |
| if (!tree.isRootVisible()) { |
| xRect = tree.getRowBounds(0); |
| } else if (model.getChildCount(root) == 0){ |
| xRect = tree.getRowBounds(0); |
| xRect.x += totalChildIndent; |
| xRect.width -= totalChildIndent + totalChildIndent; |
| } else { |
| TreePath lastChildPath = path.pathByAddingChild( |
| model.getChild(root, model.getChildCount(root) - 1)); |
| xRect = tree.getPathBounds(lastChildPath); |
| } |
| |
| rect.x = xRect.x; |
| rect.width = xRect.width; |
| } else { |
| rect = tree.getPathBounds(path.pathByAddingChild( |
| model.getChild(path.getLastPathComponent(), index))); |
| } |
| } |
| |
| if (rect.y != 0) { |
| rect.y--; |
| } |
| |
| if (!ltr) { |
| rect.x = rect.x + rect.width - 100; |
| } |
| |
| rect.width = 100; |
| rect.height = 2; |
| |
| return rect; |
| } |
| |
| /** |
| * Paints the horizontal part of the leg. The receiver should |
| * NOT modify <code>clipBounds</code>, or <code>insets</code>.<p> |
| * NOTE: <code>parentRow</code> can be -1 if the root is not visible. |
| */ |
| protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, |
| Insets insets, Rectangle bounds, |
| TreePath path, int row, |
| boolean isExpanded, |
| boolean hasBeenExpanded, boolean |
| isLeaf) { |
| if (!paintLines) { |
| return; |
| } |
| |
| // Don't paint the legs for the root'ish node if the |
| int depth = path.getPathCount() - 1; |
| if((depth == 0 || (depth == 1 && !isRootVisible())) && |
| !getShowsRootHandles()) { |
| return; |
| } |
| |
| int clipLeft = clipBounds.x; |
| int clipRight = clipBounds.x + clipBounds.width; |
| int clipTop = clipBounds.y; |
| int clipBottom = clipBounds.y + clipBounds.height; |
| int lineY = bounds.y + bounds.height / 2; |
| |
| if (leftToRight) { |
| int leftX = bounds.x - getRightChildIndent(); |
| int nodeX = bounds.x - getHorizontalLegBuffer(); |
| |
| if(lineY >= clipTop |
| && lineY < clipBottom |
| && nodeX >= clipLeft |
| && leftX < clipRight |
| && leftX < nodeX) { |
| |
| g.setColor(getHashColor()); |
| paintHorizontalLine(g, tree, lineY, leftX, nodeX - 1); |
| } |
| } else { |
| int nodeX = bounds.x + bounds.width + getHorizontalLegBuffer(); |
| int rightX = bounds.x + bounds.width + getRightChildIndent(); |
| |
| if(lineY >= clipTop |
| && lineY < clipBottom |
| && rightX >= clipLeft |
| && nodeX < clipRight |
| && nodeX < rightX) { |
| |
| g.setColor(getHashColor()); |
| paintHorizontalLine(g, tree, lineY, nodeX, rightX - 1); |
| } |
| } |
| } |
| |
| /** |
| * Paints the vertical part of the leg. The receiver should |
| * NOT modify <code>clipBounds</code>, <code>insets</code>.<p> |
| */ |
| protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, |
| Insets insets, TreePath path) { |
| if (!paintLines) { |
| return; |
| } |
| |
| int depth = path.getPathCount() - 1; |
| if (depth == 0 && !getShowsRootHandles() && !isRootVisible()) { |
| return; |
| } |
| int lineX = getRowX(-1, depth + 1); |
| if (leftToRight) { |
| lineX = lineX - getRightChildIndent() + insets.left; |
| } |
| else { |
| lineX = tree.getWidth() - lineX - insets.right + |
| getRightChildIndent() - 1; |
| } |
| int clipLeft = clipBounds.x; |
| int clipRight = clipBounds.x + (clipBounds.width - 1); |
| |
| if (lineX >= clipLeft && lineX <= clipRight) { |
| int clipTop = clipBounds.y; |
| int clipBottom = clipBounds.y + clipBounds.height; |
| Rectangle parentBounds = getPathBounds(tree, path); |
| Rectangle lastChildBounds = getPathBounds(tree, |
| getLastChildPath(path)); |
| |
| if(lastChildBounds == null) |
| // This shouldn't happen, but if the model is modified |
| // in another thread it is possible for this to happen. |
| // Swing isn't multithreaded, but I'll add this check in |
| // anyway. |
| return; |
| |
| int top; |
| |
| if(parentBounds == null) { |
| top = Math.max(insets.top + getVerticalLegBuffer(), |
| clipTop); |
| } |
| else |
| top = Math.max(parentBounds.y + parentBounds.height + |
| getVerticalLegBuffer(), clipTop); |
| if(depth == 0 && !isRootVisible()) { |
| TreeModel model = getModel(); |
| |
| if(model != null) { |
| Object root = model.getRoot(); |
| |
| if(model.getChildCount(root) > 0) { |
| parentBounds = getPathBounds(tree, path. |
| pathByAddingChild(model.getChild(root, 0))); |
| if(parentBounds != null) |
| top = Math.max(insets.top + getVerticalLegBuffer(), |
| parentBounds.y + |
| parentBounds.height / 2); |
| } |
| } |
| } |
| |
| int bottom = Math.min(lastChildBounds.y + |
| (lastChildBounds.height / 2), clipBottom); |
| |
| if (top <= bottom) { |
| g.setColor(getHashColor()); |
| paintVerticalLine(g, tree, lineX, top, bottom); |
| } |
| } |
| } |
| |
| /** |
| * Paints the expand (toggle) part of a row. The receiver should |
| * NOT modify <code>clipBounds</code>, or <code>insets</code>. |
| */ |
| protected void paintExpandControl(Graphics g, |
| Rectangle clipBounds, Insets insets, |
| Rectangle bounds, TreePath path, |
| int row, boolean isExpanded, |
| boolean hasBeenExpanded, |
| boolean isLeaf) { |
| Object value = path.getLastPathComponent(); |
| |
| // Draw icons if not a leaf and either hasn't been loaded, |
| // or the model child count is > 0. |
| if (!isLeaf && (!hasBeenExpanded || |
| treeModel.getChildCount(value) > 0)) { |
| int middleXOfKnob; |
| if (leftToRight) { |
| middleXOfKnob = bounds.x - getRightChildIndent() + 1; |
| } else { |
| middleXOfKnob = bounds.x + bounds.width + getRightChildIndent() - 1; |
| } |
| int middleYOfKnob = bounds.y + (bounds.height / 2); |
| |
| if (isExpanded) { |
| Icon expandedIcon = getExpandedIcon(); |
| if(expandedIcon != null) |
| drawCentered(tree, g, expandedIcon, middleXOfKnob, |
| middleYOfKnob ); |
| } |
| else { |
| Icon collapsedIcon = getCollapsedIcon(); |
| if(collapsedIcon != null) |
| drawCentered(tree, g, collapsedIcon, middleXOfKnob, |
| middleYOfKnob); |
| } |
| } |
| } |
| |
| /** |
| * Paints the renderer part of a row. The receiver should |
| * NOT modify <code>clipBounds</code>, or <code>insets</code>. |
| */ |
| protected void paintRow(Graphics g, Rectangle clipBounds, |
| Insets insets, Rectangle bounds, TreePath path, |
| int row, boolean isExpanded, |
| boolean hasBeenExpanded, boolean isLeaf) { |
| // Don't paint the renderer if editing this row. |
| if(editingComponent != null && editingRow == row) |
| return; |
| |
| int leadIndex; |
| |
| if(tree.hasFocus()) { |
| leadIndex = getLeadSelectionRow(); |
| } |
| else |
| leadIndex = -1; |
| |
| Component component; |
| |
| component = currentCellRenderer.getTreeCellRendererComponent |
| (tree, path.getLastPathComponent(), |
| tree.isRowSelected(row), isExpanded, isLeaf, row, |
| (leadIndex == row)); |
| |
| rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y, |
| bounds.width, bounds.height, true); |
| } |
| |
| /** |
| * Returns true if the expand (toggle) control should be drawn for |
| * the specified row. |
| */ |
| protected boolean shouldPaintExpandControl(TreePath path, int row, |
| boolean isExpanded, |
| boolean hasBeenExpanded, |
| boolean isLeaf) { |
| if(isLeaf) |
| return false; |
| |
| int depth = path.getPathCount() - 1; |
| |
| if((depth == 0 || (depth == 1 && !isRootVisible())) && |
| !getShowsRootHandles()) |
| return false; |
| return true; |
| } |
| |
| /** |
| * Paints a vertical line. |
| */ |
| protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, |
| int bottom) { |
| if (lineTypeDashed) { |
| drawDashedVerticalLine(g, x, top, bottom); |
| } else { |
| g.drawLine(x, top, x, bottom); |
| } |
| } |
| |
| /** |
| * Paints a horizontal line. |
| */ |
| protected void paintHorizontalLine(Graphics g, JComponent c, int y, |
| int left, int right) { |
| if (lineTypeDashed) { |
| drawDashedHorizontalLine(g, y, left, right); |
| } else { |
| g.drawLine(left, y, right, y); |
| } |
| } |
| |
| /** |
| * The vertical element of legs between nodes starts at the bottom of the |
| * parent node by default. This method makes the leg start below that. |
| */ |
| protected int getVerticalLegBuffer() { |
| return 0; |
| } |
| |
| /** |
| * The horizontal element of legs between nodes starts at the |
| * right of the left-hand side of the child node by default. This |
| * method makes the leg end before that. |
| */ |
| protected int getHorizontalLegBuffer() { |
| return 0; |
| } |
| |
| private int findCenteredX(int x, int iconWidth) { |
| return leftToRight |
| ? x - (int)Math.ceil(iconWidth / 2.0) |
| : x - (int)Math.floor(iconWidth / 2.0); |
| } |
| |
| // |
| // Generic painting methods |
| // |
| |
| // Draws the icon centered at (x,y) |
| protected void drawCentered(Component c, Graphics graphics, Icon icon, |
| int x, int y) { |
| icon.paintIcon(c, graphics, |
| findCenteredX(x, icon.getIconWidth()), |
| y - icon.getIconHeight() / 2); |
| } |
| |
| // This method is slow -- revisit when Java2D is ready. |
| // assumes x1 <= x2 |
| protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2){ |
| // Drawing only even coordinates helps join line segments so they |
| // appear as one line. This can be defeated by translating the |
| // Graphics by an odd amount. |
| x1 += (x1 % 2); |
| |
| for (int x = x1; x <= x2; x+=2) { |
| g.drawLine(x, y, x, y); |
| } |
| } |
| |
| // This method is slow -- revisit when Java2D is ready. |
| // assumes y1 <= y2 |
| protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) { |
| // Drawing only even coordinates helps join line segments so they |
| // appear as one line. This can be defeated by translating the |
| // Graphics by an odd amount. |
| y1 += (y1 % 2); |
| |
| for (int y = y1; y <= y2; y+=2) { |
| g.drawLine(x, y, x, y); |
| } |
| } |
| |
| // |
| // Various local methods |
| // |
| |
| /** |
| * Returns the location, along the x-axis, to render a particular row |
| * at. The return value does not include any Insets specified on the JTree. |
| * This does not check for the validity of the row or depth, it is assumed |
| * to be correct and will not throw an Exception if the row or depth |
| * doesn't match that of the tree. |
| * |
| * @param row Row to return x location for |
| * @param depth Depth of the row |
| * @return amount to indent the given row. |
| * @since 1.5 |
| */ |
| protected int getRowX(int row, int depth) { |
| return totalChildIndent * (depth + depthOffset); |
| } |
| |
| /** |
| * Makes all the nodes that are expanded in JTree expanded in LayoutCache. |
| * This invokes updateExpandedDescendants with the root path. |
| */ |
| protected void updateLayoutCacheExpandedNodes() { |
| if(treeModel != null && treeModel.getRoot() != null) |
| updateExpandedDescendants(new TreePath(treeModel.getRoot())); |
| } |
| |
| private void updateLayoutCacheExpandedNodesIfNecessary() { |
| if (treeModel != null && treeModel.getRoot() != null) { |
| TreePath rootPath = new TreePath(treeModel.getRoot()); |
| if (tree.isExpanded(rootPath)) { |
| updateLayoutCacheExpandedNodes(); |
| } else { |
| treeState.setExpandedState(rootPath, false); |
| } |
| } |
| } |
| |
| /** |
| * Updates the expanded state of all the descendants of <code>path</code> |
| * by getting the expanded descendants from the tree and forwarding |
| * to the tree state. |
| */ |
| protected void updateExpandedDescendants(TreePath path) { |
| completeEditing(); |
| if(treeState != null) { |
| treeState.setExpandedState(path, true); |
| |
| Enumeration descendants = tree.getExpandedDescendants(path); |
| |
| if(descendants != null) { |
| while(descendants.hasMoreElements()) { |
| path = (TreePath)descendants.nextElement(); |
| treeState.setExpandedState(path, true); |
| } |
| } |
| updateLeadRow(); |
| updateSize(); |
| } |
| } |
| |
| /** |
| * Returns a path to the last child of <code>parent</code>. |
| */ |
| protected TreePath getLastChildPath(TreePath parent) { |
| if(treeModel != null) { |
| int childCount = treeModel.getChildCount |
| (parent.getLastPathComponent()); |
| |
| if(childCount > 0) |
| return parent.pathByAddingChild(treeModel.getChild |
| (parent.getLastPathComponent(), childCount - 1)); |
| } |
| return null; |
| } |
| |
| /** |
| * Updates how much each depth should be offset by. |
| */ |
| protected void updateDepthOffset() { |
| if(isRootVisible()) { |
| if(getShowsRootHandles()) |
| depthOffset = 1; |
| else |
| depthOffset = 0; |
| } |
| else if(!getShowsRootHandles()) |
| depthOffset = -1; |
| else |
| depthOffset = 0; |
| } |
| |
| /** |
| * Updates the cellEditor based on the editability of the JTree that |
| * we're contained in. If the tree is editable but doesn't have a |
| * cellEditor, a basic one will be used. |
| */ |
| protected void updateCellEditor() { |
| TreeCellEditor newEditor; |
| |
| completeEditing(); |
| if(tree == null) |
| newEditor = null; |
| else { |
| if(tree.isEditable()) { |
| newEditor = tree.getCellEditor(); |
| if(newEditor == null) { |
| newEditor = createDefaultCellEditor(); |
| if(newEditor != null) { |
| tree.setCellEditor(newEditor); |
| createdCellEditor = true; |
| } |
| } |
| } |
| else |
| newEditor = null; |
| } |
| if(newEditor != cellEditor) { |
| if(cellEditor != null && cellEditorListener != null) |
| cellEditor.removeCellEditorListener(cellEditorListener); |
| cellEditor = newEditor; |
| if(cellEditorListener == null) |
| cellEditorListener = createCellEditorListener(); |
| if(newEditor != null && cellEditorListener != null) |
| newEditor.addCellEditorListener(cellEditorListener); |
| createdCellEditor = false; |
| } |
| } |
| |
| /** |
| * Messaged from the tree we're in when the renderer has changed. |
| */ |
| protected void updateRenderer() { |
| if(tree != null) { |
| TreeCellRenderer newCellRenderer; |
| |
| newCellRenderer = tree.getCellRenderer(); |
| if(newCellRenderer == null) { |
| tree.setCellRenderer(createDefaultCellRenderer()); |
| createdRenderer = true; |
| } |
| else { |
| createdRenderer = false; |
| currentCellRenderer = newCellRenderer; |
| if(createdCellEditor) { |
| tree.setCellEditor(null); |
| } |
| } |
| } |
| else { |
| createdRenderer = false; |
| currentCellRenderer = null; |
| } |
| updateCellEditor(); |
| } |
| |
| /** |
| * Resets the TreeState instance based on the tree we're providing the |
| * look and feel for. |
| */ |
| protected void configureLayoutCache() { |
| if(treeState != null && tree != null) { |
| if(nodeDimensions == null) |
| nodeDimensions = createNodeDimensions(); |
| treeState.setNodeDimensions(nodeDimensions); |
| treeState.setRootVisible(tree.isRootVisible()); |
| treeState.setRowHeight(tree.getRowHeight()); |
| treeState.setSelectionModel(getSelectionModel()); |
| // Only do this if necessary, may loss state if call with |
| // same model as it currently has. |
| if(treeState.getModel() != tree.getModel()) |
| treeState.setModel(tree.getModel()); |
| updateLayoutCacheExpandedNodesIfNecessary(); |
| // Create a listener to update preferred size when bounds |
| // changes, if necessary. |
| if(isLargeModel()) { |
| if(componentListener == null) { |
| componentListener = createComponentListener(); |
| if(componentListener != null) |
| tree.addComponentListener(componentListener); |
| } |
| } |
| else if(componentListener != null) { |
| tree.removeComponentListener(componentListener); |
| componentListener = null; |
| } |
| } |
| else if(componentListener != null) { |
| tree.removeComponentListener(componentListener); |
| componentListener = null; |
| } |
| } |
| |
| /** |
| * Marks the cached size as being invalid, and messages the |
| * tree with <code>treeDidChange</code>. |
| */ |
| protected void updateSize() { |
| validCachedPreferredSize = false; |
| tree.treeDidChange(); |
| } |
| |
| private void updateSize0() { |
| validCachedPreferredSize = false; |
| tree.revalidate(); |
| } |
| |
| /** |
| * Updates the <code>preferredSize</code> instance variable, |
| * which is returned from <code>getPreferredSize()</code>.<p> |
| * For left to right orientations, the size is determined from the |
| * current AbstractLayoutCache. For RTL orientations, the preferred size |
| * becomes the width minus the minimum x position. |
| */ |
| protected void updateCachedPreferredSize() { |
| if(treeState != null) { |
| Insets i = tree.getInsets(); |
| |
| if(isLargeModel()) { |
| Rectangle visRect = tree.getVisibleRect(); |
| |
| if (visRect.x == 0 && visRect.y == 0 && |
| visRect.width == 0 && visRect.height == 0 && |
| tree.getVisibleRowCount() > 0) { |
| // The tree doesn't have a valid bounds yet. Calculate |
| // based on visible row count. |
| visRect.width = 1; |
| visRect.height = tree.getRowHeight() * |
| tree.getVisibleRowCount(); |
| } else { |
| visRect.x -= i.left; |
| visRect.y -= i.top; |
| } |
| preferredSize.width = treeState.getPreferredWidth(visRect); |
| } |
| else { |
| preferredSize.width = treeState.getPreferredWidth(null); |
| } |
| preferredSize.height = treeState.getPreferredHeight(); |
| preferredSize.width += i.left + i.right; |
| preferredSize.height += i.top + i.bottom; |
| } |
| validCachedPreferredSize = true; |
| } |
| |
| /** |
| * Messaged from the VisibleTreeNode after it has been expanded. |
| */ |
| protected void pathWasExpanded(TreePath path) { |
| if(tree != null) { |
| tree.fireTreeExpanded(path); |
| } |
| } |
| |
| /** |
| * Messaged from the VisibleTreeNode after it has collapsed. |
| */ |
| protected void pathWasCollapsed(TreePath path) { |
| if(tree != null) { |
| tree.fireTreeCollapsed(path); |
| } |
| } |
| |
| /** |
| * Ensures that the rows identified by beginRow through endRow are |
| * visible. |
| */ |
| protected void ensureRowsAreVisible(int beginRow, int endRow) { |
| if(tree != null && beginRow >= 0 && endRow < getRowCount(tree)) { |
| boolean scrollVert = DefaultLookup.getBoolean(tree, this, |
| "Tree.scrollsHorizontallyAndVertically", false); |
| if(beginRow == endRow) { |
| Rectangle scrollBounds = getPathBounds(tree, getPathForRow |
| (tree, beginRow)); |
| |
| if(scrollBounds != null) { |
| if (!scrollVert) { |
| scrollBounds.x = tree.getVisibleRect().x; |
| scrollBounds.width = 1; |
| } |
| tree.scrollRectToVisible(scrollBounds); |
| } |
| } |
| else { |
| Rectangle beginRect = getPathBounds(tree, getPathForRow |
| (tree, beginRow)); |
| Rectangle visRect = tree.getVisibleRect(); |
| Rectangle testRect = beginRect; |
| int beginY = beginRect.y; |
| int maxY = beginY + visRect.height; |
| |
| for(int counter = beginRow + 1; counter <= endRow; counter++) { |
| testRect = getPathBounds(tree, |
| getPathForRow(tree, counter)); |
| if((testRect.y + testRect.height) > maxY) |
| counter = endRow; |
| } |
| tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1, |
| testRect.y + testRect.height- |
| beginY)); |
| } |
| } |
| } |
| |
| /** Sets the preferred minimum size. |
| */ |
| public void setPreferredMinSize(Dimension newSize) { |
| preferredMinSize = newSize; |
| } |
| |
| /** Returns the minimum preferred size. |
| */ |
| public Dimension getPreferredMinSize() { |
| if(preferredMinSize == null) |
| return null; |
| return new Dimension(preferredMinSize); |
| } |
| |
| /** Returns the preferred size to properly display the tree, |
| * this is a cover method for getPreferredSize(c, false). |
| */ |
| public Dimension getPreferredSize(JComponent c) { |
| return getPreferredSize(c, true); |
| } |
| |
| /** Returns the preferred size to represent the tree in |
| * <I>c</I>. If <I>checkConsistancy</I> is true |
| * <b>checkConsistancy</b> is messaged first. |
| */ |
| public Dimension getPreferredSize(JComponent c, |
| boolean checkConsistancy) { |
| Dimension pSize = this.getPreferredMinSize(); |
| |
| if(!validCachedPreferredSize) |
| updateCachedPreferredSize(); |
| if(tree != null) { |
| if(pSize != null) |
| return new Dimension(Math.max(pSize.width, |
| preferredSize.width), |
| Math.max(pSize.height, preferredSize.height)); |
| return new Dimension(preferredSize.width, preferredSize.height); |
| } |
| else if(pSize != null) |
| return pSize; |
| else |
| return new Dimension(0, 0); |
| } |
| |
| /** |
| * Returns the minimum size for this component. Which will be |
| * the min preferred size or 0, 0. |
| */ |
| public Dimension getMinimumSize(JComponent c) { |
| if(this.getPreferredMinSize() != null) |
| return this.getPreferredMinSize(); |
| return new Dimension(0, 0); |
| } |
| |
| /** |
| * Returns the maximum size for this component, which will be the |
| * preferred size if the instance is currently in a JTree, or 0, 0. |
| */ |
| public Dimension getMaximumSize(JComponent c) { |
| if(tree != null) |
| return getPreferredSize(tree); |
| if(this.getPreferredMinSize() != null) |
| return this.getPreferredMinSize(); |
| return new Dimension(0, 0); |
| } |
| |
| |
| /** |
| * Messages to stop the editing session. If the UI the receiver |
| * is providing the look and feel for returns true from |
| * <code>getInvokesStopCellEditing</code>, stopCellEditing will |
| * invoked on the current editor. Then completeEditing will |
| * be messaged with false, true, false to cancel any lingering |
| * editing. |
| */ |
| protected void completeEditing() { |
| /* If should invoke stopCellEditing, try that */ |
| if(tree.getInvokesStopCellEditing() && |
| stopEditingInCompleteEditing && editingComponent != null) { |
| cellEditor.stopCellEditing(); |
| } |
| /* Invoke cancelCellEditing, this will do nothing if stopCellEditing |
| was successful. */ |
| completeEditing(false, true, false); |
| } |
| |
| /** |
| * Stops the editing session. If messageStop is true the editor |
| * is messaged with stopEditing, if messageCancel is true the |
| * editor is messaged with cancelEditing. If messageTree is true |
| * the treeModel is messaged with valueForPathChanged. |
| */ |
| protected void completeEditing(boolean messageStop, |
| boolean messageCancel, |
| boolean messageTree) { |
| if(stopEditingInCompleteEditing && editingComponent != null) { |
| Component oldComponent = editingComponent; |
| TreePath oldPath = editingPath; |
| TreeCellEditor oldEditor = cellEditor; |
| Object newValue = oldEditor.getCellEditorValue(); |
| Rectangle editingBounds = getPathBounds(tree, |
| editingPath); |
| boolean requestFocus = (tree != null && |
| (tree.hasFocus() || SwingUtilities. |
| findFocusOwner(editingComponent) != null)); |
| |
| editingComponent = null; |
| editingPath = null; |
| if(messageStop) |
| oldEditor.stopCellEditing(); |
| else if(messageCancel) |
| oldEditor.cancelCellEditing(); |
| tree.remove(oldComponent); |
| if(editorHasDifferentSize) { |
| treeState.invalidatePathBounds(oldPath); |
| updateSize(); |
| } |
| else { |
| editingBounds.x = 0; |
| editingBounds.width = tree.getSize().width; |
| tree.repaint(editingBounds); |
| } |
| if(requestFocus) |
| tree.requestFocus(); |
| if(messageTree) |
| treeModel.valueForPathChanged(oldPath, newValue); |
| } |
| } |
| |
| // cover method for startEditing that allows us to pass extra |
| // information into that method via a class variable |
| private boolean startEditingOnRelease(TreePath path, |
| MouseEvent event, |
| MouseEvent releaseEvent) { |
| this.releaseEvent = releaseEvent; |
| try { |
| return startEditing(path, event); |
| } finally { |
| this.releaseEvent = null; |
| } |
| } |
| |
| /** |
| * Will start editing for node if there is a cellEditor and |
| * shouldSelectCell returns true.<p> |
| * This assumes that path is valid and visible. |
| */ |
| protected boolean startEditing(TreePath path, MouseEvent event) { |
| if (isEditing(tree) && tree.getInvokesStopCellEditing() && |
| !stopEditing(tree)) { |
| return false; |
| } |
| completeEditing(); |
| if(cellEditor != null && tree.isPathEditable(path)) { |
| int row = getRowForPath(tree, path); |
| |
| if(cellEditor.isCellEditable(event)) { |
| editingComponent = cellEditor.getTreeCellEditorComponent |
| (tree, path.getLastPathComponent(), |
| tree.isPathSelected(path), tree.isExpanded(path), |
| treeModel.isLeaf(path.getLastPathComponent()), row); |
| Rectangle nodeBounds = getPathBounds(tree, path); |
| |
| editingRow = row; |
| |
| Dimension editorSize = editingComponent.getPreferredSize(); |
| |
| // Only allow odd heights if explicitly set. |
| if(editorSize.height != nodeBounds.height && |
| getRowHeight() > 0) |
| editorSize.height = getRowHeight(); |
| |
| if(editorSize.width != nodeBounds.width || |
| editorSize.height != nodeBounds.height) { |
| // Editor wants different width or height, invalidate |
| // treeState and relayout. |
| editorHasDifferentSize = true; |
| treeState.invalidatePathBounds(path); |
| updateSize(); |
| // To make sure x/y are updated correctly, fetch |
| // the bounds again. |
| nodeBounds = getPathBounds(tree, path); |
| } |
| else |
| editorHasDifferentSize = false; |
| tree.add(editingComponent); |
| editingComponent.setBounds(nodeBounds.x, nodeBounds.y, |
| nodeBounds.width, |
| nodeBounds.height); |
| editingPath = path; |
| if (editingComponent instanceof JComponent) { |
| ((JComponent)editingComponent).revalidate(); |
| } else { |
| editingComponent.validate(); |
| } |
| editingComponent.repaint(); |
| if(cellEditor.shouldSelectCell(event)) { |
| stopEditingInCompleteEditing = false; |
| tree.setSelectionRow(row); |
| stopEditingInCompleteEditing = true; |
| } |
| |
| Component focusedComponent = SwingUtilities2. |
| compositeRequestFocus(editingComponent); |
| boolean selectAll = true; |
| |
| if(event != null) { |
| /* Find the component that will get forwarded all the |
| mouse events until mouseReleased. */ |
| Point componentPoint = SwingUtilities.convertPoint |
| (tree, new Point(event.getX(), event.getY()), |
| editingComponent); |
| |
| /* Create an instance of BasicTreeMouseListener to handle |
| passing the mouse/motion events to the necessary |
| component. */ |
| // We really want similar behavior to getMouseEventTarget, |
| // but it is package private. |
| Component activeComponent = SwingUtilities. |
| getDeepestComponentAt(editingComponent, |
| componentPoint.x, componentPoint.y); |
| if (activeComponent != null) { |
| MouseInputHandler handler = |
| new MouseInputHandler(tree, activeComponent, |
| event, focusedComponent); |
| |
| if (releaseEvent != null) { |
| handler.mouseReleased(releaseEvent); |
| } |
| |
| selectAll = false; |
| } |
| } |
| if (selectAll && focusedComponent instanceof JTextField) { |
| ((JTextField)focusedComponent).selectAll(); |
| } |
| return true; |
| } |
| else |
| editingComponent = null; |
| } |
| return false; |
| } |
| |
| // |
| // Following are primarily for handling mouse events. |
| // |
| |
| /** |
| * If the <code>mouseX</code> and <code>mouseY</code> are in the |
| * expand/collapse region of the <code>row</code>, this will toggle |
| * the row. |
| */ |
| protected void checkForClickInExpandControl(TreePath path, |
| int mouseX, int mouseY) { |
| if (isLocationInExpandControl(path, mouseX, mouseY)) { |
| handleExpandControlClick(path, mouseX, mouseY); |
| } |
| } |
| |
| /** |
| * Returns true if <code>mouseX</code> and <code>mouseY</code> fall |
| * in the area of row that is used to expand/collapse the node and |
| * the node at <code>row</code> does not represent a leaf. |
| */ |
| protected boolean isLocationInExpandControl(TreePath path, |
| int mouseX, int mouseY) { |
| if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){ |
| int boxWidth; |
| Insets i = tree.getInsets(); |
| |
| if(getExpandedIcon() != null) |
| boxWidth = getExpandedIcon().getIconWidth(); |
| else |
| boxWidth = 8; |
| |
| int boxLeftX = getRowX(tree.getRowForPath(path), |
| path.getPathCount() - 1); |
| |
| if (leftToRight) { |
| boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1; |
| } else { |
| boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1; |
| } |
| |
| boxLeftX = findCenteredX(boxLeftX, boxWidth); |
| |
| return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth)); |
| } |
| return false; |
| } |
| |
| /** |
| * Messaged when the user clicks the particular row, this invokes |
| * toggleExpandState. |
| */ |
| protected void handleExpandControlClick(TreePath path, int mouseX, |
| int mouseY) { |
| toggleExpandState(path); |
| } |
| |
| /** |
| * Expands path if it is not expanded, or collapses row if it is expanded. |
| * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible |
| * is invoked to scroll as many of the children to visible as possible |
| * (tries to scroll to last visible descendant of path). |
| */ |
| protected void toggleExpandState(TreePath path) { |
| if(!tree.isExpanded(path)) { |
| int row = getRowForPath(tree, path); |
| |
| tree.expandPath(path); |
| updateSize(); |
| if(row != -1) { |
| if(tree.getScrollsOnExpand()) |
| ensureRowsAreVisible(row, row + treeState. |
| getVisibleChildCount(path)); |
| else |
| ensureRowsAreVisible(row, row); |
| } |
| } |
| else { |
| tree.collapsePath(path); |
| updateSize(); |
| } |
| } |
| |
| /** |
| * Returning true signifies a mouse event on the node should toggle |
| * the selection of only the row under mouse. |
| */ |
| protected boolean isToggleSelectionEvent(MouseEvent event) { |
| return (SwingUtilities.isLeftMouseButton(event) && |
| event.isControlDown()); |
| } |
| |
| /** |
| * Returning true signifies a mouse event on the node should select |
| * from the anchor point. |
| */ |
| protected boolean isMultiSelectEvent(MouseEvent event) { |
| return (SwingUtilities.isLeftMouseButton(event) && |
| event.isShiftDown()); |
| } |
| |
| /** |
| * Returning true indicates the row under the mouse should be toggled |
| * based on the event. This is invoked after checkForClickInExpandControl, |
| * implying the location is not in the expand (toggle) control |
| */ |
| protected boolean isToggleEvent(MouseEvent event) { |
| if(!SwingUtilities.isLeftMouseButton(event)) { |
| return false; |
| } |
| int clickCount = tree.getToggleClickCount(); |
| |
| if(clickCount <= 0) { |
| return false; |
| } |
| return ((event.getClickCount() % clickCount) == 0); |
| } |
| |
| /** |
| * Messaged to update the selection based on a MouseEvent over a |
| * particular row. If the event is a toggle selection event, the |
| * row is either selected, or deselected. If the event identifies |
| * a multi selection event, the selection is updated from the |
| * anchor point. Otherwise the row is selected, and if the event |
| * specified a toggle event the row is expanded/collapsed. |
| */ |
| protected void selectPathForEvent(TreePath path, MouseEvent event) { |
| /* Adjust from the anchor point. */ |
| if(isMultiSelectEvent(event)) { |
| TreePath anchor = getAnchorSelectionPath(); |
| int anchorRow = (anchor == null) ? -1 : |
| getRowForPath(tree, anchor); |
| |
| if(anchorRow == -1 || tree.getSelectionModel(). |
| getSelectionMode() == TreeSelectionModel. |
| SINGLE_TREE_SELECTION) { |
| tree.setSelectionPath(path); |
| } |
| else { |
| int row = getRowForPath(tree, path); |
| TreePath lastAnchorPath = anchor; |
| |
| if (isToggleSelectionEvent(event)) { |
| if (tree.isRowSelected(anchorRow)) { |
| tree.addSelectionInterval(anchorRow, row); |
| } else { |
| tree.removeSelectionInterval(anchorRow, row); |
| tree.addSelectionInterval(row, row); |
| } |
| } else if(row < anchorRow) { |
| tree.setSelectionInterval(row, anchorRow); |
| } else { |
| tree.setSelectionInterval(anchorRow, row); |
| } |
| lastSelectedRow = row; |
| setAnchorSelectionPath(lastAnchorPath); |
| setLeadSelectionPath(path); |
| } |
| } |
| |
| // Should this event toggle the selection of this row? |
| /* Control toggles just this node. */ |
| else if(isToggleSelectionEvent(event)) { |
| if(tree.isPathSelected(path)) |
| tree.removeSelectionPath(path); |
| else |
| tree.addSelectionPath(path); |
| lastSelectedRow = getRowForPath(tree, path); |
| setAnchorSelectionPath(path); |
| setLeadSelectionPath(path); |
| } |
| |
| /* Otherwise set the selection to just this interval. */ |
| else if(SwingUtilities.isLeftMouseButton(event)) { |
| tree.setSelectionPath(path); |
| if(isToggleEvent(event)) { |
| toggleExpandState(path); |
| } |
| } |
| } |
| |
| /** |
| * @return true if the node at <code>row</code> is a leaf. |
| */ |
| protected boolean isLeaf(int row) { |
| TreePath path = getPathForRow(tree, row); |
| |
| if(path != null) |
| return treeModel.isLeaf(path.getLastPathComponent()); |
| // Have to return something here... |
| return true; |
| } |
| |
| // |
| // The following selection methods (lead/anchor) are covers for the |
| // methods in JTree. |
| // |
| private void setAnchorSelectionPath(TreePath newPath) { |
| ignoreLAChange = true; |
| try { |
| tree.setAnchorSelectionPath(newPath); |
| } finally{ |
| ignoreLAChange = false; |
| } |
| } |
| |
| private TreePath getAnchorSelectionPath() { |
| return tree.getAnchorSelectionPath(); |
| } |
| |
| private void setLeadSelectionPath(TreePath newPath) { |
| setLeadSelectionPath(newPath, false); |
| } |
| |
| private void setLeadSelectionPath(TreePath newPath, boolean repaint) { |
| Rectangle bounds = repaint ? |
| getPathBounds(tree, getLeadSelectionPath()) : null; |
| |
| ignoreLAChange = true; |
| try { |
| tree.setLeadSelectionPath(newPath); |
| } finally { |
| ignoreLAChange = false; |
| } |
| leadRow = getRowForPath(tree, newPath); |
| |
| if(repaint) { |
| if(bounds != null) |
| tree.repaint(bounds); |
| bounds = getPathBounds(tree, newPath); |
| if(bounds != null) |
| tree.repaint(bounds); |
| } |
| } |
| |
| private TreePath getLeadSelectionPath() { |
| return tree.getLeadSelectionPath(); |
| } |
| |
| private void updateLeadRow() { |
| leadRow = getRowForPath(tree, getLeadSelectionPath()); |
| } |
| |
| private int getLeadSelectionRow() { |
| return leadRow; |
| } |
| |
| /** |
| * Extends the selection from the anchor to make <code>newLead</code> |
| * the lead of the selection. This does not scroll. |
| */ |
| private void extendSelection(TreePath newLead) { |
| TreePath aPath = getAnchorSelectionPath(); |
| int aRow = (aPath == null) ? -1 : |
| getRowForPath(tree, aPath); |
| int newIndex = getRowForPath(tree, newLead); |
| |
| if(aRow == -1) { |
| tree.setSelectionRow(newIndex); |
| } |
| else { |
| if(aRow < newIndex) { |
| tree.setSelectionInterval(aRow, newIndex); |
| } |
| else { |
| tree.setSelectionInterval(newIndex, aRow); |
| } |
| setAnchorSelectionPath(aPath); |
| setLeadSelectionPath(newLead); |
| } |
| } |
| |
| /** |
| * Invokes <code>repaint</code> on the JTree for the passed in TreePath, |
| * <code>path</code>. |
| */ |
| private void repaintPath(TreePath path) { |
| if (path != null) { |
| Rectangle bounds = getPathBounds(tree, path); |
| if (bounds != null) { |
| tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height); |
| } |
| } |
| } |
| |
| /** |
| * Updates the TreeState in response to nodes expanding/collapsing. |
| */ |
| public class TreeExpansionHandler implements TreeExpansionListener { |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| /** |
| * Called whenever an item in the tree has been expanded. |
| */ |
| public void treeExpanded(TreeExpansionEvent event) { |
| getHandler().treeExpanded(event); |
| } |
| |
| /** |
| * Called whenever an item in the tree has been collapsed. |
| */ |
| public void treeCollapsed(TreeExpansionEvent event) { |
| getHandler().treeCollapsed(event); |
| } |
| } // BasicTreeUI.TreeExpansionHandler |
| |
| |
| /** |
| * Updates the preferred size when scrolling (if necessary). |
| */ |
| public class ComponentHandler extends ComponentAdapter implements |
| ActionListener { |
| /** Timer used when inside a scrollpane and the scrollbar is |
| * adjusting. */ |
| protected Timer timer; |
| /** ScrollBar that is being adjusted. */ |
| protected JScrollBar scrollBar; |
| |
| public void componentMoved(ComponentEvent e) { |
| if(timer == null) { |
| JScrollPane scrollPane = getScrollPane(); |
| |
| if(scrollPane == null) |
| updateSize(); |
| else { |
| scrollBar = scrollPane.getVerticalScrollBar(); |
| if(scrollBar == null || |
| !scrollBar.getValueIsAdjusting()) { |
| // Try the horizontal scrollbar. |
| if((scrollBar = scrollPane.getHorizontalScrollBar()) |
| != null && scrollBar.getValueIsAdjusting()) |
| startTimer(); |
| else |
| updateSize(); |
| } |
| else |
| startTimer(); |
| } |
| } |
| } |
| |
| /** |
| * Creates, if necessary, and starts a Timer to check if need to |
| * resize the bounds. |
| */ |
| protected void startTimer() { |
| if(timer == null) { |
| timer = new Timer(200, this); |
| timer.setRepeats(true); |
| } |
| timer.start(); |
| } |
| |
| /** |
| * Returns the JScrollPane housing the JTree, or null if one isn't |
| * found. |
| */ |
| protected JScrollPane getScrollPane() { |
| Component c = tree.getParent(); |
| |
| while(c != null && !(c instanceof JScrollPane)) |
| c = c.getParent(); |
| if(c instanceof JScrollPane) |
| return (JScrollPane)c; |
| return null; |
| } |
| |
| /** |
| * Public as a result of Timer. If the scrollBar is null, or |
| * not adjusting, this stops the timer and updates the sizing. |
| */ |
| public void actionPerformed(ActionEvent ae) { |
| if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { |
| if(timer != null) |
| timer.stop(); |
| updateSize(); |
| timer = null; |
| scrollBar = null; |
| } |
| } |
| } // End of BasicTreeUI.ComponentHandler |
| |
| |
| /** |
| * Forwards all TreeModel events to the TreeState. |
| */ |
| public class TreeModelHandler implements TreeModelListener { |
| |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| public void treeNodesChanged(TreeModelEvent e) { |
| getHandler().treeNodesChanged(e); |
| } |
| |
| public void treeNodesInserted(TreeModelEvent e) { |
| getHandler().treeNodesInserted(e); |
| } |
| |
| public void treeNodesRemoved(TreeModelEvent e) { |
| getHandler().treeNodesRemoved(e); |
| } |
| |
| public void treeStructureChanged(TreeModelEvent e) { |
| getHandler().treeStructureChanged(e); |
| } |
| } // End of BasicTreeUI.TreeModelHandler |
| |
| |
| /** |
| * Listens for changes in the selection model and updates the display |
| * accordingly. |
| */ |
| public class TreeSelectionHandler implements TreeSelectionListener { |
| |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| /** |
| * Messaged when the selection changes in the tree we're displaying |
| * for. Stops editing, messages super and displays the changed paths. |
| */ |
| public void valueChanged(TreeSelectionEvent event) { |
| getHandler().valueChanged(event); |
| } |
| }// End of BasicTreeUI.TreeSelectionHandler |
| |
| |
| /** |
| * Listener responsible for getting cell editing events and updating |
| * the tree accordingly. |
| */ |
| public class CellEditorHandler implements CellEditorListener { |
| |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| /** Messaged when editing has stopped in the tree. */ |
| public void editingStopped(ChangeEvent e) { |
| getHandler().editingStopped(e); |
| } |
| |
| /** Messaged when editing has been canceled in the tree. */ |
| public void editingCanceled(ChangeEvent e) { |
| getHandler().editingCanceled(e); |
| } |
| } // BasicTreeUI.CellEditorHandler |
| |
| |
| /** |
| * This is used to get mutliple key down events to appropriately generate |
| * events. |
| */ |
| public class KeyHandler extends KeyAdapter { |
| |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| // Also note these fields aren't use anymore, nor does Handler have |
| // the old functionality. This behavior worked around an old bug |
| // in JComponent that has long since been fixed. |
| |
| /** Key code that is being generated for. */ |
| protected Action repeatKeyAction; |
| |
| /** Set to true while keyPressed is active. */ |
| protected boolean isKeyDown; |
| |
| /** |
| * Invoked when a key has been typed. |
| * |
| * Moves the keyboard focus to the first element |
| * whose first letter matches the alphanumeric key |
| * pressed by the user. Subsequent same key presses |
| * move the keyboard focus to the next object that |
| * starts with the same letter. |
| */ |
| public void keyTyped(KeyEvent e) { |
| getHandler().keyTyped(e); |
| } |
| |
| public void keyPressed(KeyEvent e) { |
| getHandler().keyPressed(e); |
| } |
| |
| public void keyReleased(KeyEvent e) { |
| getHandler().keyReleased(e); |
| } |
| } // End of BasicTreeUI.KeyHandler |
| |
| |
| /** |
| * Repaints the lead selection row when focus is lost/gained. |
| */ |
| public class FocusHandler implements FocusListener { |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| /** |
| * Invoked when focus is activated on the tree we're in, redraws the |
| * lead row. |
| */ |
| public void focusGained(FocusEvent e) { |
| getHandler().focusGained(e); |
| } |
| |
| /** |
| * Invoked when focus is activated on the tree we're in, redraws the |
| * lead row. |
| */ |
| public void focusLost(FocusEvent e) { |
| getHandler().focusLost(e); |
| } |
| } // End of class BasicTreeUI.FocusHandler |
| |
| |
| /** |
| * Class responsible for getting size of node, method is forwarded |
| * to BasicTreeUI method. X location does not include insets, that is |
| * handled in getPathBounds. |
| */ |
| // This returns locations that don't include any Insets. |
| public class NodeDimensionsHandler extends |
| AbstractLayoutCache.NodeDimensions { |
| /** |
| * Responsible for getting the size of a particular node. |
| */ |
| public Rectangle getNodeDimensions(Object value, int row, |
| int depth, boolean expanded, |
| Rectangle size) { |
| // Return size of editing component, if editing and asking |
| // for editing row. |
| if(editingComponent != null && editingRow == row) { |
| Dimension prefSize = editingComponent. |
| getPreferredSize(); |
| int rh = getRowHeight(); |
| |
| if(rh > 0 && rh != prefSize.height) |
| prefSize.height = rh; |
| if(size != null) { |
| size.x = getRowX(row, depth); |
| size.width = prefSize.width; |
| size.height = prefSize.height; |
| } |
| else { |
| size = new Rectangle(getRowX(row, depth), 0, |
| prefSize.width, prefSize.height); |
| } |
| return size; |
| } |
| // Not editing, use renderer. |
| if(currentCellRenderer != null) { |
| Component aComponent; |
| |
| aComponent = currentCellRenderer.getTreeCellRendererComponent |
| (tree, value, tree.isRowSelected(row), |
| expanded, treeModel.isLeaf(value), row, |
| false); |
| if(tree != null) { |
| // Only ever removed when UI changes, this is OK! |
| rendererPane.add(aComponent); |
| aComponent.validate(); |
| } |
| Dimension prefSize = aComponent.getPreferredSize(); |
| |
| if(size != null) { |
| size.x = getRowX(row, depth); |
| size.width = prefSize.width; |
| size.height = prefSize.height; |
| } |
| else { |
| size = new Rectangle(getRowX(row, depth), 0, |
| prefSize.width, prefSize.height); |
| } |
| return size; |
| } |
| return null; |
| } |
| |
| /** |
| * @return amount to indent the given row. |
| */ |
| protected int getRowX(int row, int depth) { |
| return BasicTreeUI.this.getRowX(row, depth); |
| } |
| |
| } // End of class BasicTreeUI.NodeDimensionsHandler |
| |
| |
| /** |
| * TreeMouseListener is responsible for updating the selection |
| * based on mouse events. |
| */ |
| public class MouseHandler extends MouseAdapter implements MouseMotionListener |
| { |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| /** |
| * Invoked when a mouse button has been pressed on a component. |
| */ |
| public void mousePressed(MouseEvent e) { |
| getHandler().mousePressed(e); |
| } |
| |
| public void mouseDragged(MouseEvent e) { |
| getHandler().mouseDragged(e); |
| } |
| |
| /** |
| * Invoked when the mouse button has been moved on a component |
| * (with no buttons no down). |
| * @since 1.4 |
| */ |
| public void mouseMoved(MouseEvent e) { |
| getHandler().mouseMoved(e); |
| } |
| |
| public void mouseReleased(MouseEvent e) { |
| getHandler().mouseReleased(e); |
| } |
| } // End of BasicTreeUI.MouseHandler |
| |
| |
| /** |
| * PropertyChangeListener for the tree. Updates the appropriate |
| * varaible, or TreeState, based on what changes. |
| */ |
| public class PropertyChangeHandler implements |
| PropertyChangeListener { |
| |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| public void propertyChange(PropertyChangeEvent event) { |
| getHandler().propertyChange(event); |
| } |
| } // End of BasicTreeUI.PropertyChangeHandler |
| |
| |
| /** |
| * Listener on the TreeSelectionModel, resets the row selection if |
| * any of the properties of the model change. |
| */ |
| public class SelectionModelPropertyChangeHandler implements |
| PropertyChangeListener { |
| |
| // NOTE: This class exists only for backward compatability. All |
| // its functionality has been moved into Handler. If you need to add |
| // new functionality add it to the Handler, but make sure this |
| // class calls into the Handler. |
| |
| public void propertyChange(PropertyChangeEvent event) { |
| getHandler().propertyChange(event); |
| } |
| } // End of BasicTreeUI.SelectionModelPropertyChangeHandler |
| |
| |
| /** |
| * <code>TreeTraverseAction</code> is the action used for left/right keys. |
| * Will toggle the expandedness of a node, as well as potentially |
| * incrementing the selection. |
| */ |
| public class TreeTraverseAction extends AbstractAction { |
| /** Determines direction to traverse, 1 means expand, -1 means |
| * collapse. */ |
| protected int direction; |
| /** True if the selection is reset, false means only the lead path |
| * changes. */ |
| private boolean changeSelection; |
| |
| public TreeTraverseAction(int direction, String name) { |
| this(direction, name, true); |
| } |
| |
| private TreeTraverseAction(int direction, String name, |
| boolean changeSelection) { |
| this.direction = direction; |
| this.changeSelection = changeSelection; |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| if (tree != null) { |
| SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction, |
| changeSelection); |
| } |
| } |
| |
| public boolean isEnabled() { return (tree != null && |
| tree.isEnabled()); } |
| } // BasicTreeUI.TreeTraverseAction |
| |
| |
| /** TreePageAction handles page up and page down events. |
| */ |
| public class TreePageAction extends AbstractAction { |
| /** Specifies the direction to adjust the selection by. */ |
| protected int direction; |
| /** True indicates should set selection from anchor path. */ |
| private boolean addToSelection; |
| private boolean changeSelection; |
| |
| public TreePageAction(int direction, String name) { |
| this(direction, name, false, true); |
| } |
| |
| private TreePageAction(int direction, String name, |
| boolean addToSelection, |
| boolean changeSelection) { |
| this.direction = direction; |
| this.addToSelection = addToSelection; |
| this.changeSelection = changeSelection; |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| if (tree != null) { |
| SHARED_ACTION.page(tree, BasicTreeUI.this, direction, |
| addToSelection, changeSelection); |
| } |
| } |
| |
| public boolean isEnabled() { return (tree != null && |
| tree.isEnabled()); } |
| |
| } // BasicTreeUI.TreePageAction |
| |
| |
| /** TreeIncrementAction is used to handle up/down actions. Selection |
| * is moved up or down based on direction. |
| */ |
| public class TreeIncrementAction extends AbstractAction { |
| /** Specifies the direction to adjust the selection by. */ |
| protected int direction; |
| /** If true the new item is added to the selection, if false the |
| * selection is reset. */ |
| private boolean addToSelection; |
| private boolean changeSelection; |
| |
| public TreeIncrementAction(int direction, String name) { |
| this(direction, name, false, true); |
| } |
| |
| private TreeIncrementAction(int direction, String name, |
| boolean addToSelection, |
| boolean changeSelection) { |
| this.direction = direction; |
| this.addToSelection = addToSelection; |
| this.changeSelection = changeSelection; |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| if (tree != null) { |
| SHARED_ACTION.increment(tree, BasicTreeUI.this, direction, |
| addToSelection, changeSelection); |
| } |
| } |
| |
| public boolean isEnabled() { return (tree != null && |
| tree.isEnabled()); } |
| |
| } // End of class BasicTreeUI.TreeIncrementAction |
| |
| /** |
| * TreeHomeAction is used to handle end/home actions. |
| * Scrolls either the first or last cell to be visible based on |
| * direction. |
| */ |
| public class TreeHomeAction extends AbstractAction { |
| protected int direction; |
| /** Set to true if append to selection. */ |
| private boolean addToSelection; |
| private boolean changeSelection; |
| |
| public TreeHomeAction(int direction, String name) { |
| this(direction, name, false, true); |
| } |
| |
| private TreeHomeAction(int direction, String name, |
| boolean addToSelection, |
| boolean changeSelection) { |
| this.direction = direction; |
| this.changeSelection = changeSelection; |
| this.addToSelection = addToSelection; |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| if (tree != null) { |
| SHARED_ACTION.home(tree, BasicTreeUI.this, direction, |
| addToSelection, changeSelection); |
| } |
| } |
| |
| public boolean isEnabled() { return (tree != null && |
| tree.isEnabled()); } |
| |
| } // End of class BasicTreeUI.TreeHomeAction |
| |
| |
| /** |
| * For the first selected row expandedness will be toggled. |
| */ |
| public class TreeToggleAction extends AbstractAction { |
| public TreeToggleAction(String name) { |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| if(tree != null) { |
| SHARED_ACTION.toggle(tree, BasicTreeUI.this); |
| } |
| } |
| |
| public boolean isEnabled() { return (tree != null && |
| tree.isEnabled()); } |
| |
| } // End of class BasicTreeUI.TreeToggleAction |
| |
| |
| /** |
| * ActionListener that invokes cancelEditing when action performed. |
| */ |
| public class TreeCancelEditingAction extends AbstractAction { |
| public TreeCancelEditingAction(String name) { |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| if(tree != null) { |
| SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this); |
| } |
| } |
| |
| public boolean isEnabled() { return (tree != null && |
| tree.isEnabled() && |
| isEditing(tree)); } |
| } // End of class BasicTreeUI.TreeCancelEditingAction |
| |
| |
| /** |
| * MouseInputHandler handles passing all mouse events, |
| * including mouse motion events, until the mouse is released to |
| * the destination it is constructed with. It is assumed all the |
| * events are currently target at source. |
| */ |
| public class MouseInputHandler extends Object implements |
| MouseInputListener |
| { |
| /** Source that events are coming from. */ |
| protected Component source; |
| /** Destination that receives all events. */ |
| protected Component destination; |
| private Component focusComponent; |
| private boolean dispatchedEvent; |
| |
| public MouseInputHandler(Component source, Component destination, |
| MouseEvent event){ |
| this(source, destination, event, null); |
| } |
| |
| MouseInputHandler(Component source, Component destination, |
| MouseEvent event, Component focusComponent) { |
| this.source = source; |
| this.destination = destination; |
| this.source.addMouseListener(this); |
| this.source.addMouseMotionListener(this); |
| |
| SwingUtilities2.setSkipClickCount(destination, |
| event.getClickCount() - 1); |
| |
| /* Dispatch the editing event! */ |
| destination.dispatchEvent(SwingUtilities.convertMouseEvent |
| (source, event, destination)); |
| this.focusComponent = focusComponent; |
| } |
| |
| public void mouseClicked(MouseEvent e) { |
| if(destination != null) { |
| dispatchedEvent = true; |
| destination.dispatchEvent(SwingUtilities.convertMouseEvent |
| (source, e, destination)); |
| } |
| } |
| |
| public void mousePressed(MouseEvent e) { |
| } |
| |
| public void mouseReleased(MouseEvent e) { |
| if(destination != null) |
| destination.dispatchEvent(SwingUtilities.convertMouseEvent |
| (source, e, destination)); |
| removeFromSource(); |
| } |
| |
| public void mouseEntered(MouseEvent e) { |
| if (!SwingUtilities.isLeftMouseButton(e)) { |
| removeFromSource(); |
| } |
| } |
| |
| public void mouseExited(MouseEvent e) { |
| if (!SwingUtilities.isLeftMouseButton(e)) { |
| removeFromSource(); |
| } |
| } |
| |
| public void mouseDragged(MouseEvent e) { |
| if(destination != null) { |
| dispatchedEvent = true; |
| destination.dispatchEvent(SwingUtilities.convertMouseEvent |
| (source, e, destination)); |
| } |
| } |
| |
| public void mouseMoved(MouseEvent e) { |
| removeFromSource(); |
| } |
| |
| protected void removeFromSource() { |
| if(source != null) { |
| source.removeMouseListener(this); |
| source.removeMouseMotionListener(this); |
| if (focusComponent != null && |
| focusComponent == destination && !dispatchedEvent && |
| (focusComponent instanceof JTextField)) { |
| ((JTextField)focusComponent).selectAll(); |
| } |
| } |
| source = destination = null; |
| } |
| |
| } // End of class BasicTreeUI.MouseInputHandler |
| |
| private static final TransferHandler defaultTransferHandler = new TreeTransferHandler(); |
| |
| static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator<TreePath> { |
| |
| private JTree tree; |
| |
| /** |
| * Create a Transferable to use as the source for a data transfer. |
| * |
| * @param c The component holding the data to be transfered. This |
| * argument is provided to enable sharing of TransferHandlers by |
| * multiple components. |
| * @return The representation of the data to be transfered. |
| * |
| */ |
| protected Transferable createTransferable(JComponent c) { |
| if (c instanceof JTree) { |
| tree = (JTree) c; |
| TreePath[] paths = tree.getSelectionPaths(); |
| |
| if (paths == null || paths.length == 0) { |
| return null; |
| } |
| |
| StringBuffer plainBuf = new StringBuffer(); |
| StringBuffer htmlBuf = new StringBuffer(); |
| |
| htmlBuf.append("<html>\n<body>\n<ul>\n"); |
| |
| TreeModel model = tree.getModel(); |
| TreePath lastPath = null; |
| TreePath[] displayPaths = getDisplayOrderPaths(paths); |
| |
| for (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. |
| */ |
| 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 <code>timeFactor</code> property (or 1000 milliseconds |
| * if it is not defined). Subsequent same key presses move the keyboard |
| * focus to the next object that starts with the same letter until another |
| * key is pressed, then it is treated as the prefix with appropriate number |
| * of the same letters followed by first typed another letter. |
| */ |
| public void keyTyped(KeyEvent e) { |
| // handle first letter navigation |
| if(tree != null && tree.getRowCount()>0 && tree.hasFocus() && |
| tree.isEnabled()) { |
| if (e.isAltDown() || e.isControlDown() || e.isMetaDown() || |
| isNavigationKey(e)) { |
| return; |
| } |
| boolean startingFromSelection = true; |
| |
| char c = e.getKeyChar(); |
| |
| long time = e.getWhen(); |
| int startingRow = tree.getLeadSelectionRow(); |
| if (time - lastTime < timeFactor) { |
| typedString += c; |
| if((prefix.length() == 1) && (c == prefix.charAt(0))) { |
| // Subsequent same key presses move the keyboard focus to the next |
| // object that starts with the same letter. |
| startingRow++; |
| } else { |
| prefix = typedString; |
| } |
| } else { |
| startingRow++; |
| typedString = "" + c; |
| prefix = typedString; |
| } |
| lastTime = time; |
| |
| if (startingRow < 0 || startingRow >= tree.getRowCount()) { |
| startingFromSelection = false; |
| startingRow = 0; |
| } |
| TreePath path = tree.getNextMatch(prefix, startingRow, |
| Position.Bias.Forward); |
| if (path != null) { |
| tree.setSelectionPath(path); |
| int row = getRowForPath(tree, path); |
| ensureRowsAreVisible(row, row); |
| } else if (startingFromSelection) { |
| path = tree.getNextMatch(prefix, 0, |
| Position.Bias.Forward); |
| if (path != null) { |
| tree.setSelectionPath(path); |
| int row = getRowForPath(tree, path); |
| ensureRowsAreVisible(row, row); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Invoked when a key has been pressed. |
| * |
| * Checks to see if the key event is a navigation key to prevent |
| * dispatching these keys for the first letter navigation. |
| */ |
| public void keyPressed(KeyEvent e) { |
| if (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. |
| */ |
| 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) { |
| updateLeadRow(); |
| repaintPath((TreePath)event.getOldValue()); |
| repaintPath((TreePath)event.getNewValue()); |
| } |
| } |
| else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) { |
| if (!ignoreLAChange) { |
| repaintPath((TreePath)event.getOldValue()); |
| repaintPath((TreePath)event.getNewValue()); |
| } |
| } |
| if(changeName == JTree.CELL_RENDERER_PROPERTY) { |
| setCellRenderer((TreeCellRenderer)event.getNewValue()); |
| redoTheLayout(); |
| } |
| else if(changeName == JTree.TREE_MODEL_PROPERTY) { |
| setModel((TreeModel)event.getNewValue()); |
| } |
| else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) { |
| setRootVisible(((Boolean)event.getNewValue()). |
| booleanValue()); |
| } |
| else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) { |
| setShowsRootHandles(((Boolean)event.getNewValue()). |
| booleanValue()); |
| } |
| else if(changeName == JTree.ROW_HEIGHT_PROPERTY) { |
| setRowHeight(((Integer)event.getNewValue()). |
| intValue()); |
| } |
| else if(changeName == JTree.CELL_EDITOR_PROPERTY) { |
| setCellEditor((TreeCellEditor)event.getNewValue()); |
| } |
| else if(changeName == JTree.EDITABLE_PROPERTY) { |
| setEditable(((Boolean)event.getNewValue()).booleanValue()); |
| } |
| else if(changeName == JTree.LARGE_MODEL_PROPERTY) { |
| setLargeModel(tree.isLargeModel()); |
| } |
| else if(changeName == JTree.SELECTION_MODEL_PROPERTY) { |
| setSelectionModel(tree.getSelectionModel()); |
| } |
| else if(changeName == "font") { |
| completeEditing(); |
| if(treeState != null) |
| treeState.invalidateSizes(); |
| updateSize(); |
| } |
| else if (changeName == "componentOrientation") { |
| if (tree != null) { |
| leftToRight = BasicGraphicsUtils.isLeftToRight(tree); |
| redoTheLayout(); |
| tree.treeDidChange(); |
| |
| InputMap km = getInputMap(JComponent.WHEN_FOCUSED); |
| SwingUtilities.replaceUIInputMap(tree, |
| JComponent.WHEN_FOCUSED, km); |
| } |
| } else if ("dropLocation" == changeName) { |
| JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue(); |
| repaintDropLocation(oldValue); |
| repaintDropLocation(tree.getDropLocation()); |
| } |
| } |
| } |
| |
| private void repaintDropLocation(JTree.DropLocation loc) { |
| if (loc == null) { |
| return; |
| } |
| |
| Rectangle r; |
| |
| if (isDropLine(loc)) { |
| r = getDropLineRect(loc); |
| } else { |
| r = tree.getPathBounds(loc.getPath()); |
| } |
| |
| if (r != null) { |
| tree.repaint(r); |
| } |
| } |
| |
| // |
| // MouseListener |
| // |
| |
| // Whether or not the mouse press (which is being considered as part |
| // of a drag sequence) also caused the selection change to be fully |
| // processed. |
| private boolean dragPressDidSelection; |
| |
| // Set to true when a drag gesture has been fully recognized and DnD |
| // begins. Use this to ignore further mouse events which could be |
| // delivered if DnD is cancelled (via ESCAPE for example) |
| private boolean dragStarted; |
| |
| // The path over which the press occurred and the press event itself |
| private TreePath pressedPath; |
| private MouseEvent pressedEvent; |
| |
| // Used to detect whether the press event causes a selection change. |
| // If it does, we won't try to start editing on the release. |
| private boolean valueChangedOnPress; |
| |
| private boolean isActualPath(TreePath path, int x, int y) { |
| if (path == null) { |
| return false; |
| } |
| |
| Rectangle bounds = getPathBounds(tree, path); |
| if (y > (bounds.y + bounds.height)) { |
| return false; |
| } |
| |
| return (x >= bounds.x) && (x <= (bounds.x + bounds.width)); |
| } |
| |
| public void mouseClicked(MouseEvent e) { |
| } |
| |
| public void mouseEntered(MouseEvent e) { |
| } |
| |
| public void mouseExited(MouseEvent e) { |
| } |
| |
| /** |
| * Invoked when a mouse button has been pressed on a component. |
| */ |
| public void mousePressed(MouseEvent e) { |
| if (SwingUtilities2.shouldIgnore(e, tree)) { |
| return; |
| } |
| |
| // if we can't stop any ongoing editing, do nothing |
| if (isEditing(tree) && tree.getInvokesStopCellEditing() |
| && !stopEditing(tree)) { |
| return; |
| } |
| |
| completeEditing(); |
| |
| pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY()); |
| |
| if (tree.getDragEnabled()) { |
| mousePressedDND(e); |
| } else { |
| SwingUtilities2.adjustFocus(tree); |
| handleSelection(e); |
| } |
| } |
| |
| private void mousePressedDND(MouseEvent e) { |
| pressedEvent = e; |
| boolean grabFocus = true; |
| dragStarted = false; |
| valueChangedOnPress = false; |
| |
| // if we have a valid path and this is a drag initiating event |
| if (isActualPath(pressedPath, e.getX(), e.getY()) && |
| DragRecognitionSupport.mousePressed(e)) { |
| |
| dragPressDidSelection = false; |
| |
| if (e.isControlDown()) { |
| // do nothing for control - will be handled on release |
| // or when drag starts |
| return; |
| } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) { |
| // clicking on something that's already selected |
| // and need to make it the lead now |
| setAnchorSelectionPath(pressedPath); |
| setLeadSelectionPath(pressedPath, true); |
| return; |
| } |
| |
| dragPressDidSelection = true; |
| |
| // could be a drag initiating event - don't grab focus |
| grabFocus = false; |
| } |
| |
| if (grabFocus) { |
| SwingUtilities2.adjustFocus(tree); |
| } |
| |
| handleSelection(e); |
| } |
| |
| void handleSelection(MouseEvent e) { |
| if(pressedPath != null) { |
| Rectangle bounds = getPathBounds(tree, pressedPath); |
| |
| if(e.getY() >= (bounds.y + bounds.height)) { |
| return; |
| } |
| |
| // Preferably checkForClickInExpandControl could take |
| // the Event to do this it self! |
| if(SwingUtilities.isLeftMouseButton(e)) { |
| checkForClickInExpandControl(pressedPath, e.getX(), e.getY()); |
| } |
| |
| int x = e.getX(); |
| |
| // Perhaps they clicked the cell itself. If so, |
| // select it. |
| if (x >= bounds.x && x < (bounds.x + bounds.width)) { |
| if (tree.getDragEnabled() || !startEditing(pressedPath, e)) { |
| selectPathForEvent(pressedPath, e); |
| } |
| } |
| } |
| } |
| |
| public void dragStarting(MouseEvent me) { |
| dragStarted = true; |
| |
| if (me.isControlDown()) { |
| tree.addSelectionPath(pressedPath); |
| setAnchorSelectionPath(pressedPath); |
| setLeadSelectionPath(pressedPath, true); |
| } |
| |
| pressedEvent = null; |
| pressedPath = null; |
| } |
| |
| public void mouseDragged(MouseEvent e) { |
| if (SwingUtilities2.shouldIgnore(e, tree)) { |
| return; |
| } |
| |
| if (tree.getDragEnabled()) { |
| DragRecognitionSupport.mouseDragged(e, this); |
| } |
| } |
| |
| /** |
| * Invoked when the mouse button has been moved on a component |
| * (with no buttons no down). |
| */ |
| public void mouseMoved(MouseEvent e) { |
| } |
| |
| public void mouseReleased(MouseEvent e) { |
| if (SwingUtilities2.shouldIgnore(e, tree)) { |
| return; |
| } |
| |
| if (tree.getDragEnabled()) { |
| mouseReleasedDND(e); |
| } |
| |
| pressedEvent = null; |
| pressedPath = null; |
| } |
| |
| private void mouseReleasedDND(MouseEvent e) { |
| MouseEvent me = DragRecognitionSupport.mouseReleased(e); |
| if (me != null) { |
| SwingUtilities2.adjustFocus(tree); |
| if (!dragPressDidSelection) { |
| handleSelection(me); |
| } |
| } |
| |
| if (!dragStarted) { |
| |
| // Note: We don't give the tree a chance to start editing if the |
| // mouse press caused a selection change. Otherwise the default |
| // tree cell editor will start editing on EVERY press and |
| // release. If it turns out that this affects some editors, we |
| // can always parameterize this with a client property. ex: |
| // |
| // if (pressedPath != null && |
| // (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") || |
| // !valueChangedOnPress) && ... |
| if (pressedPath != null && !valueChangedOnPress && |
| isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) { |
| |
| startEditingOnRelease(pressedPath, pressedEvent, e); |
| } |
| } |
| } |
| |
| // |
| // FocusListener |
| // |
| public void focusGained(FocusEvent e) { |
| if(tree != null) { |
| Rectangle pBounds; |
| |
| pBounds = getPathBounds(tree, tree.getLeadSelectionPath()); |
| if(pBounds != null) |
| tree.repaint(getRepaintPathBounds(pBounds)); |
| pBounds = getPathBounds(tree, getLeadSelectionPath()); |
| if(pBounds != null) |
| tree.repaint(getRepaintPathBounds(pBounds)); |
| } |
| } |
| |
| public void focusLost(FocusEvent e) { |
| focusGained(e); |
| } |
| |
| private Rectangle getRepaintPathBounds(Rectangle bounds) { |
| if(UIManager.getBoolean("Tree.repaintWholeRow")) { |
| bounds.x = 0; |
| bounds.width = tree.getWidth(); |
| } |
| return bounds; |
| } |
| |
| // |
| // CellEditorListener |
| // |
| public void editingStopped(ChangeEvent e) { |
| completeEditing(false, false, true); |
| } |
| |
| /** Messaged when editing has been canceled in the tree. */ |
| public void editingCanceled(ChangeEvent e) { |
| completeEditing(false, false, false); |
| } |
| |
| |
| // |
| // TreeSelectionListener |
| // |
| public void valueChanged(TreeSelectionEvent event) { |
| valueChangedOnPress = true; |
| |
| // Stop editing |
| completeEditing(); |
| // Make sure all the paths are visible, if necessary. |
| // PENDING: This should be tweaked when isAdjusting is added |
| if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) { |
| TreePath[] paths = treeSelectionModel |
| .getSelectionPaths(); |
| |
| if(paths != null) { |
| for(int counter = paths.length - 1; counter >= 0; |
| counter--) { |
| TreePath path = paths[counter].getParentPath(); |
| boolean expand = true; |
| |
| while (path != null) { |
| // Indicates this path isn't valid anymore, |
| // we shouldn't attempt to expand it then. |
| if (treeModel.isLeaf(path.getLastPathComponent())){ |
| expand = false; |
| path = null; |
| } |
| else { |
| path = path.getParentPath(); |
| } |
| } |
| if (expand) { |
| tree.makeVisible(paths[counter]); |
| } |
| } |
| } |
| } |
| |
| TreePath oldLead = getLeadSelectionPath(); |
| lastSelectedRow = tree.getMinSelectionRow(); |
| TreePath lead = tree.getSelectionModel().getLeadSelectionPath(); |
| setAnchorSelectionPath(lead); |
| setLeadSelectionPath(lead); |
| |
| TreePath[] changedPaths = event.getPaths(); |
| Rectangle nodeBounds; |
| Rectangle visRect = tree.getVisibleRect(); |
| boolean paintPaths = true; |
| int nWidth = tree.getWidth(); |
| |
| if(changedPaths != null) { |
| int counter, maxCounter = changedPaths.length; |
| |
| if(maxCounter > 4) { |
| tree.repaint(); |
| paintPaths = false; |
| } |
| else { |
| for (counter = 0; counter < maxCounter; counter++) { |
| nodeBounds = getPathBounds(tree, |
| changedPaths[counter]); |
| if(nodeBounds != null && |
| visRect.intersects(nodeBounds)) |
| tree.repaint(0, nodeBounds.y, nWidth, |
| nodeBounds.height); |
| } |
| } |
| } |
| if(paintPaths) { |
| nodeBounds = getPathBounds(tree, oldLead); |
| if(nodeBounds != null && visRect.intersects(nodeBounds)) |
| tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); |
| nodeBounds = getPathBounds(tree, lead); |
| if(nodeBounds != null && visRect.intersects(nodeBounds)) |
| tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height); |
| } |
| } |
| |
| |
| // |
| // TreeExpansionListener |
| // |
| public void treeExpanded(TreeExpansionEvent event) { |
| if(event != null && tree != null) { |
| TreePath path = event.getPath(); |
| |
| updateExpandedDescendants(path); |
| } |
| } |
| |
| public void treeCollapsed(TreeExpansionEvent event) { |
| if(event != null && tree != null) { |
| TreePath path = event.getPath(); |
| |
| completeEditing(); |
| if(path != null && tree.isVisible(path)) { |
| treeState.setExpandedState(path, false); |
| updateLeadRow(); |
| updateSize(); |
| } |
| } |
| } |
| |
| // |
| // TreeModelListener |
| // |
| public void treeNodesChanged(TreeModelEvent e) { |
| if(treeState != null && e != null) { |
| TreePath parentPath = e.getTreePath(); |
| int[] indices = e.getChildIndices(); |
| if (indices == null || indices.length == 0) { |
| // The root has changed |
| treeState.treeNodesChanged(e); |
| updateSize(); |
| } |
| else if (treeState.isExpanded(parentPath)) { |
| // Changed nodes are visible |
| // Find the minimum index, we only need paint from there |
| // down. |
| int minIndex = indices[0]; |
| for (int i = indices.length - 1; i > 0; i--) { |
| minIndex = Math.min(indices[i], minIndex); |
| } |
| Object minChild = treeModel.getChild( |
| parentPath.getLastPathComponent(), minIndex); |
| TreePath minPath = parentPath.pathByAddingChild(minChild); |
| Rectangle minBounds = getPathBounds(tree, minPath); |
| |
| // Forward to the treestate |
| treeState.treeNodesChanged(e); |
| |
| // Mark preferred size as bogus. |
| updateSize0(); |
| |
| // And repaint |
| Rectangle newMinBounds = getPathBounds(tree, minPath); |
| if (indices.length == 1 && |
| newMinBounds.height == minBounds.height) { |
| tree.repaint(0, minBounds.y, tree.getWidth(), |
| minBounds.height); |
| } |
| else { |
| tree.repaint(0, minBounds.y, tree.getWidth(), |
| tree.getHeight() - minBounds.y); |
| } |
| } |
| else { |
| // Nodes that changed aren't visible. No need to paint |
| treeState.treeNodesChanged(e); |
| } |
| } |
| } |
| |
| public void treeNodesInserted(TreeModelEvent e) { |
| if(treeState != null && e != null) { |
| treeState.treeNodesInserted(e); |
| |
| updateLeadRow(); |
| |
| TreePath path = e.getTreePath(); |
| |
| if(treeState.isExpanded(path)) { |
| updateSize(); |
| } |
| else { |
| // PENDING(sky): Need a method in TreeModelEvent |
| // that can return the count, getChildIndices allocs |
| // a new array! |
| int[] indices = e.getChildIndices(); |
| int childCount = treeModel.getChildCount |
| (path.getLastPathComponent()); |
| |
| if(indices != null && (childCount - indices.length) == 0) |
| updateSize(); |
| } |
| } |
| } |
| |
| public void treeNodesRemoved(TreeModelEvent e) { |
| if(treeState != null && e != null) { |
| treeState.treeNodesRemoved(e); |
| |
| updateLeadRow(); |
| |
| TreePath path = e.getTreePath(); |
| |
| if(treeState.isExpanded(path) || |
| treeModel.getChildCount(path.getLastPathComponent()) == 0) |
| updateSize(); |
| } |
| } |
| |
| public void treeStructureChanged(TreeModelEvent e) { |
| if(treeState != null && e != null) { |
| treeState.treeStructureChanged(e); |
| |
| updateLeadRow(); |
| |
| TreePath pPath = e.getTreePath(); |
| |
| if (pPath != null) { |
| pPath = pPath.getParentPath(); |
| } |
| if(pPath == null || treeState.isExpanded(pPath)) |
| updateSize(); |
| } |
| } |
| } |
| |
| |
| |
| private static class Actions extends UIAction { |
| private static final String SELECT_PREVIOUS = "selectPrevious"; |
| private static final String SELECT_PREVIOUS_CHANGE_LEAD = |
| "selectPreviousChangeLead"; |
| private static final String SELECT_PREVIOUS_EXTEND_SELECTION = |
| "selectPreviousExtendSelection"; |
| private static final String SELECT_NEXT = "selectNext"; |
| private static final String SELECT_NEXT_CHANGE_LEAD = |
| "selectNextChangeLead"; |
| private static final String SELECT_NEXT_EXTEND_SELECTION = |
| "selectNextExtendSelection"; |
| private static final String SELECT_CHILD = "selectChild"; |
| private static final String SELECT_CHILD_CHANGE_LEAD = |
| "selectChildChangeLead"; |
| private static final String SELECT_PARENT = "selectParent"; |
| private static final String SELECT_PARENT_CHANGE_LEAD = |
| "selectParentChangeLead"; |
| private static final String SCROLL_UP_CHANGE_SELECTION = |
| "scrollUpChangeSelection"; |
| private static final String SCROLL_UP_CHANGE_LEAD = |
| "scrollUpChangeLead"; |
| private static final String SCROLL_UP_EXTEND_SELECTION = |
| "scrollUpExtendSelection"; |
| private static final String SCROLL_DOWN_CHANGE_SELECTION = |
| "scrollDownChangeSelection"; |
| private static final String SCROLL_DOWN_EXTEND_SELECTION = |
| "scrollDownExtendSelection"; |
| private static final String SCROLL_DOWN_CHANGE_LEAD = |
| "scrollDownChangeLead"; |
| private static final String SELECT_FIRST = "selectFirst"; |
| private static final String SELECT_FIRST_CHANGE_LEAD = |
| "selectFirstChangeLead"; |
| private static final String SELECT_FIRST_EXTEND_SELECTION = |
| "selectFirstExtendSelection"; |
| private static final String SELECT_LAST = "selectLast"; |
| private static final String SELECT_LAST_CHANGE_LEAD = |
| "selectLastChangeLead"; |
| private static final String SELECT_LAST_EXTEND_SELECTION = |
| "selectLastExtendSelection"; |
| private static final String TOGGLE = "toggle"; |
| private static final String CANCEL_EDITING = "cancel"; |
| private static final String START_EDITING = "startEditing"; |
| private static final String SELECT_ALL = "selectAll"; |
| private static final String CLEAR_SELECTION = "clearSelection"; |
| private static final String SCROLL_LEFT = "scrollLeft"; |
| private static final String SCROLL_RIGHT = "scrollRight"; |
| private static final String SCROLL_LEFT_EXTEND_SELECTION = |
| "scrollLeftExtendSelection"; |
| private static final String SCROLL_RIGHT_EXTEND_SELECTION = |
| "scrollRightExtendSelection"; |
| private static final String SCROLL_RIGHT_CHANGE_LEAD = |
| "scrollRightChangeLead"; |
| private static final String SCROLL_LEFT_CHANGE_LEAD = |
| "scrollLeftChangeLead"; |
| private static final String EXPAND = "expand"; |
| private static final String COLLAPSE = "collapse"; |
| private static final String MOVE_SELECTION_TO_PARENT = |
| "moveSelectionToParent"; |
| |
| // add the lead item to the selection without changing lead or anchor |
| private static final String ADD_TO_SELECTION = "addToSelection"; |
| |
| // toggle the selected state of the lead item and move the anchor to it |
| private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor"; |
| |
| // extend the selection to the lead item |
| private static final String EXTEND_TO = "extendTo"; |
| |
| // move the anchor to the lead and ensure only that item is selected |
| private static final String MOVE_SELECTION_TO = "moveSelectionTo"; |
| |
| Actions() { |
| super(null); |
| } |
| |
| Actions(String key) { |
| super(key); |
| } |
| |
| public boolean isEnabled(Object o) { |
| if (o instanceof JTree) { |
| if (getName() == CANCEL_EDITING) { |
| return ((JTree)o).isEditing(); |
| } |
| } |
| return true; |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| JTree tree = (JTree)e.getSource(); |
| BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType( |
| tree.getUI(), BasicTreeUI.class); |
| if (ui == null) { |
| return; |
| } |
| String key = getName(); |
| if (key == SELECT_PREVIOUS) { |
| increment(tree, ui, -1, false, true); |
| } |
| else if (key == SELECT_PREVIOUS_CHANGE_LEAD) { |
| increment(tree, ui, -1, false, false); |
| } |
| else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) { |
| increment(tree, ui, -1, true, true); |
| } |
| else if (key == SELECT_NEXT) { |
| increment(tree, ui, 1, false, true); |
| } |
| else if (key == SELECT_NEXT_CHANGE_LEAD) { |
| increment(tree, ui, 1, false, false); |
| } |
| else if (key == SELECT_NEXT_EXTEND_SELECTION) { |
| increment(tree, ui, 1, true, true); |
| } |
| else if (key == SELECT_CHILD) { |
| traverse(tree, ui, 1, true); |
| } |
| else if (key == SELECT_CHILD_CHANGE_LEAD) { |
| traverse(tree, ui, 1, false); |
| } |
| else if (key == SELECT_PARENT) { |
| traverse(tree, ui, -1, true); |
| } |
| else if (key == SELECT_PARENT_CHANGE_LEAD) { |
| traverse(tree, ui, -1, false); |
| } |
| else if (key == SCROLL_UP_CHANGE_SELECTION) { |
| page(tree, ui, -1, false, true); |
| } |
| else if (key == SCROLL_UP_CHANGE_LEAD) { |
| page(tree, ui, -1, false, false); |
| } |
| else if (key == SCROLL_UP_EXTEND_SELECTION) { |
| page(tree, ui, -1, true, true); |
| } |
| else if (key == SCROLL_DOWN_CHANGE_SELECTION) { |
| page(tree, ui, 1, false, true); |
| } |
| else if (key == SCROLL_DOWN_EXTEND_SELECTION) { |
| page(tree, ui, 1, true, true); |
| } |
| else if (key == SCROLL_DOWN_CHANGE_LEAD) { |
| page(tree, ui, 1, false, false); |
| } |
| else if (key == SELECT_FIRST) { |
| home(tree, ui, -1, false, true); |
| } |
| else if (key == SELECT_FIRST_CHANGE_LEAD) { |
| home(tree, ui, -1, false, false); |
| } |
| else if (key == SELECT_FIRST_EXTEND_SELECTION) { |
| home(tree, ui, -1, true, true); |
| } |
| else if (key == SELECT_LAST) { |
| home(tree, ui, 1, false, true); |
| } |
| else if (key == SELECT_LAST_CHANGE_LEAD) { |
| home(tree, ui, 1, false, false); |
| } |
| else if (key == SELECT_LAST_EXTEND_SELECTION) { |
| home(tree, ui, 1, true, true); |
| } |
| else if (key == TOGGLE) { |
| toggle(tree, ui); |
| } |
| else if (key == CANCEL_EDITING) { |
| cancelEditing(tree, ui); |
| } |
| else if (key == START_EDITING) { |
| startEditing(tree, ui); |
| } |
| else if (key == SELECT_ALL) { |
| selectAll(tree, ui, true); |
| } |
| else if (key == CLEAR_SELECTION) { |
| selectAll(tree, ui, false); |
| } |
| else if (key == ADD_TO_SELECTION) { |
| if (ui.getRowCount(tree) > 0) { |
| int lead = ui.getLeadSelectionRow(); |
| if (!tree.isRowSelected(lead)) { |
| TreePath aPath = ui.getAnchorSelectionPath(); |
| tree.addSelectionRow(lead); |
| ui.setAnchorSelectionPath(aPath); |
| } |
| } |
| } |
| else if (key == TOGGLE_AND_ANCHOR) { |
| if (ui.getRowCount(tree) > 0) { |
| int lead = ui.getLeadSelectionRow(); |
| TreePath lPath = ui.getLeadSelectionPath(); |
| if (!tree.isRowSelected(lead)) { |
| tree.addSelectionRow(lead); |
| } else { |
| tree.removeSelectionRow(lead); |
| ui.setLeadSelectionPath(lPath); |
| } |
| ui.setAnchorSelectionPath(lPath); |
| } |
| } |
| else if (key == EXTEND_TO) { |
| extendSelection(tree, ui); |
| } |
| else if (key == MOVE_SELECTION_TO) { |
| if (ui.getRowCount(tree) > 0) { |
| int lead = ui.getLeadSelectionRow(); |
| tree.setSelectionInterval(lead, lead); |
| } |
| } |
| else if (key == SCROLL_LEFT) { |
| scroll(tree, ui, SwingConstants.HORIZONTAL, -10); |
| } |
| else if (key == SCROLL_RIGHT) { |
| scroll(tree, ui, SwingConstants.HORIZONTAL, 10); |
| } |
| else if (key == SCROLL_LEFT_EXTEND_SELECTION) { |
| scrollChangeSelection(tree, ui, -1, true, true); |
| } |
| else if (key == SCROLL_RIGHT_EXTEND_SELECTION) { |
| scrollChangeSelection(tree, ui, 1, true, true); |
| } |
| else if (key == SCROLL_RIGHT_CHANGE_LEAD) { |
| scrollChangeSelection(tree, ui, 1, false, false); |
| } |
| else if (key == SCROLL_LEFT_CHANGE_LEAD) { |
| scrollChangeSelection(tree, ui, -1, false, false); |
| } |
| else if (key == EXPAND) { |
| expand(tree, ui); |
| } |
| else if (key == COLLAPSE) { |
| collapse(tree, ui); |
| } |
| else if (key == MOVE_SELECTION_TO_PARENT) { |
| moveSelectionToParent(tree, ui); |
| } |
| } |
| |
| private void scrollChangeSelection(JTree tree, BasicTreeUI ui, |
| int direction, boolean addToSelection, |
| boolean changeSelection) { |
| int rowCount; |
| |
| if((rowCount = ui.getRowCount(tree)) > 0 && |
| ui.treeSelectionModel != null) { |
| TreePath newPath; |
| Rectangle visRect = tree.getVisibleRect(); |
| |
| if (direction == -1) { |
| newPath = ui.getClosestPathForLocation(tree, visRect.x, |
| visRect.y); |
| visRect.x = Math.max(0, visRect.x - visRect.width); |
| } |
| else { |
| visRect.x = Math.min(Math.max(0, tree.getWidth() - |
| visRect.width), visRect.x + visRect.width); |
| newPath = ui.getClosestPathForLocation(tree, visRect.x, |
| visRect.y + visRect.height); |
| } |
| // Scroll |
| tree.scrollRectToVisible(visRect); |
| // select |
| if (addToSelection) { |
| ui.extendSelection(newPath); |
| } |
| else if(changeSelection) { |
| tree.setSelectionPath(newPath); |
| } |
| else { |
| ui.setLeadSelectionPath(newPath, true); |
| } |
| } |
| } |
| |
| private void scroll(JTree component, BasicTreeUI ui, int direction, |
| int amount) { |
| Rectangle visRect = component.getVisibleRect(); |
| Dimension size = component.getSize(); |
| if (direction == SwingConstants.HORIZONTAL) { |
| visRect.x += amount; |
| visRect.x = Math.max(0, visRect.x); |
| visRect.x = Math.min(Math.max(0, size.width - visRect.width), |
| visRect.x); |
| } |
| else { |
| visRect.y += amount; |
| visRect.y = Math.max(0, visRect.y); |
| visRect.y = Math.min(Math.max(0, size.width - visRect.height), |
| visRect.y); |
| } |
| component.scrollRectToVisible(visRect); |
| } |
| |
| private void extendSelection(JTree tree, BasicTreeUI ui) { |
| if (ui.getRowCount(tree) > 0) { |
| int lead = ui.getLeadSelectionRow(); |
| |
| if (lead != -1) { |
| TreePath leadP = ui.getLeadSelectionPath(); |
| TreePath aPath = ui.getAnchorSelectionPath(); |
| int aRow = ui.getRowForPath(tree, aPath); |
| |
| if(aRow == -1) |
| aRow = 0; |
| tree.setSelectionInterval(aRow, lead); |
| ui.setLeadSelectionPath(leadP); |
| ui.setAnchorSelectionPath(aPath); |
| } |
| } |
| } |
| |
| private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) { |
| int rowCount = ui.getRowCount(tree); |
| |
| if(rowCount > 0) { |
| if(selectAll) { |
| if (tree.getSelectionModel().getSelectionMode() == |
| TreeSelectionModel.SINGLE_TREE_SELECTION) { |
| |
| int lead = ui.getLeadSelectionRow(); |
| if (lead != -1) { |
| tree.setSelectionRow(lead); |
| } else if (tree.getMinSelectionRow() == -1) { |
| tree.setSelectionRow(0); |
| ui.ensureRowsAreVisible(0, 0); |
| } |
| return; |
| } |
| |
| TreePath lastPath = ui.getLeadSelectionPath(); |
| TreePath aPath = ui.getAnchorSelectionPath(); |
| |
| if(lastPath != null && !tree.isVisible(lastPath)) { |
| lastPath = null; |
| } |
| tree.setSelectionInterval(0, rowCount - 1); |
| if(lastPath != null) { |
| ui.setLeadSelectionPath(lastPath); |
| } |
| if(aPath != null && tree.isVisible(aPath)) { |
| ui.setAnchorSelectionPath(aPath); |
| } |
| } |
| else { |
| TreePath lastPath = ui.getLeadSelectionPath(); |
| TreePath aPath = ui.getAnchorSelectionPath(); |
| |
| tree.clearSelection(); |
| ui.setAnchorSelectionPath(aPath); |
| ui.setLeadSelectionPath(lastPath); |
| } |
| } |
| } |
| |
| private void startEditing(JTree tree, BasicTreeUI ui) { |
| TreePath lead = ui.getLeadSelectionPath(); |
| int editRow = (lead != null) ? |
| ui.getRowForPath(tree, lead) : -1; |
| |
| if(editRow != -1) { |
| tree.startEditingAtPath(lead); |
| } |
| } |
| |
| private void cancelEditing(JTree tree, BasicTreeUI ui) { |
| tree.cancelEditing(); |
| } |
| |
| private void toggle(JTree tree, BasicTreeUI ui) { |
| int selRow = ui.getLeadSelectionRow(); |
| |
| if(selRow != -1 && !ui.isLeaf(selRow)) { |
| TreePath aPath = ui.getAnchorSelectionPath(); |
| TreePath lPath = ui.getLeadSelectionPath(); |
| |
| ui.toggleExpandState(ui.getPathForRow(tree, selRow)); |
| ui.setAnchorSelectionPath(aPath); |
| ui.setLeadSelectionPath(lPath); |
| } |
| } |
| |
| private void expand(JTree tree, BasicTreeUI ui) { |
| int selRow = ui.getLeadSelectionRow(); |
| tree.expandRow(selRow); |
| } |
| |
| private void collapse(JTree tree, BasicTreeUI ui) { |
| int selRow = ui.getLeadSelectionRow(); |
| tree.collapseRow(selRow); |
| } |
| |
| private void increment(JTree tree, BasicTreeUI ui, int direction, |
| boolean addToSelection, |
| boolean changeSelection) { |
| |
| // disable moving of lead unless in discontiguous mode |
| if (!addToSelection && !changeSelection && |
| tree.getSelectionModel().getSelectionMode() != |
| TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { |
| changeSelection = true; |
| } |
| |
| int rowCount; |
| |
| if(ui.treeSelectionModel != null && |
| (rowCount = tree.getRowCount()) > 0) { |
| int selIndex = ui.getLeadSelectionRow(); |
| int newIndex; |
| |
| if(selIndex == -1) { |
| if(direction == 1) |
| newIndex = 0; |
| else |
| newIndex = rowCount - 1; |
| } |
| else |
| /* Aparently people don't like wrapping;( */ |
| newIndex = Math.min(rowCount - 1, Math.max |
| (0, (selIndex + direction))); |
| if(addToSelection && ui.treeSelectionModel. |
| getSelectionMode() != TreeSelectionModel. |
| SINGLE_TREE_SELECTION) { |
| ui.extendSelection(tree.getPathForRow(newIndex)); |
| } |
| else if(changeSelection) { |
| tree.setSelectionInterval(newIndex, newIndex); |
| } |
| else { |
| ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true); |
| } |
| ui.ensureRowsAreVisible(newIndex, newIndex); |
| ui.lastSelectedRow = newIndex; |
| } |
| } |
| |
| private void traverse(JTree tree, BasicTreeUI ui, int direction, |
| boolean changeSelection) { |
| |
| // disable moving of lead unless in discontiguous mode |
| if (!changeSelection && |
| tree.getSelectionModel().getSelectionMode() != |
| TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { |
| changeSelection = true; |
| } |
| |
| int rowCount; |
| |
| if((rowCount = tree.getRowCount()) > 0) { |
| int minSelIndex = ui.getLeadSelectionRow(); |
| int newIndex; |
| |
| if(minSelIndex == -1) |
| newIndex = 0; |
| else { |
| /* Try and expand the node, otherwise go to next |
| node. */ |
| if(direction == 1) { |
| TreePath minSelPath = ui.getPathForRow(tree, minSelIndex); |
| int childCount = tree.getModel(). |
| getChildCount(minSelPath.getLastPathComponent()); |
| newIndex = -1; |
| if (!ui.isLeaf(minSelIndex)) { |
| if (!tree.isExpanded(minSelIndex)) { |
| ui.toggleExpandState(minSelPath); |
| } |
| else if (childCount > 0) { |
| newIndex = Math.min(minSelIndex + 1, rowCount - 1); |
| } |
| } |
| } |
| /* Try to collapse node. */ |
| else { |
| if(!ui.isLeaf(minSelIndex) && |
| tree.isExpanded(minSelIndex)) { |
| ui.toggleExpandState(ui.getPathForRow |
| (tree, minSelIndex)); |
| newIndex = -1; |
| } |
| else { |
| TreePath path = ui.getPathForRow(tree, |
| minSelIndex); |
| |
| if(path != null && path.getPathCount() > 1) { |
| newIndex = ui.getRowForPath(tree, path. |
| getParentPath()); |
| } |
| else |
| newIndex = -1; |
| } |
| } |
| } |
| if(newIndex != -1) { |
| if(changeSelection) { |
| tree.setSelectionInterval(newIndex, newIndex); |
| } |
| else { |
| ui.setLeadSelectionPath(ui.getPathForRow( |
| tree, newIndex), true); |
| } |
| ui.ensureRowsAreVisible(newIndex, newIndex); |
| } |
| } |
| } |
| |
| private void moveSelectionToParent(JTree tree, BasicTreeUI ui) { |
| int selRow = ui.getLeadSelectionRow(); |
| TreePath path = ui.getPathForRow(tree, selRow); |
| if (path != null && path.getPathCount() > 1) { |
| int newIndex = ui.getRowForPath(tree, path.getParentPath()); |
| if (newIndex != -1) { |
| tree.setSelectionInterval(newIndex, newIndex); |
| ui.ensureRowsAreVisible(newIndex, newIndex); |
| } |
| } |
| } |
| |
| private void page(JTree tree, BasicTreeUI ui, int direction, |
| boolean addToSelection, boolean changeSelection) { |
| |
| // disable moving of lead unless in discontiguous mode |
| if (!addToSelection && !changeSelection && |
| tree.getSelectionModel().getSelectionMode() != |
| TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { |
| changeSelection = true; |
| } |
| |
| int rowCount; |
| |
| if((rowCount = ui.getRowCount(tree)) > 0 && |
| ui.treeSelectionModel != null) { |
| Dimension maxSize = tree.getSize(); |
| TreePath lead = ui.getLeadSelectionPath(); |
| TreePath newPath; |
| Rectangle visRect = tree.getVisibleRect(); |
| |
| if(direction == -1) { |
| // up. |
| newPath = ui.getClosestPathForLocation(tree, visRect.x, |
| visRect.y); |
| if(newPath.equals(lead)) { |
| visRect.y = Math.max(0, visRect.y - visRect.height); |
| newPath = tree.getClosestPathForLocation(visRect.x, |
| visRect.y); |
| } |
| } |
| else { |
| // down |
| visRect.y = Math.min(maxSize.height, visRect.y + |
| visRect.height - 1); |
| newPath = tree.getClosestPathForLocation(visRect.x, |
| visRect.y); |
| if(newPath.equals(lead)) { |
| visRect.y = Math.min(maxSize.height, visRect.y + |
| visRect.height - 1); |
| newPath = tree.getClosestPathForLocation(visRect.x, |
| visRect.y); |
| } |
| } |
| Rectangle newRect = ui.getPathBounds(tree, newPath); |
| |
| newRect.x = visRect.x; |
| newRect.width = visRect.width; |
| if(direction == -1) { |
| newRect.height = visRect.height; |
| } |
| else { |
| newRect.y -= (visRect.height - newRect.height); |
| newRect.height = visRect.height; |
| } |
| |
| if(addToSelection) { |
| ui.extendSelection(newPath); |
| } |
| else if(changeSelection) { |
| tree.setSelectionPath(newPath); |
| } |
| else { |
| ui.setLeadSelectionPath(newPath, true); |
| } |
| tree.scrollRectToVisible(newRect); |
| } |
| } |
| |
| private void home(JTree tree, BasicTreeUI ui, int direction, |
| boolean addToSelection, boolean changeSelection) { |
| |
| // disable moving of lead unless in discontiguous mode |
| if (!addToSelection && !changeSelection && |
| tree.getSelectionModel().getSelectionMode() != |
| TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) { |
| changeSelection = true; |
| } |
| |
| int rowCount = ui.getRowCount(tree); |
| |
| if (rowCount > 0) { |
| if(direction == -1) { |
| ui.ensureRowsAreVisible(0, 0); |
| if (addToSelection) { |
| TreePath aPath = ui.getAnchorSelectionPath(); |
| int aRow = (aPath == null) ? -1 : |
| ui.getRowForPath(tree, aPath); |
| |
| if (aRow == -1) { |
| tree.setSelectionInterval(0, 0); |
| } |
| else { |
| tree.setSelectionInterval(0, aRow); |
| ui.setAnchorSelectionPath(aPath); |
| ui.setLeadSelectionPath(ui.getPathForRow(tree, 0)); |
| } |
| } |
| else if(changeSelection) { |
| tree.setSelectionInterval(0, 0); |
| } |
| else { |
| ui.setLeadSelectionPath(ui.getPathForRow(tree, 0), |
| true); |
| } |
| } |
| else { |
| ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1); |
| if (addToSelection) { |
| TreePath aPath = ui.getAnchorSelectionPath(); |
| int aRow = (aPath == null) ? -1 : |
| ui.getRowForPath(tree, aPath); |
| |
| if (aRow == -1) { |
| tree.setSelectionInterval(rowCount - 1, |
| rowCount -1); |
| } |
| else { |
| tree.setSelectionInterval(aRow, rowCount - 1); |
| ui.setAnchorSelectionPath(aPath); |
| ui.setLeadSelectionPath(ui.getPathForRow(tree, |
| rowCount -1)); |
| } |
| } |
| else if(changeSelection) { |
| tree.setSelectionInterval(rowCount - 1, rowCount - 1); |
| } |
| else { |
| ui.setLeadSelectionPath(ui.getPathForRow(tree, |
| rowCount - 1), true); |
| } |
| } |
| } |
| } |
| } |
| } // End of class BasicTreeUI |