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