Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / javax / swing / plaf / basic / BasicTreeUI.java
blob1c6e6c5e502b517e9613adc89629527df0373594
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.Label;
49 import java.awt.Point;
50 import java.awt.Rectangle;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.ComponentAdapter;
54 import java.awt.event.ComponentEvent;
55 import java.awt.event.ComponentListener;
56 import java.awt.event.FocusEvent;
57 import java.awt.event.FocusListener;
58 import java.awt.event.KeyAdapter;
59 import java.awt.event.KeyEvent;
60 import java.awt.event.KeyListener;
61 import java.awt.event.MouseAdapter;
62 import java.awt.event.MouseEvent;
63 import java.awt.event.MouseListener;
64 import java.awt.event.MouseMotionListener;
65 import java.beans.PropertyChangeEvent;
66 import java.beans.PropertyChangeListener;
67 import java.util.Enumeration;
68 import java.util.Hashtable;
70 import javax.swing.AbstractAction;
71 import javax.swing.Action;
72 import javax.swing.ActionMap;
73 import javax.swing.CellRendererPane;
74 import javax.swing.Icon;
75 import javax.swing.InputMap;
76 import javax.swing.JComponent;
77 import javax.swing.JScrollBar;
78 import javax.swing.JScrollPane;
79 import javax.swing.JTextField;
80 import javax.swing.JTree;
81 import javax.swing.KeyStroke;
82 import javax.swing.LookAndFeel;
83 import javax.swing.SwingUtilities;
84 import javax.swing.Timer;
85 import javax.swing.UIManager;
86 import javax.swing.event.CellEditorListener;
87 import javax.swing.event.ChangeEvent;
88 import javax.swing.event.MouseInputListener;
89 import javax.swing.event.TreeExpansionEvent;
90 import javax.swing.event.TreeExpansionListener;
91 import javax.swing.event.TreeModelEvent;
92 import javax.swing.event.TreeModelListener;
93 import javax.swing.event.TreeSelectionEvent;
94 import javax.swing.event.TreeSelectionListener;
95 import javax.swing.plaf.ActionMapUIResource;
96 import javax.swing.plaf.ComponentUI;
97 import javax.swing.plaf.InputMapUIResource;
98 import javax.swing.plaf.TreeUI;
99 import javax.swing.text.Caret;
100 import javax.swing.tree.AbstractLayoutCache;
101 import javax.swing.tree.DefaultTreeCellEditor;
102 import javax.swing.tree.DefaultTreeCellRenderer;
103 import javax.swing.tree.FixedHeightLayoutCache;
104 import javax.swing.tree.TreeCellEditor;
105 import javax.swing.tree.TreeCellRenderer;
106 import javax.swing.tree.TreeModel;
107 import javax.swing.tree.TreeNode;
108 import javax.swing.tree.TreePath;
109 import javax.swing.tree.TreeSelectionModel;
112 * A delegate providing the user interface for <code>JTree</code> according to
113 * the Basic look and feel.
115 * @see javax.swing.JTree
116 * @author Lillian Angel (langel@redhat.com)
117 * @author Sascha Brawer (brawer@dandelis.ch)
118 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
120 public class BasicTreeUI extends TreeUI
123 * The tree cell editing may be started by the single mouse click on the
124 * selected cell. To separate it from the double mouse click, the editing
125 * session starts after this time (in ms) after that single click, and only
126 * no other clicks were performed during that time.
128 static int WAIT_TILL_EDITING = 900;
130 /** Collapse Icon for the tree. */
131 protected transient Icon collapsedIcon;
133 /** Expanded Icon for the tree. */
134 protected transient Icon expandedIcon;
136 /** Distance between left margin and where vertical dashes will be drawn. */
137 protected int leftChildIndent;
140 * Distance between leftChildIndent and where cell contents will be drawn.
142 protected int rightChildIndent;
145 * Total fistance that will be indented. The sum of leftChildIndent and
146 * rightChildIndent .
148 protected int totalChildIndent;
150 /** Index of the row that was last selected. */
151 protected int lastSelectedRow;
153 /** Component that we're going to be drawing onto. */
154 protected JTree tree;
156 /** Renderer that is being used to do the actual cell drawing. */
157 protected transient TreeCellRenderer currentCellRenderer;
160 * Set to true if the renderer that is currently in the tree was created by
161 * this instance.
163 protected boolean createdRenderer;
165 /** Editor for the tree. */
166 protected transient TreeCellEditor cellEditor;
169 * Set to true if editor that is currently in the tree was created by this
170 * instance.
172 protected boolean createdCellEditor;
175 * Set to false when editing and shouldSelectCall() returns true meaning the
176 * node should be selected before editing, used in completeEditing.
178 protected boolean stopEditingInCompleteEditing;
180 /** Used to paint the TreeCellRenderer. */
181 protected CellRendererPane rendererPane;
183 /** Size needed to completely display all the nodes. */
184 protected Dimension preferredSize;
186 /** Minimum size needed to completely display all the nodes. */
187 protected Dimension preferredMinSize;
189 /** Is the preferredSize valid? */
190 protected boolean validCachedPreferredSize;
192 /** Object responsible for handling sizing and expanded issues. */
193 protected AbstractLayoutCache treeState;
195 /** Used for minimizing the drawing of vertical lines. */
196 protected Hashtable drawingCache;
199 * True if doing optimizations for a largeModel. Subclasses that don't support
200 * this may wish to override createLayoutCache to not return a
201 * FixedHeightLayoutCache instance.
203 protected boolean largeModel;
205 /** Responsible for telling the TreeState the size needed for a node. */
206 protected AbstractLayoutCache.NodeDimensions nodeDimensions;
208 /** Used to determine what to display. */
209 protected TreeModel treeModel;
211 /** Model maintaining the selection. */
212 protected TreeSelectionModel treeSelectionModel;
215 * How much the depth should be offset to properly calculate x locations. This
216 * is based on whether or not the root is visible, and if the root handles are
217 * visible.
219 protected int depthOffset;
222 * When editing, this will be the Component that is doing the actual editing.
224 protected Component editingComponent;
226 /** Path that is being edited. */
227 protected TreePath editingPath;
230 * Row that is being edited. Should only be referenced if editingComponent is
231 * null.
233 protected int editingRow;
235 /** Set to true if the editor has a different size than the renderer. */
236 protected boolean editorHasDifferentSize;
238 /** The action bound to KeyStrokes. */
239 TreeAction action;
241 /** Boolean to keep track of editing. */
242 boolean isEditing;
244 /** The current path of the visible nodes in the tree. */
245 TreePath currentVisiblePath;
247 /** The gap between the icon and text. */
248 int gap = 4;
250 /** The max height of the nodes in the tree. */
251 int maxHeight = 0;
253 /** Listeners */
254 private PropertyChangeListener propertyChangeListener;
256 private FocusListener focusListener;
258 private TreeSelectionListener treeSelectionListener;
260 private MouseListener mouseListener;
262 private KeyListener keyListener;
264 private PropertyChangeListener selectionModelPropertyChangeListener;
266 private ComponentListener componentListener;
268 CellEditorListener cellEditorListener;
270 private TreeExpansionListener treeExpansionListener;
272 private TreeModelListener treeModelListener;
275 * This timer fires the editing action after about 1200 ms if not reset during
276 * that time. It handles the editing start with the single mouse click
277 * (and not the double mouse click) on the selected tree node.
279 Timer startEditTimer;
282 * The special value of the mouse event is sent indicating that this is not
283 * just the mouse click, but the mouse click on the selected node. Sending
284 * such event forces to start the cell editing session.
286 static final MouseEvent EDIT = new MouseEvent(new Label(), 7,7,7,7,7,7, false);
289 * Creates a new BasicTreeUI object.
291 public BasicTreeUI()
293 validCachedPreferredSize = false;
294 drawingCache = new Hashtable();
295 nodeDimensions = createNodeDimensions();
296 configureLayoutCache();
298 propertyChangeListener = createPropertyChangeListener();
299 focusListener = createFocusListener();
300 treeSelectionListener = createTreeSelectionListener();
301 mouseListener = createMouseListener();
302 keyListener = createKeyListener();
303 selectionModelPropertyChangeListener = createSelectionModelPropertyChangeListener();
304 componentListener = createComponentListener();
305 cellEditorListener = createCellEditorListener();
306 treeExpansionListener = createTreeExpansionListener();
307 treeModelListener = createTreeModelListener();
309 editingRow = -1;
310 lastSelectedRow = -1;
314 * Returns an instance of the UI delegate for the specified component.
316 * @param c
317 * the <code>JComponent</code> for which we need a UI delegate for.
318 * @return the <code>ComponentUI</code> for c.
320 public static ComponentUI createUI(JComponent c)
322 return new BasicTreeUI();
326 * Returns the Hash color.
328 * @return the <code>Color</code> of the Hash.
330 protected Color getHashColor()
332 return UIManager.getColor("Tree.hash");
336 * Sets the Hash color.
338 * @param color
339 * the <code>Color</code> to set the Hash to.
341 protected void setHashColor(Color color)
343 // FIXME: Putting something in the UIDefaults map is certainly wrong.
344 UIManager.put("Tree.hash", color);
348 * Sets the left child's indent value.
350 * @param newAmount
351 * is the new indent value for the left child.
353 public void setLeftChildIndent(int newAmount)
355 leftChildIndent = newAmount;
359 * Returns the indent value for the left child.
361 * @return the indent value for the left child.
363 public int getLeftChildIndent()
365 return leftChildIndent;
369 * Sets the right child's indent value.
371 * @param newAmount
372 * is the new indent value for the right child.
374 public void setRightChildIndent(int newAmount)
376 rightChildIndent = newAmount;
380 * Returns the indent value for the right child.
382 * @return the indent value for the right child.
384 public int getRightChildIndent()
386 return rightChildIndent;
390 * Sets the expanded icon.
392 * @param newG
393 * is the new expanded icon.
395 public void setExpandedIcon(Icon newG)
397 expandedIcon = newG;
401 * Returns the current expanded icon.
403 * @return the current expanded icon.
405 public Icon getExpandedIcon()
407 return expandedIcon;
411 * Sets the collapsed icon.
413 * @param newG
414 * is the new collapsed icon.
416 public void setCollapsedIcon(Icon newG)
418 collapsedIcon = newG;
422 * Returns the current collapsed icon.
424 * @return the current collapsed icon.
426 public Icon getCollapsedIcon()
428 return collapsedIcon;
432 * Updates the componentListener, if necessary.
434 * @param largeModel
435 * sets this.largeModel to it.
437 protected void setLargeModel(boolean largeModel)
439 if (largeModel != this.largeModel)
441 tree.removeComponentListener(componentListener);
442 this.largeModel = largeModel;
443 tree.addComponentListener(componentListener);
448 * Returns true if largeModel is set
450 * @return true if largeModel is set, otherwise false.
452 protected boolean isLargeModel()
454 return largeModel;
458 * Sets the row height.
460 * @param rowHeight
461 * is the height to set this.rowHeight to.
463 protected void setRowHeight(int rowHeight)
465 if (rowHeight == 0)
466 rowHeight = Math.max(getMaxHeight(tree), 20);
467 treeState.setRowHeight(rowHeight);
471 * Returns the current row height.
473 * @return current row height.
475 protected int getRowHeight()
477 return treeState.getRowHeight();
481 * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
482 * <code>updateRenderer</code>.
484 * @param tcr
485 * is the new TreeCellRenderer.
487 protected void setCellRenderer(TreeCellRenderer tcr)
489 currentCellRenderer = tcr;
490 updateRenderer();
494 * Return currentCellRenderer, which will either be the trees renderer, or
495 * defaultCellRenderer, which ever was not null.
497 * @return the current Cell Renderer
499 protected TreeCellRenderer getCellRenderer()
501 if (currentCellRenderer != null)
502 return currentCellRenderer;
504 return createDefaultCellRenderer();
508 * Sets the tree's model.
510 * @param model
511 * to set the treeModel to.
513 protected void setModel(TreeModel model)
515 tree.setModel(model);
516 treeModel = tree.getModel();
520 * Returns the tree's model
522 * @return treeModel
524 protected TreeModel getModel()
526 return treeModel;
530 * Sets the root to being visible.
532 * @param newValue
533 * sets the visibility of the root
535 protected void setRootVisible(boolean newValue)
537 tree.setRootVisible(newValue);
541 * Returns true if the root is visible.
543 * @return true if the root is visible.
545 protected boolean isRootVisible()
547 return tree.isRootVisible();
551 * Determines whether the node handles are to be displayed.
553 * @param newValue
554 * sets whether or not node handles should be displayed.
556 protected void setShowsRootHandles(boolean newValue)
558 tree.setShowsRootHandles(newValue);
562 * Returns true if the node handles are to be displayed.
564 * @return true if the node handles are to be displayed.
566 protected boolean getShowsRootHandles()
568 return tree.getShowsRootHandles();
572 * Sets the cell editor.
574 * @param editor
575 * to set the cellEditor to.
577 protected void setCellEditor(TreeCellEditor editor)
579 cellEditor = editor;
580 createdCellEditor = true;
584 * Returns the <code>TreeCellEditor</code> for this tree.
586 * @return the cellEditor for this tree.
588 protected TreeCellEditor getCellEditor()
590 return cellEditor;
594 * Configures the receiver to allow, or not allow, editing.
596 * @param newValue
597 * sets the receiver to allow editing if true.
599 protected void setEditable(boolean newValue)
601 tree.setEditable(newValue);
605 * Returns true if the receiver allows editing.
607 * @return true if the receiver allows editing.
609 protected boolean isEditable()
611 return tree.isEditable();
615 * Resets the selection model. The appropriate listeners are installed on the
616 * model.
618 * @param newLSM
619 * resets the selection model.
621 protected void setSelectionModel(TreeSelectionModel newLSM)
623 if (newLSM != null)
625 treeSelectionModel = newLSM;
626 tree.setSelectionModel(treeSelectionModel);
631 * Returns the current selection model.
633 * @return the current selection model.
635 protected TreeSelectionModel getSelectionModel()
637 return treeSelectionModel;
641 * Returns the Rectangle enclosing the label portion that the last item in
642 * path will be drawn to. Will return null if any component in path is
643 * currently valid.
645 * @param tree
646 * is the current tree the path will be drawn to.
647 * @param path
648 * is the current path the tree to draw to.
649 * @return the Rectangle enclosing the label portion that the last item in the
650 * path will be drawn to.
652 public Rectangle getPathBounds(JTree tree, TreePath path)
654 int row = -1;
655 Object cell = null;
656 if (path != null)
658 row = getRowForPath(tree, path);
659 cell = path.getLastPathComponent();
661 return nodeDimensions.getNodeDimensions(cell, row, getLevel(cell),
662 tree.isExpanded(path),
663 new Rectangle());
667 * Returns the max height of all the nodes in the tree.
669 * @param tree -
670 * the current tree
671 * @return the max height.
673 private int getMaxHeight(JTree tree)
675 if (maxHeight != 0)
676 return maxHeight;
678 Icon e = UIManager.getIcon("Tree.openIcon");
679 Icon c = UIManager.getIcon("Tree.closedIcon");
680 Icon l = UIManager.getIcon("Tree.leafIcon");
681 int rc = getRowCount(tree);
682 int iconHeight = 0;
684 for (int row = 0; row < rc; row++)
686 if (isLeaf(row))
687 iconHeight = l.getIconHeight();
688 else if (tree.isExpanded(row))
689 iconHeight = e.getIconHeight();
690 else
691 iconHeight = c.getIconHeight();
693 maxHeight = Math.max(maxHeight, iconHeight + gap);
696 return maxHeight;
700 * Returns the path for passed in row. If row is not visible null is returned.
702 * @param tree
703 * is the current tree to return path for.
704 * @param row
705 * is the row number of the row to return.
706 * @return the path for passed in row. If row is not visible null is returned.
708 public TreePath getPathForRow(JTree tree, int row)
710 if (treeModel != null && currentVisiblePath != null)
712 Object[] nodes = currentVisiblePath.getPath();
713 if (row < nodes.length)
714 return new TreePath(getPathToRoot(nodes[row], 0));
716 return null;
720 * Returns the row that the last item identified in path is visible at. Will
721 * return -1 if any of the elments in the path are not currently visible.
723 * @param tree
724 * is the current tree to return the row for.
725 * @param path
726 * is the path used to find the row.
727 * @return the row that the last item identified in path is visible at. Will
728 * return -1 if any of the elments in the path are not currently
729 * visible.
731 public int getRowForPath(JTree tree, TreePath path)
733 int row = 0;
734 Object dest = path.getLastPathComponent();
735 int rowCount = getRowCount(tree);
736 if (currentVisiblePath != null)
738 Object[] nodes = currentVisiblePath.getPath();
739 while (row < rowCount)
741 if (dest.equals(nodes[row]))
742 return row;
743 row++;
746 return -1;
750 * Returns the number of rows that are being displayed.
752 * @param tree
753 * is the current tree to return the number of rows for.
754 * @return the number of rows being displayed.
756 public int getRowCount(JTree tree)
758 if (currentVisiblePath != null)
759 return currentVisiblePath.getPathCount();
760 return 0;
764 * Returns the path to the node that is closest to x,y. If there is nothing
765 * currently visible this will return null, otherwise it'll always return a
766 * valid path. If you need to test if the returned object is exactly at x,y
767 * you should get the bounds for the returned path and test x,y against that.
769 * @param tree
770 * the tree to search for the closest path
771 * @param x
772 * is the x coordinate of the location to search
773 * @param y
774 * is the y coordinate of the location to search
775 * @return the tree path closes to x,y.
777 public TreePath getClosestPathForLocation(JTree tree, int x, int y)
779 int row = Math.round(y / getMaxHeight(tree));
780 TreePath path = getPathForRow(tree, row);
782 // no row is visible at this node
783 while (row > 0 && path == null)
785 --row;
786 path = getPathForRow(tree, row);
789 return path;
793 * Returns true if the tree is being edited. The item that is being edited can
794 * be returned by getEditingPath().
796 * @param tree
797 * is the tree to check for editing.
798 * @return true if the tree is being edited.
800 public boolean isEditing(JTree tree)
802 return isEditing;
806 * Stops the current editing session. This has no effect if the tree is not
807 * being edited. Returns true if the editor allows the editing session to
808 * stop.
810 * @param tree
811 * is the tree to stop the editing on
812 * @return true if the editor allows the editing session to stop.
814 public boolean stopEditing(JTree tree)
816 if (isEditing(tree))
818 completeEditing(false, false, true);
819 finish();
821 return !isEditing(tree);
825 * Cancels the current editing session.
827 * @param tree
828 * is the tree to cancel the editing session on.
830 public void cancelEditing(JTree tree)
832 // There is no need to send the cancel message to the editor,
833 // as the cancellation event itself arrives from it. This would
834 // only be necessary when cancelling the editing programatically.
835 completeEditing(false, false, false);
836 finish();
840 * Selects the last item in path and tries to edit it. Editing will fail if
841 * the CellEditor won't allow it for the selected item.
843 * @param tree
844 * is the tree to edit on.
845 * @param path
846 * is the path in tree to edit on.
848 public void startEditingAtPath(JTree tree, TreePath path)
850 startEditing(path, null);
854 * Returns the path to the element that is being editted.
856 * @param tree
857 * is the tree to get the editing path from.
858 * @return the path that is being edited.
860 public TreePath getEditingPath(JTree tree)
862 return editingPath;
866 * Invoked after the tree instance variable has been set, but before any
867 * default/listeners have been installed.
869 protected void prepareForUIInstall()
871 // TODO: Implement this properly.
875 * Invoked from installUI after all the defaults/listeners have been
876 * installed.
878 protected void completeUIInstall()
880 // TODO: Implement this properly.
884 * Invoked from uninstallUI after all the defaults/listeners have been
885 * uninstalled.
887 protected void completeUIUninstall()
889 // TODO: Implement this properly.
893 * Installs the subcomponents of the tree, which is the renderer pane.
895 protected void installComponents()
897 currentCellRenderer = createDefaultCellRenderer();
898 rendererPane = createCellRendererPane();
899 createdRenderer = true;
900 setCellRenderer(currentCellRenderer);
904 * Creates an instance of NodeDimensions that is able to determine the size of
905 * a given node in the tree.
907 * @return the NodeDimensions of a given node in the tree
909 protected AbstractLayoutCache.NodeDimensions createNodeDimensions()
911 return new NodeDimensionsHandler();
915 * Creates a listener that is reponsible for the updates the UI based on how
916 * the tree changes.
918 * @return the PropertyChangeListener that is reposnsible for the updates
920 protected PropertyChangeListener createPropertyChangeListener()
922 return new PropertyChangeHandler();
926 * Creates the listener responsible for updating the selection based on mouse
927 * events.
929 * @return the MouseListener responsible for updating.
931 protected MouseListener createMouseListener()
933 return new MouseHandler();
937 * Creates the listener that is responsible for updating the display when
938 * focus is lost/grained.
940 * @return the FocusListener responsible for updating.
942 protected FocusListener createFocusListener()
944 return new FocusHandler();
948 * Creates the listener reponsible for getting key events from the tree.
950 * @return the KeyListener responsible for getting key events.
952 protected KeyListener createKeyListener()
954 return new KeyHandler();
958 * Creates the listener responsible for getting property change events from
959 * the selection model.
961 * @returns the PropertyChangeListener reponsible for getting property change
962 * events from the selection model.
964 protected PropertyChangeListener createSelectionModelPropertyChangeListener()
966 return new SelectionModelPropertyChangeHandler();
970 * Creates the listener that updates the display based on selection change
971 * methods.
973 * @return the TreeSelectionListener responsible for updating.
975 protected TreeSelectionListener createTreeSelectionListener()
977 return new TreeSelectionHandler();
981 * Creates a listener to handle events from the current editor
983 * @return the CellEditorListener that handles events from the current editor
985 protected CellEditorListener createCellEditorListener()
987 return new CellEditorHandler();
991 * Creates and returns a new ComponentHandler. This is used for the large
992 * model to mark the validCachedPreferredSize as invalid when the component
993 * moves.
995 * @return a new ComponentHandler.
997 protected ComponentListener createComponentListener()
999 return new ComponentHandler();
1003 * Creates and returns the object responsible for updating the treestate when
1004 * a nodes expanded state changes.
1006 * @return the TreeExpansionListener responsible for updating the treestate
1008 protected TreeExpansionListener createTreeExpansionListener()
1010 return new TreeExpansionHandler();
1014 * Creates the object responsible for managing what is expanded, as well as
1015 * the size of nodes.
1017 * @return the object responsible for managing what is expanded.
1019 protected AbstractLayoutCache createLayoutCache()
1021 return new FixedHeightLayoutCache();
1025 * Returns the renderer pane that renderer components are placed in.
1027 * @return the rendererpane that render components are placed in.
1029 protected CellRendererPane createCellRendererPane()
1031 return new CellRendererPane();
1035 * Creates a default cell editor.
1037 * @return the default cell editor.
1039 protected TreeCellEditor createDefaultCellEditor()
1041 if (currentCellRenderer != null)
1042 return new DefaultTreeCellEditor(
1043 tree,
1044 (DefaultTreeCellRenderer) currentCellRenderer,
1045 cellEditor);
1046 return new DefaultTreeCellEditor(
1047 tree,
1048 (DefaultTreeCellRenderer) createDefaultCellRenderer(),
1049 cellEditor);
1053 * Returns the default cell renderer that is used to do the stamping of each
1054 * node.
1056 * @return the default cell renderer that is used to do the stamping of each
1057 * node.
1059 protected TreeCellRenderer createDefaultCellRenderer()
1061 return new DefaultTreeCellRenderer();
1065 * Returns a listener that can update the tree when the model changes.
1067 * @return a listener that can update the tree when the model changes.
1069 protected TreeModelListener createTreeModelListener()
1071 return new TreeModelHandler();
1075 * Uninstall all registered listeners
1077 protected void uninstallListeners()
1079 tree.removePropertyChangeListener(propertyChangeListener);
1080 tree.removeFocusListener(focusListener);
1081 tree.removeTreeSelectionListener(treeSelectionListener);
1082 tree.removeMouseListener(mouseListener);
1083 tree.removeKeyListener(keyListener);
1084 tree.removePropertyChangeListener(selectionModelPropertyChangeListener);
1085 tree.removeComponentListener(componentListener);
1086 tree.removeTreeExpansionListener(treeExpansionListener);
1088 TreeCellEditor tce = tree.getCellEditor();
1089 if (tce != null)
1090 tce.removeCellEditorListener(cellEditorListener);
1091 if (treeModel != null)
1092 treeModel.removeTreeModelListener(treeModelListener);
1096 * Uninstall all keyboard actions.
1098 protected void uninstallKeyboardActions()
1100 action = null;
1101 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1102 null);
1103 tree.getActionMap().setParent(null);
1107 * Uninstall the rendererPane.
1109 protected void uninstallComponents()
1111 currentCellRenderer = null;
1112 rendererPane = null;
1113 createdRenderer = false;
1114 setCellRenderer(currentCellRenderer);
1118 * The vertical element of legs between nodes starts at the bottom of the
1119 * parent node by default. This method makes the leg start below that.
1121 * @return the vertical leg buffer
1123 protected int getVerticalLegBuffer()
1125 return getRowHeight() / 2;
1129 * The horizontal element of legs between nodes starts at the right of the
1130 * left-hand side of the child node by default. This method makes the leg end
1131 * before that.
1133 * @return the horizontal leg buffer
1135 protected int getHorizontalLegBuffer()
1137 return rightChildIndent / 2;
1141 * Make all the nodes that are expanded in JTree expanded in LayoutCache. This
1142 * invokes updateExpandedDescendants with the root path.
1144 protected void updateLayoutCacheExpandedNodes()
1146 if (treeModel != null)
1147 updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1151 * Updates the expanded state of all the descendants of the <code>path</code>
1152 * by getting the expanded descendants from the tree and forwarding to the
1153 * tree state.
1155 * @param path
1156 * the path used to update the expanded states
1158 protected void updateExpandedDescendants(TreePath path)
1160 Enumeration expanded = tree.getExpandedDescendants(path);
1161 while (expanded.hasMoreElements())
1162 treeState.setExpandedState(((TreePath) expanded.nextElement()), true);
1166 * Returns a path to the last child of <code>parent</code>
1168 * @param parent
1169 * is the topmost path to specified
1170 * @return a path to the last child of parent
1172 protected TreePath getLastChildPath(TreePath parent)
1174 return ((TreePath) parent.getLastPathComponent());
1178 * Updates how much each depth should be offset by.
1180 protected void updateDepthOffset()
1182 depthOffset += getVerticalLegBuffer();
1186 * Updates the cellEditor based on editability of the JTree that we're
1187 * contained in. If the tree is editable but doesn't have a cellEditor, a
1188 * basic one will be used.
1190 protected void updateCellEditor()
1192 if (tree.isEditable() && cellEditor == null)
1193 setCellEditor(createDefaultCellEditor());
1194 createdCellEditor = true;
1198 * Messaged from the tree we're in when the renderer has changed.
1200 protected void updateRenderer()
1202 if (tree != null)
1204 if (tree.getCellRenderer() == null)
1206 if (currentCellRenderer == null)
1207 currentCellRenderer = createDefaultCellRenderer();
1208 tree.setCellRenderer(currentCellRenderer);
1214 * Resets the treeState instance based on the tree we're providing the look
1215 * and feel for.
1217 protected void configureLayoutCache()
1219 treeState = createLayoutCache();
1223 * Marks the cached size as being invalid, and messages the tree with
1224 * <code>treeDidChange</code>.
1226 protected void updateSize()
1228 preferredSize = null;
1229 updateCachedPreferredSize();
1230 tree.treeDidChange();
1234 * Updates the <code>preferredSize</code> instance variable, which is
1235 * returned from <code>getPreferredSize()</code>.
1237 protected void updateCachedPreferredSize()
1239 int maxWidth = 0;
1240 updateCurrentVisiblePath();
1241 boolean isLeaf = false;
1242 if (currentVisiblePath != null)
1244 Object[] path = currentVisiblePath.getPath();
1245 for (int i = 0; i < path.length; i++)
1247 TreePath curr = new TreePath(getPathToRoot(path[i], 0));
1248 Rectangle bounds = getPathBounds(tree, curr);
1249 if (treeModel != null)
1250 isLeaf = treeModel.isLeaf(path[i]);
1251 if (!isLeaf && hasControlIcons())
1252 bounds.width += getCurrentControlIcon(curr).getIconWidth();
1253 maxWidth = Math.max(maxWidth, bounds.x + bounds.width);
1256 maxHeight = 0;
1257 maxHeight = getMaxHeight(tree);
1258 preferredSize = new Dimension(maxWidth, (maxHeight * path.length));
1260 else
1261 preferredSize = new Dimension(0, 0);
1262 validCachedPreferredSize = true;
1266 * Messaged from the VisibleTreeNode after it has been expanded.
1268 * @param path
1269 * is the path that has been expanded.
1271 protected void pathWasExpanded(TreePath path)
1273 validCachedPreferredSize = false;
1274 tree.repaint();
1278 * Messaged from the VisibleTreeNode after it has collapsed
1280 protected void pathWasCollapsed(TreePath path)
1282 validCachedPreferredSize = false;
1283 tree.repaint();
1287 * Install all defaults for the tree.
1289 protected void installDefaults()
1291 LookAndFeel.installColorsAndFont(tree, "Tree.background",
1292 "Tree.foreground", "Tree.font");
1293 tree.setOpaque(true);
1295 rightChildIndent = UIManager.getInt("Tree.rightChildIndent");
1296 leftChildIndent = UIManager.getInt("Tree.leftChildIndent");
1297 setRowHeight(UIManager.getInt("Tree.rowHeight"));
1298 tree.setRowHeight(getRowHeight());
1299 tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand"));
1300 setExpandedIcon(UIManager.getIcon("Tree.expandedIcon"));
1301 setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon"));
1305 * Install all keyboard actions for this
1307 protected void installKeyboardActions()
1309 InputMap focusInputMap = (InputMap) UIManager.get("Tree.focusInputMap");
1310 InputMapUIResource parentInputMap = new InputMapUIResource();
1311 ActionMap parentActionMap = new ActionMapUIResource();
1312 action = new TreeAction();
1313 Object keys[] = focusInputMap.allKeys();
1315 for (int i = 0; i < keys.length; i++)
1317 parentInputMap.put(
1318 KeyStroke.getKeyStroke(
1319 ((KeyStroke) keys[i]).getKeyCode(),
1320 convertModifiers(((KeyStroke) keys[i]).getModifiers())),
1321 (String) focusInputMap.get((KeyStroke) keys[i]));
1323 parentInputMap.put(
1324 KeyStroke.getKeyStroke(
1325 ((KeyStroke) keys[i]).getKeyCode(),
1326 ((KeyStroke) keys[i]).getModifiers()),
1327 (String) focusInputMap.get((KeyStroke) keys[i]));
1329 parentActionMap.put(
1330 (String) focusInputMap.get((KeyStroke) keys[i]),
1331 new ActionListenerProxy(
1332 action,
1333 (String) focusInputMap.get((KeyStroke) keys[i])));
1337 parentInputMap.setParent(tree.getInputMap(
1338 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent());
1339 parentActionMap.setParent(tree.getActionMap().getParent());
1340 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1341 parentInputMap);
1342 tree.getActionMap().setParent(parentActionMap);
1346 * Converts the modifiers.
1348 * @param mod -
1349 * modifier to convert
1350 * @returns the new modifier
1352 private int convertModifiers(int mod)
1354 if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
1356 mod |= KeyEvent.SHIFT_MASK;
1357 mod &= ~KeyEvent.SHIFT_DOWN_MASK;
1359 if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
1361 mod |= KeyEvent.CTRL_MASK;
1362 mod &= ~KeyEvent.CTRL_DOWN_MASK;
1364 if ((mod & KeyEvent.META_DOWN_MASK) != 0)
1366 mod |= KeyEvent.META_MASK;
1367 mod &= ~KeyEvent.META_DOWN_MASK;
1369 if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
1371 mod |= KeyEvent.ALT_MASK;
1372 mod &= ~KeyEvent.ALT_DOWN_MASK;
1374 if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
1376 mod |= KeyEvent.ALT_GRAPH_MASK;
1377 mod &= ~KeyEvent.ALT_GRAPH_DOWN_MASK;
1379 return mod;
1383 * Install all listeners for this
1385 protected void installListeners()
1387 tree.addPropertyChangeListener(propertyChangeListener);
1388 tree.addFocusListener(focusListener);
1389 tree.addTreeSelectionListener(treeSelectionListener);
1390 tree.addMouseListener(mouseListener);
1391 tree.addKeyListener(keyListener);
1392 tree.addPropertyChangeListener(selectionModelPropertyChangeListener);
1393 tree.addComponentListener(componentListener);
1394 tree.addTreeExpansionListener(treeExpansionListener);
1395 if (treeModel != null)
1396 treeModel.addTreeModelListener(treeModelListener);
1400 * Install the UI for the component
1402 * @param c
1403 * the component to install UI for
1405 public void installUI(JComponent c)
1407 tree = (JTree) c;
1408 prepareForUIInstall();
1409 super.installUI(c);
1410 installDefaults();
1412 installComponents();
1413 installKeyboardActions();
1414 installListeners();
1416 setCellEditor(createDefaultCellEditor());
1417 createdCellEditor = true;
1418 isEditing = false;
1420 setModel(tree.getModel());
1421 treeSelectionModel = tree.getSelectionModel();
1423 completeUIInstall();
1427 * Uninstall the defaults for the tree
1429 protected void uninstallDefaults()
1431 tree.setFont(null);
1432 tree.setForeground(null);
1433 tree.setBackground(null);
1437 * Uninstall the UI for the component
1439 * @param c
1440 * the component to uninstall UI for
1442 public void uninstallUI(JComponent c)
1444 prepareForUIUninstall();
1445 uninstallDefaults();
1446 uninstallKeyboardActions();
1447 uninstallListeners();
1448 tree = null;
1449 uninstallComponents();
1450 completeUIUninstall();
1454 * Paints the specified component appropriate for the look and feel. This
1455 * method is invoked from the ComponentUI.update method when the specified
1456 * component is being painted. Subclasses should override this method and use
1457 * the specified Graphics object to render the content of the component.
1459 * @param g
1460 * the Graphics context in which to paint
1461 * @param c
1462 * the component being painted; this argument is often ignored, but
1463 * might be used if the UI object is stateless and shared by multiple
1464 * components
1466 public void paint(Graphics g, JComponent c)
1468 JTree tree = (JTree) c;
1469 updateCurrentVisiblePath();
1471 Rectangle clip = g.getClipBounds();
1472 Insets insets = tree.getInsets();
1474 if (clip != null && treeModel != null && currentVisiblePath != null)
1476 int startIndex = tree.getClosestRowForLocation(clip.x, clip.y);
1477 int endIndex = tree.getClosestRowForLocation(clip.x + clip.width,
1478 clip.y + clip.height);
1480 paintVerticalPartOfLeg(g, clip, insets, currentVisiblePath);
1481 for (int i = startIndex; i <= endIndex; i++)
1483 Object curr = currentVisiblePath.getPathComponent(i);
1484 boolean isLeaf = treeModel.isLeaf(curr);
1485 TreePath path = new TreePath(getPathToRoot(curr, 0));
1487 boolean isExpanded = tree.isExpanded(path);
1488 Rectangle bounds = getPathBounds(tree, path);
1489 paintHorizontalPartOfLeg(g, clip, insets, bounds, path, i,
1490 isExpanded, false, isLeaf);
1491 paintRow(g, clip, insets, bounds, path, i, isExpanded, false,
1492 isLeaf);
1498 * Ensures that the rows identified by beginRow through endRow are visible.
1500 * @param beginRow
1501 * is the first row
1502 * @param endRow
1503 * is the last row
1505 protected void ensureRowsAreVisible(int beginRow, int endRow)
1507 if (beginRow < endRow)
1509 int temp = endRow;
1510 endRow = beginRow;
1511 beginRow = temp;
1514 for (int i = beginRow; i < endRow; i++)
1516 TreePath path = getPathForRow(tree, i);
1517 if (!tree.isVisible(path))
1518 tree.makeVisible(path);
1523 * Sets the preferred minimum size.
1525 * @param newSize
1526 * is the new preferred minimum size.
1528 public void setPreferredMinSize(Dimension newSize)
1530 preferredMinSize = newSize;
1534 * Gets the preferred minimum size.
1536 * @returns the preferred minimum size.
1538 public Dimension getPreferredMinSize()
1540 return preferredMinSize;
1544 * Returns the preferred size to properly display the tree, this is a cover
1545 * method for getPreferredSize(c, false).
1547 * @param c
1548 * the component whose preferred size is being queried; this argument
1549 * is often ignored but might be used if the UI object is stateless
1550 * and shared by multiple components
1551 * @return the preferred size
1553 public Dimension getPreferredSize(JComponent c)
1555 return getPreferredSize(c, false);
1559 * Returns the preferred size to represent the tree in c. If checkConsistancy
1560 * is true, checkConsistancy is messaged first.
1562 * @param c
1563 * the component whose preferred size is being queried.
1564 * @param checkConsistancy
1565 * if true must check consistancy
1566 * @return the preferred size
1568 public Dimension getPreferredSize(JComponent c, boolean checkConsistancy)
1570 // FIXME: checkConsistancy not implemented, c not used
1571 if (!validCachedPreferredSize)
1572 updateCachedPreferredSize();
1573 return preferredSize;
1577 * Returns the minimum size for this component. Which will be the min
1578 * preferred size or (0,0).
1580 * @param c
1581 * the component whose min size is being queried.
1582 * @returns the preferred size or null
1584 public Dimension getMinimumSize(JComponent c)
1586 Dimension min = getPreferredMinSize();
1587 if (min == null)
1588 return new Dimension();
1589 return min;
1593 * Returns the maximum size for the component, which will be the preferred
1594 * size if the instance is currently in JTree or (0,0).
1596 * @param c
1597 * the component whose preferred size is being queried
1598 * @return the max size or null
1600 public Dimension getMaximumSize(JComponent c)
1602 if (c instanceof JTree)
1603 return ((JTree) c).getPreferredSize();
1604 return new Dimension();
1608 * Messages to stop the editing session. If the UI the receiver is providing
1609 * the look and feel for returns true from
1610 * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked
1611 * on the current editor. Then completeEditing will be messaged with false,
1612 * true, false to cancel any lingering editing.
1614 protected void completeEditing()
1616 completeEditing(false, true, false);
1620 * Stops the editing session. If messageStop is true, the editor is messaged
1621 * with stopEditing, if messageCancel is true the editor is messaged with
1622 * cancelEditing. If messageTree is true, the treeModel is messaged with
1623 * valueForPathChanged.
1625 * @param messageStop
1626 * message to stop editing
1627 * @param messageCancel
1628 * message to cancel editing
1629 * @param messageTree
1630 * message to treeModel
1632 protected void completeEditing(boolean messageStop, boolean messageCancel,
1633 boolean messageTree)
1635 if (messageStop)
1637 getCellEditor().stopCellEditing();
1638 stopEditingInCompleteEditing = true;
1641 if (messageCancel)
1643 getCellEditor().cancelCellEditing();
1644 stopEditingInCompleteEditing = true;
1647 if (messageTree)
1649 TreeCellEditor editor = getCellEditor();
1650 if (editor != null)
1652 Object value = editor.getCellEditorValue();
1653 treeModel.valueForPathChanged(tree.getLeadSelectionPath(), value);
1659 * Will start editing for node if there is a cellEditor and shouldSelectCall
1660 * returns true. This assumes that path is valid and visible.
1662 * @param path
1663 * is the path to start editing
1664 * @param event
1665 * is the MouseEvent performed on the path
1666 * @return true if successful
1668 protected boolean startEditing(TreePath path, MouseEvent event)
1670 // Force to recalculate the maximal row height.
1671 maxHeight = 0;
1673 // Force to recalculate the cached preferred size.
1674 validCachedPreferredSize = false;
1676 updateCellEditor();
1677 TreeCellEditor ed = getCellEditor();
1679 if (ed != null
1680 && (event == EDIT || ed.shouldSelectCell(event))
1681 && ed.isCellEditable(event))
1683 Rectangle bounds = getPathBounds(tree, path);
1685 // Extend the right boundary till the tree width.
1686 bounds.width = tree.getWidth() - bounds.x;
1688 editingPath = path;
1689 editingRow = tree.getRowForPath(editingPath);
1691 Object value = editingPath.getLastPathComponent();
1693 stopEditingInCompleteEditing = false;
1694 boolean expanded = tree.isExpanded(editingPath);
1695 isEditing = true;
1696 editingComponent = ed.getTreeCellEditorComponent(tree, value, true,
1697 expanded,
1698 isLeaf(editingRow),
1699 editingRow);
1701 // Remove all previous components (if still present). Only one
1702 // container with the editing component inside is allowed in the tree.
1703 tree.removeAll();
1705 // The editing component must be added to its container. We add the
1706 // container, not the editing component itself.
1707 Component container = editingComponent.getParent();
1708 container.setBounds(bounds);
1709 tree.add(container);
1710 editingComponent.requestFocus();
1712 return true;
1714 return false;
1718 * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or
1719 * collapse region of the row, this will toggle the row.
1721 * @param path
1722 * the path we are concerned with
1723 * @param mouseX
1724 * is the cursor's x position
1725 * @param mouseY
1726 * is the cursor's y position
1728 protected void checkForClickInExpandControl(TreePath path, int mouseX,
1729 int mouseY)
1731 if (isLocationInExpandControl(path, mouseX, mouseY))
1732 toggleExpandState(path);
1736 * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in
1737 * the area of row that is used to expand/collpse the node and the node at row
1738 * does not represent a leaf.
1740 * @param path
1741 * the path we are concerned with
1742 * @param mouseX
1743 * is the cursor's x position
1744 * @param mouseY
1745 * is the cursor's y position
1746 * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in
1747 * the area of row that is used to expand/collpse the node and the
1748 * node at row does not represent a leaf.
1750 protected boolean isLocationInExpandControl(TreePath path, int mouseX,
1751 int mouseY)
1753 boolean cntlClick = false;
1754 int row = getRowForPath(tree, path);
1756 if (!isLeaf(row))
1758 Rectangle bounds = getPathBounds(tree, path);
1760 if (hasControlIcons()
1761 && (mouseX < bounds.x)
1762 && (mouseX > (bounds.x - getCurrentControlIcon(path).getIconWidth() - gap)))
1763 cntlClick = true;
1765 return cntlClick;
1769 * Messaged when the user clicks the particular row, this invokes
1770 * toggleExpandState.
1772 * @param path
1773 * the path we are concerned with
1774 * @param mouseX
1775 * is the cursor's x position
1776 * @param mouseY
1777 * is the cursor's y position
1779 protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY)
1781 toggleExpandState(path);
1785 * Expands path if it is not expanded, or collapses row if it is expanded. If
1786 * expanding a path and JTree scroll on expand, ensureRowsAreVisible is
1787 * invoked to scroll as many of the children to visible as possible (tries to
1788 * scroll to last visible descendant of path).
1790 * @param path
1791 * the path we are concerned with
1793 protected void toggleExpandState(TreePath path)
1795 if (tree.isExpanded(path))
1796 tree.collapsePath(path);
1797 else
1798 tree.expandPath(path);
1802 * Returning true signifies a mouse event on the node should toggle the
1803 * selection of only the row under the mouse.
1805 * @param event
1806 * is the MouseEvent performed on the row.
1807 * @return true signifies a mouse event on the node should toggle the
1808 * selection of only the row under the mouse.
1810 protected boolean isToggleSelectionEvent(MouseEvent event)
1812 return (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION);
1816 * Returning true signifies a mouse event on the node should select from the
1817 * anchor point.
1819 * @param event
1820 * is the MouseEvent performed on the node.
1821 * @return true signifies a mouse event on the node should select from the
1822 * anchor point.
1824 protected boolean isMultiSelectEvent(MouseEvent event)
1826 return (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
1830 * Returning true indicates the row under the mouse should be toggled based on
1831 * the event. This is invoked after checkForClickInExpandControl, implying the
1832 * location is not in the expand (toggle) control.
1834 * @param event
1835 * is the MouseEvent performed on the row.
1836 * @return true indicates the row under the mouse should be toggled based on
1837 * the event.
1839 protected boolean isToggleEvent(MouseEvent event)
1841 return true;
1845 * Messaged to update the selection based on a MouseEvent over a particular
1846 * row. If the even is a toggle selection event, the row is either selected,
1847 * or deselected. If the event identifies a multi selection event, the
1848 * selection is updated from the anchor point. Otherwise, the row is selected,
1849 * and if the even specified a toggle event the row is expanded/collapsed.
1851 * @param path
1852 * is the path selected for an event
1853 * @param event
1854 * is the MouseEvent performed on the path.
1856 protected void selectPathForEvent(TreePath path, MouseEvent event)
1858 if (isToggleSelectionEvent(event))
1860 if (tree.isPathSelected(path))
1861 tree.removeSelectionPath(path);
1862 else
1864 tree.addSelectionPath(path);
1865 tree.setAnchorSelectionPath(path);
1868 else if (isMultiSelectEvent(event))
1870 TreePath anchor = tree.getAnchorSelectionPath();
1871 if (anchor != null)
1873 int aRow = getRowForPath(tree, anchor);
1874 tree.addSelectionInterval(aRow, getRowForPath(tree, path));
1876 else
1877 tree.addSelectionPath(path);
1879 else
1880 tree.addSelectionPath(path);
1884 * Returns true if the node at <code>row</code> is a leaf.
1886 * @param row
1887 * is the row we are concerned with.
1888 * @return true if the node at <code>row</code> is a leaf.
1890 protected boolean isLeaf(int row)
1892 TreePath pathForRow = getPathForRow(tree, row);
1893 if (pathForRow == null)
1894 return true;
1896 Object node = pathForRow.getLastPathComponent();
1897 return treeModel.isLeaf(node);
1901 * This class implements the actions that we want to happen when specific keys
1902 * are pressed for the JTree. The actionPerformed method is called when a key
1903 * that has been registered for the JTree is received.
1905 class TreeAction extends AbstractAction
1909 * What to do when this action is called.
1911 * @param e
1912 * the ActionEvent that caused this action.
1914 public void actionPerformed(ActionEvent e)
1916 TreePath lead = tree.getLeadSelectionPath();
1918 if (e.getActionCommand().equals("selectPreviousChangeLead")
1919 || e.getActionCommand().equals("selectPreviousExtendSelection")
1920 || e.getActionCommand().equals("selectPrevious")
1921 || e.getActionCommand().equals("selectNext")
1922 || e.getActionCommand().equals("selectNextExtendSelection")
1923 || e.getActionCommand().equals("selectNextChangeLead"))
1924 (new TreeIncrementAction(0, "")).actionPerformed(e);
1925 else if (e.getActionCommand().equals("selectParent")
1926 || e.getActionCommand().equals("selectChild"))
1927 (new TreeTraverseAction(0, "")).actionPerformed(e);
1928 else if (e.getActionCommand().equals("selectAll"))
1930 TreePath[] paths = new TreePath[tree.getVisibleRowCount()];
1932 Object curr = getNextVisibleNode(treeModel.getRoot());
1933 int i = 0;
1934 while (curr != null && i < paths.length)
1936 paths[i] = new TreePath(getPathToRoot(curr, 0));
1937 i++;
1940 tree.addSelectionPaths(paths);
1942 else if (e.getActionCommand().equals("startEditing"))
1943 tree.startEditingAtPath(lead);
1944 else if (e.getActionCommand().equals("toggle"))
1946 if (tree.isEditing())
1947 tree.stopEditing();
1948 else
1950 Object last = lead.getLastPathComponent();
1951 TreePath path = new TreePath(getPathToRoot(last, 0));
1952 if (!treeModel.isLeaf(last))
1953 toggleExpandState(path);
1956 else if (e.getActionCommand().equals("clearSelection"))
1957 tree.clearSelection();
1959 if (tree.isEditing() && !e.getActionCommand().equals("startEditing"))
1960 tree.stopEditing();
1962 tree.scrollPathToVisible(lead);
1967 * This class is used to mimic the behaviour of the JDK when registering
1968 * keyboard actions. It is the same as the private class used in JComponent
1969 * for the same reason. This class receives an action event and dispatches it
1970 * to the true receiver after altering the actionCommand property of the
1971 * event.
1973 private static class ActionListenerProxy extends AbstractAction
1975 ActionListener target;
1977 String bindingCommandName;
1979 public ActionListenerProxy(ActionListener li, String cmd)
1981 target = li;
1982 bindingCommandName = cmd;
1985 public void actionPerformed(ActionEvent e)
1987 ActionEvent derivedEvent = new ActionEvent(e.getSource(), e.getID(),
1988 bindingCommandName,
1989 e.getModifiers());
1991 target.actionPerformed(derivedEvent);
1996 * Updates the preferred size when scrolling, if necessary.
1998 public class ComponentHandler extends ComponentAdapter implements
1999 ActionListener
2002 * Timer used when inside a scrollpane and the scrollbar is adjusting
2004 protected Timer timer;
2006 /** ScrollBar that is being adjusted */
2007 protected JScrollBar scrollBar;
2010 * Constructor
2012 public ComponentHandler()
2014 // Nothing to do here.
2018 * Invoked when the component's position changes.
2020 * @param e
2021 * the event that occurs when moving the component
2023 public void componentMoved(ComponentEvent e)
2025 // TODO: What should be done here, if anything?
2029 * Creates, if necessary, and starts a Timer to check if needed to resize
2030 * the bounds
2032 protected void startTimer()
2034 // TODO: Implement this properly.
2038 * Returns the JScrollPane housing the JTree, or null if one isn't found.
2040 * @return JScrollPane housing the JTree, or null if one isn't found.
2042 protected JScrollPane getScrollPane()
2044 return null;
2048 * Public as a result of Timer. If the scrollBar is null, or not adjusting,
2049 * this stops the timer and updates the sizing.
2051 * @param ae
2052 * is the action performed
2054 public void actionPerformed(ActionEvent ae)
2056 // TODO: Implement this properly.
2061 * Listener responsible for getting cell editing events and updating the tree
2062 * accordingly.
2064 public class CellEditorHandler implements CellEditorListener
2067 * Constructor
2069 public CellEditorHandler()
2071 // Nothing to do here.
2075 * Messaged when editing has stopped in the tree. Tells the listeners
2076 * editing has stopped.
2078 * @param e
2079 * is the notification event
2081 public void editingStopped(ChangeEvent e)
2083 stopEditing(tree);
2087 * Messaged when editing has been canceled in the tree. This tells the
2088 * listeners the editor has canceled editing.
2090 * @param e
2091 * is the notification event
2093 public void editingCanceled(ChangeEvent e)
2095 cancelEditing(tree);
2097 }// CellEditorHandler
2100 * Repaints the lead selection row when focus is lost/grained.
2102 public class FocusHandler implements FocusListener
2105 * Constructor
2107 public FocusHandler()
2109 // Nothing to do here.
2113 * Invoked when focus is activated on the tree we're in, redraws the lead
2114 * row. Invoked when a component gains the keyboard focus.
2116 * @param e
2117 * is the focus event that is activated
2119 public void focusGained(FocusEvent e)
2121 // TODO: Implement this properly.
2125 * Invoked when focus is deactivated on the tree we're in, redraws the lead
2126 * row. Invoked when a component loses the keyboard focus.
2128 * @param e
2129 * is the focus event that is deactivated
2131 public void focusLost(FocusEvent e)
2133 // TODO: Implement this properly.
2138 * This is used to get multiple key down events to appropriately genereate
2139 * events.
2141 public class KeyHandler extends KeyAdapter
2143 /** Key code that is being generated for. */
2144 protected Action repeatKeyAction;
2146 /** Set to true while keyPressed is active */
2147 protected boolean isKeyDown;
2150 * Constructor
2152 public KeyHandler()
2154 // Nothing to do here.
2158 * Invoked when a key has been typed. Moves the keyboard focus to the first
2159 * element whose first letter matches the alphanumeric key pressed by the
2160 * user. Subsequent same key presses move the keyboard focus to the next
2161 * object that starts with the same letter.
2163 * @param e
2164 * the key typed
2166 public void keyTyped(KeyEvent e)
2168 // TODO: What should be done here, if anything?
2172 * Invoked when a key has been pressed.
2174 * @param e
2175 * the key pressed
2177 public void keyPressed(KeyEvent e)
2179 // TODO: What should be done here, if anything?
2183 * Invoked when a key has been released
2185 * @param e
2186 * the key released
2188 public void keyReleased(KeyEvent e)
2190 // TODO: What should be done here, if anything?
2195 * MouseListener is responsible for updating the selection based on mouse
2196 * events.
2198 public class MouseHandler extends MouseAdapter implements MouseMotionListener
2201 * Constructor
2203 public MouseHandler()
2205 // Nothing to do here.
2209 * Invoked when a mouse button has been pressed on a component.
2211 * @param e
2212 * is the mouse event that occured
2214 public void mousePressed(MouseEvent e)
2216 // Any mouse click cancels the previous waiting edit action, initiated
2217 // by the single click on the selected node.
2218 if (startEditTimer != null)
2220 startEditTimer.stop();
2221 startEditTimer = null;
2224 Point click = e.getPoint();
2225 TreePath path = getClosestPathForLocation(tree, click.x, click.y);
2227 if (path != null)
2229 Rectangle bounds = getPathBounds(tree, path);
2230 int row = getRowForPath(tree, path);
2231 boolean cntlClick = isLocationInExpandControl(path, click.x, click.y);
2233 boolean isLeaf = isLeaf(row);
2235 TreeCellRenderer tcr = getCellRenderer();
2236 Icon icon;
2237 if (isLeaf)
2238 icon = UIManager.getIcon("Tree.leafIcon");
2239 else if (tree.isExpanded(path))
2240 icon = UIManager.getIcon("Tree.openIcon");
2241 else
2242 icon = UIManager.getIcon("Tree.closedIcon");
2244 if (tcr instanceof DefaultTreeCellRenderer)
2246 Icon tmp = ((DefaultTreeCellRenderer) tcr).getIcon();
2247 if (tmp != null)
2248 icon = tmp;
2251 // add gap*2 for the space before and after the text
2252 if (icon != null)
2253 bounds.width += icon.getIconWidth() + gap * 2;
2255 boolean inBounds = bounds.contains(click.x, click.y);
2256 if ((inBounds || cntlClick) && tree.isVisible(path))
2258 if (inBounds)
2260 TreePath currentLead = tree.getLeadSelectionPath();
2261 if (
2262 currentLead != null &&
2263 currentLead.equals(path) &&
2264 e.getClickCount() == 1 &&
2265 tree.isEditable()
2268 // Schedule the editing session.
2269 final TreePath editPath = path;
2271 if (startEditTimer != null)
2272 startEditTimer.stop();
2274 startEditTimer = new Timer(WAIT_TILL_EDITING,
2275 new ActionListener()
2277 public void actionPerformed(ActionEvent e)
2279 startEditing(editPath, EDIT);
2282 startEditTimer.setRepeats(false);
2283 startEditTimer.start();
2285 else
2287 selectPath(tree, path);
2288 if (e.getClickCount() == 2 && !isLeaf(row))
2289 toggleExpandState(path);
2293 if (cntlClick)
2295 handleExpandControlClick(path, click.x, click.y);
2296 if (cellEditor != null)
2297 cellEditor.cancelCellEditing();
2298 tree.scrollPathToVisible(path);
2300 else if (tree.isEditable())
2301 startEditing(path, e);
2307 * Invoked when a mouse button is pressed on a component and then dragged.
2308 * MOUSE_DRAGGED events will continue to be delivered to the component where
2309 * the drag originated until the mouse button is released (regardless of
2310 * whether the mouse position is within the bounds of the component).
2312 * @param e
2313 * is the mouse event that occured
2315 public void mouseDragged(MouseEvent e)
2317 // TODO: What should be done here, if anything?
2321 * Invoked when the mouse button has been moved on a component (with no
2322 * buttons no down).
2324 * @param e
2325 * the mouse event that occured
2327 public void mouseMoved(MouseEvent e)
2329 // TODO: What should be done here, if anything?
2333 * Invoked when a mouse button has been released on a component.
2335 * @param e
2336 * is the mouse event that occured
2338 public void mouseReleased(MouseEvent e)
2340 // TODO: What should be done here, if anything?
2345 * MouseInputHandler handles passing all mouse events, including mouse motion
2346 * events, until the mouse is released to the destination it is constructed
2347 * with.
2349 public class MouseInputHandler implements MouseInputListener
2351 /** Source that events are coming from */
2352 protected Component source;
2354 /** Destination that receives all events. */
2355 protected Component destination;
2358 * Constructor
2360 * @param source
2361 * that events are coming from
2362 * @param destination
2363 * that receives all events
2364 * @param e
2365 * is the event received
2367 public MouseInputHandler(Component source, Component destination,
2368 MouseEvent e)
2370 this.source = source;
2371 this.destination = destination;
2375 * Invoked when the mouse button has been clicked (pressed and released) on
2376 * a component.
2378 * @param e
2379 * mouse event that occured
2381 public void mouseClicked(MouseEvent e)
2383 // TODO: What should be done here, if anything?
2387 * Invoked when a mouse button has been pressed on a component.
2389 * @param e
2390 * mouse event that occured
2392 public void mousePressed(MouseEvent e)
2394 // TODO: What should be done here, if anything?
2398 * Invoked when a mouse button has been released on a component.
2400 * @param e
2401 * mouse event that occured
2403 public void mouseReleased(MouseEvent e)
2405 // TODO: What should be done here, if anything?
2409 * Invoked when the mouse enters a component.
2411 * @param e
2412 * mouse event that occured
2414 public void mouseEntered(MouseEvent e)
2416 // TODO: What should be done here, if anything?
2420 * Invoked when the mouse exits a component.
2422 * @param e
2423 * mouse event that occured
2425 public void mouseExited(MouseEvent e)
2427 // TODO: What should be done here, if anything?
2431 * Invoked when a mouse button is pressed on a component and then dragged.
2432 * MOUSE_DRAGGED events will continue to be delivered to the component where
2433 * the drag originated until the mouse button is released (regardless of
2434 * whether the mouse position is within the bounds of the component).
2436 * @param e
2437 * mouse event that occured
2439 public void mouseDragged(MouseEvent e)
2441 // TODO: What should be done here, if anything?
2445 * Invoked when the mouse cursor has been moved onto a component but no
2446 * buttons have been pushed.
2448 * @param e
2449 * mouse event that occured
2451 public void mouseMoved(MouseEvent e)
2453 // TODO: What should be done here, if anything?
2457 * Removes event from the source
2459 protected void removeFromSource()
2461 // TODO: Implement this properly.
2466 * Class responsible for getting size of node, method is forwarded to
2467 * BasicTreeUI method. X location does not include insets, that is handled in
2468 * getPathBounds.
2470 public class NodeDimensionsHandler extends AbstractLayoutCache.NodeDimensions
2473 * Constructor
2475 public NodeDimensionsHandler()
2477 // Nothing to do here.
2481 * Returns, by reference in bounds, the size and x origin to place value at.
2482 * The calling method is responsible for determining the Y location. If
2483 * bounds is null, a newly created Rectangle should be returned, otherwise
2484 * the value should be placed in bounds and returned.
2486 * @param cell
2487 * the value to be represented
2488 * @param row
2489 * row being queried
2490 * @param depth
2491 * the depth of the row
2492 * @param expanded
2493 * true if row is expanded
2494 * @param size
2495 * a Rectangle containing the size needed to represent value
2496 * @return containing the node dimensions, or null if node has no dimension
2498 public Rectangle getNodeDimensions(Object cell, int row, int depth,
2499 boolean expanded, Rectangle size)
2501 if (size == null || cell == null)
2502 return null;
2504 String s = cell.toString();
2505 Font f = tree.getFont();
2506 FontMetrics fm = tree.getToolkit().getFontMetrics(f);
2508 if (s != null)
2510 size.x = getRowX(row, depth);
2511 size.width = SwingUtilities.computeStringWidth(fm, s);
2512 size.height = getMaxHeight(tree);
2513 size.y = size.height * row;
2516 return size;
2520 * Returns the amount to indent the given row
2522 * @return amount to indent the given row.
2524 protected int getRowX(int row, int depth)
2526 if (row == 0)
2527 return 0;
2528 return depth * rightChildIndent;
2530 }// NodeDimensionsHandler
2533 * PropertyChangeListener for the tree. Updates the appropriate varaible, or
2534 * TreeState, based on what changes.
2536 public class PropertyChangeHandler implements PropertyChangeListener
2540 * Constructor
2542 public PropertyChangeHandler()
2544 // Nothing to do here.
2548 * This method gets called when a bound property is changed.
2550 * @param event
2551 * A PropertyChangeEvent object describing the event source and the
2552 * property that has changed.
2554 public void propertyChange(PropertyChangeEvent event)
2556 if ((event.getPropertyName()).equals("rootVisible"))
2558 validCachedPreferredSize = false;
2559 tree.repaint();
2565 * Listener on the TreeSelectionModel, resets the row selection if any of the
2566 * properties of the model change.
2568 public class SelectionModelPropertyChangeHandler implements
2569 PropertyChangeListener
2573 * Constructor
2575 public SelectionModelPropertyChangeHandler()
2577 // Nothing to do here.
2581 * This method gets called when a bound property is changed.
2583 * @param event
2584 * A PropertyChangeEvent object describing the event source and the
2585 * property that has changed.
2587 public void propertyChange(PropertyChangeEvent event)
2589 // TODO: What should be done here, if anything?
2594 * ActionListener that invokes cancelEditing when action performed.
2596 public class TreeCancelEditingAction extends AbstractAction
2600 * Constructor
2602 public TreeCancelEditingAction(String name)
2604 // TODO: Implement this properly.
2608 * Invoked when an action occurs.
2610 * @param e
2611 * event that occured
2613 public void actionPerformed(ActionEvent e)
2615 // TODO: Implement this properly.
2619 * Returns true if the action is enabled.
2621 * @return true if the action is enabled, false otherwise
2623 public boolean isEnabled()
2625 // TODO: Implement this properly.
2626 return false;
2631 * Updates the TreeState in response to nodes expanding/collapsing.
2633 public class TreeExpansionHandler implements TreeExpansionListener
2637 * Constructor
2639 public TreeExpansionHandler()
2641 // Nothing to do here.
2645 * Called whenever an item in the tree has been expanded.
2647 * @param event
2648 * is the event that occured
2650 public void treeExpanded(TreeExpansionEvent event)
2652 validCachedPreferredSize = false;
2653 tree.repaint();
2657 * Called whenever an item in the tree has been collapsed.
2659 * @param event
2660 * is the event that occured
2662 public void treeCollapsed(TreeExpansionEvent event)
2664 validCachedPreferredSize = false;
2665 tree.repaint();
2667 }// TreeExpansionHandler
2670 * TreeHomeAction is used to handle end/home actions. Scrolls either the first
2671 * or last cell to be visible based on direction.
2673 public class TreeHomeAction extends AbstractAction
2676 /** The direction, either home or end */
2677 protected int direction;
2680 * Constructor
2682 * @param direction -
2683 * it is home or end
2684 * @param name
2685 * is the name of the direction
2687 public TreeHomeAction(int direction, String name)
2689 // TODO: Implement this properly
2693 * Invoked when an action occurs.
2695 * @param e
2696 * is the event that occured
2698 public void actionPerformed(ActionEvent e)
2700 // TODO: Implement this properly
2704 * Returns true if the action is enabled.
2706 * @return true if the action is enabled.
2708 public boolean isEnabled()
2710 // TODO: Implement this properly
2711 return false;
2716 * TreeIncrementAction is used to handle up/down actions. Selection is moved
2717 * up or down based on direction.
2719 public class TreeIncrementAction extends AbstractAction
2722 /** Specifies the direction to adjust the selection by. */
2723 protected int direction;
2726 * Constructor
2728 * @param direction
2729 * up or down
2730 * @param name
2731 * is the name of the direction
2733 public TreeIncrementAction(int direction, String name)
2735 // TODO: Implement this properly
2739 * Invoked when an action occurs.
2741 * @param e
2742 * is the event that occured
2744 public void actionPerformed(ActionEvent e)
2746 Object last = tree.getLeadSelectionPath().getLastPathComponent();
2748 if (e.getActionCommand().equals("selectPreviousChangeLead"))
2750 Object prev = getPreviousVisibleNode(last);
2752 if (prev != null)
2754 TreePath newPath = new TreePath(getPathToRoot(prev, 0));
2755 selectPath(tree, newPath);
2756 tree.setLeadSelectionPath(newPath);
2759 else if (e.getActionCommand().equals("selectPreviousExtendSelection"))
2761 Object prev = getPreviousVisibleNode(last);
2762 if (prev != null)
2764 TreePath newPath = new TreePath(getPathToRoot(prev, 0));
2765 tree.addSelectionPath(newPath);
2766 tree.setLeadSelectionPath(newPath);
2769 else if (e.getActionCommand().equals("selectPrevious"))
2771 Object prev = getPreviousVisibleNode(last);
2773 if (prev != null)
2775 TreePath newPath = new TreePath(getPathToRoot(prev, 0));
2776 selectPath(tree, newPath);
2779 else if (e.getActionCommand().equals("selectNext"))
2781 Object next = getNextVisibleNode(last);
2783 if (next != null)
2785 TreePath newPath = new TreePath(getPathToRoot(next, 0));
2786 selectPath(tree, newPath);
2789 else if (e.getActionCommand().equals("selectNextExtendSelection"))
2791 Object next = getNextVisibleNode(last);
2792 if (next != null)
2794 TreePath newPath = new TreePath(getPathToRoot(next, 0));
2795 tree.addSelectionPath(newPath);
2796 tree.setLeadSelectionPath(newPath);
2799 else if (e.getActionCommand().equals("selectNextChangeLead"))
2801 Object next = getNextVisibleNode(last);
2802 if (next != null)
2804 TreePath newPath = new TreePath(getPathToRoot(next, 0));
2805 selectPath(tree, newPath);
2806 tree.setLeadSelectionPath(newPath);
2812 * Returns true if the action is enabled.
2814 * @return true if the action is enabled.
2816 public boolean isEnabled()
2818 // TODO: Implement this properly
2819 return false;
2824 * Forwards all TreeModel events to the TreeState.
2826 public class TreeModelHandler implements TreeModelListener
2829 * Constructor
2831 public TreeModelHandler()
2833 // Nothing to do here.
2837 * Invoked after a node (or a set of siblings) has changed in some way. The
2838 * node(s) have not changed locations in the tree or altered their children
2839 * arrays, but other attributes have changed and may affect presentation.
2840 * Example: the name of a file has changed, but it is in the same location
2841 * in the file system. To indicate the root has changed, childIndices and
2842 * children will be null. Use e.getPath() to get the parent of the changed
2843 * node(s). e.getChildIndices() returns the index(es) of the changed
2844 * node(s).
2846 * @param e
2847 * is the event that occured
2849 public void treeNodesChanged(TreeModelEvent e)
2851 validCachedPreferredSize = false;
2852 tree.repaint();
2856 * Invoked after nodes have been inserted into the tree. Use e.getPath() to
2857 * get the parent of the new node(s). e.getChildIndices() returns the
2858 * index(es) of the new node(s) in ascending order.
2860 * @param e
2861 * is the event that occured
2863 public void treeNodesInserted(TreeModelEvent e)
2865 validCachedPreferredSize = false;
2866 tree.repaint();
2870 * Invoked after nodes have been removed from the tree. Note that if a
2871 * subtree is removed from the tree, this method may only be invoked once
2872 * for the root of the removed subtree, not once for each individual set of
2873 * siblings removed. Use e.getPath() to get the former parent of the deleted
2874 * node(s). e.getChildIndices() returns, in ascending order, the index(es)
2875 * the node(s) had before being deleted.
2877 * @param e
2878 * is the event that occured
2880 public void treeNodesRemoved(TreeModelEvent e)
2882 validCachedPreferredSize = false;
2883 tree.repaint();
2887 * Invoked after the tree has drastically changed structure from a given
2888 * node down. If the path returned by e.getPath() is of length one and the
2889 * first element does not identify the current root node the first element
2890 * should become the new root of the tree. Use e.getPath() to get the path
2891 * to the node. e.getChildIndices() returns null.
2893 * @param e
2894 * is the event that occured
2896 public void treeStructureChanged(TreeModelEvent e)
2898 if (e.getPath().length == 1
2899 && !e.getPath()[0].equals(treeModel.getRoot()))
2900 tree.expandPath(new TreePath(treeModel.getRoot()));
2901 validCachedPreferredSize = false;
2902 tree.repaint();
2904 }// TreeModelHandler
2907 * TreePageAction handles page up and page down events.
2909 public class TreePageAction extends AbstractAction
2911 /** Specifies the direction to adjust the selection by. */
2912 protected int direction;
2915 * Constructor
2917 * @param direction
2918 * up or down
2919 * @param name
2920 * is the name of the direction
2922 public TreePageAction(int direction, String name)
2924 this.direction = direction;
2928 * Invoked when an action occurs.
2930 * @param e
2931 * is the event that occured
2933 public void actionPerformed(ActionEvent e)
2935 // TODO: Implement this properly.
2939 * Returns true if the action is enabled.
2941 * @return true if the action is enabled.
2943 public boolean isEnabled()
2945 return false;
2947 }// TreePageAction
2950 * Listens for changes in the selection model and updates the display
2951 * accordingly.
2953 public class TreeSelectionHandler implements TreeSelectionListener
2956 * Constructor
2958 public TreeSelectionHandler()
2960 // Nothing to do here.
2964 * Messaged when the selection changes in the tree we're displaying for.
2965 * Stops editing, messages super and displays the changed paths.
2967 * @param event
2968 * the event that characterizes the change.
2970 public void valueChanged(TreeSelectionEvent event)
2972 if (tree.isEditing())
2973 tree.stopEditing();
2975 }// TreeSelectionHandler
2978 * For the first selected row expandedness will be toggled.
2980 public class TreeToggleAction extends AbstractAction
2983 * Constructor
2985 * @param name
2986 * is the name of <code>Action</code> field
2988 public TreeToggleAction(String name)
2990 // Nothing to do here.
2994 * Invoked when an action occurs.
2996 * @param e
2997 * the event that occured
2999 public void actionPerformed(ActionEvent e)
3001 // TODO: Implement this properly.
3005 * Returns true if the action is enabled.
3007 * @return true if the action is enabled, false otherwise
3009 public boolean isEnabled()
3011 return false;
3013 } // TreeToggleAction
3016 * TreeTraverseAction is the action used for left/right keys. Will toggle the
3017 * expandedness of a node, as well as potentially incrementing the selection.
3019 public class TreeTraverseAction extends AbstractAction
3022 * Determines direction to traverse, 1 means expand, -1 means collapse.
3024 protected int direction;
3027 * Constructor
3029 * @param direction
3030 * to traverse
3031 * @param name
3032 * is the name of the direction
3034 public TreeTraverseAction(int direction, String name)
3036 this.direction = direction;
3040 * Invoked when an action occurs.
3042 * @param e
3043 * the event that occured
3045 public void actionPerformed(ActionEvent e)
3047 Object last = tree.getLeadSelectionPath().getLastPathComponent();
3049 if (e.getActionCommand().equals("selectParent"))
3051 TreePath path = new TreePath(getPathToRoot(last, 0));
3052 Object p = getParent(treeModel.getRoot(), last);
3054 if (!treeModel.isLeaf(last))
3055 toggleExpandState(path);
3056 else if (p != null)
3057 selectPath(tree, new TreePath(getPathToRoot(p, 0)));
3059 else if (e.getActionCommand().equals("selectChild"))
3061 TreePath path = new TreePath(getPathToRoot(last, 0));
3063 if (!treeModel.isLeaf(last))
3064 toggleExpandState(path);
3065 else
3067 Object next = getNextVisibleNode(last);
3069 if (next != null)
3070 selectPath(tree, new TreePath(getPathToRoot(next, 0)));
3076 * Returns true if the action is enabled.
3078 * @return true if the action is enabled, false otherwise
3080 public boolean isEnabled()
3082 // TODO: Implement this properly
3083 return false;
3088 * Returns true if the LookAndFeel implements the control icons. Package
3089 * private for use in inner classes.
3091 * @returns true if there are control icons
3093 boolean hasControlIcons()
3095 if (expandedIcon != null || collapsedIcon != null)
3096 return true;
3097 return false;
3101 * Returns control icon. It is null if the LookAndFeel does not implements the
3102 * control icons. Package private for use in inner classes.
3104 * @return control icon if it exists.
3106 Icon getCurrentControlIcon(TreePath path)
3108 if (tree.isExpanded(path))
3109 return expandedIcon;
3110 return collapsedIcon;
3114 * Returns the parent of the current node
3116 * @param root
3117 * is the root of the tree
3118 * @param node
3119 * is the current node
3120 * @return is the parent of the current node
3122 Object getParent(Object root, Object node)
3124 if (root == null || node == null || root.equals(node))
3125 return null;
3127 if (node instanceof TreeNode)
3128 return ((TreeNode) node).getParent();
3129 return findNode(root, node);
3133 * Recursively checks the tree for the specified node, starting at the root.
3135 * @param root
3136 * is starting node to start searching at.
3137 * @param node
3138 * is the node to search for
3139 * @return the parent node of node
3141 private Object findNode(Object root, Object node)
3143 if (!treeModel.isLeaf(root) && !root.equals(node))
3145 int size = treeModel.getChildCount(root);
3146 for (int j = 0; j < size; j++)
3148 Object child = treeModel.getChild(root, j);
3149 if (node.equals(child))
3150 return root;
3152 Object n = findNode(child, node);
3153 if (n != null)
3154 return n;
3157 return null;
3161 * Get previous visible node in the tree. Package private for use in inner
3162 * classes.
3164 * @param node -
3165 * current node
3166 * @return the next visible node in the JTree. Return null if there are no
3167 * more.
3169 Object getPreviousVisibleNode(Object node)
3171 if (currentVisiblePath != null)
3173 Object[] nodes = currentVisiblePath.getPath();
3174 int i = 0;
3175 while (i < nodes.length && !node.equals(nodes[i]))
3176 i++;
3177 // return the next node
3178 if (i - 1 >= 0)
3179 return nodes[i - 1];
3181 return null;
3185 * Returns the next node in the tree Package private for use in inner classes.
3187 * @param curr -
3188 * current node
3189 * @return the next node in the tree
3191 Object getNextNode(Object curr)
3193 if (!treeModel.isLeaf(curr) && treeModel.getChildCount(curr) > 0)
3194 return treeModel.getChild(curr, 0);
3196 Object node = curr;
3197 Object sibling = null;
3200 sibling = getNextSibling(node);
3201 node = getParent(treeModel.getRoot(), node);
3203 while (sibling == null && node != null);
3205 return sibling;
3209 * Returns the previous node in the tree Package private for use in inner
3210 * classes.
3212 * @param node
3213 * current node
3214 * @return the previous node in the tree
3216 Object getPreviousNode(Object node)
3218 Object parent = getParent(treeModel.getRoot(), node);
3219 if (parent == null)
3220 return null;
3222 Object sibling = getPreviousSibling(node);
3224 if (sibling == null)
3225 return parent;
3227 int size = 0;
3228 if (!treeModel.isLeaf(sibling))
3229 size = treeModel.getChildCount(sibling);
3230 while (size > 0)
3232 sibling = treeModel.getChild(sibling, size - 1);
3233 if (!treeModel.isLeaf(sibling))
3234 size = treeModel.getChildCount(sibling);
3235 else
3236 size = 0;
3239 return sibling;
3243 * Returns the next sibling in the tree Package private for use in inner
3244 * classes.
3246 * @param node -
3247 * current node
3248 * @return the next sibling in the tree
3250 Object getNextSibling(Object node)
3252 Object parent = getParent(treeModel.getRoot(), node);
3253 if (parent == null)
3254 return null;
3256 int index = treeModel.getIndexOfChild(parent, node) + 1;
3258 int size = 0;
3259 if (!treeModel.isLeaf(parent))
3260 size = treeModel.getChildCount(parent);
3261 if (index == 0 || index >= size)
3262 return null;
3264 return treeModel.getChild(parent, index);
3268 * Returns the previous sibling in the tree Package private for use in inner
3269 * classes.
3271 * @param node -
3272 * current node
3273 * @return the previous sibling in the tree
3275 Object getPreviousSibling(Object node)
3277 Object parent = getParent(treeModel.getRoot(), node);
3278 if (parent == null)
3279 return null;
3281 int index = treeModel.getIndexOfChild(parent, node) - 1;
3283 int size = 0;
3284 if (!treeModel.isLeaf(parent))
3285 size = treeModel.getChildCount(parent);
3286 if (index < 0 || index >= size)
3287 return null;
3289 return treeModel.getChild(parent, index);
3293 * Selects the specified path in the tree depending on modes. Package private
3294 * for use in inner classes.
3296 * @param tree
3297 * is the tree we are selecting the path in
3298 * @param path
3299 * is the path we are selecting
3301 void selectPath(JTree tree, TreePath path)
3303 if (path != null)
3305 if (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION)
3307 tree.getSelectionModel().clearSelection();
3308 tree.addSelectionPath(path);
3309 tree.setLeadSelectionPath(path);
3311 else if (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION)
3313 // TODO
3315 else
3317 tree.addSelectionPath(path);
3318 tree.setLeadSelectionPath(path);
3319 tree.getSelectionModel().setSelectionMode(
3320 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
3326 * Returns the path from node to the root. Package private for use in inner
3327 * classes.
3329 * @param node
3330 * the node to get the path to
3331 * @param depth
3332 * the depth of the tree to return a path for
3333 * @return an array of tree nodes that represent the path to node.
3335 Object[] getPathToRoot(Object node, int depth)
3337 if (node == null)
3339 if (depth == 0)
3340 return null;
3342 return new Object[depth];
3345 Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node),
3346 depth + 1);
3347 path[path.length - depth - 1] = node;
3348 return path;
3352 * Returns the level of the node in the tree.
3354 * @param node -
3355 * current node
3356 * @return the number of the level
3358 int getLevel(Object node)
3360 int count = -1;
3362 Object current = node;
3364 if (treeModel != null)
3366 Object root = treeModel.getRoot();
3367 if (!tree.isRootVisible() && tree.isExpanded(new TreePath(root)))
3368 count--;
3372 current = getParent(root, current);
3373 count++;
3375 while (current != null);
3377 return count;
3381 * Draws a vertical line using the given graphic context
3383 * @param g
3384 * is the graphic context
3385 * @param c
3386 * is the component the new line will belong to
3387 * @param x
3388 * is the horizonal position
3389 * @param top
3390 * specifies the top of the line
3391 * @param bottom
3392 * specifies the bottom of the line
3394 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
3395 int bottom)
3397 // FIXME: Check if drawing a dashed line or not.
3398 g.setColor(getHashColor());
3399 g.drawLine(x, top, x, bottom);
3403 * Draws a horizontal line using the given graphic context
3405 * @param g
3406 * is the graphic context
3407 * @param c
3408 * is the component the new line will belong to
3409 * @param y
3410 * is the vertical position
3411 * @param left
3412 * specifies the left point of the line
3413 * @param right
3414 * specifies the right point of the line
3416 protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left,
3417 int right)
3419 // FIXME: Check if drawing a dashed line or not.
3420 g.setColor(getHashColor());
3421 g.drawLine(left, y, right, y);
3425 * Draws an icon at around a specific position
3427 * @param c
3428 * is the component the new line will belong to
3429 * @param g
3430 * is the graphic context
3431 * @param icon
3432 * is the icon which will be drawn
3433 * @param x
3434 * is the center position in x-direction
3435 * @param y
3436 * is the center position in y-direction
3438 protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y)
3440 x -= icon.getIconWidth() / 2;
3441 y -= icon.getIconHeight() / 2;
3443 if (x < 0)
3444 x = 0;
3445 if (y < 0)
3446 y = 0;
3448 icon.paintIcon(c, g, x, y);
3452 * Draws a dashed horizontal line.
3454 * @param g -
3455 * the graphics configuration.
3456 * @param y -
3457 * the y location to start drawing at
3458 * @param x1 -
3459 * the x location to start drawing at
3460 * @param x2 -
3461 * the x location to finish drawing at
3463 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)
3465 g.setColor(getHashColor());
3466 for (int i = x1; i < x2; i += 2)
3467 g.drawLine(i, y, i + 1, y);
3471 * Draws a dashed vertical line.
3473 * @param g -
3474 * the graphics configuration.
3475 * @param x -
3476 * the x location to start drawing at
3477 * @param y1 -
3478 * the y location to start drawing at
3479 * @param y2 -
3480 * the y location to finish drawing at
3482 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2)
3484 g.setColor(getHashColor());
3485 for (int i = y1; i < y2; i += 2)
3486 g.drawLine(x, i, x, i + 1);
3490 * Paints the expand (toggle) part of a row. The receiver should NOT modify
3491 * clipBounds, or insets.
3493 * @param g -
3494 * the graphics configuration
3495 * @param clipBounds -
3496 * @param insets -
3497 * @param bounds -
3498 * bounds of expand control
3499 * @param path -
3500 * path to draw control for
3501 * @param row -
3502 * row to draw control for
3503 * @param isExpanded -
3504 * is the row expanded
3505 * @param hasBeenExpanded -
3506 * has the row already been expanded
3507 * @param isLeaf -
3508 * is the path a leaf
3510 protected void paintExpandControl(Graphics g, Rectangle clipBounds,
3511 Insets insets, Rectangle bounds,
3512 TreePath path, int row, boolean isExpanded,
3513 boolean hasBeenExpanded, boolean isLeaf)
3515 if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf))
3517 Icon icon = getCurrentControlIcon(path);
3518 int iconW = icon.getIconWidth();
3519 int x = bounds.x - rightChildIndent + iconW / 2;
3520 if (x + iconW > bounds.x)
3521 x = bounds.x - rightChildIndent - gap;
3522 icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2
3523 - icon.getIconHeight() / 2);
3528 * Paints the horizontal part of the leg. The receiver should NOT modify
3529 * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not
3530 * visible.
3532 * @param g -
3533 * the graphics configuration
3534 * @param clipBounds -
3535 * @param insets -
3536 * @param bounds -
3537 * bounds of the cell
3538 * @param path -
3539 * path to draw leg for
3540 * @param row -
3541 * row to start drawing at
3542 * @param isExpanded -
3543 * is the row expanded
3544 * @param hasBeenExpanded -
3545 * has the row already been expanded
3546 * @param isLeaf -
3547 * is the path a leaf
3549 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
3550 Insets insets, Rectangle bounds,
3551 TreePath path, int row,
3552 boolean isExpanded,
3553 boolean hasBeenExpanded,
3554 boolean isLeaf)
3556 if (row != 0)
3557 paintHorizontalLine(g, tree, bounds.y + bounds.height / 2, bounds.x - gap
3558 - 2, bounds.x);
3562 * Paints the vertical part of the leg. The receiver should NOT modify
3563 * clipBounds, insets.
3565 * @param g -
3566 * the graphics configuration.
3567 * @param clipBounds -
3568 * @param insets -
3569 * @param path -
3570 * the path to draw the vertical part for.
3572 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
3573 Insets insets, TreePath path)
3575 int max = tree.getVisibleRowCount();
3576 for (int i = 0; i < max; i++)
3578 Object curr = path.getPathComponent(i);
3579 TreePath currPath = new TreePath(getPathToRoot(curr, 0));
3580 int numChild = treeModel.getChildCount(curr);
3581 if (numChild > 0 && tree.isExpanded(currPath))
3583 Rectangle bounds = getPathBounds(tree, currPath);
3584 Rectangle lastChildBounds = getPathBounds(
3585 tree,
3586 new TreePath(
3587 getPathToRoot(
3588 treeModel.getChild(
3589 curr,
3590 numChild - 1),
3591 0)));
3592 paintVerticalLine(g, tree, bounds.x + gap + 2, bounds.y
3593 + bounds.height - 2,
3594 lastChildBounds.y + lastChildBounds.height / 2);
3600 * Paints the renderer part of a row. The receiver should NOT modify
3601 * clipBounds, or insets.
3603 * @param g -
3604 * the graphics configuration
3605 * @param clipBounds -
3606 * @param insets -
3607 * @param bounds -
3608 * bounds of expand control
3609 * @param path -
3610 * path to draw control for
3611 * @param row -
3612 * row to draw control for
3613 * @param isExpanded -
3614 * is the row expanded
3615 * @param hasBeenExpanded -
3616 * has the row already been expanded
3617 * @param isLeaf -
3618 * is the path a leaf
3620 protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
3621 Rectangle bounds, TreePath path, int row,
3622 boolean isExpanded, boolean hasBeenExpanded,
3623 boolean isLeaf)
3625 boolean selected = tree.isPathSelected(path);
3626 boolean hasIcons = false;
3627 Object node = path.getLastPathComponent();
3629 if (tree.isVisible(path))
3631 if (!validCachedPreferredSize)
3632 updateCachedPreferredSize();
3634 paintExpandControl(g, clipBounds, insets, bounds, path, row,
3635 isExpanded, hasBeenExpanded, isLeaf);
3637 if (row != 0)
3638 bounds.x += gap;
3639 bounds.width = preferredSize.width + bounds.x;
3640 TreeCellRenderer dtcr = tree.getCellRenderer();
3641 if (dtcr == null)
3642 dtcr = createDefaultCellRenderer();
3644 Component c = dtcr.getTreeCellRendererComponent(tree, node, selected,
3645 isExpanded, isLeaf,
3646 row, tree.hasFocus());
3647 rendererPane.paintComponent(g, c, c.getParent(), bounds);
3652 * Prepares for the UI to uninstall.
3654 protected void prepareForUIUninstall()
3656 // TODO: Implement this properly.
3660 * Returns true if the expand (toggle) control should be drawn for the
3661 * specified row.
3663 * @param path -
3664 * current path to check for.
3665 * @param row -
3666 * current row to check for.
3667 * @param isExpanded -
3668 * true if the path is expanded
3669 * @param hasBeenExpanded -
3670 * true if the path has been expanded already
3671 * @param isLeaf -
3672 * true if the row is a lead
3674 protected boolean shouldPaintExpandControl(TreePath path, int row,
3675 boolean isExpanded,
3676 boolean hasBeenExpanded,
3677 boolean isLeaf)
3679 Object node = path.getLastPathComponent();
3680 return (!isLeaf && getLevel(node) != 0 && hasControlIcons());
3684 * Updates the cached current TreePath of all visible nodes in the tree.
3686 void updateCurrentVisiblePath()
3688 if (treeModel == null)
3689 return;
3691 Object next = treeModel.getRoot();
3692 if (next == null)
3693 return;
3695 TreePath rootPath = new TreePath(next);
3696 Rectangle bounds = getPathBounds(tree, rootPath);
3698 // If root is not a valid size to be visible, or is
3699 // not visible and the tree is expanded, then the next node acts
3700 // as the root
3701 if ((bounds.width == 0 && bounds.height == 0)
3702 || (!isRootVisible() && tree.isExpanded(new TreePath(next))))
3704 next = getNextNode(next);
3705 rootPath = new TreePath(next);
3708 Object root = next;
3709 TreePath current = null;
3710 while (next != null)
3712 if (current == null)
3713 current = rootPath;
3714 else
3715 current = current.pathByAddingChild(next);
3719 TreePath path = new TreePath(getPathToRoot(next, 0));
3720 if ((tree.isVisible(path) && tree.isExpanded(path))
3721 || treeModel.isLeaf(next))
3722 next = getNextNode(next);
3723 else
3725 Object pNext = next;
3726 next = getNextSibling(pNext);
3727 // if no next sibling, check parent's next sibling.
3728 if (next == null)
3730 Object parent = getParent(root, pNext);
3731 while (next == null && parent != null)
3733 next = getNextSibling(parent);
3734 if (next == null)
3735 parent = getParent(root, parent);
3740 while (next != null
3741 && !tree.isVisible(new TreePath(getPathToRoot(next, 0))));
3744 currentVisiblePath = current;
3745 tree.setVisibleRowCount(getRowCount(tree));
3747 if (tree.getSelectionModel() != null && tree.getSelectionCount() == 0
3748 && currentVisiblePath != null)
3749 selectPath(
3750 tree,
3751 new TreePath(
3752 getPathToRoot(
3753 currentVisiblePath.getPathComponent(0),
3754 0)));
3758 * Get next visible node in the currentVisiblePath. Package private for use in
3759 * inner classes.
3761 * @param node
3762 * current node
3763 * @return the next visible node in the JTree. Return null if there are no
3764 * more.
3766 Object getNextVisibleNode(Object node)
3768 if (currentVisiblePath != null)
3770 Object[] nodes = currentVisiblePath.getPath();
3771 int i = 0;
3772 while (i < nodes.length && !node.equals(nodes[i]))
3773 i++;
3774 // return the next node
3775 if (i + 1 < nodes.length)
3776 return nodes[i + 1];
3778 return null;
3782 * Finish the editing session.
3784 void finish()
3786 editingPath = null;
3787 editingRow = -1;
3788 stopEditingInCompleteEditing = false;
3789 isEditing = false;
3790 tree.removeAll();
3791 validCachedPreferredSize = false;
3793 // Repaint the region, where was the editing component.
3794 tree.repaint(editingComponent.getParent().getBounds());
3795 editingComponent = null;
3797 } // BasicTreeUI