OSDN Git Service

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