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