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