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