2 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax.swing.plaf.basic;
41 import gnu.classpath.NotImplementedException;
42 import gnu.javax.swing.tree.GnuPath;
44 import java.awt.Color;
45 import java.awt.Component;
46 import java.awt.Dimension;
48 import java.awt.FontMetrics;
49 import java.awt.Graphics;
50 import java.awt.Insets;
51 import java.awt.Label;
52 import java.awt.Rectangle;
53 import java.awt.event.ActionEvent;
54 import java.awt.event.ActionListener;
55 import java.awt.event.ComponentAdapter;
56 import java.awt.event.ComponentEvent;
57 import java.awt.event.ComponentListener;
58 import java.awt.event.FocusEvent;
59 import java.awt.event.FocusListener;
60 import java.awt.event.InputEvent;
61 import java.awt.event.KeyAdapter;
62 import java.awt.event.KeyEvent;
63 import java.awt.event.KeyListener;
64 import java.awt.event.MouseAdapter;
65 import java.awt.event.MouseEvent;
66 import java.awt.event.MouseListener;
67 import java.awt.event.MouseMotionListener;
68 import java.beans.PropertyChangeEvent;
69 import java.beans.PropertyChangeListener;
70 import java.util.Enumeration;
71 import java.util.Hashtable;
73 import javax.swing.AbstractAction;
74 import javax.swing.Action;
75 import javax.swing.ActionMap;
76 import javax.swing.CellRendererPane;
77 import javax.swing.Icon;
78 import javax.swing.InputMap;
79 import javax.swing.JComponent;
80 import javax.swing.JScrollBar;
81 import javax.swing.JScrollPane;
82 import javax.swing.JTree;
83 import javax.swing.LookAndFeel;
84 import javax.swing.SwingUtilities;
85 import javax.swing.Timer;
86 import javax.swing.UIManager;
87 import javax.swing.event.CellEditorListener;
88 import javax.swing.event.ChangeEvent;
89 import javax.swing.event.MouseInputListener;
90 import javax.swing.event.TreeExpansionEvent;
91 import javax.swing.event.TreeExpansionListener;
92 import javax.swing.event.TreeModelEvent;
93 import javax.swing.event.TreeModelListener;
94 import javax.swing.event.TreeSelectionEvent;
95 import javax.swing.event.TreeSelectionListener;
96 import javax.swing.plaf.ActionMapUIResource;
97 import javax.swing.plaf.ComponentUI;
98 import javax.swing.plaf.TreeUI;
99 import javax.swing.tree.AbstractLayoutCache;
100 import javax.swing.tree.DefaultTreeCellEditor;
101 import javax.swing.tree.DefaultTreeCellRenderer;
102 import javax.swing.tree.TreeCellEditor;
103 import javax.swing.tree.TreeCellRenderer;
104 import javax.swing.tree.TreeModel;
105 import javax.swing.tree.TreeNode;
106 import javax.swing.tree.TreePath;
107 import javax.swing.tree.TreeSelectionModel;
108 import javax.swing.tree.VariableHeightLayoutCache;
111 * A delegate providing the user interface for <code>JTree</code> according to
112 * the Basic look and feel.
114 * @see javax.swing.JTree
115 * @author Lillian Angel (langel@redhat.com)
116 * @author Sascha Brawer (brawer@dandelis.ch)
117 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
119 public class BasicTreeUI
123 * The tree cell editing may be started by the single mouse click on the
124 * selected cell. To separate it from the double mouse click, the editing
125 * session starts after this time (in ms) after that single click, and only no
126 * other clicks were performed during that time.
128 static int WAIT_TILL_EDITING = 900;
130 /** Collapse Icon for the tree. */
131 protected transient Icon collapsedIcon;
133 /** Expanded Icon for the tree. */
134 protected transient Icon expandedIcon;
136 /** Distance between left margin and where vertical dashes will be drawn. */
137 protected int leftChildIndent;
140 * Distance between leftChildIndent and where cell contents will be drawn.
142 protected int rightChildIndent;
145 * Total fistance that will be indented. The sum of leftChildIndent and
148 protected int totalChildIndent;
150 /** Index of the row that was last selected. */
151 protected int lastSelectedRow;
153 /** Component that we're going to be drawing onto. */
154 protected JTree tree;
156 /** Renderer that is being used to do the actual cell drawing. */
157 protected transient TreeCellRenderer currentCellRenderer;
160 * Set to true if the renderer that is currently in the tree was created by
163 protected boolean createdRenderer;
165 /** Editor for the tree. */
166 protected transient TreeCellEditor cellEditor;
169 * Set to true if editor that is currently in the tree was created by this
172 protected boolean createdCellEditor;
175 * Set to false when editing and shouldSelectCall() returns true meaning the
176 * node should be selected before editing, used in completeEditing.
177 * GNU Classpath editing is implemented differently, so this value is not
178 * actually read anywhere. However it is always set correctly to maintain
179 * interoperability with the derived classes that read this field.
181 protected boolean stopEditingInCompleteEditing;
183 /** Used to paint the TreeCellRenderer. */
184 protected CellRendererPane rendererPane;
186 /** Size needed to completely display all the nodes. */
187 protected Dimension preferredSize;
189 /** Minimum size needed to completely display all the nodes. */
190 protected Dimension preferredMinSize;
192 /** Is the preferredSize valid? */
193 protected boolean validCachedPreferredSize;
195 /** Object responsible for handling sizing and expanded issues. */
196 protected AbstractLayoutCache treeState;
198 /** Used for minimizing the drawing of vertical lines. */
199 protected Hashtable drawingCache;
202 * True if doing optimizations for a largeModel. Subclasses that don't support
203 * this may wish to override createLayoutCache to not return a
204 * FixedHeightLayoutCache instance.
206 protected boolean largeModel;
208 /** Responsible for telling the TreeState the size needed for a node. */
209 protected AbstractLayoutCache.NodeDimensions nodeDimensions;
211 /** Used to determine what to display. */
212 protected TreeModel treeModel;
214 /** Model maintaining the selection. */
215 protected TreeSelectionModel treeSelectionModel;
218 * How much the depth should be offset to properly calculate x locations. This
219 * is based on whether or not the root is visible, and if the root handles are
222 protected int depthOffset;
225 * When editing, this will be the Component that is doing the actual editing.
227 protected Component editingComponent;
229 /** Path that is being edited. */
230 protected TreePath editingPath;
233 * Row that is being edited. Should only be referenced if editingComponent is
236 protected int editingRow;
238 /** Set to true if the editor has a different size than the renderer. */
239 protected boolean editorHasDifferentSize;
241 /** Boolean to keep track of editing. */
244 /** The current path of the visible nodes in the tree. */
245 TreePath currentVisiblePath;
247 /** The gap between the icon and text. */
250 /** The max height of the nodes in the tree. */
253 /** The hash color. */
257 PropertyChangeListener propertyChangeListener;
259 FocusListener focusListener;
261 TreeSelectionListener treeSelectionListener;
263 MouseListener mouseListener;
265 KeyListener keyListener;
267 PropertyChangeListener selectionModelPropertyChangeListener;
269 ComponentListener componentListener;
271 CellEditorListener cellEditorListener;
273 TreeExpansionListener treeExpansionListener;
275 TreeModelListener treeModelListener;
278 * This timer fires the editing action after about 1200 ms if not reset during
279 * that time. It handles the editing start with the single mouse click (and
280 * not the double mouse click) on the selected tree node.
282 Timer startEditTimer;
285 * The zero size icon, used for expand controls, if they are not visible.
287 static Icon nullIcon;
290 * The special value of the mouse event is sent indicating that this is not
291 * just the mouse click, but the mouse click on the selected node. Sending
292 * such event forces to start the cell editing session.
294 static final MouseEvent EDIT = new MouseEvent(new Label(), 7, 7, 7, 7, 7, 7,
298 * Creates a new BasicTreeUI object.
302 validCachedPreferredSize = false;
303 drawingCache = new Hashtable();
304 nodeDimensions = createNodeDimensions();
305 configureLayoutCache();
308 lastSelectedRow = - 1;
312 * Returns an instance of the UI delegate for the specified component.
314 * @param c the <code>JComponent</code> for which we need a UI delegate for.
315 * @return the <code>ComponentUI</code> for c.
317 public static ComponentUI createUI(JComponent c)
319 return new BasicTreeUI();
323 * Returns the Hash color.
325 * @return the <code>Color</code> of the Hash.
327 protected Color getHashColor()
333 * Sets the Hash color.
335 * @param color the <code>Color</code> to set the Hash to.
337 protected void setHashColor(Color color)
343 * Sets the left child's indent value.
345 * @param newAmount is the new indent value for the left child.
347 public void setLeftChildIndent(int newAmount)
349 leftChildIndent = newAmount;
353 * Returns the indent value for the left child.
355 * @return the indent value for the left child.
357 public int getLeftChildIndent()
359 return leftChildIndent;
363 * Sets the right child's indent value.
365 * @param newAmount is the new indent value for the right child.
367 public void setRightChildIndent(int newAmount)
369 rightChildIndent = newAmount;
373 * Returns the indent value for the right child.
375 * @return the indent value for the right child.
377 public int getRightChildIndent()
379 return rightChildIndent;
383 * Sets the expanded icon.
385 * @param newG is the new expanded icon.
387 public void setExpandedIcon(Icon newG)
393 * Returns the current expanded icon.
395 * @return the current expanded icon.
397 public Icon getExpandedIcon()
403 * Sets the collapsed icon.
405 * @param newG is the new collapsed icon.
407 public void setCollapsedIcon(Icon newG)
409 collapsedIcon = newG;
413 * Returns the current collapsed icon.
415 * @return the current collapsed icon.
417 public Icon getCollapsedIcon()
419 return collapsedIcon;
423 * Updates the componentListener, if necessary.
425 * @param largeModel sets this.largeModel to it.
427 protected void setLargeModel(boolean largeModel)
429 if (largeModel != this.largeModel)
431 tree.removeComponentListener(componentListener);
432 this.largeModel = largeModel;
433 tree.addComponentListener(componentListener);
438 * Returns true if largeModel is set
440 * @return true if largeModel is set, otherwise false.
442 protected boolean isLargeModel()
448 * Sets the row height.
450 * @param rowHeight is the height to set this.rowHeight to.
452 protected void setRowHeight(int rowHeight)
455 rowHeight = getMaxHeight(tree);
456 treeState.setRowHeight(rowHeight);
460 * Returns the current row height.
462 * @return current row height.
464 protected int getRowHeight()
466 return tree.getRowHeight();
470 * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
471 * <code>updateRenderer</code>.
473 * @param tcr is the new TreeCellRenderer.
475 protected void setCellRenderer(TreeCellRenderer tcr)
477 // Finish editing before changing the renderer.
480 // The renderer is set in updateRenderer.
483 // Refresh the layout if necessary.
484 if (treeState != null)
486 treeState.invalidateSizes();
492 * Return currentCellRenderer, which will either be the trees renderer, or
493 * defaultCellRenderer, which ever was not null.
495 * @return the current Cell Renderer
497 protected TreeCellRenderer getCellRenderer()
499 if (currentCellRenderer != null)
500 return currentCellRenderer;
502 return createDefaultCellRenderer();
506 * Sets the tree's model.
508 * @param model to set the treeModel to.
510 protected void setModel(TreeModel model)
514 if (treeModel != null && treeModelListener != null)
515 treeModel.removeTreeModelListener(treeModelListener);
517 treeModel = tree.getModel();
519 if (treeModel != null && treeModelListener != null)
520 treeModel.addTreeModelListener(treeModelListener);
522 if (treeState != null)
524 treeState.setModel(treeModel);
525 updateLayoutCacheExpandedNodes();
531 * Returns the tree's model
535 protected TreeModel getModel()
541 * Sets the root to being visible.
543 * @param newValue sets the visibility of the root
545 protected void setRootVisible(boolean newValue)
547 tree.setRootVisible(newValue);
551 * Returns true if the root is visible.
553 * @return true if the root is visible.
555 protected boolean isRootVisible()
557 return tree.isRootVisible();
561 * Determines whether the node handles are to be displayed.
563 * @param newValue sets whether or not node handles should be displayed.
565 protected void setShowsRootHandles(boolean newValue)
569 if (treeState != null)
571 treeState.invalidateSizes();
577 * Returns true if the node handles are to be displayed.
579 * @return true if the node handles are to be displayed.
581 protected boolean getShowsRootHandles()
583 return tree.getShowsRootHandles();
587 * Sets the cell editor.
589 * @param editor to set the cellEditor to.
591 protected void setCellEditor(TreeCellEditor editor)
594 createdCellEditor = true;
598 * Returns the <code>TreeCellEditor</code> for this tree.
600 * @return the cellEditor for this tree.
602 protected TreeCellEditor getCellEditor()
608 * Configures the receiver to allow, or not allow, editing.
610 * @param newValue sets the receiver to allow editing if true.
612 protected void setEditable(boolean newValue)
614 tree.setEditable(newValue);
618 * Returns true if the receiver allows editing.
620 * @return true if the receiver allows editing.
622 protected boolean isEditable()
624 return tree.isEditable();
628 * Resets the selection model. The appropriate listeners are installed on the
631 * @param newLSM resets the selection model.
633 protected void setSelectionModel(TreeSelectionModel newLSM)
637 treeSelectionModel = newLSM;
638 tree.setSelectionModel(treeSelectionModel);
643 * Returns the current selection model.
645 * @return the current selection model.
647 protected TreeSelectionModel getSelectionModel()
649 return treeSelectionModel;
653 * Returns the Rectangle enclosing the label portion that the last item in
654 * path will be drawn to. Will return null if any component in path is
657 * @param tree is the current tree the path will be drawn to.
658 * @param path is the current path the tree to draw to.
659 * @return the Rectangle enclosing the label portion that the last item in the
660 * path will be drawn to.
662 public Rectangle getPathBounds(JTree tree, TreePath path)
664 return treeState.getBounds(path, new Rectangle());
668 * Returns the max height of all the nodes in the tree.
670 * @param tree - the current tree
671 * @return the max height.
673 int getMaxHeight(JTree tree)
678 Icon e = UIManager.getIcon("Tree.openIcon");
679 Icon c = UIManager.getIcon("Tree.closedIcon");
680 Icon l = UIManager.getIcon("Tree.leafIcon");
681 int rc = getRowCount(tree);
684 for (int row = 0; row < rc; row++)
687 iconHeight = l.getIconHeight();
688 else if (tree.isExpanded(row))
689 iconHeight = e.getIconHeight();
691 iconHeight = c.getIconHeight();
693 maxHeight = Math.max(maxHeight, iconHeight + gap);
696 treeState.setRowHeight(maxHeight);
701 * Get the tree node icon.
703 Icon getNodeIcon(TreePath path)
705 Object node = path.getLastPathComponent();
706 if (treeModel.isLeaf(node))
707 return UIManager.getIcon("Tree.leafIcon");
708 else if (treeState.getExpandedState(path))
709 return UIManager.getIcon("Tree.openIcon");
711 return UIManager.getIcon("Tree.closedIcon");
715 * Returns the path for passed in row. If row is not visible null is returned.
717 * @param tree is the current tree to return path for.
718 * @param row is the row number of the row to return.
719 * @return the path for passed in row. If row is not visible null is returned.
721 public TreePath getPathForRow(JTree tree, int row)
723 return treeState.getPathForRow(row);
727 * Returns the row that the last item identified in path is visible at. Will
728 * return -1 if any of the elments in the path are not currently visible.
730 * @param tree is the current tree to return the row for.
731 * @param path is the path used to find the row.
732 * @return the row that the last item identified in path is visible at. Will
733 * return -1 if any of the elments in the path are not currently
736 public int getRowForPath(JTree tree, TreePath path)
738 return treeState.getRowForPath(path);
742 * Returns the number of rows that are being displayed.
744 * @param tree is the current tree to return the number of rows for.
745 * @return the number of rows being displayed.
747 public int getRowCount(JTree tree)
749 return treeState.getRowCount();
753 * Returns the path to the node that is closest to x,y. If there is nothing
754 * currently visible this will return null, otherwise it'll always return a
755 * valid path. If you need to test if the returned object is exactly at x,y
756 * you should get the bounds for the returned path and test x,y against that.
758 * @param tree the tree to search for the closest path
759 * @param x is the x coordinate of the location to search
760 * @param y is the y coordinate of the location to search
761 * @return the tree path closes to x,y.
763 public TreePath getClosestPathForLocation(JTree tree, int x, int y)
765 return treeState.getPathClosestTo(x, y);
769 * Returns true if the tree is being edited. The item that is being edited can
770 * be returned by getEditingPath().
772 * @param tree is the tree to check for editing.
773 * @return true if the tree is being edited.
775 public boolean isEditing(JTree tree)
781 * Stops the current editing session. This has no effect if the tree is not
782 * being edited. Returns true if the editor allows the editing session to
785 * @param tree is the tree to stop the editing on
786 * @return true if the editor allows the editing session to stop.
788 public boolean stopEditing(JTree tree)
792 completeEditing(false, false, true);
795 return ! isEditing(tree);
799 * Cancels the current editing session.
801 * @param tree is the tree to cancel the editing session on.
803 public void cancelEditing(JTree tree)
805 // There is no need to send the cancel message to the editor,
806 // as the cancellation event itself arrives from it. This would
807 // only be necessary when cancelling the editing programatically.
808 completeEditing(false, false, false);
813 * Selects the last item in path and tries to edit it. Editing will fail if
814 * the CellEditor won't allow it for the selected item.
816 * @param tree is the tree to edit on.
817 * @param path is the path in tree to edit on.
819 public void startEditingAtPath(JTree tree, TreePath path)
821 startEditing(path, null);
825 * Returns the path to the element that is being editted.
827 * @param tree is the tree to get the editing path from.
828 * @return the path that is being edited.
830 public TreePath getEditingPath(JTree tree)
836 * Invoked after the tree instance variable has been set, but before any
837 * default/listeners have been installed.
839 protected void prepareForUIInstall()
841 lastSelectedRow = -1;
842 preferredSize = new Dimension();
843 largeModel = tree.isLargeModel();
844 preferredSize = new Dimension();
845 setModel(tree.getModel());
849 * Invoked from installUI after all the defaults/listeners have been
852 protected void completeUIInstall()
854 setShowsRootHandles(tree.getShowsRootHandles());
857 setSelectionModel(tree.getSelectionModel());
858 configureLayoutCache();
859 treeState.setRootVisible(tree.isRootVisible());
860 treeSelectionModel.setRowMapper(treeState);
865 * Invoked from uninstallUI after all the defaults/listeners have been
868 protected void completeUIUninstall()
874 * Installs the subcomponents of the tree, which is the renderer pane.
876 protected void installComponents()
878 currentCellRenderer = createDefaultCellRenderer();
879 rendererPane = createCellRendererPane();
880 createdRenderer = true;
881 setCellRenderer(currentCellRenderer);
885 * Creates an instance of NodeDimensions that is able to determine the size of
886 * a given node in the tree. The node dimensions must be created before
887 * configuring the layout cache.
889 * @return the NodeDimensions of a given node in the tree
891 protected AbstractLayoutCache.NodeDimensions createNodeDimensions()
893 return new NodeDimensionsHandler();
897 * Creates a listener that is reponsible for the updates the UI based on how
900 * @return the PropertyChangeListener that is reposnsible for the updates
902 protected PropertyChangeListener createPropertyChangeListener()
904 return new PropertyChangeHandler();
908 * Creates the listener responsible for updating the selection based on mouse
911 * @return the MouseListener responsible for updating.
913 protected MouseListener createMouseListener()
915 return new MouseHandler();
919 * Creates the listener that is responsible for updating the display when
920 * focus is lost/grained.
922 * @return the FocusListener responsible for updating.
924 protected FocusListener createFocusListener()
926 return new FocusHandler();
930 * Creates the listener reponsible for getting key events from the tree.
932 * @return the KeyListener responsible for getting key events.
934 protected KeyListener createKeyListener()
936 return new KeyHandler();
940 * Creates the listener responsible for getting property change events from
941 * the selection model.
943 * @returns the PropertyChangeListener reponsible for getting property change
944 * events from the selection model.
946 protected PropertyChangeListener createSelectionModelPropertyChangeListener()
948 return new SelectionModelPropertyChangeHandler();
952 * Creates the listener that updates the display based on selection change
955 * @return the TreeSelectionListener responsible for updating.
957 protected TreeSelectionListener createTreeSelectionListener()
959 return new TreeSelectionHandler();
963 * Creates a listener to handle events from the current editor
965 * @return the CellEditorListener that handles events from the current editor
967 protected CellEditorListener createCellEditorListener()
969 return new CellEditorHandler();
973 * Creates and returns a new ComponentHandler. This is used for the large
974 * model to mark the validCachedPreferredSize as invalid when the component
977 * @return a new ComponentHandler.
979 protected ComponentListener createComponentListener()
981 return new ComponentHandler();
985 * Creates and returns the object responsible for updating the treestate when
986 * a nodes expanded state changes.
988 * @return the TreeExpansionListener responsible for updating the treestate
990 protected TreeExpansionListener createTreeExpansionListener()
992 return new TreeExpansionHandler();
996 * Creates the object responsible for managing what is expanded, as well as
999 * @return the object responsible for managing what is expanded.
1001 protected AbstractLayoutCache createLayoutCache()
1003 return new VariableHeightLayoutCache();
1007 * Returns the renderer pane that renderer components are placed in.
1009 * @return the rendererpane that render components are placed in.
1011 protected CellRendererPane createCellRendererPane()
1013 return new CellRendererPane();
1017 * Creates a default cell editor.
1019 * @return the default cell editor.
1021 protected TreeCellEditor createDefaultCellEditor()
1023 DefaultTreeCellEditor ed;
1024 if (currentCellRenderer != null
1025 && currentCellRenderer instanceof DefaultTreeCellRenderer)
1026 ed = new DefaultTreeCellEditor(tree,
1027 (DefaultTreeCellRenderer) currentCellRenderer);
1029 ed = new DefaultTreeCellEditor(tree, null);
1034 * Returns the default cell renderer that is used to do the stamping of each
1037 * @return the default cell renderer that is used to do the stamping of each
1040 protected TreeCellRenderer createDefaultCellRenderer()
1042 return new DefaultTreeCellRenderer();
1046 * Returns a listener that can update the tree when the model changes.
1048 * @return a listener that can update the tree when the model changes.
1050 protected TreeModelListener createTreeModelListener()
1052 return new TreeModelHandler();
1056 * Uninstall all registered listeners
1058 protected void uninstallListeners()
1060 tree.removePropertyChangeListener(propertyChangeListener);
1061 tree.removeFocusListener(focusListener);
1062 tree.removeTreeSelectionListener(treeSelectionListener);
1063 tree.removeMouseListener(mouseListener);
1064 tree.removeKeyListener(keyListener);
1065 tree.removePropertyChangeListener(selectionModelPropertyChangeListener);
1066 tree.removeComponentListener(componentListener);
1067 tree.removeTreeExpansionListener(treeExpansionListener);
1069 TreeCellEditor tce = tree.getCellEditor();
1071 tce.removeCellEditorListener(cellEditorListener);
1072 if (treeModel != null)
1073 treeModel.removeTreeModelListener(treeModelListener);
1077 * Uninstall all keyboard actions.
1079 protected void uninstallKeyboardActions()
1081 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1083 tree.getActionMap().setParent(null);
1087 * Uninstall the rendererPane.
1089 protected void uninstallComponents()
1091 currentCellRenderer = null;
1092 rendererPane = null;
1093 createdRenderer = false;
1094 setCellRenderer(currentCellRenderer);
1098 * The vertical element of legs between nodes starts at the bottom of the
1099 * parent node by default. This method makes the leg start below that.
1101 * @return the vertical leg buffer
1103 protected int getVerticalLegBuffer()
1105 return getRowHeight() / 2;
1109 * The horizontal element of legs between nodes starts at the right of the
1110 * left-hand side of the child node by default. This method makes the leg end
1113 * @return the horizontal leg buffer
1115 protected int getHorizontalLegBuffer()
1117 return rightChildIndent / 2;
1121 * Make all the nodes that are expanded in JTree expanded in LayoutCache. This
1122 * invokes updateExpandedDescendants with the root path.
1124 protected void updateLayoutCacheExpandedNodes()
1126 if (treeModel != null && treeModel.getRoot() != null)
1127 updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1131 * Updates the expanded state of all the descendants of the <code>path</code>
1132 * by getting the expanded descendants from the tree and forwarding to the
1135 * @param path the path used to update the expanded states
1137 protected void updateExpandedDescendants(TreePath path)
1139 Enumeration expanded = tree.getExpandedDescendants(path);
1140 while (expanded.hasMoreElements())
1141 treeState.setExpandedState((TreePath) expanded.nextElement(), true);
1145 * Returns a path to the last child of <code>parent</code>
1147 * @param parent is the topmost path to specified
1148 * @return a path to the last child of parent
1150 protected TreePath getLastChildPath(TreePath parent)
1152 return (TreePath) parent.getLastPathComponent();
1156 * Updates how much each depth should be offset by.
1158 protected void updateDepthOffset()
1160 depthOffset += getVerticalLegBuffer();
1164 * Updates the cellEditor based on editability of the JTree that we're
1165 * contained in. If the tree is editable but doesn't have a cellEditor, a
1166 * basic one will be used.
1168 protected void updateCellEditor()
1170 if (tree.isEditable() && cellEditor == null)
1171 setCellEditor(createDefaultCellEditor());
1172 createdCellEditor = true;
1176 * Messaged from the tree we're in when the renderer has changed.
1178 protected void updateRenderer()
1182 TreeCellRenderer rend = tree.getCellRenderer();
1185 createdRenderer = false;
1186 currentCellRenderer = rend;
1187 if (createdCellEditor)
1188 tree.setCellEditor(null);
1192 tree.setCellRenderer(createDefaultCellRenderer());
1193 createdRenderer = true;
1198 currentCellRenderer = null;
1199 createdRenderer = false;
1206 * Resets the treeState instance based on the tree we're providing the look
1207 * and feel for. The node dimensions handler is required and must be created
1210 protected void configureLayoutCache()
1212 treeState = createLayoutCache();
1213 treeState.setNodeDimensions(nodeDimensions);
1217 * Marks the cached size as being invalid, and messages the tree with
1218 * <code>treeDidChange</code>.
1220 protected void updateSize()
1222 preferredSize = null;
1223 updateCachedPreferredSize();
1224 tree.treeDidChange();
1228 * Updates the <code>preferredSize</code> instance variable, which is
1229 * returned from <code>getPreferredSize()</code>.
1231 protected void updateCachedPreferredSize()
1233 validCachedPreferredSize = false;
1237 * Messaged from the VisibleTreeNode after it has been expanded.
1239 * @param path is the path that has been expanded.
1241 protected void pathWasExpanded(TreePath path)
1243 validCachedPreferredSize = false;
1244 treeState.setExpandedState(path, true);
1249 * Messaged from the VisibleTreeNode after it has collapsed
1251 protected void pathWasCollapsed(TreePath path)
1253 validCachedPreferredSize = false;
1254 treeState.setExpandedState(path, false);
1259 * Install all defaults for the tree.
1261 protected void installDefaults()
1263 LookAndFeel.installColorsAndFont(tree, "Tree.background",
1264 "Tree.foreground", "Tree.font");
1266 hashColor = UIManager.getColor("Tree.hash");
1267 if (hashColor == null)
1268 hashColor = Color.black;
1270 tree.setOpaque(true);
1272 rightChildIndent = UIManager.getInt("Tree.rightChildIndent");
1273 leftChildIndent = UIManager.getInt("Tree.leftChildIndent");
1274 totalChildIndent = rightChildIndent + leftChildIndent;
1275 setRowHeight(UIManager.getInt("Tree.rowHeight"));
1276 tree.setRowHeight(getRowHeight());
1277 tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand"));
1278 setExpandedIcon(UIManager.getIcon("Tree.expandedIcon"));
1279 setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon"));
1283 * Install all keyboard actions for this
1285 protected void installKeyboardActions()
1287 InputMap focusInputMap =
1288 (InputMap) SharedUIDefaults.get("Tree.focusInputMap");
1289 SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED,
1291 InputMap ancestorInputMap =
1292 (InputMap) SharedUIDefaults.get("Tree.ancestorInputMap");
1293 SwingUtilities.replaceUIInputMap(tree,
1294 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1297 SwingUtilities.replaceUIActionMap(tree, getActionMap());
1301 * Creates and returns the shared action map for JTrees.
1303 * @return the shared action map for JTrees
1305 private ActionMap getActionMap()
1307 ActionMap am = (ActionMap) UIManager.get("Tree.actionMap");
1310 am = createDefaultActions();
1311 UIManager.getLookAndFeelDefaults().put("Tree.actionMap", am);
1317 * Creates the default actions when there are none specified by the L&F.
1319 * @return the default actions
1321 private ActionMap createDefaultActions()
1323 ActionMapUIResource am = new ActionMapUIResource();
1327 action = new TreeHomeAction(-1, "selectFirst");
1328 am.put(action.getValue(Action.NAME), action);
1329 action = new TreeHomeAction(-1, "selectFirstChangeLead");
1330 am.put(action.getValue(Action.NAME), action);
1331 action = new TreeHomeAction(-1, "selectFirstExtendSelection");
1332 am.put(action.getValue(Action.NAME), action);
1333 action = new TreeHomeAction(1, "selectLast");
1334 am.put(action.getValue(Action.NAME), action);
1335 action = new TreeHomeAction(1, "selectLastChangeLead");
1336 am.put(action.getValue(Action.NAME), action);
1337 action = new TreeHomeAction(1, "selectLastExtendSelection");
1338 am.put(action.getValue(Action.NAME), action);
1340 // TreeIncrementAction.
1341 action = new TreeIncrementAction(-1, "selectPrevious");
1342 am.put(action.getValue(Action.NAME), action);
1343 action = new TreeIncrementAction(-1, "selectPreviousExtendSelection");
1344 am.put(action.getValue(Action.NAME), action);
1345 action = new TreeIncrementAction(-1, "selectPreviousChangeLead");
1346 am.put(action.getValue(Action.NAME), action);
1347 action = new TreeIncrementAction(1, "selectNext");
1348 am.put(action.getValue(Action.NAME), action);
1349 action = new TreeIncrementAction(1, "selectNextExtendSelection");
1350 am.put(action.getValue(Action.NAME), action);
1351 action = new TreeIncrementAction(1, "selectNextChangeLead");
1352 am.put(action.getValue(Action.NAME), action);
1354 // TreeTraverseAction.
1355 action = new TreeTraverseAction(-1, "selectParent");
1356 am.put(action.getValue(Action.NAME), action);
1357 action = new TreeTraverseAction(1, "selectChild");
1358 am.put(action.getValue(Action.NAME), action);
1360 // TreeToggleAction.
1361 action = new TreeToggleAction("toggleAndAnchor");
1362 am.put(action.getValue(Action.NAME), action);
1365 action = new TreePageAction(-1, "scrollUpChangeSelection");
1366 am.put(action.getValue(Action.NAME), action);
1367 action = new TreePageAction(-1, "scrollUpExtendSelection");
1368 am.put(action.getValue(Action.NAME), action);
1369 action = new TreePageAction(-1, "scrollUpChangeLead");
1370 am.put(action.getValue(Action.NAME), action);
1371 action = new TreePageAction(1, "scrollDownChangeSelection");
1372 am.put(action.getValue(Action.NAME), action);
1373 action = new TreePageAction(1, "scrollDownExtendSelection");
1374 am.put(action.getValue(Action.NAME), action);
1375 action = new TreePageAction(1, "scrollDownChangeLead");
1376 am.put(action.getValue(Action.NAME), action);
1378 // Tree editing actions
1379 action = new TreeStartEditingAction("startEditing");
1380 am.put(action.getValue(Action.NAME), action);
1381 action = new TreeCancelEditingAction("cancel");
1382 am.put(action.getValue(Action.NAME), action);
1389 * Converts the modifiers.
1391 * @param mod - modifier to convert
1392 * @returns the new modifier
1394 private int convertModifiers(int mod)
1396 if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
1398 mod |= KeyEvent.SHIFT_MASK;
1399 mod &= ~ KeyEvent.SHIFT_DOWN_MASK;
1401 if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
1403 mod |= KeyEvent.CTRL_MASK;
1404 mod &= ~ KeyEvent.CTRL_DOWN_MASK;
1406 if ((mod & KeyEvent.META_DOWN_MASK) != 0)
1408 mod |= KeyEvent.META_MASK;
1409 mod &= ~ KeyEvent.META_DOWN_MASK;
1411 if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
1413 mod |= KeyEvent.ALT_MASK;
1414 mod &= ~ KeyEvent.ALT_DOWN_MASK;
1416 if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
1418 mod |= KeyEvent.ALT_GRAPH_MASK;
1419 mod &= ~ KeyEvent.ALT_GRAPH_DOWN_MASK;
1425 * Install all listeners for this
1427 protected void installListeners()
1429 propertyChangeListener = createPropertyChangeListener();
1430 tree.addPropertyChangeListener(propertyChangeListener);
1432 focusListener = createFocusListener();
1433 tree.addFocusListener(focusListener);
1435 treeSelectionListener = createTreeSelectionListener();
1436 tree.addTreeSelectionListener(treeSelectionListener);
1438 mouseListener = createMouseListener();
1439 tree.addMouseListener(mouseListener);
1441 keyListener = createKeyListener();
1442 tree.addKeyListener(keyListener);
1444 selectionModelPropertyChangeListener =
1445 createSelectionModelPropertyChangeListener();
1446 if (treeSelectionModel != null
1447 && selectionModelPropertyChangeListener != null)
1449 treeSelectionModel.addPropertyChangeListener(
1450 selectionModelPropertyChangeListener);
1453 componentListener = createComponentListener();
1454 tree.addComponentListener(componentListener);
1456 treeExpansionListener = createTreeExpansionListener();
1457 tree.addTreeExpansionListener(treeExpansionListener);
1459 treeModelListener = createTreeModelListener();
1460 if (treeModel != null)
1461 treeModel.addTreeModelListener(treeModelListener);
1463 cellEditorListener = createCellEditorListener();
1467 * Install the UI for the component
1469 * @param c the component to install UI for
1471 public void installUI(JComponent c)
1475 prepareForUIInstall();
1477 installComponents();
1478 installKeyboardActions();
1480 completeUIInstall();
1484 * Uninstall the defaults for the tree
1486 protected void uninstallDefaults()
1489 tree.setForeground(null);
1490 tree.setBackground(null);
1494 * Uninstall the UI for the component
1496 * @param c the component to uninstall UI for
1498 public void uninstallUI(JComponent c)
1502 prepareForUIUninstall();
1503 uninstallDefaults();
1504 uninstallKeyboardActions();
1505 uninstallListeners();
1506 uninstallComponents();
1507 completeUIUninstall();
1511 * Paints the specified component appropriate for the look and feel. This
1512 * method is invoked from the ComponentUI.update method when the specified
1513 * component is being painted. Subclasses should override this method and use
1514 * the specified Graphics object to render the content of the component.
1516 * @param g the Graphics context in which to paint
1517 * @param c the component being painted; this argument is often ignored, but
1518 * might be used if the UI object is stateless and shared by multiple
1521 public void paint(Graphics g, JComponent c)
1523 JTree tree = (JTree) c;
1525 int rows = treeState.getRowCount();
1528 // There is nothing to do if the tree is empty.
1531 Rectangle clip = g.getClipBounds();
1533 Insets insets = tree.getInsets();
1535 if (clip != null && treeModel != null)
1537 int startIndex = tree.getClosestRowForLocation(clip.x, clip.y);
1538 int endIndex = tree.getClosestRowForLocation(clip.x + clip.width,
1539 clip.y + clip.height);
1541 // Also paint dashes to the invisible nodes below.
1542 // These should be painted first, otherwise they may cover
1543 // the control icons.
1544 if (endIndex < rows)
1545 for (int i = endIndex + 1; i < rows; i++)
1547 TreePath path = treeState.getPathForRow(i);
1548 if (isLastChild(path))
1549 paintVerticalPartOfLeg(g, clip, insets, path);
1552 // The two loops are required to ensure that the lines are not
1553 // painted over the other tree components.
1555 int n = endIndex - startIndex + 1;
1556 Rectangle[] bounds = new Rectangle[n];
1557 boolean[] isLeaf = new boolean[n];
1558 boolean[] isExpanded = new boolean[n];
1559 TreePath[] path = new TreePath[n];
1563 for (int i = startIndex; i <= endIndex; i++, k++)
1565 path[k] = treeState.getPathForRow(i);
1566 isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent());
1567 isExpanded[k] = tree.isExpanded(path[k]);
1568 bounds[k] = getPathBounds(tree, path[k]);
1570 paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k], i,
1571 isExpanded[k], false, isLeaf[k]);
1572 if (isLastChild(path[k]))
1573 paintVerticalPartOfLeg(g, clip, insets, path[k]);
1577 for (int i = startIndex; i <= endIndex; i++, k++)
1579 paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k],
1586 * Check if the path is referring to the last child of some parent.
1588 private boolean isLastChild(TreePath path)
1590 if (path instanceof GnuPath)
1592 // Except the seldom case when the layout cache is changed, this
1593 // optimized code will be executed.
1594 return ((GnuPath) path).isLastChild;
1598 // Non optimized general case.
1599 TreePath parent = path.getParentPath();
1602 int childCount = treeState.getVisibleChildCount(parent);
1603 int p = treeModel.getIndexOfChild(parent, path.getLastPathComponent());
1604 return p == childCount - 1;
1609 * Ensures that the rows identified by beginRow through endRow are visible.
1611 * @param beginRow is the first row
1612 * @param endRow is the last row
1614 protected void ensureRowsAreVisible(int beginRow, int endRow)
1616 if (beginRow < endRow)
1623 for (int i = beginRow; i < endRow; i++)
1625 TreePath path = getPathForRow(tree, i);
1626 if (! tree.isVisible(path))
1627 tree.makeVisible(path);
1632 * Sets the preferred minimum size.
1634 * @param newSize is the new preferred minimum size.
1636 public void setPreferredMinSize(Dimension newSize)
1638 preferredMinSize = newSize;
1642 * Gets the preferred minimum size.
1644 * @returns the preferred minimum size.
1646 public Dimension getPreferredMinSize()
1648 if (preferredMinSize == null)
1649 return getPreferredSize(tree);
1651 return preferredMinSize;
1655 * Returns the preferred size to properly display the tree, this is a cover
1656 * method for getPreferredSize(c, false).
1658 * @param c the component whose preferred size is being queried; this argument
1659 * is often ignored but might be used if the UI object is stateless
1660 * and shared by multiple components
1661 * @return the preferred size
1663 public Dimension getPreferredSize(JComponent c)
1665 return getPreferredSize(c, false);
1669 * Returns the preferred size to represent the tree in c. If checkConsistancy
1670 * is true, checkConsistancy is messaged first.
1672 * @param c the component whose preferred size is being queried.
1673 * @param checkConsistancy if true must check consistancy
1674 * @return the preferred size
1676 public Dimension getPreferredSize(JComponent c, boolean checkConsistancy)
1678 if (! validCachedPreferredSize)
1680 Rectangle size = tree.getBounds();
1681 // Add the scrollbar dimensions to the preferred size.
1682 preferredSize = new Dimension(treeState.getPreferredWidth(size),
1683 treeState.getPreferredHeight());
1684 validCachedPreferredSize = true;
1686 return preferredSize;
1690 * Returns the minimum size for this component. Which will be the min
1691 * preferred size or (0,0).
1693 * @param c the component whose min size is being queried.
1694 * @returns the preferred size or null
1696 public Dimension getMinimumSize(JComponent c)
1698 return preferredMinSize = getPreferredSize(c);
1702 * Returns the maximum size for the component, which will be the preferred
1703 * size if the instance is currently in JTree or (0,0).
1705 * @param c the component whose preferred size is being queried
1706 * @return the max size or null
1708 public Dimension getMaximumSize(JComponent c)
1710 return getPreferredSize(c);
1714 * Messages to stop the editing session. If the UI the receiver is providing
1715 * the look and feel for returns true from
1716 * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked
1717 * on the current editor. Then completeEditing will be messaged with false,
1718 * true, false to cancel any lingering editing.
1720 protected void completeEditing()
1722 completeEditing(false, true, false);
1726 * Stops the editing session. If messageStop is true, the editor is messaged
1727 * with stopEditing, if messageCancel is true the editor is messaged with
1728 * cancelEditing. If messageTree is true, the treeModel is messaged with
1729 * valueForPathChanged.
1731 * @param messageStop message to stop editing
1732 * @param messageCancel message to cancel editing
1733 * @param messageTree message to treeModel
1735 protected void completeEditing(boolean messageStop, boolean messageCancel,
1736 boolean messageTree)
1738 // Make no attempt to complete the non existing editing session.
1739 if (!isEditing(tree))
1744 getCellEditor().stopCellEditing();
1745 stopEditingInCompleteEditing = true;
1750 getCellEditor().cancelCellEditing();
1751 stopEditingInCompleteEditing = true;
1756 TreeCellEditor editor = getCellEditor();
1759 Object value = editor.getCellEditorValue();
1760 treeModel.valueForPathChanged(tree.getLeadSelectionPath(), value);
1766 * Will start editing for node if there is a cellEditor and shouldSelectCall
1767 * returns true. This assumes that path is valid and visible.
1769 * @param path is the path to start editing
1770 * @param event is the MouseEvent performed on the path
1771 * @return true if successful
1773 protected boolean startEditing(TreePath path, MouseEvent event)
1776 TreeCellEditor ed = getCellEditor();
1778 if (ed != null && (event == EDIT || ed.shouldSelectCell(event))
1779 && ed.isCellEditable(event))
1781 Rectangle bounds = getPathBounds(tree, path);
1783 // Extend the right boundary till the tree width.
1784 bounds.width = tree.getWidth() - bounds.x;
1787 editingRow = tree.getRowForPath(editingPath);
1789 Object value = editingPath.getLastPathComponent();
1791 stopEditingInCompleteEditing = false;
1792 boolean expanded = tree.isExpanded(editingPath);
1794 editingComponent = ed.getTreeCellEditorComponent(tree, value, true,
1799 // Remove all previous components (if still present). Only one
1800 // container with the editing component inside is allowed in the tree.
1803 // The editing component must be added to its container. We add the
1804 // container, not the editing component itself.
1805 Component container = editingComponent.getParent();
1806 container.setBounds(bounds);
1807 tree.add(container);
1808 editingComponent.requestFocus();
1816 * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or
1817 * collapse region of the row, this will toggle the row.
1819 * @param path the path we are concerned with
1820 * @param mouseX is the cursor's x position
1821 * @param mouseY is the cursor's y position
1823 protected void checkForClickInExpandControl(TreePath path, int mouseX,
1826 if (isLocationInExpandControl(path, mouseX, mouseY))
1827 handleExpandControlClick(path, mouseX, mouseY);
1831 * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in
1832 * the area of row that is used to expand/collpse the node and the node at row
1833 * does not represent a leaf.
1835 * @param path the path we are concerned with
1836 * @param mouseX is the cursor's x position
1837 * @param mouseY is the cursor's y position
1838 * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in
1839 * the area of row that is used to expand/collpse the node and the
1840 * node at row does not represent a leaf.
1842 protected boolean isLocationInExpandControl(TreePath path, int mouseX,
1845 boolean cntlClick = false;
1846 if (! treeModel.isLeaf(path.getLastPathComponent()))
1849 Icon expandedIcon = getExpandedIcon();
1850 if (expandedIcon != null)
1851 width = expandedIcon.getIconWidth();
1853 // Only guessing. This is the width of
1854 // the tree control icon in Metal L&F.
1857 Insets i = tree.getInsets();
1860 if (isRootVisible())
1861 depth = path.getPathCount()-1;
1863 depth = path.getPathCount()-2;
1865 int left = getRowX(tree.getRowForPath(path), depth)
1867 cntlClick = mouseX >= left && mouseX <= left + width;
1873 * Messaged when the user clicks the particular row, this invokes
1874 * toggleExpandState.
1876 * @param path the path we are concerned with
1877 * @param mouseX is the cursor's x position
1878 * @param mouseY is the cursor's y position
1880 protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY)
1882 toggleExpandState(path);
1886 * Expands path if it is not expanded, or collapses row if it is expanded. If
1887 * expanding a path and JTree scroll on expand, ensureRowsAreVisible is
1888 * invoked to scroll as many of the children to visible as possible (tries to
1889 * scroll to last visible descendant of path).
1891 * @param path the path we are concerned with
1893 protected void toggleExpandState(TreePath path)
1895 // tree.isExpanded(path) would do the same, but treeState knows faster.
1896 if (treeState.isExpanded(path))
1897 tree.collapsePath(path);
1899 tree.expandPath(path);
1903 * Returning true signifies a mouse event on the node should toggle the
1904 * selection of only the row under the mouse. The BasisTreeUI treats the
1905 * event as "toggle selection event" if the CTRL button was pressed while
1906 * clicking. The event is not counted as toggle event if the associated
1907 * tree does not support the multiple selection.
1909 * @param event is the MouseEvent performed on the row.
1910 * @return true signifies a mouse event on the node should toggle the
1911 * selection of only the row under the mouse.
1913 protected boolean isToggleSelectionEvent(MouseEvent event)
1916 (tree.getSelectionModel().getSelectionMode() !=
1917 TreeSelectionModel.SINGLE_TREE_SELECTION) &&
1918 ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0);
1922 * Returning true signifies a mouse event on the node should select from the
1923 * anchor point. The BasisTreeUI treats the event as "multiple selection
1924 * event" if the SHIFT button was pressed while clicking. The event is not
1925 * counted as multiple selection event if the associated tree does not support
1926 * the multiple selection.
1928 * @param event is the MouseEvent performed on the node.
1929 * @return true signifies a mouse event on the node should select from the
1932 protected boolean isMultiSelectEvent(MouseEvent event)
1935 (tree.getSelectionModel().getSelectionMode() !=
1936 TreeSelectionModel.SINGLE_TREE_SELECTION) &&
1937 ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0);
1941 * Returning true indicates the row under the mouse should be toggled based on
1942 * the event. This is invoked after checkForClickInExpandControl, implying the
1943 * location is not in the expand (toggle) control.
1945 * @param event is the MouseEvent performed on the row.
1946 * @return true indicates the row under the mouse should be toggled based on
1949 protected boolean isToggleEvent(MouseEvent event)
1951 boolean toggle = false;
1952 if (SwingUtilities.isLeftMouseButton(event))
1954 int clickCount = tree.getToggleClickCount();
1955 if (clickCount > 0 && event.getClickCount() == clickCount)
1962 * Messaged to update the selection based on a MouseEvent over a particular
1963 * row. If the even is a toggle selection event, the row is either selected,
1964 * or deselected. If the event identifies a multi selection event, the
1965 * selection is updated from the anchor point. Otherwise, the row is selected,
1966 * and the previous selection is cleared.</p>
1968 * @param path is the path selected for an event
1969 * @param event is the MouseEvent performed on the path.
1971 * @see #isToggleSelectionEvent(MouseEvent)
1972 * @see #isMultiSelectEvent(MouseEvent)
1974 protected void selectPathForEvent(TreePath path, MouseEvent event)
1976 if (isToggleSelectionEvent(event))
1978 // The event selects or unselects the clicked row.
1979 if (tree.isPathSelected(path))
1980 tree.removeSelectionPath(path);
1983 tree.addSelectionPath(path);
1984 tree.setAnchorSelectionPath(path);
1987 else if (isMultiSelectEvent(event))
1989 // The event extends selection form anchor till the clicked row.
1990 TreePath anchor = tree.getAnchorSelectionPath();
1993 int aRow = getRowForPath(tree, anchor);
1994 tree.addSelectionInterval(aRow, getRowForPath(tree, path));
1997 tree.addSelectionPath(path);
2001 // This is an ordinary event that just selects the clicked row.
2002 tree.setSelectionPath(path);
2003 if (isToggleEvent(event))
2004 toggleExpandState(path);
2009 * Returns true if the node at <code>row</code> is a leaf.
2011 * @param row is the row we are concerned with.
2012 * @return true if the node at <code>row</code> is a leaf.
2014 protected boolean isLeaf(int row)
2016 TreePath pathForRow = getPathForRow(tree, row);
2017 if (pathForRow == null)
2020 Object node = pathForRow.getLastPathComponent();
2021 return treeModel.isLeaf(node);
2025 * The action to start editing at the current lead selection path.
2027 class TreeStartEditingAction
2028 extends AbstractAction
2031 * Creates the new tree cancel editing action.
2033 * @param name the name of the action (used in toString).
2035 public TreeStartEditingAction(String name)
2041 * Start editing at the current lead selection path.
2043 * @param e the ActionEvent that caused this action.
2045 public void actionPerformed(ActionEvent e)
2047 TreePath lead = tree.getLeadSelectionPath();
2048 if (!tree.isEditing())
2049 tree.startEditingAtPath(lead);
2054 * Updates the preferred size when scrolling, if necessary.
2056 public class ComponentHandler
2057 extends ComponentAdapter
2058 implements ActionListener
2061 * Timer used when inside a scrollpane and the scrollbar is adjusting
2063 protected Timer timer;
2065 /** ScrollBar that is being adjusted */
2066 protected JScrollBar scrollBar;
2071 public ComponentHandler()
2073 // Nothing to do here.
2077 * Invoked when the component's position changes.
2079 * @param e the event that occurs when moving the component
2081 public void componentMoved(ComponentEvent e)
2085 JScrollPane scrollPane = getScrollPane();
2086 if (scrollPane == null)
2090 // Determine the scrollbar that is adjusting, if any, and
2091 // start the timer for that. If no scrollbar is adjusting,
2092 // we simply call updateSize().
2093 scrollBar = scrollPane.getVerticalScrollBar();
2094 if (scrollBar == null || !scrollBar.getValueIsAdjusting())
2096 // It's not the vertical scrollbar, try the horizontal one.
2097 scrollBar = scrollPane.getHorizontalScrollBar();
2098 if (scrollBar != null && scrollBar.getValueIsAdjusting())
2112 * Creates, if necessary, and starts a Timer to check if needed to resize
2115 protected void startTimer()
2119 timer = new Timer(200, this);
2120 timer.setRepeats(true);
2126 * Returns the JScrollPane housing the JTree, or null if one isn't found.
2128 * @return JScrollPane housing the JTree, or null if one isn't found.
2130 protected JScrollPane getScrollPane()
2132 JScrollPane found = null;
2133 Component p = tree.getParent();
2134 while (p != null && !(p instanceof JScrollPane))
2136 if (p instanceof JScrollPane)
2137 found = (JScrollPane) p;
2142 * Public as a result of Timer. If the scrollBar is null, or not adjusting,
2143 * this stops the timer and updates the sizing.
2145 * @param ae is the action performed
2147 public void actionPerformed(ActionEvent ae)
2149 if (scrollBar == null || !scrollBar.getValueIsAdjusting())
2161 * Listener responsible for getting cell editing events and updating the tree
2164 public class CellEditorHandler
2165 implements CellEditorListener
2170 public CellEditorHandler()
2172 // Nothing to do here.
2176 * Messaged when editing has stopped in the tree. Tells the listeners
2177 * editing has stopped.
2179 * @param e is the notification event
2181 public void editingStopped(ChangeEvent e)
2187 * Messaged when editing has been canceled in the tree. This tells the
2188 * listeners the editor has canceled editing.
2190 * @param e is the notification event
2192 public void editingCanceled(ChangeEvent e)
2194 cancelEditing(tree);
2196 } // CellEditorHandler
2199 * Repaints the lead selection row when focus is lost/grained.
2201 public class FocusHandler
2202 implements FocusListener
2207 public FocusHandler()
2209 // Nothing to do here.
2213 * Invoked when focus is activated on the tree we're in, redraws the lead
2214 * row. Invoked when a component gains the keyboard focus. The method
2215 * repaints the lead row that is shown differently when the tree is in
2218 * @param e is the focus event that is activated
2220 public void focusGained(FocusEvent e)
2226 * Invoked when focus is deactivated on the tree we're in, redraws the lead
2227 * row. Invoked when a component loses the keyboard focus. The method
2228 * repaints the lead row that is shown differently when the tree is in
2231 * @param e is the focus event that is deactivated
2233 public void focusLost(FocusEvent e)
2239 * Repaint the lead row.
2241 void repaintLeadRow()
2243 TreePath lead = tree.getLeadSelectionPath();
2245 tree.repaint(tree.getPathBounds(lead));
2250 * This is used to get multiple key down events to appropriately genereate
2253 public class KeyHandler
2256 /** Key code that is being generated for. */
2257 protected Action repeatKeyAction;
2259 /** Set to true while keyPressed is active */
2260 protected boolean isKeyDown;
2267 // Nothing to do here.
2271 * Invoked when a key has been typed. Moves the keyboard focus to the first
2272 * element whose first letter matches the alphanumeric key pressed by the
2273 * user. Subsequent same key presses move the keyboard focus to the next
2274 * object that starts with the same letter.
2276 * @param e the key typed
2278 public void keyTyped(KeyEvent e)
2280 char typed = Character.toLowerCase(e.getKeyChar());
2281 for (int row = tree.getLeadSelectionRow() + 1;
2282 row < tree.getRowCount(); row++)
2284 if (checkMatch(row, typed))
2286 tree.setSelectionRow(row);
2287 tree.scrollRowToVisible(row);
2292 // Not found below, search above:
2293 for (int row = 0; row < tree.getLeadSelectionRow(); row++)
2295 if (checkMatch(row, typed))
2297 tree.setSelectionRow(row);
2298 tree.scrollRowToVisible(row);
2305 * Check if the given tree row starts with this character
2307 * @param row the tree row
2308 * @param typed the typed char, must be converted to lowercase
2309 * @return true if the given tree row starts with this character
2311 boolean checkMatch(int row, char typed)
2313 TreePath path = treeState.getPathForRow(row);
2314 String node = path.getLastPathComponent().toString();
2315 if (node.length() > 0)
2317 char x = node.charAt(0);
2318 if (typed == Character.toLowerCase(x))
2325 * Invoked when a key has been pressed.
2327 * @param e the key pressed
2329 public void keyPressed(KeyEvent e)
2331 // Nothing to do here.
2335 * Invoked when a key has been released
2337 * @param e the key released
2339 public void keyReleased(KeyEvent e)
2341 // Nothing to do here.
2346 * MouseListener is responsible for updating the selection based on mouse
2349 public class MouseHandler
2350 extends MouseAdapter
2351 implements MouseMotionListener
2356 public MouseHandler()
2358 // Nothing to do here.
2362 * Invoked when a mouse button has been pressed on a component.
2364 * @param e is the mouse event that occured
2366 public void mousePressed(MouseEvent e)
2368 // Any mouse click cancels the previous waiting edit action, initiated
2369 // by the single click on the selected node.
2370 if (startEditTimer != null)
2372 startEditTimer.stop();
2373 startEditTimer = null;
2376 if (tree != null && tree.isEnabled())
2378 // Always end the current editing session if clicked on the
2379 // tree and outside the bounds of the editing component.
2380 if (isEditing(tree))
2381 if (!stopEditing(tree))
2382 // Return if we have failed to cancel the editing session.
2387 TreePath path = getClosestPathForLocation(tree, x, y);
2391 Rectangle bounds = getPathBounds(tree, path);
2392 if (SwingUtilities.isLeftMouseButton(e))
2393 checkForClickInExpandControl(path, x, y);
2395 if (x > bounds.x && x <= (bounds.x + bounds.width))
2397 TreePath currentLead = tree.getLeadSelectionPath();
2398 if (currentLead != null && currentLead.equals(path)
2399 && e.getClickCount() == 1 && tree.isEditable())
2401 // Schedule the editing session.
2402 final TreePath editPath = path;
2404 // The code below handles the required click-pause-click
2405 // functionality which must be present in the tree UI.
2406 // If the next click comes after the
2407 // time longer than the double click interval AND
2408 // the same node stays focused for the WAIT_TILL_EDITING
2409 // duration, the timer starts the editing session.
2410 if (startEditTimer != null)
2411 startEditTimer.stop();
2413 startEditTimer = new Timer(WAIT_TILL_EDITING,
2414 new ActionListener()
2416 public void actionPerformed(ActionEvent e)
2418 startEditing(editPath, EDIT);
2422 startEditTimer.setRepeats(false);
2423 startEditTimer.start();
2427 if (e.getClickCount() == 2)
2428 toggleExpandState(path);
2430 selectPathForEvent(path, e);
2436 // We need to request the focus.
2437 tree.requestFocusInWindow();
2441 * Invoked when a mouse button is pressed on a component and then dragged.
2442 * MOUSE_DRAGGED events will continue to be delivered to the component where
2443 * the drag originated until the mouse button is released (regardless of
2444 * whether the mouse position is within the bounds of the component).
2446 * @param e is the mouse event that occured
2448 public void mouseDragged(MouseEvent e)
2449 throws NotImplementedException
2451 // TODO: What should be done here, if anything?
2455 * Invoked when the mouse button has been moved on a component (with no
2458 * @param e the mouse event that occured
2460 public void mouseMoved(MouseEvent e)
2461 throws NotImplementedException
2463 // TODO: What should be done here, if anything?
2467 * Invoked when a mouse button has been released on a component.
2469 * @param e is the mouse event that occured
2471 public void mouseReleased(MouseEvent e)
2472 throws NotImplementedException
2474 // TODO: What should be done here, if anything?
2479 * MouseInputHandler handles passing all mouse events, including mouse motion
2480 * events, until the mouse is released to the destination it is constructed
2483 public class MouseInputHandler
2484 implements MouseInputListener
2486 /** Source that events are coming from */
2487 protected Component source;
2489 /** Destination that receives all events. */
2490 protected Component destination;
2495 * @param source that events are coming from
2496 * @param destination that receives all events
2497 * @param e is the event received
2499 public MouseInputHandler(Component source, Component destination,
2502 this.source = source;
2503 this.destination = destination;
2507 * Invoked when the mouse button has been clicked (pressed and released) on
2510 * @param e mouse event that occured
2512 public void mouseClicked(MouseEvent e)
2513 throws NotImplementedException
2515 // TODO: What should be done here, if anything?
2519 * Invoked when a mouse button has been pressed on a component.
2521 * @param e mouse event that occured
2523 public void mousePressed(MouseEvent e)
2524 throws NotImplementedException
2526 // TODO: What should be done here, if anything?
2530 * Invoked when a mouse button has been released on a component.
2532 * @param e mouse event that occured
2534 public void mouseReleased(MouseEvent e)
2535 throws NotImplementedException
2537 // TODO: What should be done here, if anything?
2541 * Invoked when the mouse enters a component.
2543 * @param e mouse event that occured
2545 public void mouseEntered(MouseEvent e)
2546 throws NotImplementedException
2548 // TODO: What should be done here, if anything?
2552 * Invoked when the mouse exits a component.
2554 * @param e mouse event that occured
2556 public void mouseExited(MouseEvent e)
2557 throws NotImplementedException
2559 // TODO: What should be done here, if anything?
2563 * Invoked when a mouse button is pressed on a component and then dragged.
2564 * MOUSE_DRAGGED events will continue to be delivered to the component where
2565 * the drag originated until the mouse button is released (regardless of
2566 * whether the mouse position is within the bounds of the component).
2568 * @param e mouse event that occured
2570 public void mouseDragged(MouseEvent e)
2571 throws NotImplementedException
2573 // TODO: What should be done here, if anything?
2577 * Invoked when the mouse cursor has been moved onto a component but no
2578 * buttons have been pushed.
2580 * @param e mouse event that occured
2582 public void mouseMoved(MouseEvent e)
2583 throws NotImplementedException
2585 // TODO: What should be done here, if anything?
2589 * Removes event from the source
2591 protected void removeFromSource()
2592 throws NotImplementedException
2594 // TODO: Implement this properly.
2599 * Class responsible for getting size of node, method is forwarded to
2600 * BasicTreeUI method. X location does not include insets, that is handled in
2603 public class NodeDimensionsHandler
2604 extends AbstractLayoutCache.NodeDimensions
2609 public NodeDimensionsHandler()
2611 // Nothing to do here.
2615 * Returns, by reference in bounds, the size and x origin to place value at.
2616 * The calling method is responsible for determining the Y location. If
2617 * bounds is null, a newly created Rectangle should be returned, otherwise
2618 * the value should be placed in bounds and returned.
2620 * @param cell the value to be represented
2621 * @param row row being queried
2622 * @param depth the depth of the row
2623 * @param expanded true if row is expanded
2624 * @param size a Rectangle containing the size needed to represent value
2625 * @return containing the node dimensions, or null if node has no dimension
2627 public Rectangle getNodeDimensions(Object cell, int row, int depth,
2628 boolean expanded, Rectangle size)
2630 if (size == null || cell == null)
2633 String s = cell.toString();
2634 Font f = tree.getFont();
2635 FontMetrics fm = tree.getToolkit().getFontMetrics(f);
2639 TreePath path = treeState.getPathForRow(row);
2640 size.x = getRowX(row, depth);
2641 size.width = SwingUtilities.computeStringWidth(fm, s);
2642 size.width = size.width + getCurrentControlIcon(path).getIconWidth()
2643 + gap + getNodeIcon(path).getIconWidth();
2644 size.height = getMaxHeight(tree);
2645 size.y = size.height * row;
2652 * Returns the amount to indent the given row
2654 * @return amount to indent the given row.
2656 protected int getRowX(int row, int depth)
2658 return BasicTreeUI.this.getRowX(row, depth);
2660 } // NodeDimensionsHandler
2663 * PropertyChangeListener for the tree. Updates the appropriate variable, or
2664 * TreeState, based on what changes.
2666 public class PropertyChangeHandler
2667 implements PropertyChangeListener
2673 public PropertyChangeHandler()
2675 // Nothing to do here.
2679 * This method gets called when a bound property is changed.
2681 * @param event A PropertyChangeEvent object describing the event source and
2682 * the property that has changed.
2684 public void propertyChange(PropertyChangeEvent event)
2686 String property = event.getPropertyName();
2687 if (property.equals(JTree.ROOT_VISIBLE_PROPERTY))
2689 validCachedPreferredSize = false;
2690 treeState.setRootVisible(tree.isRootVisible());
2693 else if (property.equals(JTree.SELECTION_MODEL_PROPERTY))
2695 treeSelectionModel = tree.getSelectionModel();
2696 treeSelectionModel.setRowMapper(treeState);
2698 else if (property.equals(JTree.TREE_MODEL_PROPERTY))
2700 setModel(tree.getModel());
2702 else if (property.equals(JTree.CELL_RENDERER_PROPERTY))
2704 setCellRenderer(tree.getCellRenderer());
2706 if (treeState != null)
2707 treeState.invalidateSizes();
2713 * Listener on the TreeSelectionModel, resets the row selection if any of the
2714 * properties of the model change.
2716 public class SelectionModelPropertyChangeHandler
2717 implements PropertyChangeListener
2723 public SelectionModelPropertyChangeHandler()
2725 // Nothing to do here.
2729 * This method gets called when a bound property is changed.
2731 * @param event A PropertyChangeEvent object describing the event source and
2732 * the property that has changed.
2734 public void propertyChange(PropertyChangeEvent event)
2735 throws NotImplementedException
2737 // TODO: What should be done here, if anything?
2742 * The action to cancel editing on this tree.
2744 public class TreeCancelEditingAction
2745 extends AbstractAction
2748 * Creates the new tree cancel editing action.
2750 * @param name the name of the action (used in toString).
2752 public TreeCancelEditingAction(String name)
2758 * Invoked when an action occurs, cancels the cell editing (if the
2759 * tree cell is being edited).
2761 * @param e event that occured
2763 public void actionPerformed(ActionEvent e)
2765 if (isEnabled() && tree.isEditing())
2766 tree.cancelEditing();
2771 * Updates the TreeState in response to nodes expanding/collapsing.
2773 public class TreeExpansionHandler
2774 implements TreeExpansionListener
2780 public TreeExpansionHandler()
2782 // Nothing to do here.
2786 * Called whenever an item in the tree has been expanded.
2788 * @param event is the event that occured
2790 public void treeExpanded(TreeExpansionEvent event)
2792 validCachedPreferredSize = false;
2793 treeState.setExpandedState(event.getPath(), true);
2794 // The maximal cell height may change
2801 * Called whenever an item in the tree has been collapsed.
2803 * @param event is the event that occured
2805 public void treeCollapsed(TreeExpansionEvent event)
2807 validCachedPreferredSize = false;
2808 treeState.setExpandedState(event.getPath(), false);
2809 // The maximal cell height may change
2814 } // TreeExpansionHandler
2817 * TreeHomeAction is used to handle end/home actions. Scrolls either the first
2818 * or last cell to be visible based on direction.
2820 public class TreeHomeAction
2821 extends AbstractAction
2824 /** The direction, either home or end */
2825 protected int direction;
2828 * Creates a new TreeHomeAction instance.
2830 * @param dir the direction to go to, <code>-1</code> for home,
2831 * <code>1</code> for end
2832 * @param name the name of the action
2834 public TreeHomeAction(int dir, String name)
2837 putValue(Action.NAME, name);
2841 * Invoked when an action occurs.
2843 * @param e is the event that occured
2845 public void actionPerformed(ActionEvent e)
2849 String command = (String) getValue(Action.NAME);
2850 if (command.equals("selectFirst"))
2852 ensureRowsAreVisible(0, 0);
2853 tree.setSelectionInterval(0, 0);
2855 if (command.equals("selectFirstChangeLead"))
2857 ensureRowsAreVisible(0, 0);
2858 tree.setLeadSelectionPath(getPathForRow(tree, 0));
2860 if (command.equals("selectFirstExtendSelection"))
2862 ensureRowsAreVisible(0, 0);
2863 TreePath anchorPath = tree.getAnchorSelectionPath();
2864 if (anchorPath == null)
2865 tree.setSelectionInterval(0, 0);
2868 int anchorRow = getRowForPath(tree, anchorPath);
2869 tree.setSelectionInterval(0, anchorRow);
2870 tree.setAnchorSelectionPath(anchorPath);
2871 tree.setLeadSelectionPath(getPathForRow(tree, 0));
2874 else if (command.equals("selectLast"))
2876 int end = getRowCount(tree) - 1;
2877 ensureRowsAreVisible(end, end);
2878 tree.setSelectionInterval(end, end);
2880 else if (command.equals("selectLastChangeLead"))
2882 int end = getRowCount(tree) - 1;
2883 ensureRowsAreVisible(end, end);
2884 tree.setLeadSelectionPath(getPathForRow(tree, end));
2886 else if (command.equals("selectLastExtendSelection"))
2888 int end = getRowCount(tree) - 1;
2889 ensureRowsAreVisible(end, end);
2890 TreePath anchorPath = tree.getAnchorSelectionPath();
2891 if (anchorPath == null)
2892 tree.setSelectionInterval(end, end);
2895 int anchorRow = getRowForPath(tree, anchorPath);
2896 tree.setSelectionInterval(end, anchorRow);
2897 tree.setAnchorSelectionPath(anchorPath);
2898 tree.setLeadSelectionPath(getPathForRow(tree, end));
2903 // Ensure that the lead path is visible after the increment action.
2904 tree.scrollPathToVisible(tree.getLeadSelectionPath());
2908 * Returns true if the action is enabled.
2910 * @return true if the action is enabled.
2912 public boolean isEnabled()
2914 return (tree != null) && tree.isEnabled();
2919 * TreeIncrementAction is used to handle up/down actions. Selection is moved
2920 * up or down based on direction.
2922 public class TreeIncrementAction
2923 extends AbstractAction
2927 * Specifies the direction to adjust the selection by.
2929 protected int direction;
2932 * Creates a new TreeIncrementAction.
2934 * @param dir up or down, <code>-1</code> for up, <code>1</code> for down
2935 * @param name is the name of the direction
2937 public TreeIncrementAction(int dir, String name)
2940 putValue(Action.NAME, name);
2944 * Invoked when an action occurs.
2946 * @param e is the event that occured
2948 public void actionPerformed(ActionEvent e)
2950 TreePath currentPath = tree.getLeadSelectionPath();
2953 if (currentPath != null)
2954 currentRow = treeState.getRowForPath(currentPath);
2958 int rows = treeState.getRowCount();
2960 int nextRow = currentRow + 1;
2961 int prevRow = currentRow - 1;
2962 boolean hasNext = nextRow < rows;
2963 boolean hasPrev = prevRow >= 0 && rows > 0;
2965 String command = (String) getValue(Action.NAME);
2967 if (command.equals("selectPreviousChangeLead") && hasPrev)
2969 newPath = treeState.getPathForRow(prevRow);
2970 tree.setSelectionPath(newPath);
2971 tree.setAnchorSelectionPath(newPath);
2972 tree.setLeadSelectionPath(newPath);
2974 else if (command.equals("selectPreviousExtendSelection") && hasPrev)
2976 newPath = treeState.getPathForRow(prevRow);
2978 // If the new path is already selected, the selection shrinks,
2979 // unselecting the previously current path.
2980 if (tree.isPathSelected(newPath))
2981 tree.getSelectionModel().removeSelectionPath(currentPath);
2983 // This must be called in any case because it updates the model
2984 // lead selection index.
2985 tree.addSelectionPath(newPath);
2986 tree.setLeadSelectionPath(newPath);
2988 else if (command.equals("selectPrevious") && hasPrev)
2990 newPath = treeState.getPathForRow(prevRow);
2991 tree.setSelectionPath(newPath);
2993 else if (command.equals("selectNext") && hasNext)
2995 newPath = treeState.getPathForRow(nextRow);
2996 tree.setSelectionPath(newPath);
2998 else if (command.equals("selectNextExtendSelection") && hasNext)
3000 newPath = treeState.getPathForRow(nextRow);
3002 // If the new path is already selected, the selection shrinks,
3003 // unselecting the previously current path.
3004 if (tree.isPathSelected(newPath))
3005 tree.getSelectionModel().removeSelectionPath(currentPath);
3007 // This must be called in any case because it updates the model
3008 // lead selection index.
3009 tree.addSelectionPath(newPath);
3011 tree.setLeadSelectionPath(newPath);
3013 else if (command.equals("selectNextChangeLead") && hasNext)
3015 newPath = treeState.getPathForRow(nextRow);
3016 tree.setSelectionPath(newPath);
3017 tree.setAnchorSelectionPath(newPath);
3018 tree.setLeadSelectionPath(newPath);
3021 // Ensure that the lead path is visible after the increment action.
3022 tree.scrollPathToVisible(tree.getLeadSelectionPath());
3026 * Returns true if the action is enabled.
3028 * @return true if the action is enabled.
3030 public boolean isEnabled()
3032 return (tree != null) && tree.isEnabled();
3037 * Forwards all TreeModel events to the TreeState.
3039 public class TreeModelHandler
3040 implements TreeModelListener
3045 public TreeModelHandler()
3047 // Nothing to do here.
3051 * Invoked after a node (or a set of siblings) has changed in some way. The
3052 * node(s) have not changed locations in the tree or altered their children
3053 * arrays, but other attributes have changed and may affect presentation.
3054 * Example: the name of a file has changed, but it is in the same location
3055 * in the file system. To indicate the root has changed, childIndices and
3056 * children will be null. Use e.getPath() to get the parent of the changed
3057 * node(s). e.getChildIndices() returns the index(es) of the changed
3060 * @param e is the event that occured
3062 public void treeNodesChanged(TreeModelEvent e)
3064 validCachedPreferredSize = false;
3065 treeState.treeNodesChanged(e);
3070 * Invoked after nodes have been inserted into the tree. Use e.getPath() to
3071 * get the parent of the new node(s). e.getChildIndices() returns the
3072 * index(es) of the new node(s) in ascending order.
3074 * @param e is the event that occured
3076 public void treeNodesInserted(TreeModelEvent e)
3078 validCachedPreferredSize = false;
3079 treeState.treeNodesInserted(e);
3084 * Invoked after nodes have been removed from the tree. Note that if a
3085 * subtree is removed from the tree, this method may only be invoked once
3086 * for the root of the removed subtree, not once for each individual set of
3087 * siblings removed. Use e.getPath() to get the former parent of the deleted
3088 * node(s). e.getChildIndices() returns, in ascending order, the index(es)
3089 * the node(s) had before being deleted.
3091 * @param e is the event that occured
3093 public void treeNodesRemoved(TreeModelEvent e)
3095 validCachedPreferredSize = false;
3096 treeState.treeNodesRemoved(e);
3101 * Invoked after the tree has drastically changed structure from a given
3102 * node down. If the path returned by e.getPath() is of length one and the
3103 * first element does not identify the current root node the first element
3104 * should become the new root of the tree. Use e.getPath() to get the path
3105 * to the node. e.getChildIndices() returns null.
3107 * @param e is the event that occured
3109 public void treeStructureChanged(TreeModelEvent e)
3111 if (e.getPath().length == 1
3112 && ! e.getPath()[0].equals(treeModel.getRoot()))
3113 tree.expandPath(new TreePath(treeModel.getRoot()));
3114 validCachedPreferredSize = false;
3115 treeState.treeStructureChanged(e);
3118 } // TreeModelHandler
3121 * TreePageAction handles page up and page down events.
3123 public class TreePageAction
3124 extends AbstractAction
3126 /** Specifies the direction to adjust the selection by. */
3127 protected int direction;
3132 * @param direction up or down
3133 * @param name is the name of the direction
3135 public TreePageAction(int direction, String name)
3137 this.direction = direction;
3138 putValue(Action.NAME, name);
3142 * Invoked when an action occurs.
3144 * @param e is the event that occured
3146 public void actionPerformed(ActionEvent e)
3148 String command = (String) getValue(Action.NAME);
3149 boolean extendSelection = command.equals("scrollUpExtendSelection")
3150 || command.equals("scrollDownExtendSelection");
3151 boolean changeSelection = command.equals("scrollUpChangeSelection")
3152 || command.equals("scrollDownChangeSelection");
3154 // Disable change lead, unless we are in discontinuous mode.
3155 if (!extendSelection && !changeSelection
3156 && tree.getSelectionModel().getSelectionMode() !=
3157 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
3159 changeSelection = true;
3162 int rowCount = getRowCount(tree);
3163 if (rowCount > 0 && treeSelectionModel != null)
3165 Dimension maxSize = tree.getSize();
3166 TreePath lead = tree.getLeadSelectionPath();
3167 TreePath newPath = null;
3168 Rectangle visible = tree.getVisibleRect();
3169 if (direction == -1) // The RI handles -1 as up.
3171 newPath = getClosestPathForLocation(tree, visible.x, visible.y);
3172 if (newPath.equals(lead)) // Corner case, adjust one page up.
3174 visible.y = Math.max(0, visible.y - visible.height);
3175 newPath = getClosestPathForLocation(tree, visible.x,
3181 visible.y = Math.min(maxSize.height,
3182 visible.y + visible.height - 1);
3183 newPath = getClosestPathForLocation(tree, visible.x, visible.y);
3184 if (newPath.equals(lead)) // Corner case, adjust one page down.
3186 visible.y = Math.min(maxSize.height,
3187 visible.y + visible.height - 1);
3188 newPath = getClosestPathForLocation(tree, visible.x,
3193 // Determine new visible rect.
3194 Rectangle newVisible = getPathBounds(tree, newPath);
3195 newVisible.x = visible.x;
3196 newVisible.width = visible.width;
3197 if (direction == -1)
3199 newVisible.height = visible.height;
3203 newVisible.y -= visible.height - newVisible.height;
3204 newVisible.height = visible.height;
3207 if (extendSelection)
3209 // Extend selection.
3210 TreePath anchorPath = tree.getAnchorSelectionPath();
3211 if (anchorPath == null)
3213 tree.setSelectionPath(newPath);
3217 int newIndex = getRowForPath(tree, newPath);
3218 int anchorIndex = getRowForPath(tree, anchorPath);
3219 tree.setSelectionInterval(Math.min(anchorIndex, newIndex),
3220 Math.max(anchorIndex, newIndex));
3221 tree.setAnchorSelectionPath(anchorPath);
3222 tree.setLeadSelectionPath(newPath);
3225 else if (changeSelection)
3227 tree.setSelectionPath(newPath);
3229 else // Change lead.
3231 tree.setLeadSelectionPath(newPath);
3234 tree.scrollRectToVisible(newVisible);
3239 * Returns true if the action is enabled.
3241 * @return true if the action is enabled.
3243 public boolean isEnabled()
3245 return (tree != null) && tree.isEnabled();
3250 * Listens for changes in the selection model and updates the display
3253 public class TreeSelectionHandler
3254 implements TreeSelectionListener
3259 public TreeSelectionHandler()
3261 // Nothing to do here.
3265 * Messaged when the selection changes in the tree we're displaying for.
3266 * Stops editing, messages super and displays the changed paths.
3268 * @param event the event that characterizes the change.
3270 public void valueChanged(TreeSelectionEvent event)
3272 if (tree.isEditing())
3273 tree.cancelEditing();
3275 TreePath op = event.getOldLeadSelectionPath();
3276 TreePath np = event.getNewLeadSelectionPath();
3278 // Repaint of the changed lead selection path.
3281 Rectangle o = treeState.getBounds(event.getOldLeadSelectionPath(),
3283 Rectangle n = treeState.getBounds(event.getNewLeadSelectionPath(),
3292 } // TreeSelectionHandler
3295 * For the first selected row expandedness will be toggled.
3297 public class TreeToggleAction
3298 extends AbstractAction
3301 * Creates a new TreeToggleAction.
3303 * @param name is the name of <code>Action</code> field
3305 public TreeToggleAction(String name)
3307 putValue(Action.NAME, name);
3311 * Invoked when an action occurs.
3313 * @param e the event that occured
3315 public void actionPerformed(ActionEvent e)
3317 int selected = tree.getLeadSelectionRow();
3318 if (selected != -1 && isLeaf(selected))
3320 TreePath anchorPath = tree.getAnchorSelectionPath();
3321 TreePath leadPath = tree.getLeadSelectionPath();
3322 toggleExpandState(getPathForRow(tree, selected));
3323 // Need to do this, so that the toggling doesn't mess up the lead
3325 tree.setLeadSelectionPath(leadPath);
3326 tree.setAnchorSelectionPath(anchorPath);
3328 // Ensure that the lead path is visible after the increment action.
3329 tree.scrollPathToVisible(tree.getLeadSelectionPath());
3334 * Returns true if the action is enabled.
3336 * @return true if the action is enabled, false otherwise
3338 public boolean isEnabled()
3340 return (tree != null) && tree.isEnabled();
3342 } // TreeToggleAction
3345 * TreeTraverseAction is the action used for left/right keys. Will toggle the
3346 * expandedness of a node, as well as potentially incrementing the selection.
3348 public class TreeTraverseAction
3349 extends AbstractAction
3352 * Determines direction to traverse, 1 means expand, -1 means collapse.
3354 protected int direction;
3359 * @param direction to traverse
3360 * @param name is the name of the direction
3362 public TreeTraverseAction(int direction, String name)
3364 this.direction = direction;
3365 putValue(Action.NAME, name);
3369 * Invoked when an action occurs.
3371 * @param e the event that occured
3373 public void actionPerformed(ActionEvent e)
3375 TreePath current = tree.getLeadSelectionPath();
3376 if (current == null)
3379 String command = (String) getValue(Action.NAME);
3380 if (command.equals("selectParent"))
3382 if (current == null)
3385 if (tree.isExpanded(current))
3387 tree.collapsePath(current);
3391 // If the node is not expanded (also, if it is a leaf node),
3392 // we just select the parent. We do not select the root if it
3394 TreePath parent = current.getParentPath();
3395 if (parent != null &&
3396 ! (parent.getPathCount() == 1 && ! tree.isRootVisible()))
3397 tree.setSelectionPath(parent);
3400 else if (command.equals("selectChild"))
3402 Object node = current.getLastPathComponent();
3403 int nc = treeModel.getChildCount(node);
3404 if (nc == 0 || treeState.isExpanded(current))
3406 // If the node is leaf or it is already expanded,
3407 // we just select the next row.
3408 int nextRow = tree.getLeadSelectionRow() + 1;
3409 if (nextRow <= tree.getRowCount())
3410 tree.setSelectionRow(nextRow);
3414 tree.expandPath(current);
3418 // Ensure that the lead path is visible after the increment action.
3419 tree.scrollPathToVisible(tree.getLeadSelectionPath());
3423 * Returns true if the action is enabled.
3425 * @return true if the action is enabled, false otherwise
3427 public boolean isEnabled()
3429 return (tree != null) && tree.isEnabled();
3434 * Returns true if the LookAndFeel implements the control icons. Package
3435 * private for use in inner classes.
3437 * @returns true if there are control icons
3439 boolean hasControlIcons()
3441 if (expandedIcon != null || collapsedIcon != null)
3447 * Returns control icon. It is null if the LookAndFeel does not implements the
3448 * control icons. Package private for use in inner classes.
3450 * @return control icon if it exists.
3452 Icon getCurrentControlIcon(TreePath path)
3454 if (hasControlIcons())
3456 if (tree.isExpanded(path))
3457 return expandedIcon;
3459 return collapsedIcon;
3463 if (nullIcon == null)
3464 nullIcon = new Icon()
3466 public int getIconHeight()
3471 public int getIconWidth()
3476 public void paintIcon(Component c, Graphics g, int x, int y)
3486 * Returns the parent of the current node
3488 * @param root is the root of the tree
3489 * @param node is the current node
3490 * @return is the parent of the current node
3492 Object getParent(Object root, Object node)
3494 if (root == null || node == null || root.equals(node))
3497 if (node instanceof TreeNode)
3498 return ((TreeNode) node).getParent();
3499 return findNode(root, node);
3503 * Recursively checks the tree for the specified node, starting at the root.
3505 * @param root is starting node to start searching at.
3506 * @param node is the node to search for
3507 * @return the parent node of node
3509 private Object findNode(Object root, Object node)
3511 if (! treeModel.isLeaf(root) && ! root.equals(node))
3513 int size = treeModel.getChildCount(root);
3514 for (int j = 0; j < size; j++)
3516 Object child = treeModel.getChild(root, j);
3517 if (node.equals(child))
3520 Object n = findNode(child, node);
3529 * Selects the specified path in the tree depending on modes. Package private
3530 * for use in inner classes.
3532 * @param tree is the tree we are selecting the path in
3533 * @param path is the path we are selecting
3535 void selectPath(JTree tree, TreePath path)
3539 tree.setSelectionPath(path);
3540 tree.setLeadSelectionPath(path);
3541 tree.makeVisible(path);
3542 tree.scrollPathToVisible(path);
3547 * Returns the path from node to the root. Package private for use in inner
3550 * @param node the node to get the path to
3551 * @param depth the depth of the tree to return a path for
3552 * @return an array of tree nodes that represent the path to node.
3554 Object[] getPathToRoot(Object node, int depth)
3561 return new Object[depth];
3564 Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node),
3566 path[path.length - depth - 1] = node;
3571 * Draws a vertical line using the given graphic context
3573 * @param g is the graphic context
3574 * @param c is the component the new line will belong to
3575 * @param x is the horizonal position
3576 * @param top specifies the top of the line
3577 * @param bottom specifies the bottom of the line
3579 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
3582 // FIXME: Check if drawing a dashed line or not.
3583 g.setColor(getHashColor());
3584 g.drawLine(x, top, x, bottom);
3588 * Draws a horizontal line using the given graphic context
3590 * @param g is the graphic context
3591 * @param c is the component the new line will belong to
3592 * @param y is the vertical position
3593 * @param left specifies the left point of the line
3594 * @param right specifies the right point of the line
3596 protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left,
3599 // FIXME: Check if drawing a dashed line or not.
3600 g.setColor(getHashColor());
3601 g.drawLine(left, y, right, y);
3605 * Draws an icon at around a specific position
3607 * @param c is the component the new line will belong to
3608 * @param g is the graphic context
3609 * @param icon is the icon which will be drawn
3610 * @param x is the center position in x-direction
3611 * @param y is the center position in y-direction
3613 protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y)
3615 x -= icon.getIconWidth() / 2;
3616 y -= icon.getIconHeight() / 2;
3623 icon.paintIcon(c, g, x, y);
3627 * Draws a dashed horizontal line.
3629 * @param g - the graphics configuration.
3630 * @param y - the y location to start drawing at
3631 * @param x1 - the x location to start drawing at
3632 * @param x2 - the x location to finish drawing at
3634 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)
3636 g.setColor(getHashColor());
3637 for (int i = x1; i < x2; i += 2)
3638 g.drawLine(i, y, i + 1, y);
3642 * Draws a dashed vertical line.
3644 * @param g - the graphics configuration.
3645 * @param x - the x location to start drawing at
3646 * @param y1 - the y location to start drawing at
3647 * @param y2 - the y location to finish drawing at
3649 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2)
3651 g.setColor(getHashColor());
3652 for (int i = y1; i < y2; i += 2)
3653 g.drawLine(x, i, x, i + 1);
3657 * Paints the expand (toggle) part of a row. The receiver should NOT modify
3658 * clipBounds, or insets.
3660 * @param g - the graphics configuration
3661 * @param clipBounds -
3663 * @param bounds - bounds of expand control
3664 * @param path - path to draw control for
3665 * @param row - row to draw control for
3666 * @param isExpanded - is the row expanded
3667 * @param hasBeenExpanded - has the row already been expanded
3668 * @param isLeaf - is the path a leaf
3670 protected void paintExpandControl(Graphics g, Rectangle clipBounds,
3671 Insets insets, Rectangle bounds,
3672 TreePath path, int row, boolean isExpanded,
3673 boolean hasBeenExpanded, boolean isLeaf)
3675 if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf))
3677 Icon icon = getCurrentControlIcon(path);
3678 int iconW = icon.getIconWidth();
3679 int x = bounds.x - iconW - gap;
3680 icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2
3681 - icon.getIconHeight() / 2);
3686 * Paints the horizontal part of the leg. The receiver should NOT modify
3687 * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not
3690 * @param g - the graphics configuration
3691 * @param clipBounds -
3693 * @param bounds - bounds of the cell
3694 * @param path - path to draw leg for
3695 * @param row - row to start drawing at
3696 * @param isExpanded - is the row expanded
3697 * @param hasBeenExpanded - has the row already been expanded
3698 * @param isLeaf - is the path a leaf
3700 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
3701 Insets insets, Rectangle bounds,
3702 TreePath path, int row,
3704 boolean hasBeenExpanded,
3709 paintHorizontalLine(g, tree, bounds.y + bounds.height / 2,
3710 bounds.x - leftChildIndent - gap, bounds.x - gap);
3715 * Paints the vertical part of the leg. The receiver should NOT modify
3716 * clipBounds, insets.
3718 * @param g - the graphics configuration.
3719 * @param clipBounds -
3721 * @param path - the path to draw the vertical part for.
3723 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
3724 Insets insets, TreePath path)
3726 Rectangle bounds = getPathBounds(tree, path);
3727 TreePath parent = path.getParentPath();
3730 if (isRootVisible())
3731 paintLine = parent != null;
3733 paintLine = parent != null && parent.getPathCount() > 1;
3736 Rectangle parentBounds = getPathBounds(tree, parent);
3737 paintVerticalLine(g, tree, parentBounds.x + 2 * gap,
3738 parentBounds.y + parentBounds.height / 2,
3739 bounds.y + bounds.height / 2);
3744 * Paints the renderer part of a row. The receiver should NOT modify
3745 * clipBounds, or insets.
3747 * @param g - the graphics configuration
3748 * @param clipBounds -
3750 * @param bounds - bounds of expand control
3751 * @param path - path to draw control for
3752 * @param row - row to draw control for
3753 * @param isExpanded - is the row expanded
3754 * @param hasBeenExpanded - has the row already been expanded
3755 * @param isLeaf - is the path a leaf
3757 protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
3758 Rectangle bounds, TreePath path, int row,
3759 boolean isExpanded, boolean hasBeenExpanded,
3762 boolean selected = tree.isPathSelected(path);
3763 boolean hasIcons = false;
3764 Object node = path.getLastPathComponent();
3766 paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded,
3767 hasBeenExpanded, isLeaf);
3769 TreeCellRenderer dtcr = currentCellRenderer;
3771 boolean focused = false;
3772 if (treeSelectionModel != null)
3773 focused = treeSelectionModel.getLeadSelectionRow() == row
3774 && tree.isFocusOwner();
3776 Component c = dtcr.getTreeCellRendererComponent(tree, node, selected,
3777 isExpanded, isLeaf, row,
3780 rendererPane.paintComponent(g, c, c.getParent(), bounds);
3784 * Prepares for the UI to uninstall.
3786 protected void prepareForUIUninstall()
3788 // Nothing to do here yet.
3792 * Returns true if the expand (toggle) control should be drawn for the
3795 * @param path - current path to check for.
3796 * @param row - current row to check for.
3797 * @param isExpanded - true if the path is expanded
3798 * @param hasBeenExpanded - true if the path has been expanded already
3799 * @param isLeaf - true if the row is a lead
3801 protected boolean shouldPaintExpandControl(TreePath path, int row,
3803 boolean hasBeenExpanded,
3806 Object node = path.getLastPathComponent();
3807 return ! isLeaf && hasControlIcons();
3811 * Finish the editing session.
3815 treeState.invalidatePathBounds(treeState.getPathForRow(editingRow));
3818 stopEditingInCompleteEditing = false;
3820 Rectangle bounds = editingComponent.getParent().getBounds();
3822 validCachedPreferredSize = false;
3823 // Repaint the region, where was the editing component.
3824 tree.repaint(bounds);
3825 editingComponent = null;
3826 tree.requestFocus();
3830 * Returns the amount to indent the given row
3832 * @return amount to indent the given row.
3834 protected int getRowX(int row, int depth)
3836 return depth * totalChildIndent;