< prev index next >

src/java.desktop/share/classes/javax/swing/tree/DefaultTreeSelectionModel.java

Print this page




  29 import java.io.*;
  30 import java.util.ArrayList;
  31 import java.util.BitSet;
  32 import java.util.Enumeration;
  33 import java.util.EventListener;
  34 import java.util.Hashtable;
  35 import java.util.List;
  36 import java.util.Vector;
  37 import javax.swing.event.*;
  38 import javax.swing.DefaultListSelectionModel;
  39 
  40 /**
  41  * Default implementation of TreeSelectionModel.  Listeners are notified
  42  * whenever
  43  * the paths in the selection change, not the rows. In order
  44  * to be able to track row changes you may wish to become a listener
  45  * for expansion events on the tree and test for changes from there.
  46  * <p>resetRowSelection is called from any of the methods that update
  47  * the selected paths. If you subclass any of these methods to
  48  * filter what is allowed to be selected, be sure and message
  49  * <code>resetRowSelection</code> if you do not message super.
  50  *
  51  * <strong>Warning:</strong>
  52  * Serialized objects of this class will not be compatible with
  53  * future Swing releases. The current serialization support is
  54  * appropriate for short term storage or RMI between applications running
  55  * the same version of Swing.  As of 1.4, support for long term storage
  56  * of all JavaBeans&trade;
  57  * has been added to the <code>java.beans</code> package.
  58  * Please see {@link java.beans.XMLEncoder}.
  59  *
  60  * @see javax.swing.JTree
  61  *
  62  * @author Scott Violet
  63  */
  64 @SuppressWarnings("serial")
  65 public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel
  66 {
  67     /** Property name for selectionMode. */
  68     public static final String          SELECTION_MODE_PROPERTY = "selectionMode";
  69 
  70     /** Used to messaged registered listeners. */
  71     protected SwingPropertyChangeSupport     changeSupport;
  72 
  73     /** Paths that are currently selected.  Will be null if nothing is
  74       * currently selected. */
  75     protected TreePath[]                selection;
  76 
  77     /** Event listener list. */


  80     /** Provides a row for a given path. */
  81     protected transient RowMapper               rowMapper;
  82 
  83     /** Handles maintaining the list selection model. The RowMapper is used
  84      * to map from a TreePath to a row, and the value is then placed here. */
  85     protected DefaultListSelectionModel     listSelectionModel;
  86 
  87     /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
  88      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
  89      */
  90     protected int                           selectionMode;
  91 
  92     /** Last path that was added. */
  93     protected TreePath                      leadPath;
  94     /** Index of the lead path in selection. */
  95     protected int                           leadIndex;
  96     /** Lead row. */
  97     protected int                           leadRow;
  98 
  99     /** Used to make sure the paths are unique, will contain all the paths
 100      * in <code>selection</code>.
 101      */
 102     private Hashtable<TreePath, Boolean>    uniquePaths;
 103     private Hashtable<TreePath, Boolean>    lastPaths;
 104     private TreePath[]                      tempPaths;
 105 
 106 
 107     /**
 108      * Creates a new instance of DefaultTreeSelectionModel that is
 109      * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
 110      */
 111     public DefaultTreeSelectionModel() {
 112         listSelectionModel = new DefaultListSelectionModel();
 113         selectionMode = DISCONTIGUOUS_TREE_SELECTION;
 114         leadIndex = leadRow = -1;
 115         uniquePaths = new Hashtable<TreePath, Boolean>();
 116         lastPaths = new Hashtable<TreePath, Boolean>();
 117         tempPaths = new TreePath[1];
 118     }
 119 
 120     /**
 121      * Sets the RowMapper instance. This instance is used to determine
 122      * the row for a particular TreePath.
 123      */
 124     public void setRowMapper(RowMapper newMapper) {
 125         rowMapper = newMapper;
 126         resetRowSelection();
 127     }
 128 
 129     /**
 130      * Returns the RowMapper instance that is able to map a TreePath to a
 131      * row.
 132      */
 133     public RowMapper getRowMapper() {
 134         return rowMapper;
 135     }
 136 
 137     /**
 138      * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
 139      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
 140      * is not one of the defined value,
 141      * <code>DISCONTIGUOUS_TREE_SELECTION</code> is assumed.
 142      * <p>This may change the selection if the current selection is not valid
 143      * for the new mode. For example, if three TreePaths are
 144      * selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>,
 145      * only one TreePath will remain selected. It is up to the particular
 146      * implementation to decide what TreePath remains selected.
 147      * <p>
 148      * Setting the mode to something other than the defined types will
 149      * result in the mode becoming <code>DISCONTIGUOUS_TREE_SELECTION</code>.
 150      */
 151     public void setSelectionMode(int mode) {
 152         int            oldMode = selectionMode;
 153 
 154         selectionMode = validateSelectionMode(mode);
 155         if(oldMode != selectionMode && changeSupport != null)
 156             changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
 157                                              Integer.valueOf(oldMode),
 158                                              Integer.valueOf(selectionMode));
 159     }
 160 
 161     private static int validateSelectionMode(int mode) {
 162         return (mode != TreeSelectionModel.SINGLE_TREE_SELECTION
 163                 && mode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION
 164                 && mode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 165                 ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : mode;
 166     }
 167 
 168     /**
 169      * Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>,
 170      * <code>DISCONTIGUOUS_TREE_SELECTION</code> or
 171      * <code>CONTIGUOUS_TREE_SELECTION</code>.
 172      */
 173     public int getSelectionMode() {
 174         return selectionMode;
 175     }
 176 
 177     /**
 178       * Sets the selection to path. If this represents a change, then
 179       * the TreeSelectionListeners are notified. If <code>path</code> is
 180       * null, this has the same effect as invoking <code>clearSelection</code>.
 181       *
 182       * @param path new path to select
 183       */
 184     public void setSelectionPath(TreePath path) {
 185         if(path == null)
 186             setSelectionPaths(null);
 187         else {
 188             TreePath[]          newPaths = new TreePath[1];
 189 
 190             newPaths[0] = path;
 191             setSelectionPaths(newPaths);
 192         }
 193     }
 194 
 195     /**
 196      * Sets the selection. Whether the supplied paths are taken as the
 197      * new selection depends upon the selection mode. If the supplied
 198      * array is {@code null}, or empty, the selection is cleared. If
 199      * the selection mode is {@code SINGLE_TREE_SELECTION}, only the
 200      * first path in {@code pPaths} is used. If the selection


 284 
 285             uniquePaths = lastPaths;
 286             lastPaths = tempHT;
 287             lastPaths.clear();
 288 
 289             // No reason to do this now, but will still call it.
 290             insureUniqueness();
 291 
 292             updateLeadIndex();
 293 
 294             resetRowSelection();
 295             /* Notify of the change. */
 296             if(cPaths.size() > 0)
 297                 notifyPathChange(cPaths, beginLeadPath);
 298         }
 299     }
 300 
 301     /**
 302       * Adds path to the current selection. If path is not currently
 303       * in the selection the TreeSelectionListeners are notified. This has
 304       * no effect if <code>path</code> is null.
 305       *
 306       * @param path the new path to add to the current selection
 307       */
 308     public void addSelectionPath(TreePath path) {
 309         if(path != null) {
 310             TreePath[]            toAdd = new TreePath[1];
 311 
 312             toAdd[0] = path;
 313             addSelectionPaths(toAdd);
 314         }
 315     }
 316 
 317     /**
 318       * Adds paths to the current selection. If any of the paths in
 319       * paths are not currently in the selection the TreeSelectionListeners
 320       * are notified. This has
 321       * no effect if <code>paths</code> is null.
 322       * <p>The lead path is set to the last element in <code>paths</code>.
 323       * <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>,
 324       * and adding the new paths would make the selection discontiguous.
 325       * Then two things can result: if the TreePaths in <code>paths</code>
 326       * are contiguous, then the selection becomes these TreePaths,
 327       * otherwise the TreePaths aren't contiguous and the selection becomes
 328       * the first TreePath in <code>paths</code>.
 329       *
 330       * @param paths the new path to add to the current selection
 331       */
 332     public void addSelectionPaths(TreePath[] paths) {
 333         int       newPathLength = ((paths == null) ? 0 : paths.length);
 334 
 335         if(newPathLength > 0) {
 336             if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
 337                 setSelectionPaths(paths);
 338             }
 339             else if(selectionMode == TreeSelectionModel.
 340                     CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
 341                 if(arePathsContiguous(paths)) {
 342                     setSelectionPaths(paths);
 343                 }
 344                 else {
 345                     TreePath[]          newPaths = new TreePath[1];
 346 
 347                     newPaths[0] = paths[0];
 348                     setSelectionPaths(newPaths);


 407                     selection = newSelection;
 408 
 409                     insureUniqueness();
 410 
 411                     updateLeadIndex();
 412 
 413                     resetRowSelection();
 414 
 415                     notifyPathChange(cPaths, beginLeadPath);
 416                 }
 417                 else
 418                     leadPath = beginLeadPath;
 419                 lastPaths.clear();
 420             }
 421         }
 422     }
 423 
 424     /**
 425       * Removes path from the selection. If path is in the selection
 426       * The TreeSelectionListeners are notified. This has no effect if
 427       * <code>path</code> is null.
 428       *
 429       * @param path the path to remove from the selection
 430       */
 431     public void removeSelectionPath(TreePath path) {
 432         if(path != null) {
 433             TreePath[]             rPath = new TreePath[1];
 434 
 435             rPath[0] = path;
 436             removeSelectionPaths(rPath);
 437         }
 438     }
 439 
 440     /**
 441       * Removes paths from the selection.  If any of the paths in paths
 442       * are in the selection the TreeSelectionListeners are notified.
 443       * This has no effect if <code>paths</code> is null.
 444       *
 445       * @param paths the paths to remove from the selection
 446       */
 447     public void removeSelectionPaths(TreePath[] paths) {
 448         if (paths != null && selection != null && paths.length > 0) {
 449             if(!canPathsBeRemoved(paths)) {
 450                 /* Could probably do something more interesting here! */
 451                 clearSelection();
 452             }
 453             else {
 454                 Vector<PathPlaceHolder> pathsToRemove = null;
 455 
 456                 /* Find the paths that can be removed. */
 457                 for (int removeCounter = paths.length - 1; removeCounter >= 0;
 458                      removeCounter--) {
 459                     if(paths[removeCounter] != null) {
 460                         if (uniquePaths.get(paths[removeCounter]) != null) {
 461                             if(pathsToRemove == null)
 462                                 pathsToRemove = new Vector<PathPlaceHolder>(paths.length);
 463                             uniquePaths.remove(paths[removeCounter]);


 526       */
 527     public TreePath[] getSelectionPaths() {
 528         if(selection != null) {
 529             int                 pathSize = selection.length;
 530             TreePath[]          result = new TreePath[pathSize];
 531 
 532             System.arraycopy(selection, 0, result, 0, pathSize);
 533             return result;
 534         }
 535         return new TreePath[0];
 536     }
 537 
 538     /**
 539      * Returns the number of paths that are selected.
 540      */
 541     public int getSelectionCount() {
 542         return (selection == null) ? 0 : selection.length;
 543     }
 544 
 545     /**
 546       * Returns true if the path, <code>path</code>,
 547       * is in the current selection.
 548       */
 549     public boolean isPathSelected(TreePath path) {
 550         return (path != null) ? (uniquePaths.get(path) != null) : false;
 551     }
 552 
 553     /**
 554       * Returns true if the selection is currently empty.
 555       */
 556     public boolean isSelectionEmpty() {
 557         return (selection == null || selection.length == 0);
 558     }
 559 
 560     /**
 561       * Empties the current selection.  If this represents a change in the
 562       * current selection, the selection listeners are notified.
 563       */
 564     public void clearSelection() {
 565         if (selection != null && selection.length > 0) {
 566             int                    selSize = selection.length;


 588       * @param x the new listener to be added
 589       */
 590     public void addTreeSelectionListener(TreeSelectionListener x) {
 591         listenerList.add(TreeSelectionListener.class, x);
 592     }
 593 
 594     /**
 595       * Removes x from the list of listeners that are notified each time
 596       * the set of selected TreePaths changes.
 597       *
 598       * @param x the listener to remove
 599       */
 600     public void removeTreeSelectionListener(TreeSelectionListener x) {
 601         listenerList.remove(TreeSelectionListener.class, x);
 602     }
 603 
 604     /**
 605      * Returns an array of all the tree selection listeners
 606      * registered on this model.
 607      *
 608      * @return all of this model's <code>TreeSelectionListener</code>s
 609      *         or an empty
 610      *         array if no tree selection listeners are currently registered
 611      *
 612      * @see #addTreeSelectionListener
 613      * @see #removeTreeSelectionListener
 614      *
 615      * @since 1.4
 616      */
 617     public TreeSelectionListener[] getTreeSelectionListeners() {
 618         return listenerList.getListeners(TreeSelectionListener.class);
 619     }
 620 
 621     /**
 622      * Notifies all listeners that are registered for
 623      * tree selection events on this object.
 624      *
 625      * @param e the event that characterizes the change
 626      *
 627      * @see #addTreeSelectionListener
 628      * @see EventListenerList


 635         // those that are interested in this event
 636         for (int i = listeners.length-2; i>=0; i-=2) {
 637             if (listeners[i]==TreeSelectionListener.class) {
 638                 // Lazily create the event:
 639                 // if (e == null)
 640                 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
 641                 ((TreeSelectionListener)listeners[i+1]).valueChanged(e);
 642             }
 643         }
 644     }
 645 
 646     /**
 647      * Returns an array of all the objects currently registered
 648      * as <code><em>Foo</em>Listener</code>s
 649      * upon this model.
 650      * <code><em>Foo</em>Listener</code>s are registered using the
 651      * <code>add<em>Foo</em>Listener</code> method.
 652      *
 653      * <p>
 654      *
 655      * You can specify the <code>listenerType</code> argument
 656      * with a class literal,
 657      * such as
 658      * <code><em>Foo</em>Listener.class</code>.
 659      * For example, you can query a
 660      * <code>DefaultTreeSelectionModel</code> <code>m</code>
 661      * for its tree selection listeners with the following code:
 662      *
 663      * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
 664      *
 665      * If no such listeners exist, this method returns an empty array.
 666      *
 667      * @param <T> the listener type
 668      * @param listenerType the type of listeners requested
 669      * @return an array of all objects registered as
 670      *          <code><em>Foo</em>Listener</code>s on this component,
 671      *          or an empty array if no such
 672      *          listeners have been added
 673      * @exception ClassCastException if <code>listenerType</code>
 674      *          doesn't specify a class or interface that implements
 675      *          <code>java.util.EventListener</code>
 676      *
 677      * @see #getTreeSelectionListeners
 678      * @see #getPropertyChangeListeners
 679      *
 680      * @since 1.3
 681      */
 682     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
 683         return listenerList.getListeners(listenerType);
 684     }
 685 
 686     /**
 687      * Returns the selection in terms of rows. There is not
 688      * necessarily a one-to-one mapping between the {@code TreePath}s
 689      * returned from {@code getSelectionPaths} and this method. In
 690      * particular, if a {@code TreePath} is not viewable (the {@code
 691      * RowMapper} returns {@code -1} for the row corresponding to the
 692      * {@code TreePath}), then the corresponding row is not included
 693      * in the returned array. For example, if the selection consists
 694      * of two paths, {@code A} and {@code B}, with {@code A} at row
 695      * {@code 10}, and {@code B} not currently viewable, then this method


 735 
 736     /**
 737      * Returns the smallest value obtained from the RowMapper for the
 738      * current set of selected TreePaths. If nothing is selected,
 739      * or there is no RowMapper, this will return -1.
 740       */
 741     public int getMinSelectionRow() {
 742         return listSelectionModel.getMinSelectionIndex();
 743     }
 744 
 745     /**
 746      * Returns the largest value obtained from the RowMapper for the
 747      * current set of selected TreePaths. If nothing is selected,
 748      * or there is no RowMapper, this will return -1.
 749       */
 750     public int getMaxSelectionRow() {
 751         return listSelectionModel.getMaxSelectionIndex();
 752     }
 753 
 754     /**
 755       * Returns true if the row identified by <code>row</code> is selected.
 756       */
 757     public boolean isRowSelected(int row) {
 758         return listSelectionModel.isSelectedIndex(row);
 759     }
 760 
 761     /**
 762      * Updates this object's mapping from TreePath to rows. This should
 763      * be invoked when the mapping from TreePaths to integers has changed
 764      * (for example, a node has been expanded).
 765      * <p>You do not normally have to call this, JTree and its associated
 766      * Listeners will invoke this for you. If you are implementing your own
 767      * View class, then you will have to invoke this.
 768      * <p>This will invoke <code>insureRowContinuity</code> to make sure
 769      * the currently selected TreePaths are still valid based on the
 770      * selection mode.
 771      */
 772     public void resetRowSelection() {
 773         listSelectionModel.clearSelection();
 774         if(selection != null && rowMapper != null) {
 775             int               aRow;
 776             int               validCount = 0;
 777             int[]             rows = rowMapper.getRowsForPaths(selection);
 778 
 779             for(int counter = 0, maxCounter = selection.length;
 780                 counter < maxCounter; counter++) {
 781                 aRow = rows[counter];
 782                 if(aRow != -1) {
 783                     listSelectionModel.addSelectionInterval(aRow, aRow);
 784                 }
 785             }
 786             if(leadIndex != -1 && rows != null) {
 787                 leadRow = rows[leadIndex];
 788             }


 836     }
 837 
 838     /**
 839      * Removes a PropertyChangeListener from the listener list.
 840      * This removes a PropertyChangeListener that was registered
 841      * for all properties.
 842      *
 843      * @param listener  the PropertyChangeListener to be removed
 844      */
 845 
 846     public synchronized void removePropertyChangeListener(
 847                                 PropertyChangeListener listener) {
 848         if (changeSupport == null) {
 849             return;
 850         }
 851         changeSupport.removePropertyChangeListener(listener);
 852     }
 853 
 854     /**
 855      * Returns an array of all the property change listeners
 856      * registered on this <code>DefaultTreeSelectionModel</code>.
 857      *
 858      * @return all of this model's <code>PropertyChangeListener</code>s
 859      *         or an empty
 860      *         array if no property change listeners are currently registered
 861      *
 862      * @see #addPropertyChangeListener
 863      * @see #removePropertyChangeListener
 864      *
 865      * @since 1.4
 866      */
 867     public PropertyChangeListener[] getPropertyChangeListeners() {
 868         if (changeSupport == null) {
 869             return new PropertyChangeListener[0];
 870         }
 871         return changeSupport.getPropertyChangeListeners();
 872     }
 873 
 874     /**
 875      * Makes sure the currently selected <code>TreePath</code>s are valid
 876      * for the current selection mode.
 877      * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>
 878      * and a <code>RowMapper</code> exists, this will make sure all
 879      * the rows are contiguous, that is, when sorted all the rows are
 880      * in order with no gaps.
 881      * If the selection isn't contiguous, the selection is
 882      * reset to contain the first set, when sorted, of contiguous rows.
 883      * <p>
 884      * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and
 885      * more than one TreePath is selected, the selection is reset to
 886      * contain the first path currently selected.
 887      */
 888     protected void insureRowContinuity() {
 889         if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
 890            selection != null && rowMapper != null) {
 891             DefaultListSelectionModel lModel = listSelectionModel;
 892             int                       min = lModel.getMinSelectionIndex();
 893 
 894             if(min != -1) {
 895                 for(int counter = min,
 896                         maxCounter = lModel.getMaxSelectionIndex();
 897                         counter <= maxCounter; counter++) {
 898                     if(!lModel.isSelectedIndex(counter)) {
 899                         if(counter == min) {
 900                             clearSelection();
 901                         }
 902                         else {
 903                             TreePath[] newSel = new TreePath[counter - min];
 904                             int selectionIndex[] = rowMapper.getRowsForPaths(selection);


 953                        anIndex > (min + pathCount))
 954                         return false;
 955                     if(anIndex < min)
 956                         min = anIndex;
 957                     if(!bitSet.get(anIndex)) {
 958                         bitSet.set(anIndex);
 959                         validCount++;
 960                     }
 961                 }
 962             }
 963             int          maxCounter = validCount + min;
 964 
 965             for(counter = min; counter < maxCounter; counter++)
 966                 if(!bitSet.get(counter))
 967                     return false;
 968         }
 969         return true;
 970     }
 971 
 972     /**
 973      * Used to test if a particular set of <code>TreePath</code>s can
 974      * be added. This will return true if <code>paths</code> is null (or
 975      * empty), or this object has no RowMapper, or nothing is currently selected,
 976      * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or
 977      * adding the paths to the current selection still results in a
 978      * contiguous set of <code>TreePath</code>s.
 979      *
 980      * @param paths array of {@code TreePaths} to check
 981      * @return      whether the particular set of {@code TreePaths} can be added
 982      */
 983     protected boolean canPathsBeAdded(TreePath[] paths) {
 984         if(paths == null || paths.length == 0 || rowMapper == null ||
 985            selection == null || selectionMode ==
 986            TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 987             return true;
 988         else {
 989             BitSet                       bitSet = new BitSet();
 990             DefaultListSelectionModel    lModel = listSelectionModel;
 991             int                          anIndex;
 992             int                          counter;
 993             int                          min = lModel.getMinSelectionIndex();
 994             int                          max = lModel.getMaxSelectionIndex();
 995             TreePath[]                   tempPath = new TreePath[1];
 996 
 997             if(min != -1) {
 998                 for(counter = min; counter <= max; counter++) {




  29 import java.io.*;
  30 import java.util.ArrayList;
  31 import java.util.BitSet;
  32 import java.util.Enumeration;
  33 import java.util.EventListener;
  34 import java.util.Hashtable;
  35 import java.util.List;
  36 import java.util.Vector;
  37 import javax.swing.event.*;
  38 import javax.swing.DefaultListSelectionModel;
  39 
  40 /**
  41  * Default implementation of TreeSelectionModel.  Listeners are notified
  42  * whenever
  43  * the paths in the selection change, not the rows. In order
  44  * to be able to track row changes you may wish to become a listener
  45  * for expansion events on the tree and test for changes from there.
  46  * <p>resetRowSelection is called from any of the methods that update
  47  * the selected paths. If you subclass any of these methods to
  48  * filter what is allowed to be selected, be sure and message
  49  * {@code resetRowSelection} if you do not message super.
  50  *
  51  * <strong>Warning:</strong>
  52  * Serialized objects of this class will not be compatible with
  53  * future Swing releases. The current serialization support is
  54  * appropriate for short term storage or RMI between applications running
  55  * the same version of Swing.  As of 1.4, support for long term storage
  56  * of all JavaBeans&trade;
  57  * has been added to the {@code java.beans} package.
  58  * Please see {@link java.beans.XMLEncoder}.
  59  *
  60  * @see javax.swing.JTree
  61  *
  62  * @author Scott Violet
  63  */
  64 @SuppressWarnings("serial")
  65 public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel
  66 {
  67     /** Property name for selectionMode. */
  68     public static final String          SELECTION_MODE_PROPERTY = "selectionMode";
  69 
  70     /** Used to messaged registered listeners. */
  71     protected SwingPropertyChangeSupport     changeSupport;
  72 
  73     /** Paths that are currently selected.  Will be null if nothing is
  74       * currently selected. */
  75     protected TreePath[]                selection;
  76 
  77     /** Event listener list. */


  80     /** Provides a row for a given path. */
  81     protected transient RowMapper               rowMapper;
  82 
  83     /** Handles maintaining the list selection model. The RowMapper is used
  84      * to map from a TreePath to a row, and the value is then placed here. */
  85     protected DefaultListSelectionModel     listSelectionModel;
  86 
  87     /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
  88      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
  89      */
  90     protected int                           selectionMode;
  91 
  92     /** Last path that was added. */
  93     protected TreePath                      leadPath;
  94     /** Index of the lead path in selection. */
  95     protected int                           leadIndex;
  96     /** Lead row. */
  97     protected int                           leadRow;
  98 
  99     /** Used to make sure the paths are unique, will contain all the paths
 100      * in {@code selection}.
 101      */
 102     private Hashtable<TreePath, Boolean>    uniquePaths;
 103     private Hashtable<TreePath, Boolean>    lastPaths;
 104     private TreePath[]                      tempPaths;
 105 
 106 
 107     /**
 108      * Creates a new instance of DefaultTreeSelectionModel that is
 109      * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
 110      */
 111     public DefaultTreeSelectionModel() {
 112         listSelectionModel = new DefaultListSelectionModel();
 113         selectionMode = DISCONTIGUOUS_TREE_SELECTION;
 114         leadIndex = leadRow = -1;
 115         uniquePaths = new Hashtable<TreePath, Boolean>();
 116         lastPaths = new Hashtable<TreePath, Boolean>();
 117         tempPaths = new TreePath[1];
 118     }
 119 
 120     /**
 121      * Sets the RowMapper instance. This instance is used to determine
 122      * the row for a particular TreePath.
 123      */
 124     public void setRowMapper(RowMapper newMapper) {
 125         rowMapper = newMapper;
 126         resetRowSelection();
 127     }
 128 
 129     /**
 130      * Returns the RowMapper instance that is able to map a TreePath to a
 131      * row.
 132      */
 133     public RowMapper getRowMapper() {
 134         return rowMapper;
 135     }
 136 
 137     /**
 138      * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
 139      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
 140      * is not one of the defined value,
 141      * {@code DISCONTIGUOUS_TREE_SELECTION} is assumed.
 142      * <p>This may change the selection if the current selection is not valid
 143      * for the new mode. For example, if three TreePaths are
 144      * selected when the mode is changed to {@code SINGLE_TREE_SELECTION},
 145      * only one TreePath will remain selected. It is up to the particular
 146      * implementation to decide what TreePath remains selected.
 147      * <p>
 148      * Setting the mode to something other than the defined types will
 149      * result in the mode becoming {@code DISCONTIGUOUS_TREE_SELECTION}.
 150      */
 151     public void setSelectionMode(int mode) {
 152         int            oldMode = selectionMode;
 153 
 154         selectionMode = validateSelectionMode(mode);
 155         if(oldMode != selectionMode && changeSupport != null)
 156             changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
 157                                              Integer.valueOf(oldMode),
 158                                              Integer.valueOf(selectionMode));
 159     }
 160 
 161     private static int validateSelectionMode(int mode) {
 162         return (mode != TreeSelectionModel.SINGLE_TREE_SELECTION
 163                 && mode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION
 164                 && mode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 165                 ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : mode;
 166     }
 167 
 168     /**
 169      * Returns the selection mode, one of {@code SINGLE_TREE_SELECTION},
 170      * {@code DISCONTIGUOUS_TREE_SELECTION} or
 171      * {@code CONTIGUOUS_TREE_SELECTION}.
 172      */
 173     public int getSelectionMode() {
 174         return selectionMode;
 175     }
 176 
 177     /**
 178       * Sets the selection to path. If this represents a change, then
 179       * the TreeSelectionListeners are notified. If {@code path} is
 180       * null, this has the same effect as invoking {@code clearSelection}.
 181       *
 182       * @param path new path to select
 183       */
 184     public void setSelectionPath(TreePath path) {
 185         if(path == null)
 186             setSelectionPaths(null);
 187         else {
 188             TreePath[]          newPaths = new TreePath[1];
 189 
 190             newPaths[0] = path;
 191             setSelectionPaths(newPaths);
 192         }
 193     }
 194 
 195     /**
 196      * Sets the selection. Whether the supplied paths are taken as the
 197      * new selection depends upon the selection mode. If the supplied
 198      * array is {@code null}, or empty, the selection is cleared. If
 199      * the selection mode is {@code SINGLE_TREE_SELECTION}, only the
 200      * first path in {@code pPaths} is used. If the selection


 284 
 285             uniquePaths = lastPaths;
 286             lastPaths = tempHT;
 287             lastPaths.clear();
 288 
 289             // No reason to do this now, but will still call it.
 290             insureUniqueness();
 291 
 292             updateLeadIndex();
 293 
 294             resetRowSelection();
 295             /* Notify of the change. */
 296             if(cPaths.size() > 0)
 297                 notifyPathChange(cPaths, beginLeadPath);
 298         }
 299     }
 300 
 301     /**
 302       * Adds path to the current selection. If path is not currently
 303       * in the selection the TreeSelectionListeners are notified. This has
 304       * no effect if {@code path} is null.
 305       *
 306       * @param path the new path to add to the current selection
 307       */
 308     public void addSelectionPath(TreePath path) {
 309         if(path != null) {
 310             TreePath[]            toAdd = new TreePath[1];
 311 
 312             toAdd[0] = path;
 313             addSelectionPaths(toAdd);
 314         }
 315     }
 316 
 317     /**
 318       * Adds paths to the current selection. If any of the paths in
 319       * paths are not currently in the selection the TreeSelectionListeners
 320       * are notified. This has
 321       * no effect if {@code paths} is null.
 322       * <p>The lead path is set to the last element in {@code paths}.
 323       * <p>If the selection mode is {@code CONTIGUOUS_TREE_SELECTION},
 324       * and adding the new paths would make the selection discontiguous.
 325       * Then two things can result: if the TreePaths in {@code paths}
 326       * are contiguous, then the selection becomes these TreePaths,
 327       * otherwise the TreePaths aren't contiguous and the selection becomes
 328       * the first TreePath in {@code paths}.
 329       *
 330       * @param paths the new path to add to the current selection
 331       */
 332     public void addSelectionPaths(TreePath[] paths) {
 333         int       newPathLength = ((paths == null) ? 0 : paths.length);
 334 
 335         if(newPathLength > 0) {
 336             if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
 337                 setSelectionPaths(paths);
 338             }
 339             else if(selectionMode == TreeSelectionModel.
 340                     CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
 341                 if(arePathsContiguous(paths)) {
 342                     setSelectionPaths(paths);
 343                 }
 344                 else {
 345                     TreePath[]          newPaths = new TreePath[1];
 346 
 347                     newPaths[0] = paths[0];
 348                     setSelectionPaths(newPaths);


 407                     selection = newSelection;
 408 
 409                     insureUniqueness();
 410 
 411                     updateLeadIndex();
 412 
 413                     resetRowSelection();
 414 
 415                     notifyPathChange(cPaths, beginLeadPath);
 416                 }
 417                 else
 418                     leadPath = beginLeadPath;
 419                 lastPaths.clear();
 420             }
 421         }
 422     }
 423 
 424     /**
 425       * Removes path from the selection. If path is in the selection
 426       * The TreeSelectionListeners are notified. This has no effect if
 427       * {@code path} is null.
 428       *
 429       * @param path the path to remove from the selection
 430       */
 431     public void removeSelectionPath(TreePath path) {
 432         if(path != null) {
 433             TreePath[]             rPath = new TreePath[1];
 434 
 435             rPath[0] = path;
 436             removeSelectionPaths(rPath);
 437         }
 438     }
 439 
 440     /**
 441       * Removes paths from the selection.  If any of the paths in paths
 442       * are in the selection the TreeSelectionListeners are notified.
 443       * This has no effect if {@code paths} is null.
 444       *
 445       * @param paths the paths to remove from the selection
 446       */
 447     public void removeSelectionPaths(TreePath[] paths) {
 448         if (paths != null && selection != null && paths.length > 0) {
 449             if(!canPathsBeRemoved(paths)) {
 450                 /* Could probably do something more interesting here! */
 451                 clearSelection();
 452             }
 453             else {
 454                 Vector<PathPlaceHolder> pathsToRemove = null;
 455 
 456                 /* Find the paths that can be removed. */
 457                 for (int removeCounter = paths.length - 1; removeCounter >= 0;
 458                      removeCounter--) {
 459                     if(paths[removeCounter] != null) {
 460                         if (uniquePaths.get(paths[removeCounter]) != null) {
 461                             if(pathsToRemove == null)
 462                                 pathsToRemove = new Vector<PathPlaceHolder>(paths.length);
 463                             uniquePaths.remove(paths[removeCounter]);


 526       */
 527     public TreePath[] getSelectionPaths() {
 528         if(selection != null) {
 529             int                 pathSize = selection.length;
 530             TreePath[]          result = new TreePath[pathSize];
 531 
 532             System.arraycopy(selection, 0, result, 0, pathSize);
 533             return result;
 534         }
 535         return new TreePath[0];
 536     }
 537 
 538     /**
 539      * Returns the number of paths that are selected.
 540      */
 541     public int getSelectionCount() {
 542         return (selection == null) ? 0 : selection.length;
 543     }
 544 
 545     /**
 546       * Returns true if the path, {@code path},
 547       * is in the current selection.
 548       */
 549     public boolean isPathSelected(TreePath path) {
 550         return (path != null) ? (uniquePaths.get(path) != null) : false;
 551     }
 552 
 553     /**
 554       * Returns true if the selection is currently empty.
 555       */
 556     public boolean isSelectionEmpty() {
 557         return (selection == null || selection.length == 0);
 558     }
 559 
 560     /**
 561       * Empties the current selection.  If this represents a change in the
 562       * current selection, the selection listeners are notified.
 563       */
 564     public void clearSelection() {
 565         if (selection != null && selection.length > 0) {
 566             int                    selSize = selection.length;


 588       * @param x the new listener to be added
 589       */
 590     public void addTreeSelectionListener(TreeSelectionListener x) {
 591         listenerList.add(TreeSelectionListener.class, x);
 592     }
 593 
 594     /**
 595       * Removes x from the list of listeners that are notified each time
 596       * the set of selected TreePaths changes.
 597       *
 598       * @param x the listener to remove
 599       */
 600     public void removeTreeSelectionListener(TreeSelectionListener x) {
 601         listenerList.remove(TreeSelectionListener.class, x);
 602     }
 603 
 604     /**
 605      * Returns an array of all the tree selection listeners
 606      * registered on this model.
 607      *
 608      * @return all of this model's {@code TreeSelectionListener}s
 609      *         or an empty
 610      *         array if no tree selection listeners are currently registered
 611      *
 612      * @see #addTreeSelectionListener
 613      * @see #removeTreeSelectionListener
 614      *
 615      * @since 1.4
 616      */
 617     public TreeSelectionListener[] getTreeSelectionListeners() {
 618         return listenerList.getListeners(TreeSelectionListener.class);
 619     }
 620 
 621     /**
 622      * Notifies all listeners that are registered for
 623      * tree selection events on this object.
 624      *
 625      * @param e the event that characterizes the change
 626      *
 627      * @see #addTreeSelectionListener
 628      * @see EventListenerList


 635         // those that are interested in this event
 636         for (int i = listeners.length-2; i>=0; i-=2) {
 637             if (listeners[i]==TreeSelectionListener.class) {
 638                 // Lazily create the event:
 639                 // if (e == null)
 640                 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
 641                 ((TreeSelectionListener)listeners[i+1]).valueChanged(e);
 642             }
 643         }
 644     }
 645 
 646     /**
 647      * Returns an array of all the objects currently registered
 648      * as <code><em>Foo</em>Listener</code>s
 649      * upon this model.
 650      * <code><em>Foo</em>Listener</code>s are registered using the
 651      * <code>add<em>Foo</em>Listener</code> method.
 652      *
 653      * <p>
 654      *
 655      * You can specify the {@code listenerType} argument
 656      * with a class literal,
 657      * such as
 658      * <code><em>Foo</em>Listener.class</code>.
 659      * For example, you can query a
 660      * {@code DefaultTreeSelectionModel m}
 661      * for its tree selection listeners with the following code:
 662      *
 663      * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
 664      *
 665      * If no such listeners exist, this method returns an empty array.
 666      *
 667      * @param <T> the listener type
 668      * @param listenerType the type of listeners requested
 669      * @return an array of all objects registered as
 670      *          <code><em>Foo</em>Listener</code>s on this component,
 671      *          or an empty array if no such
 672      *          listeners have been added
 673      * @exception ClassCastException if {@code listenerType}
 674      *          doesn't specify a class or interface that implements
 675      *          {@code java.util.EventListener}
 676      *
 677      * @see #getTreeSelectionListeners
 678      * @see #getPropertyChangeListeners
 679      *
 680      * @since 1.3
 681      */
 682     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
 683         return listenerList.getListeners(listenerType);
 684     }
 685 
 686     /**
 687      * Returns the selection in terms of rows. There is not
 688      * necessarily a one-to-one mapping between the {@code TreePath}s
 689      * returned from {@code getSelectionPaths} and this method. In
 690      * particular, if a {@code TreePath} is not viewable (the {@code
 691      * RowMapper} returns {@code -1} for the row corresponding to the
 692      * {@code TreePath}), then the corresponding row is not included
 693      * in the returned array. For example, if the selection consists
 694      * of two paths, {@code A} and {@code B}, with {@code A} at row
 695      * {@code 10}, and {@code B} not currently viewable, then this method


 735 
 736     /**
 737      * Returns the smallest value obtained from the RowMapper for the
 738      * current set of selected TreePaths. If nothing is selected,
 739      * or there is no RowMapper, this will return -1.
 740       */
 741     public int getMinSelectionRow() {
 742         return listSelectionModel.getMinSelectionIndex();
 743     }
 744 
 745     /**
 746      * Returns the largest value obtained from the RowMapper for the
 747      * current set of selected TreePaths. If nothing is selected,
 748      * or there is no RowMapper, this will return -1.
 749       */
 750     public int getMaxSelectionRow() {
 751         return listSelectionModel.getMaxSelectionIndex();
 752     }
 753 
 754     /**
 755       * Returns true if the row identified by {@code row} is selected.
 756       */
 757     public boolean isRowSelected(int row) {
 758         return listSelectionModel.isSelectedIndex(row);
 759     }
 760 
 761     /**
 762      * Updates this object's mapping from TreePath to rows. This should
 763      * be invoked when the mapping from TreePaths to integers has changed
 764      * (for example, a node has been expanded).
 765      * <p>You do not normally have to call this, JTree and its associated
 766      * Listeners will invoke this for you. If you are implementing your own
 767      * View class, then you will have to invoke this.
 768      * <p>This will invoke {@code insureRowContinuity} to make sure
 769      * the currently selected TreePaths are still valid based on the
 770      * selection mode.
 771      */
 772     public void resetRowSelection() {
 773         listSelectionModel.clearSelection();
 774         if(selection != null && rowMapper != null) {
 775             int               aRow;
 776             int               validCount = 0;
 777             int[]             rows = rowMapper.getRowsForPaths(selection);
 778 
 779             for(int counter = 0, maxCounter = selection.length;
 780                 counter < maxCounter; counter++) {
 781                 aRow = rows[counter];
 782                 if(aRow != -1) {
 783                     listSelectionModel.addSelectionInterval(aRow, aRow);
 784                 }
 785             }
 786             if(leadIndex != -1 && rows != null) {
 787                 leadRow = rows[leadIndex];
 788             }


 836     }
 837 
 838     /**
 839      * Removes a PropertyChangeListener from the listener list.
 840      * This removes a PropertyChangeListener that was registered
 841      * for all properties.
 842      *
 843      * @param listener  the PropertyChangeListener to be removed
 844      */
 845 
 846     public synchronized void removePropertyChangeListener(
 847                                 PropertyChangeListener listener) {
 848         if (changeSupport == null) {
 849             return;
 850         }
 851         changeSupport.removePropertyChangeListener(listener);
 852     }
 853 
 854     /**
 855      * Returns an array of all the property change listeners
 856      * registered on this {@code DefaultTreeSelectionModel}.
 857      *
 858      * @return all of this model's {@code PropertyChangeListener}s
 859      *         or an empty
 860      *         array if no property change listeners are currently registered
 861      *
 862      * @see #addPropertyChangeListener
 863      * @see #removePropertyChangeListener
 864      *
 865      * @since 1.4
 866      */
 867     public PropertyChangeListener[] getPropertyChangeListeners() {
 868         if (changeSupport == null) {
 869             return new PropertyChangeListener[0];
 870         }
 871         return changeSupport.getPropertyChangeListeners();
 872     }
 873 
 874     /**
 875      * Makes sure the currently selected {@code TreePath}s are valid
 876      * for the current selection mode.
 877      * If the selection mode is {@code CONTIGUOUS_TREE_SELECTION}
 878      * and a {@code RowMapper} exists, this will make sure all
 879      * the rows are contiguous, that is, when sorted all the rows are
 880      * in order with no gaps.
 881      * If the selection isn't contiguous, the selection is
 882      * reset to contain the first set, when sorted, of contiguous rows.
 883      * <p>
 884      * If the selection mode is {@code SINGLE_TREE_SELECTION} and
 885      * more than one TreePath is selected, the selection is reset to
 886      * contain the first path currently selected.
 887      */
 888     protected void insureRowContinuity() {
 889         if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
 890            selection != null && rowMapper != null) {
 891             DefaultListSelectionModel lModel = listSelectionModel;
 892             int                       min = lModel.getMinSelectionIndex();
 893 
 894             if(min != -1) {
 895                 for(int counter = min,
 896                         maxCounter = lModel.getMaxSelectionIndex();
 897                         counter <= maxCounter; counter++) {
 898                     if(!lModel.isSelectedIndex(counter)) {
 899                         if(counter == min) {
 900                             clearSelection();
 901                         }
 902                         else {
 903                             TreePath[] newSel = new TreePath[counter - min];
 904                             int selectionIndex[] = rowMapper.getRowsForPaths(selection);


 953                        anIndex > (min + pathCount))
 954                         return false;
 955                     if(anIndex < min)
 956                         min = anIndex;
 957                     if(!bitSet.get(anIndex)) {
 958                         bitSet.set(anIndex);
 959                         validCount++;
 960                     }
 961                 }
 962             }
 963             int          maxCounter = validCount + min;
 964 
 965             for(counter = min; counter < maxCounter; counter++)
 966                 if(!bitSet.get(counter))
 967                     return false;
 968         }
 969         return true;
 970     }
 971 
 972     /**
 973      * Used to test if a particular set of {@code TreePath}s can
 974      * be added. This will return true if {@code paths} is null (or
 975      * empty), or this object has no RowMapper, or nothing is currently selected,
 976      * or the selection mode is {@code DISCONTIGUOUS_TREE_SELECTION}, or
 977      * adding the paths to the current selection still results in a
 978      * contiguous set of {@code TreePath}s.
 979      *
 980      * @param paths array of {@code TreePaths} to check
 981      * @return      whether the particular set of {@code TreePaths} can be added
 982      */
 983     protected boolean canPathsBeAdded(TreePath[] paths) {
 984         if(paths == null || paths.length == 0 || rowMapper == null ||
 985            selection == null || selectionMode ==
 986            TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 987             return true;
 988         else {
 989             BitSet                       bitSet = new BitSet();
 990             DefaultListSelectionModel    lModel = listSelectionModel;
 991             int                          anIndex;
 992             int                          counter;
 993             int                          min = lModel.getMinSelectionIndex();
 994             int                          max = lModel.getMaxSelectionIndex();
 995             TreePath[]                   tempPath = new TreePath[1];
 996 
 997             if(min != -1) {
 998                 for(counter = min; counter <= max; counter++) {


< prev index next >