1 /* BasicTreeUI.java --
2  Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
3 
4  This file is part of GNU Classpath.
5 
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.
10 
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.
15 
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.
20 
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.
25 
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. */
37 
38 
39 package javax.swing.plaf.basic;
40 
41 import gnu.javax.swing.tree.GnuPath;
42 
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.Container;
46 import java.awt.Dimension;
47 import java.awt.Graphics;
48 import java.awt.Insets;
49 import java.awt.Label;
50 import java.awt.Point;
51 import java.awt.Rectangle;
52 import java.awt.event.ActionEvent;
53 import java.awt.event.ActionListener;
54 import java.awt.event.ComponentAdapter;
55 import java.awt.event.ComponentEvent;
56 import java.awt.event.ComponentListener;
57 import java.awt.event.FocusEvent;
58 import java.awt.event.FocusListener;
59 import java.awt.event.InputEvent;
60 import java.awt.event.KeyAdapter;
61 import java.awt.event.KeyEvent;
62 import java.awt.event.KeyListener;
63 import java.awt.event.MouseAdapter;
64 import java.awt.event.MouseEvent;
65 import java.awt.event.MouseListener;
66 import java.awt.event.MouseMotionListener;
67 import java.beans.PropertyChangeEvent;
68 import java.beans.PropertyChangeListener;
69 import java.util.Enumeration;
70 import java.util.Hashtable;
71 
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.CellRendererPane;
76 import javax.swing.Icon;
77 import javax.swing.InputMap;
78 import javax.swing.JComponent;
79 import javax.swing.JScrollBar;
80 import javax.swing.JScrollPane;
81 import javax.swing.JTree;
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.TreeUI;
98 import javax.swing.tree.AbstractLayoutCache;
99 import javax.swing.tree.DefaultTreeCellEditor;
100 import javax.swing.tree.DefaultTreeCellRenderer;
101 import javax.swing.tree.TreeCellEditor;
102 import javax.swing.tree.TreeCellRenderer;
103 import javax.swing.tree.TreeModel;
104 import javax.swing.tree.TreeNode;
105 import javax.swing.tree.TreePath;
106 import javax.swing.tree.TreeSelectionModel;
107 import javax.swing.tree.VariableHeightLayoutCache;
108 
109 /**
110  * A delegate providing the user interface for <code>JTree</code> according to
111  * the Basic look and feel.
112  *
113  * @see javax.swing.JTree
114  * @author Lillian Angel (langel@redhat.com)
115  * @author Sascha Brawer (brawer@dandelis.ch)
116  * @author Audrius Meskauskas (audriusa@bioinformatics.org)
117  */
118 public class BasicTreeUI
119   extends TreeUI
120 {
121   /**
122    * The tree cell editing may be started by the single mouse click on the
123    * selected cell. To separate it from the double mouse click, the editing
124    * session starts after this time (in ms) after that single click, and only no
125    * other clicks were performed during that time.
126    */
127   static int WAIT_TILL_EDITING = 900;
128 
129   /** Collapse Icon for the tree. */
130   protected transient Icon collapsedIcon;
131 
132   /** Expanded Icon for the tree. */
133   protected transient Icon expandedIcon;
134 
135   /** Distance between left margin and where vertical dashes will be drawn. */
136   protected int leftChildIndent;
137 
138   /**
139    * Distance between leftChildIndent and where cell contents will be drawn.
140    */
141   protected int rightChildIndent;
142 
143   /**
144    * Total fistance that will be indented. The sum of leftChildIndent and
145    * rightChildIndent .
146    */
147   protected int totalChildIndent;
148 
149   /** Index of the row that was last selected. */
150   protected int lastSelectedRow;
151 
152   /** Component that we're going to be drawing onto. */
153   protected JTree tree;
154 
155   /** Renderer that is being used to do the actual cell drawing. */
156   protected transient TreeCellRenderer currentCellRenderer;
157 
158   /**
159    * Set to true if the renderer that is currently in the tree was created by
160    * this instance.
161    */
162   protected boolean createdRenderer;
163 
164   /** Editor for the tree. */
165   protected transient TreeCellEditor cellEditor;
166 
167   /**
168    * Set to true if editor that is currently in the tree was created by this
169    * instance.
170    */
171   protected boolean createdCellEditor;
172 
173   /**
174    * Set to false when editing and shouldSelectCall() returns true meaning the
175    * node should be selected before editing, used in completeEditing.
176    * GNU Classpath editing is implemented differently, so this value is not
177    * actually read anywhere. However it is always set correctly to maintain
178    * interoperability with the derived classes that read this field.
179    */
180   protected boolean stopEditingInCompleteEditing;
181 
182   /** Used to paint the TreeCellRenderer. */
183   protected CellRendererPane rendererPane;
184 
185   /** Size needed to completely display all the nodes. */
186   protected Dimension preferredSize;
187 
188   /** Minimum size needed to completely display all the nodes. */
189   protected Dimension preferredMinSize;
190 
191   /** Is the preferredSize valid? */
192   protected boolean validCachedPreferredSize;
193 
194   /** Object responsible for handling sizing and expanded issues. */
195   protected AbstractLayoutCache treeState;
196 
197   /** Used for minimizing the drawing of vertical lines. */
198   protected Hashtable<TreePath, Boolean> drawingCache;
199 
200   /**
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.
204    */
205   protected boolean largeModel;
206 
207   /** Responsible for telling the TreeState the size needed for a node. */
208   protected AbstractLayoutCache.NodeDimensions nodeDimensions;
209 
210   /** Used to determine what to display. */
211   protected TreeModel treeModel;
212 
213   /** Model maintaining the selection. */
214   protected TreeSelectionModel treeSelectionModel;
215 
216   /**
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.
220    */
221   protected int depthOffset;
222 
223   /**
224    * When editing, this will be the Component that is doing the actual editing.
225    */
226   protected Component editingComponent;
227 
228   /** Path that is being edited. */
229   protected TreePath editingPath;
230 
231   /**
232    * Row that is being edited. Should only be referenced if editingComponent is
233    * null.
234    */
235   protected int editingRow;
236 
237   /** Set to true if the editor has a different size than the renderer. */
238   protected boolean editorHasDifferentSize;
239 
240   /** Boolean to keep track of editing. */
241   boolean isEditing;
242 
243   /** The current path of the visible nodes in the tree. */
244   TreePath currentVisiblePath;
245 
246   /** The gap between the icon and text. */
247   int gap = 4;
248 
249   /** The max height of the nodes in the tree. */
250   int maxHeight;
251 
252   /** The hash color. */
253   Color hashColor;
254 
255   /** Listeners */
256   PropertyChangeListener propertyChangeListener;
257 
258   FocusListener focusListener;
259 
260   TreeSelectionListener treeSelectionListener;
261 
262   MouseListener mouseListener;
263 
264   KeyListener keyListener;
265 
266   PropertyChangeListener selectionModelPropertyChangeListener;
267 
268   ComponentListener componentListener;
269 
270   CellEditorListener cellEditorListener;
271 
272   TreeExpansionListener treeExpansionListener;
273 
274   TreeModelListener treeModelListener;
275 
276   /**
277    * The zero size icon, used for expand controls, if they are not visible.
278    */
279   static Icon nullIcon;
280 
281   /**
282    * Creates a new BasicTreeUI object.
283    */
BasicTreeUI()284   public BasicTreeUI()
285   {
286     validCachedPreferredSize = false;
287     drawingCache = new Hashtable();
288     nodeDimensions = createNodeDimensions();
289     configureLayoutCache();
290 
291     editingRow = - 1;
292     lastSelectedRow = - 1;
293   }
294 
295   /**
296    * Returns an instance of the UI delegate for the specified component.
297    *
298    * @param c the <code>JComponent</code> for which we need a UI delegate for.
299    * @return the <code>ComponentUI</code> for c.
300    */
createUI(JComponent c)301   public static ComponentUI createUI(JComponent c)
302   {
303     return new BasicTreeUI();
304   }
305 
306   /**
307    * Returns the Hash color.
308    *
309    * @return the <code>Color</code> of the Hash.
310    */
getHashColor()311   protected Color getHashColor()
312   {
313     return hashColor;
314   }
315 
316   /**
317    * Sets the Hash color.
318    *
319    * @param color the <code>Color</code> to set the Hash to.
320    */
setHashColor(Color color)321   protected void setHashColor(Color color)
322   {
323     hashColor = color;
324   }
325 
326   /**
327    * Sets the left child's indent value.
328    *
329    * @param newAmount is the new indent value for the left child.
330    */
setLeftChildIndent(int newAmount)331   public void setLeftChildIndent(int newAmount)
332   {
333     leftChildIndent = newAmount;
334   }
335 
336   /**
337    * Returns the indent value for the left child.
338    *
339    * @return the indent value for the left child.
340    */
getLeftChildIndent()341   public int getLeftChildIndent()
342   {
343     return leftChildIndent;
344   }
345 
346   /**
347    * Sets the right child's indent value.
348    *
349    * @param newAmount is the new indent value for the right child.
350    */
setRightChildIndent(int newAmount)351   public void setRightChildIndent(int newAmount)
352   {
353     rightChildIndent = newAmount;
354   }
355 
356   /**
357    * Returns the indent value for the right child.
358    *
359    * @return the indent value for the right child.
360    */
getRightChildIndent()361   public int getRightChildIndent()
362   {
363     return rightChildIndent;
364   }
365 
366   /**
367    * Sets the expanded icon.
368    *
369    * @param newG is the new expanded icon.
370    */
setExpandedIcon(Icon newG)371   public void setExpandedIcon(Icon newG)
372   {
373     expandedIcon = newG;
374   }
375 
376   /**
377    * Returns the current expanded icon.
378    *
379    * @return the current expanded icon.
380    */
getExpandedIcon()381   public Icon getExpandedIcon()
382   {
383     return expandedIcon;
384   }
385 
386   /**
387    * Sets the collapsed icon.
388    *
389    * @param newG is the new collapsed icon.
390    */
setCollapsedIcon(Icon newG)391   public void setCollapsedIcon(Icon newG)
392   {
393     collapsedIcon = newG;
394   }
395 
396   /**
397    * Returns the current collapsed icon.
398    *
399    * @return the current collapsed icon.
400    */
getCollapsedIcon()401   public Icon getCollapsedIcon()
402   {
403     return collapsedIcon;
404   }
405 
406   /**
407    * Updates the componentListener, if necessary.
408    *
409    * @param largeModel sets this.largeModel to it.
410    */
setLargeModel(boolean largeModel)411   protected void setLargeModel(boolean largeModel)
412   {
413     if (largeModel != this.largeModel)
414       {
415         completeEditing();
416         tree.removeComponentListener(componentListener);
417         this.largeModel = largeModel;
418         tree.addComponentListener(componentListener);
419       }
420   }
421 
422   /**
423    * Returns true if largeModel is set
424    *
425    * @return true if largeModel is set, otherwise false.
426    */
isLargeModel()427   protected boolean isLargeModel()
428   {
429     return largeModel;
430   }
431 
432   /**
433    * Sets the row height.
434    *
435    * @param rowHeight is the height to set this.rowHeight to.
436    */
setRowHeight(int rowHeight)437   protected void setRowHeight(int rowHeight)
438   {
439     completeEditing();
440     if (rowHeight == 0)
441       rowHeight = getMaxHeight(tree);
442     treeState.setRowHeight(rowHeight);
443   }
444 
445   /**
446    * Returns the current row height.
447    *
448    * @return current row height.
449    */
getRowHeight()450   protected int getRowHeight()
451   {
452     return tree.getRowHeight();
453   }
454 
455   /**
456    * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
457    * <code>updateRenderer</code>.
458    *
459    * @param tcr is the new TreeCellRenderer.
460    */
setCellRenderer(TreeCellRenderer tcr)461   protected void setCellRenderer(TreeCellRenderer tcr)
462   {
463     // Finish editing before changing the renderer.
464     completeEditing();
465 
466     // The renderer is set in updateRenderer.
467     updateRenderer();
468 
469     // Refresh the layout if necessary.
470     if (treeState != null)
471       {
472         treeState.invalidateSizes();
473         updateSize();
474       }
475   }
476 
477   /**
478    * Return currentCellRenderer, which will either be the trees renderer, or
479    * defaultCellRenderer, which ever was not null.
480    *
481    * @return the current Cell Renderer
482    */
getCellRenderer()483   protected TreeCellRenderer getCellRenderer()
484   {
485     if (currentCellRenderer != null)
486       return currentCellRenderer;
487 
488     return createDefaultCellRenderer();
489   }
490 
491   /**
492    * Sets the tree's model.
493    *
494    * @param model to set the treeModel to.
495    */
setModel(TreeModel model)496   protected void setModel(TreeModel model)
497   {
498     completeEditing();
499 
500     if (treeModel != null && treeModelListener != null)
501       treeModel.removeTreeModelListener(treeModelListener);
502 
503     treeModel = tree.getModel();
504 
505     if (treeModel != null && treeModelListener != null)
506       treeModel.addTreeModelListener(treeModelListener);
507 
508     if (treeState != null)
509       {
510         treeState.setModel(treeModel);
511         updateLayoutCacheExpandedNodes();
512         updateSize();
513       }
514   }
515 
516   /**
517    * Returns the tree's model
518    *
519    * @return treeModel
520    */
getModel()521   protected TreeModel getModel()
522   {
523     return treeModel;
524   }
525 
526   /**
527    * Sets the root to being visible.
528    *
529    * @param newValue sets the visibility of the root
530    */
setRootVisible(boolean newValue)531   protected void setRootVisible(boolean newValue)
532   {
533     completeEditing();
534     tree.setRootVisible(newValue);
535   }
536 
537   /**
538    * Returns true if the root is visible.
539    *
540    * @return true if the root is visible.
541    */
isRootVisible()542   protected boolean isRootVisible()
543   {
544     return tree.isRootVisible();
545   }
546 
547   /**
548    * Determines whether the node handles are to be displayed.
549    *
550    * @param newValue sets whether or not node handles should be displayed.
551    */
setShowsRootHandles(boolean newValue)552   protected void setShowsRootHandles(boolean newValue)
553   {
554     completeEditing();
555     updateDepthOffset();
556     if (treeState != null)
557       {
558         treeState.invalidateSizes();
559         updateSize();
560       }
561   }
562 
563   /**
564    * Returns true if the node handles are to be displayed.
565    *
566    * @return true if the node handles are to be displayed.
567    */
getShowsRootHandles()568   protected boolean getShowsRootHandles()
569   {
570     return tree.getShowsRootHandles();
571   }
572 
573   /**
574    * Sets the cell editor.
575    *
576    * @param editor to set the cellEditor to.
577    */
setCellEditor(TreeCellEditor editor)578   protected void setCellEditor(TreeCellEditor editor)
579   {
580     updateCellEditor();
581   }
582 
583   /**
584    * Returns the <code>TreeCellEditor</code> for this tree.
585    *
586    * @return the cellEditor for this tree.
587    */
getCellEditor()588   protected TreeCellEditor getCellEditor()
589   {
590     return cellEditor;
591   }
592 
593   /**
594    * Configures the receiver to allow, or not allow, editing.
595    *
596    * @param newValue sets the receiver to allow editing if true.
597    */
setEditable(boolean newValue)598   protected void setEditable(boolean newValue)
599   {
600     updateCellEditor();
601   }
602 
603   /**
604    * Returns true if the receiver allows editing.
605    *
606    * @return true if the receiver allows editing.
607    */
isEditable()608   protected boolean isEditable()
609   {
610     return tree.isEditable();
611   }
612 
613   /**
614    * Resets the selection model. The appropriate listeners are installed on the
615    * model.
616    *
617    * @param newLSM resets the selection model.
618    */
setSelectionModel(TreeSelectionModel newLSM)619   protected void setSelectionModel(TreeSelectionModel newLSM)
620   {
621     completeEditing();
622     if (newLSM != null)
623       {
624         treeSelectionModel = newLSM;
625         tree.setSelectionModel(treeSelectionModel);
626       }
627   }
628 
629   /**
630    * Returns the current selection model.
631    *
632    * @return the current selection model.
633    */
getSelectionModel()634   protected TreeSelectionModel getSelectionModel()
635   {
636     return treeSelectionModel;
637   }
638 
639   /**
640    * Returns the Rectangle enclosing the label portion that the last item in
641    * path will be drawn to. Will return null if any component in path is
642    * currently valid.
643    *
644    * @param tree is the current tree the path will be drawn to.
645    * @param path is the current path the tree to draw to.
646    * @return the Rectangle enclosing the label portion that the last item in the
647    *         path will be drawn to.
648    */
getPathBounds(JTree tree, TreePath path)649   public Rectangle getPathBounds(JTree tree, TreePath path)
650   {
651     Rectangle bounds = null;
652     if (tree != null && treeState != null)
653       {
654         bounds = treeState.getBounds(path, null);
655         Insets i = tree.getInsets();
656         if (bounds != null && i != null)
657           {
658             bounds.x += i.left;
659             bounds.y += i.top;
660           }
661       }
662     return bounds;
663   }
664 
665   /**
666    * Returns the max height of all the nodes in the tree.
667    *
668    * @param tree - the current tree
669    * @return the max height.
670    */
getMaxHeight(JTree tree)671   int getMaxHeight(JTree tree)
672   {
673     if (maxHeight != 0)
674       return maxHeight;
675 
676     Icon e = UIManager.getIcon("Tree.openIcon");
677     Icon c = UIManager.getIcon("Tree.closedIcon");
678     Icon l = UIManager.getIcon("Tree.leafIcon");
679     int rc = getRowCount(tree);
680     int iconHeight = 0;
681 
682     for (int row = 0; row < rc; row++)
683       {
684         if (isLeaf(row))
685           iconHeight = l.getIconHeight();
686         else if (tree.isExpanded(row))
687           iconHeight = e.getIconHeight();
688         else
689           iconHeight = c.getIconHeight();
690 
691         maxHeight = Math.max(maxHeight, iconHeight + gap);
692       }
693 
694     treeState.setRowHeight(maxHeight);
695     return maxHeight;
696   }
697 
698   /**
699    * Get the tree node icon.
700    */
getNodeIcon(TreePath path)701   Icon getNodeIcon(TreePath path)
702   {
703     Object node = path.getLastPathComponent();
704     if (treeModel.isLeaf(node))
705       return UIManager.getIcon("Tree.leafIcon");
706     else if (treeState.getExpandedState(path))
707       return UIManager.getIcon("Tree.openIcon");
708     else
709       return UIManager.getIcon("Tree.closedIcon");
710   }
711 
712   /**
713    * Returns the path for passed in row. If row is not visible null is returned.
714    *
715    * @param tree is the current tree to return path for.
716    * @param row is the row number of the row to return.
717    * @return the path for passed in row. If row is not visible null is returned.
718    */
getPathForRow(JTree tree, int row)719   public TreePath getPathForRow(JTree tree, int row)
720   {
721     return treeState.getPathForRow(row);
722   }
723 
724   /**
725    * Returns the row that the last item identified in path is visible at. Will
726    * return -1 if any of the elments in the path are not currently visible.
727    *
728    * @param tree is the current tree to return the row for.
729    * @param path is the path used to find the row.
730    * @return the row that the last item identified in path is visible at. Will
731    *         return -1 if any of the elments in the path are not currently
732    *         visible.
733    */
getRowForPath(JTree tree, TreePath path)734   public int getRowForPath(JTree tree, TreePath path)
735   {
736     return treeState.getRowForPath(path);
737   }
738 
739   /**
740    * Returns the number of rows that are being displayed.
741    *
742    * @param tree is the current tree to return the number of rows for.
743    * @return the number of rows being displayed.
744    */
getRowCount(JTree tree)745   public int getRowCount(JTree tree)
746   {
747     return treeState.getRowCount();
748   }
749 
750   /**
751    * Returns the path to the node that is closest to x,y. If there is nothing
752    * currently visible this will return null, otherwise it'll always return a
753    * valid path. If you need to test if the returned object is exactly at x,y
754    * you should get the bounds for the returned path and test x,y against that.
755    *
756    * @param tree the tree to search for the closest path
757    * @param x is the x coordinate of the location to search
758    * @param y is the y coordinate of the location to search
759    * @return the tree path closes to x,y.
760    */
getClosestPathForLocation(JTree tree, int x, int y)761   public TreePath getClosestPathForLocation(JTree tree, int x, int y)
762   {
763     return treeState.getPathClosestTo(x, y);
764   }
765 
766   /**
767    * Returns true if the tree is being edited. The item that is being edited can
768    * be returned by getEditingPath().
769    *
770    * @param tree is the tree to check for editing.
771    * @return true if the tree is being edited.
772    */
isEditing(JTree tree)773   public boolean isEditing(JTree tree)
774   {
775     return isEditing;
776   }
777 
778   /**
779    * Stops the current editing session. This has no effect if the tree is not
780    * being edited. Returns true if the editor allows the editing session to
781    * stop.
782    *
783    * @param tree is the tree to stop the editing on
784    * @return true if the editor allows the editing session to stop.
785    */
stopEditing(JTree tree)786   public boolean stopEditing(JTree tree)
787   {
788     boolean ret = false;
789     if (editingComponent != null && cellEditor.stopCellEditing())
790       {
791         completeEditing(false, false, true);
792         ret = true;
793       }
794     return ret;
795   }
796 
797   /**
798    * Cancels the current editing session.
799    *
800    * @param tree is the tree to cancel the editing session on.
801    */
cancelEditing(JTree tree)802   public void cancelEditing(JTree tree)
803   {
804     // There is no need to send the cancel message to the editor,
805     // as the cancellation event itself arrives from it. This would
806     // only be necessary when cancelling the editing programatically.
807     if (editingComponent != null)
808       completeEditing(false, true, false);
809   }
810 
811   /**
812    * Selects the last item in path and tries to edit it. Editing will fail if
813    * the CellEditor won't allow it for the selected item.
814    *
815    * @param tree is the tree to edit on.
816    * @param path is the path in tree to edit on.
817    */
startEditingAtPath(JTree tree, TreePath path)818   public void startEditingAtPath(JTree tree, TreePath path)
819   {
820     tree.scrollPathToVisible(path);
821     if (path != null && tree.isVisible(path))
822       startEditing(path, null);
823   }
824 
825   /**
826    * Returns the path to the element that is being editted.
827    *
828    * @param tree is the tree to get the editing path from.
829    * @return the path that is being edited.
830    */
getEditingPath(JTree tree)831   public TreePath getEditingPath(JTree tree)
832   {
833     return editingPath;
834   }
835 
836   /**
837    * Invoked after the tree instance variable has been set, but before any
838    * default/listeners have been installed.
839    */
prepareForUIInstall()840   protected void prepareForUIInstall()
841   {
842     lastSelectedRow = -1;
843     preferredSize = new Dimension();
844     largeModel = tree.isLargeModel();
845     preferredSize = new Dimension();
846     stopEditingInCompleteEditing = true;
847     setModel(tree.getModel());
848   }
849 
850   /**
851    * Invoked from installUI after all the defaults/listeners have been
852    * installed.
853    */
completeUIInstall()854   protected void completeUIInstall()
855   {
856     setShowsRootHandles(tree.getShowsRootHandles());
857     updateRenderer();
858     updateDepthOffset();
859     setSelectionModel(tree.getSelectionModel());
860     configureLayoutCache();
861     treeState.setRootVisible(tree.isRootVisible());
862     treeSelectionModel.setRowMapper(treeState);
863     updateSize();
864   }
865 
866   /**
867    * Invoked from uninstallUI after all the defaults/listeners have been
868    * uninstalled.
869    */
completeUIUninstall()870   protected void completeUIUninstall()
871   {
872     tree = null;
873   }
874 
875   /**
876    * Installs the subcomponents of the tree, which is the renderer pane.
877    */
installComponents()878   protected void installComponents()
879   {
880     currentCellRenderer = createDefaultCellRenderer();
881     rendererPane = createCellRendererPane();
882     createdRenderer = true;
883     setCellRenderer(currentCellRenderer);
884   }
885 
886   /**
887    * Creates an instance of NodeDimensions that is able to determine the size of
888    * a given node in the tree. The node dimensions must be created before
889    * configuring the layout cache.
890    *
891    * @return the NodeDimensions of a given node in the tree
892    */
createNodeDimensions()893   protected AbstractLayoutCache.NodeDimensions createNodeDimensions()
894   {
895     return new NodeDimensionsHandler();
896   }
897 
898   /**
899    * Creates a listener that is reponsible for the updates the UI based on how
900    * the tree changes.
901    *
902    * @return the PropertyChangeListener that is reposnsible for the updates
903    */
createPropertyChangeListener()904   protected PropertyChangeListener createPropertyChangeListener()
905   {
906     return new PropertyChangeHandler();
907   }
908 
909   /**
910    * Creates the listener responsible for updating the selection based on mouse
911    * events.
912    *
913    * @return the MouseListener responsible for updating.
914    */
createMouseListener()915   protected MouseListener createMouseListener()
916   {
917     return new MouseHandler();
918   }
919 
920   /**
921    * Creates the listener that is responsible for updating the display when
922    * focus is lost/grained.
923    *
924    * @return the FocusListener responsible for updating.
925    */
createFocusListener()926   protected FocusListener createFocusListener()
927   {
928     return new FocusHandler();
929   }
930 
931   /**
932    * Creates the listener reponsible for getting key events from the tree.
933    *
934    * @return the KeyListener responsible for getting key events.
935    */
createKeyListener()936   protected KeyListener createKeyListener()
937   {
938     return new KeyHandler();
939   }
940 
941   /**
942    * Creates the listener responsible for getting property change events from
943    * the selection model.
944    *
945    * @returns the PropertyChangeListener reponsible for getting property change
946    *          events from the selection model.
947    */
createSelectionModelPropertyChangeListener()948   protected PropertyChangeListener createSelectionModelPropertyChangeListener()
949   {
950     return new SelectionModelPropertyChangeHandler();
951   }
952 
953   /**
954    * Creates the listener that updates the display based on selection change
955    * methods.
956    *
957    * @return the TreeSelectionListener responsible for updating.
958    */
createTreeSelectionListener()959   protected TreeSelectionListener createTreeSelectionListener()
960   {
961     return new TreeSelectionHandler();
962   }
963 
964   /**
965    * Creates a listener to handle events from the current editor
966    *
967    * @return the CellEditorListener that handles events from the current editor
968    */
createCellEditorListener()969   protected CellEditorListener createCellEditorListener()
970   {
971     return new CellEditorHandler();
972   }
973 
974   /**
975    * Creates and returns a new ComponentHandler. This is used for the large
976    * model to mark the validCachedPreferredSize as invalid when the component
977    * moves.
978    *
979    * @return a new ComponentHandler.
980    */
createComponentListener()981   protected ComponentListener createComponentListener()
982   {
983     return new ComponentHandler();
984   }
985 
986   /**
987    * Creates and returns the object responsible for updating the treestate when
988    * a nodes expanded state changes.
989    *
990    * @return the TreeExpansionListener responsible for updating the treestate
991    */
createTreeExpansionListener()992   protected TreeExpansionListener createTreeExpansionListener()
993   {
994     return new TreeExpansionHandler();
995   }
996 
997   /**
998    * Creates the object responsible for managing what is expanded, as well as
999    * the size of nodes.
1000    *
1001    * @return the object responsible for managing what is expanded.
1002    */
createLayoutCache()1003   protected AbstractLayoutCache createLayoutCache()
1004   {
1005     return new VariableHeightLayoutCache();
1006   }
1007 
1008   /**
1009    * Returns the renderer pane that renderer components are placed in.
1010    *
1011    * @return the rendererpane that render components are placed in.
1012    */
createCellRendererPane()1013   protected CellRendererPane createCellRendererPane()
1014   {
1015     return new CellRendererPane();
1016   }
1017 
1018   /**
1019    * Creates a default cell editor.
1020    *
1021    * @return the default cell editor.
1022    */
createDefaultCellEditor()1023   protected TreeCellEditor createDefaultCellEditor()
1024   {
1025     DefaultTreeCellEditor ed;
1026     if (currentCellRenderer != null
1027         && currentCellRenderer instanceof DefaultTreeCellRenderer)
1028       ed = new DefaultTreeCellEditor(tree,
1029                                 (DefaultTreeCellRenderer) currentCellRenderer);
1030     else
1031       ed = new DefaultTreeCellEditor(tree, null);
1032     return ed;
1033   }
1034 
1035   /**
1036    * Returns the default cell renderer that is used to do the stamping of each
1037    * node.
1038    *
1039    * @return the default cell renderer that is used to do the stamping of each
1040    *         node.
1041    */
createDefaultCellRenderer()1042   protected TreeCellRenderer createDefaultCellRenderer()
1043   {
1044     return new DefaultTreeCellRenderer();
1045   }
1046 
1047   /**
1048    * Returns a listener that can update the tree when the model changes.
1049    *
1050    * @return a listener that can update the tree when the model changes.
1051    */
createTreeModelListener()1052   protected TreeModelListener createTreeModelListener()
1053   {
1054     return new TreeModelHandler();
1055   }
1056 
1057   /**
1058    * Uninstall all registered listeners
1059    */
uninstallListeners()1060   protected void uninstallListeners()
1061   {
1062     tree.removePropertyChangeListener(propertyChangeListener);
1063     tree.removeFocusListener(focusListener);
1064     tree.removeTreeSelectionListener(treeSelectionListener);
1065     tree.removeMouseListener(mouseListener);
1066     tree.removeKeyListener(keyListener);
1067     tree.removePropertyChangeListener(selectionModelPropertyChangeListener);
1068     tree.removeComponentListener(componentListener);
1069     tree.removeTreeExpansionListener(treeExpansionListener);
1070 
1071     TreeCellEditor tce = tree.getCellEditor();
1072     if (tce != null)
1073       tce.removeCellEditorListener(cellEditorListener);
1074     if (treeModel != null)
1075       treeModel.removeTreeModelListener(treeModelListener);
1076   }
1077 
1078   /**
1079    * Uninstall all keyboard actions.
1080    */
uninstallKeyboardActions()1081   protected void uninstallKeyboardActions()
1082   {
1083     tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1084                                                                               null);
1085     tree.getActionMap().setParent(null);
1086   }
1087 
1088   /**
1089    * Uninstall the rendererPane.
1090    */
uninstallComponents()1091   protected void uninstallComponents()
1092   {
1093     currentCellRenderer = null;
1094     rendererPane = null;
1095     createdRenderer = false;
1096     setCellRenderer(currentCellRenderer);
1097   }
1098 
1099   /**
1100    * The vertical element of legs between nodes starts at the bottom of the
1101    * parent node by default. This method makes the leg start below that.
1102    *
1103    * @return the vertical leg buffer
1104    */
getVerticalLegBuffer()1105   protected int getVerticalLegBuffer()
1106   {
1107     return getRowHeight() / 2;
1108   }
1109 
1110   /**
1111    * The horizontal element of legs between nodes starts at the right of the
1112    * left-hand side of the child node by default. This method makes the leg end
1113    * before that.
1114    *
1115    * @return the horizontal leg buffer
1116    */
getHorizontalLegBuffer()1117   protected int getHorizontalLegBuffer()
1118   {
1119     return rightChildIndent / 2;
1120   }
1121 
1122   /**
1123    * Make all the nodes that are expanded in JTree expanded in LayoutCache. This
1124    * invokes updateExpandedDescendants with the root path.
1125    */
updateLayoutCacheExpandedNodes()1126   protected void updateLayoutCacheExpandedNodes()
1127   {
1128     if (treeModel != null && treeModel.getRoot() != null)
1129       updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1130   }
1131 
1132   /**
1133    * Updates the expanded state of all the descendants of the <code>path</code>
1134    * by getting the expanded descendants from the tree and forwarding to the
1135    * tree state.
1136    *
1137    * @param path the path used to update the expanded states
1138    */
updateExpandedDescendants(TreePath path)1139   protected void updateExpandedDescendants(TreePath path)
1140   {
1141     completeEditing();
1142     Enumeration expanded = tree.getExpandedDescendants(path);
1143     while (expanded.hasMoreElements())
1144       treeState.setExpandedState((TreePath) expanded.nextElement(), true);
1145   }
1146 
1147   /**
1148    * Returns a path to the last child of <code>parent</code>
1149    *
1150    * @param parent is the topmost path to specified
1151    * @return a path to the last child of parent
1152    */
getLastChildPath(TreePath parent)1153   protected TreePath getLastChildPath(TreePath parent)
1154   {
1155     return (TreePath) parent.getLastPathComponent();
1156   }
1157 
1158   /**
1159    * Updates how much each depth should be offset by.
1160    */
updateDepthOffset()1161   protected void updateDepthOffset()
1162   {
1163     depthOffset += getVerticalLegBuffer();
1164   }
1165 
1166   /**
1167    * Updates the cellEditor based on editability of the JTree that we're
1168    * contained in. If the tree is editable but doesn't have a cellEditor, a
1169    * basic one will be used.
1170    */
updateCellEditor()1171   protected void updateCellEditor()
1172   {
1173     completeEditing();
1174     TreeCellEditor newEd = null;
1175     if (tree != null && tree.isEditable())
1176       {
1177         newEd = tree.getCellEditor();
1178         if (newEd == null)
1179           {
1180             newEd = createDefaultCellEditor();
1181             if (newEd != null)
1182               {
1183                 tree.setCellEditor(newEd);
1184                 createdCellEditor = true;
1185               }
1186           }
1187       }
1188     // Update listeners.
1189     if (newEd != cellEditor)
1190       {
1191         if (cellEditor != null && cellEditorListener != null)
1192           cellEditor.removeCellEditorListener(cellEditorListener);
1193         cellEditor = newEd;
1194         if (cellEditorListener == null)
1195           cellEditorListener = createCellEditorListener();
1196         if (cellEditor != null && cellEditorListener != null)
1197           cellEditor.addCellEditorListener(cellEditorListener);
1198         createdCellEditor = false;
1199       }
1200   }
1201 
1202   /**
1203    * Messaged from the tree we're in when the renderer has changed.
1204    */
updateRenderer()1205   protected void updateRenderer()
1206   {
1207     if (tree != null)
1208       {
1209         TreeCellRenderer rend = tree.getCellRenderer();
1210         if (rend != null)
1211           {
1212             createdRenderer = false;
1213             currentCellRenderer = rend;
1214             if (createdCellEditor)
1215               tree.setCellEditor(null);
1216           }
1217         else
1218           {
1219             tree.setCellRenderer(createDefaultCellRenderer());
1220             createdRenderer = true;
1221           }
1222       }
1223     else
1224       {
1225         currentCellRenderer = null;
1226         createdRenderer = false;
1227       }
1228 
1229     updateCellEditor();
1230   }
1231 
1232   /**
1233    * Resets the treeState instance based on the tree we're providing the look
1234    * and feel for. The node dimensions handler is required and must be created
1235    * in advance.
1236    */
configureLayoutCache()1237   protected void configureLayoutCache()
1238   {
1239     treeState = createLayoutCache();
1240     treeState.setNodeDimensions(nodeDimensions);
1241   }
1242 
1243   /**
1244    * Marks the cached size as being invalid, and messages the tree with
1245    * <code>treeDidChange</code>.
1246    */
updateSize()1247   protected void updateSize()
1248   {
1249     preferredSize = null;
1250     updateCachedPreferredSize();
1251     tree.treeDidChange();
1252   }
1253 
1254   /**
1255    * Updates the <code>preferredSize</code> instance variable, which is
1256    * returned from <code>getPreferredSize()</code>.
1257    */
updateCachedPreferredSize()1258   protected void updateCachedPreferredSize()
1259   {
1260     validCachedPreferredSize = false;
1261   }
1262 
1263   /**
1264    * Messaged from the VisibleTreeNode after it has been expanded.
1265    *
1266    * @param path is the path that has been expanded.
1267    */
pathWasExpanded(TreePath path)1268   protected void pathWasExpanded(TreePath path)
1269   {
1270     validCachedPreferredSize = false;
1271     treeState.setExpandedState(path, true);
1272     tree.repaint();
1273   }
1274 
1275   /**
1276    * Messaged from the VisibleTreeNode after it has collapsed
1277    */
pathWasCollapsed(TreePath path)1278   protected void pathWasCollapsed(TreePath path)
1279   {
1280     validCachedPreferredSize = false;
1281     treeState.setExpandedState(path, false);
1282     tree.repaint();
1283   }
1284 
1285   /**
1286    * Install all defaults for the tree.
1287    */
installDefaults()1288   protected void installDefaults()
1289   {
1290     LookAndFeel.installColorsAndFont(tree, "Tree.background",
1291                                      "Tree.foreground", "Tree.font");
1292 
1293     hashColor = UIManager.getColor("Tree.hash");
1294     if (hashColor == null)
1295       hashColor = Color.black;
1296 
1297     tree.setOpaque(true);
1298 
1299     rightChildIndent = UIManager.getInt("Tree.rightChildIndent");
1300     leftChildIndent = UIManager.getInt("Tree.leftChildIndent");
1301     totalChildIndent = rightChildIndent + leftChildIndent;
1302     setRowHeight(UIManager.getInt("Tree.rowHeight"));
1303     tree.setRowHeight(getRowHeight());
1304     tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand"));
1305     setExpandedIcon(UIManager.getIcon("Tree.expandedIcon"));
1306     setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon"));
1307   }
1308 
1309   /**
1310    * Install all keyboard actions for this
1311    */
installKeyboardActions()1312   protected void installKeyboardActions()
1313   {
1314     InputMap focusInputMap =
1315       (InputMap) SharedUIDefaults.get("Tree.focusInputMap");
1316     SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED,
1317                                      focusInputMap);
1318     InputMap ancestorInputMap =
1319       (InputMap) SharedUIDefaults.get("Tree.ancestorInputMap");
1320     SwingUtilities.replaceUIInputMap(tree,
1321                                  JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1322                                  ancestorInputMap);
1323 
1324     SwingUtilities.replaceUIActionMap(tree, getActionMap());
1325   }
1326 
1327   /**
1328    * Creates and returns the shared action map for JTrees.
1329    *
1330    * @return the shared action map for JTrees
1331    */
getActionMap()1332   private ActionMap getActionMap()
1333   {
1334     ActionMap am = (ActionMap) UIManager.get("Tree.actionMap");
1335     if (am == null)
1336       {
1337         am = createDefaultActions();
1338         UIManager.getLookAndFeelDefaults().put("Tree.actionMap", am);
1339       }
1340     return am;
1341   }
1342 
1343   /**
1344    * Creates the default actions when there are none specified by the L&F.
1345    *
1346    * @return the default actions
1347    */
createDefaultActions()1348   private ActionMap createDefaultActions()
1349   {
1350     ActionMapUIResource am = new ActionMapUIResource();
1351     Action action;
1352 
1353     // TreeHomeAction.
1354     action = new TreeHomeAction(-1, "selectFirst");
1355     am.put(action.getValue(Action.NAME), action);
1356     action = new TreeHomeAction(-1, "selectFirstChangeLead");
1357     am.put(action.getValue(Action.NAME), action);
1358     action = new TreeHomeAction(-1, "selectFirstExtendSelection");
1359     am.put(action.getValue(Action.NAME), action);
1360     action = new TreeHomeAction(1, "selectLast");
1361     am.put(action.getValue(Action.NAME), action);
1362     action = new TreeHomeAction(1, "selectLastChangeLead");
1363     am.put(action.getValue(Action.NAME), action);
1364     action = new TreeHomeAction(1, "selectLastExtendSelection");
1365     am.put(action.getValue(Action.NAME), action);
1366 
1367     // TreeIncrementAction.
1368     action = new TreeIncrementAction(-1, "selectPrevious");
1369     am.put(action.getValue(Action.NAME), action);
1370     action = new TreeIncrementAction(-1, "selectPreviousExtendSelection");
1371     am.put(action.getValue(Action.NAME), action);
1372     action = new TreeIncrementAction(-1, "selectPreviousChangeLead");
1373     am.put(action.getValue(Action.NAME), action);
1374     action = new TreeIncrementAction(1, "selectNext");
1375     am.put(action.getValue(Action.NAME), action);
1376     action = new TreeIncrementAction(1, "selectNextExtendSelection");
1377     am.put(action.getValue(Action.NAME), action);
1378     action = new TreeIncrementAction(1, "selectNextChangeLead");
1379     am.put(action.getValue(Action.NAME), action);
1380 
1381     // TreeTraverseAction.
1382     action = new TreeTraverseAction(-1, "selectParent");
1383     am.put(action.getValue(Action.NAME), action);
1384     action = new TreeTraverseAction(1, "selectChild");
1385     am.put(action.getValue(Action.NAME), action);
1386 
1387     // TreeToggleAction.
1388     action = new TreeToggleAction("toggleAndAnchor");
1389     am.put(action.getValue(Action.NAME), action);
1390 
1391     // TreePageAction.
1392     action = new TreePageAction(-1, "scrollUpChangeSelection");
1393     am.put(action.getValue(Action.NAME), action);
1394     action = new TreePageAction(-1, "scrollUpExtendSelection");
1395     am.put(action.getValue(Action.NAME), action);
1396     action = new TreePageAction(-1, "scrollUpChangeLead");
1397     am.put(action.getValue(Action.NAME), action);
1398     action = new TreePageAction(1, "scrollDownChangeSelection");
1399     am.put(action.getValue(Action.NAME), action);
1400     action = new TreePageAction(1, "scrollDownExtendSelection");
1401     am.put(action.getValue(Action.NAME), action);
1402     action = new TreePageAction(1, "scrollDownChangeLead");
1403     am.put(action.getValue(Action.NAME), action);
1404 
1405     // Tree editing actions
1406     action = new TreeStartEditingAction("startEditing");
1407     am.put(action.getValue(Action.NAME), action);
1408     action = new TreeCancelEditingAction("cancel");
1409     am.put(action.getValue(Action.NAME), action);
1410 
1411 
1412     return am;
1413   }
1414 
1415   /**
1416    * Converts the modifiers.
1417    *
1418    * @param mod - modifier to convert
1419    * @returns the new modifier
1420    */
convertModifiers(int mod)1421   private int convertModifiers(int mod)
1422   {
1423     if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
1424       {
1425         mod |= KeyEvent.SHIFT_MASK;
1426         mod &= ~ KeyEvent.SHIFT_DOWN_MASK;
1427       }
1428     if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
1429       {
1430         mod |= KeyEvent.CTRL_MASK;
1431         mod &= ~ KeyEvent.CTRL_DOWN_MASK;
1432       }
1433     if ((mod & KeyEvent.META_DOWN_MASK) != 0)
1434       {
1435         mod |= KeyEvent.META_MASK;
1436         mod &= ~ KeyEvent.META_DOWN_MASK;
1437       }
1438     if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
1439       {
1440         mod |= KeyEvent.ALT_MASK;
1441         mod &= ~ KeyEvent.ALT_DOWN_MASK;
1442       }
1443     if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
1444       {
1445         mod |= KeyEvent.ALT_GRAPH_MASK;
1446         mod &= ~ KeyEvent.ALT_GRAPH_DOWN_MASK;
1447       }
1448     return mod;
1449   }
1450 
1451   /**
1452    * Install all listeners for this
1453    */
installListeners()1454   protected void installListeners()
1455   {
1456     propertyChangeListener = createPropertyChangeListener();
1457     tree.addPropertyChangeListener(propertyChangeListener);
1458 
1459     focusListener = createFocusListener();
1460     tree.addFocusListener(focusListener);
1461 
1462     treeSelectionListener = createTreeSelectionListener();
1463     tree.addTreeSelectionListener(treeSelectionListener);
1464 
1465     mouseListener = createMouseListener();
1466     tree.addMouseListener(mouseListener);
1467 
1468     keyListener = createKeyListener();
1469     tree.addKeyListener(keyListener);
1470 
1471     selectionModelPropertyChangeListener =
1472       createSelectionModelPropertyChangeListener();
1473     if (treeSelectionModel != null
1474         && selectionModelPropertyChangeListener != null)
1475       {
1476         treeSelectionModel.addPropertyChangeListener(
1477             selectionModelPropertyChangeListener);
1478       }
1479 
1480     componentListener = createComponentListener();
1481     tree.addComponentListener(componentListener);
1482 
1483     treeExpansionListener = createTreeExpansionListener();
1484     tree.addTreeExpansionListener(treeExpansionListener);
1485 
1486     treeModelListener = createTreeModelListener();
1487     if (treeModel != null)
1488       treeModel.addTreeModelListener(treeModelListener);
1489 
1490     cellEditorListener = createCellEditorListener();
1491   }
1492 
1493   /**
1494    * Install the UI for the component
1495    *
1496    * @param c the component to install UI for
1497    */
installUI(JComponent c)1498   public void installUI(JComponent c)
1499   {
1500     tree = (JTree) c;
1501 
1502     prepareForUIInstall();
1503     installDefaults();
1504     installComponents();
1505     installKeyboardActions();
1506     installListeners();
1507     completeUIInstall();
1508   }
1509 
1510   /**
1511    * Uninstall the defaults for the tree
1512    */
uninstallDefaults()1513   protected void uninstallDefaults()
1514   {
1515     tree.setFont(null);
1516     tree.setForeground(null);
1517     tree.setBackground(null);
1518   }
1519 
1520   /**
1521    * Uninstall the UI for the component
1522    *
1523    * @param c the component to uninstall UI for
1524    */
uninstallUI(JComponent c)1525   public void uninstallUI(JComponent c)
1526   {
1527     completeEditing();
1528 
1529     prepareForUIUninstall();
1530     uninstallDefaults();
1531     uninstallKeyboardActions();
1532     uninstallListeners();
1533     uninstallComponents();
1534     completeUIUninstall();
1535   }
1536 
1537   /**
1538    * Paints the specified component appropriate for the look and feel. This
1539    * method is invoked from the ComponentUI.update method when the specified
1540    * component is being painted. Subclasses should override this method and use
1541    * the specified Graphics object to render the content of the component.
1542    *
1543    * @param g the Graphics context in which to paint
1544    * @param c the component being painted; this argument is often ignored, but
1545    *          might be used if the UI object is stateless and shared by multiple
1546    *          components
1547    */
paint(Graphics g, JComponent c)1548   public void paint(Graphics g, JComponent c)
1549   {
1550     JTree tree = (JTree) c;
1551 
1552     int rows = treeState.getRowCount();
1553 
1554     if (rows == 0)
1555       // There is nothing to do if the tree is empty.
1556       return;
1557 
1558     Rectangle clip = g.getClipBounds();
1559 
1560     Insets insets = tree.getInsets();
1561 
1562     if (clip != null && treeModel != null)
1563       {
1564         int startIndex = tree.getClosestRowForLocation(clip.x, clip.y);
1565         int endIndex = tree.getClosestRowForLocation(clip.x + clip.width,
1566                                                      clip.y + clip.height);
1567         // Also paint dashes to the invisible nodes below.
1568         // These should be painted first, otherwise they may cover
1569         // the control icons.
1570         if (endIndex < rows)
1571           for (int i = endIndex + 1; i < rows; i++)
1572             {
1573               TreePath path = treeState.getPathForRow(i);
1574               if (isLastChild(path))
1575                 paintVerticalPartOfLeg(g, clip, insets, path);
1576             }
1577 
1578         // The two loops are required to ensure that the lines are not
1579         // painted over the other tree components.
1580 
1581         int n = endIndex - startIndex + 1;
1582         Rectangle[] bounds = new Rectangle[n];
1583         boolean[] isLeaf = new boolean[n];
1584         boolean[] isExpanded = new boolean[n];
1585         TreePath[] path = new TreePath[n];
1586         int k;
1587 
1588         k = 0;
1589         for (int i = startIndex; i <= endIndex; i++, k++)
1590           {
1591             path[k] = treeState.getPathForRow(i);
1592             if (path[k] != null)
1593               {
1594                 isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent());
1595                 isExpanded[k] = tree.isExpanded(path[k]);
1596                 bounds[k] = getPathBounds(tree, path[k]);
1597 
1598                 paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k],
1599                                          i, isExpanded[k], false, isLeaf[k]);
1600               }
1601             if (isLastChild(path[k]))
1602               paintVerticalPartOfLeg(g, clip, insets, path[k]);
1603           }
1604 
1605         k = 0;
1606         for (int i = startIndex; i <= endIndex; i++, k++)
1607           {
1608             if (path[k] != null)
1609               paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k],
1610                        false, isLeaf[k]);
1611           }
1612       }
1613   }
1614 
1615   /**
1616    * Check if the path is referring to the last child of some parent.
1617    */
isLastChild(TreePath path)1618   private boolean isLastChild(TreePath path)
1619   {
1620     if (path == null)
1621       return false;
1622     else if (path instanceof GnuPath)
1623       {
1624         // Except the seldom case when the layout cache is changed, this
1625         // optimized code will be executed.
1626         return ((GnuPath) path).isLastChild;
1627       }
1628     else
1629       {
1630         // Non optimized general case.
1631         TreePath parent = path.getParentPath();
1632         if (parent == null)
1633           return false;
1634         int childCount = treeState.getVisibleChildCount(parent);
1635         int p = treeModel.getIndexOfChild(parent, path.getLastPathComponent());
1636         return p == childCount - 1;
1637       }
1638   }
1639 
1640   /**
1641    * Ensures that the rows identified by beginRow through endRow are visible.
1642    *
1643    * @param beginRow is the first row
1644    * @param endRow is the last row
1645    */
ensureRowsAreVisible(int beginRow, int endRow)1646   protected void ensureRowsAreVisible(int beginRow, int endRow)
1647   {
1648     if (beginRow < endRow)
1649       {
1650         int temp = endRow;
1651         endRow = beginRow;
1652         beginRow = temp;
1653       }
1654 
1655     for (int i = beginRow; i < endRow; i++)
1656       {
1657         TreePath path = getPathForRow(tree, i);
1658         if (! tree.isVisible(path))
1659           tree.makeVisible(path);
1660       }
1661   }
1662 
1663   /**
1664    * Sets the preferred minimum size.
1665    *
1666    * @param newSize is the new preferred minimum size.
1667    */
setPreferredMinSize(Dimension newSize)1668   public void setPreferredMinSize(Dimension newSize)
1669   {
1670     preferredMinSize = newSize;
1671   }
1672 
1673   /**
1674    * Gets the preferred minimum size.
1675    *
1676    * @returns the preferred minimum size.
1677    */
getPreferredMinSize()1678   public Dimension getPreferredMinSize()
1679   {
1680     if (preferredMinSize == null)
1681       return getPreferredSize(tree);
1682     else
1683       return preferredMinSize;
1684   }
1685 
1686   /**
1687    * Returns the preferred size to properly display the tree, this is a cover
1688    * method for getPreferredSize(c, false).
1689    *
1690    * @param c the component whose preferred size is being queried; this argument
1691    *          is often ignored but might be used if the UI object is stateless
1692    *          and shared by multiple components
1693    * @return the preferred size
1694    */
getPreferredSize(JComponent c)1695   public Dimension getPreferredSize(JComponent c)
1696   {
1697     return getPreferredSize(c, false);
1698   }
1699 
1700   /**
1701    * Returns the preferred size to represent the tree in c. If checkConsistancy
1702    * is true, checkConsistancy is messaged first.
1703    *
1704    * @param c the component whose preferred size is being queried.
1705    * @param checkConsistancy if true must check consistancy
1706    * @return the preferred size
1707    */
getPreferredSize(JComponent c, boolean checkConsistancy)1708   public Dimension getPreferredSize(JComponent c, boolean checkConsistancy)
1709   {
1710     if (! validCachedPreferredSize)
1711       {
1712         Rectangle size = tree.getBounds();
1713         // Add the scrollbar dimensions to the preferred size.
1714         preferredSize = new Dimension(treeState.getPreferredWidth(size),
1715                                       treeState.getPreferredHeight());
1716         validCachedPreferredSize = true;
1717       }
1718     return preferredSize;
1719   }
1720 
1721   /**
1722    * Returns the minimum size for this component. Which will be the min
1723    * preferred size or (0,0).
1724    *
1725    * @param c the component whose min size is being queried.
1726    * @returns the preferred size or null
1727    */
getMinimumSize(JComponent c)1728   public Dimension getMinimumSize(JComponent c)
1729   {
1730     return preferredMinSize = getPreferredSize(c);
1731   }
1732 
1733   /**
1734    * Returns the maximum size for the component, which will be the preferred
1735    * size if the instance is currently in JTree or (0,0).
1736    *
1737    * @param c the component whose preferred size is being queried
1738    * @return the max size or null
1739    */
getMaximumSize(JComponent c)1740   public Dimension getMaximumSize(JComponent c)
1741   {
1742     return getPreferredSize(c);
1743   }
1744 
1745   /**
1746    * Messages to stop the editing session. If the UI the receiver is providing
1747    * the look and feel for returns true from
1748    * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked
1749    * on the current editor. Then completeEditing will be messaged with false,
1750    * true, false to cancel any lingering editing.
1751    */
completeEditing()1752   protected void completeEditing()
1753   {
1754     if (tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing
1755         && editingComponent != null)
1756       cellEditor.stopCellEditing();
1757 
1758     completeEditing(false, true, false);
1759   }
1760 
1761   /**
1762    * Stops the editing session. If messageStop is true, the editor is messaged
1763    * with stopEditing, if messageCancel is true the editor is messaged with
1764    * cancelEditing. If messageTree is true, the treeModel is messaged with
1765    * valueForPathChanged.
1766    *
1767    * @param messageStop message to stop editing
1768    * @param messageCancel message to cancel editing
1769    * @param messageTree message to treeModel
1770    */
completeEditing(boolean messageStop, boolean messageCancel, boolean messageTree)1771   protected void completeEditing(boolean messageStop, boolean messageCancel,
1772                                  boolean messageTree)
1773   {
1774     // Make no attempt to complete the non existing editing session.
1775     if (stopEditingInCompleteEditing && editingComponent != null)
1776       {
1777         Component comp = editingComponent;
1778         TreePath p = editingPath;
1779         editingComponent = null;
1780         editingPath = null;
1781         if (messageStop)
1782           cellEditor.stopCellEditing();
1783         else if (messageCancel)
1784           cellEditor.cancelCellEditing();
1785 
1786         tree.remove(comp);
1787 
1788         if (editorHasDifferentSize)
1789           {
1790             treeState.invalidatePathBounds(p);
1791             updateSize();
1792           }
1793         else
1794           {
1795             // Need to refresh the tree.
1796             Rectangle b = getPathBounds(tree, p);
1797             tree.repaint(0, b.y, tree.getWidth(), b.height);
1798           }
1799 
1800         if (messageTree)
1801           {
1802             Object value = cellEditor.getCellEditorValue();
1803             treeModel.valueForPathChanged(p, value);
1804           }
1805       }
1806   }
1807 
1808   /**
1809    * Will start editing for node if there is a cellEditor and shouldSelectCall
1810    * returns true. This assumes that path is valid and visible.
1811    *
1812    * @param path is the path to start editing
1813    * @param event is the MouseEvent performed on the path
1814    * @return true if successful
1815    */
startEditing(TreePath path, MouseEvent event)1816   protected boolean startEditing(TreePath path, MouseEvent event)
1817   {
1818     // Maybe cancel editing.
1819     if (isEditing(tree) && tree.getInvokesStopCellEditing()
1820         && ! stopEditing(tree))
1821       return false;
1822 
1823     completeEditing();
1824     TreeCellEditor ed = cellEditor;
1825     if (ed != null && tree.isPathEditable(path))
1826       {
1827         if (ed.isCellEditable(event))
1828           {
1829             editingRow = getRowForPath(tree, path);
1830             Object value = path.getLastPathComponent();
1831             boolean isSelected = tree.isPathSelected(path);
1832             boolean isExpanded = tree.isExpanded(editingPath);
1833             boolean isLeaf = treeModel.isLeaf(value);
1834             editingComponent = ed.getTreeCellEditorComponent(tree, value,
1835                                                              isSelected,
1836                                                              isExpanded,
1837                                                              isLeaf,
1838                                                              editingRow);
1839 
1840             Rectangle bounds = getPathBounds(tree, path);
1841 
1842             Dimension size = editingComponent.getPreferredSize();
1843             int rowHeight = getRowHeight();
1844             if (size.height != bounds.height && rowHeight > 0)
1845               size.height = rowHeight;
1846 
1847             if (size.width != bounds.width || size.height != bounds.height)
1848               {
1849                 editorHasDifferentSize = true;
1850                 treeState.invalidatePathBounds(path);
1851                 updateSize();
1852               }
1853             else
1854               editorHasDifferentSize = false;
1855 
1856             // The editing component must be added to its container. We add the
1857             // container, not the editing component itself.
1858             tree.add(editingComponent);
1859             editingComponent.setBounds(bounds.x, bounds.y, size.width,
1860                                        size.height);
1861             editingComponent.validate();
1862             editingPath = path;
1863 
1864             if (ed.shouldSelectCell(event))
1865               {
1866                 stopEditingInCompleteEditing = false;
1867                 tree.setSelectionRow(editingRow);
1868                 stopEditingInCompleteEditing = true;
1869               }
1870 
1871             editorRequestFocus(editingComponent);
1872             // Register MouseInputHandler to redispatch initial mouse events
1873             // correctly.
1874             if (event instanceof MouseEvent)
1875               {
1876                 Point p = SwingUtilities.convertPoint(tree, event.getX(), event.getY(),
1877                                                       editingComponent);
1878                 Component active =
1879                   SwingUtilities.getDeepestComponentAt(editingComponent, p.x, p.y);
1880                 if (active != null)
1881                   {
1882                     MouseInputHandler ih = new MouseInputHandler(tree, active, event);
1883 
1884                   }
1885               }
1886 
1887             return true;
1888           }
1889         else
1890           editingComponent = null;
1891       }
1892     return false;
1893   }
1894 
1895   /**
1896    * Requests focus on the editor. The method is necessary since the
1897    * DefaultTreeCellEditor returns a container that contains the
1898    * actual editor, and we want to request focus on the editor, not the
1899    * container.
1900    */
editorRequestFocus(Component c)1901   private void editorRequestFocus(Component c)
1902   {
1903     if (c instanceof Container)
1904       {
1905         // TODO: Maybe do something more reasonable here, like queriying the
1906         // FocusTraversalPolicy.
1907         Container cont = (Container) c;
1908         if (cont.getComponentCount() > 0)
1909           cont.getComponent(0).requestFocus();
1910       }
1911     else if (c.isFocusable())
1912       c.requestFocus();
1913 
1914   }
1915 
1916   /**
1917    * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or
1918    * collapse region of the row, this will toggle the row.
1919    *
1920    * @param path the path we are concerned with
1921    * @param mouseX is the cursor's x position
1922    * @param mouseY is the cursor's y position
1923    */
checkForClickInExpandControl(TreePath path, int mouseX, int mouseY)1924   protected void checkForClickInExpandControl(TreePath path, int mouseX,
1925                                               int mouseY)
1926   {
1927     if (isLocationInExpandControl(path, mouseX, mouseY))
1928       handleExpandControlClick(path, mouseX, mouseY);
1929   }
1930 
1931   /**
1932    * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in
1933    * the area of row that is used to expand/collpse the node and the node at row
1934    * does not represent a leaf.
1935    *
1936    * @param path the path we are concerned with
1937    * @param mouseX is the cursor's x position
1938    * @param mouseY is the cursor's y position
1939    * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in
1940    *         the area of row that is used to expand/collpse the node and the
1941    *         node at row does not represent a leaf.
1942    */
isLocationInExpandControl(TreePath path, int mouseX, int mouseY)1943   protected boolean isLocationInExpandControl(TreePath path, int mouseX,
1944                                               int mouseY)
1945   {
1946     boolean cntlClick = false;
1947     if (! treeModel.isLeaf(path.getLastPathComponent()))
1948       {
1949         int width;
1950         Icon expandedIcon = getExpandedIcon();
1951         if (expandedIcon != null)
1952           width = expandedIcon.getIconWidth();
1953         else
1954           // Only guessing. This is the width of
1955           // the tree control icon in Metal L&F.
1956           width = 18;
1957 
1958         Insets i = tree.getInsets();
1959 
1960         int depth;
1961         if (isRootVisible())
1962           depth = path.getPathCount()-1;
1963         else
1964           depth = path.getPathCount()-2;
1965 
1966         int left = getRowX(tree.getRowForPath(path), depth)
1967                    - width + i.left;
1968         cntlClick = mouseX >= left && mouseX <= left + width;
1969       }
1970     return cntlClick;
1971   }
1972 
1973   /**
1974    * Messaged when the user clicks the particular row, this invokes
1975    * toggleExpandState.
1976    *
1977    * @param path the path we are concerned with
1978    * @param mouseX is the cursor's x position
1979    * @param mouseY is the cursor's y position
1980    */
handleExpandControlClick(TreePath path, int mouseX, int mouseY)1981   protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY)
1982   {
1983     toggleExpandState(path);
1984   }
1985 
1986   /**
1987    * Expands path if it is not expanded, or collapses row if it is expanded. If
1988    * expanding a path and JTree scroll on expand, ensureRowsAreVisible is
1989    * invoked to scroll as many of the children to visible as possible (tries to
1990    * scroll to last visible descendant of path).
1991    *
1992    * @param path the path we are concerned with
1993    */
toggleExpandState(TreePath path)1994   protected void toggleExpandState(TreePath path)
1995   {
1996     // tree.isExpanded(path) would do the same, but treeState knows faster.
1997     if (treeState.isExpanded(path))
1998       tree.collapsePath(path);
1999     else
2000       tree.expandPath(path);
2001   }
2002 
2003   /**
2004    * Returning true signifies a mouse event on the node should toggle the
2005    * selection of only the row under the mouse. The BasisTreeUI treats the
2006    * event as "toggle selection event" if the CTRL button was pressed while
2007    * clicking. The event is not counted as toggle event if the associated
2008    * tree does not support the multiple selection.
2009    *
2010    * @param event is the MouseEvent performed on the row.
2011    * @return true signifies a mouse event on the node should toggle the
2012    *         selection of only the row under the mouse.
2013    */
isToggleSelectionEvent(MouseEvent event)2014   protected boolean isToggleSelectionEvent(MouseEvent event)
2015   {
2016     return
2017       (tree.getSelectionModel().getSelectionMode() !=
2018         TreeSelectionModel.SINGLE_TREE_SELECTION) &&
2019       ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0);
2020   }
2021 
2022   /**
2023    * Returning true signifies a mouse event on the node should select from the
2024    * anchor point. The BasisTreeUI treats the event as "multiple selection
2025    * event" if the SHIFT button was pressed while clicking. The event is not
2026    * counted as multiple selection event if the associated tree does not support
2027    * the multiple selection.
2028    *
2029    * @param event is the MouseEvent performed on the node.
2030    * @return true signifies a mouse event on the node should select from the
2031    *         anchor point.
2032    */
isMultiSelectEvent(MouseEvent event)2033   protected boolean isMultiSelectEvent(MouseEvent event)
2034   {
2035     return
2036       (tree.getSelectionModel().getSelectionMode() !=
2037         TreeSelectionModel.SINGLE_TREE_SELECTION) &&
2038       ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0);
2039   }
2040 
2041   /**
2042    * Returning true indicates the row under the mouse should be toggled based on
2043    * the event. This is invoked after checkForClickInExpandControl, implying the
2044    * location is not in the expand (toggle) control.
2045    *
2046    * @param event is the MouseEvent performed on the row.
2047    * @return true indicates the row under the mouse should be toggled based on
2048    *         the event.
2049    */
isToggleEvent(MouseEvent event)2050   protected boolean isToggleEvent(MouseEvent event)
2051   {
2052     boolean toggle = false;
2053     if (SwingUtilities.isLeftMouseButton(event))
2054       {
2055         int clickCount = tree.getToggleClickCount();
2056         if (clickCount > 0 && event.getClickCount() == clickCount)
2057           toggle = true;
2058       }
2059     return toggle;
2060   }
2061 
2062   /**
2063    * Messaged to update the selection based on a MouseEvent over a particular
2064    * row. If the even is a toggle selection event, the row is either selected,
2065    * or deselected. If the event identifies a multi selection event, the
2066    * selection is updated from the anchor point. Otherwise, the row is selected,
2067    * and the previous selection is cleared.</p>
2068    *
2069    * @param path is the path selected for an event
2070    * @param event is the MouseEvent performed on the path.
2071    *
2072    * @see #isToggleSelectionEvent(MouseEvent)
2073    * @see #isMultiSelectEvent(MouseEvent)
2074    */
selectPathForEvent(TreePath path, MouseEvent event)2075   protected void selectPathForEvent(TreePath path, MouseEvent event)
2076   {
2077     if (isToggleSelectionEvent(event))
2078       {
2079         // The event selects or unselects the clicked row.
2080         if (tree.isPathSelected(path))
2081           tree.removeSelectionPath(path);
2082         else
2083           {
2084             tree.addSelectionPath(path);
2085             tree.setAnchorSelectionPath(path);
2086           }
2087       }
2088     else if (isMultiSelectEvent(event))
2089       {
2090         // The event extends selection form anchor till the clicked row.
2091         TreePath anchor = tree.getAnchorSelectionPath();
2092         if (anchor != null)
2093           {
2094             int aRow = getRowForPath(tree, anchor);
2095             tree.addSelectionInterval(aRow, getRowForPath(tree, path));
2096           }
2097         else
2098           tree.addSelectionPath(path);
2099       }
2100     else
2101       {
2102         // This is an ordinary event that just selects the clicked row.
2103         tree.setSelectionPath(path);
2104         if (isToggleEvent(event))
2105           toggleExpandState(path);
2106       }
2107   }
2108 
2109   /**
2110    * Returns true if the node at <code>row</code> is a leaf.
2111    *
2112    * @param row is the row we are concerned with.
2113    * @return true if the node at <code>row</code> is a leaf.
2114    */
isLeaf(int row)2115   protected boolean isLeaf(int row)
2116   {
2117     TreePath pathForRow = getPathForRow(tree, row);
2118     if (pathForRow == null)
2119       return true;
2120 
2121     Object node = pathForRow.getLastPathComponent();
2122     return treeModel.isLeaf(node);
2123   }
2124 
2125   /**
2126    * The action to start editing at the current lead selection path.
2127    */
2128   class TreeStartEditingAction
2129       extends AbstractAction
2130   {
2131     /**
2132      * Creates the new tree cancel editing action.
2133      *
2134      * @param name the name of the action (used in toString).
2135      */
TreeStartEditingAction(String name)2136     public TreeStartEditingAction(String name)
2137     {
2138       super(name);
2139     }
2140 
2141     /**
2142      * Start editing at the current lead selection path.
2143      *
2144      * @param e the ActionEvent that caused this action.
2145      */
actionPerformed(ActionEvent e)2146     public void actionPerformed(ActionEvent e)
2147     {
2148       TreePath lead = tree.getLeadSelectionPath();
2149       if (!tree.isEditing())
2150         tree.startEditingAtPath(lead);
2151     }
2152   }
2153 
2154   /**
2155    * Updates the preferred size when scrolling, if necessary.
2156    */
2157   public class ComponentHandler
2158       extends ComponentAdapter
2159       implements ActionListener
2160   {
2161     /**
2162      * Timer used when inside a scrollpane and the scrollbar is adjusting
2163      */
2164     protected Timer timer;
2165 
2166     /** ScrollBar that is being adjusted */
2167     protected JScrollBar scrollBar;
2168 
2169     /**
2170      * Constructor
2171      */
ComponentHandler()2172     public ComponentHandler()
2173     {
2174       // Nothing to do here.
2175     }
2176 
2177     /**
2178      * Invoked when the component's position changes.
2179      *
2180      * @param e the event that occurs when moving the component
2181      */
componentMoved(ComponentEvent e)2182     public void componentMoved(ComponentEvent e)
2183     {
2184       if (timer == null)
2185         {
2186           JScrollPane scrollPane = getScrollPane();
2187           if (scrollPane == null)
2188             updateSize();
2189           else
2190             {
2191               // Determine the scrollbar that is adjusting, if any, and
2192               // start the timer for that. If no scrollbar is adjusting,
2193               // we simply call updateSize().
2194               scrollBar = scrollPane.getVerticalScrollBar();
2195               if (scrollBar == null || !scrollBar.getValueIsAdjusting())
2196                 {
2197                   // It's not the vertical scrollbar, try the horizontal one.
2198                   scrollBar = scrollPane.getHorizontalScrollBar();
2199                   if (scrollBar != null && scrollBar.getValueIsAdjusting())
2200                     startTimer();
2201                   else
2202                     updateSize();
2203                 }
2204               else
2205                 {
2206                   startTimer();
2207                 }
2208             }
2209         }
2210     }
2211 
2212     /**
2213      * Creates, if necessary, and starts a Timer to check if needed to resize
2214      * the bounds
2215      */
startTimer()2216     protected void startTimer()
2217     {
2218       if (timer == null)
2219         {
2220           timer = new Timer(200, this);
2221           timer.setRepeats(true);
2222         }
2223       timer.start();
2224     }
2225 
2226     /**
2227      * Returns the JScrollPane housing the JTree, or null if one isn't found.
2228      *
2229      * @return JScrollPane housing the JTree, or null if one isn't found.
2230      */
getScrollPane()2231     protected JScrollPane getScrollPane()
2232     {
2233       JScrollPane found = null;
2234       Component p = tree.getParent();
2235       while (p != null && !(p instanceof JScrollPane))
2236         p = p.getParent();
2237       if (p instanceof JScrollPane)
2238         found = (JScrollPane) p;
2239       return found;
2240     }
2241 
2242     /**
2243      * Public as a result of Timer. If the scrollBar is null, or not adjusting,
2244      * this stops the timer and updates the sizing.
2245      *
2246      * @param ae is the action performed
2247      */
actionPerformed(ActionEvent ae)2248     public void actionPerformed(ActionEvent ae)
2249     {
2250       if (scrollBar == null || !scrollBar.getValueIsAdjusting())
2251         {
2252           if (timer != null)
2253             timer.stop();
2254           updateSize();
2255           timer = null;
2256           scrollBar = null;
2257         }
2258     }
2259   }
2260 
2261   /**
2262    * Listener responsible for getting cell editing events and updating the tree
2263    * accordingly.
2264    */
2265   public class CellEditorHandler
2266       implements CellEditorListener
2267   {
2268     /**
2269      * Constructor
2270      */
CellEditorHandler()2271     public CellEditorHandler()
2272     {
2273       // Nothing to do here.
2274     }
2275 
2276     /**
2277      * Messaged when editing has stopped in the tree. Tells the listeners
2278      * editing has stopped.
2279      *
2280      * @param e is the notification event
2281      */
editingStopped(ChangeEvent e)2282     public void editingStopped(ChangeEvent e)
2283     {
2284       completeEditing(false, false, true);
2285     }
2286 
2287     /**
2288      * Messaged when editing has been canceled in the tree. This tells the
2289      * listeners the editor has canceled editing.
2290      *
2291      * @param e is the notification event
2292      */
editingCanceled(ChangeEvent e)2293     public void editingCanceled(ChangeEvent e)
2294     {
2295       completeEditing(false, false, false);
2296     }
2297   } // CellEditorHandler
2298 
2299   /**
2300    * Repaints the lead selection row when focus is lost/grained.
2301    */
2302   public class FocusHandler
2303       implements FocusListener
2304   {
2305     /**
2306      * Constructor
2307      */
FocusHandler()2308     public FocusHandler()
2309     {
2310       // Nothing to do here.
2311     }
2312 
2313     /**
2314      * Invoked when focus is activated on the tree we're in, redraws the lead
2315      * row. Invoked when a component gains the keyboard focus. The method
2316      * repaints the lead row that is shown differently when the tree is in
2317      * focus.
2318      *
2319      * @param e is the focus event that is activated
2320      */
focusGained(FocusEvent e)2321     public void focusGained(FocusEvent e)
2322     {
2323       repaintLeadRow();
2324     }
2325 
2326     /**
2327      * Invoked when focus is deactivated on the tree we're in, redraws the lead
2328      * row. Invoked when a component loses the keyboard focus. The method
2329      * repaints the lead row that is shown differently when the tree is in
2330      * focus.
2331      *
2332      * @param e is the focus event that is deactivated
2333      */
focusLost(FocusEvent e)2334     public void focusLost(FocusEvent e)
2335     {
2336       repaintLeadRow();
2337     }
2338 
2339     /**
2340      * Repaint the lead row.
2341      */
repaintLeadRow()2342     void repaintLeadRow()
2343     {
2344       TreePath lead = tree.getLeadSelectionPath();
2345       if (lead != null)
2346         tree.repaint(tree.getPathBounds(lead));
2347     }
2348   }
2349 
2350   /**
2351    * This is used to get multiple key down events to appropriately genereate
2352    * events.
2353    */
2354   public class KeyHandler
2355       extends KeyAdapter
2356   {
2357     /** Key code that is being generated for. */
2358     protected Action repeatKeyAction;
2359 
2360     /** Set to true while keyPressed is active */
2361     protected boolean isKeyDown;
2362 
2363     /**
2364      * Constructor
2365      */
KeyHandler()2366     public KeyHandler()
2367     {
2368       // Nothing to do here.
2369     }
2370 
2371     /**
2372      * Invoked when a key has been typed. Moves the keyboard focus to the first
2373      * element whose first letter matches the alphanumeric key pressed by the
2374      * user. Subsequent same key presses move the keyboard focus to the next
2375      * object that starts with the same letter.
2376      *
2377      * @param e the key typed
2378      */
keyTyped(KeyEvent e)2379     public void keyTyped(KeyEvent e)
2380     {
2381       char typed = Character.toLowerCase(e.getKeyChar());
2382       for (int row = tree.getLeadSelectionRow() + 1;
2383         row < tree.getRowCount(); row++)
2384         {
2385            if (checkMatch(row, typed))
2386              {
2387                tree.setSelectionRow(row);
2388                tree.scrollRowToVisible(row);
2389                return;
2390              }
2391         }
2392 
2393       // Not found below, search above:
2394       for (int row = 0; row < tree.getLeadSelectionRow(); row++)
2395         {
2396            if (checkMatch(row, typed))
2397              {
2398                tree.setSelectionRow(row);
2399                tree.scrollRowToVisible(row);
2400                return;
2401              }
2402         }
2403     }
2404 
2405     /**
2406      * Check if the given tree row starts with this character
2407      *
2408      * @param row the tree row
2409      * @param typed the typed char, must be converted to lowercase
2410      * @return true if the given tree row starts with this character
2411      */
checkMatch(int row, char typed)2412     boolean checkMatch(int row, char typed)
2413     {
2414       TreePath path = treeState.getPathForRow(row);
2415       String node = path.getLastPathComponent().toString();
2416       if (node.length() > 0)
2417         {
2418           char x = node.charAt(0);
2419           if (typed == Character.toLowerCase(x))
2420             return true;
2421         }
2422       return false;
2423     }
2424 
2425     /**
2426      * Invoked when a key has been pressed.
2427      *
2428      * @param e the key pressed
2429      */
keyPressed(KeyEvent e)2430     public void keyPressed(KeyEvent e)
2431     {
2432       // Nothing to do here.
2433     }
2434 
2435     /**
2436      * Invoked when a key has been released
2437      *
2438      * @param e the key released
2439      */
keyReleased(KeyEvent e)2440     public void keyReleased(KeyEvent e)
2441     {
2442       // Nothing to do here.
2443     }
2444   }
2445 
2446   /**
2447    * MouseListener is responsible for updating the selection based on mouse
2448    * events.
2449    */
2450   public class MouseHandler
2451     extends MouseAdapter
2452     implements MouseMotionListener
2453   {
2454 
2455     /**
2456      * If the cell has been selected on mouse press.
2457      */
2458     private boolean selectedOnPress;
2459 
2460     /**
2461      * Constructor
2462      */
MouseHandler()2463     public MouseHandler()
2464     {
2465       // Nothing to do here.
2466     }
2467 
2468     /**
2469      * Invoked when a mouse button has been pressed on a component.
2470      *
2471      * @param e is the mouse event that occured
2472      */
mousePressed(MouseEvent e)2473     public void mousePressed(MouseEvent e)
2474     {
2475       if (! e.isConsumed())
2476         {
2477           handleEvent(e);
2478           selectedOnPress = true;
2479         }
2480       else
2481         {
2482           selectedOnPress = false;
2483         }
2484     }
2485 
2486     /**
2487      * Invoked when a mouse button is pressed on a component and then dragged.
2488      * MOUSE_DRAGGED events will continue to be delivered to the component where
2489      * the drag originated until the mouse button is released (regardless of
2490      * whether the mouse position is within the bounds of the component).
2491      *
2492      * @param e is the mouse event that occured
2493      */
mouseDragged(MouseEvent e)2494     public void mouseDragged(MouseEvent e)
2495     {
2496       // Nothing to do here.
2497     }
2498 
2499     /**
2500      * Invoked when the mouse button has been moved on a component (with no
2501      * buttons no down).
2502      *
2503      * @param e the mouse event that occured
2504      */
mouseMoved(MouseEvent e)2505     public void mouseMoved(MouseEvent e)
2506     {
2507       // Nothing to do here.
2508     }
2509 
2510     /**
2511      * Invoked when a mouse button has been released on a component.
2512      *
2513      * @param e is the mouse event that occured
2514      */
mouseReleased(MouseEvent e)2515     public void mouseReleased(MouseEvent e)
2516     {
2517       if (! e.isConsumed() && ! selectedOnPress)
2518         handleEvent(e);
2519     }
2520 
2521     /**
2522      * Handles press and release events.
2523      *
2524      * @param e the mouse event
2525      */
handleEvent(MouseEvent e)2526     private void handleEvent(MouseEvent e)
2527     {
2528       if (tree != null && tree.isEnabled())
2529         {
2530           // Maybe stop editing.
2531           if (isEditing(tree) && tree.getInvokesStopCellEditing()
2532               && ! stopEditing(tree))
2533             return;
2534 
2535           // Explicitly request focus.
2536           tree.requestFocusInWindow();
2537 
2538           int x = e.getX();
2539           int y = e.getY();
2540           TreePath path = getClosestPathForLocation(tree, x, y);
2541           if (path != null)
2542             {
2543               Rectangle b = getPathBounds(tree, path);
2544               if (y <= b.y + b.height)
2545                 {
2546                   if (SwingUtilities.isLeftMouseButton(e))
2547                     checkForClickInExpandControl(path, x, y);
2548                   if (x > b.x && x <= b.x + b.width)
2549                     {
2550                       if (! startEditing(path, e))
2551                         selectPathForEvent(path, e);
2552                     }
2553                 }
2554             }
2555         }
2556     }
2557   }
2558 
2559   /**
2560    * MouseInputHandler handles passing all mouse events, including mouse motion
2561    * events, until the mouse is released to the destination it is constructed
2562    * with.
2563    */
2564   public class MouseInputHandler
2565       implements MouseInputListener
2566   {
2567     /** Source that events are coming from */
2568     protected Component source;
2569 
2570     /** Destination that receives all events. */
2571     protected Component destination;
2572 
2573     /**
2574      * Constructor
2575      *
2576      * @param source that events are coming from
2577      * @param destination that receives all events
2578      * @param e is the event received
2579      */
MouseInputHandler(Component source, Component destination, MouseEvent e)2580     public MouseInputHandler(Component source, Component destination,
2581                              MouseEvent e)
2582     {
2583       this.source = source;
2584       this.destination = destination;
2585       source.addMouseListener(this);
2586       source.addMouseMotionListener(this);
2587       dispatch(e);
2588     }
2589 
2590     /**
2591      * Invoked when the mouse button has been clicked (pressed and released) on
2592      * a component.
2593      *
2594      * @param e mouse event that occured
2595      */
mouseClicked(MouseEvent e)2596     public void mouseClicked(MouseEvent e)
2597     {
2598       dispatch(e);
2599     }
2600 
2601     /**
2602      * Invoked when a mouse button has been pressed on a component.
2603      *
2604      * @param e mouse event that occured
2605      */
mousePressed(MouseEvent e)2606     public void mousePressed(MouseEvent e)
2607     {
2608       // Nothing to do here.
2609     }
2610 
2611     /**
2612      * Invoked when a mouse button has been released on a component.
2613      *
2614      * @param e mouse event that occured
2615      */
mouseReleased(MouseEvent e)2616     public void mouseReleased(MouseEvent e)
2617     {
2618       dispatch(e);
2619       removeFromSource();
2620     }
2621 
2622     /**
2623      * Invoked when the mouse enters a component.
2624      *
2625      * @param e mouse event that occured
2626      */
mouseEntered(MouseEvent e)2627     public void mouseEntered(MouseEvent e)
2628     {
2629       if (! SwingUtilities.isLeftMouseButton(e))
2630         removeFromSource();
2631     }
2632 
2633     /**
2634      * Invoked when the mouse exits a component.
2635      *
2636      * @param e mouse event that occured
2637      */
mouseExited(MouseEvent e)2638     public void mouseExited(MouseEvent e)
2639     {
2640       if (! SwingUtilities.isLeftMouseButton(e))
2641         removeFromSource();
2642     }
2643 
2644     /**
2645      * Invoked when a mouse button is pressed on a component and then dragged.
2646      * MOUSE_DRAGGED events will continue to be delivered to the component where
2647      * the drag originated until the mouse button is released (regardless of
2648      * whether the mouse position is within the bounds of the component).
2649      *
2650      * @param e mouse event that occured
2651      */
mouseDragged(MouseEvent e)2652     public void mouseDragged(MouseEvent e)
2653     {
2654       dispatch(e);
2655     }
2656 
2657     /**
2658      * Invoked when the mouse cursor has been moved onto a component but no
2659      * buttons have been pushed.
2660      *
2661      * @param e mouse event that occured
2662      */
mouseMoved(MouseEvent e)2663     public void mouseMoved(MouseEvent e)
2664     {
2665       removeFromSource();
2666     }
2667 
2668     /**
2669      * Removes event from the source
2670      */
removeFromSource()2671     protected void removeFromSource()
2672     {
2673       if (source != null)
2674         {
2675           source.removeMouseListener(this);
2676           source.removeMouseMotionListener(this);
2677         }
2678       source = null;
2679       destination = null;
2680     }
2681 
2682     /**
2683      * Redispatches mouse events to the destination.
2684      *
2685      * @param e the mouse event to redispatch
2686      */
dispatch(MouseEvent e)2687     private void dispatch(MouseEvent e)
2688     {
2689       if (destination != null)
2690         {
2691           MouseEvent e2 = SwingUtilities.convertMouseEvent(source, e,
2692                                                            destination);
2693           destination.dispatchEvent(e2);
2694         }
2695     }
2696   }
2697 
2698   /**
2699    * Class responsible for getting size of node, method is forwarded to
2700    * BasicTreeUI method. X location does not include insets, that is handled in
2701    * getPathBounds.
2702    */
2703   public class NodeDimensionsHandler
2704       extends AbstractLayoutCache.NodeDimensions
2705   {
2706     /**
2707      * Constructor
2708      */
NodeDimensionsHandler()2709     public NodeDimensionsHandler()
2710     {
2711       // Nothing to do here.
2712     }
2713 
2714     /**
2715      * Returns, by reference in bounds, the size and x origin to place value at.
2716      * The calling method is responsible for determining the Y location. If
2717      * bounds is null, a newly created Rectangle should be returned, otherwise
2718      * the value should be placed in bounds and returned.
2719      *
2720      * @param cell the value to be represented
2721      * @param row row being queried
2722      * @param depth the depth of the row
2723      * @param expanded true if row is expanded
2724      * @param size a Rectangle containing the size needed to represent value
2725      * @return containing the node dimensions, or null if node has no dimension
2726      */
getNodeDimensions(Object cell, int row, int depth, boolean expanded, Rectangle size)2727     public Rectangle getNodeDimensions(Object cell, int row, int depth,
2728                                        boolean expanded, Rectangle size)
2729     {
2730       Dimension prefSize;
2731       if (editingComponent != null && editingRow == row)
2732         {
2733           // Editing, ask editor for preferred size.
2734           prefSize = editingComponent.getPreferredSize();
2735           int rowHeight = getRowHeight();
2736           if (rowHeight > 0 && rowHeight != prefSize.height)
2737             prefSize.height = rowHeight;
2738         }
2739       else
2740         {
2741           // Not editing, ask renderer for preferred size.
2742           Component rend =
2743             currentCellRenderer.getTreeCellRendererComponent(tree, cell,
2744                                                        tree.isRowSelected(row),
2745                                                        expanded,
2746                                                        treeModel.isLeaf(cell),
2747                                                        row, false);
2748           // Make sure the layout is valid.
2749           rendererPane.add(rend);
2750           rend.validate();
2751           prefSize = rend.getPreferredSize();
2752         }
2753       if (size != null)
2754         {
2755           size.x = getRowX(row, depth);
2756           // FIXME: This should be handled by the layout cache.
2757           size.y = prefSize.height * row;
2758           size.width =  prefSize.width;
2759           size.height = prefSize.height;
2760         }
2761       else
2762         // FIXME: The y should be handled by the layout cache.
2763         size = new Rectangle(getRowX(row, depth), prefSize.height * row, prefSize.width,
2764                              prefSize.height);
2765 
2766       return size;
2767     }
2768 
2769     /**
2770      * Returns the amount to indent the given row
2771      *
2772      * @return amount to indent the given row.
2773      */
getRowX(int row, int depth)2774     protected int getRowX(int row, int depth)
2775     {
2776       return BasicTreeUI.this.getRowX(row, depth);
2777     }
2778   } // NodeDimensionsHandler
2779 
2780   /**
2781    * PropertyChangeListener for the tree. Updates the appropriate variable, or
2782    * TreeState, based on what changes.
2783    */
2784   public class PropertyChangeHandler
2785       implements PropertyChangeListener
2786   {
2787 
2788     /**
2789      * Constructor
2790      */
PropertyChangeHandler()2791     public PropertyChangeHandler()
2792     {
2793       // Nothing to do here.
2794     }
2795 
2796     /**
2797      * This method gets called when a bound property is changed.
2798      *
2799      * @param event A PropertyChangeEvent object describing the event source and
2800      *          the property that has changed.
2801      */
propertyChange(PropertyChangeEvent event)2802     public void propertyChange(PropertyChangeEvent event)
2803     {
2804       String property = event.getPropertyName();
2805       if (property.equals(JTree.ROOT_VISIBLE_PROPERTY))
2806         {
2807           validCachedPreferredSize = false;
2808           treeState.setRootVisible(tree.isRootVisible());
2809           tree.repaint();
2810         }
2811       else if (property.equals(JTree.SELECTION_MODEL_PROPERTY))
2812         {
2813           treeSelectionModel = tree.getSelectionModel();
2814           treeSelectionModel.setRowMapper(treeState);
2815         }
2816       else if (property.equals(JTree.TREE_MODEL_PROPERTY))
2817         {
2818           setModel(tree.getModel());
2819         }
2820       else if (property.equals(JTree.CELL_RENDERER_PROPERTY))
2821         {
2822           setCellRenderer(tree.getCellRenderer());
2823           // Update layout.
2824           if (treeState != null)
2825             treeState.invalidateSizes();
2826         }
2827       else if (property.equals(JTree.EDITABLE_PROPERTY))
2828         setEditable(((Boolean) event.getNewValue()).booleanValue());
2829 
2830     }
2831   }
2832 
2833   /**
2834    * Listener on the TreeSelectionModel, resets the row selection if any of the
2835    * properties of the model change.
2836    */
2837   public class SelectionModelPropertyChangeHandler
2838     implements PropertyChangeListener
2839   {
2840 
2841     /**
2842      * Constructor
2843      */
SelectionModelPropertyChangeHandler()2844     public SelectionModelPropertyChangeHandler()
2845     {
2846       // Nothing to do here.
2847     }
2848 
2849     /**
2850      * This method gets called when a bound property is changed.
2851      *
2852      * @param event A PropertyChangeEvent object describing the event source and
2853      *          the property that has changed.
2854      */
propertyChange(PropertyChangeEvent event)2855     public void propertyChange(PropertyChangeEvent event)
2856     {
2857       treeSelectionModel.resetRowSelection();
2858     }
2859   }
2860 
2861   /**
2862    * The action to cancel editing on this tree.
2863    */
2864   public class TreeCancelEditingAction
2865       extends AbstractAction
2866   {
2867     /**
2868      * Creates the new tree cancel editing action.
2869      *
2870      * @param name the name of the action (used in toString).
2871      */
TreeCancelEditingAction(String name)2872     public TreeCancelEditingAction(String name)
2873     {
2874       super(name);
2875     }
2876 
2877     /**
2878      * Invoked when an action occurs, cancels the cell editing (if the
2879      * tree cell is being edited).
2880      *
2881      * @param e event that occured
2882      */
actionPerformed(ActionEvent e)2883     public void actionPerformed(ActionEvent e)
2884     {
2885       if (isEnabled() && tree.isEditing())
2886         tree.cancelEditing();
2887     }
2888   }
2889 
2890   /**
2891    * Updates the TreeState in response to nodes expanding/collapsing.
2892    */
2893   public class TreeExpansionHandler
2894       implements TreeExpansionListener
2895   {
2896 
2897     /**
2898      * Constructor
2899      */
TreeExpansionHandler()2900     public TreeExpansionHandler()
2901     {
2902       // Nothing to do here.
2903     }
2904 
2905     /**
2906      * Called whenever an item in the tree has been expanded.
2907      *
2908      * @param event is the event that occured
2909      */
treeExpanded(TreeExpansionEvent event)2910     public void treeExpanded(TreeExpansionEvent event)
2911     {
2912       validCachedPreferredSize = false;
2913       treeState.setExpandedState(event.getPath(), true);
2914       // The maximal cell height may change
2915       maxHeight = 0;
2916       tree.revalidate();
2917       tree.repaint();
2918     }
2919 
2920     /**
2921      * Called whenever an item in the tree has been collapsed.
2922      *
2923      * @param event is the event that occured
2924      */
treeCollapsed(TreeExpansionEvent event)2925     public void treeCollapsed(TreeExpansionEvent event)
2926     {
2927       completeEditing();
2928       validCachedPreferredSize = false;
2929       treeState.setExpandedState(event.getPath(), false);
2930       // The maximal cell height may change
2931       maxHeight = 0;
2932       tree.revalidate();
2933       tree.repaint();
2934     }
2935   } // TreeExpansionHandler
2936 
2937   /**
2938    * TreeHomeAction is used to handle end/home actions. Scrolls either the first
2939    * or last cell to be visible based on direction.
2940    */
2941   public class TreeHomeAction
2942       extends AbstractAction
2943   {
2944 
2945     /** The direction, either home or end */
2946     protected int direction;
2947 
2948     /**
2949      * Creates a new TreeHomeAction instance.
2950      *
2951      * @param dir the direction to go to, <code>-1</code> for home,
2952      *        <code>1</code> for end
2953      * @param name the name of the action
2954      */
TreeHomeAction(int dir, String name)2955     public TreeHomeAction(int dir, String name)
2956     {
2957       direction = dir;
2958       putValue(Action.NAME, name);
2959     }
2960 
2961     /**
2962      * Invoked when an action occurs.
2963      *
2964      * @param e is the event that occured
2965      */
actionPerformed(ActionEvent e)2966     public void actionPerformed(ActionEvent e)
2967     {
2968       if (tree != null)
2969         {
2970           String command = (String) getValue(Action.NAME);
2971           if (command.equals("selectFirst"))
2972             {
2973               ensureRowsAreVisible(0, 0);
2974               tree.setSelectionInterval(0, 0);
2975             }
2976           if (command.equals("selectFirstChangeLead"))
2977             {
2978               ensureRowsAreVisible(0, 0);
2979               tree.setLeadSelectionPath(getPathForRow(tree, 0));
2980             }
2981           if (command.equals("selectFirstExtendSelection"))
2982             {
2983               ensureRowsAreVisible(0, 0);
2984               TreePath anchorPath = tree.getAnchorSelectionPath();
2985               if (anchorPath == null)
2986                 tree.setSelectionInterval(0, 0);
2987               else
2988                 {
2989                   int anchorRow = getRowForPath(tree, anchorPath);
2990                   tree.setSelectionInterval(0, anchorRow);
2991                   tree.setAnchorSelectionPath(anchorPath);
2992                   tree.setLeadSelectionPath(getPathForRow(tree, 0));
2993                 }
2994             }
2995           else if (command.equals("selectLast"))
2996             {
2997               int end = getRowCount(tree) - 1;
2998               ensureRowsAreVisible(end, end);
2999               tree.setSelectionInterval(end, end);
3000             }
3001           else if (command.equals("selectLastChangeLead"))
3002             {
3003               int end = getRowCount(tree) - 1;
3004               ensureRowsAreVisible(end, end);
3005               tree.setLeadSelectionPath(getPathForRow(tree, end));
3006             }
3007           else if (command.equals("selectLastExtendSelection"))
3008             {
3009               int end = getRowCount(tree) - 1;
3010               ensureRowsAreVisible(end, end);
3011               TreePath anchorPath = tree.getAnchorSelectionPath();
3012               if (anchorPath == null)
3013                 tree.setSelectionInterval(end, end);
3014               else
3015                 {
3016                   int anchorRow = getRowForPath(tree, anchorPath);
3017                   tree.setSelectionInterval(end, anchorRow);
3018                   tree.setAnchorSelectionPath(anchorPath);
3019                   tree.setLeadSelectionPath(getPathForRow(tree, end));
3020                 }
3021             }
3022         }
3023 
3024       // Ensure that the lead path is visible after the increment action.
3025       tree.scrollPathToVisible(tree.getLeadSelectionPath());
3026     }
3027 
3028     /**
3029      * Returns true if the action is enabled.
3030      *
3031      * @return true if the action is enabled.
3032      */
isEnabled()3033     public boolean isEnabled()
3034     {
3035       return (tree != null) && tree.isEnabled();
3036     }
3037   }
3038 
3039   /**
3040    * TreeIncrementAction is used to handle up/down actions. Selection is moved
3041    * up or down based on direction.
3042    */
3043   public class TreeIncrementAction
3044     extends AbstractAction
3045   {
3046 
3047     /**
3048      * Specifies the direction to adjust the selection by.
3049      */
3050     protected int direction;
3051 
3052     /**
3053      * Creates a new TreeIncrementAction.
3054      *
3055      * @param dir up or down, <code>-1</code> for up, <code>1</code> for down
3056      * @param name is the name of the direction
3057      */
TreeIncrementAction(int dir, String name)3058     public TreeIncrementAction(int dir, String name)
3059     {
3060       direction = dir;
3061       putValue(Action.NAME, name);
3062     }
3063 
3064     /**
3065      * Invoked when an action occurs.
3066      *
3067      * @param e is the event that occured
3068      */
actionPerformed(ActionEvent e)3069     public void actionPerformed(ActionEvent e)
3070     {
3071       TreePath currentPath = tree.getLeadSelectionPath();
3072       int currentRow;
3073 
3074       if (currentPath != null)
3075         currentRow = treeState.getRowForPath(currentPath);
3076       else
3077         currentRow = 0;
3078 
3079       int rows = treeState.getRowCount();
3080 
3081       int nextRow = currentRow + 1;
3082       int prevRow = currentRow - 1;
3083       boolean hasNext = nextRow < rows;
3084       boolean hasPrev = prevRow >= 0 && rows > 0;
3085       TreePath newPath;
3086       String command = (String) getValue(Action.NAME);
3087 
3088       if (command.equals("selectPreviousChangeLead") && hasPrev)
3089         {
3090           newPath = treeState.getPathForRow(prevRow);
3091           tree.setSelectionPath(newPath);
3092           tree.setAnchorSelectionPath(newPath);
3093           tree.setLeadSelectionPath(newPath);
3094         }
3095       else if (command.equals("selectPreviousExtendSelection") && hasPrev)
3096         {
3097           newPath = treeState.getPathForRow(prevRow);
3098 
3099           // If the new path is already selected, the selection shrinks,
3100           // unselecting the previously current path.
3101           if (tree.isPathSelected(newPath))
3102             tree.getSelectionModel().removeSelectionPath(currentPath);
3103 
3104           // This must be called in any case because it updates the model
3105           // lead selection index.
3106           tree.addSelectionPath(newPath);
3107           tree.setLeadSelectionPath(newPath);
3108         }
3109       else if (command.equals("selectPrevious") && hasPrev)
3110         {
3111           newPath = treeState.getPathForRow(prevRow);
3112           tree.setSelectionPath(newPath);
3113         }
3114       else if (command.equals("selectNext") && hasNext)
3115         {
3116           newPath = treeState.getPathForRow(nextRow);
3117           tree.setSelectionPath(newPath);
3118         }
3119       else if (command.equals("selectNextExtendSelection") && hasNext)
3120         {
3121           newPath = treeState.getPathForRow(nextRow);
3122 
3123           // If the new path is already selected, the selection shrinks,
3124           // unselecting the previously current path.
3125           if (tree.isPathSelected(newPath))
3126             tree.getSelectionModel().removeSelectionPath(currentPath);
3127 
3128           // This must be called in any case because it updates the model
3129           // lead selection index.
3130           tree.addSelectionPath(newPath);
3131 
3132           tree.setLeadSelectionPath(newPath);
3133         }
3134       else if (command.equals("selectNextChangeLead") && hasNext)
3135         {
3136           newPath = treeState.getPathForRow(nextRow);
3137           tree.setSelectionPath(newPath);
3138           tree.setAnchorSelectionPath(newPath);
3139           tree.setLeadSelectionPath(newPath);
3140         }
3141 
3142       // Ensure that the lead path is visible after the increment action.
3143       tree.scrollPathToVisible(tree.getLeadSelectionPath());
3144     }
3145 
3146     /**
3147      * Returns true if the action is enabled.
3148      *
3149      * @return true if the action is enabled.
3150      */
isEnabled()3151     public boolean isEnabled()
3152     {
3153       return (tree != null) && tree.isEnabled();
3154     }
3155   }
3156 
3157   /**
3158    * Forwards all TreeModel events to the TreeState.
3159    */
3160   public class TreeModelHandler
3161       implements TreeModelListener
3162   {
3163     /**
3164      * Constructor
3165      */
TreeModelHandler()3166     public TreeModelHandler()
3167     {
3168       // Nothing to do here.
3169     }
3170 
3171     /**
3172      * Invoked after a node (or a set of siblings) has changed in some way. The
3173      * node(s) have not changed locations in the tree or altered their children
3174      * arrays, but other attributes have changed and may affect presentation.
3175      * Example: the name of a file has changed, but it is in the same location
3176      * in the file system. To indicate the root has changed, childIndices and
3177      * children will be null. Use e.getPath() to get the parent of the changed
3178      * node(s). e.getChildIndices() returns the index(es) of the changed
3179      * node(s).
3180      *
3181      * @param e is the event that occured
3182      */
treeNodesChanged(TreeModelEvent e)3183     public void treeNodesChanged(TreeModelEvent e)
3184     {
3185       validCachedPreferredSize = false;
3186       treeState.treeNodesChanged(e);
3187       tree.repaint();
3188     }
3189 
3190     /**
3191      * Invoked after nodes have been inserted into the tree. Use e.getPath() to
3192      * get the parent of the new node(s). e.getChildIndices() returns the
3193      * index(es) of the new node(s) in ascending order.
3194      *
3195      * @param e is the event that occured
3196      */
treeNodesInserted(TreeModelEvent e)3197     public void treeNodesInserted(TreeModelEvent e)
3198     {
3199       validCachedPreferredSize = false;
3200       treeState.treeNodesInserted(e);
3201       tree.repaint();
3202     }
3203 
3204     /**
3205      * Invoked after nodes have been removed from the tree. Note that if a
3206      * subtree is removed from the tree, this method may only be invoked once
3207      * for the root of the removed subtree, not once for each individual set of
3208      * siblings removed. Use e.getPath() to get the former parent of the deleted
3209      * node(s). e.getChildIndices() returns, in ascending order, the index(es)
3210      * the node(s) had before being deleted.
3211      *
3212      * @param e is the event that occured
3213      */
treeNodesRemoved(TreeModelEvent e)3214     public void treeNodesRemoved(TreeModelEvent e)
3215     {
3216       validCachedPreferredSize = false;
3217       treeState.treeNodesRemoved(e);
3218       tree.repaint();
3219     }
3220 
3221     /**
3222      * Invoked after the tree has drastically changed structure from a given
3223      * node down. If the path returned by e.getPath() is of length one and the
3224      * first element does not identify the current root node the first element
3225      * should become the new root of the tree. Use e.getPath() to get the path
3226      * to the node. e.getChildIndices() returns null.
3227      *
3228      * @param e is the event that occured
3229      */
treeStructureChanged(TreeModelEvent e)3230     public void treeStructureChanged(TreeModelEvent e)
3231     {
3232       if (e.getPath().length == 1
3233           && ! e.getPath()[0].equals(treeModel.getRoot()))
3234         tree.expandPath(new TreePath(treeModel.getRoot()));
3235       validCachedPreferredSize = false;
3236       treeState.treeStructureChanged(e);
3237       tree.repaint();
3238     }
3239   } // TreeModelHandler
3240 
3241   /**
3242    * TreePageAction handles page up and page down events.
3243    */
3244   public class TreePageAction
3245       extends AbstractAction
3246   {
3247     /** Specifies the direction to adjust the selection by. */
3248     protected int direction;
3249 
3250     /**
3251      * Constructor
3252      *
3253      * @param direction up or down
3254      * @param name is the name of the direction
3255      */
TreePageAction(int direction, String name)3256     public TreePageAction(int direction, String name)
3257     {
3258       this.direction = direction;
3259       putValue(Action.NAME, name);
3260     }
3261 
3262     /**
3263      * Invoked when an action occurs.
3264      *
3265      * @param e is the event that occured
3266      */
actionPerformed(ActionEvent e)3267     public void actionPerformed(ActionEvent e)
3268     {
3269       String command = (String) getValue(Action.NAME);
3270       boolean extendSelection = command.equals("scrollUpExtendSelection")
3271                                 || command.equals("scrollDownExtendSelection");
3272       boolean changeSelection = command.equals("scrollUpChangeSelection")
3273                                 || command.equals("scrollDownChangeSelection");
3274 
3275       // Disable change lead, unless we are in discontinuous mode.
3276       if (!extendSelection && !changeSelection
3277           && tree.getSelectionModel().getSelectionMode() !=
3278             TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
3279         {
3280           changeSelection = true;
3281         }
3282 
3283       int rowCount = getRowCount(tree);
3284       if (rowCount > 0 && treeSelectionModel != null)
3285         {
3286           Dimension maxSize = tree.getSize();
3287           TreePath lead = tree.getLeadSelectionPath();
3288           TreePath newPath = null;
3289           Rectangle visible = tree.getVisibleRect();
3290           if (direction == -1) // The RI handles -1 as up.
3291             {
3292               newPath = getClosestPathForLocation(tree, visible.x, visible.y);
3293               if (newPath.equals(lead)) // Corner case, adjust one page up.
3294                 {
3295                   visible.y = Math.max(0, visible.y - visible.height);
3296                   newPath = getClosestPathForLocation(tree, visible.x,
3297                                                       visible.y);
3298                 }
3299             }
3300           else // +1 is down.
3301             {
3302               visible.y = Math.min(maxSize.height,
3303                                    visible.y + visible.height - 1);
3304               newPath = getClosestPathForLocation(tree, visible.x, visible.y);
3305               if (newPath.equals(lead)) // Corner case, adjust one page down.
3306                 {
3307                   visible.y = Math.min(maxSize.height,
3308                                        visible.y + visible.height - 1);
3309                   newPath = getClosestPathForLocation(tree, visible.x,
3310                                                       visible.y);
3311                 }
3312             }
3313 
3314           // Determine new visible rect.
3315           Rectangle newVisible = getPathBounds(tree, newPath);
3316           newVisible.x = visible.x;
3317           newVisible.width = visible.width;
3318           if (direction == -1)
3319             {
3320               newVisible.height = visible.height;
3321             }
3322           else
3323             {
3324               newVisible.y -= visible.height - newVisible.height;
3325               newVisible.height = visible.height;
3326             }
3327 
3328           if (extendSelection)
3329             {
3330               // Extend selection.
3331               TreePath anchorPath = tree.getAnchorSelectionPath();
3332               if (anchorPath == null)
3333                 {
3334                   tree.setSelectionPath(newPath);
3335                 }
3336               else
3337                 {
3338                   int newIndex = getRowForPath(tree, newPath);
3339                   int anchorIndex = getRowForPath(tree, anchorPath);
3340                   tree.setSelectionInterval(Math.min(anchorIndex, newIndex),
3341                                             Math.max(anchorIndex, newIndex));
3342                   tree.setAnchorSelectionPath(anchorPath);
3343                   tree.setLeadSelectionPath(newPath);
3344                 }
3345             }
3346           else if (changeSelection)
3347             {
3348               tree.setSelectionPath(newPath);
3349             }
3350           else // Change lead.
3351             {
3352               tree.setLeadSelectionPath(newPath);
3353             }
3354 
3355           tree.scrollRectToVisible(newVisible);
3356         }
3357     }
3358 
3359     /**
3360      * Returns true if the action is enabled.
3361      *
3362      * @return true if the action is enabled.
3363      */
isEnabled()3364     public boolean isEnabled()
3365     {
3366       return (tree != null) && tree.isEnabled();
3367     }
3368   } // TreePageAction
3369 
3370   /**
3371    * Listens for changes in the selection model and updates the display
3372    * accordingly.
3373    */
3374   public class TreeSelectionHandler
3375       implements TreeSelectionListener
3376   {
3377     /**
3378      * Constructor
3379      */
TreeSelectionHandler()3380     public TreeSelectionHandler()
3381     {
3382       // Nothing to do here.
3383     }
3384 
3385     /**
3386      * Messaged when the selection changes in the tree we're displaying for.
3387      * Stops editing, messages super and displays the changed paths.
3388      *
3389      * @param event the event that characterizes the change.
3390      */
valueChanged(TreeSelectionEvent event)3391     public void valueChanged(TreeSelectionEvent event)
3392     {
3393       completeEditing();
3394 
3395       TreePath op = event.getOldLeadSelectionPath();
3396       TreePath np = event.getNewLeadSelectionPath();
3397 
3398       // Repaint of the changed lead selection path.
3399       if (op != np)
3400         {
3401           Rectangle o = treeState.getBounds(event.getOldLeadSelectionPath(),
3402                                            new Rectangle());
3403           Rectangle n = treeState.getBounds(event.getNewLeadSelectionPath(),
3404                                            new Rectangle());
3405 
3406           if (o != null)
3407             tree.repaint(o);
3408           if (n != null)
3409             tree.repaint(n);
3410         }
3411     }
3412   } // TreeSelectionHandler
3413 
3414   /**
3415    * For the first selected row expandedness will be toggled.
3416    */
3417   public class TreeToggleAction
3418       extends AbstractAction
3419   {
3420     /**
3421      * Creates a new TreeToggleAction.
3422      *
3423      * @param name is the name of <code>Action</code> field
3424      */
TreeToggleAction(String name)3425     public TreeToggleAction(String name)
3426     {
3427       putValue(Action.NAME, name);
3428     }
3429 
3430     /**
3431      * Invoked when an action occurs.
3432      *
3433      * @param e the event that occured
3434      */
actionPerformed(ActionEvent e)3435     public void actionPerformed(ActionEvent e)
3436     {
3437       int selected = tree.getLeadSelectionRow();
3438       if (selected != -1 && isLeaf(selected))
3439         {
3440           TreePath anchorPath = tree.getAnchorSelectionPath();
3441           TreePath leadPath = tree.getLeadSelectionPath();
3442           toggleExpandState(getPathForRow(tree, selected));
3443           // Need to do this, so that the toggling doesn't mess up the lead
3444           // and anchor.
3445           tree.setLeadSelectionPath(leadPath);
3446           tree.setAnchorSelectionPath(anchorPath);
3447 
3448           // Ensure that the lead path is visible after the increment action.
3449           tree.scrollPathToVisible(tree.getLeadSelectionPath());
3450         }
3451     }
3452 
3453     /**
3454      * Returns true if the action is enabled.
3455      *
3456      * @return true if the action is enabled, false otherwise
3457      */
isEnabled()3458     public boolean isEnabled()
3459     {
3460       return (tree != null) && tree.isEnabled();
3461     }
3462   } // TreeToggleAction
3463 
3464   /**
3465    * TreeTraverseAction is the action used for left/right keys. Will toggle the
3466    * expandedness of a node, as well as potentially incrementing the selection.
3467    */
3468   public class TreeTraverseAction
3469       extends AbstractAction
3470   {
3471     /**
3472      * Determines direction to traverse, 1 means expand, -1 means collapse.
3473      */
3474     protected int direction;
3475 
3476     /**
3477      * Constructor
3478      *
3479      * @param direction to traverse
3480      * @param name is the name of the direction
3481      */
TreeTraverseAction(int direction, String name)3482     public TreeTraverseAction(int direction, String name)
3483     {
3484       this.direction = direction;
3485       putValue(Action.NAME, name);
3486     }
3487 
3488     /**
3489      * Invoked when an action occurs.
3490      *
3491      * @param e the event that occured
3492      */
actionPerformed(ActionEvent e)3493     public void actionPerformed(ActionEvent e)
3494     {
3495       TreePath current = tree.getLeadSelectionPath();
3496       if (current == null)
3497         return;
3498 
3499       String command = (String) getValue(Action.NAME);
3500       if (command.equals("selectParent"))
3501         {
3502           if (current == null)
3503             return;
3504 
3505           if (tree.isExpanded(current))
3506             {
3507               tree.collapsePath(current);
3508             }
3509           else
3510             {
3511               // If the node is not expanded (also, if it is a leaf node),
3512               // we just select the parent. We do not select the root if it
3513               // is not visible.
3514               TreePath parent = current.getParentPath();
3515               if (parent != null &&
3516                   ! (parent.getPathCount() == 1 && ! tree.isRootVisible()))
3517                 tree.setSelectionPath(parent);
3518             }
3519         }
3520       else if (command.equals("selectChild"))
3521         {
3522           Object node = current.getLastPathComponent();
3523           int nc = treeModel.getChildCount(node);
3524           if (nc == 0 || treeState.isExpanded(current))
3525             {
3526               // If the node is leaf or it is already expanded,
3527               // we just select the next row.
3528               int nextRow = tree.getLeadSelectionRow() + 1;
3529               if (nextRow <= tree.getRowCount())
3530                 tree.setSelectionRow(nextRow);
3531             }
3532           else
3533             {
3534               tree.expandPath(current);
3535             }
3536         }
3537 
3538       // Ensure that the lead path is visible after the increment action.
3539       tree.scrollPathToVisible(tree.getLeadSelectionPath());
3540     }
3541 
3542     /**
3543      * Returns true if the action is enabled.
3544      *
3545      * @return true if the action is enabled, false otherwise
3546      */
isEnabled()3547     public boolean isEnabled()
3548     {
3549       return (tree != null) && tree.isEnabled();
3550     }
3551   }
3552 
3553   /**
3554    * Returns true if the LookAndFeel implements the control icons. Package
3555    * private for use in inner classes.
3556    *
3557    * @returns true if there are control icons
3558    */
hasControlIcons()3559   boolean hasControlIcons()
3560   {
3561     if (expandedIcon != null || collapsedIcon != null)
3562       return true;
3563     return false;
3564   }
3565 
3566   /**
3567    * Returns control icon. It is null if the LookAndFeel does not implements the
3568    * control icons. Package private for use in inner classes.
3569    *
3570    * @return control icon if it exists.
3571    */
getCurrentControlIcon(TreePath path)3572   Icon getCurrentControlIcon(TreePath path)
3573   {
3574     if (hasControlIcons())
3575       {
3576         if (tree.isExpanded(path))
3577           return expandedIcon;
3578         else
3579           return collapsedIcon;
3580       }
3581     else
3582       {
3583         if (nullIcon == null)
3584           nullIcon = new Icon()
3585           {
3586             public int getIconHeight()
3587             {
3588               return 0;
3589             }
3590 
3591             public int getIconWidth()
3592             {
3593               return 0;
3594             }
3595 
3596             public void paintIcon(Component c, Graphics g, int x, int y)
3597             {
3598               // No action here.
3599             }
3600           };
3601         return nullIcon;
3602       }
3603   }
3604 
3605   /**
3606    * Returns the parent of the current node
3607    *
3608    * @param root is the root of the tree
3609    * @param node is the current node
3610    * @return is the parent of the current node
3611    */
getParent(Object root, Object node)3612   Object getParent(Object root, Object node)
3613   {
3614     if (root == null || node == null || root.equals(node))
3615       return null;
3616 
3617     if (node instanceof TreeNode)
3618       return ((TreeNode) node).getParent();
3619     return findNode(root, node);
3620   }
3621 
3622   /**
3623    * Recursively checks the tree for the specified node, starting at the root.
3624    *
3625    * @param root is starting node to start searching at.
3626    * @param node is the node to search for
3627    * @return the parent node of node
3628    */
findNode(Object root, Object node)3629   private Object findNode(Object root, Object node)
3630   {
3631     if (! treeModel.isLeaf(root) && ! root.equals(node))
3632       {
3633         int size = treeModel.getChildCount(root);
3634         for (int j = 0; j < size; j++)
3635           {
3636             Object child = treeModel.getChild(root, j);
3637             if (node.equals(child))
3638               return root;
3639 
3640             Object n = findNode(child, node);
3641             if (n != null)
3642               return n;
3643           }
3644       }
3645     return null;
3646   }
3647 
3648   /**
3649    * Selects the specified path in the tree depending on modes. Package private
3650    * for use in inner classes.
3651    *
3652    * @param tree is the tree we are selecting the path in
3653    * @param path is the path we are selecting
3654    */
selectPath(JTree tree, TreePath path)3655   void selectPath(JTree tree, TreePath path)
3656   {
3657     if (path != null)
3658       {
3659         tree.setSelectionPath(path);
3660         tree.setLeadSelectionPath(path);
3661         tree.makeVisible(path);
3662         tree.scrollPathToVisible(path);
3663       }
3664   }
3665 
3666   /**
3667    * Returns the path from node to the root. Package private for use in inner
3668    * classes.
3669    *
3670    * @param node the node to get the path to
3671    * @param depth the depth of the tree to return a path for
3672    * @return an array of tree nodes that represent the path to node.
3673    */
getPathToRoot(Object node, int depth)3674   Object[] getPathToRoot(Object node, int depth)
3675   {
3676     if (node == null)
3677       {
3678         if (depth == 0)
3679           return null;
3680 
3681         return new Object[depth];
3682       }
3683 
3684     Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node),
3685                                   depth + 1);
3686     path[path.length - depth - 1] = node;
3687     return path;
3688   }
3689 
3690   /**
3691    * Draws a vertical line using the given graphic context
3692    *
3693    * @param g is the graphic context
3694    * @param c is the component the new line will belong to
3695    * @param x is the horizonal position
3696    * @param top specifies the top of the line
3697    * @param bottom specifies the bottom of the line
3698    */
paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom)3699   protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
3700                                    int bottom)
3701   {
3702     // FIXME: Check if drawing a dashed line or not.
3703     g.setColor(getHashColor());
3704     g.drawLine(x, top, x, bottom);
3705   }
3706 
3707   /**
3708    * Draws a horizontal line using the given graphic context
3709    *
3710    * @param g is the graphic context
3711    * @param c is the component the new line will belong to
3712    * @param y is the vertical position
3713    * @param left specifies the left point of the line
3714    * @param right specifies the right point of the line
3715    */
paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right)3716   protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left,
3717                                      int right)
3718   {
3719     // FIXME: Check if drawing a dashed line or not.
3720     g.setColor(getHashColor());
3721     g.drawLine(left, y, right, y);
3722   }
3723 
3724   /**
3725    * Draws an icon at around a specific position
3726    *
3727    * @param c is the component the new line will belong to
3728    * @param g is the graphic context
3729    * @param icon is the icon which will be drawn
3730    * @param x is the center position in x-direction
3731    * @param y is the center position in y-direction
3732    */
drawCentered(Component c, Graphics g, Icon icon, int x, int y)3733   protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y)
3734   {
3735     x -= icon.getIconWidth() / 2;
3736     y -= icon.getIconHeight() / 2;
3737 
3738     if (x < 0)
3739       x = 0;
3740     if (y < 0)
3741       y = 0;
3742 
3743     icon.paintIcon(c, g, x, y);
3744   }
3745 
3746   /**
3747    * Draws a dashed horizontal line.
3748    *
3749    * @param g - the graphics configuration.
3750    * @param y - the y location to start drawing at
3751    * @param x1 - the x location to start drawing at
3752    * @param x2 - the x location to finish drawing at
3753    */
drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)3754   protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)
3755   {
3756     g.setColor(getHashColor());
3757     for (int i = x1; i < x2; i += 2)
3758       g.drawLine(i, y, i + 1, y);
3759   }
3760 
3761   /**
3762    * Draws a dashed vertical line.
3763    *
3764    * @param g - the graphics configuration.
3765    * @param x - the x location to start drawing at
3766    * @param y1 - the y location to start drawing at
3767    * @param y2 - the y location to finish drawing at
3768    */
drawDashedVerticalLine(Graphics g, int x, int y1, int y2)3769   protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2)
3770   {
3771     g.setColor(getHashColor());
3772     for (int i = y1; i < y2; i += 2)
3773       g.drawLine(x, i, x, i + 1);
3774   }
3775 
3776   /**
3777    * Paints the expand (toggle) part of a row. The receiver should NOT modify
3778    * clipBounds, or insets.
3779    *
3780    * @param g - the graphics configuration
3781    * @param clipBounds -
3782    * @param insets -
3783    * @param bounds - bounds of expand control
3784    * @param path - path to draw control for
3785    * @param row - row to draw control for
3786    * @param isExpanded - is the row expanded
3787    * @param hasBeenExpanded - has the row already been expanded
3788    * @param isLeaf - is the path a leaf
3789    */
paintExpandControl(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)3790   protected void paintExpandControl(Graphics g, Rectangle clipBounds,
3791                                     Insets insets, Rectangle bounds,
3792                                     TreePath path, int row, boolean isExpanded,
3793                                     boolean hasBeenExpanded, boolean isLeaf)
3794   {
3795     if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf))
3796       {
3797         Icon icon = getCurrentControlIcon(path);
3798         int iconW = icon.getIconWidth();
3799         int x = bounds.x - iconW - gap;
3800         icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2
3801                                    - icon.getIconHeight() / 2);
3802       }
3803   }
3804 
3805   /**
3806    * Paints the horizontal part of the leg. The receiver should NOT modify
3807    * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not
3808    * visible.
3809    *
3810    * @param g - the graphics configuration
3811    * @param clipBounds -
3812    * @param insets -
3813    * @param bounds - bounds of the cell
3814    * @param path - path to draw leg for
3815    * @param row - row to start drawing at
3816    * @param isExpanded - is the row expanded
3817    * @param hasBeenExpanded - has the row already been expanded
3818    * @param isLeaf - is the path a leaf
3819    */
paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)3820   protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
3821                                           Insets insets, Rectangle bounds,
3822                                           TreePath path, int row,
3823                                           boolean isExpanded,
3824                                           boolean hasBeenExpanded,
3825                                           boolean isLeaf)
3826   {
3827     if (row != 0)
3828       {
3829         paintHorizontalLine(g, tree, bounds.y + bounds.height / 2,
3830                             bounds.x - leftChildIndent - gap, bounds.x - gap);
3831       }
3832   }
3833 
3834   /**
3835    * Paints the vertical part of the leg. The receiver should NOT modify
3836    * clipBounds, insets.
3837    *
3838    * @param g - the graphics configuration.
3839    * @param clipBounds -
3840    * @param insets -
3841    * @param path - the path to draw the vertical part for.
3842    */
paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path)3843   protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
3844                                         Insets insets, TreePath path)
3845   {
3846     Rectangle bounds = getPathBounds(tree, path);
3847     TreePath parent = path.getParentPath();
3848 
3849     boolean paintLine;
3850     if (isRootVisible())
3851       paintLine = parent != null;
3852     else
3853       paintLine = parent != null && parent.getPathCount() > 1;
3854     if (paintLine)
3855       {
3856         Rectangle parentBounds = getPathBounds(tree, parent);
3857         paintVerticalLine(g, tree, parentBounds.x + 2 * gap,
3858                           parentBounds.y + parentBounds.height / 2,
3859                           bounds.y + bounds.height / 2);
3860       }
3861   }
3862 
3863   /**
3864    * Paints the renderer part of a row. The receiver should NOT modify
3865    * clipBounds, or insets.
3866    *
3867    * @param g - the graphics configuration
3868    * @param clipBounds -
3869    * @param insets -
3870    * @param bounds - bounds of expand control
3871    * @param path - path to draw control for
3872    * @param row - row to draw control for
3873    * @param isExpanded - is the row expanded
3874    * @param hasBeenExpanded - has the row already been expanded
3875    * @param isLeaf - is the path a leaf
3876    */
paintRow(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)3877   protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
3878                           Rectangle bounds, TreePath path, int row,
3879                           boolean isExpanded, boolean hasBeenExpanded,
3880                           boolean isLeaf)
3881   {
3882     boolean selected = tree.isPathSelected(path);
3883     boolean hasIcons = false;
3884     Object node = path.getLastPathComponent();
3885 
3886     paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded,
3887                        hasBeenExpanded, isLeaf);
3888 
3889     TreeCellRenderer dtcr = currentCellRenderer;
3890 
3891     boolean focused = false;
3892     if (treeSelectionModel != null)
3893       focused = treeSelectionModel.getLeadSelectionRow() == row
3894                 && tree.isFocusOwner();
3895 
3896     Component c = dtcr.getTreeCellRendererComponent(tree, node, selected,
3897                                                     isExpanded, isLeaf, row,
3898                                                     focused);
3899 
3900     rendererPane.paintComponent(g, c, c.getParent(), bounds);
3901   }
3902 
3903   /**
3904    * Prepares for the UI to uninstall.
3905    */
prepareForUIUninstall()3906   protected void prepareForUIUninstall()
3907   {
3908     // Nothing to do here yet.
3909   }
3910 
3911   /**
3912    * Returns true if the expand (toggle) control should be drawn for the
3913    * specified row.
3914    *
3915    * @param path - current path to check for.
3916    * @param row - current row to check for.
3917    * @param isExpanded - true if the path is expanded
3918    * @param hasBeenExpanded - true if the path has been expanded already
3919    * @param isLeaf - true if the row is a lead
3920    */
shouldPaintExpandControl(TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)3921   protected boolean shouldPaintExpandControl(TreePath path, int row,
3922                                              boolean isExpanded,
3923                                              boolean hasBeenExpanded,
3924                                              boolean isLeaf)
3925   {
3926     Object node = path.getLastPathComponent();
3927     return ! isLeaf && hasControlIcons();
3928   }
3929 
3930   /**
3931    * Returns the amount to indent the given row
3932    *
3933    * @return amount to indent the given row.
3934    */
getRowX(int row, int depth)3935   protected int getRowX(int row, int depth)
3936   {
3937     return depth * totalChildIndent;
3938   }
3939 } // BasicTreeUI
3940