OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / tree / DefaultTreeSelectionModel.java
1 /* DefaultTreeSelectionModel.java 
2    Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
3    
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.tree;
40
41 import java.beans.PropertyChangeListener;
42 import java.io.IOException;
43 import java.io.ObjectInputStream;
44 import java.io.ObjectOutputStream;
45 import java.io.Serializable;
46 import java.util.Arrays;
47 import java.util.BitSet;
48 import java.util.EventListener;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.Vector;
52
53 import javax.swing.DefaultListSelectionModel;
54 import javax.swing.event.EventListenerList;
55 import javax.swing.event.SwingPropertyChangeSupport;
56 import javax.swing.event.TreeSelectionEvent;
57 import javax.swing.event.TreeSelectionListener;
58
59 /**
60  * The implementation of the default tree selection model. The installed
61  * listeners are notified about the path and not the row changes. If you
62  * specifically need to track the row changes, register the listener for the
63  * expansion events.
64  * 
65  * @author Andrew Selkirk
66  * @author Audrius Meskauskas
67  */
68 public class DefaultTreeSelectionModel
69     implements Cloneable, Serializable, TreeSelectionModel
70 {
71
72   /**
73    * According to the API docs, the method
74    * {@link DefaultTreeSelectionModel#notifyPathChange} should
75    * expect instances of a class PathPlaceHolder in the Vector parameter.
76    * This seems to be a non-public class, so I can only make guesses about the
77    * use of it.
78    */
79   private static class PathPlaceHolder
80   {
81     /**
82      * The path that we wrap.
83      */
84     TreePath path;
85
86     /**
87      * Indicates if the path is new or already in the selection.
88      */
89     boolean isNew;
90
91     /**
92      * Creates a new instance.
93      *
94      * @param p the path to wrap
95      * @param n if the path is new or already in the selection
96      */
97     PathPlaceHolder(TreePath p, boolean n)
98     {
99       path = p;
100       isNew = n;
101     }
102   }
103
104   /**
105    * Use serialVersionUID for interoperability.
106    */
107   static final long serialVersionUID = 3288129636638950196L;
108
109   /**
110    * The name of the selection mode property.
111    */
112   public static final String SELECTION_MODE_PROPERTY = "selectionMode";
113
114   /**
115    * Our Swing property change support.
116    */
117   protected SwingPropertyChangeSupport changeSupport;
118
119   /**
120    * The current selection.
121    */
122   protected TreePath[] selection;
123
124   /**
125    * Our TreeSelectionListeners.
126    */
127   protected EventListenerList listenerList;
128
129   /**
130    * The current RowMapper.
131    */
132   protected transient RowMapper rowMapper;
133
134   /**
135    * The current listSelectionModel.
136    */
137   protected DefaultListSelectionModel listSelectionModel;
138
139   /**
140    * The current selection mode.
141    */
142   protected int selectionMode;
143
144   /**
145    * The path that has been added last.
146    */
147   protected TreePath leadPath;
148
149   /**
150    * The index of the last added path.
151    */
152   protected int leadIndex;
153
154   /**
155    * The row of the last added path according to the RowMapper.
156    */
157   protected int leadRow = -1;
158
159   /**
160    * A supporting datastructure that is used in addSelectionPaths() and
161    * removeSelectionPaths(). It contains currently selected paths.
162    *
163    * @see #addSelectionPaths(TreePath[])
164    * @see #removeSelectionPaths(TreePath[])
165    * @see #setSelectionPaths(TreePath[])
166    */
167   private transient HashSet selectedPaths;
168
169   /**
170    * A supporting datastructure that is used in addSelectionPaths() and
171    * removeSelectionPaths(). It contains the paths that are added or removed.
172    *
173    * @see #addSelectionPaths(TreePath[])
174    * @see #removeSelectionPaths(TreePath[])
175    * @see #setSelectionPaths(TreePath[])
176    */
177   private transient HashSet tmpPaths;
178
179   /**
180    * Constructs a new DefaultTreeSelectionModel.
181    */
182   public DefaultTreeSelectionModel()
183   {
184     setSelectionMode(DISCONTIGUOUS_TREE_SELECTION);
185     listSelectionModel = new DefaultListSelectionModel();
186     listenerList = new EventListenerList();
187     leadIndex = -1;
188     tmpPaths = new HashSet();
189     selectedPaths = new HashSet();
190   }
191
192   /**
193    * Creates a clone of this DefaultTreeSelectionModel with the same selection.
194    * The cloned instance will have the same registered listeners, the listeners
195    * themselves will not be cloned. The selection will be cloned.
196    * 
197    * @exception CloneNotSupportedException should not be thrown here
198    * @return a copy of this DefaultTreeSelectionModel
199    */
200   public Object clone() throws CloneNotSupportedException
201   {
202     DefaultTreeSelectionModel cloned = 
203       (DefaultTreeSelectionModel) super.clone();
204     cloned.changeSupport = null;
205     cloned.selection = (TreePath[]) selection.clone();
206     cloned.listenerList = new EventListenerList();
207     cloned.listSelectionModel =
208       (DefaultListSelectionModel) listSelectionModel.clone();
209     cloned.selectedPaths = new HashSet();
210     cloned.tmpPaths = new HashSet();
211
212     return cloned;
213   }
214
215   /**
216    * Returns a string that shows this object's properties.
217    * The returned string lists the selected tree rows, if any.
218    * 
219    * @return a string that shows this object's properties
220    */
221   public String toString() 
222   {
223     if (isSelectionEmpty())
224       return "[selection empty]";
225     else
226       {
227         StringBuffer b = new StringBuffer("selected rows: [");
228         for (int i = 0; i < selection.length; i++)
229           {
230             b.append(getRow(selection[i]));
231             b.append(' ');
232           }
233         b.append(", lead " + getLeadSelectionRow());
234         return b.toString();
235       }
236   }
237
238   /**
239    * writeObject
240    * 
241    * @param value0 TODO
242    * @exception IOException TODO
243    */
244   private void writeObject(ObjectOutputStream value0) throws IOException
245   {
246     // TODO
247   }
248
249   /**
250    * readObject
251    * 
252    * @param value0 TODO
253    * @exception IOException TODO
254    * @exception ClassNotFoundException TODO
255    */
256   private void readObject(ObjectInputStream value0) throws IOException,
257       ClassNotFoundException
258   {
259     // TODO
260   }
261
262   /**
263    * Sets the RowMapper that should be used to map between paths and their rows.
264    * 
265    * @param mapper the RowMapper to set
266    * @see RowMapper
267    */
268   public void setRowMapper(RowMapper mapper)
269   {
270     rowMapper = mapper;
271     resetRowSelection();
272   }
273
274   /**
275    * Returns the RowMapper that is currently used to map between paths and their
276    * rows.
277    * 
278    * @return the current RowMapper
279    * @see RowMapper
280    */
281   public RowMapper getRowMapper()
282   {
283     return rowMapper;
284   }
285
286   /**
287    * Sets the current selection mode. Possible values are
288    * {@link #SINGLE_TREE_SELECTION}, {@link #CONTIGUOUS_TREE_SELECTION} and
289    * {@link #DISCONTIGUOUS_TREE_SELECTION}.
290    * 
291    * @param mode the selection mode to be set
292    * @see #getSelectionMode
293    * @see #SINGLE_TREE_SELECTION
294    * @see #CONTIGUOUS_TREE_SELECTION
295    * @see #DISCONTIGUOUS_TREE_SELECTION
296    */
297   public void setSelectionMode(int mode)
298   {
299     int oldMode = selectionMode;
300     selectionMode = mode;
301     // Make sure we have a valid selection mode.
302     if (selectionMode != SINGLE_TREE_SELECTION
303         && selectionMode != CONTIGUOUS_TREE_SELECTION
304         && selectionMode != DISCONTIGUOUS_TREE_SELECTION)
305       selectionMode = DISCONTIGUOUS_TREE_SELECTION;
306
307     // Fire property change event.
308     if (oldMode != selectionMode && changeSupport != null)
309       changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, oldMode,
310                                        selectionMode);
311   }
312
313   /**
314    * Returns the current selection mode.
315    * 
316    * @return the current selection mode
317    * @see #setSelectionMode
318    * @see #SINGLE_TREE_SELECTION
319    * @see #CONTIGUOUS_TREE_SELECTION
320    * @see #DISCONTIGUOUS_TREE_SELECTION
321    */
322   public int getSelectionMode()
323   {
324     return selectionMode;
325   }
326
327   /**
328    * Sets this path as the only selection. If this changes the selection the
329    * registered TreeSelectionListeners are notified.
330    * 
331    * @param path the path to set as selection
332    */
333   public void setSelectionPath(TreePath path)
334   {
335     TreePath[] paths = null;
336     if (path != null)
337       paths = new TreePath[]{ path };
338     setSelectionPaths(paths);
339   }
340   
341   /**
342    * Get the number of the tree row for the given path.
343    * 
344    * @param path the tree path
345    * @return the tree row for this path or -1 if the path is not visible.
346    */
347   int getRow(TreePath path)
348   {
349     RowMapper mapper = getRowMapper();
350
351     if (mapper instanceof AbstractLayoutCache)
352       {
353         // The absolute majority of cases, unless the TreeUI is very
354         // seriously rewritten
355         AbstractLayoutCache ama = (AbstractLayoutCache) mapper;
356         return ama.getRowForPath(path);
357       }
358     else if (mapper != null)
359       {
360         // Generic non optimized implementation.
361         int[] rows = mapper.getRowsForPaths(new TreePath[] { path });
362         if (rows.length == 0)
363           return - 1;
364         else
365           return rows[0];
366       }
367     return -1;
368   }
369
370   /**
371    * Sets the paths as selection. This method checks for duplicates and removes
372    * them. If this changes the selection the registered TreeSelectionListeners
373    * are notified.
374    * 
375    * @param paths the paths to set as selection
376    */
377   public void setSelectionPaths(TreePath[] paths)
378   {
379     int oldLength = 0;
380     if (selection != null)
381       oldLength = selection.length;
382     int newLength = 0;
383     if (paths != null)
384       newLength = paths.length;
385     if (newLength > 0 || oldLength > 0)
386       {
387         // For SINGLE_TREE_SELECTION and for CONTIGUOUS_TREE_SELECTION with
388         // a non-contiguous path, we only allow the first path element.
389         if ((selectionMode == SINGLE_TREE_SELECTION && newLength > 1)
390             || (selectionMode == CONTIGUOUS_TREE_SELECTION && newLength > 0
391                 && ! arePathsContiguous(paths)))
392           {
393             paths = new TreePath[] { paths[0] };
394             newLength = 1;
395           }
396         // Find new paths.
397         Vector changedPaths = null;
398         tmpPaths.clear();
399         int validPaths = 0;
400         TreePath oldLeadPath = leadPath;
401         for (int i = 0; i < newLength; i++)
402           {
403             if (paths[i] != null && ! tmpPaths.contains(paths[i]))
404               {
405                 validPaths++;
406                 tmpPaths.add(paths[i]);
407                 if (! selectedPaths.contains(paths[i]))
408                   {
409                     if (changedPaths == null)
410                       changedPaths = new Vector();
411                     changedPaths.add(new PathPlaceHolder(paths[i], true));
412                   }
413                 leadPath = paths[i];
414               }
415           }
416         // Put together the new selection.
417         TreePath[] newSelection = null;
418         if (validPaths != 0)
419           {
420             if (validPaths != newLength)
421               {
422                 // Some of the paths are already selected, put together
423                 // the new selection carefully.
424                 newSelection = new TreePath[validPaths];
425                 Iterator newPaths = tmpPaths.iterator();
426                 validPaths = 0;
427                 for (int i = 0; newPaths.hasNext(); i++)
428                   newSelection[i] = (TreePath) newPaths.next();
429               }
430             else
431               {
432                 newSelection = new TreePath[paths.length];
433                 System.arraycopy(paths, 0, newSelection, 0, paths.length);
434               }
435           }
436
437         // Find paths that have been selected, but are no more.
438         for (int i = 0; i < oldLength; i++)
439           {
440             if (selection[i] != null && ! tmpPaths.contains(selection[i]))
441               {
442                 if (changedPaths == null)
443                   changedPaths = new Vector();
444                 changedPaths.add(new PathPlaceHolder(selection[i], false));
445               }
446           }
447
448         // Perform changes and notification.
449         selection = newSelection;
450         HashSet tmp = selectedPaths;
451         selectedPaths = tmpPaths;
452         tmpPaths = tmp;
453         tmpPaths.clear();
454
455         // Not necessary, but required according to the specs and to tests.
456         if (selection != null)
457           insureUniqueness();
458         updateLeadIndex();
459         resetRowSelection();
460         if (changedPaths != null && changedPaths.size() > 0)
461           notifyPathChange(changedPaths, oldLeadPath);
462       }
463   }
464
465   /**
466    * Adds a path to the list of selected paths. This method checks if the path
467    * is already selected and doesn't add the same path twice. If this changes
468    * the selection the registered TreeSelectionListeners are notified.
469    * 
470    * The lead path is changed to the added path. This also happen if the 
471    * passed path was already selected before.
472    * 
473    * @param path the path to add to the selection
474    */
475   public void addSelectionPath(TreePath path)
476   {
477     if (path != null)
478       {
479         TreePath[] add = new TreePath[]{ path };
480         addSelectionPaths(add);
481       }
482   }
483
484   /**
485    * Adds the paths to the list of selected paths. This method checks if the
486    * paths are already selected and doesn't add the same path twice. If this
487    * changes the selection the registered TreeSelectionListeners are notified.
488    * 
489    * @param paths the paths to add to the selection
490    */
491   public void addSelectionPaths(TreePath[] paths)
492   {
493     int length = paths != null ? paths.length : 0;
494     if (length > 0)
495       {
496         if (selectionMode == SINGLE_TREE_SELECTION)
497           setSelectionPaths(paths);
498         else if (selectionMode == CONTIGUOUS_TREE_SELECTION
499                  &&  ! canPathsBeAdded(paths))
500           {
501             if (arePathsContiguous(paths))
502               setSelectionPaths(paths);
503             else
504               setSelectionPaths(new TreePath[] { paths[0] });
505           }
506         else
507           {
508             Vector changedPaths = null;
509             tmpPaths.clear();
510             int validPaths = 0;
511             TreePath oldLeadPath = leadPath;
512             int oldPaths = 0;
513             if (selection != null)
514               oldPaths = selection.length;
515             int i;
516             for (i = 0; i < length; i++)
517               {
518                 if (paths[i] != null)
519                   {
520                     if (! selectedPaths.contains(paths[i]))
521                       {
522                         validPaths++;
523                         if (changedPaths == null)
524                           changedPaths = new Vector();
525                         changedPaths.add(new PathPlaceHolder(paths[i], true));
526                         selectedPaths.add(paths[i]);
527                         tmpPaths.add(paths[i]);
528                       }
529                     leadPath = paths[i];
530                   }
531               }
532             if (validPaths > 0)
533               {
534                 TreePath[] newSelection = new TreePath[oldPaths + validPaths];
535                 if (oldPaths > 0)
536                   System.arraycopy(selection, 0, newSelection, 0, oldPaths);
537                 if (validPaths != paths.length)
538                   {
539                     // Some of the paths are already selected, put together
540                     // the new selection carefully.
541                     Iterator newPaths = tmpPaths.iterator();
542                     i = oldPaths;
543                     while (newPaths.hasNext())
544                       {
545                         newSelection[i] = (TreePath) newPaths.next();
546                         i++;
547                       }
548                   }
549                 else
550                   System.arraycopy(paths, 0, newSelection, oldPaths,
551                                    validPaths);
552                 selection = newSelection;
553                 insureUniqueness();
554                 updateLeadIndex();
555                 resetRowSelection();
556                 if (changedPaths != null && changedPaths.size() > 0)
557                   notifyPathChange(changedPaths, oldLeadPath);
558               }
559             else
560               leadPath = oldLeadPath;
561             tmpPaths.clear();
562           }
563       }
564   }
565
566   /**
567    * Removes the path from the selection. If this changes the selection the
568    * registered TreeSelectionListeners are notified.
569    * 
570    * @param path the path to remove
571    */
572   public void removeSelectionPath(TreePath path)
573   {
574     if (path != null)
575       removeSelectionPaths(new TreePath[]{ path });
576   }
577
578   /**
579    * Removes the paths from the selection. If this changes the selection the
580    * registered TreeSelectionListeners are notified.
581    * 
582    * @param paths the paths to remove
583    */
584   public void removeSelectionPaths(TreePath[] paths)
585   {
586     if (paths != null && selection != null && paths.length > 0)
587       {
588         if (! canPathsBeRemoved(paths))
589           clearSelection();
590         else
591           {
592             Vector pathsToRemove = null;
593             for (int i = paths.length - 1; i >= 0; i--)
594               {
595                 if (paths[i] != null && selectedPaths.contains(paths[i]))
596                   {
597                     if (pathsToRemove == null)
598                       pathsToRemove = new Vector();
599                     selectedPaths.remove(paths[i]);
600                     pathsToRemove.add(new PathPlaceHolder(paths[i],
601                                                           false));
602                   }
603               }
604             if (pathsToRemove != null)
605               {
606                 int numRemove = pathsToRemove.size();
607                 TreePath oldLead = leadPath;
608                 if (numRemove == selection.length)
609                   selection = null;
610                 else
611                   {
612                     selection = new TreePath[selection.length - numRemove];
613                     Iterator keep = selectedPaths.iterator();
614                     for (int valid = 0; keep.hasNext(); valid++)
615                       selection[valid] = (TreePath) keep.next();
616                   }
617                 // Update lead path.
618                 if (leadPath != null && ! selectedPaths.contains(leadPath))
619                   {
620                     if (selection != null)
621                       leadPath = selection[selection.length - 1];
622                     else
623                       leadPath = null;
624                   }
625                 else if (selection != null)
626                   leadPath = selection[selection.length - 1];
627                 else
628                   leadPath = null;
629                 updateLeadIndex();
630                 resetRowSelection();
631                 notifyPathChange(pathsToRemove, oldLead);
632               }
633           }
634       }
635   }
636
637   /**
638    * Returns the first path in the selection. This is especially useful when the
639    * selectionMode is {@link #SINGLE_TREE_SELECTION}.
640    * 
641    * @return the first path in the selection
642    */
643   public TreePath getSelectionPath()
644   {
645     if ((selection == null) || (selection.length == 0))
646       return null;
647     else
648       return selection[0];
649   }
650
651   /**
652    * Returns the complete selection.
653    * 
654    * @return the complete selection
655    */
656   public TreePath[] getSelectionPaths()
657   {
658     return selection;
659   }
660
661   /**
662    * Returns the number of paths in the selection.
663    * 
664    * @return the number of paths in the selection
665    */
666   public int getSelectionCount()
667   {
668     if (selection == null)
669       return 0;
670     else
671       return selection.length;
672   }
673
674   /**
675    * Checks if a given path is in the selection.
676    * 
677    * @param path the path to check
678    * @return <code>true</code> if the path is in the selection,
679    *         <code>false</code> otherwise
680    */
681   public boolean isPathSelected(TreePath path)
682   {
683     if (selection == null)
684       return false;
685
686     for (int i = 0; i < selection.length; i++)
687       {
688         if (selection[i].equals(path))
689           return true;
690       }
691     return false;
692   }
693
694   /**
695    * Checks if the selection is empty.
696    * 
697    * @return <code>true</code> if the selection is empty, <code>false</code>
698    *         otherwise
699    */
700   public boolean isSelectionEmpty()
701   {
702     return (selection == null) || (selection.length == 0);
703   }
704
705   /**
706    * Removes all paths from the selection. Fire the unselection event.
707    */
708   public void clearSelection()
709   {
710     if (selection != null)
711       {
712         int selectionLength = selection.length;
713         boolean[] news = new boolean[selectionLength];
714         Arrays.fill(news, false);
715         TreeSelectionEvent event = new TreeSelectionEvent(this, selection,
716                                                           news, leadPath,
717                                                           null);
718         leadPath = null;
719         leadIndex = 0;
720         leadRow = 0;
721         selectedPaths.clear();
722         selection = null;
723         resetRowSelection();
724         fireValueChanged(event);
725       }
726   }
727
728   /**
729    * Adds a <code>TreeSelectionListener</code> object to this model.
730    * 
731    * @param listener the listener to add
732    */
733   public void addTreeSelectionListener(TreeSelectionListener listener)
734   {
735     listenerList.add(TreeSelectionListener.class, listener);
736   }
737
738   /**
739    * Removes a <code>TreeSelectionListener</code> object from this model.
740    * 
741    * @param listener the listener to remove
742    */
743   public void removeTreeSelectionListener(TreeSelectionListener listener)
744   {
745     listenerList.remove(TreeSelectionListener.class, listener);
746   }
747
748   /**
749    * Returns all <code>TreeSelectionListener</code> added to this model.
750    * 
751    * @return an array of listeners
752    * @since 1.4
753    */
754   public TreeSelectionListener[] getTreeSelectionListeners()
755   {
756     return (TreeSelectionListener[]) getListeners(TreeSelectionListener.class);
757   }
758
759   /**
760    * fireValueChanged
761    * 
762    * @param event the event to fire.
763    */
764   protected void fireValueChanged(TreeSelectionEvent event)
765   {
766     TreeSelectionListener[] listeners = getTreeSelectionListeners();
767
768     for (int i = 0; i < listeners.length; ++i)
769       listeners[i].valueChanged(event);
770   }
771
772   /**
773    * Returns all added listeners of a special type.
774    * 
775    * @param listenerType the listener type
776    * @return an array of listeners
777    * @since 1.3
778    */
779   public <T extends EventListener> T[] getListeners(Class<T> listenerType)
780   {
781     return listenerList.getListeners(listenerType);
782   }
783
784   /**
785    * Returns the currently selected rows.
786    * 
787    * @return the currently selected rows
788    */
789   public int[] getSelectionRows()
790   {
791     int[] rows = null;
792     if (rowMapper != null && selection != null)
793       {
794         rows = rowMapper.getRowsForPaths(selection);
795         if (rows != null)
796           {
797             // Find invisible rows.
798             int invisible = 0;
799             for (int i = rows.length - 1; i >= 0; i--)
800               {
801                 if (rows[i] == -1)
802                   invisible++;
803                 
804               }
805             // Clean up invisible rows.
806             if (invisible > 0)
807               {
808                 if (invisible == rows.length)
809                   rows = null;
810                 else
811                   {
812                     int[] newRows = new int[rows.length - invisible];
813                     int visCount = 0;
814                     for (int i = rows.length - 1; i >= 0; i--)
815                       {
816                         if (rows[i] != -1)
817                           {
818                             newRows[visCount] = rows[i];
819                             visCount++;
820                           }
821                       }
822                     rows = newRows;
823                   }
824               }
825           }
826       }
827     return rows;
828   }
829
830   /**
831    * Returns the smallest row index from the selection.
832    * 
833    * @return the smallest row index from the selection
834    */
835   public int getMinSelectionRow()
836   {
837     return listSelectionModel.getMinSelectionIndex();
838   }
839
840   /**
841    * Returns the largest row index from the selection.
842    * 
843    * @return the largest row index from the selection
844    */
845   public int getMaxSelectionRow()
846   {
847     return listSelectionModel.getMaxSelectionIndex();
848   }
849
850   /**
851    * Checks if a particular row is selected.
852    * 
853    * @param row the index of the row to check
854    * @return <code>true</code> if the row is in this selection,
855    *         <code>false</code> otherwise
856    * @throws NullPointerException if the row mapper is not set (can only happen
857    *           if the user has plugged in the custom incorrect TreeUI
858    *           implementation.
859    */
860   public boolean isRowSelected(int row)
861   {
862     return listSelectionModel.isSelectedIndex(row);
863   }
864
865   /**
866    * Updates the mappings from TreePaths to row indices.
867    */
868   public void resetRowSelection()
869   {
870     listSelectionModel.clearSelection();
871     if (selection != null && rowMapper != null)
872       {
873         int[] rows = rowMapper.getRowsForPaths(selection);
874         // Update list selection model.
875         for (int i = 0; i < rows.length; i++)
876           {
877             int row = rows[i];
878             if (row != -1)
879               listSelectionModel.addSelectionInterval(row, row);
880           }
881         // Update lead selection.
882         if (leadIndex != -1 && rows != null)
883           leadRow = rows[leadIndex];
884         else if (leadPath != null)
885           {
886             TreePath[] tmp = new TreePath[]{ leadPath };
887             rows = rowMapper.getRowsForPaths(tmp);
888             leadRow = rows != null ? rows[0] : -1;
889           }
890         else
891           leadRow = -1;
892         insureRowContinuity();
893       }
894     else
895       leadRow = -1;
896   }
897
898   /**
899    * getLeadSelectionRow
900    * 
901    * @return int
902    */
903   public int getLeadSelectionRow()
904   {
905     return leadRow;
906   }
907
908   /**
909    * getLeadSelectionPath
910    * 
911    * @return TreePath
912    */
913   public TreePath getLeadSelectionPath()
914   {
915     return leadPath;
916   }
917
918   /**
919    * Adds a <code>PropertyChangeListener</code> object to this model.
920    * 
921    * @param listener the listener to add.
922    */
923   public void addPropertyChangeListener(PropertyChangeListener listener)
924   {
925     if (changeSupport == null)
926       changeSupport = new SwingPropertyChangeSupport(this);
927     changeSupport.addPropertyChangeListener(listener);
928   }
929
930   /**
931    * Removes a <code>PropertyChangeListener</code> object from this model.
932    * 
933    * @param listener the listener to remove.
934    */
935   public void removePropertyChangeListener(PropertyChangeListener listener)
936   {
937     if (changeSupport != null)
938       changeSupport.removePropertyChangeListener(listener);
939   }
940
941   /**
942    * Returns all added <code>PropertyChangeListener</code> objects.
943    * 
944    * @return an array of listeners.
945    * @since 1.4
946    */
947   public PropertyChangeListener[] getPropertyChangeListeners()
948   {
949     PropertyChangeListener[] listeners = null;
950     if (changeSupport != null)
951       listeners = changeSupport.getPropertyChangeListeners();
952     else
953       listeners = new PropertyChangeListener[0];
954     return listeners;
955   }
956
957   /**
958    * Makes sure the currently selected paths are valid according to the current
959    * selectionMode. If the selectionMode is set to
960    * {@link #CONTIGUOUS_TREE_SELECTION} and the selection isn't contiguous then
961    * the selection is reset to the first set of contguous paths. If the
962    * selectionMode is set to {@link #SINGLE_TREE_SELECTION} and the selection
963    * has more than one path, the selection is reset to the contain only the
964    * first path.
965    */
966   protected void insureRowContinuity()
967   {
968     if (selectionMode == CONTIGUOUS_TREE_SELECTION && selection != null
969         && rowMapper != null)
970       {
971         int min = listSelectionModel.getMinSelectionIndex();
972         if (min != -1)
973           {
974             int max = listSelectionModel.getMaxSelectionIndex();
975             for (int i = min; i <= max; i++)
976               {
977                 if (! listSelectionModel.isSelectedIndex(i))
978                   {
979                     if (i == min)
980                       clearSelection();
981                     else
982                       {
983                         TreePath[] newSelection = new TreePath[i - min];
984                         int[] rows = rowMapper.getRowsForPaths(selection);
985                         for (int j = 0; j < rows.length; j++)
986                           {
987                             if (rows[j] < i)
988                               newSelection[rows[j] - min] = selection[j];
989                           }
990                         setSelectionPaths(newSelection);
991                         break;
992                       }
993                   }
994               }
995           }
996       }
997     else if (selectionMode == SINGLE_TREE_SELECTION && selection != null
998         && selection.length > 1)
999       setSelectionPath(selection[0]);
1000   }
1001   
1002   /**
1003    * Returns <code>true</code> if the paths are contiguous (take subsequent
1004    * rows in the diplayed tree view. The method returns <code>true</code> if
1005    * we have no RowMapper assigned.
1006    * 
1007    * @param paths the paths to check for continuity
1008    * @return <code>true</code> if the paths are contiguous or we have no
1009    *         RowMapper assigned
1010    */
1011   protected boolean arePathsContiguous(TreePath[] paths)
1012   {
1013     if (rowMapper == null || paths.length < 2)
1014       return true;
1015
1016     int length = paths.length;
1017     TreePath[] tmp = new TreePath[1];
1018     tmp[0] = paths[0];
1019     int min = rowMapper.getRowsForPaths(tmp)[0];
1020     BitSet selected = new BitSet();
1021     int valid = 0;
1022     for (int i = 0; i < length; i++)
1023       {
1024         if (paths[i] != null)
1025           {
1026             tmp[0] = paths[i];
1027             int[] rows = rowMapper.getRowsForPaths(tmp);
1028             if (rows == null)
1029               return false; // No row mapping yet, can't be selected.
1030             int row = rows[0];
1031             if (row == -1 || row < (min - length) || row > (min + length))
1032               return false; // Not contiguous.
1033             min = Math.min(min, row);
1034             if (! selected.get(row))
1035               {
1036                 selected.set(row);
1037                 valid++;
1038               }
1039             
1040           }
1041       }
1042     int max = valid + min;
1043     for (int i = min; i < max; i++)
1044       if (! selected.get(i))
1045         return false; // Not contiguous.
1046     return true;
1047   }
1048
1049   /**
1050    * Checks if the paths can be added. This returns <code>true</code> if:
1051    * <ul>
1052    * <li><code>paths</code> is <code>null</code> or empty</li>
1053    * <li>we have no RowMapper assigned</li>
1054    * <li>nothing is currently selected</li>
1055    * <li>selectionMode is {@link #DISCONTIGUOUS_TREE_SELECTION}</li>
1056    * <li>adding the paths to the selection still results in a contiguous set of
1057    * paths</li>
1058    * 
1059    * @param paths the paths to check
1060    * @return <code>true</code> if the paths can be added with respect to the
1061    *         selectionMode
1062    */
1063   protected boolean canPathsBeAdded(TreePath[] paths)
1064   {
1065     if (paths == null || paths.length == 0 || rowMapper == null
1066         || selection == null || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
1067       return true;
1068
1069     BitSet selected = new BitSet();
1070     int min = listSelectionModel.getMinSelectionIndex();
1071     int max = listSelectionModel.getMaxSelectionIndex();
1072     TreePath[] tmp = new TreePath[1];
1073     if (min != -1)
1074       {
1075         // Set the bitmask of selected elements.
1076         for (int i = min; i <= max; i++)
1077           selected.set(i);
1078       }
1079     else
1080       {
1081         tmp[0] = paths[0];
1082         min = rowMapper.getRowsForPaths(tmp)[0];
1083         max = min;
1084       }
1085     // Mark new paths as selected.
1086     for (int i = paths.length - 1; i >= 0; i--)
1087       {
1088         if (paths[i] != null)
1089           {
1090             tmp[0] = paths[i];
1091             int[] rows = rowMapper.getRowsForPaths(tmp);
1092             if (rows == null)
1093               return false; // Now row mapping yet, can't be selected.
1094             int row = rows[0];
1095             if (row == -1)
1096               return false; // Now row mapping yet, can't be selected.
1097             min = Math.min(min, row);
1098             max = Math.max(max, row);
1099             selected.set(row);
1100           }
1101       }
1102     // Now look if the new selection would be contiguous.
1103     for (int i = min; i <= max; i++)
1104       if (! selected.get(i))
1105         return false;
1106     return true;
1107   }
1108   
1109   /**
1110    * Checks if the paths can be removed without breaking the continuity of the
1111    * selection according to selectionMode.
1112    * 
1113    * @param paths the paths to check
1114    * @return <code>true</code> if the paths can be removed with respect to the
1115    *         selectionMode
1116    */
1117   protected boolean canPathsBeRemoved(TreePath[] paths)
1118   {
1119     if (rowMapper == null || isSelectionEmpty()
1120         || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
1121       return true;
1122     
1123     HashSet set = new HashSet();
1124     for (int i = 0; i < selection.length; i++)
1125       set.add(selection[i]);
1126     
1127     for (int i = 0; i < paths.length; i++)
1128       set.remove(paths[i]);
1129     
1130     TreePath[] remaining = new TreePath[set.size()];
1131     Iterator iter = set.iterator();
1132     
1133     for (int i = 0; i < remaining.length; i++)
1134       remaining[i] = (TreePath) iter.next();
1135     
1136     return arePathsContiguous(remaining);
1137   }
1138
1139   /**
1140    * Notify the installed listeners that the given patches have changed. This
1141    * method will call listeners if invoked, but it is not called from the
1142    * implementation of this class.
1143    * 
1144    * @param vPaths the vector of the changed patches
1145    * @param oldLeadSelection the old selection index
1146    */
1147   protected void notifyPathChange(Vector vPaths, TreePath oldLeadSelection)
1148   {
1149
1150     int numChangedPaths = vPaths.size();
1151     boolean[] news = new boolean[numChangedPaths];
1152     TreePath[] paths = new TreePath[numChangedPaths];
1153     for (int i = 0; i < numChangedPaths; i++)
1154       {
1155         PathPlaceHolder p = (PathPlaceHolder) vPaths.get(i);
1156         news[i] = p.isNew;
1157         paths[i] = p.path;
1158       }
1159
1160     TreeSelectionEvent event = new TreeSelectionEvent(this, paths, news,
1161                                                       oldLeadSelection,
1162                                                       leadPath);
1163     fireValueChanged(event);
1164   }
1165
1166   /**
1167    * Updates the lead selection row number after changing the lead selection
1168    * path.
1169    */
1170   protected void updateLeadIndex()
1171   {
1172     leadIndex = -1;
1173     if (leadPath != null)
1174       {
1175         leadRow = -1;
1176         if (selection == null)
1177           leadPath = null;
1178         else
1179           {
1180             for (int i = selection.length - 1; i >= 0 && leadIndex == -1; i--)
1181               {
1182                 if (selection[i] == leadPath)
1183                   leadIndex = i;
1184               }
1185           }
1186       }
1187   }
1188
1189   /**
1190    * This method exists due historical reasons and returns without action
1191    * (unless overridden). For compatibility with the applications that override
1192    * it, it is still called from the {@link #setSelectionPaths(TreePath[])} and
1193    * {@link #addSelectionPaths(TreePath[])}.
1194    */
1195   protected void insureUniqueness()
1196   {
1197     // Following the API 1.4, the method should return without action.
1198   }
1199 }