Merge from mainline
[official-gcc.git] / libjava / classpath / javax / swing / plaf / basic / BasicTreeUI.java
blobf2ebcfca9ac002ab8ed15df57a94e5cb828e8b45
1 /* BasicTreeUI.java --
2 Copyright (C) 2002, 2004, 2005 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)
9 any later version.
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
19 02110-1301 USA.
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
24 combination.
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 java.awt.Color;
42 import java.awt.Component;
43 import java.awt.Dimension;
44 import java.awt.Font;
45 import java.awt.FontMetrics;
46 import java.awt.Graphics;
47 import java.awt.Insets;
48 import java.awt.Point;
49 import java.awt.Rectangle;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.ActionListener;
52 import java.awt.event.ComponentAdapter;
53 import java.awt.event.ComponentEvent;
54 import java.awt.event.ComponentListener;
55 import java.awt.event.FocusEvent;
56 import java.awt.event.FocusListener;
57 import java.awt.event.KeyAdapter;
58 import java.awt.event.KeyEvent;
59 import java.awt.event.KeyListener;
60 import java.awt.event.MouseAdapter;
61 import java.awt.event.MouseEvent;
62 import java.awt.event.MouseListener;
63 import java.awt.event.MouseMotionListener;
64 import java.beans.PropertyChangeEvent;
65 import java.beans.PropertyChangeListener;
66 import java.util.Enumeration;
67 import java.util.Hashtable;
69 import javax.swing.AbstractAction;
70 import javax.swing.Action;
71 import javax.swing.ActionMap;
72 import javax.swing.CellRendererPane;
73 import javax.swing.Icon;
74 import javax.swing.InputMap;
75 import javax.swing.JComponent;
76 import javax.swing.JScrollBar;
77 import javax.swing.JScrollPane;
78 import javax.swing.JTextField;
79 import javax.swing.JTree;
80 import javax.swing.KeyStroke;
81 import javax.swing.LookAndFeel;
82 import javax.swing.SwingUtilities;
83 import javax.swing.Timer;
84 import javax.swing.UIManager;
85 import javax.swing.event.CellEditorListener;
86 import javax.swing.event.ChangeEvent;
87 import javax.swing.event.MouseInputListener;
88 import javax.swing.event.TreeExpansionEvent;
89 import javax.swing.event.TreeExpansionListener;
90 import javax.swing.event.TreeModelEvent;
91 import javax.swing.event.TreeModelListener;
92 import javax.swing.event.TreeSelectionEvent;
93 import javax.swing.event.TreeSelectionListener;
94 import javax.swing.plaf.ActionMapUIResource;
95 import javax.swing.plaf.ComponentUI;
96 import javax.swing.plaf.InputMapUIResource;
97 import javax.swing.plaf.TreeUI;
98 import javax.swing.text.Caret;
99 import javax.swing.tree.AbstractLayoutCache;
100 import javax.swing.tree.DefaultTreeCellEditor;
101 import javax.swing.tree.DefaultTreeCellRenderer;
102 import javax.swing.tree.FixedHeightLayoutCache;
103 import javax.swing.tree.TreeCellEditor;
104 import javax.swing.tree.TreeCellRenderer;
105 import javax.swing.tree.TreeModel;
106 import javax.swing.tree.TreeNode;
107 import javax.swing.tree.TreePath;
108 import javax.swing.tree.TreeSelectionModel;
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)
118 public class BasicTreeUI extends TreeUI
120 /** Collapse Icon for the tree. */
121 protected transient Icon collapsedIcon;
123 /** Expanded Icon for the tree. */
124 protected transient Icon expandedIcon;
126 /** Distance between left margin and where vertical dashes will be drawn. */
127 protected int leftChildIndent;
130 * Distance between leftChildIndent and where cell contents will be drawn.
132 protected int rightChildIndent;
135 * Total fistance that will be indented. The sum of leftChildIndent and
136 * rightChildIndent .
138 protected int totalChildIndent;
140 /** Index of the row that was last selected. */
141 protected int lastSelectedRow;
143 /** Component that we're going to be drawing onto. */
144 protected JTree tree;
146 /** Renderer that is being used to do the actual cell drawing. */
147 protected transient TreeCellRenderer currentCellRenderer;
150 * Set to true if the renderer that is currently in the tree was created by
151 * this instance.
153 protected boolean createdRenderer;
155 /** Editor for the tree. */
156 protected transient TreeCellEditor cellEditor;
159 * Set to true if editor that is currently in the tree was created by this
160 * instance.
162 protected boolean createdCellEditor;
165 * Set to false when editing and shouldSelectCall() returns true meaning the
166 * node should be selected before editing, used in completeEditing.
168 protected boolean stopEditingInCompleteEditing;
170 /** Used to paint the TreeCellRenderer. */
171 protected CellRendererPane rendererPane;
173 /** Size needed to completely display all the nodes. */
174 protected Dimension preferredSize;
176 /** Minimum size needed to completely display all the nodes. */
177 protected Dimension preferredMinSize;
179 /** Is the preferredSize valid? */
180 protected boolean validCachedPreferredSize;
182 /** Object responsible for handling sizing and expanded issues. */
183 protected AbstractLayoutCache treeState;
185 /** Used for minimizing the drawing of vertical lines. */
186 protected Hashtable drawingCache;
189 * True if doing optimizations for a largeModel. Subclasses that don't support
190 * this may wish to override createLayoutCache to not return a
191 * FixedHeightLayoutCache instance.
193 protected boolean largeModel;
195 /** Responsible for telling the TreeState the size needed for a node. */
196 protected AbstractLayoutCache.NodeDimensions nodeDimensions;
198 /** Used to determine what to display. */
199 protected TreeModel treeModel;
201 /** Model maintaining the selection. */
202 protected TreeSelectionModel treeSelectionModel;
205 * How much the depth should be offset to properly calculate x locations. This
206 * is based on whether or not the root is visible, and if the root handles are
207 * visible.
209 protected int depthOffset;
212 * When editing, this will be the Component that is doing the actual editing.
214 protected Component editingComponent;
216 /** Path that is being edited. */
217 protected TreePath editingPath;
220 * Row that is being edited. Should only be referenced if editingComponent is
221 * null.
223 protected int editingRow;
225 /** Set to true if the editor has a different size than the renderer. */
226 protected boolean editorHasDifferentSize;
228 /** The action listener for the editor's Timer. */
229 Timer editorTimer = new EditorUpdateTimer();
231 /** The new value of the node after editing. */
232 Object newVal;
234 /** The action bound to KeyStrokes. */
235 TreeAction action;
237 /** Boolean to keep track of editing. */
238 boolean isEditing;
240 /** The current path of the visible nodes in the tree. */
241 TreePath currentVisiblePath;
243 /** The gap between the icon and text. */
244 int gap = 4;
246 /** The max height of the nodes in the tree. */
247 int maxHeight = 0;
249 /** Listeners */
250 private PropertyChangeListener propertyChangeListener;
252 private FocusListener focusListener;
254 private TreeSelectionListener treeSelectionListener;
256 private MouseListener mouseListener;
258 private KeyListener keyListener;
260 private PropertyChangeListener selectionModelPropertyChangeListener;
262 private ComponentListener componentListener;
264 CellEditorListener cellEditorListener;
266 private TreeExpansionListener treeExpansionListener;
268 private TreeModelListener treeModelListener;
271 * Creates a new BasicTreeUI object.
273 public BasicTreeUI()
275 validCachedPreferredSize = false;
276 drawingCache = new Hashtable();
277 nodeDimensions = createNodeDimensions();
278 configureLayoutCache();
280 propertyChangeListener = createPropertyChangeListener();
281 focusListener = createFocusListener();
282 treeSelectionListener = createTreeSelectionListener();
283 mouseListener = createMouseListener();
284 keyListener = createKeyListener();
285 selectionModelPropertyChangeListener = createSelectionModelPropertyChangeListener();
286 componentListener = createComponentListener();
287 cellEditorListener = createCellEditorListener();
288 treeExpansionListener = createTreeExpansionListener();
289 treeModelListener = createTreeModelListener();
291 editingRow = -1;
292 lastSelectedRow = -1;
296 * Returns an instance of the UI delegate for the specified component.
298 * @param c
299 * the <code>JComponent</code> for which we need a UI delegate for.
300 * @return the <code>ComponentUI</code> for c.
302 public static ComponentUI createUI(JComponent c)
304 return new BasicTreeUI();
308 * Returns the Hash color.
310 * @return the <code>Color</code> of the Hash.
312 protected Color getHashColor()
314 return UIManager.getColor("Tree.hash");
318 * Sets the Hash color.
320 * @param color
321 * the <code>Color</code> to set the Hash to.
323 protected void setHashColor(Color color)
325 // FIXME: Putting something in the UIDefaults map is certainly wrong.
326 UIManager.put("Tree.hash", color);
330 * Sets the left child's indent value.
332 * @param newAmount
333 * is the new indent value for the left child.
335 public void setLeftChildIndent(int newAmount)
337 leftChildIndent = newAmount;
341 * Returns the indent value for the left child.
343 * @return the indent value for the left child.
345 public int getLeftChildIndent()
347 return leftChildIndent;
351 * Sets the right child's indent value.
353 * @param newAmount
354 * is the new indent value for the right child.
356 public void setRightChildIndent(int newAmount)
358 rightChildIndent = newAmount;
362 * Returns the indent value for the right child.
364 * @return the indent value for the right child.
366 public int getRightChildIndent()
368 return rightChildIndent;
372 * Sets the expanded icon.
374 * @param newG
375 * is the new expanded icon.
377 public void setExpandedIcon(Icon newG)
379 expandedIcon = newG;
383 * Returns the current expanded icon.
385 * @return the current expanded icon.
387 public Icon getExpandedIcon()
389 return expandedIcon;
393 * Sets the collapsed icon.
395 * @param newG
396 * is the new collapsed icon.
398 public void setCollapsedIcon(Icon newG)
400 collapsedIcon = newG;
404 * Returns the current collapsed icon.
406 * @return the current collapsed icon.
408 public Icon getCollapsedIcon()
410 return collapsedIcon;
414 * Updates the componentListener, if necessary.
416 * @param largeModel
417 * sets this.largeModel to it.
419 protected void setLargeModel(boolean largeModel)
421 if (largeModel != this.largeModel)
423 tree.removeComponentListener(componentListener);
424 this.largeModel = largeModel;
425 tree.addComponentListener(componentListener);
430 * Returns true if largeModel is set
432 * @return true if largeModel is set, otherwise false.
434 protected boolean isLargeModel()
436 return largeModel;
440 * Sets the row height.
442 * @param rowHeight
443 * is the height to set this.rowHeight to.
445 protected void setRowHeight(int rowHeight)
447 if (rowHeight == 0)
448 rowHeight = Math.max(getMaxHeight(tree), 20);
449 treeState.setRowHeight(rowHeight);
453 * Returns the current row height.
455 * @return current row height.
457 protected int getRowHeight()
459 return treeState.getRowHeight();
463 * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
464 * <code>updateRenderer</code>.
466 * @param tcr
467 * is the new TreeCellRenderer.
469 protected void setCellRenderer(TreeCellRenderer tcr)
471 currentCellRenderer = tcr;
472 updateRenderer();
476 * Return currentCellRenderer, which will either be the trees renderer, or
477 * defaultCellRenderer, which ever was not null.
479 * @return the current Cell Renderer
481 protected TreeCellRenderer getCellRenderer()
483 if (currentCellRenderer != null)
484 return currentCellRenderer;
486 return createDefaultCellRenderer();
490 * Sets the tree's model.
492 * @param model
493 * to set the treeModel to.
495 protected void setModel(TreeModel model)
497 tree.setModel(model);
498 treeModel = tree.getModel();
502 * Returns the tree's model
504 * @return treeModel
506 protected TreeModel getModel()
508 return treeModel;
512 * Sets the root to being visible.
514 * @param newValue
515 * sets the visibility of the root
517 protected void setRootVisible(boolean newValue)
519 tree.setRootVisible(newValue);
523 * Returns true if the root is visible.
525 * @return true if the root is visible.
527 protected boolean isRootVisible()
529 return tree.isRootVisible();
533 * Determines whether the node handles are to be displayed.
535 * @param newValue
536 * sets whether or not node handles should be displayed.
538 protected void setShowsRootHandles(boolean newValue)
540 tree.setShowsRootHandles(newValue);
544 * Returns true if the node handles are to be displayed.
546 * @return true if the node handles are to be displayed.
548 protected boolean getShowsRootHandles()
550 return tree.getShowsRootHandles();
554 * Sets the cell editor.
556 * @param editor
557 * to set the cellEditor to.
559 protected void setCellEditor(TreeCellEditor editor)
561 cellEditor = editor;
562 createdCellEditor = true;
566 * Returns the <code>TreeCellEditor</code> for this tree.
568 * @return the cellEditor for this tree.
570 protected TreeCellEditor getCellEditor()
572 return cellEditor;
576 * Configures the receiver to allow, or not allow, editing.
578 * @param newValue
579 * sets the receiver to allow editing if true.
581 protected void setEditable(boolean newValue)
583 tree.setEditable(newValue);
587 * Returns true if the receiver allows editing.
589 * @return true if the receiver allows editing.
591 protected boolean isEditable()
593 return tree.isEditable();
597 * Resets the selection model. The appropriate listeners are installed on the
598 * model.
600 * @param newLSM
601 * resets the selection model.
603 protected void setSelectionModel(TreeSelectionModel newLSM)
605 if (newLSM != null)
607 treeSelectionModel = newLSM;
608 tree.setSelectionModel(treeSelectionModel);
613 * Returns the current selection model.
615 * @return the current selection model.
617 protected TreeSelectionModel getSelectionModel()
619 return treeSelectionModel;
623 * Returns the Rectangle enclosing the label portion that the last item in
624 * path will be drawn to. Will return null if any component in path is
625 * currently valid.
627 * @param tree
628 * is the current tree the path will be drawn to.
629 * @param path
630 * is the current path the tree to draw to.
631 * @return the Rectangle enclosing the label portion that the last item in the
632 * path will be drawn to.
634 public Rectangle getPathBounds(JTree tree, TreePath path)
636 int row = -1;
637 Object cell = null;
638 if (path != null)
640 row = getRowForPath(tree, path);
641 cell = path.getLastPathComponent();
643 return nodeDimensions.getNodeDimensions(cell, row, getLevel(cell),
644 tree.isExpanded(path),
645 new Rectangle());
649 * Returns the max height of all the nodes in the tree.
651 * @param tree -
652 * the current tree
653 * @return the max height.
655 private int getMaxHeight(JTree tree)
657 if (maxHeight != 0)
658 return maxHeight;
660 Icon e = UIManager.getIcon("Tree.openIcon");
661 Icon c = UIManager.getIcon("Tree.closedIcon");
662 Icon l = UIManager.getIcon("Tree.leafIcon");
663 int rc = getRowCount(tree);
664 int iconHeight = 0;
666 for (int row = 0; row < rc; row++)
668 if (isLeaf(row))
669 iconHeight = l.getIconHeight();
670 else if (tree.isExpanded(row))
671 iconHeight = e.getIconHeight();
672 else
673 iconHeight = c.getIconHeight();
675 maxHeight = Math.max(maxHeight, iconHeight + gap);
678 return maxHeight;
682 * Returns the path for passed in row. If row is not visible null is returned.
684 * @param tree
685 * is the current tree to return path for.
686 * @param row
687 * is the row number of the row to return.
688 * @return the path for passed in row. If row is not visible null is returned.
690 public TreePath getPathForRow(JTree tree, int row)
692 if (treeModel != null && currentVisiblePath != null)
694 Object[] nodes = currentVisiblePath.getPath();
695 if (row < nodes.length)
696 return new TreePath(getPathToRoot(nodes[row], 0));
698 return null;
702 * Returns the row that the last item identified in path is visible at. Will
703 * return -1 if any of the elments in the path are not currently visible.
705 * @param tree
706 * is the current tree to return the row for.
707 * @param path
708 * is the path used to find the row.
709 * @return the row that the last item identified in path is visible at. Will
710 * return -1 if any of the elments in the path are not currently
711 * visible.
713 public int getRowForPath(JTree tree, TreePath path)
715 int row = 0;
716 Object dest = path.getLastPathComponent();
717 int rowCount = getRowCount(tree);
718 if (currentVisiblePath != null)
720 Object[] nodes = currentVisiblePath.getPath();
721 while (row < rowCount)
723 if (dest.equals(nodes[row]))
724 return row;
725 row++;
728 return -1;
732 * Returns the number of rows that are being displayed.
734 * @param tree
735 * is the current tree to return the number of rows for.
736 * @return the number of rows being displayed.
738 public int getRowCount(JTree tree)
740 if (currentVisiblePath != null)
741 return currentVisiblePath.getPathCount();
742 return 0;
746 * Returns the path to the node that is closest to x,y. If there is nothing
747 * currently visible this will return null, otherwise it'll always return a
748 * valid path. If you need to test if the returned object is exactly at x,y
749 * you should get the bounds for the returned path and test x,y against that.
751 * @param tree
752 * the tree to search for the closest path
753 * @param x
754 * is the x coordinate of the location to search
755 * @param y
756 * is the y coordinate of the location to search
757 * @return the tree path closes to x,y.
759 public TreePath getClosestPathForLocation(JTree tree, int x, int y)
761 int row = Math.round(y / getMaxHeight(tree));
762 TreePath path = getPathForRow(tree, row);
764 // no row is visible at this node
765 while (row > 0 && path == null)
767 --row;
768 path = getPathForRow(tree, row);
771 return path;
775 * Returns true if the tree is being edited. The item that is being edited can
776 * be returned by getEditingPath().
778 * @param tree
779 * is the tree to check for editing.
780 * @return true if the tree is being edited.
782 public boolean isEditing(JTree tree)
784 return isEditing;
788 * Stops the current editing session. This has no effect if the tree is not
789 * being edited. Returns true if the editor allows the editing session to
790 * stop.
792 * @param tree
793 * is the tree to stop the editing on
794 * @return true if the editor allows the editing session to stop.
796 public boolean stopEditing(JTree tree)
798 if (isEditing(tree))
799 completeEditing(true, false, false);
800 return !isEditing(tree);
804 * Cancels the current editing session.
806 * @param tree
807 * is the tree to cancel the editing session on.
809 public void cancelEditing(JTree tree)
811 if (isEditing(tree))
812 completeEditing(false, true, false);
816 * Selects the last item in path and tries to edit it. Editing will fail if
817 * the CellEditor won't allow it for the selected item.
819 * @param tree
820 * is the tree to edit on.
821 * @param path
822 * is the path in tree to edit on.
824 public void startEditingAtPath(JTree tree, TreePath path)
826 startEditing(path, null);
830 * Returns the path to the element that is being editted.
832 * @param tree
833 * is the tree to get the editing path from.
834 * @return the path that is being edited.
836 public TreePath getEditingPath(JTree tree)
838 return editingPath;
842 * Invoked after the tree instance variable has been set, but before any
843 * default/listeners have been installed.
845 protected void prepareForUIInstall()
847 // TODO: Implement this properly.
851 * Invoked from installUI after all the defaults/listeners have been
852 * installed.
854 protected void completeUIInstall()
856 // TODO: Implement this properly.
860 * Invoked from uninstallUI after all the defaults/listeners have been
861 * uninstalled.
863 protected void completeUIUninstall()
865 // TODO: Implement this properly.
869 * Installs the subcomponents of the tree, which is the renderer pane.
871 protected void installComponents()
873 currentCellRenderer = createDefaultCellRenderer();
874 rendererPane = createCellRendererPane();
875 createdRenderer = true;
876 setCellRenderer(currentCellRenderer);
880 * Creates an instance of NodeDimensions that is able to determine the size of
881 * a given node in the tree.
883 * @return the NodeDimensions of a given node in the tree
885 protected AbstractLayoutCache.NodeDimensions createNodeDimensions()
887 return new NodeDimensionsHandler();
891 * Creates a listener that is reponsible for the updates the UI based on how
892 * the tree changes.
894 * @return the PropertyChangeListener that is reposnsible for the updates
896 protected PropertyChangeListener createPropertyChangeListener()
898 return new PropertyChangeHandler();
902 * Creates the listener responsible for updating the selection based on mouse
903 * events.
905 * @return the MouseListener responsible for updating.
907 protected MouseListener createMouseListener()
909 return new MouseHandler();
913 * Creates the listener that is responsible for updating the display when
914 * focus is lost/grained.
916 * @return the FocusListener responsible for updating.
918 protected FocusListener createFocusListener()
920 return new FocusHandler();
924 * Creates the listener reponsible for getting key events from the tree.
926 * @return the KeyListener responsible for getting key events.
928 protected KeyListener createKeyListener()
930 return new KeyHandler();
934 * Creates the listener responsible for getting property change events from
935 * the selection model.
937 * @returns the PropertyChangeListener reponsible for getting property change
938 * events from the selection model.
940 protected PropertyChangeListener createSelectionModelPropertyChangeListener()
942 return new SelectionModelPropertyChangeHandler();
946 * Creates the listener that updates the display based on selection change
947 * methods.
949 * @return the TreeSelectionListener responsible for updating.
951 protected TreeSelectionListener createTreeSelectionListener()
953 return new TreeSelectionHandler();
957 * Creates a listener to handle events from the current editor
959 * @return the CellEditorListener that handles events from the current editor
961 protected CellEditorListener createCellEditorListener()
963 return new CellEditorHandler();
967 * Creates and returns a new ComponentHandler. This is used for the large
968 * model to mark the validCachedPreferredSize as invalid when the component
969 * moves.
971 * @return a new ComponentHandler.
973 protected ComponentListener createComponentListener()
975 return new ComponentHandler();
979 * Creates and returns the object responsible for updating the treestate when
980 * a nodes expanded state changes.
982 * @return the TreeExpansionListener responsible for updating the treestate
984 protected TreeExpansionListener createTreeExpansionListener()
986 return new TreeExpansionHandler();
990 * Creates the object responsible for managing what is expanded, as well as
991 * the size of nodes.
993 * @return the object responsible for managing what is expanded.
995 protected AbstractLayoutCache createLayoutCache()
997 return new FixedHeightLayoutCache();
1001 * Returns the renderer pane that renderer components are placed in.
1003 * @return the rendererpane that render components are placed in.
1005 protected CellRendererPane createCellRendererPane()
1007 return new CellRendererPane();
1011 * Creates a default cell editor.
1013 * @return the default cell editor.
1015 protected TreeCellEditor createDefaultCellEditor()
1017 if (currentCellRenderer != null)
1018 return new DefaultTreeCellEditor(
1019 tree,
1020 (DefaultTreeCellRenderer) currentCellRenderer,
1021 cellEditor);
1022 return new DefaultTreeCellEditor(
1023 tree,
1024 (DefaultTreeCellRenderer) createDefaultCellRenderer(),
1025 cellEditor);
1029 * Returns the default cell renderer that is used to do the stamping of each
1030 * node.
1032 * @return the default cell renderer that is used to do the stamping of each
1033 * node.
1035 protected TreeCellRenderer createDefaultCellRenderer()
1037 return new DefaultTreeCellRenderer();
1041 * Returns a listener that can update the tree when the model changes.
1043 * @return a listener that can update the tree when the model changes.
1045 protected TreeModelListener createTreeModelListener()
1047 return new TreeModelHandler();
1051 * Uninstall all registered listeners
1053 protected void uninstallListeners()
1055 tree.removePropertyChangeListener(propertyChangeListener);
1056 tree.removeFocusListener(focusListener);
1057 tree.removeTreeSelectionListener(treeSelectionListener);
1058 tree.removeMouseListener(mouseListener);
1059 tree.removeKeyListener(keyListener);
1060 tree.removePropertyChangeListener(selectionModelPropertyChangeListener);
1061 tree.removeComponentListener(componentListener);
1062 tree.removeTreeExpansionListener(treeExpansionListener);
1064 TreeCellEditor tce = tree.getCellEditor();
1065 if (tce != null)
1066 tce.removeCellEditorListener(cellEditorListener);
1067 if (treeModel != null)
1068 treeModel.removeTreeModelListener(treeModelListener);
1072 * Uninstall all keyboard actions.
1074 protected void uninstallKeyboardActions()
1076 action = null;
1077 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1078 null);
1079 tree.getActionMap().setParent(null);
1083 * Uninstall the rendererPane.
1085 protected void uninstallComponents()
1087 currentCellRenderer = null;
1088 rendererPane = null;
1089 createdRenderer = false;
1090 setCellRenderer(currentCellRenderer);
1094 * The vertical element of legs between nodes starts at the bottom of the
1095 * parent node by default. This method makes the leg start below that.
1097 * @return the vertical leg buffer
1099 protected int getVerticalLegBuffer()
1101 return getRowHeight() / 2;
1105 * The horizontal element of legs between nodes starts at the right of the
1106 * left-hand side of the child node by default. This method makes the leg end
1107 * before that.
1109 * @return the horizontal leg buffer
1111 protected int getHorizontalLegBuffer()
1113 return rightChildIndent / 2;
1117 * Make all the nodes that are expanded in JTree expanded in LayoutCache. This
1118 * invokes updateExpandedDescendants with the root path.
1120 protected void updateLayoutCacheExpandedNodes()
1122 if (treeModel != null)
1123 updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1127 * Updates the expanded state of all the descendants of the <code>path</code>
1128 * by getting the expanded descendants from the tree and forwarding to the
1129 * tree state.
1131 * @param path
1132 * the path used to update the expanded states
1134 protected void updateExpandedDescendants(TreePath path)
1136 Enumeration expanded = tree.getExpandedDescendants(path);
1137 while (expanded.hasMoreElements())
1138 treeState.setExpandedState(((TreePath) expanded.nextElement()), true);
1142 * Returns a path to the last child of <code>parent</code>
1144 * @param parent
1145 * is the topmost path to specified
1146 * @return a path to the last child of parent
1148 protected TreePath getLastChildPath(TreePath parent)
1150 return ((TreePath) parent.getLastPathComponent());
1154 * Updates how much each depth should be offset by.
1156 protected void updateDepthOffset()
1158 depthOffset += getVerticalLegBuffer();
1162 * Updates the cellEditor based on editability of the JTree that we're
1163 * contained in. If the tree is editable but doesn't have a cellEditor, a
1164 * basic one will be used.
1166 protected void updateCellEditor()
1168 if (tree.isEditable() && cellEditor == null)
1169 setCellEditor(createDefaultCellEditor());
1170 createdCellEditor = true;
1174 * Messaged from the tree we're in when the renderer has changed.
1176 protected void updateRenderer()
1178 if (tree != null)
1180 if (tree.getCellRenderer() == null)
1182 if (currentCellRenderer == null)
1183 currentCellRenderer = createDefaultCellRenderer();
1184 tree.setCellRenderer(currentCellRenderer);
1190 * Resets the treeState instance based on the tree we're providing the look
1191 * and feel for.
1193 protected void configureLayoutCache()
1195 treeState = createLayoutCache();
1199 * Marks the cached size as being invalid, and messages the tree with
1200 * <code>treeDidChange</code>.
1202 protected void updateSize()
1204 preferredSize = null;
1205 updateCachedPreferredSize();
1206 tree.treeDidChange();
1210 * Updates the <code>preferredSize</code> instance variable, which is
1211 * returned from <code>getPreferredSize()</code>.
1213 protected void updateCachedPreferredSize()
1215 int maxWidth = 0;
1216 boolean isLeaf = false;
1217 if (currentVisiblePath != null)
1219 Object[] path = currentVisiblePath.getPath();
1220 for (int i = 0; i < path.length; i++)
1222 TreePath curr = new TreePath(getPathToRoot(path[i], 0));
1223 Rectangle bounds = getPathBounds(tree, curr);
1224 if (treeModel != null)
1225 isLeaf = treeModel.isLeaf(path[i]);
1226 if (!isLeaf && hasControlIcons())
1227 bounds.width += getCurrentControlIcon(curr).getIconWidth();
1228 maxWidth = Math.max(maxWidth, bounds.x + bounds.width);
1231 maxHeight = 0;
1232 maxHeight = getMaxHeight(tree);
1233 preferredSize = new Dimension(maxWidth, (maxHeight * path.length));
1235 else
1236 preferredSize = new Dimension(0, 0);
1237 validCachedPreferredSize = true;
1241 * Messaged from the VisibleTreeNode after it has been expanded.
1243 * @param path
1244 * is the path that has been expanded.
1246 protected void pathWasExpanded(TreePath path)
1248 validCachedPreferredSize = false;
1249 tree.repaint();
1253 * Messaged from the VisibleTreeNode after it has collapsed
1255 protected void pathWasCollapsed(TreePath path)
1257 validCachedPreferredSize = false;
1258 tree.repaint();
1262 * Install all defaults for the tree.
1264 protected void installDefaults()
1266 LookAndFeel.installColorsAndFont(tree, "Tree.background",
1267 "Tree.foreground", "Tree.font");
1268 tree.setOpaque(true);
1270 rightChildIndent = UIManager.getInt("Tree.rightChildIndent");
1271 leftChildIndent = UIManager.getInt("Tree.leftChildIndent");
1272 setRowHeight(UIManager.getInt("Tree.rowHeight"));
1273 tree.setRowHeight(getRowHeight());
1274 tree.requestFocusInWindow(false);
1275 tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand"));
1276 setExpandedIcon(UIManager.getIcon("Tree.expandedIcon"));
1277 setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon"));
1281 * Install all keyboard actions for this
1283 protected void installKeyboardActions()
1285 InputMap focusInputMap = (InputMap) UIManager.get("Tree.focusInputMap");
1286 InputMapUIResource parentInputMap = new InputMapUIResource();
1287 ActionMap parentActionMap = new ActionMapUIResource();
1288 action = new TreeAction();
1289 Object keys[] = focusInputMap.allKeys();
1291 for (int i = 0; i < keys.length; i++)
1293 parentInputMap.put(
1294 KeyStroke.getKeyStroke(
1295 ((KeyStroke) keys[i]).getKeyCode(),
1296 convertModifiers(((KeyStroke) keys[i]).getModifiers())),
1297 (String) focusInputMap.get((KeyStroke) keys[i]));
1299 parentInputMap.put(
1300 KeyStroke.getKeyStroke(
1301 ((KeyStroke) keys[i]).getKeyCode(),
1302 ((KeyStroke) keys[i]).getModifiers()),
1303 (String) focusInputMap.get((KeyStroke) keys[i]));
1305 parentActionMap.put(
1306 (String) focusInputMap.get((KeyStroke) keys[i]),
1307 new ActionListenerProxy(
1308 action,
1309 (String) focusInputMap.get((KeyStroke) keys[i])));
1313 parentInputMap.setParent(tree.getInputMap(
1314 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent());
1315 parentActionMap.setParent(tree.getActionMap().getParent());
1316 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1317 parentInputMap);
1318 tree.getActionMap().setParent(parentActionMap);
1322 * Converts the modifiers.
1324 * @param mod -
1325 * modifier to convert
1326 * @returns the new modifier
1328 private int convertModifiers(int mod)
1330 if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
1332 mod |= KeyEvent.SHIFT_MASK;
1333 mod &= ~KeyEvent.SHIFT_DOWN_MASK;
1335 if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
1337 mod |= KeyEvent.CTRL_MASK;
1338 mod &= ~KeyEvent.CTRL_DOWN_MASK;
1340 if ((mod & KeyEvent.META_DOWN_MASK) != 0)
1342 mod |= KeyEvent.META_MASK;
1343 mod &= ~KeyEvent.META_DOWN_MASK;
1345 if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
1347 mod |= KeyEvent.ALT_MASK;
1348 mod &= ~KeyEvent.ALT_DOWN_MASK;
1350 if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
1352 mod |= KeyEvent.ALT_GRAPH_MASK;
1353 mod &= ~KeyEvent.ALT_GRAPH_DOWN_MASK;
1355 return mod;
1359 * Install all listeners for this
1361 protected void installListeners()
1363 tree.addPropertyChangeListener(propertyChangeListener);
1364 tree.addFocusListener(focusListener);
1365 tree.addTreeSelectionListener(treeSelectionListener);
1366 tree.addMouseListener(mouseListener);
1367 tree.addKeyListener(keyListener);
1368 tree.addPropertyChangeListener(selectionModelPropertyChangeListener);
1369 tree.addComponentListener(componentListener);
1370 tree.addTreeExpansionListener(treeExpansionListener);
1371 if (treeModel != null)
1372 treeModel.addTreeModelListener(treeModelListener);
1376 * Install the UI for the component
1378 * @param c
1379 * the component to install UI for
1381 public void installUI(JComponent c)
1383 tree = (JTree) c;
1384 prepareForUIInstall();
1385 super.installUI(c);
1386 installDefaults();
1388 installComponents();
1389 installKeyboardActions();
1390 installListeners();
1392 setCellEditor(createDefaultCellEditor());
1393 createdCellEditor = true;
1394 isEditing = false;
1396 setModel(tree.getModel());
1397 treeSelectionModel = tree.getSelectionModel();
1399 completeUIInstall();
1403 * Uninstall the defaults for the tree
1405 protected void uninstallDefaults()
1407 tree.setFont(null);
1408 tree.setForeground(null);
1409 tree.setBackground(null);
1413 * Uninstall the UI for the component
1415 * @param c
1416 * the component to uninstall UI for
1418 public void uninstallUI(JComponent c)
1420 prepareForUIUninstall();
1421 uninstallDefaults();
1422 uninstallKeyboardActions();
1423 uninstallListeners();
1424 tree = null;
1425 uninstallComponents();
1426 completeUIUninstall();
1430 * Paints the specified component appropriate for the look and feel. This
1431 * method is invoked from the ComponentUI.update method when the specified
1432 * component is being painted. Subclasses should override this method and use
1433 * the specified Graphics object to render the content of the component.
1435 * @param g
1436 * the Graphics context in which to paint
1437 * @param c
1438 * the component being painted; this argument is often ignored, but
1439 * might be used if the UI object is stateless and shared by multiple
1440 * components
1442 public void paint(Graphics g, JComponent c)
1444 JTree tree = (JTree) c;
1445 updateCurrentVisiblePath();
1447 Rectangle clip = g.getClipBounds();
1448 Insets insets = tree.getInsets();
1450 if (clip != null && treeModel != null && currentVisiblePath != null)
1452 int startIndex = tree.getClosestRowForLocation(clip.x, clip.y);
1453 int endIndex = tree.getClosestRowForLocation(clip.x + clip.width,
1454 clip.y + clip.height);
1456 paintVerticalPartOfLeg(g, clip, insets, currentVisiblePath);
1457 for (int i = startIndex; i <= endIndex; i++)
1459 Object curr = currentVisiblePath.getPathComponent(i);
1460 boolean isLeaf = treeModel.isLeaf(curr);
1461 TreePath path = new TreePath(getPathToRoot(curr, 0));
1463 boolean isExpanded = tree.isExpanded(path);
1464 Rectangle bounds = getPathBounds(tree, path);
1465 paintHorizontalPartOfLeg(g, clip, insets, bounds, path, i,
1466 isExpanded, false, isLeaf);
1467 paintRow(g, clip, insets, bounds, path, i, isExpanded, false,
1468 isLeaf);
1474 * Ensures that the rows identified by beginRow through endRow are visible.
1476 * @param beginRow
1477 * is the first row
1478 * @param endRow
1479 * is the last row
1481 protected void ensureRowsAreVisible(int beginRow, int endRow)
1483 if (beginRow < endRow)
1485 int temp = endRow;
1486 endRow = beginRow;
1487 beginRow = temp;
1490 for (int i = beginRow; i < endRow; i++)
1492 TreePath path = getPathForRow(tree, i);
1493 if (!tree.isVisible(path))
1494 tree.makeVisible(path);
1499 * Sets the preferred minimum size.
1501 * @param newSize
1502 * is the new preferred minimum size.
1504 public void setPreferredMinSize(Dimension newSize)
1506 preferredMinSize = newSize;
1510 * Gets the preferred minimum size.
1512 * @returns the preferred minimum size.
1514 public Dimension getPreferredMinSize()
1516 return preferredMinSize;
1520 * Returns the preferred size to properly display the tree, this is a cover
1521 * method for getPreferredSize(c, false).
1523 * @param c
1524 * the component whose preferred size is being queried; this argument
1525 * is often ignored but might be used if the UI object is stateless
1526 * and shared by multiple components
1527 * @return the preferred size
1529 public Dimension getPreferredSize(JComponent c)
1531 return getPreferredSize(c, false);
1535 * Returns the preferred size to represent the tree in c. If checkConsistancy
1536 * is true, checkConsistancy is messaged first.
1538 * @param c
1539 * the component whose preferred size is being queried.
1540 * @param checkConsistancy
1541 * if true must check consistancy
1542 * @return the preferred size
1544 public Dimension getPreferredSize(JComponent c, boolean checkConsistancy)
1546 // FIXME: checkConsistancy not implemented, c not used
1547 if (!validCachedPreferredSize)
1548 updateCachedPreferredSize();
1549 return preferredSize;
1553 * Returns the minimum size for this component. Which will be the min
1554 * preferred size or (0,0).
1556 * @param c
1557 * the component whose min size is being queried.
1558 * @returns the preferred size or null
1560 public Dimension getMinimumSize(JComponent c)
1562 Dimension min = getPreferredMinSize();
1563 if (min == null)
1564 return new Dimension();
1565 return min;
1569 * Returns the maximum size for the component, which will be the preferred
1570 * size if the instance is currently in JTree or (0,0).
1572 * @param c
1573 * the component whose preferred size is being queried
1574 * @return the max size or null
1576 public Dimension getMaximumSize(JComponent c)
1578 if (c instanceof JTree)
1579 return ((JTree) c).getPreferredSize();
1580 return new Dimension();
1584 * Messages to stop the editing session. If the UI the receiver is providing
1585 * the look and feel for returns true from
1586 * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked
1587 * on the current editor. Then completeEditing will be messaged with false,
1588 * true, false to cancel any lingering editing.
1590 protected void completeEditing()
1592 completeEditing(false, true, false);
1596 * Stops the editing session. If messageStop is true, the editor is messaged
1597 * with stopEditing, if messageCancel is true the editor is messaged with
1598 * cancelEditing. If messageTree is true, the treeModel is messaged with
1599 * valueForPathChanged.
1601 * @param messageStop
1602 * message to stop editing
1603 * @param messageCancel
1604 * message to cancel editing
1605 * @param messageTree
1606 * message to treeModel
1608 protected void completeEditing(boolean messageStop, boolean messageCancel,
1609 boolean messageTree)
1611 if (messageStop)
1613 getCellEditor().stopCellEditing();
1614 stopEditingInCompleteEditing = true;
1617 if (messageCancel)
1619 getCellEditor().cancelCellEditing();
1620 stopEditingInCompleteEditing = true;
1623 if (messageTree)
1624 treeModel.valueForPathChanged(tree.getLeadSelectionPath(), newVal);
1628 * Will start editing for node if there is a cellEditor and shouldSelectCall
1629 * returns true. This assumes that path is valid and visible.
1631 * @param path
1632 * is the path to start editing
1633 * @param event
1634 * is the MouseEvent performed on the path
1635 * @return true if successful
1637 protected boolean startEditing(TreePath path, MouseEvent event)
1639 int x;
1640 int y;
1641 if (event == null)
1643 Rectangle bounds = getPathBounds(tree, path);
1644 x = bounds.x;
1645 y = bounds.y;
1647 else
1649 x = event.getX();
1650 y = event.getY();
1653 updateCellEditor();
1654 TreeCellEditor ed = getCellEditor();
1655 if (ed != null && ed.shouldSelectCell(event) && ed.isCellEditable(event))
1657 editingPath = path;
1658 editingRow = tree.getRowForPath(editingPath);
1660 Object val = editingPath.getLastPathComponent();
1661 cellEditor.addCellEditorListener(cellEditorListener);
1662 stopEditingInCompleteEditing = false;
1663 boolean expanded = tree.isExpanded(editingPath);
1664 isEditing = true;
1665 editingComponent = ed.getTreeCellEditorComponent(tree, val, true,
1666 expanded,
1667 isLeaf(editingRow),
1668 editingRow);
1669 editingComponent.getParent().setVisible(true);
1670 editingComponent.getParent().validate();
1671 tree.add(editingComponent.getParent());
1672 editingComponent.getParent().validate();
1673 validCachedPreferredSize = false;
1675 ((JTextField) editingComponent).requestFocusInWindow(false);
1676 editorTimer.start();
1677 return true;
1679 return false;
1683 * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or
1684 * collapse region of the row, this will toggle the row.
1686 * @param path
1687 * the path we are concerned with
1688 * @param mouseX
1689 * is the cursor's x position
1690 * @param mouseY
1691 * is the cursor's y position
1693 protected void checkForClickInExpandControl(TreePath path, int mouseX,
1694 int mouseY)
1696 if (isLocationInExpandControl(path, mouseX, mouseY))
1697 toggleExpandState(path);
1701 * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in
1702 * the area of row that is used to expand/collpse the node and the node at row
1703 * does not represent a leaf.
1705 * @param path
1706 * the path we are concerned with
1707 * @param mouseX
1708 * is the cursor's x position
1709 * @param mouseY
1710 * is the cursor's y position
1711 * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in
1712 * the area of row that is used to expand/collpse the node and the
1713 * node at row does not represent a leaf.
1715 protected boolean isLocationInExpandControl(TreePath path, int mouseX,
1716 int mouseY)
1718 boolean cntlClick = false;
1719 int row = getRowForPath(tree, path);
1721 if (!isLeaf(row))
1723 Rectangle bounds = getPathBounds(tree, path);
1725 if (hasControlIcons()
1726 && (mouseX < bounds.x)
1727 && (mouseX > (bounds.x - getCurrentControlIcon(path).getIconWidth() - gap)))
1728 cntlClick = true;
1730 return cntlClick;
1734 * Messaged when the user clicks the particular row, this invokes
1735 * toggleExpandState.
1737 * @param path
1738 * the path we are concerned with
1739 * @param mouseX
1740 * is the cursor's x position
1741 * @param mouseY
1742 * is the cursor's y position
1744 protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY)
1746 toggleExpandState(path);
1750 * Expands path if it is not expanded, or collapses row if it is expanded. If
1751 * expanding a path and JTree scroll on expand, ensureRowsAreVisible is
1752 * invoked to scroll as many of the children to visible as possible (tries to
1753 * scroll to last visible descendant of path).
1755 * @param path
1756 * the path we are concerned with
1758 protected void toggleExpandState(TreePath path)
1760 if (tree.isExpanded(path))
1761 tree.collapsePath(path);
1762 else
1763 tree.expandPath(path);
1767 * Returning true signifies a mouse event on the node should toggle the
1768 * selection of only the row under the mouse.
1770 * @param event
1771 * is the MouseEvent performed on the row.
1772 * @return true signifies a mouse event on the node should toggle the
1773 * selection of only the row under the mouse.
1775 protected boolean isToggleSelectionEvent(MouseEvent event)
1777 return (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION);
1781 * Returning true signifies a mouse event on the node should select from the
1782 * anchor point.
1784 * @param event
1785 * is the MouseEvent performed on the node.
1786 * @return true signifies a mouse event on the node should select from the
1787 * anchor point.
1789 protected boolean isMultiSelectEvent(MouseEvent event)
1791 return (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
1795 * Returning true indicates the row under the mouse should be toggled based on
1796 * the event. This is invoked after checkForClickInExpandControl, implying the
1797 * location is not in the expand (toggle) control.
1799 * @param event
1800 * is the MouseEvent performed on the row.
1801 * @return true indicates the row under the mouse should be toggled based on
1802 * the event.
1804 protected boolean isToggleEvent(MouseEvent event)
1806 return true;
1810 * Messaged to update the selection based on a MouseEvent over a particular
1811 * row. If the even is a toggle selection event, the row is either selected,
1812 * or deselected. If the event identifies a multi selection event, the
1813 * selection is updated from the anchor point. Otherwise, the row is selected,
1814 * and if the even specified a toggle event the row is expanded/collapsed.
1816 * @param path
1817 * is the path selected for an event
1818 * @param event
1819 * is the MouseEvent performed on the path.
1821 protected void selectPathForEvent(TreePath path, MouseEvent event)
1823 if (isToggleSelectionEvent(event))
1825 if (tree.isPathSelected(path))
1826 tree.removeSelectionPath(path);
1827 else
1829 tree.addSelectionPath(path);
1830 tree.setAnchorSelectionPath(path);
1833 else if (isMultiSelectEvent(event))
1835 TreePath anchor = tree.getAnchorSelectionPath();
1836 if (anchor != null)
1838 int aRow = getRowForPath(tree, anchor);
1839 tree.addSelectionInterval(aRow, getRowForPath(tree, path));
1841 else
1842 tree.addSelectionPath(path);
1844 else
1845 tree.addSelectionPath(path);
1849 * Returns true if the node at <code>row</code> is a leaf.
1851 * @param row
1852 * is the row we are concerned with.
1853 * @return true if the node at <code>row</code> is a leaf.
1855 protected boolean isLeaf(int row)
1857 TreePath pathForRow = getPathForRow(tree, row);
1858 if (pathForRow == null)
1859 return true;
1861 Object node = pathForRow.getLastPathComponent();
1862 return treeModel.isLeaf(node);
1866 * This class implements the actions that we want to happen when specific keys
1867 * are pressed for the JTree. The actionPerformed method is called when a key
1868 * that has been registered for the JTree is received.
1870 class TreeAction extends AbstractAction
1874 * What to do when this action is called.
1876 * @param e
1877 * the ActionEvent that caused this action.
1879 public void actionPerformed(ActionEvent e)
1881 TreePath lead = tree.getLeadSelectionPath();
1883 if (e.getActionCommand().equals("selectPreviousChangeLead")
1884 || e.getActionCommand().equals("selectPreviousExtendSelection")
1885 || e.getActionCommand().equals("selectPrevious")
1886 || e.getActionCommand().equals("selectNext")
1887 || e.getActionCommand().equals("selectNextExtendSelection")
1888 || e.getActionCommand().equals("selectNextChangeLead"))
1889 (new TreeIncrementAction(0, "")).actionPerformed(e);
1890 else if (e.getActionCommand().equals("selectParent")
1891 || e.getActionCommand().equals("selectChild"))
1892 (new TreeTraverseAction(0, "")).actionPerformed(e);
1893 else if (e.getActionCommand().equals("selectAll"))
1895 TreePath[] paths = new TreePath[tree.getVisibleRowCount()];
1897 Object curr = getNextVisibleNode(treeModel.getRoot());
1898 int i = 0;
1899 while (curr != null && i < paths.length)
1901 paths[i] = new TreePath(getPathToRoot(curr, 0));
1902 i++;
1905 tree.addSelectionPaths(paths);
1907 else if (e.getActionCommand().equals("startEditing"))
1908 tree.startEditingAtPath(lead);
1909 else if (e.getActionCommand().equals("toggle"))
1911 if (tree.isEditing())
1912 tree.stopEditing();
1913 else
1915 Object last = lead.getLastPathComponent();
1916 TreePath path = new TreePath(getPathToRoot(last, 0));
1917 if (!treeModel.isLeaf(last))
1918 toggleExpandState(path);
1921 else if (e.getActionCommand().equals("clearSelection"))
1922 tree.clearSelection();
1924 if (tree.isEditing() && !e.getActionCommand().equals("startEditing"))
1925 tree.cancelEditing();
1927 tree.scrollPathToVisible(lead);
1932 * This class is used to mimic the behaviour of the JDK when registering
1933 * keyboard actions. It is the same as the private class used in JComponent
1934 * for the same reason. This class receives an action event and dispatches it
1935 * to the true receiver after altering the actionCommand property of the
1936 * event.
1938 private static class ActionListenerProxy extends AbstractAction
1940 ActionListener target;
1942 String bindingCommandName;
1944 public ActionListenerProxy(ActionListener li, String cmd)
1946 target = li;
1947 bindingCommandName = cmd;
1950 public void actionPerformed(ActionEvent e)
1952 ActionEvent derivedEvent = new ActionEvent(e.getSource(), e.getID(),
1953 bindingCommandName,
1954 e.getModifiers());
1956 target.actionPerformed(derivedEvent);
1961 * The timer that updates the editor component.
1963 private class EditorUpdateTimer extends Timer implements ActionListener
1966 * Creates a new EditorUpdateTimer object with a default delay of 0.3
1967 * seconds.
1969 public EditorUpdateTimer()
1971 super(300, null);
1972 addActionListener(this);
1976 * Lets the caret blink and repaints the table.
1978 public void actionPerformed(ActionEvent ev)
1980 Caret c = ((JTextField) editingComponent).getCaret();
1981 if (c != null)
1982 c.setVisible(!c.isVisible());
1983 tree.repaint();
1987 * Updates the blink delay according to the current caret.
1989 public void update()
1991 stop();
1992 Caret c = ((JTextField) editingComponent).getCaret();
1993 if (c != null)
1995 setDelay(c.getBlinkRate());
1996 if (((JTextField) editingComponent).isEditable())
1997 start();
1998 else
1999 c.setVisible(false);
2005 * Updates the preferred size when scrolling, if necessary.
2007 public class ComponentHandler extends ComponentAdapter implements
2008 ActionListener
2011 * Timer used when inside a scrollpane and the scrollbar is adjusting
2013 protected Timer timer;
2015 /** ScrollBar that is being adjusted */
2016 protected JScrollBar scrollBar;
2019 * Constructor
2021 public ComponentHandler()
2023 // Nothing to do here.
2027 * Invoked when the component's position changes.
2029 * @param e
2030 * the event that occurs when moving the component
2032 public void componentMoved(ComponentEvent e)
2034 // TODO: What should be done here, if anything?
2038 * Creates, if necessary, and starts a Timer to check if needed to resize
2039 * the bounds
2041 protected void startTimer()
2043 // TODO: Implement this properly.
2047 * Returns the JScrollPane housing the JTree, or null if one isn't found.
2049 * @return JScrollPane housing the JTree, or null if one isn't found.
2051 protected JScrollPane getScrollPane()
2053 return null;
2057 * Public as a result of Timer. If the scrollBar is null, or not adjusting,
2058 * this stops the timer and updates the sizing.
2060 * @param ae
2061 * is the action performed
2063 public void actionPerformed(ActionEvent ae)
2065 // TODO: Implement this properly.
2070 * Listener responsible for getting cell editing events and updating the tree
2071 * accordingly.
2073 public class CellEditorHandler implements CellEditorListener
2076 * Constructor
2078 public CellEditorHandler()
2080 // Nothing to do here.
2084 * Messaged when editing has stopped in the tree. Tells the listeners
2085 * editing has stopped.
2087 * @param e
2088 * is the notification event
2090 public void editingStopped(ChangeEvent e)
2092 editingPath = null;
2093 editingRow = -1;
2094 stopEditingInCompleteEditing = false;
2095 if (editingComponent != null)
2097 tree.remove(editingComponent.getParent());
2098 editingComponent = null;
2100 if (cellEditor != null)
2102 newVal = ((JTextField) getCellEditor().getCellEditorValue()).getText();
2103 completeEditing(false, false, true);
2104 if (cellEditor instanceof DefaultTreeCellEditor)
2105 tree.removeTreeSelectionListener((DefaultTreeCellEditor) cellEditor);
2106 cellEditor.removeCellEditorListener(cellEditorListener);
2107 setCellEditor(null);
2108 createdCellEditor = false;
2110 isEditing = false;
2111 tree.requestFocusInWindow(false);
2112 editorTimer.stop();
2113 validCachedPreferredSize = false;
2114 tree.repaint();
2118 * Messaged when editing has been canceled in the tree. This tells the
2119 * listeners the editor has canceled editing.
2121 * @param e
2122 * is the notification event
2124 public void editingCanceled(ChangeEvent e)
2126 editingPath = null;
2127 editingRow = -1;
2128 stopEditingInCompleteEditing = false;
2129 if (editingComponent != null)
2130 tree.remove(editingComponent.getParent());
2131 editingComponent = null;
2132 if (cellEditor != null)
2134 if (cellEditor instanceof DefaultTreeCellEditor)
2135 tree.removeTreeSelectionListener((DefaultTreeCellEditor) cellEditor);
2136 cellEditor.removeCellEditorListener(cellEditorListener);
2137 setCellEditor(null);
2138 createdCellEditor = false;
2140 tree.requestFocusInWindow(false);
2141 editorTimer.stop();
2142 isEditing = false;
2143 validCachedPreferredSize = false;
2144 tree.repaint();
2146 }// CellEditorHandler
2149 * Repaints the lead selection row when focus is lost/grained.
2151 public class FocusHandler implements FocusListener
2154 * Constructor
2156 public FocusHandler()
2158 // Nothing to do here.
2162 * Invoked when focus is activated on the tree we're in, redraws the lead
2163 * row. Invoked when a component gains the keyboard focus.
2165 * @param e
2166 * is the focus event that is activated
2168 public void focusGained(FocusEvent e)
2170 // TODO: Implement this properly.
2174 * Invoked when focus is deactivated on the tree we're in, redraws the lead
2175 * row. Invoked when a component loses the keyboard focus.
2177 * @param e
2178 * is the focus event that is deactivated
2180 public void focusLost(FocusEvent e)
2182 // TODO: Implement this properly.
2187 * This is used to get multiple key down events to appropriately genereate
2188 * events.
2190 public class KeyHandler extends KeyAdapter
2192 /** Key code that is being generated for. */
2193 protected Action repeatKeyAction;
2195 /** Set to true while keyPressed is active */
2196 protected boolean isKeyDown;
2199 * Constructor
2201 public KeyHandler()
2203 // Nothing to do here.
2207 * Invoked when a key has been typed. Moves the keyboard focus to the first
2208 * element whose first letter matches the alphanumeric key pressed by the
2209 * user. Subsequent same key presses move the keyboard focus to the next
2210 * object that starts with the same letter.
2212 * @param e
2213 * the key typed
2215 public void keyTyped(KeyEvent e)
2217 // TODO: What should be done here, if anything?
2221 * Invoked when a key has been pressed.
2223 * @param e
2224 * the key pressed
2226 public void keyPressed(KeyEvent e)
2228 // TODO: What should be done here, if anything?
2232 * Invoked when a key has been released
2234 * @param e
2235 * the key released
2237 public void keyReleased(KeyEvent e)
2239 // TODO: What should be done here, if anything?
2244 * MouseListener is responsible for updating the selection based on mouse
2245 * events.
2247 public class MouseHandler extends MouseAdapter implements MouseMotionListener
2250 * Constructor
2252 public MouseHandler()
2254 // Nothing to do here.
2258 * Invoked when a mouse button has been pressed on a component.
2260 * @param e
2261 * is the mouse event that occured
2263 public void mousePressed(MouseEvent e)
2265 Point click = e.getPoint();
2266 TreePath path = getClosestPathForLocation(tree, click.x, click.y);
2268 if (path != null)
2270 Rectangle bounds = getPathBounds(tree, path);
2271 int row = getRowForPath(tree, path);
2272 boolean cntlClick = isLocationInExpandControl(path, click.x, click.y);
2274 boolean isLeaf = isLeaf(row);
2276 TreeCellRenderer tcr = getCellRenderer();
2277 Icon icon;
2278 if (isLeaf)
2279 icon = UIManager.getIcon("Tree.leafIcon");
2280 else if (tree.isExpanded(path))
2281 icon = UIManager.getIcon("Tree.openIcon");
2282 else
2283 icon = UIManager.getIcon("Tree.closedIcon");
2285 if (tcr instanceof DefaultTreeCellRenderer)
2287 Icon tmp = ((DefaultTreeCellRenderer) tcr).getIcon();
2288 if (tmp != null)
2289 icon = tmp;
2292 // add gap*2 for the space before and after the text
2293 if (icon != null)
2294 bounds.width += icon.getIconWidth() + gap * 2;
2296 boolean inBounds = bounds.contains(click.x, click.y);
2297 if ((inBounds || cntlClick) && tree.isVisible(path))
2299 if (inBounds)
2301 selectPath(tree, path);
2302 if (e.getClickCount() == 2 && !isLeaf(row))
2303 toggleExpandState(path);
2306 if (cntlClick)
2308 handleExpandControlClick(path, click.x, click.y);
2309 if (cellEditor != null)
2310 cellEditor.cancelCellEditing();
2311 tree.scrollPathToVisible(path);
2313 else if (tree.isEditable())
2314 startEditing(path, e);
2320 * Invoked when a mouse button is pressed on a component and then dragged.
2321 * MOUSE_DRAGGED events will continue to be delivered to the component where
2322 * the drag originated until the mouse button is released (regardless of
2323 * whether the mouse position is within the bounds of the component).
2325 * @param e
2326 * is the mouse event that occured
2328 public void mouseDragged(MouseEvent e)
2330 // TODO: What should be done here, if anything?
2334 * Invoked when the mouse button has been moved on a component (with no
2335 * buttons no down).
2337 * @param e
2338 * the mouse event that occured
2340 public void mouseMoved(MouseEvent e)
2342 // TODO: What should be done here, if anything?
2346 * Invoked when a mouse button has been released on a component.
2348 * @param e
2349 * is the mouse event that occured
2351 public void mouseReleased(MouseEvent e)
2353 // TODO: What should be done here, if anything?
2358 * MouseInputHandler handles passing all mouse events, including mouse motion
2359 * events, until the mouse is released to the destination it is constructed
2360 * with.
2362 public class MouseInputHandler implements MouseInputListener
2364 /** Source that events are coming from */
2365 protected Component source;
2367 /** Destination that receives all events. */
2368 protected Component destination;
2371 * Constructor
2373 * @param source
2374 * that events are coming from
2375 * @param destination
2376 * that receives all events
2377 * @param e
2378 * is the event received
2380 public MouseInputHandler(Component source, Component destination,
2381 MouseEvent e)
2383 this.source = source;
2384 this.destination = destination;
2388 * Invoked when the mouse button has been clicked (pressed and released) on
2389 * a component.
2391 * @param e
2392 * mouse event that occured
2394 public void mouseClicked(MouseEvent e)
2396 // TODO: What should be done here, if anything?
2400 * Invoked when a mouse button has been pressed on a component.
2402 * @param e
2403 * mouse event that occured
2405 public void mousePressed(MouseEvent e)
2407 // TODO: What should be done here, if anything?
2411 * Invoked when a mouse button has been released on a component.
2413 * @param e
2414 * mouse event that occured
2416 public void mouseReleased(MouseEvent e)
2418 // TODO: What should be done here, if anything?
2422 * Invoked when the mouse enters a component.
2424 * @param e
2425 * mouse event that occured
2427 public void mouseEntered(MouseEvent e)
2429 // TODO: What should be done here, if anything?
2433 * Invoked when the mouse exits a component.
2435 * @param e
2436 * mouse event that occured
2438 public void mouseExited(MouseEvent e)
2440 // TODO: What should be done here, if anything?
2444 * Invoked when a mouse button is pressed on a component and then dragged.
2445 * MOUSE_DRAGGED events will continue to be delivered to the component where
2446 * the drag originated until the mouse button is released (regardless of
2447 * whether the mouse position is within the bounds of the component).
2449 * @param e
2450 * mouse event that occured
2452 public void mouseDragged(MouseEvent e)
2454 // TODO: What should be done here, if anything?
2458 * Invoked when the mouse cursor has been moved onto a component but no
2459 * buttons have been pushed.
2461 * @param e
2462 * mouse event that occured
2464 public void mouseMoved(MouseEvent e)
2466 // TODO: What should be done here, if anything?
2470 * Removes event from the source
2472 protected void removeFromSource()
2474 // TODO: Implement this properly.
2479 * Class responsible for getting size of node, method is forwarded to
2480 * BasicTreeUI method. X location does not include insets, that is handled in
2481 * getPathBounds.
2483 public class NodeDimensionsHandler extends AbstractLayoutCache.NodeDimensions
2486 * Constructor
2488 public NodeDimensionsHandler()
2490 // Nothing to do here.
2494 * Returns, by reference in bounds, the size and x origin to place value at.
2495 * The calling method is responsible for determining the Y location. If
2496 * bounds is null, a newly created Rectangle should be returned, otherwise
2497 * the value should be placed in bounds and returned.
2499 * @param cell
2500 * the value to be represented
2501 * @param row
2502 * row being queried
2503 * @param depth
2504 * the depth of the row
2505 * @param expanded
2506 * true if row is expanded
2507 * @param size
2508 * a Rectangle containing the size needed to represent value
2509 * @return containing the node dimensions, or null if node has no dimension
2511 public Rectangle getNodeDimensions(Object cell, int row, int depth,
2512 boolean expanded, Rectangle size)
2514 if (size == null || cell == null)
2515 return null;
2517 String s = cell.toString();
2518 Font f = tree.getFont();
2519 FontMetrics fm = tree.getToolkit().getFontMetrics(f);
2521 if (s != null)
2523 size.x = getRowX(row, depth);
2524 size.width = SwingUtilities.computeStringWidth(fm, s);
2525 size.height = getMaxHeight(tree);
2526 size.y = size.height * row;
2529 return size;
2533 * Returns the amount to indent the given row
2535 * @return amount to indent the given row.
2537 protected int getRowX(int row, int depth)
2539 if (row == 0)
2540 return 0;
2541 return depth * rightChildIndent;
2543 }// NodeDimensionsHandler
2546 * PropertyChangeListener for the tree. Updates the appropriate varaible, or
2547 * TreeState, based on what changes.
2549 public class PropertyChangeHandler implements PropertyChangeListener
2553 * Constructor
2555 public PropertyChangeHandler()
2557 // Nothing to do here.
2561 * This method gets called when a bound property is changed.
2563 * @param event
2564 * A PropertyChangeEvent object describing the event source and the
2565 * property that has changed.
2567 public void propertyChange(PropertyChangeEvent event)
2569 if ((event.getPropertyName()).equals("rootVisible"))
2571 validCachedPreferredSize = false;
2572 tree.repaint();
2578 * Listener on the TreeSelectionModel, resets the row selection if any of the
2579 * properties of the model change.
2581 public class SelectionModelPropertyChangeHandler implements
2582 PropertyChangeListener
2586 * Constructor
2588 public SelectionModelPropertyChangeHandler()
2590 // Nothing to do here.
2594 * This method gets called when a bound property is changed.
2596 * @param event
2597 * A PropertyChangeEvent object describing the event source and the
2598 * property that has changed.
2600 public void propertyChange(PropertyChangeEvent event)
2602 // TODO: What should be done here, if anything?
2607 * ActionListener that invokes cancelEditing when action performed.
2609 public class TreeCancelEditingAction extends AbstractAction
2613 * Constructor
2615 public TreeCancelEditingAction(String name)
2617 // TODO: Implement this properly.
2621 * Invoked when an action occurs.
2623 * @param e
2624 * event that occured
2626 public void actionPerformed(ActionEvent e)
2628 // TODO: Implement this properly.
2632 * Returns true if the action is enabled.
2634 * @return true if the action is enabled, false otherwise
2636 public boolean isEnabled()
2638 // TODO: Implement this properly.
2639 return false;
2644 * Updates the TreeState in response to nodes expanding/collapsing.
2646 public class TreeExpansionHandler implements TreeExpansionListener
2650 * Constructor
2652 public TreeExpansionHandler()
2654 // Nothing to do here.
2658 * Called whenever an item in the tree has been expanded.
2660 * @param event
2661 * is the event that occured
2663 public void treeExpanded(TreeExpansionEvent event)
2665 validCachedPreferredSize = false;
2666 tree.repaint();
2670 * Called whenever an item in the tree has been collapsed.
2672 * @param event
2673 * is the event that occured
2675 public void treeCollapsed(TreeExpansionEvent event)
2677 validCachedPreferredSize = false;
2678 tree.repaint();
2680 }// TreeExpansionHandler
2683 * TreeHomeAction is used to handle end/home actions. Scrolls either the first
2684 * or last cell to be visible based on direction.
2686 public class TreeHomeAction extends AbstractAction
2689 /** direction is either home or end */
2690 protected int direction;
2693 * Constructor
2695 * @param direction -
2696 * it is home or end
2697 * @param name
2698 * is the name of the direction
2700 public TreeHomeAction(int direction, String name)
2702 // TODO: Implement this properly
2706 * Invoked when an action occurs.
2708 * @param e
2709 * is the event that occured
2711 public void actionPerformed(ActionEvent e)
2713 // TODO: Implement this properly
2717 * Returns true if the action is enabled.
2719 * @return true if the action is enabled.
2721 public boolean isEnabled()
2723 // TODO: Implement this properly
2724 return false;
2729 * TreeIncrementAction is used to handle up/down actions. Selection is moved
2730 * up or down based on direction.
2732 public class TreeIncrementAction extends AbstractAction
2735 /** Specifies the direction to adjust the selection by. */
2736 protected int direction;
2739 * Constructor
2741 * @param direction
2742 * up or down
2743 * @param name
2744 * is the name of the direction
2746 public TreeIncrementAction(int direction, String name)
2748 // TODO: Implement this properly
2752 * Invoked when an action occurs.
2754 * @param e
2755 * is the event that occured
2757 public void actionPerformed(ActionEvent e)
2759 Object last = tree.getLeadSelectionPath().getLastPathComponent();
2761 if (e.getActionCommand().equals("selectPreviousChangeLead"))
2763 Object prev = getPreviousVisibleNode(last);
2765 if (prev != null)
2767 TreePath newPath = new TreePath(getPathToRoot(prev, 0));
2768 selectPath(tree, newPath);
2769 tree.setLeadSelectionPath(newPath);
2772 else if (e.getActionCommand().equals("selectPreviousExtendSelection"))
2774 Object prev = getPreviousVisibleNode(last);
2775 if (prev != null)
2777 TreePath newPath = new TreePath(getPathToRoot(prev, 0));
2778 tree.addSelectionPath(newPath);
2779 tree.setLeadSelectionPath(newPath);
2782 else if (e.getActionCommand().equals("selectPrevious"))
2784 Object prev = getPreviousVisibleNode(last);
2786 if (prev != null)
2788 TreePath newPath = new TreePath(getPathToRoot(prev, 0));
2789 selectPath(tree, newPath);
2792 else if (e.getActionCommand().equals("selectNext"))
2794 Object next = getNextVisibleNode(last);
2796 if (next != null)
2798 TreePath newPath = new TreePath(getPathToRoot(next, 0));
2799 selectPath(tree, newPath);
2802 else if (e.getActionCommand().equals("selectNextExtendSelection"))
2804 Object next = getNextVisibleNode(last);
2805 if (next != null)
2807 TreePath newPath = new TreePath(getPathToRoot(next, 0));
2808 tree.addSelectionPath(newPath);
2809 tree.setLeadSelectionPath(newPath);
2812 else if (e.getActionCommand().equals("selectNextChangeLead"))
2814 Object next = getNextVisibleNode(last);
2815 if (next != null)
2817 TreePath newPath = new TreePath(getPathToRoot(next, 0));
2818 selectPath(tree, newPath);
2819 tree.setLeadSelectionPath(newPath);
2825 * Returns true if the action is enabled.
2827 * @return true if the action is enabled.
2829 public boolean isEnabled()
2831 // TODO: Implement this properly
2832 return false;
2837 * Forwards all TreeModel events to the TreeState.
2839 public class TreeModelHandler implements TreeModelListener
2842 * Constructor
2844 public TreeModelHandler()
2846 // Nothing to do here.
2850 * Invoked after a node (or a set of siblings) has changed in some way. The
2851 * node(s) have not changed locations in the tree or altered their children
2852 * arrays, but other attributes have changed and may affect presentation.
2853 * Example: the name of a file has changed, but it is in the same location
2854 * in the file system. To indicate the root has changed, childIndices and
2855 * children will be null. Use e.getPath() to get the parent of the changed
2856 * node(s). e.getChildIndices() returns the index(es) of the changed
2857 * node(s).
2859 * @param e
2860 * is the event that occured
2862 public void treeNodesChanged(TreeModelEvent e)
2864 validCachedPreferredSize = false;
2865 tree.repaint();
2869 * Invoked after nodes have been inserted into the tree. Use e.getPath() to
2870 * get the parent of the new node(s). e.getChildIndices() returns the
2871 * index(es) of the new node(s) in ascending order.
2873 * @param e
2874 * is the event that occured
2876 public void treeNodesInserted(TreeModelEvent e)
2878 validCachedPreferredSize = false;
2879 tree.repaint();
2883 * Invoked after nodes have been removed from the tree. Note that if a
2884 * subtree is removed from the tree, this method may only be invoked once
2885 * for the root of the removed subtree, not once for each individual set of
2886 * siblings removed. Use e.getPath() to get the former parent of the deleted
2887 * node(s). e.getChildIndices() returns, in ascending order, the index(es)
2888 * the node(s) had before being deleted.
2890 * @param e
2891 * is the event that occured
2893 public void treeNodesRemoved(TreeModelEvent e)
2895 validCachedPreferredSize = false;
2896 tree.repaint();
2900 * Invoked after the tree has drastically changed structure from a given
2901 * node down. If the path returned by e.getPath() is of length one and the
2902 * first element does not identify the current root node the first element
2903 * should become the new root of the tree. Use e.getPath() to get the path
2904 * to the node. e.getChildIndices() returns null.
2906 * @param e
2907 * is the event that occured
2909 public void treeStructureChanged(TreeModelEvent e)
2911 if (e.getPath().length == 1
2912 && !e.getPath()[0].equals(treeModel.getRoot()))
2913 tree.expandPath(new TreePath(treeModel.getRoot()));
2914 validCachedPreferredSize = false;
2915 tree.repaint();
2917 }// TreeModelHandler
2920 * TreePageAction handles page up and page down events.
2922 public class TreePageAction extends AbstractAction
2924 /** Specifies the direction to adjust the selection by. */
2925 protected int direction;
2928 * Constructor
2930 * @param direction
2931 * up or down
2932 * @param name
2933 * is the name of the direction
2935 public TreePageAction(int direction, String name)
2937 this.direction = direction;
2941 * Invoked when an action occurs.
2943 * @param e
2944 * is the event that occured
2946 public void actionPerformed(ActionEvent e)
2948 // TODO: Implement this properly.
2952 * Returns true if the action is enabled.
2954 * @return true if the action is enabled.
2956 public boolean isEnabled()
2958 return false;
2960 }// TreePageAction
2963 * Listens for changes in the selection model and updates the display
2964 * accordingly.
2966 public class TreeSelectionHandler implements TreeSelectionListener
2969 * Constructor
2971 public TreeSelectionHandler()
2973 // Nothing to do here.
2977 * Messaged when the selection changes in the tree we're displaying for.
2978 * Stops editing, messages super and displays the changed paths.
2980 * @param event
2981 * the event that characterizes the change.
2983 public void valueChanged(TreeSelectionEvent event)
2985 if (tree.isEditing())
2986 tree.cancelEditing();
2988 }// TreeSelectionHandler
2991 * For the first selected row expandedness will be toggled.
2993 public class TreeToggleAction extends AbstractAction
2996 * Constructor
2998 * @param name
2999 * is the name of <code>Action</code> field
3001 public TreeToggleAction(String name)
3003 // Nothing to do here.
3007 * Invoked when an action occurs.
3009 * @param e
3010 * the event that occured
3012 public void actionPerformed(ActionEvent e)
3014 // TODO: Implement this properly.
3018 * Returns true if the action is enabled.
3020 * @return true if the action is enabled, false otherwise
3022 public boolean isEnabled()
3024 return false;
3026 } // TreeToggleAction
3029 * TreeTraverseAction is the action used for left/right keys. Will toggle the
3030 * expandedness of a node, as well as potentially incrementing the selection.
3032 public class TreeTraverseAction extends AbstractAction
3035 * Determines direction to traverse, 1 means expand, -1 means collapse.
3037 protected int direction;
3040 * Constructor
3042 * @param direction
3043 * to traverse
3044 * @param name
3045 * is the name of the direction
3047 public TreeTraverseAction(int direction, String name)
3049 this.direction = direction;
3053 * Invoked when an action occurs.
3055 * @param e
3056 * the event that occured
3058 public void actionPerformed(ActionEvent e)
3060 Object last = tree.getLeadSelectionPath().getLastPathComponent();
3062 if (e.getActionCommand().equals("selectParent"))
3064 TreePath path = new TreePath(getPathToRoot(last, 0));
3065 Object p = getParent(treeModel.getRoot(), last);
3067 if (!treeModel.isLeaf(last))
3068 toggleExpandState(path);
3069 else if (p != null)
3070 selectPath(tree, new TreePath(getPathToRoot(p, 0)));
3072 else if (e.getActionCommand().equals("selectChild"))
3074 TreePath path = new TreePath(getPathToRoot(last, 0));
3076 if (!treeModel.isLeaf(last))
3077 toggleExpandState(path);
3078 else
3080 Object next = getNextVisibleNode(last);
3082 if (next != null)
3083 selectPath(tree, new TreePath(getPathToRoot(next, 0)));
3089 * Returns true if the action is enabled.
3091 * @return true if the action is enabled, false otherwise
3093 public boolean isEnabled()
3095 // TODO: Implement this properly
3096 return false;
3101 * Returns true if the LookAndFeel implements the control icons. Package
3102 * private for use in inner classes.
3104 * @returns true if there are control icons
3106 boolean hasControlIcons()
3108 if (expandedIcon != null || collapsedIcon != null)
3109 return true;
3110 return false;
3114 * Returns control icon. It is null if the LookAndFeel does not implements the
3115 * control icons. Package private for use in inner classes.
3117 * @return control icon if it exists.
3119 Icon getCurrentControlIcon(TreePath path)
3121 if (tree.isExpanded(path))
3122 return expandedIcon;
3123 return collapsedIcon;
3127 * Returns the parent of the current node
3129 * @param root
3130 * is the root of the tree
3131 * @param node
3132 * is the current node
3133 * @return is the parent of the current node
3135 Object getParent(Object root, Object node)
3137 if (root == null || node == null || root.equals(node))
3138 return null;
3140 if (node instanceof TreeNode)
3141 return ((TreeNode) node).getParent();
3142 return findNode(root, node);
3146 * Recursively checks the tree for the specified node, starting at the root.
3148 * @param root
3149 * is starting node to start searching at.
3150 * @param node
3151 * is the node to search for
3152 * @return the parent node of node
3154 private Object findNode(Object root, Object node)
3156 if (!treeModel.isLeaf(root) && !root.equals(node))
3158 int size = treeModel.getChildCount(root);
3159 for (int j = 0; j < size; j++)
3161 Object child = treeModel.getChild(root, j);
3162 if (node.equals(child))
3163 return root;
3165 Object n = findNode(child, node);
3166 if (n != null)
3167 return n;
3170 return null;
3174 * Get previous visible node in the tree. Package private for use in inner
3175 * classes.
3177 * @param node -
3178 * current node
3179 * @return the next visible node in the JTree. Return null if there are no
3180 * more.
3182 Object getPreviousVisibleNode(Object node)
3184 if (currentVisiblePath != null)
3186 Object[] nodes = currentVisiblePath.getPath();
3187 int i = 0;
3188 while (i < nodes.length && !node.equals(nodes[i]))
3189 i++;
3190 // return the next node
3191 if (i - 1 >= 0)
3192 return nodes[i - 1];
3194 return null;
3198 * Returns the next node in the tree Package private for use in inner classes.
3200 * @param curr -
3201 * current node
3202 * @return the next node in the tree
3204 Object getNextNode(Object curr)
3206 if (!treeModel.isLeaf(curr) && treeModel.getChildCount(curr) > 0)
3207 return treeModel.getChild(curr, 0);
3209 Object node = curr;
3210 Object sibling = null;
3213 sibling = getNextSibling(node);
3214 node = getParent(treeModel.getRoot(), node);
3216 while (sibling == null && node != null);
3218 return sibling;
3222 * Returns the previous node in the tree Package private for use in inner
3223 * classes.
3225 * @param node
3226 * current node
3227 * @return the previous node in the tree
3229 Object getPreviousNode(Object node)
3231 Object parent = getParent(treeModel.getRoot(), node);
3232 if (parent == null)
3233 return null;
3235 Object sibling = getPreviousSibling(node);
3237 if (sibling == null)
3238 return parent;
3240 int size = 0;
3241 if (!treeModel.isLeaf(sibling))
3242 size = treeModel.getChildCount(sibling);
3243 while (size > 0)
3245 sibling = treeModel.getChild(sibling, size - 1);
3246 if (!treeModel.isLeaf(sibling))
3247 size = treeModel.getChildCount(sibling);
3248 else
3249 size = 0;
3252 return sibling;
3256 * Returns the next sibling in the tree Package private for use in inner
3257 * classes.
3259 * @param node -
3260 * current node
3261 * @return the next sibling in the tree
3263 Object getNextSibling(Object node)
3265 Object parent = getParent(treeModel.getRoot(), node);
3266 if (parent == null)
3267 return null;
3269 int index = treeModel.getIndexOfChild(parent, node) + 1;
3271 int size = 0;
3272 if (!treeModel.isLeaf(parent))
3273 size = treeModel.getChildCount(parent);
3274 if (index == 0 || index >= size)
3275 return null;
3277 return treeModel.getChild(parent, index);
3281 * Returns the previous sibling in the tree Package private for use in inner
3282 * classes.
3284 * @param node -
3285 * current node
3286 * @return the previous sibling in the tree
3288 Object getPreviousSibling(Object node)
3290 Object parent = getParent(treeModel.getRoot(), node);
3291 if (parent == null)
3292 return null;
3294 int index = treeModel.getIndexOfChild(parent, node) - 1;
3296 int size = 0;
3297 if (!treeModel.isLeaf(parent))
3298 size = treeModel.getChildCount(parent);
3299 if (index < 0 || index >= size)
3300 return null;
3302 return treeModel.getChild(parent, index);
3306 * Selects the specified path in the tree depending on modes. Package private
3307 * for use in inner classes.
3309 * @param tree
3310 * is the tree we are selecting the path in
3311 * @param path
3312 * is the path we are selecting
3314 void selectPath(JTree tree, TreePath path)
3316 if (path != null)
3318 if (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION)
3320 tree.getSelectionModel().clearSelection();
3321 tree.addSelectionPath(path);
3322 tree.setLeadSelectionPath(path);
3324 else if (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION)
3326 // TODO
3328 else
3330 tree.addSelectionPath(path);
3331 tree.setLeadSelectionPath(path);
3332 tree.getSelectionModel().setSelectionMode(
3333 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
3339 * Returns the path from node to the root. Package private for use in inner
3340 * classes.
3342 * @param node
3343 * the node to get the path to
3344 * @param depth
3345 * the depth of the tree to return a path for
3346 * @return an array of tree nodes that represent the path to node.
3348 Object[] getPathToRoot(Object node, int depth)
3350 if (node == null)
3352 if (depth == 0)
3353 return null;
3355 return new Object[depth];
3358 Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node),
3359 depth + 1);
3360 path[path.length - depth - 1] = node;
3361 return path;
3365 * Returns the level of the node in the tree.
3367 * @param node -
3368 * current node
3369 * @return the number of the level
3371 int getLevel(Object node)
3373 int count = -1;
3375 Object current = node;
3377 if (treeModel != null)
3379 Object root = treeModel.getRoot();
3380 if (!tree.isRootVisible() && tree.isExpanded(new TreePath(root)))
3381 count--;
3385 current = getParent(root, current);
3386 count++;
3388 while (current != null);
3390 return count;
3394 * Draws a vertical line using the given graphic context
3396 * @param g
3397 * is the graphic context
3398 * @param c
3399 * is the component the new line will belong to
3400 * @param x
3401 * is the horizonal position
3402 * @param top
3403 * specifies the top of the line
3404 * @param bottom
3405 * specifies the bottom of the line
3407 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
3408 int bottom)
3410 // FIXME: Check if drawing a dashed line or not.
3411 g.setColor(getHashColor());
3412 g.drawLine(x, top, x, bottom);
3416 * Draws a horizontal line using the given graphic context
3418 * @param g
3419 * is the graphic context
3420 * @param c
3421 * is the component the new line will belong to
3422 * @param y
3423 * is the vertical position
3424 * @param left
3425 * specifies the left point of the line
3426 * @param right
3427 * specifies the right point of the line
3429 protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left,
3430 int right)
3432 // FIXME: Check if drawing a dashed line or not.
3433 g.setColor(getHashColor());
3434 g.drawLine(left, y, right, y);
3438 * Draws an icon at around a specific position
3440 * @param c
3441 * is the component the new line will belong to
3442 * @param g
3443 * is the graphic context
3444 * @param icon
3445 * is the icon which will be drawn
3446 * @param x
3447 * is the center position in x-direction
3448 * @param y
3449 * is the center position in y-direction
3451 protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y)
3453 x -= icon.getIconWidth() / 2;
3454 y -= icon.getIconHeight() / 2;
3456 if (x < 0)
3457 x = 0;
3458 if (y < 0)
3459 y = 0;
3461 icon.paintIcon(c, g, x, y);
3465 * Draws a dashed horizontal line.
3467 * @param g -
3468 * the graphics configuration.
3469 * @param y -
3470 * the y location to start drawing at
3471 * @param x1 -
3472 * the x location to start drawing at
3473 * @param x2 -
3474 * the x location to finish drawing at
3476 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)
3478 g.setColor(getHashColor());
3479 for (int i = x1; i < x2; i += 2)
3480 g.drawLine(i, y, i + 1, y);
3484 * Draws a dashed vertical line.
3486 * @param g -
3487 * the graphics configuration.
3488 * @param x -
3489 * the x location to start drawing at
3490 * @param y1 -
3491 * the y location to start drawing at
3492 * @param y2 -
3493 * the y location to finish drawing at
3495 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2)
3497 g.setColor(getHashColor());
3498 for (int i = y1; i < y2; i += 2)
3499 g.drawLine(x, i, x, i + 1);
3503 * Paints the expand (toggle) part of a row. The receiver should NOT modify
3504 * clipBounds, or insets.
3506 * @param g -
3507 * the graphics configuration
3508 * @param clipBounds -
3509 * @param insets -
3510 * @param bounds -
3511 * bounds of expand control
3512 * @param path -
3513 * path to draw control for
3514 * @param row -
3515 * row to draw control for
3516 * @param isExpanded -
3517 * is the row expanded
3518 * @param hasBeenExpanded -
3519 * has the row already been expanded
3520 * @param isLeaf -
3521 * is the path a leaf
3523 protected void paintExpandControl(Graphics g, Rectangle clipBounds,
3524 Insets insets, Rectangle bounds,
3525 TreePath path, int row, boolean isExpanded,
3526 boolean hasBeenExpanded, boolean isLeaf)
3528 if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf))
3530 Icon icon = getCurrentControlIcon(path);
3531 int iconW = icon.getIconWidth();
3532 int x = bounds.x - rightChildIndent + iconW / 2;
3533 if (x + iconW > bounds.x)
3534 x = bounds.x - rightChildIndent - gap;
3535 icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2
3536 - icon.getIconHeight() / 2);
3541 * Paints the horizontal part of the leg. The receiver should NOT modify
3542 * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not
3543 * visible.
3545 * @param g -
3546 * the graphics configuration
3547 * @param clipBounds -
3548 * @param insets -
3549 * @param bounds -
3550 * bounds of the cell
3551 * @param path -
3552 * path to draw leg for
3553 * @param row -
3554 * row to start drawing at
3555 * @param isExpanded -
3556 * is the row expanded
3557 * @param hasBeenExpanded -
3558 * has the row already been expanded
3559 * @param isLeaf -
3560 * is the path a leaf
3562 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
3563 Insets insets, Rectangle bounds,
3564 TreePath path, int row,
3565 boolean isExpanded,
3566 boolean hasBeenExpanded,
3567 boolean isLeaf)
3569 if (row != 0)
3570 paintHorizontalLine(g, tree, bounds.y + bounds.height / 2, bounds.x - gap
3571 - 2, bounds.x);
3575 * Paints the vertical part of the leg. The receiver should NOT modify
3576 * clipBounds, insets.
3578 * @param g -
3579 * the graphics configuration.
3580 * @param clipBounds -
3581 * @param insets -
3582 * @param path -
3583 * the path to draw the vertical part for.
3585 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
3586 Insets insets, TreePath path)
3588 int max = tree.getVisibleRowCount();
3589 for (int i = 0; i < max; i++)
3591 Object curr = path.getPathComponent(i);
3592 TreePath currPath = new TreePath(getPathToRoot(curr, 0));
3593 int numChild = treeModel.getChildCount(curr);
3594 if (numChild > 0 && tree.isExpanded(currPath))
3596 Rectangle bounds = getPathBounds(tree, currPath);
3597 Rectangle lastChildBounds = getPathBounds(
3598 tree,
3599 new TreePath(
3600 getPathToRoot(
3601 treeModel.getChild(
3602 curr,
3603 numChild - 1),
3604 0)));
3605 paintVerticalLine(g, tree, bounds.x + gap + 2, bounds.y
3606 + bounds.height - 2,
3607 lastChildBounds.y + lastChildBounds.height / 2);
3613 * Paints the renderer part of a row. The receiver should NOT modify
3614 * clipBounds, or insets.
3616 * @param g -
3617 * the graphics configuration
3618 * @param clipBounds -
3619 * @param insets -
3620 * @param bounds -
3621 * bounds of expand control
3622 * @param path -
3623 * path to draw control for
3624 * @param row -
3625 * row to draw control for
3626 * @param isExpanded -
3627 * is the row expanded
3628 * @param hasBeenExpanded -
3629 * has the row already been expanded
3630 * @param isLeaf -
3631 * is the path a leaf
3633 protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
3634 Rectangle bounds, TreePath path, int row,
3635 boolean isExpanded, boolean hasBeenExpanded,
3636 boolean isLeaf)
3638 boolean selected = tree.isPathSelected(path);
3639 boolean hasIcons = false;
3640 Object node = path.getLastPathComponent();
3642 if (tree.isVisible(path))
3644 if (!validCachedPreferredSize)
3645 updateCachedPreferredSize();
3647 paintExpandControl(g, clipBounds, insets, bounds, path, row,
3648 isExpanded, hasBeenExpanded, isLeaf);
3650 if (row != 0)
3651 bounds.x += gap;
3652 bounds.width = preferredSize.width + bounds.x;
3653 if (editingComponent != null && editingPath != null && isEditing(tree)
3654 && node.equals(editingPath.getLastPathComponent()))
3656 rendererPane.paintComponent(g, editingComponent.getParent(), null,
3657 bounds);
3659 else
3661 TreeCellRenderer dtcr = tree.getCellRenderer();
3662 if (dtcr == null)
3663 dtcr = createDefaultCellRenderer();
3665 Component c = dtcr.getTreeCellRendererComponent(tree, node,
3666 selected,
3667 isExpanded, isLeaf,
3668 row,
3669 tree.hasFocus());
3670 rendererPane.paintComponent(g, c, c.getParent(), bounds);
3676 * Prepares for the UI to uninstall.
3678 protected void prepareForUIUninstall()
3680 // TODO: Implement this properly.
3684 * Returns true if the expand (toggle) control should be drawn for the
3685 * specified row.
3687 * @param path -
3688 * current path to check for.
3689 * @param row -
3690 * current row to check for.
3691 * @param isExpanded -
3692 * true if the path is expanded
3693 * @param hasBeenExpanded -
3694 * true if the path has been expanded already
3695 * @param isLeaf -
3696 * true if the row is a lead
3698 protected boolean shouldPaintExpandControl(TreePath path, int row,
3699 boolean isExpanded,
3700 boolean hasBeenExpanded,
3701 boolean isLeaf)
3703 Object node = path.getLastPathComponent();
3704 return (!isLeaf && getLevel(node) != 0 && hasControlIcons());
3708 * Updates the cached current TreePath of all visible nodes in the tree.
3710 void updateCurrentVisiblePath()
3712 if (treeModel == null)
3713 return;
3715 Object next = treeModel.getRoot();
3716 if (next == null)
3717 return;
3719 TreePath rootPath = new TreePath(next);
3720 Rectangle bounds = getPathBounds(tree, rootPath);
3722 // If root is not a valid size to be visible, or is
3723 // not visible and the tree is expanded, then the next node acts
3724 // as the root
3725 if ((bounds.width == 0 && bounds.height == 0)
3726 || (!isRootVisible() && tree.isExpanded(new TreePath(next))))
3728 next = getNextNode(next);
3729 rootPath = new TreePath(next);
3732 Object root = next;
3733 TreePath current = null;
3734 while (next != null)
3736 if (current == null)
3737 current = rootPath;
3738 else
3739 current = current.pathByAddingChild(next);
3743 TreePath path = new TreePath(getPathToRoot(next, 0));
3744 if ((tree.isVisible(path) && tree.isExpanded(path))
3745 || treeModel.isLeaf(next))
3746 next = getNextNode(next);
3747 else
3749 Object pNext = next;
3750 next = getNextSibling(pNext);
3751 // if no next sibling, check parent's next sibling.
3752 if (next == null)
3754 Object parent = getParent(root, pNext);
3755 while (next == null && parent != null)
3757 next = getNextSibling(parent);
3758 if (next == null)
3759 parent = getParent(root, parent);
3764 while (next != null
3765 && !tree.isVisible(new TreePath(getPathToRoot(next, 0))));
3768 currentVisiblePath = current;
3769 tree.setVisibleRowCount(getRowCount(tree));
3771 if (tree.getSelectionModel() != null && tree.getSelectionCount() == 0
3772 && currentVisiblePath != null)
3773 selectPath(
3774 tree,
3775 new TreePath(
3776 getPathToRoot(
3777 currentVisiblePath.getPathComponent(0),
3778 0)));
3782 * Get next visible node in the currentVisiblePath. Package private for use in
3783 * inner classes.
3785 * @param node
3786 * current node
3787 * @return the next visible node in the JTree. Return null if there are no
3788 * more.
3790 Object getNextVisibleNode(Object node)
3792 if (currentVisiblePath != null)
3794 Object[] nodes = currentVisiblePath.getPath();
3795 int i = 0;
3796 while (i < nodes.length && !node.equals(nodes[i]))
3797 i++;
3798 // return the next node
3799 if (i + 1 < nodes.length)
3800 return nodes[i + 1];
3802 return null;
3804 } // BasicTreeUI