Home » openjdk-7 » javax » swing » tree » [javadoc | source]

    1   /*
    2    * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   
   26   package javax.swing.tree;
   27   
   28   import java.beans.PropertyChangeListener;
   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<sup><font size="-2">TM</font></sup>
   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   public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel
   65   {
   66       /** Property name for selectionMode. */
   67       public static final String          SELECTION_MODE_PROPERTY = "selectionMode";
   68   
   69       /** Used to messaged registered listeners. */
   70       protected SwingPropertyChangeSupport     changeSupport;
   71   
   72       /** Paths that are currently selected.  Will be null if nothing is
   73         * currently selected. */
   74       protected TreePath[]                selection;
   75   
   76       /** Event listener list. */
   77       protected EventListenerList   listenerList = new EventListenerList();
   78   
   79       /** Provides a row for a given path. */
   80       transient protected RowMapper               rowMapper;
   81   
   82       /** Handles maintaining the list selection model. The RowMapper is used
   83        * to map from a TreePath to a row, and the value is then placed here. */
   84       protected DefaultListSelectionModel     listSelectionModel;
   85   
   86       /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
   87        * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
   88        */
   89       protected int                           selectionMode;
   90   
   91       /** Last path that was added. */
   92       protected TreePath                      leadPath;
   93       /** Index of the lead path in selection. */
   94       protected int                           leadIndex;
   95       /** Lead row. */
   96       protected int                           leadRow;
   97   
   98       /** Used to make sure the paths are unique, will contain all the paths
   99        * in <code>selection</code>.
  100        */
  101       private Hashtable<TreePath, Boolean>    uniquePaths;
  102       private Hashtable<TreePath, Boolean>    lastPaths;
  103       private TreePath[]                      tempPaths;
  104   
  105   
  106       /**
  107        * Creates a new instance of DefaultTreeSelectionModel that is
  108        * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
  109        */
  110       public DefaultTreeSelectionModel() {
  111           listSelectionModel = new DefaultListSelectionModel();
  112           selectionMode = DISCONTIGUOUS_TREE_SELECTION;
  113           leadIndex = leadRow = -1;
  114           uniquePaths = new Hashtable<TreePath, Boolean>();
  115           lastPaths = new Hashtable<TreePath, Boolean>();
  116           tempPaths = new TreePath[1];
  117       }
  118   
  119       /**
  120        * Sets the RowMapper instance. This instance is used to determine
  121        * the row for a particular TreePath.
  122        */
  123       public void setRowMapper(RowMapper newMapper) {
  124           rowMapper = newMapper;
  125           resetRowSelection();
  126       }
  127   
  128       /**
  129        * Returns the RowMapper instance that is able to map a TreePath to a
  130        * row.
  131        */
  132       public RowMapper getRowMapper() {
  133           return rowMapper;
  134       }
  135   
  136       /**
  137        * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
  138        * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
  139        * is not one of the defined value,
  140        * <code>DISCONTIGUOUS_TREE_SELECTION</code> is assumed.
  141        * <p>This may change the selection if the current selection is not valid
  142        * for the new mode. For example, if three TreePaths are
  143        * selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>,
  144        * only one TreePath will remain selected. It is up to the particular
  145        * implementation to decide what TreePath remains selected.
  146        * <p>
  147        * Setting the mode to something other than the defined types will
  148        * result in the mode becoming <code>DISCONTIGUOUS_TREE_SELECTION</code>.
  149        */
  150       public void setSelectionMode(int mode) {
  151           int            oldMode = selectionMode;
  152   
  153           selectionMode = mode;
  154           if(selectionMode != TreeSelectionModel.SINGLE_TREE_SELECTION &&
  155              selectionMode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
  156              selectionMode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
  157               selectionMode = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
  158           if(oldMode != selectionMode && changeSupport != null)
  159               changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
  160                                                Integer.valueOf(oldMode),
  161                                                Integer.valueOf(selectionMode));
  162       }
  163   
  164       /**
  165        * Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>,
  166        * <code>DISCONTIGUOUS_TREE_SELECTION</code> or
  167        * <code>CONTIGUOUS_TREE_SELECTION</code>.
  168        */
  169       public int getSelectionMode() {
  170           return selectionMode;
  171       }
  172   
  173       /**
  174         * Sets the selection to path. If this represents a change, then
  175         * the TreeSelectionListeners are notified. If <code>path</code> is
  176         * null, this has the same effect as invoking <code>clearSelection</code>.
  177         *
  178         * @param path new path to select
  179         */
  180       public void setSelectionPath(TreePath path) {
  181           if(path == null)
  182               setSelectionPaths(null);
  183           else {
  184               TreePath[]          newPaths = new TreePath[1];
  185   
  186               newPaths[0] = path;
  187               setSelectionPaths(newPaths);
  188           }
  189       }
  190   
  191       /**
  192        * Sets the selection. Whether the supplied paths are taken as the
  193        * new selection depends upon the selection mode. If the supplied
  194        * array is {@code null}, or empty, the selection is cleared. If
  195        * the selection mode is {@code SINGLE_TREE_SELECTION}, only the
  196        * first path in {@code pPaths} is used. If the selection
  197        * mode is {@code CONTIGUOUS_TREE_SELECTION} and the supplied paths
  198        * are not contiguous, then only the first path in {@code pPaths} is
  199        * used. If the selection mode is
  200        * {@code DISCONTIGUOUS_TREE_SELECTION}, then all paths are used.
  201        * <p>
  202        * All {@code null} paths in {@code pPaths} are ignored.
  203        * <p>
  204        * If this represents a change, all registered {@code
  205        * TreeSelectionListener}s are notified.
  206        * <p>
  207        * The lead path is set to the last unique path.
  208        * <p>
  209        * The paths returned from {@code getSelectionPaths} are in the same
  210        * order as those supplied to this method.
  211        *
  212        * @param pPaths the new selection
  213        */
  214       public void setSelectionPaths(TreePath[] pPaths) {
  215           int            newCount, newCounter, oldCount, oldCounter;
  216           TreePath[]     paths = pPaths;
  217   
  218           if(paths == null)
  219               newCount = 0;
  220           else
  221               newCount = paths.length;
  222           if(selection == null)
  223               oldCount = 0;
  224           else
  225               oldCount = selection.length;
  226           if((newCount + oldCount) != 0) {
  227               if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
  228                   /* If single selection and more than one path, only allow
  229                      first. */
  230                   if(newCount > 1) {
  231                       paths = new TreePath[1];
  232                       paths[0] = pPaths[0];
  233                       newCount = 1;
  234                   }
  235               }
  236               else if(selectionMode ==
  237                       TreeSelectionModel.CONTIGUOUS_TREE_SELECTION) {
  238                   /* If contiguous selection and paths aren't contiguous,
  239                      only select the first path item. */
  240                   if(newCount > 0 && !arePathsContiguous(paths)) {
  241                       paths = new TreePath[1];
  242                       paths[0] = pPaths[0];
  243                       newCount = 1;
  244                   }
  245               }
  246   
  247               TreePath         beginLeadPath = leadPath;
  248               Vector<PathPlaceHolder> cPaths = new Vector<PathPlaceHolder>(newCount + oldCount);
  249               List<TreePath> newSelectionAsList =
  250                       new ArrayList<TreePath>(newCount);
  251   
  252               lastPaths.clear();
  253               leadPath = null;
  254               /* Find the paths that are new. */
  255               for(newCounter = 0; newCounter < newCount; newCounter++) {
  256                   TreePath path = paths[newCounter];
  257                   if (path != null && lastPaths.get(path) == null) {
  258                       lastPaths.put(path, Boolean.TRUE);
  259                       if (uniquePaths.get(path) == null) {
  260                           cPaths.addElement(new PathPlaceHolder(path, true));
  261                       }
  262                       leadPath = path;
  263                       newSelectionAsList.add(path);
  264                   }
  265               }
  266   
  267               TreePath[] newSelection = newSelectionAsList.toArray(
  268                       new TreePath[newSelectionAsList.size()]);
  269   
  270               /* Get the paths that were selected but no longer selected. */
  271               for(oldCounter = 0; oldCounter < oldCount; oldCounter++)
  272                   if(selection[oldCounter] != null &&
  273                       lastPaths.get(selection[oldCounter]) == null)
  274                       cPaths.addElement(new PathPlaceHolder
  275                                         (selection[oldCounter], false));
  276   
  277               selection = newSelection;
  278   
  279               Hashtable<TreePath, Boolean>  tempHT = uniquePaths;
  280   
  281               uniquePaths = lastPaths;
  282               lastPaths = tempHT;
  283               lastPaths.clear();
  284   
  285               // No reason to do this now, but will still call it.
  286               insureUniqueness();
  287   
  288               updateLeadIndex();
  289   
  290               resetRowSelection();
  291               /* Notify of the change. */
  292               if(cPaths.size() > 0)
  293                   notifyPathChange(cPaths, beginLeadPath);
  294           }
  295       }
  296   
  297       /**
  298         * Adds path to the current selection. If path is not currently
  299         * in the selection the TreeSelectionListeners are notified. This has
  300         * no effect if <code>path</code> is null.
  301         *
  302         * @param path the new path to add to the current selection
  303         */
  304       public void addSelectionPath(TreePath path) {
  305           if(path != null) {
  306               TreePath[]            toAdd = new TreePath[1];
  307   
  308               toAdd[0] = path;
  309               addSelectionPaths(toAdd);
  310           }
  311       }
  312   
  313       /**
  314         * Adds paths to the current selection. If any of the paths in
  315         * paths are not currently in the selection the TreeSelectionListeners
  316         * are notified. This has
  317         * no effect if <code>paths</code> is null.
  318         * <p>The lead path is set to the last element in <code>paths</code>.
  319         * <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>,
  320         * and adding the new paths would make the selection discontiguous.
  321         * Then two things can result: if the TreePaths in <code>paths</code>
  322         * are contiguous, then the selection becomes these TreePaths,
  323         * otherwise the TreePaths aren't contiguous and the selection becomes
  324         * the first TreePath in <code>paths</code>.
  325         *
  326         * @param paths the new path to add to the current selection
  327         */
  328       public void addSelectionPaths(TreePath[] paths) {
  329           int       newPathLength = ((paths == null) ? 0 : paths.length);
  330   
  331           if(newPathLength > 0) {
  332               if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
  333                   setSelectionPaths(paths);
  334               }
  335               else if(selectionMode == TreeSelectionModel.
  336                       CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
  337                   if(arePathsContiguous(paths)) {
  338                       setSelectionPaths(paths);
  339                   }
  340                   else {
  341                       TreePath[]          newPaths = new TreePath[1];
  342   
  343                       newPaths[0] = paths[0];
  344                       setSelectionPaths(newPaths);
  345                   }
  346               }
  347               else {
  348                   int               counter, validCount;
  349                   int               oldCount;
  350                   TreePath          beginLeadPath = leadPath;
  351                   Vector<PathPlaceHolder>  cPaths = null;
  352   
  353                   if(selection == null)
  354                       oldCount = 0;
  355                   else
  356                       oldCount = selection.length;
  357                   /* Determine the paths that aren't currently in the
  358                      selection. */
  359                   lastPaths.clear();
  360                   for(counter = 0, validCount = 0; counter < newPathLength;
  361                       counter++) {
  362                       if(paths[counter] != null) {
  363                           if (uniquePaths.get(paths[counter]) == null) {
  364                               validCount++;
  365                               if(cPaths == null)
  366                                   cPaths = new Vector<PathPlaceHolder>();
  367                               cPaths.addElement(new PathPlaceHolder
  368                                                 (paths[counter], true));
  369                               uniquePaths.put(paths[counter], Boolean.TRUE);
  370                               lastPaths.put(paths[counter], Boolean.TRUE);
  371                           }
  372                           leadPath = paths[counter];
  373                       }
  374                   }
  375   
  376                   if(leadPath == null) {
  377                       leadPath = beginLeadPath;
  378                   }
  379   
  380                   if(validCount > 0) {
  381                       TreePath         newSelection[] = new TreePath[oldCount +
  382                                                                     validCount];
  383   
  384                       /* And build the new selection. */
  385                       if(oldCount > 0)
  386                           System.arraycopy(selection, 0, newSelection, 0,
  387                                            oldCount);
  388                       if(validCount != paths.length) {
  389                           /* Some of the paths in paths are already in
  390                              the selection. */
  391                           Enumeration<TreePath> newPaths = lastPaths.keys();
  392   
  393                           counter = oldCount;
  394                           while (newPaths.hasMoreElements()) {
  395                               newSelection[counter++] = newPaths.nextElement();
  396                           }
  397                       }
  398                       else {
  399                           System.arraycopy(paths, 0, newSelection, oldCount,
  400                                            validCount);
  401                       }
  402   
  403                       selection = newSelection;
  404   
  405                       insureUniqueness();
  406   
  407                       updateLeadIndex();
  408   
  409                       resetRowSelection();
  410   
  411                       notifyPathChange(cPaths, beginLeadPath);
  412                   }
  413                   else
  414                       leadPath = beginLeadPath;
  415                   lastPaths.clear();
  416               }
  417           }
  418       }
  419   
  420       /**
  421         * Removes path from the selection. If path is in the selection
  422         * The TreeSelectionListeners are notified. This has no effect if
  423         * <code>path</code> is null.
  424         *
  425         * @param path the path to remove from the selection
  426         */
  427       public void removeSelectionPath(TreePath path) {
  428           if(path != null) {
  429               TreePath[]             rPath = new TreePath[1];
  430   
  431               rPath[0] = path;
  432               removeSelectionPaths(rPath);
  433           }
  434       }
  435   
  436       /**
  437         * Removes paths from the selection.  If any of the paths in paths
  438         * are in the selection the TreeSelectionListeners are notified.
  439         * This has no effect if <code>paths</code> is null.
  440         *
  441         * @param paths the paths to remove from the selection
  442         */
  443       public void removeSelectionPaths(TreePath[] paths) {
  444           if (paths != null && selection != null && paths.length > 0) {
  445               if(!canPathsBeRemoved(paths)) {
  446                   /* Could probably do something more interesting here! */
  447                   clearSelection();
  448               }
  449               else {
  450                   Vector<PathPlaceHolder> pathsToRemove = null;
  451   
  452                   /* Find the paths that can be removed. */
  453                   for (int removeCounter = paths.length - 1; removeCounter >= 0;
  454                        removeCounter--) {
  455                       if(paths[removeCounter] != null) {
  456                           if (uniquePaths.get(paths[removeCounter]) != null) {
  457                               if(pathsToRemove == null)
  458                                   pathsToRemove = new Vector<PathPlaceHolder>(paths.length);
  459                               uniquePaths.remove(paths[removeCounter]);
  460                               pathsToRemove.addElement(new PathPlaceHolder
  461                                            (paths[removeCounter], false));
  462                           }
  463                       }
  464                   }
  465                   if(pathsToRemove != null) {
  466                       int         removeCount = pathsToRemove.size();
  467                       TreePath    beginLeadPath = leadPath;
  468   
  469                       if(removeCount == selection.length) {
  470                           selection = null;
  471                       }
  472                       else {
  473                           Enumeration<TreePath> pEnum = uniquePaths.keys();
  474                           int                  validCount = 0;
  475   
  476                           selection = new TreePath[selection.length -
  477                                                   removeCount];
  478                           while (pEnum.hasMoreElements()) {
  479                               selection[validCount++] = pEnum.nextElement();
  480                           }
  481                       }
  482                       if (leadPath != null &&
  483                           uniquePaths.get(leadPath) == null) {
  484                           if (selection != null) {
  485                               leadPath = selection[selection.length - 1];
  486                           }
  487                           else {
  488                               leadPath = null;
  489                           }
  490                       }
  491                       else if (selection != null) {
  492                           leadPath = selection[selection.length - 1];
  493                       }
  494                       else {
  495                           leadPath = null;
  496                       }
  497                       updateLeadIndex();
  498   
  499                       resetRowSelection();
  500   
  501                       notifyPathChange(pathsToRemove, beginLeadPath);
  502                   }
  503               }
  504           }
  505       }
  506   
  507       /**
  508         * Returns the first path in the selection. This is useful if there
  509         * if only one item currently selected.
  510         */
  511       public TreePath getSelectionPath() {
  512           if (selection != null && selection.length > 0) {
  513               return selection[0];
  514           }
  515           return null;
  516       }
  517   
  518       /**
  519         * Returns the selection.
  520         *
  521         * @return the selection
  522         */
  523       public TreePath[] getSelectionPaths() {
  524           if(selection != null) {
  525               int                 pathSize = selection.length;
  526               TreePath[]          result = new TreePath[pathSize];
  527   
  528               System.arraycopy(selection, 0, result, 0, pathSize);
  529               return result;
  530           }
  531           return new TreePath[0];
  532       }
  533   
  534       /**
  535        * Returns the number of paths that are selected.
  536        */
  537       public int getSelectionCount() {
  538           return (selection == null) ? 0 : selection.length;
  539       }
  540   
  541       /**
  542         * Returns true if the path, <code>path</code>,
  543         * is in the current selection.
  544         */
  545       public boolean isPathSelected(TreePath path) {
  546           return (path != null) ? (uniquePaths.get(path) != null) : false;
  547       }
  548   
  549       /**
  550         * Returns true if the selection is currently empty.
  551         */
  552       public boolean isSelectionEmpty() {
  553           return (selection == null || selection.length == 0);
  554       }
  555   
  556       /**
  557         * Empties the current selection.  If this represents a change in the
  558         * current selection, the selection listeners are notified.
  559         */
  560       public void clearSelection() {
  561           if (selection != null && selection.length > 0) {
  562               int                    selSize = selection.length;
  563               boolean[]              newness = new boolean[selSize];
  564   
  565               for(int counter = 0; counter < selSize; counter++)
  566                   newness[counter] = false;
  567   
  568               TreeSelectionEvent     event = new TreeSelectionEvent
  569                   (this, selection, newness, leadPath, null);
  570   
  571               leadPath = null;
  572               leadIndex = leadRow = -1;
  573               uniquePaths.clear();
  574               selection = null;
  575               resetRowSelection();
  576               fireValueChanged(event);
  577           }
  578       }
  579   
  580       /**
  581         * Adds x to the list of listeners that are notified each time the
  582         * set of selected TreePaths changes.
  583         *
  584         * @param x the new listener to be added
  585         */
  586       public void addTreeSelectionListener(TreeSelectionListener x) {
  587           listenerList.add(TreeSelectionListener.class, x);
  588       }
  589   
  590       /**
  591         * Removes x from the list of listeners that are notified each time
  592         * the set of selected TreePaths changes.
  593         *
  594         * @param x the listener to remove
  595         */
  596       public void removeTreeSelectionListener(TreeSelectionListener x) {
  597           listenerList.remove(TreeSelectionListener.class, x);
  598       }
  599   
  600       /**
  601        * Returns an array of all the tree selection listeners
  602        * registered on this model.
  603        *
  604        * @return all of this model's <code>TreeSelectionListener</code>s
  605        *         or an empty
  606        *         array if no tree selection listeners are currently registered
  607        *
  608        * @see #addTreeSelectionListener
  609        * @see #removeTreeSelectionListener
  610        *
  611        * @since 1.4
  612        */
  613       public TreeSelectionListener[] getTreeSelectionListeners() {
  614           return listenerList.getListeners(TreeSelectionListener.class);
  615       }
  616   
  617       /**
  618        * Notifies all listeners that are registered for
  619        * tree selection events on this object.
  620        * @see #addTreeSelectionListener
  621        * @see EventListenerList
  622        */
  623       protected void fireValueChanged(TreeSelectionEvent e) {
  624           // Guaranteed to return a non-null array
  625           Object[] listeners = listenerList.getListenerList();
  626           // TreeSelectionEvent e = null;
  627           // Process the listeners last to first, notifying
  628           // those that are interested in this event
  629           for (int i = listeners.length-2; i>=0; i-=2) {
  630               if (listeners[i]==TreeSelectionListener.class) {
  631                   // Lazily create the event:
  632                   // if (e == null)
  633                   // e = new ListSelectionEvent(this, firstIndex, lastIndex);
  634                   ((TreeSelectionListener)listeners[i+1]).valueChanged(e);
  635               }
  636           }
  637       }
  638   
  639       /**
  640        * Returns an array of all the objects currently registered
  641        * as <code><em>Foo</em>Listener</code>s
  642        * upon this model.
  643        * <code><em>Foo</em>Listener</code>s are registered using the
  644        * <code>add<em>Foo</em>Listener</code> method.
  645        *
  646        * <p>
  647        *
  648        * You can specify the <code>listenerType</code> argument
  649        * with a class literal,
  650        * such as
  651        * <code><em>Foo</em>Listener.class</code>.
  652        * For example, you can query a
  653        * <code>DefaultTreeSelectionModel</code> <code>m</code>
  654        * for its tree selection listeners with the following code:
  655        *
  656        * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
  657        *
  658        * If no such listeners exist, this method returns an empty array.
  659        *
  660        * @param listenerType the type of listeners requested; this parameter
  661        *          should specify an interface that descends from
  662        *          <code>java.util.EventListener</code>
  663        * @return an array of all objects registered as
  664        *          <code><em>Foo</em>Listener</code>s on this component,
  665        *          or an empty array if no such
  666        *          listeners have been added
  667        * @exception ClassCastException if <code>listenerType</code>
  668        *          doesn't specify a class or interface that implements
  669        *          <code>java.util.EventListener</code>
  670        *
  671        * @see #getTreeSelectionListeners
  672        * @see #getPropertyChangeListeners
  673        *
  674        * @since 1.3
  675        */
  676       public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
  677           return listenerList.getListeners(listenerType);
  678       }
  679   
  680       /**
  681        * Returns the selection in terms of rows. There is not
  682        * necessarily a one-to-one mapping between the {@code TreePath}s
  683        * returned from {@code getSelectionPaths} and this method. In
  684        * particular, if a {@code TreePath} is not viewable (the {@code
  685        * RowMapper} returns {@code -1} for the row corresponding to the
  686        * {@code TreePath}), then the corresponding row is not included
  687        * in the returned array. For example, if the selection consists
  688        * of two paths, {@code A} and {@code B}, with {@code A} at row
  689        * {@code 10}, and {@code B} not currently viewable, then this method
  690        * returns an array with the single entry {@code 10}.
  691        *
  692        * @return the selection in terms of rows
  693        */
  694       public int[] getSelectionRows() {
  695           // This is currently rather expensive.  Needs
  696           // to be better support from ListSelectionModel to speed this up.
  697           if (rowMapper != null && selection != null && selection.length > 0) {
  698               int[]      rows = rowMapper.getRowsForPaths(selection);
  699   
  700               if (rows != null) {
  701                   int       invisCount = 0;
  702   
  703                   for (int counter = rows.length - 1; counter >= 0; counter--) {
  704                       if (rows[counter] == -1) {
  705                           invisCount++;
  706                       }
  707                   }
  708                   if (invisCount > 0) {
  709                       if (invisCount == rows.length) {
  710                           rows = null;
  711                       }
  712                       else {
  713                           int[]    tempRows = new int[rows.length - invisCount];
  714   
  715                           for (int counter = rows.length - 1, visCounter = 0;
  716                                counter >= 0; counter--) {
  717                               if (rows[counter] != -1) {
  718                                   tempRows[visCounter++] = rows[counter];
  719                               }
  720                           }
  721                           rows = tempRows;
  722                       }
  723                   }
  724               }
  725               return rows;
  726           }
  727           return new int[0];
  728       }
  729   
  730       /**
  731        * Returns the smallest value obtained from the RowMapper for the
  732        * current set of selected TreePaths. If nothing is selected,
  733        * or there is no RowMapper, this will return -1.
  734         */
  735       public int getMinSelectionRow() {
  736           return listSelectionModel.getMinSelectionIndex();
  737       }
  738   
  739       /**
  740        * Returns the largest value obtained from the RowMapper for the
  741        * current set of selected TreePaths. If nothing is selected,
  742        * or there is no RowMapper, this will return -1.
  743         */
  744       public int getMaxSelectionRow() {
  745           return listSelectionModel.getMaxSelectionIndex();
  746       }
  747   
  748       /**
  749         * Returns true if the row identified by <code>row</code> is selected.
  750         */
  751       public boolean isRowSelected(int row) {
  752           return listSelectionModel.isSelectedIndex(row);
  753       }
  754   
  755       /**
  756        * Updates this object's mapping from TreePath to rows. This should
  757        * be invoked when the mapping from TreePaths to integers has changed
  758        * (for example, a node has been expanded).
  759        * <p>You do not normally have to call this, JTree and its associated
  760        * Listeners will invoke this for you. If you are implementing your own
  761        * View class, then you will have to invoke this.
  762        * <p>This will invoke <code>insureRowContinuity</code> to make sure
  763        * the currently selected TreePaths are still valid based on the
  764        * selection mode.
  765        */
  766       public void resetRowSelection() {
  767           listSelectionModel.clearSelection();
  768           if(selection != null && rowMapper != null) {
  769               int               aRow;
  770               int               validCount = 0;
  771               int[]             rows = rowMapper.getRowsForPaths(selection);
  772   
  773               for(int counter = 0, maxCounter = selection.length;
  774                   counter < maxCounter; counter++) {
  775                   aRow = rows[counter];
  776                   if(aRow != -1) {
  777                       listSelectionModel.addSelectionInterval(aRow, aRow);
  778                   }
  779               }
  780               if(leadIndex != -1 && rows != null) {
  781                   leadRow = rows[leadIndex];
  782               }
  783               else if (leadPath != null) {
  784                   // Lead selection path doesn't have to be in the selection.
  785                   tempPaths[0] = leadPath;
  786                   rows = rowMapper.getRowsForPaths(tempPaths);
  787                   leadRow = (rows != null) ? rows[0] : -1;
  788               }
  789               else {
  790                   leadRow = -1;
  791               }
  792               insureRowContinuity();
  793   
  794           }
  795           else
  796               leadRow = -1;
  797       }
  798   
  799       /**
  800        * Returns the lead selection index. That is the last index that was
  801        * added.
  802        */
  803       public int getLeadSelectionRow() {
  804           return leadRow;
  805       }
  806   
  807       /**
  808        * Returns the last path that was added. This may differ from the
  809        * leadSelectionPath property maintained by the JTree.
  810        */
  811       public TreePath getLeadSelectionPath() {
  812           return leadPath;
  813       }
  814   
  815       /**
  816        * Adds a PropertyChangeListener to the listener list.
  817        * The listener is registered for all properties.
  818        * <p>
  819        * A PropertyChangeEvent will get fired when the selection mode
  820        * changes.
  821        *
  822        * @param listener  the PropertyChangeListener to be added
  823        */
  824       public synchronized void addPropertyChangeListener(
  825                                   PropertyChangeListener listener) {
  826           if (changeSupport == null) {
  827               changeSupport = new SwingPropertyChangeSupport(this);
  828           }
  829           changeSupport.addPropertyChangeListener(listener);
  830       }
  831   
  832       /**
  833        * Removes a PropertyChangeListener from the listener list.
  834        * This removes a PropertyChangeListener that was registered
  835        * for all properties.
  836        *
  837        * @param listener  the PropertyChangeListener to be removed
  838        */
  839   
  840       public synchronized void removePropertyChangeListener(
  841                                   PropertyChangeListener listener) {
  842           if (changeSupport == null) {
  843               return;
  844           }
  845           changeSupport.removePropertyChangeListener(listener);
  846       }
  847   
  848       /**
  849        * Returns an array of all the property change listeners
  850        * registered on this <code>DefaultTreeSelectionModel</code>.
  851        *
  852        * @return all of this model's <code>PropertyChangeListener</code>s
  853        *         or an empty
  854        *         array if no property change listeners are currently registered
  855        *
  856        * @see #addPropertyChangeListener
  857        * @see #removePropertyChangeListener
  858        *
  859        * @since 1.4
  860        */
  861       public PropertyChangeListener[] getPropertyChangeListeners() {
  862           if (changeSupport == null) {
  863               return new PropertyChangeListener[0];
  864           }
  865           return changeSupport.getPropertyChangeListeners();
  866       }
  867   
  868       /**
  869        * Makes sure the currently selected <code>TreePath</code>s are valid
  870        * for the current selection mode.
  871        * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>
  872        * and a <code>RowMapper</code> exists, this will make sure all
  873        * the rows are contiguous, that is, when sorted all the rows are
  874        * in order with no gaps.
  875        * If the selection isn't contiguous, the selection is
  876        * reset to contain the first set, when sorted, of contiguous rows.
  877        * <p>
  878        * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and
  879        * more than one TreePath is selected, the selection is reset to
  880        * contain the first path currently selected.
  881        */
  882       protected void insureRowContinuity() {
  883           if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
  884              selection != null && rowMapper != null) {
  885               DefaultListSelectionModel lModel = listSelectionModel;
  886               int                       min = lModel.getMinSelectionIndex();
  887   
  888               if(min != -1) {
  889                   for(int counter = min,
  890                           maxCounter = lModel.getMaxSelectionIndex();
  891                           counter <= maxCounter; counter++) {
  892                       if(!lModel.isSelectedIndex(counter)) {
  893                           if(counter == min) {
  894                               clearSelection();
  895                           }
  896                           else {
  897                               TreePath[] newSel = new TreePath[counter - min];
  898                               int selectionIndex[] = rowMapper.getRowsForPaths(selection);
  899                               // find the actual selection pathes corresponded to the
  900                               // rows of the new selection
  901                               for (int i = 0; i < selectionIndex.length; i++) {
  902                                   if (selectionIndex[i]<counter) {
  903                                       newSel[selectionIndex[i]-min] = selection[i];
  904                                   }
  905                               }
  906                               setSelectionPaths(newSel);
  907                               break;
  908                           }
  909                       }
  910                   }
  911               }
  912           }
  913           else if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION &&
  914                   selection != null && selection.length > 1) {
  915               setSelectionPath(selection[0]);
  916           }
  917       }
  918   
  919       /**
  920        * Returns true if the paths are contiguous,
  921        * or this object has no RowMapper.
  922        */
  923       protected boolean arePathsContiguous(TreePath[] paths) {
  924           if(rowMapper == null || paths.length < 2)
  925               return true;
  926           else {
  927               BitSet                             bitSet = new BitSet(32);
  928               int                                anIndex, counter, min;
  929               int                                pathCount = paths.length;
  930               int                                validCount = 0;
  931               TreePath[]                         tempPath = new TreePath[1];
  932   
  933               tempPath[0] = paths[0];
  934               min = rowMapper.getRowsForPaths(tempPath)[0];
  935               for(counter = 0; counter < pathCount; counter++) {
  936                   if(paths[counter] != null) {
  937                       tempPath[0] = paths[counter];
  938                       int[] rows = rowMapper.getRowsForPaths(tempPath);
  939                       if (rows == null) {
  940                           return false;
  941                       }
  942                       anIndex = rows[0];
  943                       if(anIndex == -1 || anIndex < (min - pathCount) ||
  944                          anIndex > (min + pathCount))
  945                           return false;
  946                       if(anIndex < min)
  947                           min = anIndex;
  948                       if(!bitSet.get(anIndex)) {
  949                           bitSet.set(anIndex);
  950                           validCount++;
  951                       }
  952                   }
  953               }
  954               int          maxCounter = validCount + min;
  955   
  956               for(counter = min; counter < maxCounter; counter++)
  957                   if(!bitSet.get(counter))
  958                       return false;
  959           }
  960           return true;
  961       }
  962   
  963       /**
  964        * Used to test if a particular set of <code>TreePath</code>s can
  965        * be added. This will return true if <code>paths</code> is null (or
  966        * empty), or this object has no RowMapper, or nothing is currently selected,
  967        * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or
  968        * adding the paths to the current selection still results in a
  969        * contiguous set of <code>TreePath</code>s.
  970        */
  971       protected boolean canPathsBeAdded(TreePath[] paths) {
  972           if(paths == null || paths.length == 0 || rowMapper == null ||
  973              selection == null || selectionMode ==
  974              TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
  975               return true;
  976           else {
  977               BitSet                       bitSet = new BitSet();
  978               DefaultListSelectionModel    lModel = listSelectionModel;
  979               int                          anIndex;
  980               int                          counter;
  981               int                          min = lModel.getMinSelectionIndex();
  982               int                          max = lModel.getMaxSelectionIndex();
  983               TreePath[]                   tempPath = new TreePath[1];
  984   
  985               if(min != -1) {
  986                   for(counter = min; counter <= max; counter++) {
  987                       if(lModel.isSelectedIndex(counter))
  988                           bitSet.set(counter);
  989                   }
  990               }
  991               else {
  992                   tempPath[0] = paths[0];
  993                   min = max = rowMapper.getRowsForPaths(tempPath)[0];
  994               }
  995               for(counter = paths.length - 1; counter >= 0; counter--) {
  996                   if(paths[counter] != null) {
  997                       tempPath[0] = paths[counter];
  998                       int[]   rows = rowMapper.getRowsForPaths(tempPath);
  999                       if (rows == null) {
 1000                           return false;
 1001                       }
 1002                       anIndex = rows[0];
 1003                       min = Math.min(anIndex, min);
 1004                       max = Math.max(anIndex, max);
 1005                       if(anIndex == -1)
 1006                           return false;
 1007                       bitSet.set(anIndex);
 1008                   }
 1009               }
 1010               for(counter = min; counter <= max; counter++)
 1011                   if(!bitSet.get(counter))
 1012                       return false;
 1013           }
 1014           return true;
 1015       }
 1016   
 1017       /**
 1018        * Returns true if the paths can be removed without breaking the
 1019        * continuity of the model.
 1020        * This is rather expensive.
 1021        */
 1022       protected boolean canPathsBeRemoved(TreePath[] paths) {
 1023           if(rowMapper == null || selection == null ||
 1024              selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 1025               return true;
 1026           else {
 1027               BitSet               bitSet = new BitSet();
 1028               int                  counter;
 1029               int                  pathCount = paths.length;
 1030               int                  anIndex;
 1031               int                  min = -1;
 1032               int                  validCount = 0;
 1033               TreePath[]           tempPath = new TreePath[1];
 1034               int[]                rows;
 1035   
 1036               /* Determine the rows for the removed entries. */
 1037               lastPaths.clear();
 1038               for (counter = 0; counter < pathCount; counter++) {
 1039                   if (paths[counter] != null) {
 1040                       lastPaths.put(paths[counter], Boolean.TRUE);
 1041                   }
 1042               }
 1043               for(counter = selection.length - 1; counter >= 0; counter--) {
 1044                   if(lastPaths.get(selection[counter]) == null) {
 1045                       tempPath[0] = selection[counter];
 1046                       rows = rowMapper.getRowsForPaths(tempPath);
 1047                       if(rows != null && rows[0] != -1 && !bitSet.get(rows[0])) {
 1048                           validCount++;
 1049                           if(min == -1)
 1050                               min = rows[0];
 1051                           else
 1052                               min = Math.min(min, rows[0]);
 1053                           bitSet.set(rows[0]);
 1054                       }
 1055                   }
 1056               }
 1057               lastPaths.clear();
 1058               /* Make sure they are contiguous. */
 1059               if(validCount > 1) {
 1060                   for(counter = min + validCount - 1; counter >= min;
 1061                       counter--)
 1062                       if(!bitSet.get(counter))
 1063                           return false;
 1064               }
 1065           }
 1066           return true;
 1067       }
 1068   
 1069       /**
 1070        * Notifies listeners of a change in path. changePaths should contain
 1071        * instances of PathPlaceHolder.
 1072        *
 1073        * @deprecated As of JDK version 1.7
 1074        */
 1075       @Deprecated
 1076       protected void notifyPathChange(Vector changedPaths,
 1077                                       TreePath oldLeadSelection) {
 1078           int                    cPathCount = changedPaths.size();
 1079           boolean[]              newness = new boolean[cPathCount];
 1080           TreePath[]            paths = new TreePath[cPathCount];
 1081           PathPlaceHolder        placeholder;
 1082   
 1083           for(int counter = 0; counter < cPathCount; counter++) {
 1084               placeholder = (PathPlaceHolder) changedPaths.elementAt(counter);
 1085               newness[counter] = placeholder.isNew;
 1086               paths[counter] = placeholder.path;
 1087           }
 1088   
 1089           TreeSelectionEvent     event = new TreeSelectionEvent
 1090                             (this, paths, newness, oldLeadSelection, leadPath);
 1091   
 1092           fireValueChanged(event);
 1093       }
 1094   
 1095       /**
 1096        * Updates the leadIndex instance variable.
 1097        */
 1098       protected void updateLeadIndex() {
 1099           if(leadPath != null) {
 1100               if(selection == null) {
 1101                   leadPath = null;
 1102                   leadIndex = leadRow = -1;
 1103               }
 1104               else {
 1105                   leadRow = leadIndex = -1;
 1106                   for(int counter = selection.length - 1; counter >= 0;
 1107                       counter--) {
 1108                       // Can use == here since we know leadPath came from
 1109                       // selection
 1110                       if(selection[counter] == leadPath) {
 1111                           leadIndex = counter;
 1112                           break;
 1113                       }
 1114                   }
 1115               }
 1116           }
 1117           else {
 1118               leadIndex = -1;
 1119           }
 1120       }
 1121   
 1122       /**
 1123        * This method is obsolete and its implementation is now a noop.  It's
 1124        * still called by setSelectionPaths and addSelectionPaths, but only
 1125        * for backwards compatability.
 1126        */
 1127       protected void insureUniqueness() {
 1128       }
 1129   
 1130   
 1131       /**
 1132        * Returns a string that displays and identifies this
 1133        * object's properties.
 1134        *
 1135        * @return a String representation of this object
 1136        */
 1137       public String toString() {
 1138           int                selCount = getSelectionCount();
 1139           StringBuffer       retBuffer = new StringBuffer();
 1140           int[]              rows;
 1141   
 1142           if(rowMapper != null)
 1143               rows = rowMapper.getRowsForPaths(selection);
 1144           else
 1145               rows = null;
 1146           retBuffer.append(getClass().getName() + " " + hashCode() + " [ ");
 1147           for(int counter = 0; counter < selCount; counter++) {
 1148               if(rows != null)
 1149                   retBuffer.append(selection[counter].toString() + "@" +
 1150                                    Integer.toString(rows[counter])+ " ");
 1151               else
 1152                   retBuffer.append(selection[counter].toString() + " ");
 1153           }
 1154           retBuffer.append("]");
 1155           return retBuffer.toString();
 1156       }
 1157   
 1158       /**
 1159        * Returns a clone of this object with the same selection.
 1160        * This method does not duplicate
 1161        * selection listeners and property listeners.
 1162        *
 1163        * @exception CloneNotSupportedException never thrown by instances of
 1164        *                                       this class
 1165        */
 1166       public Object clone() throws CloneNotSupportedException {
 1167           DefaultTreeSelectionModel        clone = (DefaultTreeSelectionModel)
 1168                               super.clone();
 1169   
 1170           clone.changeSupport = null;
 1171           if(selection != null) {
 1172               int              selLength = selection.length;
 1173   
 1174               clone.selection = new TreePath[selLength];
 1175               System.arraycopy(selection, 0, clone.selection, 0, selLength);
 1176           }
 1177           clone.listenerList = new EventListenerList();
 1178           clone.listSelectionModel = (DefaultListSelectionModel)
 1179               listSelectionModel.clone();
 1180           clone.uniquePaths = new Hashtable<TreePath, Boolean>();
 1181           clone.lastPaths = new Hashtable<TreePath, Boolean>();
 1182           clone.tempPaths = new TreePath[1];
 1183           return clone;
 1184       }
 1185   
 1186       // Serialization support.
 1187       private void writeObject(ObjectOutputStream s) throws IOException {
 1188           Object[]             tValues;
 1189   
 1190           s.defaultWriteObject();
 1191           // Save the rowMapper, if it implements Serializable
 1192           if(rowMapper != null && rowMapper instanceof Serializable) {
 1193               tValues = new Object[2];
 1194               tValues[0] = "rowMapper";
 1195               tValues[1] = rowMapper;
 1196           }
 1197           else
 1198               tValues = new Object[0];
 1199           s.writeObject(tValues);
 1200       }
 1201   
 1202   
 1203       private void readObject(ObjectInputStream s)
 1204           throws IOException, ClassNotFoundException {
 1205           Object[]      tValues;
 1206   
 1207           s.defaultReadObject();
 1208   
 1209           tValues = (Object[])s.readObject();
 1210   
 1211           if(tValues.length > 0 && tValues[0].equals("rowMapper"))
 1212               rowMapper = (RowMapper)tValues[1];
 1213       }
 1214   }
 1215   
 1216   /**
 1217    * Holds a path and whether or not it is new.
 1218    */
 1219   class PathPlaceHolder {
 1220       protected boolean             isNew;
 1221       protected TreePath           path;
 1222   
 1223       PathPlaceHolder(TreePath path, boolean isNew) {
 1224           this.path = path;
 1225           this.isNew = isNew;
 1226       }
 1227   }

Home » openjdk-7 » javax » swing » tree » [javadoc | source]