001    /* JTree.java 
002       Copyright (C) 2002, 2004, 2005 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    package javax.swing;
039    
040    import java.awt.Color;
041    import java.awt.Cursor;
042    import java.awt.Dimension;
043    import java.awt.Font;
044    import java.awt.FontMetrics;
045    import java.awt.Point;
046    import java.awt.Rectangle;
047    import java.awt.event.FocusListener;
048    import java.beans.PropertyChangeListener;
049    import java.io.Serializable;
050    import java.util.Enumeration;
051    import java.util.Hashtable;
052    import java.util.Iterator;
053    import java.util.Locale;
054    import java.util.Vector;
055    
056    import javax.accessibility.Accessible;
057    import javax.accessibility.AccessibleAction;
058    import javax.accessibility.AccessibleComponent;
059    import javax.accessibility.AccessibleContext;
060    import javax.accessibility.AccessibleRole;
061    import javax.accessibility.AccessibleSelection;
062    import javax.accessibility.AccessibleState;
063    import javax.accessibility.AccessibleStateSet;
064    import javax.accessibility.AccessibleText;
065    import javax.accessibility.AccessibleValue;
066    import javax.swing.event.TreeExpansionEvent;
067    import javax.swing.event.TreeExpansionListener;
068    import javax.swing.event.TreeModelEvent;
069    import javax.swing.event.TreeModelListener;
070    import javax.swing.event.TreeSelectionEvent;
071    import javax.swing.event.TreeSelectionListener;
072    import javax.swing.event.TreeWillExpandListener;
073    import javax.swing.plaf.TreeUI;
074    import javax.swing.text.Position;
075    import javax.swing.tree.DefaultMutableTreeNode;
076    import javax.swing.tree.DefaultTreeModel;
077    import javax.swing.tree.DefaultTreeSelectionModel;
078    import javax.swing.tree.ExpandVetoException;
079    import javax.swing.tree.TreeCellEditor;
080    import javax.swing.tree.TreeCellRenderer;
081    import javax.swing.tree.TreeModel;
082    import javax.swing.tree.TreeNode;
083    import javax.swing.tree.TreePath;
084    import javax.swing.tree.TreeSelectionModel;
085    
086    public class JTree extends JComponent implements Scrollable, Accessible
087    {
088    
089      /**
090       * This class implements accessibility support for the JTree class. It 
091       * provides an implementation of the Java Accessibility API appropriate 
092       * to tree user-interface elements.
093       */
094      protected class AccessibleJTree extends JComponent.AccessibleJComponent
095          implements AccessibleSelection, TreeSelectionListener, TreeModelListener,
096          TreeExpansionListener
097      {
098        
099        /**
100         * This class implements accessibility support for the JTree child. It provides 
101         * an implementation of the Java Accessibility API appropriate to tree nodes.
102         */
103        protected class AccessibleJTreeNode extends AccessibleContext 
104           implements Accessible, AccessibleComponent, AccessibleSelection, 
105           AccessibleAction
106        {
107          
108          private JTree tree;
109          private TreePath tp;
110          private Accessible acc;
111          private AccessibleStateSet states;
112          private Vector selectionList;
113          private Vector actionList;
114          private TreeModel mod;
115          private Cursor cursor;
116          
117          /**
118           * Constructs an AccessibleJTreeNode
119           * 
120           * @param t - the current tree
121           * @param p - the current path to be dealt with
122           * @param ap - the accessible object to use
123           */
124          public AccessibleJTreeNode(JTree t, TreePath p, Accessible ap)
125          {
126            states = new AccessibleStateSet();
127            selectionList = new Vector();
128            actionList = new Vector();
129            mod = tree.getModel();
130            cursor = JTree.this.getCursor();
131                    
132            tree = t;
133            tp = p;
134            acc = ap;
135            
136            // Add all the children of this path that may already be
137            // selected to the selection list.
138            TreePath[] selected = tree.getSelectionPaths();
139            for (int i = 0; i < selected.length; i++)
140              {
141                TreePath sel = selected[i];
142                if ((sel.getParentPath()).equals(tp))
143                  selectionList.add(sel);
144              }
145            
146            // Add all the actions available for a node to 
147            // the action list.
148            actionList.add("EXPAND");
149            actionList.add("COLLAPSE");
150            actionList.add("EDIT");
151            actionList.add("SELECT");
152            actionList.add("DESELECT");
153          }      
154          
155          /**
156           * Adds the specified selected item in the object to the object's
157           * selection.
158           * 
159           * @param i - the i-th child of this node.
160           */
161          public void addAccessibleSelection(int i)
162          {
163            if (mod != null)
164              {
165                Object child = mod.getChild(tp.getLastPathComponent(), i);
166                if (child != null)
167                  {
168                    if (!states.contains(AccessibleState.MULTISELECTABLE))
169                      clearAccessibleSelection();
170                    selectionList.add(child);                  
171                    tree.addSelectionPath(tp.pathByAddingChild(child));
172                  }
173              }
174          }
175          
176          /**
177           * Adds the specified focus listener to receive focus events 
178           * from this component.
179           * 
180           * @param l - the new focus listener
181           */
182          public void addFocusListener(FocusListener l)
183          {
184            tree.addFocusListener(l);
185          }
186          
187          /**
188           * Add a PropertyChangeListener to the listener list.
189           * 
190           * @param l - the new property change listener
191           */
192          public void addPropertyChangeListener(PropertyChangeListener l)
193          {
194            // Nothing to do here.
195          }
196          
197          /**
198           * Clears the selection in the object, so that nothing in the 
199           * object is selected.
200           */
201          public void clearAccessibleSelection()
202          {
203            selectionList.clear();
204          }
205          
206          /**
207           * Checks whether the specified point is within this object's 
208           * bounds, where the point's x and y coordinates are defined to be 
209           * relative to the coordinate system of the object. 
210           * 
211           * @param p - the point to check
212           * @return true if p is in the bounds
213           */
214          public boolean contains(Point p)
215          {
216            return getBounds().contains(p);
217          }
218          
219          /**
220           * Perform the specified Action on the tree node.
221           * 
222           * @param i - the i-th action to perform
223           * @return true if the the action was performed; else false.
224           */
225          public boolean doAccessibleAction(int i)
226          {
227            if (i >= actionList.size() || i < 0)
228              return false;
229            
230            if (actionList.get(i).equals("EXPAND"))
231              tree.expandPath(tp);
232            else if (actionList.get(i).equals("COLLAPSE"))
233              tree.collapsePath(tp);
234            else if (actionList.get(i).equals("SELECT"))
235              tree.addSelectionPath(tp);
236            else if (actionList.get(i).equals("DESELECT"))
237              tree.removeSelectionPath(tp);
238            else if (actionList.get(i).equals("EDIT"))
239              tree.startEditingAtPath(tp);
240            else
241              return false;
242            return true;
243          }
244          
245          /**
246           * Get the AccessibleAction associated with this object.
247           * 
248           * @return the action
249           */
250          public AccessibleAction getAccessibleAction()
251          {
252            return this;
253          }
254          
255          /**
256           * Returns the number of accessible actions available in this tree node.
257           * 
258           * @return the number of actions
259           */
260          public int getAccessibleActionCount()
261          {
262            return actionList.size();
263          }
264          
265          /**
266           * Return a description of the specified action of the tree node.
267           * 
268           * @param i - the i-th action's description
269           * @return a description of the action
270           */
271          public String getAccessibleActionDescription(int i)
272          {
273            if (i < 0 || i >= actionList.size())
274              return (actionList.get(i)).toString();
275            return super.getAccessibleDescription();
276          }
277          
278          /**
279           * Returns the Accessible child, if one exists, contained at the 
280           * local coordinate Point.
281           * 
282           * @param p - the point of the accessible
283           * @return the accessible at point p if it exists
284           */
285          public Accessible getAccessibleAt(Point p)
286          {
287            TreePath acc = tree.getClosestPathForLocation(p.x, p.y);
288            if (acc != null)
289              return new AccessibleJTreeNode(tree, acc, this);
290            return null;
291          }
292          
293          /**
294           * Return the specified Accessible child of the object.
295           * 
296           * @param i - the i-th child of the current path
297           * @return the child if it exists
298           */
299          public Accessible getAccessibleChild(int i)
300          {
301            if (mod != null)
302              {
303                Object child = mod.getChild(tp.getLastPathComponent(), i);
304                if (child != null)
305                  return new AccessibleJTreeNode(tree, tp.pathByAddingChild(child),
306                                                 acc);
307              }
308            return null;
309          }
310          
311          /**
312           * Returns the number of accessible children in the object.
313           * 
314           * @return the number of children the current node has
315           */
316          public int getAccessibleChildrenCount()
317          {
318            TreeModel mod = getModel();
319            if (mod != null)
320              return mod.getChildCount(tp.getLastPathComponent());
321            return 0;
322          }
323          
324          /**
325           * Get the AccessibleComponent associated with this object.
326           * 
327           * @return the accessible component if it is supported.
328           */
329          public AccessibleComponent getAccessibleComponent()
330          {
331            return this;
332          }
333          
334          /**
335           * Get the AccessibleContext associated with this tree node.
336           * 
337           * @return an instance of this class
338           */
339          public AccessibleContext getAccessibleContext()
340          {
341            return this;
342          }
343          
344          /**
345           * Get the accessible description of this object.
346           * 
347           * @return the accessible description
348           */
349          public String getAccessibleDescription()
350          {
351            return super.getAccessibleDescription();
352          }
353          
354          /**
355           * Get the index of this object in its accessible parent.
356           * 
357           * @return the index of this in the parent.
358           */
359          public int getAccessibleIndexInParent()
360          {
361            AccessibleContext parent = getAccessibleParent().getAccessibleContext();
362            if (parent != null)
363              for (int i = 0; i < parent.getAccessibleChildrenCount(); i++)
364                {
365                  if ((parent.getAccessibleChild(i)).equals(this))
366                    return i;
367                }
368            return -1;
369          }
370          
371          /**
372           * Get the accessible name of this object.
373           * 
374           * @return the accessible name
375           */
376          public String getAccessibleName()
377          {
378            return super.getAccessibleName();
379          }
380          
381          /**
382           * Get the Accessible parent of this object.
383           * 
384           * @return the accessible parent if it exists.
385           */
386          public Accessible getAccessibleParent()
387          {
388            return super.getAccessibleParent();
389          }
390          
391          /**
392           * Get the role of this object.
393           * 
394           * @return the accessible role
395           */
396          public AccessibleRole getAccessibleRole()
397          {
398            return AccessibleJTree.this.getAccessibleRole();
399          }
400          
401          /**
402           * Get the AccessibleSelection associated with this object if one exists.
403           * 
404           * @return the accessible selection for this.
405           */
406          public AccessibleSelection getAccessibleSelection()
407          {
408            return this;
409          }
410          
411          /**
412           * Returns an Accessible representing the specified selected item 
413           * in the object.
414           * 
415           * @return the accessible representing a certain selected item.
416           */
417          public Accessible getAccessibleSelection(int i)
418          {
419            if (i > 0 && i < getAccessibleSelectionCount())
420                return new AccessibleJTreeNode(tree, 
421                      tp.pathByAddingChild(selectionList.get(i)), acc);
422            return null;
423          }
424          
425          /**
426           * Returns the number of items currently selected.
427           * 
428           * @return the number of items selected.
429           */
430          public int getAccessibleSelectionCount()
431          {
432            return selectionList.size();
433          }
434          
435          /**
436           * Get the state set of this object.
437           * 
438           * @return the state set for this object
439           */
440          public AccessibleStateSet getAccessibleStateSet()
441          {
442            if (isVisible())
443              states.add(AccessibleState.VISIBLE);
444            if (tree.isCollapsed(tp))
445              states.add(AccessibleState.COLLAPSED);
446            if (tree.isEditable())
447              states.add(AccessibleState.EDITABLE);
448            if (mod != null && 
449                !mod.isLeaf(tp.getLastPathComponent()))
450              states.add(AccessibleState.EXPANDABLE);
451            if (tree.isExpanded(tp))
452              states.add(AccessibleState.EXPANDED);
453            if (isFocusable())
454              states.add(AccessibleState.FOCUSABLE);
455            if (hasFocus())
456              states.add(AccessibleState.FOCUSED);
457            if (tree.getSelectionModel().getSelectionMode() != 
458              TreeSelectionModel.SINGLE_TREE_SELECTION)
459              states.add(AccessibleState.MULTISELECTABLE);
460            if (tree.isOpaque())
461              states.add(AccessibleState.OPAQUE);
462            if (tree.isPathSelected(tp))
463              states.add(AccessibleState.SELECTED);
464            if (isShowing())
465              states.add(AccessibleState.SHOWING);
466    
467            states.add(AccessibleState.SELECTABLE);
468            return states;
469          }
470          
471          /**
472           * Get the AccessibleText associated with this object if one exists.
473           * 
474           * @return the accessible text
475           */
476          public AccessibleText getAccessibleText()
477          {
478            return super.getAccessibleText();
479          }
480          
481          /**
482           * Get the AccessibleValue associated with this object if one exists.
483           * 
484           * @return the accessible value if it exists
485           */
486          public AccessibleValue getAccessibleValue()
487          {
488            return super.getAccessibleValue();
489          }
490          
491          /**
492           * Get the background color of this object.
493           * 
494           * @return the color of the background.
495           */
496          public Color getBackground()
497          {
498            return tree.getBackground();
499          }
500          
501          /**
502           * Gets the bounds of this object in the form of a Rectangle object.
503           * 
504           * @return the bounds of the current node.
505           */
506          public Rectangle getBounds()
507          {
508            return tree.getPathBounds(tp);
509          }
510          
511          /**
512           * Gets the Cursor of this object.
513           * 
514           * @return the cursor for the current node
515           */
516          public Cursor getCursor()
517          {
518            return cursor;
519          }
520          
521          /**
522           * Gets the Font of this object.
523           * 
524           * @return the font for the current node
525           */
526          public Font getFont()
527          {
528            return tree.getFont();
529          }
530          
531          /**
532           * Gets the FontMetrics of this object.
533           * 
534           * @param f - the current font.
535           * @return the font metrics for the given font.
536           */
537          public FontMetrics getFontMetrics(Font f)
538          {
539            return tree.getFontMetrics(f);
540          }
541          
542          /**
543           * Get the foreground color of this object.
544           * 
545           * @return the foreground for this object.
546           */
547          public Color getForeground()
548          {
549            return tree.getForeground();
550          }
551          
552          /**
553           * Gets the locale of the component.
554           * 
555           * @return the locale of the component.
556           */
557          public Locale getLocale()
558          {
559            return tree.getLocale();
560          }
561          
562          /**
563           * Gets the location of the object relative to the 
564           * parent in the form of a point specifying the object's 
565           * top-left corner in the screen's coordinate space. 
566           * 
567           * @return the location of the current node.
568           */
569          public Point getLocation()
570          {
571            return getLocationInJTree();
572          }
573          
574          /**
575           * Returns the location in the tree.
576           * 
577           * @return the location in the JTree.
578           */
579          protected Point getLocationInJTree()
580          {
581            Rectangle bounds = tree.getPathBounds(tp);
582            return new Point(bounds.x, bounds.y);
583          }
584          
585          /**
586           * Returns the location of the object on the screen.
587           * 
588           * @return the location of the object on the screen.
589           */
590          public Point getLocationOnScreen()
591          {
592            Point loc = getLocation();
593            SwingUtilities.convertPointToScreen(loc, tree);
594            return loc;
595          }
596          
597          /**
598           * Returns the size of this object in the form of a Dimension object.
599           * 
600           * @return the size of the object
601           */
602          public Dimension getSize()
603          {
604            Rectangle b = getBounds();
605            return b.getSize();
606          }
607          
608          /**
609           * Returns true if the current child of this object is selected.
610           * 
611           * @param i - the child of the current node
612           * @return true if the child is selected.
613           */
614          public boolean isAccessibleChildSelected(int i)
615          {
616            Object child = mod.getChild(tp.getLastPathComponent(), i);
617            if (child != null)
618              return tree.isPathSelected(tp.pathByAddingChild(child));
619            return false;
620          }
621          
622          /**
623           * Determines if the object is enabled.
624           * 
625           * @return true if the tree is enabled
626           */
627          public boolean isEnabled()
628          {
629            return tree.isEnabled();
630          }
631          
632          /**
633           * Returns whether this object can accept focus or not.
634           * 
635           * @return true, it is always focus traversable
636           */
637          public boolean isFocusTraversable()
638          {
639            return true;
640          }
641          
642          /**
643           * Determines if the object is showing.
644           * 
645           * @return true if the object is visible and the
646           * parent is visible.
647           */
648          public boolean isShowing()
649          {
650            return isVisible() && tree.isShowing();
651          }
652          
653          /**
654           * Determines if the object is visible.
655           * 
656           * @return true if the object is visible.
657           */
658          public boolean isVisible()
659          {
660            return tree.isVisible(tp);
661          }
662          
663          /**
664           * Removes the specified selected item in the object from the
665           * object's selection.
666           * 
667           * @param i - the specified item to remove
668           */
669          public void removeAccessibleSelection(int i)
670          {
671            if (mod != null)
672              {
673                Object child = mod.getChild(tp.getLastPathComponent(), i);
674                if (child != null)
675                  {
676                    if (!states.contains(AccessibleState.MULTISELECTABLE))
677                      clearAccessibleSelection();
678                    if (selectionList.contains(child))
679                      {
680                        selectionList.remove(child);                  
681                        tree.removeSelectionPath(tp.pathByAddingChild(child));
682                      }
683                  }
684              }
685          }
686          
687          /**
688           * Removes the specified focus listener so it no longer receives focus 
689           * events from this component.
690           * 
691           * @param l - the focus listener to remove
692           */
693          public void removeFocusListener(FocusListener l)
694          {
695            tree.removeFocusListener(l);
696          }
697          
698          /**
699           * Remove a PropertyChangeListener from the listener list.
700           * 
701           * @param l - the property change listener to remove.
702           */
703          public void removePropertyChangeListener(PropertyChangeListener l)
704          {
705            // Nothing to do here.
706          }
707          
708          /**
709           * Requests focus for this object.
710           */
711          public void requestFocus()
712          {
713            tree.requestFocus();
714          }
715          
716          /**
717           * Causes every selected item in the object to be selected if the object 
718           * supports multiple selections.
719           */
720          public void selectAllAccessibleSelection()
721          {
722            Object parent = tp.getLastPathComponent();
723            if (mod != null)
724              {
725                for (int i = 0; i < mod.getChildCount(parent); i++)
726                  {
727                    Object child = mod.getChild(parent, i);
728                    if (child != null)
729                      {
730                        if (!states.contains(AccessibleState.MULTISELECTABLE))
731                          clearAccessibleSelection();
732                        if (selectionList.contains(child))
733                          {
734                            selectionList.add(child);
735                            tree.addSelectionPath(tp.pathByAddingChild(child));
736                          }
737                      }
738                  }
739              }
740          }
741          
742          /**
743           * Set the accessible description of this object.
744           * 
745           * @param s - the string to set the accessible description to.
746           */
747          public void setAccessibleDescription(String s)
748          {
749            super.setAccessibleDescription(s);
750          }
751          
752          /**
753           * Set the localized accessible name of this object.
754           * 
755           * @param s - the string to set the accessible name to.
756           */
757          public void setAccessibleName(String s)
758          {
759            super.setAccessibleName(s);
760          }
761          
762          /**
763           * Set the background color of this object.
764           * 
765           * @param c - the color to set the background to.
766           */
767          public void setBackground(Color c)
768          {
769            // Nothing to do here.
770          }
771          
772          /**
773           * Sets the bounds of this object in the form of a Rectangle object.
774           * 
775           * @param r - the bounds to set the object o
776           */
777          public void setBounds(Rectangle r)
778          {
779            // Nothing to do here.
780          }
781          
782          /**
783           * Sets the Cursor of this object.
784           * 
785           * @param c - the new cursor
786           */
787          public void setCursor(Cursor c)
788          {
789            cursor = c;
790          }
791          
792          /**
793           * Sets the enabled state of the object.
794           * 
795           * @param b - boolean to enable or disable object
796           */
797          public void setEnabled(boolean b)
798          {
799             // Nothing to do here.
800          }
801          
802          /**
803           * Sets the Font of this object.
804           * 
805           * @param f - the new font.
806           */
807          public void setFont(Font f)
808          {
809             // Nothing to do here.
810          }
811          
812          /**
813           * Sets the foreground color of this object.
814           * 
815           * @param c - the new foreground color.
816           */
817          public void setForeground(Color c)
818          {
819            // Nothing to do here.
820          }
821          
822          /**
823           * Sets the location of the object relative to the parent.
824           * 
825           * @param p - the new location for the object.
826           */
827          public void setLocation(Point p)
828          {
829            // Nothing to do here.
830          }
831          
832          /**
833           * Resizes this object so that it has width and height.
834           * 
835           * @param d - the new size for the object.
836           */
837          public void setSize(Dimension d)
838          {
839            // Nothing to do here.
840          }
841          
842          /**
843           * Sets the visible state of the object.
844           * 
845           * @param b - sets the objects visibility.
846           */
847          public void setVisible(boolean b)
848          {
849            // Nothing to do here.
850          }
851        }
852        
853        /**
854         * Constructor
855         */
856        public AccessibleJTree()
857        {
858          // Nothing to do here.
859        }
860        
861        /**
862         * Adds the specified selected item in the object to the object's selection.
863         * 
864         * @param i - the row to add to the tree's selection
865         */
866        public void addAccessibleSelection(int i)
867        {
868          addSelectionInterval(i, i);
869        }
870        
871        /**
872         * Clears the selection in the object, so that nothing in the object is selected.
873         */
874        public void clearAccessibleSelection()
875        {
876          clearSelection();
877        }
878        
879        /**
880         * Fire a visible data property change notification.
881         */
882        public void fireVisibleDataPropertyChange()
883        {
884          treeDidChange();
885        }
886        
887        /**
888         * Returns the Accessible child, if one exists, contained at the local 
889         * coordinate Point.
890         * 
891         * @param p - the point of the accessible to get.
892         * @return the accessible at point p.
893         */
894        public Accessible getAccessibleAt(Point p)
895        {
896          TreePath tp = getClosestPathForLocation(p.x, p.y);
897          if (tp != null)
898            return new AccessibleJTreeNode(JTree.this, tp, null);
899          return null;
900        }
901        
902        /**
903         * Return the nth Accessible child of the object.
904         * 
905         * @param i - the accessible child to get
906         * @return the i-th child
907         */
908        public Accessible getAccessibleChild(int i)
909        {
910          return null;
911        }
912        
913        /**
914         * Returns the number of top-level children nodes of this JTree.
915         * 
916         * @return the number of top-level children
917         */
918        public int getAccessibleChildrenCount()
919        {
920          TreeModel model = getModel();
921          if (model != null)
922            return model.getChildCount(model.getRoot());
923          return 0;
924        }
925        
926        /**
927         * Get the index of this object in its accessible parent.
928         * 
929         * @return the index of this object.
930         */
931        public int getAccessibleIndexInParent()
932        {
933          return 0;
934        }
935        
936        /**
937         * Get the role of this object.
938         * 
939         * @return the role of this object
940         */
941        public AccessibleRole getAccessibleRole()
942        {
943          return AccessibleRole.TREE;
944        }
945        
946        /**
947         * Get the AccessibleSelection associated with this object.
948         * 
949         * @return the accessible selection of the tree
950         */
951        public AccessibleSelection getAccessibleSelection()
952        {
953          TreeModel mod = getModel();
954          if (mod != null)
955            return (new AccessibleJTreeNode(JTree.this, 
956                      new TreePath(mod.getRoot()), null)).getAccessibleSelection();
957          return null;
958        }
959        
960        /**
961         * Returns an Accessible representing the specified selected item in the object.
962         * 
963         * @return the i-th accessible in the selection
964         */
965        public Accessible getAccessibleSelection(int i)
966        {
967          TreeModel mod = getModel();
968          if (mod != null)
969            return (new AccessibleJTreeNode(JTree.this, 
970                      new TreePath(mod.getRoot()), null)).getAccessibleSelection(i);
971          return null;
972        }
973        
974        /**
975         * Returns the number of items currently selected.
976         * 
977         * @return the number of selected accessibles.
978         */
979        public int getAccessibleSelectionCount()
980        {
981          return getSelectionCount();
982        }
983        
984        /**
985         * Returns true if the current child of this object is selected.
986         * 
987         * @param i - the child of this object
988         * @return true if the i-th child is selected.
989         */
990        public boolean isAccessibleChildSelected(int i)
991        {
992          // Nothing to do here.
993          return false;
994        }
995        
996        /**
997         * Removes the specified selected item in the object from the object's
998         * selection.
999         * 
1000         * @param i - the i-th selected item to remove
1001         */
1002        public void removeAccessibleSelection(int i)
1003        {
1004          removeSelectionInterval(i, i);
1005        }
1006        
1007        /**
1008         * Causes every selected item in the object to be selected if the object
1009         * supports multiple selections.
1010         */
1011        public void selectAllAccessibleSelection()
1012        {
1013          if (getSelectionModel().getSelectionMode() != 
1014            TreeSelectionModel.SINGLE_TREE_SELECTION)
1015          addSelectionInterval(0, getVisibleRowCount());
1016        }
1017        
1018        /**
1019         * Tree Collapsed notification
1020         * 
1021         * @param e - the event
1022         */
1023        public void treeCollapsed(TreeExpansionEvent e)
1024        {
1025          fireTreeCollapsed(e.getPath());
1026        }
1027       
1028        /**
1029         * Tree Model Expansion notification.
1030         * 
1031         * @param e - the event
1032         */
1033        public void treeExpanded(TreeExpansionEvent e)
1034        {
1035          fireTreeExpanded(e.getPath());
1036        }
1037        
1038        /**
1039         * Tree Model Node change notification.
1040         * 
1041         * @param e - the event
1042         */
1043        public void treeNodesChanged(TreeModelEvent e)
1044        {
1045          // Nothing to do here.
1046        }
1047        
1048        /**
1049         * Tree Model Node change notification.
1050         * 
1051         * @param e - the event
1052         */
1053        public void treeNodesInserted(TreeModelEvent e)
1054        {
1055          // Nothing to do here.
1056        }
1057        
1058        /**
1059         * Tree Model Node change notification.
1060         * 
1061         * @param e - the event
1062         */
1063        public void treeNodesRemoved(TreeModelEvent e)
1064        {
1065          // Nothing to do here.
1066        }
1067        
1068        /**
1069         * Tree Model structure change change notification.
1070         * 
1071         * @param e - the event
1072         */
1073        public void treeStructureChanged(TreeModelEvent e)
1074        {
1075          // Nothing to do here.
1076        }
1077        
1078        /**
1079         * Tree Selection Listener value change method.
1080         * 
1081         * @param e - the event
1082         */
1083        public void valueChanged(TreeSelectionEvent e)
1084        {
1085          fireValueChanged(e);
1086        }
1087      }
1088      
1089      public static class DynamicUtilTreeNode extends DefaultMutableTreeNode
1090      {
1091        protected Object childValue;
1092    
1093        protected boolean loadedChildren;
1094    
1095        /**
1096         * Currently not set or used by this class. It might be set and used in
1097         * later versions of this class.
1098         */
1099        protected boolean hasChildren;
1100    
1101        public DynamicUtilTreeNode(Object value, Object children)
1102        {
1103          super(value);
1104          childValue = children;
1105          loadedChildren = false;
1106        }
1107    
1108        public int getChildCount()
1109        {
1110          loadChildren();
1111          return super.getChildCount();
1112        }
1113    
1114        protected void loadChildren()
1115        {
1116          if (!loadedChildren)
1117            {
1118              createChildren(this, childValue);
1119              loadedChildren = true;
1120            }
1121        }
1122    
1123        public Enumeration children()
1124        {
1125          loadChildren();
1126          return super.children();
1127        }
1128    
1129        /**
1130         * Returns the child node at position <code>pos</code>. Subclassed
1131         * here to load the children if necessary.
1132         * 
1133         * @param pos the position of the child node to fetch
1134         * 
1135         * @return the childnode at the specified position
1136         */
1137        public TreeNode getChildAt(int pos)
1138        {
1139          loadChildren();
1140          return super.getChildAt(pos);
1141        }
1142    
1143        public boolean isLeaf()
1144        {
1145          return childValue == null || !(childValue instanceof Hashtable
1146              || childValue instanceof Vector 
1147              || childValue.getClass().isArray());
1148        }
1149    
1150        public static void createChildren(DefaultMutableTreeNode parent,
1151                                          Object children)
1152        {
1153          if (children instanceof Hashtable)
1154            {
1155              Hashtable tab = (Hashtable) children;
1156              Enumeration e = tab.keys();
1157              while (e.hasMoreElements())
1158                {
1159                  Object key = e.nextElement();
1160                  Object val = tab.get(key);
1161                  parent.add(new DynamicUtilTreeNode(key, val));
1162                }
1163            }
1164          else if (children instanceof Vector)
1165            {
1166              Iterator i = ((Vector) children).iterator();
1167              while (i.hasNext())
1168                {
1169                  Object n = i.next();
1170                  parent.add(new DynamicUtilTreeNode(n, n));
1171                }
1172            }
1173          else if (children != null && children.getClass().isArray())
1174            {
1175              Object[] arr = (Object[]) children;
1176              for (int i = 0; i < arr.length; ++i)
1177                parent.add(new DynamicUtilTreeNode(arr[i], arr[i]));
1178            }
1179        }
1180      }
1181    
1182      /**
1183       * Listens to the model of the JTree and updates the property 
1184       * <code>expandedState</code> if nodes are removed or changed.
1185       */
1186      protected class TreeModelHandler implements TreeModelListener
1187      {
1188    
1189        /**
1190         * Creates a new instance of TreeModelHandler.
1191         */
1192        protected TreeModelHandler()
1193        {
1194          // Nothing to do here.
1195        }
1196    
1197        /**
1198         * Notifies when a node has changed in some ways. This does not include
1199         * that a node has changed its location or changed it's children. It
1200         * only means that some attributes of the node have changed that might
1201         * affect its presentation.
1202         * 
1203         * This method is called after the actual change occured.
1204         * 
1205         * @param ev the TreeModelEvent describing the change
1206         */
1207        public void treeNodesChanged(TreeModelEvent ev)
1208        {
1209          // Nothing to do here.
1210        }
1211    
1212        /**
1213         * Notifies when a node is inserted into the tree.
1214         * 
1215         * This method is called after the actual change occured.
1216         * 
1217         * @param ev the TreeModelEvent describing the change
1218         */
1219        public void treeNodesInserted(TreeModelEvent ev)
1220        {
1221          // nothing to do here
1222        }
1223    
1224        /**
1225         * Notifies when a node is removed from the tree.
1226         * 
1227         * This method is called after the actual change occured.
1228         *
1229         * @param ev the TreeModelEvent describing the change
1230             */
1231        public void treeNodesRemoved(TreeModelEvent ev)
1232        {
1233          if (ev != null)
1234            {
1235              TreePath parent = ev.getTreePath();
1236              Object[] children = ev.getChildren();
1237              TreeSelectionModel sm = getSelectionModel();
1238              if (children != null)
1239                {
1240                  TreePath path;
1241                  Vector toRemove = new Vector();
1242                  // Collect items that we must remove.
1243                  for (int i = children.length - 1; i >= 0; i--)
1244                    {
1245                      path = parent.pathByAddingChild(children[i]);
1246                      if (nodeStates.containsKey(path))
1247                        toRemove.add(path);
1248                      // Clear selection while we are at it.
1249                      if (sm != null)
1250                        removeDescendantSelectedPaths(path, true);
1251                    }
1252                  if (toRemove.size() > 0)
1253                    removeDescendantToggledPaths(toRemove.elements());
1254                  TreeModel model = getModel();
1255                  if (model == null || model.isLeaf(parent.getLastPathComponent()))
1256                    nodeStates.remove(parent);
1257                }
1258            }
1259        }
1260    
1261        /**
1262         * Notifies when the structure of the tree is changed.
1263         * 
1264         * This method is called after the actual change occured.
1265         * 
1266         * @param ev the TreeModelEvent describing the change
1267         */
1268        public void treeStructureChanged(TreeModelEvent ev)
1269        {
1270          if (ev != null)
1271            {
1272              TreePath parent = ev.getTreePath();
1273              if (parent != null)
1274                {
1275                  if (parent.getPathCount() == 1)
1276                    {
1277                      // We have a new root, clear everything.
1278                      clearToggledPaths();
1279                      Object root = treeModel.getRoot();
1280                      if (root != null && treeModel.isLeaf(root))
1281                        nodeStates.put(parent, Boolean.TRUE);
1282                    }
1283                  else if (nodeStates.containsKey(parent))
1284                    {
1285                      Vector toRemove = new Vector();
1286                      boolean expanded = isExpanded(parent);
1287                      toRemove.add(parent);
1288                      removeDescendantToggledPaths(toRemove.elements());
1289                      if (expanded)
1290                        {
1291                          TreeModel model = getModel();
1292                          if (model != null
1293                              || model.isLeaf(parent.getLastPathComponent()))
1294                            collapsePath(parent);
1295                          else
1296                            nodeStates.put(parent, Boolean.TRUE);
1297                        }
1298                    }
1299                  removeDescendantSelectedPaths(parent, false);
1300                }
1301            }
1302        }
1303      }
1304    
1305      /**
1306       * This redirects TreeSelectionEvents and rewrites the source of it to be
1307       * this JTree. This is typically done when the tree model generates an
1308       * event, but the JTree object associated with that model should be listed
1309       * as the actual source of the event.
1310       */
1311      protected class TreeSelectionRedirector implements TreeSelectionListener,
1312                                                         Serializable
1313      {
1314        /** The serial version UID. */
1315        private static final long serialVersionUID = -3505069663646241664L;
1316    
1317        /**
1318         * Creates a new instance of TreeSelectionRedirector
1319         */
1320        protected TreeSelectionRedirector()
1321        {
1322          // Nothing to do here.
1323        }
1324    
1325        /**
1326         * Notifies when the tree selection changes.
1327         * 
1328         * @param ev the TreeSelectionEvent that describes the change
1329         */
1330        public void valueChanged(TreeSelectionEvent ev)
1331        {
1332          TreeSelectionEvent rewritten = 
1333            (TreeSelectionEvent) ev.cloneWithSource(JTree.this);
1334          fireValueChanged(rewritten);
1335        }
1336      }
1337    
1338      /**
1339       * A TreeModel that does not allow anything to be selected.
1340       */
1341      protected static class EmptySelectionModel extends DefaultTreeSelectionModel
1342      {
1343        /** The serial version UID. */
1344        private static final long serialVersionUID = -5815023306225701477L;
1345    
1346        /**
1347         * The shared instance of this model.
1348         */
1349        protected static final EmptySelectionModel sharedInstance =
1350          new EmptySelectionModel();
1351    
1352        /**
1353         * Creates a new instance of EmptySelectionModel.
1354         */
1355        protected EmptySelectionModel()
1356        {
1357          // Nothing to do here.
1358        }
1359    
1360        /**
1361         * Returns the shared instance of EmptySelectionModel.
1362         * 
1363         * @return the shared instance of EmptySelectionModel
1364         */
1365        public static EmptySelectionModel sharedInstance()
1366        {
1367          return sharedInstance;
1368        }
1369    
1370        /**
1371         * This catches attempts to set a selection and sets nothing instead.
1372         * 
1373         * @param paths not used here
1374         */
1375        public void setSelectionPaths(TreePath[] paths)
1376        {
1377          // We don't allow selections in this class.
1378        }
1379    
1380        /**
1381         * This catches attempts to add something to the selection.
1382         * 
1383         * @param paths not used here
1384         */
1385        public void addSelectionPaths(TreePath[] paths)
1386        {
1387          // We don't allow selections in this class.
1388        }
1389    
1390        /**
1391         * This catches attempts to remove something from the selection.
1392         * 
1393         * @param paths not used here
1394         */
1395        public void removeSelectionPaths(TreePath[] paths)
1396        {
1397          // We don't allow selections in this class.
1398        }
1399      }
1400    
1401      private static final long serialVersionUID = 7559816092864483649L;
1402    
1403      public static final String CELL_EDITOR_PROPERTY = "cellEditor";
1404    
1405      public static final String CELL_RENDERER_PROPERTY = "cellRenderer";
1406    
1407      public static final String EDITABLE_PROPERTY = "editable";
1408    
1409      public static final String INVOKES_STOP_CELL_EDITING_PROPERTY =
1410        "invokesStopCellEditing";
1411    
1412      public static final String LARGE_MODEL_PROPERTY = "largeModel";
1413    
1414      public static final String ROOT_VISIBLE_PROPERTY = "rootVisible";
1415    
1416      public static final String ROW_HEIGHT_PROPERTY = "rowHeight";
1417    
1418      public static final String SCROLLS_ON_EXPAND_PROPERTY = "scrollsOnExpand";
1419    
1420      public static final String SELECTION_MODEL_PROPERTY = "selectionModel";
1421    
1422      public static final String SHOWS_ROOT_HANDLES_PROPERTY = "showsRootHandles";
1423    
1424      public static final String TOGGLE_CLICK_COUNT_PROPERTY = "toggleClickCount";
1425    
1426      public static final String TREE_MODEL_PROPERTY = "model";
1427    
1428      public static final String VISIBLE_ROW_COUNT_PROPERTY = "visibleRowCount";
1429    
1430      /** @since 1.3 */
1431      public static final String ANCHOR_SELECTION_PATH_PROPERTY =
1432        "anchorSelectionPath";
1433    
1434            /** @since 1.3 */
1435      public static final String LEAD_SELECTION_PATH_PROPERTY = "leadSelectionPath";
1436    
1437      /** @since 1.3 */
1438      public static final String EXPANDS_SELECTED_PATHS_PROPERTY =
1439        "expandsSelectedPaths";
1440    
1441      private static final Object EXPANDED = Boolean.TRUE;
1442    
1443      private static final Object COLLAPSED = Boolean.FALSE;
1444    
1445      private boolean dragEnabled;
1446    
1447      private boolean expandsSelectedPaths;
1448    
1449      private TreePath anchorSelectionPath;
1450    
1451      /**
1452       * This contains the state of all nodes in the tree. Al/ entries map the
1453       * TreePath of a note to to its state. Valid states are EXPANDED and
1454       * COLLAPSED. Nodes not in this Hashtable are assumed state COLLAPSED.
1455       *
1456       * This is package private to avoid accessor methods.
1457       */
1458      Hashtable nodeStates = new Hashtable();
1459    
1460      protected transient TreeCellEditor cellEditor;
1461    
1462      protected transient TreeCellRenderer cellRenderer;
1463    
1464      protected boolean editable;
1465    
1466      protected boolean invokesStopCellEditing;
1467    
1468      protected boolean largeModel;
1469    
1470      protected boolean rootVisible;
1471    
1472      protected int rowHeight;
1473    
1474      protected boolean scrollsOnExpand;
1475    
1476      protected transient TreeSelectionModel selectionModel;
1477    
1478      protected boolean showsRootHandles;
1479    
1480      protected int toggleClickCount;
1481    
1482      protected transient TreeModel treeModel;
1483    
1484      protected int visibleRowCount;
1485    
1486      /**
1487       * Handles TreeModelEvents to update the expandedState.
1488       */
1489      protected transient TreeModelListener treeModelListener;
1490    
1491      /**
1492       * Redirects TreeSelectionEvents so that the source is this JTree.
1493       */
1494      protected TreeSelectionRedirector selectionRedirector =
1495        new TreeSelectionRedirector();
1496    
1497      /**
1498       * Indicates if the rowHeight property has been set by a client
1499       * program or by the UI.
1500       *
1501       * @see #setUIProperty(String, Object)
1502       * @see LookAndFeel#installProperty(JComponent, String, Object)
1503       */
1504      private boolean clientRowHeightSet = false;
1505    
1506      /**
1507       * Indicates if the scrollsOnExpand property has been set by a client
1508       * program or by the UI.
1509       *
1510       * @see #setUIProperty(String, Object)
1511       * @see LookAndFeel#installProperty(JComponent, String, Object)
1512       */
1513      private boolean clientScrollsOnExpandSet = false;
1514    
1515      /**
1516       * Indicates if the showsRootHandles property has been set by a client
1517       * program or by the UI.
1518       *
1519       * @see #setUIProperty(String, Object)
1520       * @see LookAndFeel#installProperty(JComponent, String, Object)
1521       */
1522      private boolean clientShowsRootHandlesSet = false;
1523    
1524      /**
1525       * Creates a new <code>JTree</code> object.
1526       */
1527      public JTree()
1528      {
1529        this(getDefaultTreeModel());
1530      }
1531    
1532      /**
1533       * Creates a new <code>JTree</code> object.
1534       * 
1535       * @param value the initial nodes in the tree
1536       */
1537      public JTree(Hashtable<?, ?> value)
1538      {
1539        this(createTreeModel(value));
1540      }
1541    
1542      /**
1543       * Creates a new <code>JTree</code> object.
1544       * 
1545       * @param value the initial nodes in the tree
1546       */
1547      public JTree(Object[] value)
1548      {
1549        this(createTreeModel(value));
1550      }
1551    
1552      /**
1553       * Creates a new <code>JTree</code> object.
1554       * 
1555       * @param model the model to use
1556       */
1557      public JTree(TreeModel model)
1558      {
1559        setRootVisible(true);
1560        setSelectionModel( new DefaultTreeSelectionModel() );
1561        
1562        // The root node appears expanded by default.
1563        nodeStates = new Hashtable();
1564    
1565        // The cell renderer gets set by the UI.
1566        cellRenderer = null;
1567    
1568        // Install the UI before installing the model. This way we avoid double
1569        // initialization of lots of UI and model stuff inside the UI and related
1570        // classes. The necessary UI updates are performed via property change
1571        // events to the UI.
1572        updateUI();
1573        setModel(model);
1574      }
1575    
1576      /**
1577       * Creates a new <code>JTree</code> object.
1578       * 
1579       * @param root the root node
1580       */
1581      public JTree(TreeNode root)
1582      {
1583        this(root, false);
1584      }
1585    
1586      /**
1587       * Creates a new <code>JTree</code> object.
1588       * 
1589       * @param root the root node
1590       * @param asksAllowChildren if false, all nodes without children are leaf
1591       *        nodes. If true, only nodes that do not allow children are leaf
1592       *        nodes.
1593       */
1594      public JTree(TreeNode root, boolean asksAllowChildren)
1595      {
1596        this(new DefaultTreeModel(root, asksAllowChildren));
1597      }
1598    
1599      /**
1600       * Creates a new <code>JTree</code> object.
1601       * 
1602       * @param value the initial nodes in the tree
1603       */
1604      public JTree(Vector<?> value)
1605      {
1606        this(createTreeModel(value));
1607      }
1608    
1609      public int getRowForPath(TreePath path)
1610      {
1611        TreeUI ui = getUI();
1612    
1613        if (ui != null)
1614          return ui.getRowForPath(this, path);
1615    
1616        return -1;
1617      }
1618    
1619      public TreePath getPathForRow(int row)
1620      {
1621        TreeUI ui = getUI();
1622        return ui != null ? ui.getPathForRow(this, row) : null;
1623      }
1624      
1625      /**
1626       * Get the pathes that are displayes between the two given rows.
1627       * 
1628       * @param index0 the starting row, inclusive
1629       * @param index1 the ending row, inclusive
1630       * 
1631       * @return the array of the tree pathes
1632       */
1633      protected TreePath[] getPathBetweenRows(int index0, int index1)
1634      {
1635        TreeUI ui = getUI();
1636    
1637        if (ui == null)
1638          return null;
1639    
1640        int minIndex = Math.min(index0, index1);
1641        int maxIndex = Math.max(index0, index1);
1642        TreePath[] paths = new TreePath[maxIndex - minIndex + 1];
1643    
1644        for (int i = minIndex; i <= maxIndex; ++i)
1645          paths[i - minIndex] = ui.getPathForRow(this, i);
1646    
1647        return paths;
1648      }
1649    
1650      /**
1651       * Creates a new <code>TreeModel</code> object.
1652       * 
1653       * @param value the values stored in the model
1654       */
1655      protected static TreeModel createTreeModel(Object value)
1656      {
1657        return new DefaultTreeModel(new DynamicUtilTreeNode(value, value));
1658      }
1659    
1660      /**
1661       * Return the UI associated with this <code>JTree</code> object.
1662       * 
1663       * @return the associated <code>TreeUI</code> object
1664       */
1665      public TreeUI getUI()
1666      {
1667        return (TreeUI) ui;
1668      }
1669    
1670      /**
1671       * Sets the UI associated with this <code>JTree</code> object.
1672       * 
1673       * @param ui the <code>TreeUI</code> to associate
1674       */
1675      public void setUI(TreeUI ui)
1676      {
1677        super.setUI(ui);
1678      }
1679    
1680      /**
1681       * This method resets the UI used to the Look and Feel defaults..
1682       */
1683      public void updateUI()
1684      {
1685        setUI((TreeUI) UIManager.getUI(this));
1686      }
1687    
1688      /**
1689       * This method returns the String ID of the UI class of Separator.
1690       * 
1691       * @return The UI class' String ID.
1692       */
1693      public String getUIClassID()
1694      {
1695        return "TreeUI";
1696      }
1697    
1698      /**
1699       * Gets the AccessibleContext associated with this
1700       * <code>JTree</code>.
1701       * 
1702       * @return the associated context
1703       */
1704      public AccessibleContext getAccessibleContext()
1705      {
1706        return new AccessibleJTree();
1707      }
1708    
1709      /**
1710       * Returns the preferred viewport size.
1711       * 
1712       * @return the preferred size
1713       */
1714      public Dimension getPreferredScrollableViewportSize()
1715      {
1716        return getPreferredSize();
1717      }
1718      
1719      /**
1720       * Return the preferred scrolling amount (in pixels) for the given scrolling
1721       * direction and orientation. This method handles a partially exposed row by
1722       * returning the distance required to completely expose the item.
1723       * 
1724       * @param visibleRect the currently visible part of the component.
1725       * @param orientation the scrolling orientation
1726       * @param direction the scrolling direction (negative - up, positive -down).
1727       *          The values greater than one means that more mouse wheel or similar
1728       *          events were generated, and hence it is better to scroll the longer
1729       *          distance.
1730       * @author Audrius Meskauskas (audriusa@bioinformatics.org)
1731       */
1732      public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
1733                                            int direction)
1734      {
1735        int delta = 0;
1736    
1737        // Round so that the top would start from the row boundary
1738        if (orientation == SwingConstants.VERTICAL)
1739          {
1740            int row = getClosestRowForLocation(0, visibleRect.y);
1741            if (row != -1)
1742              {
1743                Rectangle b = getRowBounds(row);
1744                if (b.y != visibleRect.y)
1745                  {
1746                    if (direction < 0)
1747                      delta = Math.max(0, visibleRect.y - b.y);
1748                    else
1749                      delta = b.y + b.height - visibleRect.y;
1750                  }
1751                else
1752                  {
1753                    if (direction < 0)
1754                      {
1755                        if (row != 0)
1756                          {
1757                            b = getRowBounds(row - 1);
1758                            delta = b.height;
1759                          }
1760                      }
1761                    else
1762                      delta = b.height;
1763                  }
1764              }
1765          }
1766        else
1767          // The RI always  returns 4 for HORIZONTAL scrolling.
1768          delta = 4;
1769        return delta;
1770      }
1771    
1772      public int getScrollableBlockIncrement(Rectangle visibleRect,
1773                                             int orientation, int direction)
1774      {
1775        int block;
1776        if (orientation == SwingConstants.VERTICAL)
1777          block = visibleRect.height;
1778        else
1779          block = visibleRect.width;
1780        return block;
1781      }
1782    
1783      public boolean getScrollableTracksViewportHeight()
1784      {
1785        if (getParent() instanceof JViewport)
1786          return ((JViewport) getParent()).getHeight() > getPreferredSize().height;
1787        return false;
1788      }
1789    
1790      public boolean getScrollableTracksViewportWidth()
1791      {
1792        if (getParent() instanceof JViewport)
1793          return ((JViewport) getParent()).getWidth() > getPreferredSize().width;
1794        return false;
1795      }
1796    
1797      /**
1798       * Adds a <code>TreeExpansionListener</code> object to the tree.
1799       * 
1800       * @param listener the listener to add
1801       */
1802      public void addTreeExpansionListener(TreeExpansionListener listener)
1803      {
1804        listenerList.add(TreeExpansionListener.class, listener);
1805      }
1806    
1807      /**
1808       * Removes a <code>TreeExpansionListener</code> object from the tree.
1809       * 
1810       * @param listener the listener to remove
1811       */
1812      public void removeTreeExpansionListener(TreeExpansionListener listener)
1813      {
1814        listenerList.remove(TreeExpansionListener.class, listener);
1815      }
1816    
1817      /**
1818       * Returns all added <code>TreeExpansionListener</code> objects.
1819       * 
1820       * @return an array of listeners
1821       */
1822      public TreeExpansionListener[] getTreeExpansionListeners()
1823      {
1824        return (TreeExpansionListener[]) getListeners(TreeExpansionListener.class);
1825      }
1826    
1827      /**
1828       * Notifies all listeners that the tree was collapsed.
1829       * 
1830       * @param path the path to the node that was collapsed
1831       */
1832      public void fireTreeCollapsed(TreePath path)
1833      {
1834        TreeExpansionEvent event = new TreeExpansionEvent(this, path);
1835        TreeExpansionListener[] listeners = getTreeExpansionListeners();
1836    
1837        for (int index = 0; index < listeners.length; ++index)
1838          listeners[index].treeCollapsed(event);
1839      }
1840    
1841      /**
1842       * Notifies all listeners that the tree was expanded.
1843       * 
1844       * @param path the path to the node that was expanded
1845       */
1846      public void fireTreeExpanded(TreePath path)
1847      {
1848        TreeExpansionEvent event = new TreeExpansionEvent(this, path);
1849        TreeExpansionListener[] listeners = getTreeExpansionListeners();
1850    
1851        for (int index = 0; index < listeners.length; ++index)
1852          listeners[index].treeExpanded(event);
1853      }
1854    
1855      /**
1856       * Adds a <code>TreeSelctionListener</code> object to the tree.
1857       * 
1858       * @param listener the listener to add
1859       */
1860      public void addTreeSelectionListener(TreeSelectionListener listener)
1861      {
1862        listenerList.add(TreeSelectionListener.class, listener);
1863      }
1864    
1865      /**
1866       * Removes a <code>TreeSelectionListener</code> object from the tree.
1867       * 
1868       * @param listener the listener to remove
1869       */
1870      public void removeTreeSelectionListener(TreeSelectionListener listener)
1871      {
1872        listenerList.remove(TreeSelectionListener.class, listener);
1873      }
1874    
1875      /**
1876       * Returns all added <code>TreeSelectionListener</code> objects.
1877       * 
1878       * @return an array of listeners
1879       */
1880      public TreeSelectionListener[] getTreeSelectionListeners()
1881      {
1882        return (TreeSelectionListener[]) 
1883        getListeners(TreeSelectionListener.class);
1884      }
1885    
1886      /**
1887       * Notifies all listeners when the selection of the tree changed.
1888       * 
1889       * @param event the event to send
1890       */
1891      protected void fireValueChanged(TreeSelectionEvent event)
1892      {
1893        TreeSelectionListener[] listeners = getTreeSelectionListeners();
1894    
1895        for (int index = 0; index < listeners.length; ++index)
1896          listeners[index].valueChanged(event);
1897      }
1898    
1899      /**
1900       * Adds a <code>TreeWillExpandListener</code> object to the tree.
1901       * 
1902       * @param listener the listener to add
1903       */
1904      public void addTreeWillExpandListener(TreeWillExpandListener listener)
1905      {
1906        listenerList.add(TreeWillExpandListener.class, listener);
1907      }
1908    
1909      /**
1910       * Removes a <code>TreeWillExpandListener</code> object from the tree.
1911       * 
1912       * @param listener the listener to remove
1913       */
1914      public void removeTreeWillExpandListener(TreeWillExpandListener listener)
1915      {
1916        listenerList.remove(TreeWillExpandListener.class, listener);
1917      }
1918    
1919      /**
1920       * Returns all added <code>TreeWillExpandListener</code> objects.
1921       * 
1922       * @return an array of listeners
1923       */
1924      public TreeWillExpandListener[] getTreeWillExpandListeners()
1925      {
1926        return (TreeWillExpandListener[]) 
1927        getListeners(TreeWillExpandListener.class);
1928      }
1929    
1930      /**
1931       * Notifies all listeners that the tree will collapse.
1932       * 
1933       * @param path the path to the node that will collapse
1934       */
1935      public void fireTreeWillCollapse(TreePath path) throws ExpandVetoException
1936      {
1937        TreeExpansionEvent event = new TreeExpansionEvent(this, path);
1938        TreeWillExpandListener[] listeners = getTreeWillExpandListeners();
1939    
1940        for (int index = 0; index < listeners.length; ++index)
1941          listeners[index].treeWillCollapse(event);
1942      }
1943    
1944      /**
1945       * Notifies all listeners that the tree will expand.
1946       * 
1947       * @param path the path to the node that will expand
1948       */
1949      public void fireTreeWillExpand(TreePath path) throws ExpandVetoException
1950      {
1951        TreeExpansionEvent event = new TreeExpansionEvent(this, path);
1952        TreeWillExpandListener[] listeners = getTreeWillExpandListeners();
1953    
1954        for (int index = 0; index < listeners.length; ++index)
1955          listeners[index].treeWillExpand(event);
1956      }
1957    
1958      /**
1959       * Returns the model of this <code>JTree</code> object.
1960       * 
1961       * @return the associated <code>TreeModel</code>
1962       */
1963      public TreeModel getModel()
1964      {
1965        return treeModel;
1966      }
1967    
1968      /**
1969       * Sets the model to use in <code>JTree</code>.
1970       * 
1971       * @param model the <code>TreeModel</code> to use
1972       */
1973      public void setModel(TreeModel model)
1974      {
1975        if (treeModel == model)
1976          return;
1977    
1978        // Remove listeners from old model.
1979        if (treeModel != null && treeModelListener != null)
1980          treeModel.removeTreeModelListener(treeModelListener);
1981    
1982        // add treeModelListener to the new model
1983        if (treeModelListener == null)
1984          treeModelListener = createTreeModelListener();
1985        if (model != null) // as setModel(null) is allowed
1986          model.addTreeModelListener(treeModelListener);
1987    
1988        TreeModel oldValue = treeModel;
1989        treeModel = model;
1990        clearToggledPaths();
1991    
1992        if (treeModel != null)
1993          {
1994            if (treeModelListener == null)
1995              treeModelListener = createTreeModelListener();
1996            if (treeModelListener != null)
1997              treeModel.addTreeModelListener(treeModelListener);
1998            Object root = treeModel.getRoot();
1999            if (root != null && !treeModel.isLeaf(root))
2000              {
2001                nodeStates.put(new TreePath(root), Boolean.TRUE);
2002              }
2003          }
2004    
2005        firePropertyChange(TREE_MODEL_PROPERTY, oldValue, model);
2006      }
2007    
2008      /**
2009       * Checks if this <code>JTree</code> object is editable.
2010       * 
2011       * @return <code>true</code> if this tree object is editable,
2012       *         <code>false</code> otherwise
2013       */
2014      public boolean isEditable()
2015      {
2016        return editable;
2017      }
2018    
2019      /**
2020       * Sets the <code>editable</code> property.
2021       * 
2022       * @param flag <code>true</code> to make this tree object editable,
2023       *        <code>false</code> otherwise
2024       */
2025      public void setEditable(boolean flag)
2026      {
2027        if (editable == flag)
2028          return;
2029    
2030        boolean oldValue = editable;
2031        editable = flag;
2032        firePropertyChange(EDITABLE_PROPERTY, oldValue, editable);
2033      }
2034    
2035      /**
2036       * Checks if the root element is visible.
2037       * 
2038       * @return <code>true</code> if the root element is visible,
2039       *         <code>false</code> otherwise
2040       */
2041      public boolean isRootVisible()
2042      {
2043        return rootVisible;
2044      }
2045    
2046      public void setRootVisible(boolean flag)
2047      {
2048        if (rootVisible == flag)
2049          return;
2050    
2051        // If the root is currently selected, unselect it
2052        if (rootVisible && !flag)
2053          {
2054            TreeSelectionModel model = getSelectionModel();
2055            // The root is always shown in the first row
2056            TreePath rootPath = getPathForRow(0);
2057            model.removeSelectionPath(rootPath);
2058          }
2059        
2060        boolean oldValue = rootVisible;
2061        rootVisible = flag;
2062        firePropertyChange(ROOT_VISIBLE_PROPERTY, oldValue, flag);
2063        
2064      }
2065    
2066      public boolean getShowsRootHandles()
2067      {
2068        return showsRootHandles;
2069      }
2070    
2071      public void setShowsRootHandles(boolean flag)
2072      {
2073        clientShowsRootHandlesSet = true;
2074    
2075        if (showsRootHandles == flag)
2076          return;
2077        
2078        boolean oldValue = showsRootHandles;
2079        showsRootHandles = flag;
2080        firePropertyChange(SHOWS_ROOT_HANDLES_PROPERTY, oldValue, flag);
2081      }
2082    
2083      public TreeCellEditor getCellEditor()
2084      {
2085        return cellEditor;
2086      }
2087    
2088      public void setCellEditor(TreeCellEditor editor)
2089      {
2090        if (cellEditor == editor)
2091          return;
2092    
2093        TreeCellEditor oldValue = cellEditor;
2094        cellEditor = editor;
2095        firePropertyChange(CELL_EDITOR_PROPERTY, oldValue, editor);
2096      }
2097    
2098      public TreeCellRenderer getCellRenderer()
2099      {
2100        return cellRenderer;
2101      }
2102    
2103      public void setCellRenderer(TreeCellRenderer newRenderer)
2104      {
2105        if (cellRenderer == newRenderer)
2106          return;
2107    
2108        TreeCellRenderer oldValue = cellRenderer;
2109        cellRenderer = newRenderer;
2110        firePropertyChange(CELL_RENDERER_PROPERTY, oldValue, newRenderer);
2111      }
2112    
2113      public TreeSelectionModel getSelectionModel()
2114      {
2115        return selectionModel;
2116      }
2117    
2118      public void setSelectionModel(TreeSelectionModel model)
2119      {
2120        if (selectionModel == model)
2121          return;
2122    
2123        if( model == null )
2124          model = EmptySelectionModel.sharedInstance();
2125    
2126        if (selectionModel != null)
2127          selectionModel.removeTreeSelectionListener(selectionRedirector);
2128    
2129        TreeSelectionModel oldValue = selectionModel;
2130        selectionModel = model;
2131    
2132        selectionModel.addTreeSelectionListener(selectionRedirector);
2133    
2134        firePropertyChange(SELECTION_MODEL_PROPERTY, oldValue, model);
2135        revalidate();
2136        repaint();
2137      }
2138    
2139      public int getVisibleRowCount()
2140      {
2141        return visibleRowCount;
2142      }
2143    
2144      public void setVisibleRowCount(int rows)
2145      {
2146        if (visibleRowCount == rows)
2147          return;
2148    
2149        int oldValue = visibleRowCount;
2150        visibleRowCount = rows;
2151        firePropertyChange(VISIBLE_ROW_COUNT_PROPERTY, oldValue, rows);
2152      }
2153    
2154      public boolean isLargeModel()
2155      {
2156        return largeModel;
2157      }
2158    
2159      public void setLargeModel(boolean large)
2160      {
2161        if (largeModel == large)
2162          return;
2163    
2164        boolean oldValue = largeModel;
2165        largeModel = large;
2166        firePropertyChange(LARGE_MODEL_PROPERTY, oldValue, large);
2167      }
2168    
2169      public int getRowHeight()
2170      {
2171        return rowHeight;
2172      }
2173    
2174      public void setRowHeight(int height)
2175      {
2176        clientRowHeightSet = true;
2177    
2178        if (rowHeight == height)
2179          return;
2180    
2181        int oldValue = rowHeight;
2182        rowHeight = height;
2183        firePropertyChange(ROW_HEIGHT_PROPERTY, oldValue, height);
2184      }
2185    
2186      public boolean isFixedRowHeight()
2187      {
2188        return rowHeight > 0;
2189      }
2190    
2191      public boolean getInvokesStopCellEditing()
2192      {
2193        return invokesStopCellEditing;
2194      }
2195    
2196      public void setInvokesStopCellEditing(boolean invoke)
2197      {
2198        if (invokesStopCellEditing == invoke)
2199          return;
2200    
2201        boolean oldValue = invokesStopCellEditing;
2202        invokesStopCellEditing = invoke;
2203        firePropertyChange(INVOKES_STOP_CELL_EDITING_PROPERTY, 
2204                           oldValue, invoke);
2205      }
2206    
2207      /**
2208       * @since 1.3
2209       */
2210      public int getToggleClickCount()
2211      {
2212        return toggleClickCount;
2213      }
2214    
2215      /**
2216       * @since 1.3
2217       */
2218      public void setToggleClickCount(int count)
2219      {
2220        if (toggleClickCount == count)
2221          return;
2222    
2223        int oldValue = toggleClickCount;
2224        toggleClickCount = count;
2225        firePropertyChange(TOGGLE_CLICK_COUNT_PROPERTY, oldValue, count);
2226      }
2227    
2228      public void scrollPathToVisible(TreePath path)
2229      {
2230        if (path == null)
2231          return;
2232        Rectangle rect = getPathBounds(path);
2233        scrollRectToVisible(rect);
2234      }
2235    
2236      public void scrollRowToVisible(int row)
2237      {
2238        scrollPathToVisible(getPathForRow(row));
2239      }
2240    
2241      public boolean getScrollsOnExpand()
2242      {
2243        return scrollsOnExpand;
2244      }
2245    
2246      public void setScrollsOnExpand(boolean scroll)
2247      {
2248        clientScrollsOnExpandSet = true;
2249        if (scrollsOnExpand == scroll)
2250          return;
2251    
2252        boolean oldValue = scrollsOnExpand;
2253        scrollsOnExpand = scroll;
2254        firePropertyChange(SCROLLS_ON_EXPAND_PROPERTY, oldValue, scroll);
2255      }
2256    
2257      public void setSelectionPath(TreePath path)
2258      {
2259        clearSelectionPathStates();
2260        selectionModel.setSelectionPath(path);
2261      }
2262    
2263      public void setSelectionPaths(TreePath[] paths)
2264      {
2265        clearSelectionPathStates();
2266        selectionModel.setSelectionPaths(paths);
2267      }
2268      
2269      /**
2270       * This method, and all calls to it, should be removed once the
2271       * DefaultTreeModel fires events properly.  Maintenance of the nodeStates
2272       * table should really be done in the TreeModelHandler.  
2273       */
2274      private void clearSelectionPathStates()
2275      {
2276        TreePath[] oldPaths = selectionModel.getSelectionPaths();
2277        if (oldPaths != null)
2278          for (int i = 0; i < oldPaths.length; i++)
2279            nodeStates.remove(oldPaths[i]);
2280      }
2281    
2282      public void setSelectionRow(int row)
2283      {
2284        TreePath path = getPathForRow(row);
2285    
2286        if (path != null)
2287          setSelectionPath(path);
2288      }
2289    
2290      public void setSelectionRows(int[] rows)
2291      {
2292        // Make sure we have an UI so getPathForRow() does not return null.
2293        if (rows == null || getUI() == null)
2294          return;
2295    
2296        TreePath[] paths = new TreePath[rows.length];
2297    
2298        for (int i = rows.length - 1; i >= 0; --i)
2299          paths[i] = getPathForRow(rows[i]);
2300    
2301        setSelectionPaths(paths);
2302      }
2303    
2304      public void setSelectionInterval(int index0, int index1)
2305      {
2306        TreePath[] paths = getPathBetweenRows(index0, index1);
2307    
2308        if (paths != null)
2309          setSelectionPaths(paths);
2310      }
2311    
2312      public void addSelectionPath(TreePath path)
2313      {
2314        selectionModel.addSelectionPath(path);
2315      }
2316    
2317      public void addSelectionPaths(TreePath[] paths)
2318      {
2319        selectionModel.addSelectionPaths(paths);
2320      }
2321    
2322      public void addSelectionRow(int row)
2323      {
2324        TreePath path = getPathForRow(row);
2325    
2326        if (path != null)
2327          selectionModel.addSelectionPath(path);
2328      }
2329    
2330      public void addSelectionRows(int[] rows)
2331      {
2332        // Make sure we have an UI so getPathForRow() does not return null.
2333        if (rows == null || getUI() == null)
2334          return;
2335    
2336        TreePath[] paths = new TreePath[rows.length];
2337    
2338        for (int i = rows.length - 1; i >= 0; --i)
2339          paths[i] = getPathForRow(rows[i]);
2340    
2341        addSelectionPaths(paths);
2342      }
2343      
2344      /**
2345       * Select all rows between the two given indexes, inclusive. The method
2346       * will not select the inner leaves and braches of the currently collapsed
2347       * nodes in this interval.
2348       * 
2349       * @param index0 the starting row, inclusive
2350       * @param index1 the ending row, inclusive
2351       */
2352      public void addSelectionInterval(int index0, int index1)
2353      {
2354        TreePath[] paths = getPathBetweenRows(index0, index1);
2355    
2356        if (paths != null)
2357          addSelectionPaths(paths);
2358      }
2359    
2360      public void removeSelectionPath(TreePath path)
2361      {
2362        clearSelectionPathStates();
2363        selectionModel.removeSelectionPath(path);
2364      }
2365    
2366      public void removeSelectionPaths(TreePath[] paths)
2367      {
2368        clearSelectionPathStates();
2369        selectionModel.removeSelectionPaths(paths);
2370      }
2371    
2372      public void removeSelectionRow(int row)
2373      {
2374        TreePath path = getPathForRow(row);
2375    
2376        if (path != null)
2377          removeSelectionPath(path);
2378      }
2379    
2380      public void removeSelectionRows(int[] rows)
2381      {
2382        if (rows == null || getUI() == null)
2383          return;
2384    
2385        TreePath[] paths = new TreePath[rows.length];
2386    
2387        for (int i = rows.length - 1; i >= 0; --i)
2388          paths[i] = getPathForRow(rows[i]);
2389    
2390        removeSelectionPaths(paths);
2391      }
2392    
2393      public void removeSelectionInterval(int index0, int index1)
2394      {
2395        TreePath[] paths = getPathBetweenRows(index0, index1);
2396    
2397        if (paths != null)
2398          removeSelectionPaths(paths);
2399      }
2400    
2401      public void clearSelection()
2402      {
2403        selectionModel.clearSelection();
2404        setLeadSelectionPath(null);
2405      }
2406    
2407      public TreePath getLeadSelectionPath()
2408      {
2409        if (selectionModel == null)
2410          return null;
2411        else
2412          return selectionModel.getLeadSelectionPath();
2413      }
2414    
2415      /**
2416       * @since 1.3
2417       */
2418      public void setLeadSelectionPath(TreePath path)
2419      {
2420        if (selectionModel != null)
2421          {
2422            TreePath oldValue = selectionModel.getLeadSelectionPath();
2423            if (path == oldValue || path != null && path.equals(oldValue))
2424              return;
2425           
2426            // Repaint the previous and current rows with the lead selection path.
2427            if (path != null)
2428              {
2429                repaint(getPathBounds(path));
2430                selectionModel.addSelectionPath(path);
2431              }
2432            
2433            if (oldValue != null)
2434              repaint(getPathBounds(oldValue));
2435            
2436            firePropertyChange(LEAD_SELECTION_PATH_PROPERTY, oldValue, path);
2437          }
2438      }
2439    
2440      /**
2441       * @since 1.3
2442       */
2443      public TreePath getAnchorSelectionPath()
2444      {
2445        return anchorSelectionPath;
2446      }
2447    
2448      /**
2449       * @since 1.3
2450       */
2451      public void setAnchorSelectionPath(TreePath path)
2452      {
2453        if (anchorSelectionPath == path)
2454          return;
2455    
2456        TreePath oldValue = anchorSelectionPath;
2457        anchorSelectionPath = path;
2458        firePropertyChange(ANCHOR_SELECTION_PATH_PROPERTY, oldValue, path);
2459      }
2460    
2461      public int getLeadSelectionRow()
2462      {
2463        return selectionModel.getLeadSelectionRow();
2464      }
2465    
2466      public int getMaxSelectionRow()
2467      {
2468        return selectionModel.getMaxSelectionRow();
2469      }
2470    
2471      public int getMinSelectionRow()
2472      {
2473        return selectionModel.getMinSelectionRow();
2474      }
2475    
2476      public int getSelectionCount()
2477      {
2478        return selectionModel.getSelectionCount();
2479      }
2480    
2481      public TreePath getSelectionPath()
2482      {
2483        return selectionModel.getSelectionPath();
2484      }
2485    
2486      public TreePath[] getSelectionPaths()
2487      {
2488        return selectionModel.getSelectionPaths();
2489      }
2490    
2491      public int[] getSelectionRows()
2492      {
2493        return selectionModel.getSelectionRows();
2494      }
2495    
2496      public boolean isPathSelected(TreePath path)
2497      {
2498        return selectionModel.isPathSelected(path);
2499      }
2500    
2501      /**
2502       * Returns <code>true</code> when the specified row is selected,
2503       * <code>false</code> otherwise. This call is delegated to the
2504       * {@link TreeSelectionModel#isRowSelected(int)} method.
2505       *
2506       * @param row the row to check
2507       *
2508       * @return <code>true</code> when the specified row is selected,
2509       *         <code>false</code> otherwise
2510       */
2511      public boolean isRowSelected(int row)
2512      {
2513        return selectionModel.isRowSelected(row);
2514      }
2515    
2516      public boolean isSelectionEmpty()
2517      {
2518        return selectionModel.isSelectionEmpty();
2519      }
2520    
2521      /**
2522       * Return the value of the <code>dragEnabled</code> property.
2523       * 
2524       * @return the value
2525       * 
2526       * @since 1.4
2527       */
2528      public boolean getDragEnabled()
2529      {
2530        return dragEnabled;
2531      }
2532    
2533      /**
2534       * Set the <code>dragEnabled</code> property.
2535       * 
2536       * @param enabled new value
2537       * 
2538       * @since 1.4
2539       */
2540      public void setDragEnabled(boolean enabled)
2541      {
2542        dragEnabled = enabled;
2543      }
2544    
2545      public int getRowCount()
2546      {
2547        TreeUI ui = getUI();
2548    
2549        if (ui != null)
2550          return ui.getRowCount(this);
2551    
2552        return 0;
2553      }
2554    
2555      public void collapsePath(TreePath path)
2556      {
2557        try
2558          {
2559            fireTreeWillCollapse(path);
2560          }
2561        catch (ExpandVetoException ev)
2562          {
2563            // We do nothing if attempt has been vetoed.
2564          }
2565        setExpandedState(path, false);
2566        fireTreeCollapsed(path);
2567      }
2568    
2569      public void collapseRow(int row)
2570      {
2571        if (row < 0 || row >= getRowCount())
2572          return;
2573    
2574        TreePath path = getPathForRow(row);
2575    
2576        if (path != null)
2577          collapsePath(path);
2578      }
2579    
2580      public void expandPath(TreePath path)
2581      {
2582        // Don't expand if path is null
2583        // or is already expanded.
2584        if (path == null || isExpanded(path))
2585          return;
2586    
2587        try
2588          {
2589            fireTreeWillExpand(path);
2590          }
2591        catch (ExpandVetoException ev)
2592          {
2593            // We do nothing if attempt has been vetoed.
2594          }
2595    
2596        setExpandedState(path, true);
2597        fireTreeExpanded(path);
2598      }
2599    
2600      public void expandRow(int row)
2601      {
2602        if (row < 0 || row >= getRowCount())
2603          return;
2604    
2605        TreePath path = getPathForRow(row);
2606    
2607        if (path != null)
2608          expandPath(path);
2609      }
2610    
2611      public boolean isCollapsed(TreePath path)
2612      {
2613        return !isExpanded(path);
2614      }
2615    
2616      public boolean isCollapsed(int row)
2617      {
2618        if (row < 0 || row >= getRowCount())
2619          return false;
2620    
2621        TreePath path = getPathForRow(row);
2622    
2623        if (path != null)
2624          return isCollapsed(path);
2625    
2626        return false;
2627      }
2628    
2629      public boolean isExpanded(TreePath path)
2630      {
2631        if (path == null)
2632          return false;
2633    
2634        Object state = nodeStates.get(path);
2635    
2636        if ((state == null) || (state != EXPANDED))
2637          return false;
2638    
2639        TreePath parent = path.getParentPath();
2640    
2641        if (parent != null)
2642          return isExpanded(parent);
2643    
2644        return true;
2645      }
2646    
2647      public boolean isExpanded(int row)
2648      {
2649        if (row < 0 || row >= getRowCount())
2650          return false;
2651    
2652        TreePath path = getPathForRow(row);
2653    
2654        if (path != null)
2655          return isExpanded(path);
2656    
2657        return false;
2658      }
2659    
2660      /**
2661       * @since 1.3
2662       */
2663      public boolean getExpandsSelectedPaths()
2664      {
2665        return expandsSelectedPaths;
2666      }
2667    
2668      /**
2669       * @since 1.3
2670       */
2671      public void setExpandsSelectedPaths(boolean flag)
2672      {
2673        if (expandsSelectedPaths == flag)
2674          return;
2675    
2676        boolean oldValue = expandsSelectedPaths;
2677        expandsSelectedPaths = flag;
2678        firePropertyChange(EXPANDS_SELECTED_PATHS_PROPERTY, oldValue, flag);
2679      }
2680    
2681      public Rectangle getPathBounds(TreePath path)
2682      {
2683        TreeUI ui = getUI();
2684    
2685        if (ui == null)
2686          return null;
2687    
2688        return ui.getPathBounds(this, path);
2689      }
2690    
2691      public Rectangle getRowBounds(int row)
2692      {
2693        TreePath path = getPathForRow(row);
2694    
2695        if (path != null)
2696          return getPathBounds(path);
2697    
2698        return null;
2699      }
2700    
2701      public boolean isEditing()
2702      {
2703        TreeUI ui = getUI();
2704    
2705        if (ui != null)
2706          return ui.isEditing(this);
2707    
2708        return false;
2709      }
2710    
2711      public boolean stopEditing()
2712      {
2713        TreeUI ui = getUI();
2714    
2715        if (isEditing())
2716          if (ui != null)
2717            return ui.stopEditing(this);
2718    
2719        return false;
2720      }
2721    
2722      public void cancelEditing()
2723      {
2724        TreeUI ui = getUI();
2725          
2726        if (isEditing())
2727          if (ui != null)
2728            ui.cancelEditing(this);
2729      }
2730    
2731      public void startEditingAtPath(TreePath path)
2732      {
2733        TreeUI ui = getUI();
2734    
2735        if (ui != null)
2736          ui.startEditingAtPath(this, path);
2737      }
2738    
2739      public TreePath getEditingPath()
2740      {
2741        TreeUI ui = getUI();
2742    
2743        if (ui != null)
2744          return ui.getEditingPath(this);
2745    
2746        return null;
2747      }
2748    
2749      public TreePath getPathForLocation(int x, int y)
2750      {
2751        TreePath path = getClosestPathForLocation(x, y);
2752    
2753        if (path != null)
2754          {
2755            Rectangle rect = getPathBounds(path);
2756    
2757            if ((rect != null) && rect.contains(x, y))
2758              return path;
2759          }
2760    
2761        return null;
2762      }
2763    
2764      public int getRowForLocation(int x, int y)
2765      {
2766        TreePath path = getPathForLocation(x, y);
2767    
2768        if (path != null)
2769          return getRowForPath(path);
2770    
2771        return -1;
2772      }
2773    
2774      public TreePath getClosestPathForLocation(int x, int y)
2775      {
2776        TreeUI ui = getUI();
2777    
2778        if (ui != null)
2779          return ui.getClosestPathForLocation(this, x, y);
2780    
2781        return null;
2782      }
2783    
2784      public int getClosestRowForLocation(int x, int y)
2785      {
2786        TreePath path = getClosestPathForLocation(x, y);
2787    
2788        if (path != null)
2789          return getRowForPath(path);
2790    
2791        return -1;
2792      }
2793    
2794      public Object getLastSelectedPathComponent()
2795      {
2796        TreePath path = getSelectionPath();
2797    
2798        if (path != null)
2799          return path.getLastPathComponent();
2800    
2801        return null;
2802      }
2803    
2804      private void doExpandParents(TreePath path, boolean state)
2805      {
2806        TreePath parent = path.getParentPath();             
2807    
2808        if (!isExpanded(parent) && parent != null)
2809          doExpandParents(parent, false);
2810        
2811        nodeStates.put(path, state ? EXPANDED : COLLAPSED);
2812      }
2813    
2814      protected void setExpandedState(TreePath path, boolean state)
2815      {
2816        if (path == null)
2817          return;
2818    
2819        doExpandParents(path, state);
2820      }
2821    
2822      protected void clearToggledPaths()
2823      {
2824        nodeStates.clear();
2825      }
2826    
2827      protected Enumeration<TreePath> getDescendantToggledPaths(TreePath parent)
2828      {
2829        if (parent == null)
2830          return null;
2831    
2832        Enumeration nodes = nodeStates.keys();
2833        Vector result = new Vector();
2834    
2835        while (nodes.hasMoreElements())
2836          {
2837            TreePath path = (TreePath) nodes.nextElement();
2838    
2839            if (path.isDescendant(parent))
2840              result.addElement(path);
2841          }
2842    
2843        return result.elements();
2844      }
2845    
2846      public boolean hasBeenExpanded(TreePath path)
2847      {
2848        if (path == null)
2849          return false;
2850    
2851        return nodeStates.get(path) != null;
2852      }
2853    
2854      public boolean isVisible(TreePath path)
2855      {
2856        if (path == null)
2857          return false;
2858    
2859        TreePath parent = path.getParentPath();
2860    
2861        if (parent == null)
2862          return true; // Is root node.
2863    
2864        return isExpanded(parent);
2865      }
2866    
2867      public void makeVisible(TreePath path)
2868      {
2869        if (path == null)
2870          return;
2871        
2872        expandPath(path.getParentPath());
2873      }
2874    
2875      public boolean isPathEditable(TreePath path)
2876      {
2877        return isEditable();
2878      }
2879    
2880      /**
2881       * Creates and returns an instance of {@link TreeModelHandler}.
2882       * 
2883       * @return an instance of {@link TreeModelHandler}
2884       */
2885      protected TreeModelListener createTreeModelListener()
2886      {
2887        return new TreeModelHandler();
2888      }
2889    
2890      /**
2891       * Returns a sample TreeModel that can be used in a JTree. This can be used
2892       * in Bean- or GUI-Builders to show something interesting.
2893       * 
2894       * @return a sample TreeModel that can be used in a JTree
2895       */
2896      protected static TreeModel getDefaultTreeModel()
2897      {
2898        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root node");
2899        DefaultMutableTreeNode child1 = new DefaultMutableTreeNode("Child node 1");
2900        DefaultMutableTreeNode child11 =
2901          new DefaultMutableTreeNode("Child node 1.1");
2902        DefaultMutableTreeNode child12 =
2903          new DefaultMutableTreeNode("Child node 1.2");
2904        DefaultMutableTreeNode child13 =
2905          new DefaultMutableTreeNode("Child node 1.3");
2906        DefaultMutableTreeNode child2 = new DefaultMutableTreeNode("Child node 2");
2907        DefaultMutableTreeNode child21 =
2908          new DefaultMutableTreeNode("Child node 2.1");
2909        DefaultMutableTreeNode child22 =
2910          new DefaultMutableTreeNode("Child node 2.2");
2911        DefaultMutableTreeNode child23 =
2912          new DefaultMutableTreeNode("Child node 2.3");
2913        DefaultMutableTreeNode child24 =
2914          new DefaultMutableTreeNode("Child node 2.4");
2915    
2916        DefaultMutableTreeNode child3 = new DefaultMutableTreeNode("Child node 3");
2917        root.add(child1);
2918        root.add(child2);
2919        root.add(child3);
2920        child1.add(child11);
2921        child1.add(child12);
2922        child1.add(child13);
2923        child2.add(child21);
2924        child2.add(child22);
2925        child2.add(child23);
2926        child2.add(child24);
2927        return new DefaultTreeModel(root);
2928      }
2929    
2930      /**
2931       * Converts the specified value to a String. This is used by the renderers
2932       * of this JTree and its nodes.
2933       * 
2934       * This implementation simply returns <code>value.toString()</code> and
2935       * ignores all other parameters. Subclass this method to control the
2936       * conversion.
2937       * 
2938       * @param value the value that is converted to a String
2939       * @param selected indicates if that value is selected or not
2940       * @param expanded indicates if that value is expanded or not
2941       * @param leaf indicates if that value is a leaf node or not
2942       * @param row the row of the node
2943       * @param hasFocus indicates if that node has focus or not
2944       */
2945      public String convertValueToText(Object value, boolean selected,
2946                                       boolean expanded, boolean leaf, int row, boolean hasFocus)
2947      {
2948        return value.toString();
2949      }
2950    
2951      /**
2952       * A String representation of this JTree. This is intended to be used for
2953       * debugging. The returned string may be empty but may not be
2954       * <code>null</code>.
2955       * 
2956       * @return a String representation of this JTree
2957       */
2958      protected String paramString()
2959      {
2960        // TODO: this is completely legal, but it would possibly be nice
2961        // to return some more content, like the tree structure, some properties
2962        // etc ...
2963        return "";
2964      }
2965    
2966      /**
2967       * Returns all TreePath objects which are a descendants of the given path
2968       * and are exapanded at the moment of the execution of this method. If the
2969       * state of any node is beeing toggled while this method is executing this
2970       * change may be left unaccounted.
2971       * 
2972       * @param path The parent of this request
2973       *
2974       * @return An Enumeration containing TreePath objects
2975       */
2976      public Enumeration<TreePath> getExpandedDescendants(TreePath path)
2977      {
2978        Enumeration paths = nodeStates.keys();
2979        Vector relevantPaths = new Vector();
2980        while (paths.hasMoreElements())
2981          {
2982            TreePath nextPath = (TreePath) paths.nextElement();
2983            if (nodeStates.get(nextPath) == EXPANDED
2984                && path.isDescendant(nextPath))
2985              {
2986                relevantPaths.add(nextPath);
2987              }
2988          }
2989        return relevantPaths.elements();
2990      }
2991    
2992      /**
2993       * Returns the next table element (beginning from the row
2994       * <code>startingRow</code> that starts with <code>prefix</code>.
2995       * Searching is done in the direction specified by <code>bias</code>.
2996       * 
2997       * @param prefix the prefix to search for in the cell values
2998       * @param startingRow the index of the row where to start searching from
2999       * @param bias the search direction, either {@link Position.Bias#Forward} or
3000       *        {@link Position.Bias#Backward}
3001       * 
3002       * @return the path to the found element or -1 if no such element has been
3003       *         found
3004       * 
3005       * @throws IllegalArgumentException if prefix is <code>null</code> or
3006       *         startingRow is not valid
3007       * 
3008       * @since 1.4
3009       */
3010      public TreePath getNextMatch(String prefix, int startingRow,
3011                                   Position.Bias bias)
3012      {
3013        if (prefix == null)
3014          throw new IllegalArgumentException("The argument 'prefix' must not be"
3015                                             + " null.");
3016        if (startingRow < 0)
3017          throw new IllegalArgumentException("The argument 'startingRow' must not"
3018                                             + " be less than zero.");
3019    
3020        int size = getRowCount();
3021        if (startingRow > size)
3022          throw new IllegalArgumentException("The argument 'startingRow' must not"
3023                                             + " be greater than the number of"
3024                                             + " elements in the TreeModel.");
3025    
3026        TreePath foundPath = null;
3027        if (bias == Position.Bias.Forward)
3028          {
3029            for (int i = startingRow; i < size; i++)
3030              {
3031                TreePath path = getPathForRow(i);
3032                Object o = path.getLastPathComponent();
3033                // FIXME: in the following call to convertValueToText the
3034                // last argument (hasFocus) should be done right.
3035                String item = convertValueToText(o, isRowSelected(i),
3036                                                 isExpanded(i), treeModel.isLeaf(o),
3037                                                 i, false);
3038                if (item.startsWith(prefix))
3039                  {
3040                    foundPath = path;
3041                    break;
3042                  }
3043              }
3044          }
3045        else
3046          {
3047            for (int i = startingRow; i >= 0; i--)
3048              {
3049                TreePath path = getPathForRow(i);
3050                Object o = path.getLastPathComponent();
3051                // FIXME: in the following call to convertValueToText the
3052                // last argument (hasFocus) should be done right.
3053                String item = convertValueToText(o, isRowSelected(i),
3054                                                 isExpanded(i), treeModel.isLeaf(o), i, false);
3055                if (item.startsWith(prefix))
3056                  {
3057                    foundPath = path;
3058                    break;
3059                  }
3060              }
3061          }
3062        return foundPath;
3063      }
3064    
3065      /**
3066       * Removes any paths in the current set of selected paths that are
3067       * descendants of <code>path</code>. If <code>includePath</code> is set
3068       * to <code>true</code> and <code>path</code> itself is selected, then
3069       * it will be removed too.
3070       * 
3071       * @param path the path from which selected descendants are to be removed
3072       * @param includeSelected if <code>true</code> then <code>path</code> itself
3073       *        will also be remove if it's selected
3074       * 
3075       * @return <code>true</code> if something has been removed,
3076       *         <code>false</code> otherwise
3077       * 
3078       * @since 1.3
3079       */
3080      protected boolean removeDescendantSelectedPaths(TreePath path,
3081                                                      boolean includeSelected)
3082      {
3083        boolean removedSomething = false;
3084        TreePath[] selected = getSelectionPaths();
3085        for (int index = 0; index < selected.length; index++)
3086          {
3087            if ((selected[index] == path && includeSelected)
3088                || (selected[index].isDescendant(path)))
3089              {
3090                removeSelectionPath(selected[index]);
3091                removedSomething = true;
3092              }
3093          }
3094        return removedSomething;
3095      }
3096      
3097      /**
3098       * Removes any descendants of the TreePaths in toRemove that have been 
3099       * expanded.
3100       * 
3101       * @param toRemove - Enumeration of TreePaths that need to be removed from
3102       * cache of toggled tree paths.
3103       */
3104      protected void removeDescendantToggledPaths(Enumeration<TreePath> toRemove)
3105      {
3106        while (toRemove.hasMoreElements())
3107          {
3108            TreePath current = toRemove.nextElement();
3109            Enumeration descendants = getDescendantToggledPaths(current);
3110            
3111            while (descendants.hasMoreElements())
3112              {
3113                TreePath currentDes = (TreePath) descendants.nextElement();
3114                if (isExpanded(currentDes))
3115                    nodeStates.remove(currentDes);
3116              }
3117          }
3118      }
3119    
3120      /**
3121       * <p>
3122       * Sent when the tree has changed enough that we need to resize the bounds,
3123       * but not enough that we need to remove the expanded node set (e.g nodes were
3124       * expanded or collapsed, or nodes were inserted into the tree). You should
3125       * never have to invoke this, the UI will invoke this as it needs to.
3126       * </p>
3127       * <p>
3128       * If the tree uses {@link DefaultTreeModel}, you must call
3129       * {@link DefaultTreeModel#reload(TreeNode)} or
3130       * {@link DefaultTreeModel#reload()} after adding or removing nodes. Following
3131       * the official Java 1.5 API standard, just calling treeDidChange, repaint()
3132       * or revalidate() does <i>not</i> update the tree appearance properly.
3133       * 
3134       * @see DefaultTreeModel#reload()
3135       */
3136      public void treeDidChange()
3137      {
3138        repaint();
3139      }
3140    
3141      /**
3142       * Helper method for
3143       * {@link LookAndFeel#installProperty(JComponent, String, Object)}.
3144       * 
3145       * @param propertyName the name of the property
3146       * @param value the value of the property
3147       *
3148       * @throws IllegalArgumentException if the specified property cannot be set
3149       *         by this method
3150       * @throws ClassCastException if the property value does not match the
3151       *         property type
3152       * @throws NullPointerException if <code>c</code> or
3153       *         <code>propertyValue</code> is <code>null</code>
3154       */
3155      void setUIProperty(String propertyName, Object value)
3156      {
3157        if (propertyName.equals("rowHeight"))
3158          {
3159            if (! clientRowHeightSet)
3160              {
3161                setRowHeight(((Integer) value).intValue());
3162                clientRowHeightSet = false;
3163              }
3164          }
3165        else if (propertyName.equals("scrollsOnExpand"))
3166          {
3167            if (! clientScrollsOnExpandSet)
3168              {
3169                setScrollsOnExpand(((Boolean) value).booleanValue());
3170                clientScrollsOnExpandSet = false;
3171              }
3172          }
3173        else if (propertyName.equals("showsRootHandles"))
3174          {
3175            if (! clientShowsRootHandlesSet)
3176              {
3177                setShowsRootHandles(((Boolean) value).booleanValue());
3178                clientShowsRootHandlesSet = false;
3179              }
3180          }
3181        else
3182          {
3183            super.setUIProperty(propertyName, value);
3184          }
3185      }
3186    }