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 }