Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / javax / swing / plaf / basic / BasicTreeUI.java
blobbe61ccaec22f4718fd18ae27bd30f0903b65df19
1 /* BasicTreeUI.java --
2 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
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 gnu.javax.swing.tree.GnuPath;
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.Dimension;
46 import java.awt.Font;
47 import java.awt.FontMetrics;
48 import java.awt.Graphics;
49 import java.awt.Insets;
50 import java.awt.Label;
51 import java.awt.Point;
52 import java.awt.Rectangle;
53 import java.awt.event.ActionEvent;
54 import java.awt.event.ActionListener;
55 import java.awt.event.ComponentAdapter;
56 import java.awt.event.ComponentEvent;
57 import java.awt.event.ComponentListener;
58 import java.awt.event.FocusEvent;
59 import java.awt.event.FocusListener;
60 import java.awt.event.InputEvent;
61 import java.awt.event.KeyAdapter;
62 import java.awt.event.KeyEvent;
63 import java.awt.event.KeyListener;
64 import java.awt.event.MouseAdapter;
65 import java.awt.event.MouseEvent;
66 import java.awt.event.MouseListener;
67 import java.awt.event.MouseMotionListener;
68 import java.beans.PropertyChangeEvent;
69 import java.beans.PropertyChangeListener;
70 import java.util.Enumeration;
71 import java.util.Hashtable;
73 import javax.swing.AbstractAction;
74 import javax.swing.Action;
75 import javax.swing.ActionMap;
76 import javax.swing.CellRendererPane;
77 import javax.swing.Icon;
78 import javax.swing.InputMap;
79 import javax.swing.JComponent;
80 import javax.swing.JScrollBar;
81 import javax.swing.JScrollPane;
82 import javax.swing.JTree;
83 import javax.swing.KeyStroke;
84 import javax.swing.LookAndFeel;
85 import javax.swing.SwingUtilities;
86 import javax.swing.Timer;
87 import javax.swing.UIManager;
88 import javax.swing.event.CellEditorListener;
89 import javax.swing.event.ChangeEvent;
90 import javax.swing.event.MouseInputListener;
91 import javax.swing.event.TreeExpansionEvent;
92 import javax.swing.event.TreeExpansionListener;
93 import javax.swing.event.TreeModelEvent;
94 import javax.swing.event.TreeModelListener;
95 import javax.swing.event.TreeSelectionEvent;
96 import javax.swing.event.TreeSelectionListener;
97 import javax.swing.plaf.ActionMapUIResource;
98 import javax.swing.plaf.ComponentUI;
99 import javax.swing.plaf.InputMapUIResource;
100 import javax.swing.plaf.TreeUI;
101 import javax.swing.tree.AbstractLayoutCache;
102 import javax.swing.tree.DefaultTreeCellEditor;
103 import javax.swing.tree.DefaultTreeCellRenderer;
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;
110 import javax.swing.tree.VariableHeightLayoutCache;
113 * A delegate providing the user interface for <code>JTree</code> according to
114 * the Basic look and feel.
116 * @see javax.swing.JTree
117 * @author Lillian Angel (langel@redhat.com)
118 * @author Sascha Brawer (brawer@dandelis.ch)
119 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
121 public class BasicTreeUI
122 extends TreeUI
125 * The tree cell editing may be started by the single mouse click on the
126 * selected cell. To separate it from the double mouse click, the editing
127 * session starts after this time (in ms) after that single click, and only no
128 * other clicks were performed during that time.
130 static int WAIT_TILL_EDITING = 900;
132 /** Collapse Icon for the tree. */
133 protected transient Icon collapsedIcon;
135 /** Expanded Icon for the tree. */
136 protected transient Icon expandedIcon;
138 /** Distance between left margin and where vertical dashes will be drawn. */
139 protected int leftChildIndent;
142 * Distance between leftChildIndent and where cell contents will be drawn.
144 protected int rightChildIndent;
147 * Total fistance that will be indented. The sum of leftChildIndent and
148 * rightChildIndent .
150 protected int totalChildIndent;
152 /** Index of the row that was last selected. */
153 protected int lastSelectedRow;
155 /** Component that we're going to be drawing onto. */
156 protected JTree tree;
158 /** Renderer that is being used to do the actual cell drawing. */
159 protected transient TreeCellRenderer currentCellRenderer;
162 * Set to true if the renderer that is currently in the tree was created by
163 * this instance.
165 protected boolean createdRenderer;
167 /** Editor for the tree. */
168 protected transient TreeCellEditor cellEditor;
171 * Set to true if editor that is currently in the tree was created by this
172 * instance.
174 protected boolean createdCellEditor;
177 * Set to false when editing and shouldSelectCall() returns true meaning the
178 * node should be selected before editing, used in completeEditing.
180 protected boolean stopEditingInCompleteEditing;
182 /** Used to paint the TreeCellRenderer. */
183 protected CellRendererPane rendererPane;
185 /** Size needed to completely display all the nodes. */
186 protected Dimension preferredSize;
188 /** Minimum size needed to completely display all the nodes. */
189 protected Dimension preferredMinSize;
191 /** Is the preferredSize valid? */
192 protected boolean validCachedPreferredSize;
194 /** Object responsible for handling sizing and expanded issues. */
195 protected AbstractLayoutCache treeState;
197 /** Used for minimizing the drawing of vertical lines. */
198 protected Hashtable drawingCache;
201 * True if doing optimizations for a largeModel. Subclasses that don't support
202 * this may wish to override createLayoutCache to not return a
203 * FixedHeightLayoutCache instance.
205 protected boolean largeModel;
207 /** Responsible for telling the TreeState the size needed for a node. */
208 protected AbstractLayoutCache.NodeDimensions nodeDimensions;
210 /** Used to determine what to display. */
211 protected TreeModel treeModel;
213 /** Model maintaining the selection. */
214 protected TreeSelectionModel treeSelectionModel;
217 * How much the depth should be offset to properly calculate x locations. This
218 * is based on whether or not the root is visible, and if the root handles are
219 * visible.
221 protected int depthOffset;
224 * When editing, this will be the Component that is doing the actual editing.
226 protected Component editingComponent;
228 /** Path that is being edited. */
229 protected TreePath editingPath;
232 * Row that is being edited. Should only be referenced if editingComponent is
233 * null.
235 protected int editingRow;
237 /** Set to true if the editor has a different size than the renderer. */
238 protected boolean editorHasDifferentSize;
240 /** The action bound to KeyStrokes. */
241 TreeAction action;
243 /** Boolean to keep track of editing. */
244 boolean isEditing;
246 /** The current path of the visible nodes in the tree. */
247 TreePath currentVisiblePath;
249 /** The gap between the icon and text. */
250 int gap = 4;
252 /** The max height of the nodes in the tree. */
253 int maxHeight = 0;
255 /** Listeners */
256 PropertyChangeListener propertyChangeListener;
258 FocusListener focusListener;
260 TreeSelectionListener treeSelectionListener;
262 MouseListener mouseListener;
264 KeyListener keyListener;
266 PropertyChangeListener selectionModelPropertyChangeListener;
268 ComponentListener componentListener;
270 CellEditorListener cellEditorListener;
272 TreeExpansionListener treeExpansionListener;
274 TreeModelListener treeModelListener;
277 * This timer fires the editing action after about 1200 ms if not reset during
278 * that time. It handles the editing start with the single mouse click (and
279 * not the double mouse click) on the selected tree node.
281 Timer startEditTimer;
284 * The special value of the mouse event is sent indicating that this is not
285 * just the mouse click, but the mouse click on the selected node. Sending
286 * such event forces to start the cell editing session.
288 static final MouseEvent EDIT = new MouseEvent(new Label(), 7, 7, 7, 7, 7, 7,
289 false);
292 * Creates a new BasicTreeUI object.
294 public BasicTreeUI()
296 validCachedPreferredSize = false;
297 drawingCache = new Hashtable();
298 nodeDimensions = createNodeDimensions();
299 configureLayoutCache();
301 propertyChangeListener = createPropertyChangeListener();
302 focusListener = createFocusListener();
303 treeSelectionListener = createTreeSelectionListener();
304 mouseListener = createMouseListener();
305 keyListener = createKeyListener();
306 selectionModelPropertyChangeListener = createSelectionModelPropertyChangeListener();
307 componentListener = createComponentListener();
308 cellEditorListener = createCellEditorListener();
309 treeExpansionListener = createTreeExpansionListener();
310 treeModelListener = createTreeModelListener();
312 editingRow = - 1;
313 lastSelectedRow = - 1;
317 * Returns an instance of the UI delegate for the specified component.
319 * @param c the <code>JComponent</code> for which we need a UI delegate for.
320 * @return the <code>ComponentUI</code> for c.
322 public static ComponentUI createUI(JComponent c)
324 return new BasicTreeUI();
328 * Returns the Hash color.
330 * @return the <code>Color</code> of the Hash.
332 protected Color getHashColor()
334 return UIManager.getColor("Tree.hash");
338 * Sets the Hash color.
340 * @param color the <code>Color</code> to set the Hash to.
342 protected void setHashColor(Color color)
344 // FIXME: Putting something in the UIDefaults map is certainly wrong.
345 UIManager.put("Tree.hash", color);
349 * Sets the left child's indent value.
351 * @param newAmount 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 is the new indent value for the right child.
373 public void setRightChildIndent(int newAmount)
375 rightChildIndent = newAmount;
379 * Returns the indent value for the right child.
381 * @return the indent value for the right child.
383 public int getRightChildIndent()
385 return rightChildIndent;
389 * Sets the expanded icon.
391 * @param newG is the new expanded icon.
393 public void setExpandedIcon(Icon newG)
395 expandedIcon = newG;
399 * Returns the current expanded icon.
401 * @return the current expanded icon.
403 public Icon getExpandedIcon()
405 return expandedIcon;
409 * Sets the collapsed icon.
411 * @param newG is the new collapsed icon.
413 public void setCollapsedIcon(Icon newG)
415 collapsedIcon = newG;
419 * Returns the current collapsed icon.
421 * @return the current collapsed icon.
423 public Icon getCollapsedIcon()
425 return collapsedIcon;
429 * Updates the componentListener, if necessary.
431 * @param largeModel sets this.largeModel to it.
433 protected void setLargeModel(boolean largeModel)
435 if (largeModel != this.largeModel)
437 tree.removeComponentListener(componentListener);
438 this.largeModel = largeModel;
439 tree.addComponentListener(componentListener);
444 * Returns true if largeModel is set
446 * @return true if largeModel is set, otherwise false.
448 protected boolean isLargeModel()
450 return largeModel;
454 * Sets the row height.
456 * @param rowHeight is the height to set this.rowHeight to.
458 protected void setRowHeight(int rowHeight)
460 if (rowHeight == 0)
461 rowHeight = getMaxHeight(tree);
462 treeState.setRowHeight(rowHeight);
466 * Returns the current row height.
468 * @return current row height.
470 protected int getRowHeight()
472 return tree.getRowHeight();
476 * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
477 * <code>updateRenderer</code>.
479 * @param tcr is the new TreeCellRenderer.
481 protected void setCellRenderer(TreeCellRenderer tcr)
483 currentCellRenderer = tcr;
484 updateRenderer();
488 * Return currentCellRenderer, which will either be the trees renderer, or
489 * defaultCellRenderer, which ever was not null.
491 * @return the current Cell Renderer
493 protected TreeCellRenderer getCellRenderer()
495 if (currentCellRenderer != null)
496 return currentCellRenderer;
498 return createDefaultCellRenderer();
502 * Sets the tree's model.
504 * @param model to set the treeModel to.
506 protected void setModel(TreeModel model)
508 tree.setModel(model);
509 treeModel = tree.getModel();
510 treeState.setModel(treeModel);
514 * Returns the tree's model
516 * @return treeModel
518 protected TreeModel getModel()
520 return treeModel;
524 * Sets the root to being visible.
526 * @param newValue sets the visibility of the root
528 protected void setRootVisible(boolean newValue)
530 tree.setRootVisible(newValue);
534 * Returns true if the root is visible.
536 * @return true if the root is visible.
538 protected boolean isRootVisible()
540 return tree.isRootVisible();
544 * Determines whether the node handles are to be displayed.
546 * @param newValue sets whether or not node handles should be displayed.
548 protected void setShowsRootHandles(boolean newValue)
550 tree.setShowsRootHandles(newValue);
554 * Returns true if the node handles are to be displayed.
556 * @return true if the node handles are to be displayed.
558 protected boolean getShowsRootHandles()
560 return tree.getShowsRootHandles();
564 * Sets the cell editor.
566 * @param editor to set the cellEditor to.
568 protected void setCellEditor(TreeCellEditor editor)
570 cellEditor = editor;
571 createdCellEditor = true;
575 * Returns the <code>TreeCellEditor</code> for this tree.
577 * @return the cellEditor for this tree.
579 protected TreeCellEditor getCellEditor()
581 return cellEditor;
585 * Configures the receiver to allow, or not allow, editing.
587 * @param newValue sets the receiver to allow editing if true.
589 protected void setEditable(boolean newValue)
591 tree.setEditable(newValue);
595 * Returns true if the receiver allows editing.
597 * @return true if the receiver allows editing.
599 protected boolean isEditable()
601 return tree.isEditable();
605 * Resets the selection model. The appropriate listeners are installed on the
606 * model.
608 * @param newLSM resets the selection model.
610 protected void setSelectionModel(TreeSelectionModel newLSM)
612 if (newLSM != null)
614 treeSelectionModel = newLSM;
615 tree.setSelectionModel(treeSelectionModel);
620 * Returns the current selection model.
622 * @return the current selection model.
624 protected TreeSelectionModel getSelectionModel()
626 return treeSelectionModel;
630 * Returns the Rectangle enclosing the label portion that the last item in
631 * path will be drawn to. Will return null if any component in path is
632 * currently valid.
634 * @param tree is the current tree the path will be drawn to.
635 * @param path is the current path the tree to draw to.
636 * @return the Rectangle enclosing the label portion that the last item in the
637 * path will be drawn to.
639 public Rectangle getPathBounds(JTree tree, TreePath path)
641 return treeState.getBounds(path, new Rectangle());
645 * Returns the max height of all the nodes in the tree.
647 * @param tree - the current tree
648 * @return the max height.
650 int getMaxHeight(JTree tree)
652 if (maxHeight != 0)
653 return maxHeight;
655 Icon e = UIManager.getIcon("Tree.openIcon");
656 Icon c = UIManager.getIcon("Tree.closedIcon");
657 Icon l = UIManager.getIcon("Tree.leafIcon");
658 int rc = getRowCount(tree);
659 int iconHeight = 0;
661 for (int row = 0; row < rc; row++)
663 if (isLeaf(row))
664 iconHeight = l.getIconHeight();
665 else if (tree.isExpanded(row))
666 iconHeight = e.getIconHeight();
667 else
668 iconHeight = c.getIconHeight();
670 maxHeight = Math.max(maxHeight, iconHeight + gap);
673 treeState.setRowHeight(maxHeight);
674 return maxHeight;
678 * Returns the path for passed in row. If row is not visible null is returned.
680 * @param tree is the current tree to return path for.
681 * @param row is the row number of the row to return.
682 * @return the path for passed in row. If row is not visible null is returned.
684 public TreePath getPathForRow(JTree tree, int row)
686 return treeState.getPathForRow(row);
690 * Returns the row that the last item identified in path is visible at. Will
691 * return -1 if any of the elments in the path are not currently visible.
693 * @param tree is the current tree to return the row for.
694 * @param path is the path used to find the row.
695 * @return the row that the last item identified in path is visible at. Will
696 * return -1 if any of the elments in the path are not currently
697 * visible.
699 public int getRowForPath(JTree tree, TreePath path)
701 return treeState.getRowForPath(path);
705 * Returns the number of rows that are being displayed.
707 * @param tree is the current tree to return the number of rows for.
708 * @return the number of rows being displayed.
710 public int getRowCount(JTree tree)
712 return treeState.getRowCount();
716 * Returns the path to the node that is closest to x,y. If there is nothing
717 * currently visible this will return null, otherwise it'll always return a
718 * valid path. If you need to test if the returned object is exactly at x,y
719 * you should get the bounds for the returned path and test x,y against that.
721 * @param tree the tree to search for the closest path
722 * @param x is the x coordinate of the location to search
723 * @param y is the y coordinate of the location to search
724 * @return the tree path closes to x,y.
726 public TreePath getClosestPathForLocation(JTree tree, int x, int y)
728 return treeState.getPathClosestTo(x, y);
732 * Returns true if the tree is being edited. The item that is being edited can
733 * be returned by getEditingPath().
735 * @param tree is the tree to check for editing.
736 * @return true if the tree is being edited.
738 public boolean isEditing(JTree tree)
740 return isEditing;
744 * Stops the current editing session. This has no effect if the tree is not
745 * being edited. Returns true if the editor allows the editing session to
746 * stop.
748 * @param tree is the tree to stop the editing on
749 * @return true if the editor allows the editing session to stop.
751 public boolean stopEditing(JTree tree)
753 if (isEditing(tree))
755 completeEditing(false, false, true);
756 finish();
758 return ! isEditing(tree);
762 * Cancels the current editing session.
764 * @param tree is the tree to cancel the editing session on.
766 public void cancelEditing(JTree tree)
768 // There is no need to send the cancel message to the editor,
769 // as the cancellation event itself arrives from it. This would
770 // only be necessary when cancelling the editing programatically.
771 completeEditing(false, false, false);
772 finish();
776 * Selects the last item in path and tries to edit it. Editing will fail if
777 * the CellEditor won't allow it for the selected item.
779 * @param tree is the tree to edit on.
780 * @param path is the path in tree to edit on.
782 public void startEditingAtPath(JTree tree, TreePath path)
784 startEditing(path, null);
788 * Returns the path to the element that is being editted.
790 * @param tree is the tree to get the editing path from.
791 * @return the path that is being edited.
793 public TreePath getEditingPath(JTree tree)
795 return editingPath;
799 * Invoked after the tree instance variable has been set, but before any
800 * default/listeners have been installed.
802 protected void prepareForUIInstall()
804 // TODO: Implement this properly.
808 * Invoked from installUI after all the defaults/listeners have been
809 * installed.
811 protected void completeUIInstall()
813 // TODO: Implement this properly.
817 * Invoked from uninstallUI after all the defaults/listeners have been
818 * uninstalled.
820 protected void completeUIUninstall()
822 // TODO: Implement this properly.
826 * Installs the subcomponents of the tree, which is the renderer pane.
828 protected void installComponents()
830 currentCellRenderer = createDefaultCellRenderer();
831 rendererPane = createCellRendererPane();
832 createdRenderer = true;
833 setCellRenderer(currentCellRenderer);
837 * Creates an instance of NodeDimensions that is able to determine the size of
838 * a given node in the tree. The node dimensions must be created before
839 * configuring the layout cache.
841 * @return the NodeDimensions of a given node in the tree
843 protected AbstractLayoutCache.NodeDimensions createNodeDimensions()
845 return new NodeDimensionsHandler();
849 * Creates a listener that is reponsible for the updates the UI based on how
850 * the tree changes.
852 * @return the PropertyChangeListener that is reposnsible for the updates
854 protected PropertyChangeListener createPropertyChangeListener()
856 return new PropertyChangeHandler();
860 * Creates the listener responsible for updating the selection based on mouse
861 * events.
863 * @return the MouseListener responsible for updating.
865 protected MouseListener createMouseListener()
867 return new MouseHandler();
871 * Creates the listener that is responsible for updating the display when
872 * focus is lost/grained.
874 * @return the FocusListener responsible for updating.
876 protected FocusListener createFocusListener()
878 return new FocusHandler();
882 * Creates the listener reponsible for getting key events from the tree.
884 * @return the KeyListener responsible for getting key events.
886 protected KeyListener createKeyListener()
888 return new KeyHandler();
892 * Creates the listener responsible for getting property change events from
893 * the selection model.
895 * @returns the PropertyChangeListener reponsible for getting property change
896 * events from the selection model.
898 protected PropertyChangeListener createSelectionModelPropertyChangeListener()
900 return new SelectionModelPropertyChangeHandler();
904 * Creates the listener that updates the display based on selection change
905 * methods.
907 * @return the TreeSelectionListener responsible for updating.
909 protected TreeSelectionListener createTreeSelectionListener()
911 return new TreeSelectionHandler();
915 * Creates a listener to handle events from the current editor
917 * @return the CellEditorListener that handles events from the current editor
919 protected CellEditorListener createCellEditorListener()
921 return new CellEditorHandler();
925 * Creates and returns a new ComponentHandler. This is used for the large
926 * model to mark the validCachedPreferredSize as invalid when the component
927 * moves.
929 * @return a new ComponentHandler.
931 protected ComponentListener createComponentListener()
933 return new ComponentHandler();
937 * Creates and returns the object responsible for updating the treestate when
938 * a nodes expanded state changes.
940 * @return the TreeExpansionListener responsible for updating the treestate
942 protected TreeExpansionListener createTreeExpansionListener()
944 return new TreeExpansionHandler();
948 * Creates the object responsible for managing what is expanded, as well as
949 * the size of nodes.
951 * @return the object responsible for managing what is expanded.
953 protected AbstractLayoutCache createLayoutCache()
955 return new VariableHeightLayoutCache();
959 * Returns the renderer pane that renderer components are placed in.
961 * @return the rendererpane that render components are placed in.
963 protected CellRendererPane createCellRendererPane()
965 return new CellRendererPane();
969 * Creates a default cell editor.
971 * @return the default cell editor.
973 protected TreeCellEditor createDefaultCellEditor()
975 if (currentCellRenderer != null)
976 return new DefaultTreeCellEditor(
977 tree,
978 (DefaultTreeCellRenderer) currentCellRenderer,
979 cellEditor);
980 return new DefaultTreeCellEditor(
981 tree,
982 (DefaultTreeCellRenderer) createDefaultCellRenderer(),
983 cellEditor);
987 * Returns the default cell renderer that is used to do the stamping of each
988 * node.
990 * @return the default cell renderer that is used to do the stamping of each
991 * node.
993 protected TreeCellRenderer createDefaultCellRenderer()
995 return new DefaultTreeCellRenderer();
999 * Returns a listener that can update the tree when the model changes.
1001 * @return a listener that can update the tree when the model changes.
1003 protected TreeModelListener createTreeModelListener()
1005 return new TreeModelHandler();
1009 * Uninstall all registered listeners
1011 protected void uninstallListeners()
1013 tree.removePropertyChangeListener(propertyChangeListener);
1014 tree.removeFocusListener(focusListener);
1015 tree.removeTreeSelectionListener(treeSelectionListener);
1016 tree.removeMouseListener(mouseListener);
1017 tree.removeKeyListener(keyListener);
1018 tree.removePropertyChangeListener(selectionModelPropertyChangeListener);
1019 tree.removeComponentListener(componentListener);
1020 tree.removeTreeExpansionListener(treeExpansionListener);
1022 TreeCellEditor tce = tree.getCellEditor();
1023 if (tce != null)
1024 tce.removeCellEditorListener(cellEditorListener);
1025 if (treeModel != null)
1026 treeModel.removeTreeModelListener(treeModelListener);
1030 * Uninstall all keyboard actions.
1032 protected void uninstallKeyboardActions()
1034 action = null;
1035 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1036 null);
1037 tree.getActionMap().setParent(null);
1041 * Uninstall the rendererPane.
1043 protected void uninstallComponents()
1045 currentCellRenderer = null;
1046 rendererPane = null;
1047 createdRenderer = false;
1048 setCellRenderer(currentCellRenderer);
1052 * The vertical element of legs between nodes starts at the bottom of the
1053 * parent node by default. This method makes the leg start below that.
1055 * @return the vertical leg buffer
1057 protected int getVerticalLegBuffer()
1059 return getRowHeight() / 2;
1063 * The horizontal element of legs between nodes starts at the right of the
1064 * left-hand side of the child node by default. This method makes the leg end
1065 * before that.
1067 * @return the horizontal leg buffer
1069 protected int getHorizontalLegBuffer()
1071 return rightChildIndent / 2;
1075 * Make all the nodes that are expanded in JTree expanded in LayoutCache. This
1076 * invokes updateExpandedDescendants with the root path.
1078 protected void updateLayoutCacheExpandedNodes()
1080 if (treeModel != null)
1081 updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1085 * Updates the expanded state of all the descendants of the <code>path</code>
1086 * by getting the expanded descendants from the tree and forwarding to the
1087 * tree state.
1089 * @param path the path used to update the expanded states
1091 protected void updateExpandedDescendants(TreePath path)
1093 Enumeration expanded = tree.getExpandedDescendants(path);
1094 while (expanded.hasMoreElements())
1095 treeState.setExpandedState(((TreePath) expanded.nextElement()), true);
1099 * Returns a path to the last child of <code>parent</code>
1101 * @param parent is the topmost path to specified
1102 * @return a path to the last child of parent
1104 protected TreePath getLastChildPath(TreePath parent)
1106 return ((TreePath) parent.getLastPathComponent());
1110 * Updates how much each depth should be offset by.
1112 protected void updateDepthOffset()
1114 depthOffset += getVerticalLegBuffer();
1118 * Updates the cellEditor based on editability of the JTree that we're
1119 * contained in. If the tree is editable but doesn't have a cellEditor, a
1120 * basic one will be used.
1122 protected void updateCellEditor()
1124 if (tree.isEditable() && cellEditor == null)
1125 setCellEditor(createDefaultCellEditor());
1126 createdCellEditor = true;
1130 * Messaged from the tree we're in when the renderer has changed.
1132 protected void updateRenderer()
1134 if (tree != null)
1136 if (tree.getCellRenderer() == null)
1138 if (currentCellRenderer == null)
1139 currentCellRenderer = createDefaultCellRenderer();
1140 tree.setCellRenderer(currentCellRenderer);
1146 * Resets the treeState instance based on the tree we're providing the look
1147 * and feel for. The node dimensions handler is required and must be created
1148 * in advance.
1150 protected void configureLayoutCache()
1152 treeState = createLayoutCache();
1153 treeState.setNodeDimensions(nodeDimensions);
1157 * Marks the cached size as being invalid, and messages the tree with
1158 * <code>treeDidChange</code>.
1160 protected void updateSize()
1162 preferredSize = null;
1163 updateCachedPreferredSize();
1164 tree.treeDidChange();
1168 * Updates the <code>preferredSize</code> instance variable, which is
1169 * returned from <code>getPreferredSize()</code>.
1171 protected void updateCachedPreferredSize()
1173 validCachedPreferredSize = false;
1177 * Messaged from the VisibleTreeNode after it has been expanded.
1179 * @param path is the path that has been expanded.
1181 protected void pathWasExpanded(TreePath path)
1183 validCachedPreferredSize = false;
1184 treeState.setExpandedState(path, true);
1185 tree.repaint();
1189 * Messaged from the VisibleTreeNode after it has collapsed
1191 protected void pathWasCollapsed(TreePath path)
1193 validCachedPreferredSize = false;
1194 treeState.setExpandedState(path, false);
1195 tree.repaint();
1199 * Install all defaults for the tree.
1201 protected void installDefaults()
1203 LookAndFeel.installColorsAndFont(tree, "Tree.background",
1204 "Tree.foreground", "Tree.font");
1205 tree.setOpaque(true);
1207 rightChildIndent = UIManager.getInt("Tree.rightChildIndent");
1208 leftChildIndent = UIManager.getInt("Tree.leftChildIndent");
1209 setRowHeight(UIManager.getInt("Tree.rowHeight"));
1210 tree.setRowHeight(getRowHeight());
1211 tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand"));
1212 setExpandedIcon(UIManager.getIcon("Tree.expandedIcon"));
1213 setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon"));
1217 * Install all keyboard actions for this
1219 protected void installKeyboardActions()
1221 InputMap focusInputMap = (InputMap) UIManager.get("Tree.focusInputMap");
1222 InputMapUIResource parentInputMap = new InputMapUIResource();
1223 ActionMap parentActionMap = new ActionMapUIResource();
1224 action = new TreeAction();
1225 Object keys[] = focusInputMap.allKeys();
1227 for (int i = 0; i < keys.length; i++)
1229 parentInputMap.put(
1230 KeyStroke.getKeyStroke(
1231 ((KeyStroke) keys[i]).getKeyCode(),
1232 convertModifiers(((KeyStroke) keys[i]).getModifiers())),
1233 (String) focusInputMap.get((KeyStroke) keys[i]));
1235 parentInputMap.put(
1236 KeyStroke.getKeyStroke(
1237 ((KeyStroke) keys[i]).getKeyCode(),
1238 ((KeyStroke) keys[i]).getModifiers()),
1239 (String) focusInputMap.get((KeyStroke) keys[i]));
1241 parentActionMap.put(
1242 (String) focusInputMap.get((KeyStroke) keys[i]),
1243 new ActionListenerProxy(
1244 action,
1245 (String) focusInputMap.get((KeyStroke) keys[i])));
1249 parentInputMap.setParent(tree.getInputMap(
1250 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent());
1251 parentActionMap.setParent(tree.getActionMap().getParent());
1252 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1253 parentInputMap);
1254 tree.getActionMap().setParent(parentActionMap);
1258 * Converts the modifiers.
1260 * @param mod - modifier to convert
1261 * @returns the new modifier
1263 private int convertModifiers(int mod)
1265 if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
1267 mod |= KeyEvent.SHIFT_MASK;
1268 mod &= ~ KeyEvent.SHIFT_DOWN_MASK;
1270 if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
1272 mod |= KeyEvent.CTRL_MASK;
1273 mod &= ~ KeyEvent.CTRL_DOWN_MASK;
1275 if ((mod & KeyEvent.META_DOWN_MASK) != 0)
1277 mod |= KeyEvent.META_MASK;
1278 mod &= ~ KeyEvent.META_DOWN_MASK;
1280 if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
1282 mod |= KeyEvent.ALT_MASK;
1283 mod &= ~ KeyEvent.ALT_DOWN_MASK;
1285 if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
1287 mod |= KeyEvent.ALT_GRAPH_MASK;
1288 mod &= ~ KeyEvent.ALT_GRAPH_DOWN_MASK;
1290 return mod;
1294 * Install all listeners for this
1296 protected void installListeners()
1298 tree.addPropertyChangeListener(propertyChangeListener);
1299 tree.addFocusListener(focusListener);
1300 tree.addTreeSelectionListener(treeSelectionListener);
1301 tree.addMouseListener(mouseListener);
1302 tree.addKeyListener(keyListener);
1303 tree.addPropertyChangeListener(selectionModelPropertyChangeListener);
1304 tree.addComponentListener(componentListener);
1305 tree.addTreeExpansionListener(treeExpansionListener);
1306 if (treeModel != null)
1307 treeModel.addTreeModelListener(treeModelListener);
1311 * Install the UI for the component
1313 * @param c the component to install UI for
1315 public void installUI(JComponent c)
1317 tree = (JTree) c;
1318 treeModel = tree.getModel();
1320 prepareForUIInstall();
1321 super.installUI(c);
1322 installDefaults();
1323 installComponents();
1324 installKeyboardActions();
1325 installListeners();
1327 setCellEditor(createDefaultCellEditor());
1328 createdCellEditor = true;
1329 isEditing = false;
1331 setModel(tree.getModel());
1332 treeSelectionModel = tree.getSelectionModel();
1333 setRootVisible(tree.isRootVisible());
1334 treeState.setRootVisible(tree.isRootVisible());
1336 completeUIInstall();
1340 * Uninstall the defaults for the tree
1342 protected void uninstallDefaults()
1344 tree.setFont(null);
1345 tree.setForeground(null);
1346 tree.setBackground(null);
1350 * Uninstall the UI for the component
1352 * @param c the component to uninstall UI for
1354 public void uninstallUI(JComponent c)
1356 prepareForUIUninstall();
1357 uninstallDefaults();
1358 uninstallKeyboardActions();
1359 uninstallListeners();
1360 tree = null;
1361 uninstallComponents();
1362 completeUIUninstall();
1366 * Paints the specified component appropriate for the look and feel. This
1367 * method is invoked from the ComponentUI.update method when the specified
1368 * component is being painted. Subclasses should override this method and use
1369 * the specified Graphics object to render the content of the component.
1371 * @param g the Graphics context in which to paint
1372 * @param c the component being painted; this argument is often ignored, but
1373 * might be used if the UI object is stateless and shared by multiple
1374 * components
1376 public void paint(Graphics g, JComponent c)
1378 JTree tree = (JTree) c;
1380 int rows = treeState.getRowCount();
1382 if (rows == 0)
1383 // There is nothing to do if the tree is empty.
1384 return;
1386 Rectangle clip = g.getClipBounds();
1388 Insets insets = tree.getInsets();
1390 if (clip != null && treeModel != null)
1392 int startIndex = tree.getClosestRowForLocation(clip.x, clip.y);
1393 int endIndex = tree.getClosestRowForLocation(clip.x + clip.width,
1394 clip.y + clip.height);
1396 // Also paint dashes to the invisible nodes below.
1397 // These should be painted first, otherwise they may cover
1398 // the control icons.
1399 if (endIndex < rows)
1400 for (int i = endIndex + 1; i < rows; i++)
1402 TreePath path = treeState.getPathForRow(i);
1403 if (isLastChild(path))
1404 paintVerticalPartOfLeg(g, clip, insets, path);
1407 // The two loops are required to ensure that the lines are not
1408 // painted over the other tree components.
1410 int n = endIndex - startIndex + 1;
1411 Rectangle[] bounds = new Rectangle[n];
1412 boolean[] isLeaf = new boolean[n];
1413 boolean[] isExpanded = new boolean[n];
1414 TreePath[] path = new TreePath[n];
1415 int k;
1417 k = 0;
1418 for (int i = startIndex; i <= endIndex; i++, k++)
1420 path[k] = treeState.getPathForRow(i);
1421 isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent());
1422 isExpanded[k] = tree.isExpanded(path[k]);
1423 bounds[k] = getPathBounds(tree, path[k]);
1425 paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k], i,
1426 isExpanded[k], false, isLeaf[k]);
1427 if (isLastChild(path[k]))
1428 paintVerticalPartOfLeg(g, clip, insets, path[k]);
1431 k = 0;
1432 for (int i = startIndex; i <= endIndex; i++, k++)
1434 paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k],
1435 false, isLeaf[k]);
1441 * Check if the path is referring to the last child of some parent.
1443 private boolean isLastChild(TreePath path)
1445 if (path instanceof GnuPath)
1447 // Except the seldom case when the layout cache is changed, this
1448 // optimized code will be executed.
1449 return ((GnuPath) path).isLastChild;
1451 else
1453 // Non optimized general case.
1454 TreePath parent = path.getParentPath();
1455 if (parent == null)
1456 return false;
1457 int childCount = treeState.getVisibleChildCount(parent);
1458 int p = treeModel.getIndexOfChild(parent, path.getLastPathComponent());
1459 return p == childCount - 1;
1464 * Ensures that the rows identified by beginRow through endRow are visible.
1466 * @param beginRow is the first row
1467 * @param endRow is the last row
1469 protected void ensureRowsAreVisible(int beginRow, int endRow)
1471 if (beginRow < endRow)
1473 int temp = endRow;
1474 endRow = beginRow;
1475 beginRow = temp;
1478 for (int i = beginRow; i < endRow; i++)
1480 TreePath path = getPathForRow(tree, i);
1481 if (! tree.isVisible(path))
1482 tree.makeVisible(path);
1487 * Sets the preferred minimum size.
1489 * @param newSize is the new preferred minimum size.
1491 public void setPreferredMinSize(Dimension newSize)
1493 preferredMinSize = newSize;
1497 * Gets the preferred minimum size.
1499 * @returns the preferred minimum size.
1501 public Dimension getPreferredMinSize()
1503 if (preferredMinSize == null)
1504 return getPreferredSize(tree);
1505 else
1506 return preferredMinSize;
1510 * Returns the preferred size to properly display the tree, this is a cover
1511 * method for getPreferredSize(c, false).
1513 * @param c the component whose preferred size is being queried; this argument
1514 * is often ignored but might be used if the UI object is stateless
1515 * and shared by multiple components
1516 * @return the preferred size
1518 public Dimension getPreferredSize(JComponent c)
1520 return getPreferredSize(c, false);
1524 * Returns the preferred size to represent the tree in c. If checkConsistancy
1525 * is true, checkConsistancy is messaged first.
1527 * @param c the component whose preferred size is being queried.
1528 * @param checkConsistancy if true must check consistancy
1529 * @return the preferred size
1531 public Dimension getPreferredSize(JComponent c, boolean checkConsistancy)
1533 if (! validCachedPreferredSize)
1535 Rectangle size = tree.getBounds();
1536 // Add the scrollbar dimensions to the preferred size.
1537 preferredSize = new Dimension(treeState.getPreferredWidth(size),
1538 treeState.getPreferredHeight());
1539 validCachedPreferredSize = true;
1541 return preferredSize;
1545 * Returns the minimum size for this component. Which will be the min
1546 * preferred size or (0,0).
1548 * @param c the component whose min size is being queried.
1549 * @returns the preferred size or null
1551 public Dimension getMinimumSize(JComponent c)
1553 return preferredMinSize = getPreferredSize(c);
1557 * Returns the maximum size for the component, which will be the preferred
1558 * size if the instance is currently in JTree or (0,0).
1560 * @param c the component whose preferred size is being queried
1561 * @return the max size or null
1563 public Dimension getMaximumSize(JComponent c)
1565 return getPreferredSize(c);
1569 * Messages to stop the editing session. If the UI the receiver is providing
1570 * the look and feel for returns true from
1571 * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked
1572 * on the current editor. Then completeEditing will be messaged with false,
1573 * true, false to cancel any lingering editing.
1575 protected void completeEditing()
1577 completeEditing(false, true, false);
1581 * Stops the editing session. If messageStop is true, the editor is messaged
1582 * with stopEditing, if messageCancel is true the editor is messaged with
1583 * cancelEditing. If messageTree is true, the treeModel is messaged with
1584 * valueForPathChanged.
1586 * @param messageStop message to stop editing
1587 * @param messageCancel message to cancel editing
1588 * @param messageTree message to treeModel
1590 protected void completeEditing(boolean messageStop, boolean messageCancel,
1591 boolean messageTree)
1593 if (messageStop)
1595 getCellEditor().stopCellEditing();
1596 stopEditingInCompleteEditing = true;
1599 if (messageCancel)
1601 getCellEditor().cancelCellEditing();
1602 stopEditingInCompleteEditing = true;
1605 if (messageTree)
1607 TreeCellEditor editor = getCellEditor();
1608 if (editor != null)
1610 Object value = editor.getCellEditorValue();
1611 treeModel.valueForPathChanged(tree.getLeadSelectionPath(), value);
1617 * Will start editing for node if there is a cellEditor and shouldSelectCall
1618 * returns true. This assumes that path is valid and visible.
1620 * @param path is the path to start editing
1621 * @param event is the MouseEvent performed on the path
1622 * @return true if successful
1624 protected boolean startEditing(TreePath path, MouseEvent event)
1626 updateCellEditor();
1627 TreeCellEditor ed = getCellEditor();
1629 if (ed != null && (event == EDIT || ed.shouldSelectCell(event))
1630 && ed.isCellEditable(event))
1632 Rectangle bounds = getPathBounds(tree, path);
1634 // Extend the right boundary till the tree width.
1635 bounds.width = tree.getWidth() - bounds.x;
1637 editingPath = path;
1638 editingRow = tree.getRowForPath(editingPath);
1640 Object value = editingPath.getLastPathComponent();
1642 stopEditingInCompleteEditing = false;
1643 boolean expanded = tree.isExpanded(editingPath);
1644 isEditing = true;
1645 editingComponent = ed.getTreeCellEditorComponent(tree, value, true,
1646 expanded,
1647 isLeaf(editingRow),
1648 editingRow);
1650 // Remove all previous components (if still present). Only one
1651 // container with the editing component inside is allowed in the tree.
1652 tree.removeAll();
1654 // The editing component must be added to its container. We add the
1655 // container, not the editing component itself.
1656 Component container = editingComponent.getParent();
1657 container.setBounds(bounds);
1658 tree.add(container);
1659 editingComponent.requestFocus();
1661 return true;
1663 return false;
1667 * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or
1668 * collapse region of the row, this will toggle the row.
1670 * @param path the path we are concerned with
1671 * @param mouseX is the cursor's x position
1672 * @param mouseY is the cursor's y position
1674 protected void checkForClickInExpandControl(TreePath path, int mouseX,
1675 int mouseY)
1677 if (isLocationInExpandControl(path, mouseX, mouseY))
1678 toggleExpandState(path);
1682 * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in
1683 * the area of row that is used to expand/collpse the node and the node at row
1684 * does not represent a leaf.
1686 * @param path the path we are concerned with
1687 * @param mouseX is the cursor's x position
1688 * @param mouseY is the cursor's y position
1689 * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in
1690 * the area of row that is used to expand/collpse the node and the
1691 * node at row does not represent a leaf.
1693 protected boolean isLocationInExpandControl(TreePath path, int mouseX,
1694 int mouseY)
1696 boolean cntlClick = false;
1697 int row = getRowForPath(tree, path);
1699 if (! isLeaf(row))
1701 Rectangle bounds = getPathBounds(tree, path);
1703 if (hasControlIcons()
1704 && (mouseX < bounds.x)
1705 && (mouseX > (bounds.x - getCurrentControlIcon(path).getIconWidth() - gap)))
1706 cntlClick = true;
1708 return cntlClick;
1712 * Messaged when the user clicks the particular row, this invokes
1713 * toggleExpandState.
1715 * @param path the path we are concerned with
1716 * @param mouseX is the cursor's x position
1717 * @param mouseY is the cursor's y position
1719 protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY)
1721 toggleExpandState(path);
1725 * Expands path if it is not expanded, or collapses row if it is expanded. If
1726 * expanding a path and JTree scroll on expand, ensureRowsAreVisible is
1727 * invoked to scroll as many of the children to visible as possible (tries to
1728 * scroll to last visible descendant of path).
1730 * @param path the path we are concerned with
1732 protected void toggleExpandState(TreePath path)
1734 if (tree.isExpanded(path))
1735 tree.collapsePath(path);
1736 else
1737 tree.expandPath(path);
1741 * Returning true signifies a mouse event on the node should toggle the
1742 * selection of only the row under the mouse. The BasisTreeUI treats the
1743 * event as "toggle selection event" if the CTRL button was pressed while
1744 * clicking. The event is not counted as toggle event if the associated
1745 * tree does not support the multiple selection.
1747 * @param event is the MouseEvent performed on the row.
1748 * @return true signifies a mouse event on the node should toggle the
1749 * selection of only the row under the mouse.
1751 protected boolean isToggleSelectionEvent(MouseEvent event)
1753 return
1754 (tree.getSelectionModel().getSelectionMode() !=
1755 TreeSelectionModel.SINGLE_TREE_SELECTION) &&
1756 ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0);
1760 * Returning true signifies a mouse event on the node should select from the
1761 * anchor point. The BasisTreeUI treats the event as "multiple selection
1762 * event" if the SHIFT button was pressed while clicking. The event is not
1763 * counted as multiple selection event if the associated tree does not support
1764 * the multiple selection.
1766 * @param event is the MouseEvent performed on the node.
1767 * @return true signifies a mouse event on the node should select from the
1768 * anchor point.
1770 protected boolean isMultiSelectEvent(MouseEvent event)
1772 return
1773 (tree.getSelectionModel().getSelectionMode() !=
1774 TreeSelectionModel.SINGLE_TREE_SELECTION) &&
1775 ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0);
1779 * Returning true indicates the row under the mouse should be toggled based on
1780 * the event. This is invoked after checkForClickInExpandControl, implying the
1781 * location is not in the expand (toggle) control.
1783 * @param event is the MouseEvent performed on the row.
1784 * @return true indicates the row under the mouse should be toggled based on
1785 * the event.
1787 protected boolean isToggleEvent(MouseEvent event)
1789 return true;
1793 * Messaged to update the selection based on a MouseEvent over a particular
1794 * row. If the even is a toggle selection event, the row is either selected,
1795 * or deselected. If the event identifies a multi selection event, the
1796 * selection is updated from the anchor point. Otherwise, the row is selected,
1797 * and the previous selection is cleared.</p>
1799 * @param path is the path selected for an event
1800 * @param event is the MouseEvent performed on the path.
1802 * @see #isToggleSelectionEvent(MouseEvent)
1803 * @see #isMultiSelectEvent(MouseEvent)
1805 protected void selectPathForEvent(TreePath path, MouseEvent event)
1807 if (isToggleSelectionEvent(event))
1809 // The event selects or unselects the clicked row.
1810 if (tree.isPathSelected(path))
1811 tree.removeSelectionPath(path);
1812 else
1814 tree.addSelectionPath(path);
1815 tree.setAnchorSelectionPath(path);
1818 else if (isMultiSelectEvent(event))
1820 // The event extends selection form anchor till the clicked row.
1821 TreePath anchor = tree.getAnchorSelectionPath();
1822 if (anchor != null)
1824 int aRow = getRowForPath(tree, anchor);
1825 tree.addSelectionInterval(aRow, getRowForPath(tree, path));
1827 else
1828 tree.addSelectionPath(path);
1830 else
1832 // This is an ordinary event that just selects the clicked row.
1833 tree.setSelectionPath(path);
1834 tree.setAnchorSelectionPath(path);
1839 * Returns true if the node at <code>row</code> is a leaf.
1841 * @param row is the row we are concerned with.
1842 * @return true if the node at <code>row</code> is a leaf.
1844 protected boolean isLeaf(int row)
1846 TreePath pathForRow = getPathForRow(tree, row);
1847 if (pathForRow == null)
1848 return true;
1850 Object node = pathForRow.getLastPathComponent();
1851 return treeModel.isLeaf(node);
1855 * This class implements the actions that we want to happen when specific keys
1856 * are pressed for the JTree. The actionPerformed method is called when a key
1857 * that has been registered for the JTree is received.
1859 class TreeAction
1860 extends AbstractAction
1864 * What to do when this action is called.
1866 * @param e the ActionEvent that caused this action.
1868 public void actionPerformed(ActionEvent e)
1870 String command = e.getActionCommand();
1871 TreePath lead = tree.getLeadSelectionPath();
1873 if (command.equals("selectPreviousChangeLead")
1874 || command.equals("selectPreviousExtendSelection")
1875 || command.equals("selectPrevious") || command.equals("selectNext")
1876 || command.equals("selectNextExtendSelection")
1877 || command.equals("selectNextChangeLead"))
1878 (new TreeIncrementAction(0, "")).actionPerformed(e);
1879 else if (command.equals("selectParent") || command.equals("selectChild"))
1880 (new TreeTraverseAction(0, "")).actionPerformed(e);
1881 else if (command.equals("selectAll"))
1883 TreePath[] paths = new TreePath[treeState.getRowCount()];
1884 for (int i = 0; i < paths.length; i++)
1885 paths[i] = treeState.getPathForRow(i);
1886 tree.addSelectionPaths(paths);
1888 else if (command.equals("startEditing"))
1889 tree.startEditingAtPath(lead);
1890 else if (command.equals("toggle"))
1892 if (tree.isEditing())
1893 tree.stopEditing();
1894 else
1896 Object last = lead.getLastPathComponent();
1897 TreePath path = new TreePath(getPathToRoot(last, 0));
1898 if (! treeModel.isLeaf(last))
1899 toggleExpandState(path);
1902 else if (command.equals("clearSelection"))
1903 tree.clearSelection();
1905 if (tree.isEditing() && ! command.equals("startEditing"))
1906 tree.stopEditing();
1908 tree.scrollPathToVisible(tree.getLeadSelectionPath());
1913 * This class is used to mimic the behaviour of the JDK when registering
1914 * keyboard actions. It is the same as the private class used in JComponent
1915 * for the same reason. This class receives an action event and dispatches it
1916 * to the true receiver after altering the actionCommand property of the
1917 * event.
1919 private static class ActionListenerProxy
1920 extends AbstractAction
1922 ActionListener target;
1924 String bindingCommandName;
1926 public ActionListenerProxy(ActionListener li, String cmd)
1928 target = li;
1929 bindingCommandName = cmd;
1932 public void actionPerformed(ActionEvent e)
1934 ActionEvent derivedEvent = new ActionEvent(e.getSource(), e.getID(),
1935 bindingCommandName,
1936 e.getModifiers());
1938 target.actionPerformed(derivedEvent);
1943 * Updates the preferred size when scrolling, if necessary.
1945 public class ComponentHandler
1946 extends ComponentAdapter
1947 implements ActionListener
1950 * Timer used when inside a scrollpane and the scrollbar is adjusting
1952 protected Timer timer;
1954 /** ScrollBar that is being adjusted */
1955 protected JScrollBar scrollBar;
1958 * Constructor
1960 public ComponentHandler()
1962 // Nothing to do here.
1966 * Invoked when the component's position changes.
1968 * @param e the event that occurs when moving the component
1970 public void componentMoved(ComponentEvent e)
1972 // TODO: What should be done here, if anything?
1976 * Creates, if necessary, and starts a Timer to check if needed to resize
1977 * the bounds
1979 protected void startTimer()
1981 // TODO: Implement this properly.
1985 * Returns the JScrollPane housing the JTree, or null if one isn't found.
1987 * @return JScrollPane housing the JTree, or null if one isn't found.
1989 protected JScrollPane getScrollPane()
1991 return null;
1995 * Public as a result of Timer. If the scrollBar is null, or not adjusting,
1996 * this stops the timer and updates the sizing.
1998 * @param ae is the action performed
2000 public void actionPerformed(ActionEvent ae)
2002 // TODO: Implement this properly.
2007 * Listener responsible for getting cell editing events and updating the tree
2008 * accordingly.
2010 public class CellEditorHandler
2011 implements CellEditorListener
2014 * Constructor
2016 public CellEditorHandler()
2018 // Nothing to do here.
2022 * Messaged when editing has stopped in the tree. Tells the listeners
2023 * editing has stopped.
2025 * @param e is the notification event
2027 public void editingStopped(ChangeEvent e)
2029 stopEditing(tree);
2033 * Messaged when editing has been canceled in the tree. This tells the
2034 * listeners the editor has canceled editing.
2036 * @param e is the notification event
2038 public void editingCanceled(ChangeEvent e)
2040 cancelEditing(tree);
2042 }// CellEditorHandler
2045 * Repaints the lead selection row when focus is lost/grained.
2047 public class FocusHandler
2048 implements FocusListener
2051 * Constructor
2053 public FocusHandler()
2055 // Nothing to do here.
2059 * Invoked when focus is activated on the tree we're in, redraws the lead
2060 * row. Invoked when a component gains the keyboard focus. The method
2061 * repaints the lead row that is shown differently when the tree is in
2062 * focus.
2064 * @param e is the focus event that is activated
2066 public void focusGained(FocusEvent e)
2068 repaintLeadRow();
2072 * Invoked when focus is deactivated on the tree we're in, redraws the lead
2073 * row. Invoked when a component loses the keyboard focus. The method
2074 * repaints the lead row that is shown differently when the tree is in
2075 * focus.
2077 * @param e is the focus event that is deactivated
2079 public void focusLost(FocusEvent e)
2081 repaintLeadRow();
2085 * Repaint the lead row.
2087 void repaintLeadRow()
2089 TreePath lead = tree.getLeadSelectionPath();
2090 if (lead!=null)
2091 tree.repaint(tree.getPathBounds(lead));
2096 * This is used to get multiple key down events to appropriately genereate
2097 * events.
2099 public class KeyHandler
2100 extends KeyAdapter
2102 /** Key code that is being generated for. */
2103 protected Action repeatKeyAction;
2105 /** Set to true while keyPressed is active */
2106 protected boolean isKeyDown;
2109 * Constructor
2111 public KeyHandler()
2113 // Nothing to do here.
2117 * Invoked when a key has been typed. Moves the keyboard focus to the first
2118 * element whose first letter matches the alphanumeric key pressed by the
2119 * user. Subsequent same key presses move the keyboard focus to the next
2120 * object that starts with the same letter.
2122 * @param e the key typed
2124 public void keyTyped(KeyEvent e)
2126 // TODO: What should be done here, if anything?
2130 * Invoked when a key has been pressed.
2132 * @param e the key pressed
2134 public void keyPressed(KeyEvent e)
2136 // TODO: What should be done here, if anything?
2140 * Invoked when a key has been released
2142 * @param e the key released
2144 public void keyReleased(KeyEvent e)
2146 // TODO: What should be done here, if anything?
2151 * MouseListener is responsible for updating the selection based on mouse
2152 * events.
2154 public class MouseHandler
2155 extends MouseAdapter
2156 implements MouseMotionListener
2159 * Constructor
2161 public MouseHandler()
2163 // Nothing to do here.
2167 * Invoked when a mouse button has been pressed on a component.
2169 * @param e is the mouse event that occured
2171 public void mousePressed(MouseEvent e)
2173 // Any mouse click cancels the previous waiting edit action, initiated
2174 // by the single click on the selected node.
2175 if (startEditTimer != null)
2177 startEditTimer.stop();
2178 startEditTimer = null;
2181 Point click = e.getPoint();
2182 TreePath path = getClosestPathForLocation(tree, click.x, click.y);
2184 if (path != null)
2186 Rectangle bounds = getPathBounds(tree, path);
2187 int row = getRowForPath(tree, path);
2189 // Cancel the editing session if clicked on the different row.
2190 if (tree.isEditing() && row != editingRow)
2191 cancelEditing(tree);
2193 boolean cntlClick = isLocationInExpandControl(path, click.x, click.y);
2195 boolean isLeaf = isLeaf(row);
2197 TreeCellRenderer tcr = getCellRenderer();
2198 Icon icon;
2199 if (isLeaf)
2200 icon = UIManager.getIcon("Tree.leafIcon");
2201 else if (tree.isExpanded(path))
2202 icon = UIManager.getIcon("Tree.openIcon");
2203 else
2204 icon = UIManager.getIcon("Tree.closedIcon");
2206 if (tcr instanceof DefaultTreeCellRenderer)
2208 Icon tmp = ((DefaultTreeCellRenderer) tcr).getIcon();
2209 if (tmp != null)
2210 icon = tmp;
2213 // add gap*2 for the space before and after the text
2214 if (icon != null)
2215 bounds.width += icon.getIconWidth() + gap * 2;
2217 boolean inBounds = bounds.contains(click.x, click.y);
2218 if ((inBounds || cntlClick) && tree.isVisible(path))
2220 if (inBounds)
2222 TreePath currentLead = tree.getLeadSelectionPath();
2223 if (currentLead != null && currentLead.equals(path)
2224 && e.getClickCount() == 1 && tree.isEditable())
2226 // Schedule the editing session.
2227 final TreePath editPath = path;
2229 if (startEditTimer != null)
2230 startEditTimer.stop();
2232 startEditTimer = new Timer(WAIT_TILL_EDITING,
2233 new ActionListener()
2235 public void actionPerformed(ActionEvent e)
2237 startEditing(editPath, EDIT);
2240 startEditTimer.setRepeats(false);
2241 startEditTimer.start();
2243 else
2245 if (e.getClickCount() == 2 && ! isLeaf(row))
2246 toggleExpandState(path);
2247 else
2248 selectPathForEvent(path, e);
2252 if (cntlClick)
2254 handleExpandControlClick(path, click.x, click.y);
2255 if (cellEditor != null)
2256 cellEditor.cancelCellEditing();
2257 tree.scrollPathToVisible(path);
2259 else if (tree.isEditable())
2260 startEditing(path, e);
2266 * Invoked when a mouse button is pressed on a component and then dragged.
2267 * MOUSE_DRAGGED events will continue to be delivered to the component where
2268 * the drag originated until the mouse button is released (regardless of
2269 * whether the mouse position is within the bounds of the component).
2271 * @param e is the mouse event that occured
2273 public void mouseDragged(MouseEvent e)
2275 // TODO: What should be done here, if anything?
2279 * Invoked when the mouse button has been moved on a component (with no
2280 * buttons no down).
2282 * @param e the mouse event that occured
2284 public void mouseMoved(MouseEvent e)
2286 // TODO: What should be done here, if anything?
2290 * Invoked when a mouse button has been released on a component.
2292 * @param e is the mouse event that occured
2294 public void mouseReleased(MouseEvent e)
2296 // TODO: What should be done here, if anything?
2301 * MouseInputHandler handles passing all mouse events, including mouse motion
2302 * events, until the mouse is released to the destination it is constructed
2303 * with.
2305 public class MouseInputHandler
2306 implements MouseInputListener
2308 /** Source that events are coming from */
2309 protected Component source;
2311 /** Destination that receives all events. */
2312 protected Component destination;
2315 * Constructor
2317 * @param source that events are coming from
2318 * @param destination that receives all events
2319 * @param e is the event received
2321 public MouseInputHandler(Component source, Component destination,
2322 MouseEvent e)
2324 this.source = source;
2325 this.destination = destination;
2329 * Invoked when the mouse button has been clicked (pressed and released) on
2330 * a component.
2332 * @param e mouse event that occured
2334 public void mouseClicked(MouseEvent e)
2336 // TODO: What should be done here, if anything?
2340 * Invoked when a mouse button has been pressed on a component.
2342 * @param e mouse event that occured
2344 public void mousePressed(MouseEvent e)
2346 // TODO: What should be done here, if anything?
2350 * Invoked when a mouse button has been released on a component.
2352 * @param e mouse event that occured
2354 public void mouseReleased(MouseEvent e)
2356 // TODO: What should be done here, if anything?
2360 * Invoked when the mouse enters a component.
2362 * @param e mouse event that occured
2364 public void mouseEntered(MouseEvent e)
2366 // TODO: What should be done here, if anything?
2370 * Invoked when the mouse exits a component.
2372 * @param e mouse event that occured
2374 public void mouseExited(MouseEvent e)
2376 // TODO: What should be done here, if anything?
2380 * Invoked when a mouse button is pressed on a component and then dragged.
2381 * MOUSE_DRAGGED events will continue to be delivered to the component where
2382 * the drag originated until the mouse button is released (regardless of
2383 * whether the mouse position is within the bounds of the component).
2385 * @param e mouse event that occured
2387 public void mouseDragged(MouseEvent e)
2389 // TODO: What should be done here, if anything?
2393 * Invoked when the mouse cursor has been moved onto a component but no
2394 * buttons have been pushed.
2396 * @param e mouse event that occured
2398 public void mouseMoved(MouseEvent e)
2400 // TODO: What should be done here, if anything?
2404 * Removes event from the source
2406 protected void removeFromSource()
2408 // TODO: Implement this properly.
2413 * Class responsible for getting size of node, method is forwarded to
2414 * BasicTreeUI method. X location does not include insets, that is handled in
2415 * getPathBounds.
2417 public class NodeDimensionsHandler
2418 extends AbstractLayoutCache.NodeDimensions
2421 * Constructor
2423 public NodeDimensionsHandler()
2425 // Nothing to do here.
2429 * Returns, by reference in bounds, the size and x origin to place value at.
2430 * The calling method is responsible for determining the Y location. If
2431 * bounds is null, a newly created Rectangle should be returned, otherwise
2432 * the value should be placed in bounds and returned.
2434 * @param cell the value to be represented
2435 * @param row row being queried
2436 * @param depth the depth of the row
2437 * @param expanded true if row is expanded
2438 * @param size a Rectangle containing the size needed to represent value
2439 * @return containing the node dimensions, or null if node has no dimension
2441 public Rectangle getNodeDimensions(Object cell, int row, int depth,
2442 boolean expanded, Rectangle size)
2444 if (size == null || cell == null)
2445 return null;
2447 String s = cell.toString();
2448 Font f = tree.getFont();
2449 FontMetrics fm = tree.getToolkit().getFontMetrics(f);
2451 if (s != null)
2453 size.x = getRowX(row, depth);
2454 size.width = SwingUtilities.computeStringWidth(fm, s);
2455 size.width = size.width + getCurrentControlIcon(null).getIconWidth()
2456 + gap;
2457 size.height = getMaxHeight(tree);
2458 size.y = size.height * row;
2461 return size;
2465 * Returns the amount to indent the given row
2467 * @return amount to indent the given row.
2469 protected int getRowX(int row, int depth)
2471 int iw = getCurrentControlIcon(null).getIconWidth();
2472 return depth * (rightChildIndent + iw/2);
2474 }// NodeDimensionsHandler
2477 * PropertyChangeListener for the tree. Updates the appropriate variable, or
2478 * TreeState, based on what changes.
2480 public class PropertyChangeHandler
2481 implements PropertyChangeListener
2485 * Constructor
2487 public PropertyChangeHandler()
2489 // Nothing to do here.
2493 * This method gets called when a bound property is changed.
2495 * @param event A PropertyChangeEvent object describing the event source and
2496 * the property that has changed.
2498 public void propertyChange(PropertyChangeEvent event)
2500 String property = event.getPropertyName();
2501 if (property.equals(JTree.ROOT_VISIBLE_PROPERTY))
2503 validCachedPreferredSize = false;
2504 treeState.setRootVisible(tree.isRootVisible());
2505 tree.repaint();
2507 else if (property.equals(JTree.SELECTION_MODEL_PROPERTY))
2509 treeSelectionModel = tree.getSelectionModel();
2510 treeSelectionModel.setRowMapper(treeState);
2512 else if (property.equals(JTree.TREE_MODEL_PROPERTY))
2514 treeModel = tree.getModel();
2515 treeModel.addTreeModelListener(treeModelListener);
2521 * Listener on the TreeSelectionModel, resets the row selection if any of the
2522 * properties of the model change.
2524 public class SelectionModelPropertyChangeHandler
2525 implements PropertyChangeListener
2529 * Constructor
2531 public SelectionModelPropertyChangeHandler()
2533 // Nothing to do here.
2537 * This method gets called when a bound property is changed.
2539 * @param event A PropertyChangeEvent object describing the event source and
2540 * the property that has changed.
2542 public void propertyChange(PropertyChangeEvent event)
2544 // TODO: What should be done here, if anything?
2549 * ActionListener that invokes cancelEditing when action performed.
2551 public class TreeCancelEditingAction
2552 extends AbstractAction
2556 * Constructor
2558 public TreeCancelEditingAction(String name)
2560 // TODO: Implement this properly.
2564 * Invoked when an action occurs.
2566 * @param e event that occured
2568 public void actionPerformed(ActionEvent e)
2570 // TODO: Implement this properly.
2574 * Returns true if the action is enabled.
2576 * @return true if the action is enabled, false otherwise
2578 public boolean isEnabled()
2580 // TODO: Implement this properly.
2581 return false;
2586 * Updates the TreeState in response to nodes expanding/collapsing.
2588 public class TreeExpansionHandler
2589 implements TreeExpansionListener
2593 * Constructor
2595 public TreeExpansionHandler()
2597 // Nothing to do here.
2601 * Called whenever an item in the tree has been expanded.
2603 * @param event is the event that occured
2605 public void treeExpanded(TreeExpansionEvent event)
2607 validCachedPreferredSize = false;
2608 treeState.setExpandedState(event.getPath(), true);
2609 tree.revalidate();
2610 tree.repaint();
2614 * Called whenever an item in the tree has been collapsed.
2616 * @param event is the event that occured
2618 public void treeCollapsed(TreeExpansionEvent event)
2620 validCachedPreferredSize = false;
2621 treeState.setExpandedState(event.getPath(), false);
2622 tree.revalidate();
2623 tree.repaint();
2625 }// TreeExpansionHandler
2628 * TreeHomeAction is used to handle end/home actions. Scrolls either the first
2629 * or last cell to be visible based on direction.
2631 public class TreeHomeAction
2632 extends AbstractAction
2635 /** The direction, either home or end */
2636 protected int direction;
2639 * Constructor
2641 * @param direction - it is home or end
2642 * @param name is the name of the direction
2644 public TreeHomeAction(int direction, String name)
2646 // TODO: Implement this properly
2650 * Invoked when an action occurs.
2652 * @param e is the event that occured
2654 public void actionPerformed(ActionEvent e)
2656 // TODO: Implement this properly
2660 * Returns true if the action is enabled.
2662 * @return true if the action is enabled.
2664 public boolean isEnabled()
2666 // TODO: Implement this properly
2667 return false;
2672 * TreeIncrementAction is used to handle up/down actions. Selection is moved
2673 * up or down based on direction.
2675 public class TreeIncrementAction
2676 extends AbstractAction
2679 /** Specifies the direction to adjust the selection by. */
2680 protected int direction;
2683 * Constructor
2685 * @param direction up or down
2686 * @param name is the name of the direction
2688 public TreeIncrementAction(int direction, String name)
2690 // TODO: Implement this properly
2694 * Invoked when an action occurs.
2696 * @param e is the event that occured
2698 public void actionPerformed(ActionEvent e)
2700 TreePath currentPath = tree.getLeadSelectionPath();
2701 int currentRow;
2703 if (currentPath != null)
2704 currentRow = treeState.getRowForPath(currentPath);
2705 else
2706 currentRow = 0;
2708 int rows = treeState.getRowCount();
2710 int nextRow = currentRow + 1;
2711 int prevRow = currentRow - 1;
2712 boolean hasNext = nextRow < rows;
2713 boolean hasPrev = prevRow >= 0 && rows > 0;
2714 TreePath newPath;
2715 String command = e.getActionCommand();
2717 if (command.equals("selectPreviousChangeLead") && hasPrev)
2719 newPath = treeState.getPathForRow(prevRow);
2720 tree.setSelectionPath(newPath);
2721 tree.setAnchorSelectionPath(newPath);
2722 tree.setLeadSelectionPath(newPath);
2724 else if (command.equals("selectPreviousExtendSelection") && hasPrev)
2726 newPath = treeState.getPathForRow(prevRow);
2728 // If the new path is already selected, the selection shrinks,
2729 // unselecting the previously current path.
2730 if (tree.isPathSelected(newPath))
2731 tree.getSelectionModel().removeSelectionPath(currentPath);
2733 // This must be called in any case because it updates the model
2734 // lead selection index.
2735 tree.addSelectionPath(newPath);
2736 tree.setLeadSelectionPath(newPath);
2738 else if (command.equals("selectPrevious") && hasPrev)
2740 newPath = treeState.getPathForRow(prevRow);
2741 tree.setSelectionPath(newPath);
2743 else if (command.equals("selectNext") && hasNext)
2745 newPath = treeState.getPathForRow(nextRow);
2746 tree.setSelectionPath(newPath);
2748 else if (command.equals("selectNextExtendSelection") && hasNext)
2750 newPath = treeState.getPathForRow(nextRow);
2752 // If the new path is already selected, the selection shrinks,
2753 // unselecting the previously current path.
2754 if (tree.isPathSelected(newPath))
2755 tree.getSelectionModel().removeSelectionPath(currentPath);
2757 // This must be called in any case because it updates the model
2758 // lead selection index.
2759 tree.addSelectionPath(newPath);
2761 tree.setLeadSelectionPath(newPath);
2763 else if (command.equals("selectNextChangeLead") && hasNext)
2765 newPath = treeState.getPathForRow(nextRow);
2766 tree.setSelectionPath(newPath);
2767 tree.setAnchorSelectionPath(newPath);
2768 tree.setLeadSelectionPath(newPath);
2773 * Returns true if the action is enabled.
2775 * @return true if the action is enabled.
2777 public boolean isEnabled()
2779 // TODO: Implement this properly
2780 return false;
2785 * Forwards all TreeModel events to the TreeState.
2787 public class TreeModelHandler
2788 implements TreeModelListener
2791 * Constructor
2793 public TreeModelHandler()
2795 // Nothing to do here.
2799 * Invoked after a node (or a set of siblings) has changed in some way. The
2800 * node(s) have not changed locations in the tree or altered their children
2801 * arrays, but other attributes have changed and may affect presentation.
2802 * Example: the name of a file has changed, but it is in the same location
2803 * in the file system. To indicate the root has changed, childIndices and
2804 * children will be null. Use e.getPath() to get the parent of the changed
2805 * node(s). e.getChildIndices() returns the index(es) of the changed
2806 * node(s).
2808 * @param e is the event that occured
2810 public void treeNodesChanged(TreeModelEvent e)
2812 validCachedPreferredSize = false;
2813 treeState.treeNodesChanged(e);
2814 tree.repaint();
2818 * Invoked after nodes have been inserted into the tree. Use e.getPath() to
2819 * get the parent of the new node(s). e.getChildIndices() returns the
2820 * index(es) of the new node(s) in ascending order.
2822 * @param e is the event that occured
2824 public void treeNodesInserted(TreeModelEvent e)
2826 validCachedPreferredSize = false;
2827 treeState.treeNodesInserted(e);
2828 tree.repaint();
2832 * Invoked after nodes have been removed from the tree. Note that if a
2833 * subtree is removed from the tree, this method may only be invoked once
2834 * for the root of the removed subtree, not once for each individual set of
2835 * siblings removed. Use e.getPath() to get the former parent of the deleted
2836 * node(s). e.getChildIndices() returns, in ascending order, the index(es)
2837 * the node(s) had before being deleted.
2839 * @param e is the event that occured
2841 public void treeNodesRemoved(TreeModelEvent e)
2843 validCachedPreferredSize = false;
2844 treeState.treeNodesRemoved(e);
2845 tree.repaint();
2849 * Invoked after the tree has drastically changed structure from a given
2850 * node down. If the path returned by e.getPath() is of length one and the
2851 * first element does not identify the current root node the first element
2852 * should become the new root of the tree. Use e.getPath() to get the path
2853 * to the node. e.getChildIndices() returns null.
2855 * @param e is the event that occured
2857 public void treeStructureChanged(TreeModelEvent e)
2859 if (e.getPath().length == 1
2860 && ! e.getPath()[0].equals(treeModel.getRoot()))
2861 tree.expandPath(new TreePath(treeModel.getRoot()));
2862 validCachedPreferredSize = false;
2863 treeState.treeStructureChanged(e);
2864 tree.repaint();
2866 }// TreeModelHandler
2869 * TreePageAction handles page up and page down events.
2871 public class TreePageAction
2872 extends AbstractAction
2874 /** Specifies the direction to adjust the selection by. */
2875 protected int direction;
2878 * Constructor
2880 * @param direction up or down
2881 * @param name is the name of the direction
2883 public TreePageAction(int direction, String name)
2885 this.direction = direction;
2889 * Invoked when an action occurs.
2891 * @param e is the event that occured
2893 public void actionPerformed(ActionEvent e)
2895 // TODO: Implement this properly.
2899 * Returns true if the action is enabled.
2901 * @return true if the action is enabled.
2903 public boolean isEnabled()
2905 return false;
2907 }// TreePageAction
2910 * Listens for changes in the selection model and updates the display
2911 * accordingly.
2913 public class TreeSelectionHandler
2914 implements TreeSelectionListener
2917 * Constructor
2919 public TreeSelectionHandler()
2921 // Nothing to do here.
2925 * Messaged when the selection changes in the tree we're displaying for.
2926 * Stops editing, messages super and displays the changed paths.
2928 * @param event the event that characterizes the change.
2930 public void valueChanged(TreeSelectionEvent event)
2932 if (tree.isEditing())
2933 tree.cancelEditing();
2935 TreePath op = event.getOldLeadSelectionPath();
2936 TreePath np = event.getNewLeadSelectionPath();
2938 // Repaint of the changed lead selection path.
2939 if (op != np)
2941 Rectangle o = treeState.getBounds(event.getOldLeadSelectionPath(),
2942 new Rectangle());
2943 Rectangle n = treeState.getBounds(event.getNewLeadSelectionPath(),
2944 new Rectangle());
2946 if (o!=null)
2947 tree.repaint(o);
2948 if (n!=null)
2949 tree.repaint(n);
2952 }// TreeSelectionHandler
2955 * For the first selected row expandedness will be toggled.
2957 public class TreeToggleAction
2958 extends AbstractAction
2961 * Constructor
2963 * @param name is the name of <code>Action</code> field
2965 public TreeToggleAction(String name)
2967 // Nothing to do here.
2971 * Invoked when an action occurs.
2973 * @param e the event that occured
2975 public void actionPerformed(ActionEvent e)
2977 // TODO: Implement this properly.
2981 * Returns true if the action is enabled.
2983 * @return true if the action is enabled, false otherwise
2985 public boolean isEnabled()
2987 return false;
2989 } // TreeToggleAction
2992 * TreeTraverseAction is the action used for left/right keys. Will toggle the
2993 * expandedness of a node, as well as potentially incrementing the selection.
2995 public class TreeTraverseAction
2996 extends AbstractAction
2999 * Determines direction to traverse, 1 means expand, -1 means collapse.
3001 protected int direction;
3004 * Constructor
3006 * @param direction to traverse
3007 * @param name is the name of the direction
3009 public TreeTraverseAction(int direction, String name)
3011 this.direction = direction;
3015 * Invoked when an action occurs.
3017 * @param e the event that occured
3019 public void actionPerformed(ActionEvent e)
3021 TreePath current = tree.getLeadSelectionPath();
3022 if (current == null)
3023 return;
3025 if (e.getActionCommand().equals("selectParent"))
3027 if (current == null)
3028 return;
3030 if (tree.isExpanded(current))
3032 tree.collapsePath(current);
3034 else
3036 // If the node is not expanded (also, if it is a leaf node),
3037 // we just select the parent. We do not select the root if it
3038 // is not visible.
3039 TreePath parent = current.getParentPath();
3040 if (parent != null &&
3041 !(parent.getPathCount()==1 && !tree.isRootVisible()) )
3042 tree.setSelectionPath(parent);
3045 else if (e.getActionCommand().equals("selectChild"))
3047 Object node = current.getLastPathComponent();
3048 int nc = treeModel.getChildCount(node);
3049 if (nc == 0 || treeState.isExpanded(current))
3051 // If the node is leaf or it is already expanded,
3052 // we just select the next row.
3053 int nextRow = tree.getLeadSelectionRow() + 1;
3054 if (nextRow <= tree.getRowCount())
3055 tree.setSelectionRow(nextRow);
3057 else
3059 tree.expandPath(current);
3065 * Returns true if the action is enabled.
3067 * @return true if the action is enabled, false otherwise
3069 public boolean isEnabled()
3071 // TODO: Implement this properly
3072 return false;
3077 * Returns true if the LookAndFeel implements the control icons. Package
3078 * private for use in inner classes.
3080 * @returns true if there are control icons
3082 boolean hasControlIcons()
3084 if (expandedIcon != null || collapsedIcon != null)
3085 return true;
3086 return false;
3090 * Returns control icon. It is null if the LookAndFeel does not implements the
3091 * control icons. Package private for use in inner classes.
3093 * @return control icon if it exists.
3095 Icon getCurrentControlIcon(TreePath path)
3097 if (tree.isExpanded(path))
3098 return expandedIcon;
3099 return collapsedIcon;
3103 * Returns the parent of the current node
3105 * @param root is the root of the tree
3106 * @param node is the current node
3107 * @return is the parent of the current node
3109 Object getParent(Object root, Object node)
3111 if (root == null || node == null || root.equals(node))
3112 return null;
3114 if (node instanceof TreeNode)
3115 return ((TreeNode) node).getParent();
3116 return findNode(root, node);
3120 * Recursively checks the tree for the specified node, starting at the root.
3122 * @param root is starting node to start searching at.
3123 * @param node is the node to search for
3124 * @return the parent node of node
3126 private Object findNode(Object root, Object node)
3128 if (! treeModel.isLeaf(root) && ! root.equals(node))
3130 int size = treeModel.getChildCount(root);
3131 for (int j = 0; j < size; j++)
3133 Object child = treeModel.getChild(root, j);
3134 if (node.equals(child))
3135 return root;
3137 Object n = findNode(child, node);
3138 if (n != null)
3139 return n;
3142 return null;
3146 * Selects the specified path in the tree depending on modes. Package private
3147 * for use in inner classes.
3149 * @param tree is the tree we are selecting the path in
3150 * @param path is the path we are selecting
3152 void selectPath(JTree tree, TreePath path)
3154 if (path != null)
3156 tree.setSelectionPath(path);
3157 tree.setLeadSelectionPath(path);
3158 tree.makeVisible(path);
3159 tree.scrollPathToVisible(path);
3164 * Returns the path from node to the root. Package private for use in inner
3165 * classes.
3167 * @param node the node to get the path to
3168 * @param depth the depth of the tree to return a path for
3169 * @return an array of tree nodes that represent the path to node.
3171 Object[] getPathToRoot(Object node, int depth)
3173 if (node == null)
3175 if (depth == 0)
3176 return null;
3178 return new Object[depth];
3181 Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node),
3182 depth + 1);
3183 path[path.length - depth - 1] = node;
3184 return path;
3188 * Draws a vertical line using the given graphic context
3190 * @param g is the graphic context
3191 * @param c is the component the new line will belong to
3192 * @param x is the horizonal position
3193 * @param top specifies the top of the line
3194 * @param bottom specifies the bottom of the line
3196 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
3197 int bottom)
3199 // FIXME: Check if drawing a dashed line or not.
3200 g.setColor(getHashColor());
3201 g.drawLine(x, top, x, bottom);
3205 * Draws a horizontal line using the given graphic context
3207 * @param g is the graphic context
3208 * @param c is the component the new line will belong to
3209 * @param y is the vertical position
3210 * @param left specifies the left point of the line
3211 * @param right specifies the right point of the line
3213 protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left,
3214 int right)
3216 // FIXME: Check if drawing a dashed line or not.
3217 g.setColor(getHashColor());
3218 g.drawLine(left, y, right, y);
3222 * Draws an icon at around a specific position
3224 * @param c is the component the new line will belong to
3225 * @param g is the graphic context
3226 * @param icon is the icon which will be drawn
3227 * @param x is the center position in x-direction
3228 * @param y is the center position in y-direction
3230 protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y)
3232 x -= icon.getIconWidth() / 2;
3233 y -= icon.getIconHeight() / 2;
3235 if (x < 0)
3236 x = 0;
3237 if (y < 0)
3238 y = 0;
3240 icon.paintIcon(c, g, x, y);
3244 * Draws a dashed horizontal line.
3246 * @param g - the graphics configuration.
3247 * @param y - the y location to start drawing at
3248 * @param x1 - the x location to start drawing at
3249 * @param x2 - the x location to finish drawing at
3251 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)
3253 g.setColor(getHashColor());
3254 for (int i = x1; i < x2; i += 2)
3255 g.drawLine(i, y, i + 1, y);
3259 * Draws a dashed vertical line.
3261 * @param g - the graphics configuration.
3262 * @param x - the x location to start drawing at
3263 * @param y1 - the y location to start drawing at
3264 * @param y2 - the y location to finish drawing at
3266 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2)
3268 g.setColor(getHashColor());
3269 for (int i = y1; i < y2; i += 2)
3270 g.drawLine(x, i, x, i + 1);
3274 * Paints the expand (toggle) part of a row. The receiver should NOT modify
3275 * clipBounds, or insets.
3277 * @param g - the graphics configuration
3278 * @param clipBounds -
3279 * @param insets -
3280 * @param bounds - bounds of expand control
3281 * @param path - path to draw control for
3282 * @param row - row to draw control for
3283 * @param isExpanded - is the row expanded
3284 * @param hasBeenExpanded - has the row already been expanded
3285 * @param isLeaf - is the path a leaf
3287 protected void paintExpandControl(Graphics g, Rectangle clipBounds,
3288 Insets insets, Rectangle bounds,
3289 TreePath path, int row, boolean isExpanded,
3290 boolean hasBeenExpanded, boolean isLeaf)
3292 if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf))
3294 Icon icon = getCurrentControlIcon(path);
3295 int iconW = icon.getIconWidth();
3296 int x = bounds.x - iconW - gap;
3297 icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2
3298 - icon.getIconHeight() / 2);
3303 * Paints the horizontal part of the leg. The receiver should NOT modify
3304 * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not
3305 * visible.
3307 * @param g - the graphics configuration
3308 * @param clipBounds -
3309 * @param insets -
3310 * @param bounds - bounds of the cell
3311 * @param path - path to draw leg for
3312 * @param row - row to start drawing at
3313 * @param isExpanded - is the row expanded
3314 * @param hasBeenExpanded - has the row already been expanded
3315 * @param isLeaf - is the path a leaf
3317 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
3318 Insets insets, Rectangle bounds,
3319 TreePath path, int row,
3320 boolean isExpanded,
3321 boolean hasBeenExpanded,
3322 boolean isLeaf)
3324 if (row != 0)
3326 Icon icon = getCurrentControlIcon(path);
3327 int iconW = icon.getIconWidth();
3328 paintHorizontalLine(g, tree, bounds.y + bounds.height / 2,
3329 bounds.x - iconW/2 - gap, bounds.x - gap);
3334 * Paints the vertical part of the leg. The receiver should NOT modify
3335 * clipBounds, insets.
3337 * @param g - the graphics configuration.
3338 * @param clipBounds -
3339 * @param insets -
3340 * @param path - the path to draw the vertical part for.
3342 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
3343 Insets insets, TreePath path)
3345 Rectangle bounds = getPathBounds(tree, path);
3346 TreePath parent = path.getParentPath();
3347 if (parent != null)
3349 Rectangle parentBounds = getPathBounds(tree, parent);
3350 paintVerticalLine(g, tree, parentBounds.x + 2* gap,
3351 parentBounds.y + parentBounds.height / 2,
3352 bounds.y + bounds.height / 2);
3357 * Paints the renderer part of a row. The receiver should NOT modify
3358 * clipBounds, or insets.
3360 * @param g - the graphics configuration
3361 * @param clipBounds -
3362 * @param insets -
3363 * @param bounds - bounds of expand control
3364 * @param path - path to draw control for
3365 * @param row - row to draw control for
3366 * @param isExpanded - is the row expanded
3367 * @param hasBeenExpanded - has the row already been expanded
3368 * @param isLeaf - is the path a leaf
3370 protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
3371 Rectangle bounds, TreePath path, int row,
3372 boolean isExpanded, boolean hasBeenExpanded,
3373 boolean isLeaf)
3375 boolean selected = tree.isPathSelected(path);
3376 boolean hasIcons = false;
3377 Object node = path.getLastPathComponent();
3379 paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded,
3380 hasBeenExpanded, isLeaf);
3382 TreeCellRenderer dtcr = tree.getCellRenderer();
3383 if (dtcr == null)
3384 dtcr = createDefaultCellRenderer();
3386 boolean focused = false;
3387 if (treeSelectionModel!= null)
3388 focused = treeSelectionModel.getLeadSelectionRow() == row
3389 && tree.isFocusOwner();
3391 Component c = dtcr.getTreeCellRendererComponent(tree, node, selected,
3392 isExpanded, isLeaf, row,
3393 focused);
3395 rendererPane.paintComponent(g, c, c.getParent(), bounds);
3399 * Prepares for the UI to uninstall.
3401 protected void prepareForUIUninstall()
3403 // TODO: Implement this properly.
3407 * Returns true if the expand (toggle) control should be drawn for the
3408 * specified row.
3410 * @param path - current path to check for.
3411 * @param row - current row to check for.
3412 * @param isExpanded - true if the path is expanded
3413 * @param hasBeenExpanded - true if the path has been expanded already
3414 * @param isLeaf - true if the row is a lead
3416 protected boolean shouldPaintExpandControl(TreePath path, int row,
3417 boolean isExpanded,
3418 boolean hasBeenExpanded,
3419 boolean isLeaf)
3421 Object node = path.getLastPathComponent();
3422 return (! isLeaf && hasControlIcons());
3426 * Finish the editing session.
3428 void finish()
3430 treeState.invalidatePathBounds(treeState.getPathForRow(editingRow));
3431 editingPath = null;
3432 editingRow = - 1;
3433 stopEditingInCompleteEditing = false;
3434 isEditing = false;
3435 Rectangle bounds = editingComponent.getParent().getBounds();
3436 tree.removeAll();
3437 validCachedPreferredSize = false;
3438 // Repaint the region, where was the editing component.
3439 tree.repaint(bounds);
3440 editingComponent = null;
3441 tree.requestFocus();
3443 } // BasicTreeUI