KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > tree > DefaultTreeSelectionModel


1 /*
2  * @(#)DefaultTreeSelectionModel.java 1.50 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.swing.tree;
9
10 import java.beans.PropertyChangeListener JavaDoc;
11 import java.io.*;
12 import java.util.BitSet JavaDoc;
13 import java.util.Enumeration JavaDoc;
14 import java.util.EventListener JavaDoc;
15 import java.util.Hashtable JavaDoc;
16 import java.util.Vector JavaDoc;
17 import javax.swing.event.*;
18 import javax.swing.DefaultListSelectionModel JavaDoc;
19
20 /**
21  * Default implementation of TreeSelectionModel. Listeners are notified
22  * whenever
23  * the paths in the selection change, not the rows. In order
24  * to be able to track row changes you may wish to become a listener
25  * for expansion events on the tree and test for changes from there.
26  * <p>resetRowSelection is called from any of the methods that update
27  * the selected paths. If you subclass any of these methods to
28  * filter what is allowed to be selected, be sure and message
29  * <code>resetRowSelection</code> if you do not message super.
30  *
31  * <p>
32  *
33  * <strong>Warning:</strong>
34  * Serialized objects of this class will not be compatible with
35  * future Swing releases. The current serialization support is
36  * appropriate for short term storage or RMI between applications running
37  * the same version of Swing. As of 1.4, support for long term storage
38  * of all JavaBeans<sup><font size="-2">TM</font></sup>
39  * has been added to the <code>java.beans</code> package.
40  * Please see {@link java.beans.XMLEncoder}.
41  *
42  * @see javax.swing.JTree
43  *
44  * @version 1.50 05/05/04
45  * @author Scott Violet
46  */

47 public class DefaultTreeSelectionModel extends Object JavaDoc implements Cloneable JavaDoc, Serializable, TreeSelectionModel JavaDoc
48 {
49     /** Property name for selectionMode. */
50     public static final String JavaDoc SELECTION_MODE_PROPERTY = "selectionMode";
51
52     /** Used to messaged registered listeners. */
53     protected SwingPropertyChangeSupport changeSupport;
54
55     /** Paths that are currently selected. Will be null if nothing is
56       * currently selected. */

57     protected TreePath JavaDoc[] selection;
58
59     /** Event listener list. */
60     protected EventListenerList listenerList = new EventListenerList();
61
62     /** Provides a row for a given path. */
63     transient protected RowMapper JavaDoc rowMapper;
64
65     /** Handles maintaining the list selection model. The RowMapper is used
66      * to map from a TreePath to a row, and the value is then placed here. */

67     protected DefaultListSelectionModel JavaDoc listSelectionModel;
68
69     /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
70      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
71      */

72     protected int selectionMode;
73
74     /** Last path that was added. */
75     protected TreePath JavaDoc leadPath;
76     /** Index of the lead path in selection. */
77     protected int leadIndex;
78     /** Lead row. */
79     protected int leadRow;
80
81     /** Used to make sure the paths are unique, will contain all the paths
82      * in <code>selection</code>.
83      */

84     private Hashtable JavaDoc uniquePaths;
85     private Hashtable JavaDoc lastPaths;
86     private TreePath JavaDoc[] tempPaths;
87
88
89     /**
90      * Creates a new instance of DefaultTreeSelectionModel that is
91      * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
92      */

93     public DefaultTreeSelectionModel() {
94     listSelectionModel = new DefaultListSelectionModel JavaDoc();
95     selectionMode = DISCONTIGUOUS_TREE_SELECTION;
96     leadIndex = leadRow = -1;
97     uniquePaths = new Hashtable JavaDoc();
98     lastPaths = new Hashtable JavaDoc();
99     tempPaths = new TreePath JavaDoc[1];
100     }
101
102     /**
103      * Sets the RowMapper instance. This instance is used to determine
104      * the row for a particular TreePath.
105      */

106     public void setRowMapper(RowMapper JavaDoc newMapper) {
107     rowMapper = newMapper;
108     resetRowSelection();
109     }
110
111     /**
112      * Returns the RowMapper instance that is able to map a TreePath to a
113      * row.
114      */

115     public RowMapper JavaDoc getRowMapper() {
116     return rowMapper;
117     }
118
119     /**
120      * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
121      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
122      * is not one of the defined value,
123      * <code>DISCONTIGUOUS_TREE_SELECTION</code> is assumed.
124      * <p>This may change the selection if the current selection is not valid
125      * for the new mode. For example, if three TreePaths are
126      * selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>,
127      * only one TreePath will remain selected. It is up to the particular
128      * implementation to decide what TreePath remains selected.
129      * <p>
130      * Setting the mode to something other than the defined types will
131      * result in the mode becoming <code>DISCONTIGUOUS_TREE_SELECTION</code>.
132      */

133     public void setSelectionMode(int mode) {
134     int oldMode = selectionMode;
135
136     selectionMode = mode;
137     if(selectionMode != TreeSelectionModel.SINGLE_TREE_SELECTION &&
138        selectionMode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
139        selectionMode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
140         selectionMode = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
141     if(oldMode != selectionMode && changeSupport != null)
142         changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
143                          new Integer JavaDoc(oldMode),
144                          new Integer JavaDoc(selectionMode));
145     }
146
147     /**
148      * Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>,
149      * <code>DISCONTIGUOUS_TREE_SELECTION</code> or
150      * <code>CONTIGUOUS_TREE_SELECTION</code>.
151      */

152     public int getSelectionMode() {
153     return selectionMode;
154     }
155
156     /**
157       * Sets the selection to path. If this represents a change, then
158       * the TreeSelectionListeners are notified. If <code>path</code> is
159       * null, this has the same effect as invoking <code>clearSelection</code>.
160       *
161       * @param path new path to select
162       */

163     public void setSelectionPath(TreePath JavaDoc path) {
164     if(path == null)
165         setSelectionPaths(null);
166     else {
167         TreePath JavaDoc[] newPaths = new TreePath JavaDoc[1];
168
169         newPaths[0] = path;
170         setSelectionPaths(newPaths);
171     }
172     }
173
174     /**
175       * Sets the selection to the paths in paths. If this represents a
176       * change the TreeSelectionListeners are notified. Potentially
177       * paths will be held by this object; in other words don't change
178       * any of the objects in the array once passed in.
179       * <p>If <code>paths</code> is
180       * null, this has the same effect as invoking <code>clearSelection</code>.
181       * <p>The lead path is set to the last path in <code>pPaths</code>.
182       * <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>,
183       * and adding the new paths would make the selection discontiguous,
184       * the selection is reset to the first TreePath in <code>paths</code>.
185       *
186       * @param pPaths new selection
187       */

188     public void setSelectionPaths(TreePath JavaDoc[] pPaths) {
189     int newCount, newCounter, oldCount, oldCounter;
190     TreePath JavaDoc[] paths = pPaths;
191
192     if(paths == null)
193         newCount = 0;
194     else
195         newCount = paths.length;
196     if(selection == null)
197         oldCount = 0;
198     else
199         oldCount = selection.length;
200     if((newCount + oldCount) != 0) {
201         if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
202         /* If single selection and more than one path, only allow
203            first. */

204         if(newCount > 1) {
205             paths = new TreePath JavaDoc[1];
206             paths[0] = pPaths[0];
207             newCount = 1;
208         }
209         }
210         else if(selectionMode ==
211             TreeSelectionModel.CONTIGUOUS_TREE_SELECTION) {
212         /* If contiguous selection and paths aren't contiguous,
213            only select the first path item. */

214         if(newCount > 0 && !arePathsContiguous(paths)) {
215             paths = new TreePath JavaDoc[1];
216             paths[0] = pPaths[0];
217             newCount = 1;
218         }
219         }
220
221         int validCount = 0;
222         TreePath JavaDoc beginLeadPath = leadPath;
223         Vector JavaDoc cPaths = new Vector JavaDoc(newCount + oldCount);
224
225         lastPaths.clear();
226         leadPath = null;
227         /* Find the paths that are new. */
228         for(newCounter = 0; newCounter < newCount; newCounter++) {
229         if(paths[newCounter] != null &&
230            lastPaths.get(paths[newCounter]) == null) {
231             validCount++;
232             lastPaths.put(paths[newCounter], Boolean.TRUE);
233             if (uniquePaths.get(paths[newCounter]) == null) {
234             cPaths.addElement(new PathPlaceHolder
235                       (paths[newCounter], true));
236             }
237             leadPath = paths[newCounter];
238         }
239         }
240
241         /* If the validCount isn't equal to newCount it means there
242            are some null in paths, remove them and set selection to
243            the new path. */

244         TreePath JavaDoc[] newSelection;
245
246         if(validCount == 0) {
247         newSelection = null;
248         }
249         else if (validCount != newCount) {
250         Enumeration JavaDoc keys = lastPaths.keys();
251
252         newSelection = new TreePath JavaDoc[validCount];
253         validCount = 0;
254         while (keys.hasMoreElements()) {
255             newSelection[validCount++] = (TreePath JavaDoc)keys.nextElement();
256         }
257         }
258         else {
259         newSelection = new TreePath JavaDoc[paths.length];
260         System.arraycopy(paths, 0, newSelection, 0, paths.length);
261         }
262
263         /* Get the paths that were selected but no longer selected. */
264         for(oldCounter = 0; oldCounter < oldCount; oldCounter++)
265         if(selection[oldCounter] != null &&
266             lastPaths.get(selection[oldCounter]) == null)
267             cPaths.addElement(new PathPlaceHolder
268                       (selection[oldCounter], false));
269
270         selection = newSelection;
271
272         Hashtable JavaDoc tempHT = uniquePaths;
273
274         uniquePaths = lastPaths;
275         lastPaths = tempHT;
276         lastPaths.clear();
277
278         // No reason to do this now, but will still call it.
279
if(selection != null)
280         insureUniqueness();
281
282         updateLeadIndex();
283
284         resetRowSelection();
285         /* Notify of the change. */
286         if(cPaths.size() > 0)
287         notifyPathChange(cPaths, beginLeadPath);
288     }
289     }
290
291     /**
292       * Adds path to the current selection. If path is not currently
293       * in the selection the TreeSelectionListeners are notified. This has
294       * no effect if <code>path</code> is null.
295       *
296       * @param path the new path to add to the current selection
297       */

298     public void addSelectionPath(TreePath JavaDoc path) {
299     if(path != null) {
300         TreePath JavaDoc[] toAdd = new TreePath JavaDoc[1];
301
302         toAdd[0] = path;
303         addSelectionPaths(toAdd);
304     }
305     }
306
307     /**
308       * Adds paths to the current selection. If any of the paths in
309       * paths are not currently in the selection the TreeSelectionListeners
310       * are notified. This has
311       * no effect if <code>paths</code> is null.
312       * <p>The lead path is set to the last element in <code>paths</code>.
313       * <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>,
314       * and adding the new paths would make the selection discontiguous.
315       * Then two things can result: if the TreePaths in <code>paths</code>
316       * are contiguous, then the selection becomes these TreePaths,
317       * otherwise the TreePaths aren't contiguous and the selection becomes
318       * the first TreePath in <code>paths</code>.
319       *
320       * @param paths the new path to add to the current selection
321       */

322     public void addSelectionPaths(TreePath JavaDoc[] paths) {
323     int newPathLength = ((paths == null) ? 0 : paths.length);
324
325     if(newPathLength > 0) {
326         if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
327         setSelectionPaths(paths);
328         }
329         else if(selectionMode == TreeSelectionModel.
330             CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
331         if(arePathsContiguous(paths)) {
332             setSelectionPaths(paths);
333         }
334         else {
335             TreePath JavaDoc[] newPaths = new TreePath JavaDoc[1];
336
337             newPaths[0] = paths[0];
338             setSelectionPaths(newPaths);
339         }
340         }
341         else {
342         int counter, validCount;
343         int oldCount;
344         TreePath JavaDoc beginLeadPath = leadPath;
345         Vector JavaDoc cPaths = null;
346
347         if(selection == null)
348             oldCount = 0;
349         else
350             oldCount = selection.length;
351         /* Determine the paths that aren't currently in the
352            selection. */

353         lastPaths.clear();
354         for(counter = 0, validCount = 0; counter < newPathLength;
355             counter++) {
356             if(paths[counter] != null) {
357             if (uniquePaths.get(paths[counter]) == null) {
358                 validCount++;
359                 if(cPaths == null)
360                 cPaths = new Vector JavaDoc();
361                 cPaths.addElement(new PathPlaceHolder
362                           (paths[counter], true));
363                 uniquePaths.put(paths[counter], Boolean.TRUE);
364                 lastPaths.put(paths[counter], Boolean.TRUE);
365             }
366             leadPath = paths[counter];
367             }
368         }
369
370         if(leadPath == null) {
371             leadPath = beginLeadPath;
372         }
373
374         if(validCount > 0) {
375             TreePath JavaDoc newSelection[] = new TreePath JavaDoc[oldCount +
376                                   validCount];
377
378             /* And build the new selection. */
379             if(oldCount > 0)
380             System.arraycopy(selection, 0, newSelection, 0,
381                      oldCount);
382             if(validCount != paths.length) {
383             /* Some of the paths in paths are already in
384                the selection. */

385             Enumeration JavaDoc newPaths = lastPaths.keys();
386
387             counter = oldCount;
388             while (newPaths.hasMoreElements()) {
389                 newSelection[counter++] = (TreePath JavaDoc)newPaths.
390                                       nextElement();
391             }
392             }
393             else {
394             System.arraycopy(paths, 0, newSelection, oldCount,
395                      validCount);
396             }
397
398             selection = newSelection;
399
400             insureUniqueness();
401
402             updateLeadIndex();
403
404             resetRowSelection();
405
406             notifyPathChange(cPaths, beginLeadPath);
407         }
408         else
409             leadPath = beginLeadPath;
410         lastPaths.clear();
411         }
412     }
413     }
414
415     /**
416       * Removes path from the selection. If path is in the selection
417       * The TreeSelectionListeners are notified. This has no effect if
418       * <code>path</code> is null.
419       *
420       * @param path the path to remove from the selection
421       */

422     public void removeSelectionPath(TreePath JavaDoc path) {
423     if(path != null) {
424         TreePath JavaDoc[] rPath = new TreePath JavaDoc[1];
425
426         rPath[0] = path;
427         removeSelectionPaths(rPath);
428     }
429     }
430
431     /**
432       * Removes paths from the selection. If any of the paths in paths
433       * are in the selection the TreeSelectionListeners are notified.
434       * This has no effect if <code>paths</code> is null.
435       *
436       * @param paths the paths to remove from the selection
437       */

438     public void removeSelectionPaths(TreePath JavaDoc[] paths) {
439     if (paths != null && selection != null && paths.length > 0) {
440         if(!canPathsBeRemoved(paths)) {
441         /* Could probably do something more interesting here! */
442         clearSelection();
443         }
444         else {
445         Vector JavaDoc pathsToRemove = null;
446
447         /* Find the paths that can be removed. */
448         for (int removeCounter = paths.length - 1; removeCounter >= 0;
449              removeCounter--) {
450             if(paths[removeCounter] != null) {
451             if (uniquePaths.get(paths[removeCounter]) != null) {
452                 if(pathsToRemove == null)
453                 pathsToRemove = new Vector JavaDoc(paths.length);
454                 uniquePaths.remove(paths[removeCounter]);
455                 pathsToRemove.addElement(new PathPlaceHolder
456                      (paths[removeCounter], false));
457             }
458             }
459         }
460         if(pathsToRemove != null) {
461             int removeCount = pathsToRemove.size();
462             TreePath JavaDoc beginLeadPath = leadPath;
463
464             if(removeCount == selection.length) {
465             selection = null;
466             }
467             else {
468             Enumeration JavaDoc pEnum = uniquePaths.keys();
469             int validCount = 0;
470
471             selection = new TreePath JavaDoc[selection.length -
472                         removeCount];
473             while (pEnum.hasMoreElements()) {
474                 selection[validCount++] = (TreePath JavaDoc)pEnum.
475                                           nextElement();
476             }
477             }
478             if (leadPath != null &&
479             uniquePaths.get(leadPath) == null) {
480             if (selection != null) {
481                 leadPath = selection[selection.length - 1];
482             }
483             else {
484                 leadPath = null;
485             }
486             }
487             else if (selection != null) {
488             leadPath = selection[selection.length - 1];
489             }
490             else {
491             leadPath = null;
492             }
493             updateLeadIndex();
494
495             resetRowSelection();
496
497             notifyPathChange(pathsToRemove, beginLeadPath);
498         }
499         }
500     }
501     }
502
503     /**
504       * Returns the first path in the selection. This is useful if there
505       * if only one item currently selected.
506       */

507     public TreePath JavaDoc getSelectionPath() {
508     if(selection != null)
509         return selection[0];
510     return null;
511     }
512
513     /**
514       * Returns the paths in the selection. This will return null (or an
515       * empty array) if nothing is currently selected.
516       */

517     public TreePath JavaDoc[] getSelectionPaths() {
518     if(selection != null) {
519         int pathSize = selection.length;
520         TreePath JavaDoc[] result = new TreePath JavaDoc[pathSize];
521
522         System.arraycopy(selection, 0, result, 0, pathSize);
523         return result;
524     }
525     return null;
526     }
527
528     /**
529      * Returns the number of paths that are selected.
530      */

531     public int getSelectionCount() {
532     return (selection == null) ? 0 : selection.length;
533     }
534
535     /**
536       * Returns true if the path, <code>path</code>,
537       * is in the current selection.
538       */

539     public boolean isPathSelected(TreePath JavaDoc path) {
540     return (path != null) ? (uniquePaths.get(path) != null) : false;
541     }
542
543     /**
544       * Returns true if the selection is currently empty.
545       */

546     public boolean isSelectionEmpty() {
547     return (selection == null);
548     }
549
550     /**
551       * Empties the current selection. If this represents a change in the
552       * current selection, the selection listeners are notified.
553       */

554     public void clearSelection() {
555     if(selection != null) {
556         int selSize = selection.length;
557         boolean[] newness = new boolean[selSize];
558
559         for(int counter = 0; counter < selSize; counter++)
560         newness[counter] = false;
561
562         TreeSelectionEvent event = new TreeSelectionEvent
563         (this, selection, newness, leadPath, null);
564
565         leadPath = null;
566         leadIndex = leadRow = -1;
567         uniquePaths.clear();
568         selection = null;
569         resetRowSelection();
570         fireValueChanged(event);
571     }
572     }
573
574     /**
575       * Adds x to the list of listeners that are notified each time the
576       * set of selected TreePaths changes.
577       *
578       * @param x the new listener to be added
579       */

580     public void addTreeSelectionListener(TreeSelectionListener x) {
581     listenerList.add(TreeSelectionListener.class, x);
582     }
583
584     /**
585       * Removes x from the list of listeners that are notified each time
586       * the set of selected TreePaths changes.
587       *
588       * @param x the listener to remove
589       */

590     public void removeTreeSelectionListener(TreeSelectionListener x) {
591     listenerList.remove(TreeSelectionListener.class, x);
592     }
593
594     /**
595      * Returns an array of all the tree selection listeners
596      * registered on this model.
597      *
598      * @return all of this model's <code>TreeSelectionListener</code>s
599      * or an empty
600      * array if no tree selection listeners are currently registered
601      *
602      * @see #addTreeSelectionListener
603      * @see #removeTreeSelectionListener
604      *
605      * @since 1.4
606      */

607     public TreeSelectionListener[] getTreeSelectionListeners() {
608         return (TreeSelectionListener[])listenerList.getListeners(
609                 TreeSelectionListener.class);
610     }
611
612     /**
613      * Notifies all listeners that are registered for
614      * tree selection events on this object.
615      * @see #addTreeSelectionListener
616      * @see EventListenerList
617      */

618     protected void fireValueChanged(TreeSelectionEvent e) {
619     // Guaranteed to return a non-null array
620
Object JavaDoc[] listeners = listenerList.getListenerList();
621     // TreeSelectionEvent e = null;
622
// Process the listeners last to first, notifying
623
// those that are interested in this event
624
for (int i = listeners.length-2; i>=0; i-=2) {
625         if (listeners[i]==TreeSelectionListener.class) {
626         // Lazily create the event:
627
// if (e == null)
628
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
629
((TreeSelectionListener)listeners[i+1]).valueChanged(e);
630         }
631     }
632     }
633
634     /**
635      * Returns an array of all the objects currently registered
636      * as <code><em>Foo</em>Listener</code>s
637      * upon this model.
638      * <code><em>Foo</em>Listener</code>s are registered using the
639      * <code>add<em>Foo</em>Listener</code> method.
640      *
641      * <p>
642      *
643      * You can specify the <code>listenerType</code> argument
644      * with a class literal,
645      * such as
646      * <code><em>Foo</em>Listener.class</code>.
647      * For example, you can query a
648      * <code>DefaultTreeSelectionModel</code> <code>m</code>
649      * for its tree selection listeners with the following code:
650      *
651      * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
652      *
653      * If no such listeners exist, this method returns an empty array.
654      *
655      * @param listenerType the type of listeners requested; this parameter
656      * should specify an interface that descends from
657      * <code>java.util.EventListener</code>
658      * @return an array of all objects registered as
659      * <code><em>Foo</em>Listener</code>s on this component,
660      * or an empty array if no such
661      * listeners have been added
662      * @exception ClassCastException if <code>listenerType</code>
663      * doesn't specify a class or interface that implements
664      * <code>java.util.EventListener</code>
665      *
666      * @see #getTreeSelectionListeners
667      * @see #getPropertyChangeListeners
668      *
669      * @since 1.3
670      */

671     public <T extends EventListener JavaDoc> T[] getListeners(Class JavaDoc<T> listenerType) {
672     return listenerList.getListeners(listenerType);
673     }
674
675     /**
676       * Returns all of the currently selected rows. This will return
677       * null (or an empty array) if there are no selected TreePaths or
678       * a RowMapper has not been set.
679       * This may return an array of length less that than of the selected
680       * TreePaths if some of the rows are not visible (that is the
681       * RowMapper returned -1 for the row corresponding to the TreePath).
682       */

683     public int[] getSelectionRows() {
684     // This is currently rather expensive. Needs
685
// to be better support from ListSelectionModel to speed this up.
686
if(rowMapper != null && selection != null) {
687         int[] rows = rowMapper.getRowsForPaths(selection);
688
689         if (rows != null) {
690         int invisCount = 0;
691
692         for (int counter = rows.length - 1; counter >= 0; counter--) {
693             if (rows[counter] == -1) {
694             invisCount++;
695             }
696         }
697         if (invisCount > 0) {
698             if (invisCount == rows.length) {
699             rows = null;
700             }
701             else {
702             int[] tempRows = new int[rows.length - invisCount];
703
704             for (int counter = rows.length - 1, visCounter = 0;
705                  counter >= 0; counter--) {
706                 if (rows[counter] != -1) {
707                 tempRows[visCounter++] = rows[counter];
708                 }
709             }
710             rows = tempRows;
711             }
712         }
713         }
714         return rows;
715     }
716     return null;
717     }
718
719     /**
720      * Returns the smallest value obtained from the RowMapper for the
721      * current set of selected TreePaths. If nothing is selected,
722      * or there is no RowMapper, this will return -1.
723       */

724     public int getMinSelectionRow() {
725     return listSelectionModel.getMinSelectionIndex();
726     }
727
728     /**
729      * Returns the largest value obtained from the RowMapper for the
730      * current set of selected TreePaths. If nothing is selected,
731      * or there is no RowMapper, this will return -1.
732       */

733     public int getMaxSelectionRow() {
734     return listSelectionModel.getMaxSelectionIndex();
735     }
736
737     /**
738       * Returns true if the row identified by <code>row</code> is selected.
739       */

740     public boolean isRowSelected(int row) {
741     return listSelectionModel.isSelectedIndex(row);
742     }
743
744     /**
745      * Updates this object's mapping from TreePath to rows. This should
746      * be invoked when the mapping from TreePaths to integers has changed
747      * (for example, a node has been expanded).
748      * <p>You do not normally have to call this, JTree and its associated
749      * Listeners will invoke this for you. If you are implementing your own
750      * View class, then you will have to invoke this.
751      * <p>This will invoke <code>insureRowContinuity</code> to make sure
752      * the currently selected TreePaths are still valid based on the
753      * selection mode.
754      */

755     public void resetRowSelection() {
756     listSelectionModel.clearSelection();
757     if(selection != null && rowMapper != null) {
758         int aRow;
759         int validCount = 0;
760         int[] rows = rowMapper.getRowsForPaths(selection);
761
762         for(int counter = 0, maxCounter = selection.length;
763         counter < maxCounter; counter++) {
764         aRow = rows[counter];
765         if(aRow != -1) {
766             listSelectionModel.addSelectionInterval(aRow, aRow);
767         }
768         }
769         if(leadIndex != -1 && rows != null) {
770         leadRow = rows[leadIndex];
771         }
772         else if (leadPath != null) {
773         // Lead selection path doesn't have to be in the selection.
774
tempPaths[0] = leadPath;
775         rows = rowMapper.getRowsForPaths(tempPaths);
776         leadRow = (rows != null) ? rows[0] : -1;
777         }
778         else {
779         leadRow = -1;
780         }
781         insureRowContinuity();
782
783     }
784     else
785         leadRow = -1;
786     }
787
788     /**
789      * Returns the lead selection index. That is the last index that was
790      * added.
791      */

792     public int getLeadSelectionRow() {
793     return leadRow;
794     }
795
796     /**
797      * Returns the last path that was added. This may differ from the
798      * leadSelectionPath property maintained by the JTree.
799      */

800     public TreePath JavaDoc getLeadSelectionPath() {
801     return leadPath;
802     }
803
804     /**
805      * Adds a PropertyChangeListener to the listener list.
806      * The listener is registered for all properties.
807      * <p>
808      * A PropertyChangeEvent will get fired when the selection mode
809      * changes.
810      *
811      * @param listener the PropertyChangeListener to be added
812      */

813     public synchronized void addPropertyChangeListener(
814                                 PropertyChangeListener JavaDoc listener) {
815         if (changeSupport == null) {
816             changeSupport = new SwingPropertyChangeSupport(this);
817         }
818         changeSupport.addPropertyChangeListener(listener);
819     }
820
821     /**
822      * Removes a PropertyChangeListener from the listener list.
823      * This removes a PropertyChangeListener that was registered
824      * for all properties.
825      *
826      * @param listener the PropertyChangeListener to be removed
827      */

828
829     public synchronized void removePropertyChangeListener(
830                                 PropertyChangeListener JavaDoc listener) {
831         if (changeSupport == null) {
832             return;
833         }
834         changeSupport.removePropertyChangeListener(listener);
835     }
836
837     /**
838      * Returns an array of all the property change listeners
839      * registered on this <code>DefaultTreeSelectionModel</code>.
840      *
841      * @return all of this model's <code>PropertyChangeListener</code>s
842      * or an empty
843      * array if no property change listeners are currently registered
844      *
845      * @see #addPropertyChangeListener
846      * @see #removePropertyChangeListener
847      *
848      * @since 1.4
849      */

850     public PropertyChangeListener JavaDoc[] getPropertyChangeListeners() {
851         if (changeSupport == null) {
852             return new PropertyChangeListener JavaDoc[0];
853         }
854         return changeSupport.getPropertyChangeListeners();
855     }
856
857     /**
858      * Makes sure the currently selected <code>TreePath</code>s are valid
859      * for the current selection mode.
860      * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>
861      * and a <code>RowMapper</code> exists, this will make sure all
862      * the rows are contiguous, that is, when sorted all the rows are
863      * in order with no gaps.
864      * If the selection isn't contiguous, the selection is
865      * reset to contain the first set, when sorted, of contiguous rows.
866      * <p>
867      * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and
868      * more than one TreePath is selected, the selection is reset to
869      * contain the first path currently selected.
870      */

871     protected void insureRowContinuity() {
872     if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
873        selection != null && rowMapper != null) {
874         DefaultListSelectionModel JavaDoc lModel = listSelectionModel;
875         int min = lModel.getMinSelectionIndex();
876
877         if(min != -1) {
878         for(int counter = min,
879             maxCounter = lModel.getMaxSelectionIndex();
880                 counter <= maxCounter; counter++) {
881             if(!lModel.isSelectedIndex(counter)) {
882             if(counter == min) {
883                 clearSelection();
884             }
885             else {
886                 TreePath JavaDoc[] newSel = new TreePath JavaDoc[counter - min];
887                 int selectionIndex[] = rowMapper.getRowsForPaths(selection);
888                 // find the actual selection pathes corresponded to the
889
// rows of the new selection
890
for (int i = 0; i < selectionIndex.length; i++) {
891                 if (selectionIndex[i]<counter) {
892                     newSel[selectionIndex[i]-min] = selection[i];
893                 }
894                 }
895                 setSelectionPaths(newSel);
896                 break;
897             }
898             }
899         }
900         }
901     }
902     else if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION &&
903         selection != null && selection.length > 1) {
904         setSelectionPath(selection[0]);
905     }
906     }
907
908     /**
909      * Returns true if the paths are contiguous,
910      * or this object has no RowMapper.
911      */

912     protected boolean arePathsContiguous(TreePath JavaDoc[] paths) {
913     if(rowMapper == null || paths.length < 2)
914         return true;
915     else {
916         BitSet JavaDoc bitSet = new BitSet JavaDoc(32);
917         int anIndex, counter, min;
918         int pathCount = paths.length;
919         int validCount = 0;
920         TreePath JavaDoc[] tempPath = new TreePath JavaDoc[1];
921
922         tempPath[0] = paths[0];
923         min = rowMapper.getRowsForPaths(tempPath)[0];
924         for(counter = 0; counter < pathCount; counter++) {
925         if(paths[counter] != null) {
926             tempPath[0] = paths[counter];
927             int[] rows = rowMapper.getRowsForPaths(tempPath);
928             if (rows == null) {
929             return false;
930             }
931             anIndex = rows[0];
932             if(anIndex == -1 || anIndex < (min - pathCount) ||
933                anIndex > (min + pathCount))
934             return false;
935             if(anIndex < min)
936             min = anIndex;
937             if(!bitSet.get(anIndex)) {
938             bitSet.set(anIndex);
939             validCount++;
940             }
941         }
942         }
943         int maxCounter = validCount + min;
944
945         for(counter = min; counter < maxCounter; counter++)
946         if(!bitSet.get(counter))
947             return false;
948     }
949     return true;
950     }
951
952     /**
953      * Used to test if a particular set of <code>TreePath</code>s can
954      * be added. This will return true if <code>paths</code> is null (or
955      * empty), or this object has no RowMapper, or nothing is currently selected,
956      * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or
957      * adding the paths to the current selection still results in a
958      * contiguous set of <code>TreePath</code>s.
959      */

960     protected boolean canPathsBeAdded(TreePath JavaDoc[] paths) {
961     if(paths == null || paths.length == 0 || rowMapper == null ||
962        selection == null || selectionMode ==
963        TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
964         return true;
965     else {
966         BitSet JavaDoc bitSet = new BitSet JavaDoc();
967         DefaultListSelectionModel JavaDoc lModel = listSelectionModel;
968         int anIndex;
969         int counter;
970         int min = lModel.getMinSelectionIndex();
971         int max = lModel.getMaxSelectionIndex();
972         TreePath JavaDoc[] tempPath = new TreePath JavaDoc[1];
973
974         if(min != -1) {
975         for(counter = min; counter <= max; counter++) {
976             if(lModel.isSelectedIndex(counter))
977             bitSet.set(counter);
978         }
979         }
980         else {
981         tempPath[0] = paths[0];
982         min = max = rowMapper.getRowsForPaths(tempPath)[0];
983         }
984         for(counter = paths.length - 1; counter >= 0; counter--) {
985         if(paths[counter] != null) {
986             tempPath[0] = paths[counter];
987             int[] rows = rowMapper.getRowsForPaths(tempPath);
988             if (rows == null) {
989             return false;
990             }
991             anIndex = rows[0];
992             min = Math.min(anIndex, min);
993             max = Math.max(anIndex, max);
994             if(anIndex == -1)
995             return false;
996             bitSet.set(anIndex);
997         }
998         }
999         for(counter = min; counter <= max; counter++)
1000        if(!bitSet.get(counter))
1001            return false;
1002    }
1003    return true;
1004    }
1005
1006    /**
1007     * Returns true if the paths can be removed without breaking the
1008     * continuity of the model.
1009     * This is rather expensive.
1010     */

1011    protected boolean canPathsBeRemoved(TreePath JavaDoc[] paths) {
1012    if(rowMapper == null || selection == null ||
1013       selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
1014        return true;
1015    else {
1016        BitSet JavaDoc bitSet = new BitSet JavaDoc();
1017        int counter;
1018        int pathCount = paths.length;
1019        int anIndex;
1020        int min = -1;
1021        int validCount = 0;
1022        TreePath JavaDoc[] tempPath = new TreePath JavaDoc[1];
1023        int[] rows;
1024
1025        /* Determine the rows for the removed entries. */
1026        lastPaths.clear();
1027        for (counter = 0; counter < pathCount; counter++) {
1028        if (paths[counter] != null) {
1029            lastPaths.put(paths[counter], Boolean.TRUE);
1030        }
1031        }
1032        for(counter = selection.length - 1; counter >= 0; counter--) {
1033        if(lastPaths.get(selection[counter]) == null) {
1034            tempPath[0] = selection[counter];
1035            rows = rowMapper.getRowsForPaths(tempPath);
1036            if(rows != null && rows[0] != -1 && !bitSet.get(rows[0])) {
1037            validCount++;
1038            if(min == -1)
1039                min = rows[0];
1040            else
1041                min = Math.min(min, rows[0]);
1042            bitSet.set(rows[0]);
1043            }
1044        }
1045        }
1046        lastPaths.clear();
1047        /* Make sure they are contiguous. */
1048        if(validCount > 1) {
1049        for(counter = min + validCount - 1; counter >= min;
1050            counter--)
1051            if(!bitSet.get(counter))
1052            return false;
1053        }
1054    }
1055    return true;
1056    }
1057
1058    /**
1059      * Notifies listeners of a change in path. changePaths should contain
1060      * instances of PathPlaceHolder.
1061      */

1062    protected void notifyPathChange(Vector JavaDoc<PathPlaceHolder> changedPaths,
1063                    TreePath JavaDoc oldLeadSelection) {
1064    int cPathCount = changedPaths.size();
1065    boolean[] newness = new boolean[cPathCount];
1066    TreePath JavaDoc[] paths = new TreePath JavaDoc[cPathCount];
1067    PathPlaceHolder placeholder;
1068    
1069    for(int counter = 0; counter < cPathCount; counter++) {
1070        placeholder = (PathPlaceHolder)changedPaths.elementAt(counter);
1071        newness[counter] = placeholder.isNew;
1072        paths[counter] = placeholder.path;
1073    }
1074    
1075    TreeSelectionEvent event = new TreeSelectionEvent
1076                      (this, paths, newness, oldLeadSelection, leadPath);
1077    
1078    fireValueChanged(event);
1079    }
1080
1081    /**
1082     * Updates the leadIndex instance variable.
1083     */

1084    protected void updateLeadIndex() {
1085    if(leadPath != null) {
1086        if(selection == null) {
1087        leadPath = null;
1088        leadIndex = leadRow = -1;
1089        }
1090        else {
1091        leadRow = leadIndex = -1;
1092        for(int counter = selection.length - 1; counter >= 0;
1093            counter--) {
1094            // Can use == here since we know leadPath came from
1095
// selection
1096
if(selection[counter] == leadPath) {
1097            leadIndex = counter;
1098            break;
1099            }
1100        }
1101        }
1102    }
1103    else {
1104        leadIndex = -1;
1105    }
1106    }
1107
1108    /**
1109     * This method is obsolete and its implementation is now a noop. It's
1110     * still called by setSelectionPaths and addSelectionPaths, but only
1111     * for backwards compatability.
1112     */

1113    protected void insureUniqueness() {
1114    }
1115
1116
1117    /**
1118     * Returns a string that displays and identifies this
1119     * object's properties.
1120     *
1121     * @return a String representation of this object
1122     */

1123    public String JavaDoc toString() {
1124    int selCount = getSelectionCount();
1125    StringBuffer JavaDoc retBuffer = new StringBuffer JavaDoc();
1126    int[] rows;
1127
1128    if(rowMapper != null)
1129        rows = rowMapper.getRowsForPaths(selection);
1130    else
1131        rows = null;
1132    retBuffer.append(getClass().getName() + " " + hashCode() + " [ ");
1133    for(int counter = 0; counter < selCount; counter++) {
1134        if(rows != null)
1135        retBuffer.append(selection[counter].toString() + "@" +
1136                 Integer.toString(rows[counter])+ " ");
1137        else
1138        retBuffer.append(selection[counter].toString() + " ");
1139    }
1140    retBuffer.append("]");
1141    return retBuffer.toString();
1142    }
1143
1144    /**
1145     * Returns a clone of this object with the same selection.
1146     * This method does not duplicate
1147     * selection listeners and property listeners.
1148     *
1149     * @exception CloneNotSupportedException never thrown by instances of
1150     * this class
1151     */

1152    public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
1153    DefaultTreeSelectionModel JavaDoc clone = (DefaultTreeSelectionModel JavaDoc)
1154                        super.clone();
1155
1156    clone.changeSupport = null;
1157    if(selection != null) {
1158        int selLength = selection.length;
1159
1160        clone.selection = new TreePath JavaDoc[selLength];
1161        System.arraycopy(selection, 0, clone.selection, 0, selLength);
1162    }
1163    clone.listenerList = new EventListenerList();
1164    clone.listSelectionModel = (DefaultListSelectionModel JavaDoc)
1165        listSelectionModel.clone();
1166    clone.uniquePaths = new Hashtable JavaDoc();
1167    clone.lastPaths = new Hashtable JavaDoc();
1168    clone.tempPaths = new TreePath JavaDoc[1];
1169    return clone;
1170    }
1171
1172    // Serialization support.
1173
private void writeObject(ObjectOutputStream s) throws IOException {
1174    Object JavaDoc[] tValues;
1175
1176    s.defaultWriteObject();
1177    // Save the rowMapper, if it implements Serializable
1178
if(rowMapper != null && rowMapper instanceof Serializable) {
1179        tValues = new Object JavaDoc[2];
1180        tValues[0] = "rowMapper";
1181        tValues[1] = rowMapper;
1182    }
1183    else
1184        tValues = new Object JavaDoc[0];
1185    s.writeObject(tValues);
1186    }
1187
1188
1189    private void readObject(ObjectInputStream s)
1190    throws IOException, ClassNotFoundException JavaDoc {
1191    Object JavaDoc[] tValues;
1192
1193    s.defaultReadObject();
1194
1195    tValues = (Object JavaDoc[])s.readObject();
1196
1197    if(tValues.length > 0 && tValues[0].equals("rowMapper"))
1198        rowMapper = (RowMapper JavaDoc)tValues[1];
1199    }
1200}
1201
1202/**
1203 * Holds a path and whether or not it is new.
1204 */

1205class PathPlaceHolder {
1206    protected boolean isNew;
1207    protected TreePath JavaDoc path;
1208
1209    PathPlaceHolder(TreePath JavaDoc path, boolean isNew) {
1210    this.path = path;
1211    this.isNew = isNew;
1212    }
1213}
1214
Popular Tags