29 import java.io.*;
30 import java.util.ArrayList;
31 import java.util.BitSet;
32 import java.util.Enumeration;
33 import java.util.EventListener;
34 import java.util.Hashtable;
35 import java.util.List;
36 import java.util.Vector;
37 import javax.swing.event.*;
38 import javax.swing.DefaultListSelectionModel;
39
40 /**
41 * Default implementation of TreeSelectionModel. Listeners are notified
42 * whenever
43 * the paths in the selection change, not the rows. In order
44 * to be able to track row changes you may wish to become a listener
45 * for expansion events on the tree and test for changes from there.
46 * <p>resetRowSelection is called from any of the methods that update
47 * the selected paths. If you subclass any of these methods to
48 * filter what is allowed to be selected, be sure and message
49 * <code>resetRowSelection</code> if you do not message super.
50 *
51 * <strong>Warning:</strong>
52 * Serialized objects of this class will not be compatible with
53 * future Swing releases. The current serialization support is
54 * appropriate for short term storage or RMI between applications running
55 * the same version of Swing. As of 1.4, support for long term storage
56 * of all JavaBeans™
57 * has been added to the <code>java.beans</code> package.
58 * Please see {@link java.beans.XMLEncoder}.
59 *
60 * @see javax.swing.JTree
61 *
62 * @author Scott Violet
63 */
64 @SuppressWarnings("serial")
65 public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel
66 {
67 /** Property name for selectionMode. */
68 public static final String SELECTION_MODE_PROPERTY = "selectionMode";
69
70 /** Used to messaged registered listeners. */
71 protected SwingPropertyChangeSupport changeSupport;
72
73 /** Paths that are currently selected. Will be null if nothing is
74 * currently selected. */
75 protected TreePath[] selection;
76
77 /** Event listener list. */
80 /** Provides a row for a given path. */
81 protected transient RowMapper rowMapper;
82
83 /** Handles maintaining the list selection model. The RowMapper is used
84 * to map from a TreePath to a row, and the value is then placed here. */
85 protected DefaultListSelectionModel listSelectionModel;
86
87 /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
88 * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
89 */
90 protected int selectionMode;
91
92 /** Last path that was added. */
93 protected TreePath leadPath;
94 /** Index of the lead path in selection. */
95 protected int leadIndex;
96 /** Lead row. */
97 protected int leadRow;
98
99 /** Used to make sure the paths are unique, will contain all the paths
100 * in <code>selection</code>.
101 */
102 private Hashtable<TreePath, Boolean> uniquePaths;
103 private Hashtable<TreePath, Boolean> lastPaths;
104 private TreePath[] tempPaths;
105
106
107 /**
108 * Creates a new instance of DefaultTreeSelectionModel that is
109 * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
110 */
111 public DefaultTreeSelectionModel() {
112 listSelectionModel = new DefaultListSelectionModel();
113 selectionMode = DISCONTIGUOUS_TREE_SELECTION;
114 leadIndex = leadRow = -1;
115 uniquePaths = new Hashtable<TreePath, Boolean>();
116 lastPaths = new Hashtable<TreePath, Boolean>();
117 tempPaths = new TreePath[1];
118 }
119
120 /**
121 * Sets the RowMapper instance. This instance is used to determine
122 * the row for a particular TreePath.
123 */
124 public void setRowMapper(RowMapper newMapper) {
125 rowMapper = newMapper;
126 resetRowSelection();
127 }
128
129 /**
130 * Returns the RowMapper instance that is able to map a TreePath to a
131 * row.
132 */
133 public RowMapper getRowMapper() {
134 return rowMapper;
135 }
136
137 /**
138 * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
139 * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
140 * is not one of the defined value,
141 * <code>DISCONTIGUOUS_TREE_SELECTION</code> is assumed.
142 * <p>This may change the selection if the current selection is not valid
143 * for the new mode. For example, if three TreePaths are
144 * selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>,
145 * only one TreePath will remain selected. It is up to the particular
146 * implementation to decide what TreePath remains selected.
147 * <p>
148 * Setting the mode to something other than the defined types will
149 * result in the mode becoming <code>DISCONTIGUOUS_TREE_SELECTION</code>.
150 */
151 public void setSelectionMode(int mode) {
152 int oldMode = selectionMode;
153
154 selectionMode = validateSelectionMode(mode);
155 if(oldMode != selectionMode && changeSupport != null)
156 changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
157 Integer.valueOf(oldMode),
158 Integer.valueOf(selectionMode));
159 }
160
161 private static int validateSelectionMode(int mode) {
162 return (mode != TreeSelectionModel.SINGLE_TREE_SELECTION
163 && mode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION
164 && mode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
165 ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : mode;
166 }
167
168 /**
169 * Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>,
170 * <code>DISCONTIGUOUS_TREE_SELECTION</code> or
171 * <code>CONTIGUOUS_TREE_SELECTION</code>.
172 */
173 public int getSelectionMode() {
174 return selectionMode;
175 }
176
177 /**
178 * Sets the selection to path. If this represents a change, then
179 * the TreeSelectionListeners are notified. If <code>path</code> is
180 * null, this has the same effect as invoking <code>clearSelection</code>.
181 *
182 * @param path new path to select
183 */
184 public void setSelectionPath(TreePath path) {
185 if(path == null)
186 setSelectionPaths(null);
187 else {
188 TreePath[] newPaths = new TreePath[1];
189
190 newPaths[0] = path;
191 setSelectionPaths(newPaths);
192 }
193 }
194
195 /**
196 * Sets the selection. Whether the supplied paths are taken as the
197 * new selection depends upon the selection mode. If the supplied
198 * array is {@code null}, or empty, the selection is cleared. If
199 * the selection mode is {@code SINGLE_TREE_SELECTION}, only the
200 * first path in {@code pPaths} is used. If the selection
284
285 uniquePaths = lastPaths;
286 lastPaths = tempHT;
287 lastPaths.clear();
288
289 // No reason to do this now, but will still call it.
290 insureUniqueness();
291
292 updateLeadIndex();
293
294 resetRowSelection();
295 /* Notify of the change. */
296 if(cPaths.size() > 0)
297 notifyPathChange(cPaths, beginLeadPath);
298 }
299 }
300
301 /**
302 * Adds path to the current selection. If path is not currently
303 * in the selection the TreeSelectionListeners are notified. This has
304 * no effect if <code>path</code> is null.
305 *
306 * @param path the new path to add to the current selection
307 */
308 public void addSelectionPath(TreePath path) {
309 if(path != null) {
310 TreePath[] toAdd = new TreePath[1];
311
312 toAdd[0] = path;
313 addSelectionPaths(toAdd);
314 }
315 }
316
317 /**
318 * Adds paths to the current selection. If any of the paths in
319 * paths are not currently in the selection the TreeSelectionListeners
320 * are notified. This has
321 * no effect if <code>paths</code> is null.
322 * <p>The lead path is set to the last element in <code>paths</code>.
323 * <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>,
324 * and adding the new paths would make the selection discontiguous.
325 * Then two things can result: if the TreePaths in <code>paths</code>
326 * are contiguous, then the selection becomes these TreePaths,
327 * otherwise the TreePaths aren't contiguous and the selection becomes
328 * the first TreePath in <code>paths</code>.
329 *
330 * @param paths the new path to add to the current selection
331 */
332 public void addSelectionPaths(TreePath[] paths) {
333 int newPathLength = ((paths == null) ? 0 : paths.length);
334
335 if(newPathLength > 0) {
336 if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
337 setSelectionPaths(paths);
338 }
339 else if(selectionMode == TreeSelectionModel.
340 CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
341 if(arePathsContiguous(paths)) {
342 setSelectionPaths(paths);
343 }
344 else {
345 TreePath[] newPaths = new TreePath[1];
346
347 newPaths[0] = paths[0];
348 setSelectionPaths(newPaths);
407 selection = newSelection;
408
409 insureUniqueness();
410
411 updateLeadIndex();
412
413 resetRowSelection();
414
415 notifyPathChange(cPaths, beginLeadPath);
416 }
417 else
418 leadPath = beginLeadPath;
419 lastPaths.clear();
420 }
421 }
422 }
423
424 /**
425 * Removes path from the selection. If path is in the selection
426 * The TreeSelectionListeners are notified. This has no effect if
427 * <code>path</code> is null.
428 *
429 * @param path the path to remove from the selection
430 */
431 public void removeSelectionPath(TreePath path) {
432 if(path != null) {
433 TreePath[] rPath = new TreePath[1];
434
435 rPath[0] = path;
436 removeSelectionPaths(rPath);
437 }
438 }
439
440 /**
441 * Removes paths from the selection. If any of the paths in paths
442 * are in the selection the TreeSelectionListeners are notified.
443 * This has no effect if <code>paths</code> is null.
444 *
445 * @param paths the paths to remove from the selection
446 */
447 public void removeSelectionPaths(TreePath[] paths) {
448 if (paths != null && selection != null && paths.length > 0) {
449 if(!canPathsBeRemoved(paths)) {
450 /* Could probably do something more interesting here! */
451 clearSelection();
452 }
453 else {
454 Vector<PathPlaceHolder> pathsToRemove = null;
455
456 /* Find the paths that can be removed. */
457 for (int removeCounter = paths.length - 1; removeCounter >= 0;
458 removeCounter--) {
459 if(paths[removeCounter] != null) {
460 if (uniquePaths.get(paths[removeCounter]) != null) {
461 if(pathsToRemove == null)
462 pathsToRemove = new Vector<PathPlaceHolder>(paths.length);
463 uniquePaths.remove(paths[removeCounter]);
526 */
527 public TreePath[] getSelectionPaths() {
528 if(selection != null) {
529 int pathSize = selection.length;
530 TreePath[] result = new TreePath[pathSize];
531
532 System.arraycopy(selection, 0, result, 0, pathSize);
533 return result;
534 }
535 return new TreePath[0];
536 }
537
538 /**
539 * Returns the number of paths that are selected.
540 */
541 public int getSelectionCount() {
542 return (selection == null) ? 0 : selection.length;
543 }
544
545 /**
546 * Returns true if the path, <code>path</code>,
547 * is in the current selection.
548 */
549 public boolean isPathSelected(TreePath path) {
550 return (path != null) ? (uniquePaths.get(path) != null) : false;
551 }
552
553 /**
554 * Returns true if the selection is currently empty.
555 */
556 public boolean isSelectionEmpty() {
557 return (selection == null || selection.length == 0);
558 }
559
560 /**
561 * Empties the current selection. If this represents a change in the
562 * current selection, the selection listeners are notified.
563 */
564 public void clearSelection() {
565 if (selection != null && selection.length > 0) {
566 int selSize = selection.length;
588 * @param x the new listener to be added
589 */
590 public void addTreeSelectionListener(TreeSelectionListener x) {
591 listenerList.add(TreeSelectionListener.class, x);
592 }
593
594 /**
595 * Removes x from the list of listeners that are notified each time
596 * the set of selected TreePaths changes.
597 *
598 * @param x the listener to remove
599 */
600 public void removeTreeSelectionListener(TreeSelectionListener x) {
601 listenerList.remove(TreeSelectionListener.class, x);
602 }
603
604 /**
605 * Returns an array of all the tree selection listeners
606 * registered on this model.
607 *
608 * @return all of this model's <code>TreeSelectionListener</code>s
609 * or an empty
610 * array if no tree selection listeners are currently registered
611 *
612 * @see #addTreeSelectionListener
613 * @see #removeTreeSelectionListener
614 *
615 * @since 1.4
616 */
617 public TreeSelectionListener[] getTreeSelectionListeners() {
618 return listenerList.getListeners(TreeSelectionListener.class);
619 }
620
621 /**
622 * Notifies all listeners that are registered for
623 * tree selection events on this object.
624 *
625 * @param e the event that characterizes the change
626 *
627 * @see #addTreeSelectionListener
628 * @see EventListenerList
635 // those that are interested in this event
636 for (int i = listeners.length-2; i>=0; i-=2) {
637 if (listeners[i]==TreeSelectionListener.class) {
638 // Lazily create the event:
639 // if (e == null)
640 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
641 ((TreeSelectionListener)listeners[i+1]).valueChanged(e);
642 }
643 }
644 }
645
646 /**
647 * Returns an array of all the objects currently registered
648 * as <code><em>Foo</em>Listener</code>s
649 * upon this model.
650 * <code><em>Foo</em>Listener</code>s are registered using the
651 * <code>add<em>Foo</em>Listener</code> method.
652 *
653 * <p>
654 *
655 * You can specify the <code>listenerType</code> argument
656 * with a class literal,
657 * such as
658 * <code><em>Foo</em>Listener.class</code>.
659 * For example, you can query a
660 * <code>DefaultTreeSelectionModel</code> <code>m</code>
661 * for its tree selection listeners with the following code:
662 *
663 * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
664 *
665 * If no such listeners exist, this method returns an empty array.
666 *
667 * @param <T> the listener type
668 * @param listenerType the type of listeners requested
669 * @return an array of all objects registered as
670 * <code><em>Foo</em>Listener</code>s on this component,
671 * or an empty array if no such
672 * listeners have been added
673 * @exception ClassCastException if <code>listenerType</code>
674 * doesn't specify a class or interface that implements
675 * <code>java.util.EventListener</code>
676 *
677 * @see #getTreeSelectionListeners
678 * @see #getPropertyChangeListeners
679 *
680 * @since 1.3
681 */
682 public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
683 return listenerList.getListeners(listenerType);
684 }
685
686 /**
687 * Returns the selection in terms of rows. There is not
688 * necessarily a one-to-one mapping between the {@code TreePath}s
689 * returned from {@code getSelectionPaths} and this method. In
690 * particular, if a {@code TreePath} is not viewable (the {@code
691 * RowMapper} returns {@code -1} for the row corresponding to the
692 * {@code TreePath}), then the corresponding row is not included
693 * in the returned array. For example, if the selection consists
694 * of two paths, {@code A} and {@code B}, with {@code A} at row
695 * {@code 10}, and {@code B} not currently viewable, then this method
735
736 /**
737 * Returns the smallest value obtained from the RowMapper for the
738 * current set of selected TreePaths. If nothing is selected,
739 * or there is no RowMapper, this will return -1.
740 */
741 public int getMinSelectionRow() {
742 return listSelectionModel.getMinSelectionIndex();
743 }
744
745 /**
746 * Returns the largest value obtained from the RowMapper for the
747 * current set of selected TreePaths. If nothing is selected,
748 * or there is no RowMapper, this will return -1.
749 */
750 public int getMaxSelectionRow() {
751 return listSelectionModel.getMaxSelectionIndex();
752 }
753
754 /**
755 * Returns true if the row identified by <code>row</code> is selected.
756 */
757 public boolean isRowSelected(int row) {
758 return listSelectionModel.isSelectedIndex(row);
759 }
760
761 /**
762 * Updates this object's mapping from TreePath to rows. This should
763 * be invoked when the mapping from TreePaths to integers has changed
764 * (for example, a node has been expanded).
765 * <p>You do not normally have to call this, JTree and its associated
766 * Listeners will invoke this for you. If you are implementing your own
767 * View class, then you will have to invoke this.
768 * <p>This will invoke <code>insureRowContinuity</code> to make sure
769 * the currently selected TreePaths are still valid based on the
770 * selection mode.
771 */
772 public void resetRowSelection() {
773 listSelectionModel.clearSelection();
774 if(selection != null && rowMapper != null) {
775 int aRow;
776 int validCount = 0;
777 int[] rows = rowMapper.getRowsForPaths(selection);
778
779 for(int counter = 0, maxCounter = selection.length;
780 counter < maxCounter; counter++) {
781 aRow = rows[counter];
782 if(aRow != -1) {
783 listSelectionModel.addSelectionInterval(aRow, aRow);
784 }
785 }
786 if(leadIndex != -1 && rows != null) {
787 leadRow = rows[leadIndex];
788 }
836 }
837
838 /**
839 * Removes a PropertyChangeListener from the listener list.
840 * This removes a PropertyChangeListener that was registered
841 * for all properties.
842 *
843 * @param listener the PropertyChangeListener to be removed
844 */
845
846 public synchronized void removePropertyChangeListener(
847 PropertyChangeListener listener) {
848 if (changeSupport == null) {
849 return;
850 }
851 changeSupport.removePropertyChangeListener(listener);
852 }
853
854 /**
855 * Returns an array of all the property change listeners
856 * registered on this <code>DefaultTreeSelectionModel</code>.
857 *
858 * @return all of this model's <code>PropertyChangeListener</code>s
859 * or an empty
860 * array if no property change listeners are currently registered
861 *
862 * @see #addPropertyChangeListener
863 * @see #removePropertyChangeListener
864 *
865 * @since 1.4
866 */
867 public PropertyChangeListener[] getPropertyChangeListeners() {
868 if (changeSupport == null) {
869 return new PropertyChangeListener[0];
870 }
871 return changeSupport.getPropertyChangeListeners();
872 }
873
874 /**
875 * Makes sure the currently selected <code>TreePath</code>s are valid
876 * for the current selection mode.
877 * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>
878 * and a <code>RowMapper</code> exists, this will make sure all
879 * the rows are contiguous, that is, when sorted all the rows are
880 * in order with no gaps.
881 * If the selection isn't contiguous, the selection is
882 * reset to contain the first set, when sorted, of contiguous rows.
883 * <p>
884 * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and
885 * more than one TreePath is selected, the selection is reset to
886 * contain the first path currently selected.
887 */
888 protected void insureRowContinuity() {
889 if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
890 selection != null && rowMapper != null) {
891 DefaultListSelectionModel lModel = listSelectionModel;
892 int min = lModel.getMinSelectionIndex();
893
894 if(min != -1) {
895 for(int counter = min,
896 maxCounter = lModel.getMaxSelectionIndex();
897 counter <= maxCounter; counter++) {
898 if(!lModel.isSelectedIndex(counter)) {
899 if(counter == min) {
900 clearSelection();
901 }
902 else {
903 TreePath[] newSel = new TreePath[counter - min];
904 int selectionIndex[] = rowMapper.getRowsForPaths(selection);
953 anIndex > (min + pathCount))
954 return false;
955 if(anIndex < min)
956 min = anIndex;
957 if(!bitSet.get(anIndex)) {
958 bitSet.set(anIndex);
959 validCount++;
960 }
961 }
962 }
963 int maxCounter = validCount + min;
964
965 for(counter = min; counter < maxCounter; counter++)
966 if(!bitSet.get(counter))
967 return false;
968 }
969 return true;
970 }
971
972 /**
973 * Used to test if a particular set of <code>TreePath</code>s can
974 * be added. This will return true if <code>paths</code> is null (or
975 * empty), or this object has no RowMapper, or nothing is currently selected,
976 * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or
977 * adding the paths to the current selection still results in a
978 * contiguous set of <code>TreePath</code>s.
979 *
980 * @param paths array of {@code TreePaths} to check
981 * @return whether the particular set of {@code TreePaths} can be added
982 */
983 protected boolean canPathsBeAdded(TreePath[] paths) {
984 if(paths == null || paths.length == 0 || rowMapper == null ||
985 selection == null || selectionMode ==
986 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
987 return true;
988 else {
989 BitSet bitSet = new BitSet();
990 DefaultListSelectionModel lModel = listSelectionModel;
991 int anIndex;
992 int counter;
993 int min = lModel.getMinSelectionIndex();
994 int max = lModel.getMaxSelectionIndex();
995 TreePath[] tempPath = new TreePath[1];
996
997 if(min != -1) {
998 for(counter = min; counter <= max; counter++) {
|
29 import java.io.*;
30 import java.util.ArrayList;
31 import java.util.BitSet;
32 import java.util.Enumeration;
33 import java.util.EventListener;
34 import java.util.Hashtable;
35 import java.util.List;
36 import java.util.Vector;
37 import javax.swing.event.*;
38 import javax.swing.DefaultListSelectionModel;
39
40 /**
41 * Default implementation of TreeSelectionModel. Listeners are notified
42 * whenever
43 * the paths in the selection change, not the rows. In order
44 * to be able to track row changes you may wish to become a listener
45 * for expansion events on the tree and test for changes from there.
46 * <p>resetRowSelection is called from any of the methods that update
47 * the selected paths. If you subclass any of these methods to
48 * filter what is allowed to be selected, be sure and message
49 * {@code resetRowSelection} if you do not message super.
50 *
51 * <strong>Warning:</strong>
52 * Serialized objects of this class will not be compatible with
53 * future Swing releases. The current serialization support is
54 * appropriate for short term storage or RMI between applications running
55 * the same version of Swing. As of 1.4, support for long term storage
56 * of all JavaBeans™
57 * has been added to the {@code java.beans} package.
58 * Please see {@link java.beans.XMLEncoder}.
59 *
60 * @see javax.swing.JTree
61 *
62 * @author Scott Violet
63 */
64 @SuppressWarnings("serial")
65 public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel
66 {
67 /** Property name for selectionMode. */
68 public static final String SELECTION_MODE_PROPERTY = "selectionMode";
69
70 /** Used to messaged registered listeners. */
71 protected SwingPropertyChangeSupport changeSupport;
72
73 /** Paths that are currently selected. Will be null if nothing is
74 * currently selected. */
75 protected TreePath[] selection;
76
77 /** Event listener list. */
80 /** Provides a row for a given path. */
81 protected transient RowMapper rowMapper;
82
83 /** Handles maintaining the list selection model. The RowMapper is used
84 * to map from a TreePath to a row, and the value is then placed here. */
85 protected DefaultListSelectionModel listSelectionModel;
86
87 /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
88 * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
89 */
90 protected int selectionMode;
91
92 /** Last path that was added. */
93 protected TreePath leadPath;
94 /** Index of the lead path in selection. */
95 protected int leadIndex;
96 /** Lead row. */
97 protected int leadRow;
98
99 /** Used to make sure the paths are unique, will contain all the paths
100 * in {@code selection}.
101 */
102 private Hashtable<TreePath, Boolean> uniquePaths;
103 private Hashtable<TreePath, Boolean> lastPaths;
104 private TreePath[] tempPaths;
105
106
107 /**
108 * Creates a new instance of DefaultTreeSelectionModel that is
109 * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
110 */
111 public DefaultTreeSelectionModel() {
112 listSelectionModel = new DefaultListSelectionModel();
113 selectionMode = DISCONTIGUOUS_TREE_SELECTION;
114 leadIndex = leadRow = -1;
115 uniquePaths = new Hashtable<TreePath, Boolean>();
116 lastPaths = new Hashtable<TreePath, Boolean>();
117 tempPaths = new TreePath[1];
118 }
119
120 /**
121 * Sets the RowMapper instance. This instance is used to determine
122 * the row for a particular TreePath.
123 */
124 public void setRowMapper(RowMapper newMapper) {
125 rowMapper = newMapper;
126 resetRowSelection();
127 }
128
129 /**
130 * Returns the RowMapper instance that is able to map a TreePath to a
131 * row.
132 */
133 public RowMapper getRowMapper() {
134 return rowMapper;
135 }
136
137 /**
138 * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
139 * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
140 * is not one of the defined value,
141 * {@code DISCONTIGUOUS_TREE_SELECTION} is assumed.
142 * <p>This may change the selection if the current selection is not valid
143 * for the new mode. For example, if three TreePaths are
144 * selected when the mode is changed to {@code SINGLE_TREE_SELECTION},
145 * only one TreePath will remain selected. It is up to the particular
146 * implementation to decide what TreePath remains selected.
147 * <p>
148 * Setting the mode to something other than the defined types will
149 * result in the mode becoming {@code DISCONTIGUOUS_TREE_SELECTION}.
150 */
151 public void setSelectionMode(int mode) {
152 int oldMode = selectionMode;
153
154 selectionMode = validateSelectionMode(mode);
155 if(oldMode != selectionMode && changeSupport != null)
156 changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
157 Integer.valueOf(oldMode),
158 Integer.valueOf(selectionMode));
159 }
160
161 private static int validateSelectionMode(int mode) {
162 return (mode != TreeSelectionModel.SINGLE_TREE_SELECTION
163 && mode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION
164 && mode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
165 ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : mode;
166 }
167
168 /**
169 * Returns the selection mode, one of {@code SINGLE_TREE_SELECTION},
170 * {@code DISCONTIGUOUS_TREE_SELECTION} or
171 * {@code CONTIGUOUS_TREE_SELECTION}.
172 */
173 public int getSelectionMode() {
174 return selectionMode;
175 }
176
177 /**
178 * Sets the selection to path. If this represents a change, then
179 * the TreeSelectionListeners are notified. If {@code path} is
180 * null, this has the same effect as invoking {@code clearSelection}.
181 *
182 * @param path new path to select
183 */
184 public void setSelectionPath(TreePath path) {
185 if(path == null)
186 setSelectionPaths(null);
187 else {
188 TreePath[] newPaths = new TreePath[1];
189
190 newPaths[0] = path;
191 setSelectionPaths(newPaths);
192 }
193 }
194
195 /**
196 * Sets the selection. Whether the supplied paths are taken as the
197 * new selection depends upon the selection mode. If the supplied
198 * array is {@code null}, or empty, the selection is cleared. If
199 * the selection mode is {@code SINGLE_TREE_SELECTION}, only the
200 * first path in {@code pPaths} is used. If the selection
284
285 uniquePaths = lastPaths;
286 lastPaths = tempHT;
287 lastPaths.clear();
288
289 // No reason to do this now, but will still call it.
290 insureUniqueness();
291
292 updateLeadIndex();
293
294 resetRowSelection();
295 /* Notify of the change. */
296 if(cPaths.size() > 0)
297 notifyPathChange(cPaths, beginLeadPath);
298 }
299 }
300
301 /**
302 * Adds path to the current selection. If path is not currently
303 * in the selection the TreeSelectionListeners are notified. This has
304 * no effect if {@code path} is null.
305 *
306 * @param path the new path to add to the current selection
307 */
308 public void addSelectionPath(TreePath path) {
309 if(path != null) {
310 TreePath[] toAdd = new TreePath[1];
311
312 toAdd[0] = path;
313 addSelectionPaths(toAdd);
314 }
315 }
316
317 /**
318 * Adds paths to the current selection. If any of the paths in
319 * paths are not currently in the selection the TreeSelectionListeners
320 * are notified. This has
321 * no effect if {@code paths} is null.
322 * <p>The lead path is set to the last element in {@code paths}.
323 * <p>If the selection mode is {@code CONTIGUOUS_TREE_SELECTION},
324 * and adding the new paths would make the selection discontiguous.
325 * Then two things can result: if the TreePaths in {@code paths}
326 * are contiguous, then the selection becomes these TreePaths,
327 * otherwise the TreePaths aren't contiguous and the selection becomes
328 * the first TreePath in {@code paths}.
329 *
330 * @param paths the new path to add to the current selection
331 */
332 public void addSelectionPaths(TreePath[] paths) {
333 int newPathLength = ((paths == null) ? 0 : paths.length);
334
335 if(newPathLength > 0) {
336 if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
337 setSelectionPaths(paths);
338 }
339 else if(selectionMode == TreeSelectionModel.
340 CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
341 if(arePathsContiguous(paths)) {
342 setSelectionPaths(paths);
343 }
344 else {
345 TreePath[] newPaths = new TreePath[1];
346
347 newPaths[0] = paths[0];
348 setSelectionPaths(newPaths);
407 selection = newSelection;
408
409 insureUniqueness();
410
411 updateLeadIndex();
412
413 resetRowSelection();
414
415 notifyPathChange(cPaths, beginLeadPath);
416 }
417 else
418 leadPath = beginLeadPath;
419 lastPaths.clear();
420 }
421 }
422 }
423
424 /**
425 * Removes path from the selection. If path is in the selection
426 * The TreeSelectionListeners are notified. This has no effect if
427 * {@code path} is null.
428 *
429 * @param path the path to remove from the selection
430 */
431 public void removeSelectionPath(TreePath path) {
432 if(path != null) {
433 TreePath[] rPath = new TreePath[1];
434
435 rPath[0] = path;
436 removeSelectionPaths(rPath);
437 }
438 }
439
440 /**
441 * Removes paths from the selection. If any of the paths in paths
442 * are in the selection the TreeSelectionListeners are notified.
443 * This has no effect if {@code paths} is null.
444 *
445 * @param paths the paths to remove from the selection
446 */
447 public void removeSelectionPaths(TreePath[] paths) {
448 if (paths != null && selection != null && paths.length > 0) {
449 if(!canPathsBeRemoved(paths)) {
450 /* Could probably do something more interesting here! */
451 clearSelection();
452 }
453 else {
454 Vector<PathPlaceHolder> pathsToRemove = null;
455
456 /* Find the paths that can be removed. */
457 for (int removeCounter = paths.length - 1; removeCounter >= 0;
458 removeCounter--) {
459 if(paths[removeCounter] != null) {
460 if (uniquePaths.get(paths[removeCounter]) != null) {
461 if(pathsToRemove == null)
462 pathsToRemove = new Vector<PathPlaceHolder>(paths.length);
463 uniquePaths.remove(paths[removeCounter]);
526 */
527 public TreePath[] getSelectionPaths() {
528 if(selection != null) {
529 int pathSize = selection.length;
530 TreePath[] result = new TreePath[pathSize];
531
532 System.arraycopy(selection, 0, result, 0, pathSize);
533 return result;
534 }
535 return new TreePath[0];
536 }
537
538 /**
539 * Returns the number of paths that are selected.
540 */
541 public int getSelectionCount() {
542 return (selection == null) ? 0 : selection.length;
543 }
544
545 /**
546 * Returns true if the path, {@code path},
547 * is in the current selection.
548 */
549 public boolean isPathSelected(TreePath path) {
550 return (path != null) ? (uniquePaths.get(path) != null) : false;
551 }
552
553 /**
554 * Returns true if the selection is currently empty.
555 */
556 public boolean isSelectionEmpty() {
557 return (selection == null || selection.length == 0);
558 }
559
560 /**
561 * Empties the current selection. If this represents a change in the
562 * current selection, the selection listeners are notified.
563 */
564 public void clearSelection() {
565 if (selection != null && selection.length > 0) {
566 int selSize = selection.length;
588 * @param x the new listener to be added
589 */
590 public void addTreeSelectionListener(TreeSelectionListener x) {
591 listenerList.add(TreeSelectionListener.class, x);
592 }
593
594 /**
595 * Removes x from the list of listeners that are notified each time
596 * the set of selected TreePaths changes.
597 *
598 * @param x the listener to remove
599 */
600 public void removeTreeSelectionListener(TreeSelectionListener x) {
601 listenerList.remove(TreeSelectionListener.class, x);
602 }
603
604 /**
605 * Returns an array of all the tree selection listeners
606 * registered on this model.
607 *
608 * @return all of this model's {@code TreeSelectionListener}s
609 * or an empty
610 * array if no tree selection listeners are currently registered
611 *
612 * @see #addTreeSelectionListener
613 * @see #removeTreeSelectionListener
614 *
615 * @since 1.4
616 */
617 public TreeSelectionListener[] getTreeSelectionListeners() {
618 return listenerList.getListeners(TreeSelectionListener.class);
619 }
620
621 /**
622 * Notifies all listeners that are registered for
623 * tree selection events on this object.
624 *
625 * @param e the event that characterizes the change
626 *
627 * @see #addTreeSelectionListener
628 * @see EventListenerList
635 // those that are interested in this event
636 for (int i = listeners.length-2; i>=0; i-=2) {
637 if (listeners[i]==TreeSelectionListener.class) {
638 // Lazily create the event:
639 // if (e == null)
640 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
641 ((TreeSelectionListener)listeners[i+1]).valueChanged(e);
642 }
643 }
644 }
645
646 /**
647 * Returns an array of all the objects currently registered
648 * as <code><em>Foo</em>Listener</code>s
649 * upon this model.
650 * <code><em>Foo</em>Listener</code>s are registered using the
651 * <code>add<em>Foo</em>Listener</code> method.
652 *
653 * <p>
654 *
655 * You can specify the {@code listenerType} argument
656 * with a class literal,
657 * such as
658 * <code><em>Foo</em>Listener.class</code>.
659 * For example, you can query a
660 * {@code DefaultTreeSelectionModel m}
661 * for its tree selection listeners with the following code:
662 *
663 * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
664 *
665 * If no such listeners exist, this method returns an empty array.
666 *
667 * @param <T> the listener type
668 * @param listenerType the type of listeners requested
669 * @return an array of all objects registered as
670 * <code><em>Foo</em>Listener</code>s on this component,
671 * or an empty array if no such
672 * listeners have been added
673 * @exception ClassCastException if {@code listenerType}
674 * doesn't specify a class or interface that implements
675 * {@code java.util.EventListener}
676 *
677 * @see #getTreeSelectionListeners
678 * @see #getPropertyChangeListeners
679 *
680 * @since 1.3
681 */
682 public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
683 return listenerList.getListeners(listenerType);
684 }
685
686 /**
687 * Returns the selection in terms of rows. There is not
688 * necessarily a one-to-one mapping between the {@code TreePath}s
689 * returned from {@code getSelectionPaths} and this method. In
690 * particular, if a {@code TreePath} is not viewable (the {@code
691 * RowMapper} returns {@code -1} for the row corresponding to the
692 * {@code TreePath}), then the corresponding row is not included
693 * in the returned array. For example, if the selection consists
694 * of two paths, {@code A} and {@code B}, with {@code A} at row
695 * {@code 10}, and {@code B} not currently viewable, then this method
735
736 /**
737 * Returns the smallest value obtained from the RowMapper for the
738 * current set of selected TreePaths. If nothing is selected,
739 * or there is no RowMapper, this will return -1.
740 */
741 public int getMinSelectionRow() {
742 return listSelectionModel.getMinSelectionIndex();
743 }
744
745 /**
746 * Returns the largest value obtained from the RowMapper for the
747 * current set of selected TreePaths. If nothing is selected,
748 * or there is no RowMapper, this will return -1.
749 */
750 public int getMaxSelectionRow() {
751 return listSelectionModel.getMaxSelectionIndex();
752 }
753
754 /**
755 * Returns true if the row identified by {@code row} is selected.
756 */
757 public boolean isRowSelected(int row) {
758 return listSelectionModel.isSelectedIndex(row);
759 }
760
761 /**
762 * Updates this object's mapping from TreePath to rows. This should
763 * be invoked when the mapping from TreePaths to integers has changed
764 * (for example, a node has been expanded).
765 * <p>You do not normally have to call this, JTree and its associated
766 * Listeners will invoke this for you. If you are implementing your own
767 * View class, then you will have to invoke this.
768 * <p>This will invoke {@code insureRowContinuity} to make sure
769 * the currently selected TreePaths are still valid based on the
770 * selection mode.
771 */
772 public void resetRowSelection() {
773 listSelectionModel.clearSelection();
774 if(selection != null && rowMapper != null) {
775 int aRow;
776 int validCount = 0;
777 int[] rows = rowMapper.getRowsForPaths(selection);
778
779 for(int counter = 0, maxCounter = selection.length;
780 counter < maxCounter; counter++) {
781 aRow = rows[counter];
782 if(aRow != -1) {
783 listSelectionModel.addSelectionInterval(aRow, aRow);
784 }
785 }
786 if(leadIndex != -1 && rows != null) {
787 leadRow = rows[leadIndex];
788 }
836 }
837
838 /**
839 * Removes a PropertyChangeListener from the listener list.
840 * This removes a PropertyChangeListener that was registered
841 * for all properties.
842 *
843 * @param listener the PropertyChangeListener to be removed
844 */
845
846 public synchronized void removePropertyChangeListener(
847 PropertyChangeListener listener) {
848 if (changeSupport == null) {
849 return;
850 }
851 changeSupport.removePropertyChangeListener(listener);
852 }
853
854 /**
855 * Returns an array of all the property change listeners
856 * registered on this {@code DefaultTreeSelectionModel}.
857 *
858 * @return all of this model's {@code PropertyChangeListener}s
859 * or an empty
860 * array if no property change listeners are currently registered
861 *
862 * @see #addPropertyChangeListener
863 * @see #removePropertyChangeListener
864 *
865 * @since 1.4
866 */
867 public PropertyChangeListener[] getPropertyChangeListeners() {
868 if (changeSupport == null) {
869 return new PropertyChangeListener[0];
870 }
871 return changeSupport.getPropertyChangeListeners();
872 }
873
874 /**
875 * Makes sure the currently selected {@code TreePath}s are valid
876 * for the current selection mode.
877 * If the selection mode is {@code CONTIGUOUS_TREE_SELECTION}
878 * and a {@code RowMapper} exists, this will make sure all
879 * the rows are contiguous, that is, when sorted all the rows are
880 * in order with no gaps.
881 * If the selection isn't contiguous, the selection is
882 * reset to contain the first set, when sorted, of contiguous rows.
883 * <p>
884 * If the selection mode is {@code SINGLE_TREE_SELECTION} and
885 * more than one TreePath is selected, the selection is reset to
886 * contain the first path currently selected.
887 */
888 protected void insureRowContinuity() {
889 if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
890 selection != null && rowMapper != null) {
891 DefaultListSelectionModel lModel = listSelectionModel;
892 int min = lModel.getMinSelectionIndex();
893
894 if(min != -1) {
895 for(int counter = min,
896 maxCounter = lModel.getMaxSelectionIndex();
897 counter <= maxCounter; counter++) {
898 if(!lModel.isSelectedIndex(counter)) {
899 if(counter == min) {
900 clearSelection();
901 }
902 else {
903 TreePath[] newSel = new TreePath[counter - min];
904 int selectionIndex[] = rowMapper.getRowsForPaths(selection);
953 anIndex > (min + pathCount))
954 return false;
955 if(anIndex < min)
956 min = anIndex;
957 if(!bitSet.get(anIndex)) {
958 bitSet.set(anIndex);
959 validCount++;
960 }
961 }
962 }
963 int maxCounter = validCount + min;
964
965 for(counter = min; counter < maxCounter; counter++)
966 if(!bitSet.get(counter))
967 return false;
968 }
969 return true;
970 }
971
972 /**
973 * Used to test if a particular set of {@code TreePath}s can
974 * be added. This will return true if {@code paths} is null (or
975 * empty), or this object has no RowMapper, or nothing is currently selected,
976 * or the selection mode is {@code DISCONTIGUOUS_TREE_SELECTION}, or
977 * adding the paths to the current selection still results in a
978 * contiguous set of {@code TreePath}s.
979 *
980 * @param paths array of {@code TreePaths} to check
981 * @return whether the particular set of {@code TreePaths} can be added
982 */
983 protected boolean canPathsBeAdded(TreePath[] paths) {
984 if(paths == null || paths.length == 0 || rowMapper == null ||
985 selection == null || selectionMode ==
986 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
987 return true;
988 else {
989 BitSet bitSet = new BitSet();
990 DefaultListSelectionModel lModel = listSelectionModel;
991 int anIndex;
992 int counter;
993 int min = lModel.getMinSelectionIndex();
994 int max = lModel.getMaxSelectionIndex();
995 TreePath[] tempPath = new TreePath[1];
996
997 if(min != -1) {
998 for(counter = min; counter <= max; counter++) {
|