mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			1203 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			1203 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Java
		
	
	
	
| /* DefaultTreeSelectionModel.java
 | |
|    Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
 | |
| 
 | |
| This file is part of GNU Classpath.
 | |
| 
 | |
| GNU Classpath is free software; you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation; either version 2, or (at your option)
 | |
| any later version.
 | |
| 
 | |
| GNU Classpath is distributed in the hope that it will be useful, but
 | |
| WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| General Public License for more details.
 | |
| 
 | |
| You should have received a copy of the GNU General Public License
 | |
| along with GNU Classpath; see the file COPYING.  If not, write to the
 | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | |
| 02110-1301 USA.
 | |
| 
 | |
| Linking this library statically or dynamically with other modules is
 | |
| making a combined work based on this library.  Thus, the terms and
 | |
| conditions of the GNU General Public License cover the whole
 | |
| combination.
 | |
| 
 | |
| As a special exception, the copyright holders of this library give you
 | |
| permission to link this library with independent modules to produce an
 | |
| executable, regardless of the license terms of these independent
 | |
| modules, and to copy and distribute the resulting executable under
 | |
| terms of your choice, provided that you also meet, for each linked
 | |
| independent module, the terms and conditions of the license of that
 | |
| module.  An independent module is a module which is not derived from
 | |
| or based on this library.  If you modify this library, you may extend
 | |
| this exception to your version of the library, but you are not
 | |
| obligated to do so.  If you do not wish to do so, delete this
 | |
| exception statement from your version. */
 | |
| 
 | |
| 
 | |
| package javax.swing.tree;
 | |
| 
 | |
| import gnu.java.lang.CPStringBuilder;
 | |
| 
 | |
| import java.beans.PropertyChangeListener;
 | |
| import java.io.IOException;
 | |
| import java.io.ObjectInputStream;
 | |
| import java.io.ObjectOutputStream;
 | |
| import java.io.Serializable;
 | |
| import java.util.Arrays;
 | |
| import java.util.BitSet;
 | |
| import java.util.EventListener;
 | |
| import java.util.HashSet;
 | |
| import java.util.Iterator;
 | |
| import java.util.Vector;
 | |
| 
 | |
| import javax.swing.DefaultListSelectionModel;
 | |
| import javax.swing.event.EventListenerList;
 | |
| import javax.swing.event.SwingPropertyChangeSupport;
 | |
| import javax.swing.event.TreeSelectionEvent;
 | |
| import javax.swing.event.TreeSelectionListener;
 | |
| 
 | |
| /**
 | |
|  * The implementation of the default tree selection model. The installed
 | |
|  * listeners are notified about the path and not the row changes. If you
 | |
|  * specifically need to track the row changes, register the listener for the
 | |
|  * expansion events.
 | |
|  *
 | |
|  * @author Andrew Selkirk
 | |
|  * @author Audrius Meskauskas
 | |
|  */
 | |
| public class DefaultTreeSelectionModel
 | |
|     implements Cloneable, Serializable, TreeSelectionModel
 | |
| {
 | |
| 
 | |
|   /**
 | |
|    * According to the API docs, the method
 | |
|    * {@link DefaultTreeSelectionModel#notifyPathChange} should
 | |
|    * expect instances of a class PathPlaceHolder in the Vector parameter.
 | |
|    * This seems to be a non-public class, so I can only make guesses about the
 | |
|    * use of it.
 | |
|    */
 | |
|   private static class PathPlaceHolder
 | |
|   {
 | |
|     /**
 | |
|      * The path that we wrap.
 | |
|      */
 | |
|     TreePath path;
 | |
| 
 | |
|     /**
 | |
|      * Indicates if the path is new or already in the selection.
 | |
|      */
 | |
|     boolean isNew;
 | |
| 
 | |
|     /**
 | |
|      * Creates a new instance.
 | |
|      *
 | |
|      * @param p the path to wrap
 | |
|      * @param n if the path is new or already in the selection
 | |
|      */
 | |
|     PathPlaceHolder(TreePath p, boolean n)
 | |
|     {
 | |
|       path = p;
 | |
|       isNew = n;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Use serialVersionUID for interoperability.
 | |
|    */
 | |
|   static final long serialVersionUID = 3288129636638950196L;
 | |
| 
 | |
|   /**
 | |
|    * The name of the selection mode property.
 | |
|    */
 | |
|   public static final String SELECTION_MODE_PROPERTY = "selectionMode";
 | |
| 
 | |
|   /**
 | |
|    * Our Swing property change support.
 | |
|    */
 | |
|   protected SwingPropertyChangeSupport changeSupport;
 | |
| 
 | |
|   /**
 | |
|    * The current selection.
 | |
|    */
 | |
|   protected TreePath[] selection;
 | |
| 
 | |
|   /**
 | |
|    * Our TreeSelectionListeners.
 | |
|    */
 | |
|   protected EventListenerList listenerList;
 | |
| 
 | |
|   /**
 | |
|    * The current RowMapper.
 | |
|    */
 | |
|   protected transient RowMapper rowMapper;
 | |
| 
 | |
|   /**
 | |
|    * The current listSelectionModel.
 | |
|    */
 | |
|   protected DefaultListSelectionModel listSelectionModel;
 | |
| 
 | |
|   /**
 | |
|    * The current selection mode.
 | |
|    */
 | |
|   protected int selectionMode;
 | |
| 
 | |
|   /**
 | |
|    * The path that has been added last.
 | |
|    */
 | |
|   protected TreePath leadPath;
 | |
| 
 | |
|   /**
 | |
|    * The index of the last added path.
 | |
|    */
 | |
|   protected int leadIndex;
 | |
| 
 | |
|   /**
 | |
|    * The row of the last added path according to the RowMapper.
 | |
|    */
 | |
|   protected int leadRow = -1;
 | |
| 
 | |
|   /**
 | |
|    * A supporting datastructure that is used in addSelectionPaths() and
 | |
|    * removeSelectionPaths(). It contains currently selected paths.
 | |
|    *
 | |
|    * @see #addSelectionPaths(TreePath[])
 | |
|    * @see #removeSelectionPaths(TreePath[])
 | |
|    * @see #setSelectionPaths(TreePath[])
 | |
|    */
 | |
|   private transient HashSet<TreePath> selectedPaths;
 | |
| 
 | |
|   /**
 | |
|    * A supporting datastructure that is used in addSelectionPaths() and
 | |
|    * removeSelectionPaths(). It contains the paths that are added or removed.
 | |
|    *
 | |
|    * @see #addSelectionPaths(TreePath[])
 | |
|    * @see #removeSelectionPaths(TreePath[])
 | |
|    * @see #setSelectionPaths(TreePath[])
 | |
|    */
 | |
|   private transient HashSet<TreePath> tmpPaths;
 | |
| 
 | |
|   /**
 | |
|    * Constructs a new DefaultTreeSelectionModel.
 | |
|    */
 | |
|   public DefaultTreeSelectionModel()
 | |
|   {
 | |
|     setSelectionMode(DISCONTIGUOUS_TREE_SELECTION);
 | |
|     listSelectionModel = new DefaultListSelectionModel();
 | |
|     listenerList = new EventListenerList();
 | |
|     leadIndex = -1;
 | |
|     tmpPaths = new HashSet<TreePath>();
 | |
|     selectedPaths = new HashSet<TreePath>();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Creates a clone of this DefaultTreeSelectionModel with the same selection.
 | |
|    * The cloned instance will have the same registered listeners, the listeners
 | |
|    * themselves will not be cloned. The selection will be cloned.
 | |
|    *
 | |
|    * @exception CloneNotSupportedException should not be thrown here
 | |
|    * @return a copy of this DefaultTreeSelectionModel
 | |
|    */
 | |
|   public Object clone() throws CloneNotSupportedException
 | |
|   {
 | |
|     DefaultTreeSelectionModel cloned =
 | |
|       (DefaultTreeSelectionModel) super.clone();
 | |
|     cloned.changeSupport = null;
 | |
|     cloned.selection = (TreePath[]) selection.clone();
 | |
|     cloned.listenerList = new EventListenerList();
 | |
|     cloned.listSelectionModel =
 | |
|       (DefaultListSelectionModel) listSelectionModel.clone();
 | |
|     cloned.selectedPaths = new HashSet<TreePath>();
 | |
|     cloned.tmpPaths = new HashSet<TreePath>();
 | |
| 
 | |
|     return cloned;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns a string that shows this object's properties.
 | |
|    * The returned string lists the selected tree rows, if any.
 | |
|    *
 | |
|    * @return a string that shows this object's properties
 | |
|    */
 | |
|   public String toString()
 | |
|   {
 | |
|     if (isSelectionEmpty())
 | |
|       return "[selection empty]";
 | |
|     else
 | |
|       {
 | |
|         CPStringBuilder b = new CPStringBuilder("selected rows: [");
 | |
|         for (int i = 0; i < selection.length; i++)
 | |
|           {
 | |
|             b.append(getRow(selection[i]));
 | |
|             b.append(' ');
 | |
|           }
 | |
|         b.append(", lead " + getLeadSelectionRow());
 | |
|         return b.toString();
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * writeObject
 | |
|    *
 | |
|    * @param value0 TODO
 | |
|    * @exception IOException TODO
 | |
|    */
 | |
|   private void writeObject(ObjectOutputStream value0) throws IOException
 | |
|   {
 | |
|     // TODO
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * readObject
 | |
|    *
 | |
|    * @param value0 TODO
 | |
|    * @exception IOException TODO
 | |
|    * @exception ClassNotFoundException TODO
 | |
|    */
 | |
|   private void readObject(ObjectInputStream value0) throws IOException,
 | |
|       ClassNotFoundException
 | |
|   {
 | |
|     // TODO
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets the RowMapper that should be used to map between paths and their rows.
 | |
|    *
 | |
|    * @param mapper the RowMapper to set
 | |
|    * @see RowMapper
 | |
|    */
 | |
|   public void setRowMapper(RowMapper mapper)
 | |
|   {
 | |
|     rowMapper = mapper;
 | |
|     resetRowSelection();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the RowMapper that is currently used to map between paths and their
 | |
|    * rows.
 | |
|    *
 | |
|    * @return the current RowMapper
 | |
|    * @see RowMapper
 | |
|    */
 | |
|   public RowMapper getRowMapper()
 | |
|   {
 | |
|     return rowMapper;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets the current selection mode. Possible values are
 | |
|    * {@link #SINGLE_TREE_SELECTION}, {@link #CONTIGUOUS_TREE_SELECTION} and
 | |
|    * {@link #DISCONTIGUOUS_TREE_SELECTION}.
 | |
|    *
 | |
|    * @param mode the selection mode to be set
 | |
|    * @see #getSelectionMode
 | |
|    * @see #SINGLE_TREE_SELECTION
 | |
|    * @see #CONTIGUOUS_TREE_SELECTION
 | |
|    * @see #DISCONTIGUOUS_TREE_SELECTION
 | |
|    */
 | |
|   public void setSelectionMode(int mode)
 | |
|   {
 | |
|     int oldMode = selectionMode;
 | |
|     selectionMode = mode;
 | |
|     // Make sure we have a valid selection mode.
 | |
|     if (selectionMode != SINGLE_TREE_SELECTION
 | |
|         && selectionMode != CONTIGUOUS_TREE_SELECTION
 | |
|         && selectionMode != DISCONTIGUOUS_TREE_SELECTION)
 | |
|       selectionMode = DISCONTIGUOUS_TREE_SELECTION;
 | |
| 
 | |
|     // Fire property change event.
 | |
|     if (oldMode != selectionMode && changeSupport != null)
 | |
|       changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, oldMode,
 | |
|                                        selectionMode);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the current selection mode.
 | |
|    *
 | |
|    * @return the current selection mode
 | |
|    * @see #setSelectionMode
 | |
|    * @see #SINGLE_TREE_SELECTION
 | |
|    * @see #CONTIGUOUS_TREE_SELECTION
 | |
|    * @see #DISCONTIGUOUS_TREE_SELECTION
 | |
|    */
 | |
|   public int getSelectionMode()
 | |
|   {
 | |
|     return selectionMode;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets this path as the only selection. If this changes the selection the
 | |
|    * registered TreeSelectionListeners are notified.
 | |
|    *
 | |
|    * @param path the path to set as selection
 | |
|    */
 | |
|   public void setSelectionPath(TreePath path)
 | |
|   {
 | |
|     TreePath[] paths = null;
 | |
|     if (path != null)
 | |
|       paths = new TreePath[]{ path };
 | |
|     setSelectionPaths(paths);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get the number of the tree row for the given path.
 | |
|    *
 | |
|    * @param path the tree path
 | |
|    * @return the tree row for this path or -1 if the path is not visible.
 | |
|    */
 | |
|   int getRow(TreePath path)
 | |
|   {
 | |
|     RowMapper mapper = getRowMapper();
 | |
| 
 | |
|     if (mapper instanceof AbstractLayoutCache)
 | |
|       {
 | |
|         // The absolute majority of cases, unless the TreeUI is very
 | |
|         // seriously rewritten
 | |
|         AbstractLayoutCache ama = (AbstractLayoutCache) mapper;
 | |
|         return ama.getRowForPath(path);
 | |
|       }
 | |
|     else if (mapper != null)
 | |
|       {
 | |
|         // Generic non optimized implementation.
 | |
|         int[] rows = mapper.getRowsForPaths(new TreePath[] { path });
 | |
|         if (rows.length == 0)
 | |
|           return - 1;
 | |
|         else
 | |
|           return rows[0];
 | |
|       }
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets the paths as selection. This method checks for duplicates and removes
 | |
|    * them. If this changes the selection the registered TreeSelectionListeners
 | |
|    * are notified.
 | |
|    *
 | |
|    * @param paths the paths to set as selection
 | |
|    */
 | |
|   public void setSelectionPaths(TreePath[] paths)
 | |
|   {
 | |
|     int oldLength = 0;
 | |
|     if (selection != null)
 | |
|       oldLength = selection.length;
 | |
|     int newLength = 0;
 | |
|     if (paths != null)
 | |
|       newLength = paths.length;
 | |
|     if (newLength > 0 || oldLength > 0)
 | |
|       {
 | |
|         // For SINGLE_TREE_SELECTION and for CONTIGUOUS_TREE_SELECTION with
 | |
|         // a non-contiguous path, we only allow the first path element.
 | |
|         if ((selectionMode == SINGLE_TREE_SELECTION && newLength > 1)
 | |
|             || (selectionMode == CONTIGUOUS_TREE_SELECTION && newLength > 0
 | |
|                 && ! arePathsContiguous(paths)))
 | |
|           {
 | |
|             paths = new TreePath[] { paths[0] };
 | |
|             newLength = 1;
 | |
|           }
 | |
|         // Find new paths.
 | |
|         Vector<PathPlaceHolder> changedPaths = null;
 | |
|         tmpPaths.clear();
 | |
|         int validPaths = 0;
 | |
|         TreePath oldLeadPath = leadPath;
 | |
|         for (int i = 0; i < newLength; i++)
 | |
|           {
 | |
|             if (paths[i] != null && ! tmpPaths.contains(paths[i]))
 | |
|               {
 | |
|                 validPaths++;
 | |
|                 tmpPaths.add(paths[i]);
 | |
|                 if (! selectedPaths.contains(paths[i]))
 | |
|                   {
 | |
|                     if (changedPaths == null)
 | |
|                       changedPaths = new Vector<PathPlaceHolder>();
 | |
|                     changedPaths.add(new PathPlaceHolder(paths[i], true));
 | |
|                   }
 | |
|                 leadPath = paths[i];
 | |
|               }
 | |
|           }
 | |
|         // Put together the new selection.
 | |
|         TreePath[] newSelection = null;
 | |
|         if (validPaths != 0)
 | |
|           {
 | |
|             if (validPaths != newLength)
 | |
|               {
 | |
|                 // Some of the paths are already selected, put together
 | |
|                 // the new selection carefully.
 | |
|                 newSelection = new TreePath[validPaths];
 | |
|                 Iterator<TreePath> newPaths = tmpPaths.iterator();
 | |
|                 validPaths = 0;
 | |
|                 for (int i = 0; newPaths.hasNext(); i++)
 | |
|                   newSelection[i] = newPaths.next();
 | |
|               }
 | |
|             else
 | |
|               {
 | |
|                 newSelection = new TreePath[paths.length];
 | |
|                 System.arraycopy(paths, 0, newSelection, 0, paths.length);
 | |
|               }
 | |
|           }
 | |
| 
 | |
|         // Find paths that have been selected, but are no more.
 | |
|         for (int i = 0; i < oldLength; i++)
 | |
|           {
 | |
|             if (selection[i] != null && ! tmpPaths.contains(selection[i]))
 | |
|               {
 | |
|                 if (changedPaths == null)
 | |
|                   changedPaths = new Vector<PathPlaceHolder>();
 | |
|                 changedPaths.add(new PathPlaceHolder(selection[i], false));
 | |
|               }
 | |
|           }
 | |
| 
 | |
|         // Perform changes and notification.
 | |
|         selection = newSelection;
 | |
|         HashSet<TreePath> tmp = selectedPaths;
 | |
|         selectedPaths = tmpPaths;
 | |
|         tmpPaths = tmp;
 | |
|         tmpPaths.clear();
 | |
| 
 | |
|         // Not necessary, but required according to the specs and to tests.
 | |
|         if (selection != null)
 | |
|           insureUniqueness();
 | |
|         updateLeadIndex();
 | |
|         resetRowSelection();
 | |
|         if (changedPaths != null && changedPaths.size() > 0)
 | |
|           notifyPathChange(changedPaths, oldLeadPath);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Adds a path to the list of selected paths. This method checks if the path
 | |
|    * is already selected and doesn't add the same path twice. If this changes
 | |
|    * the selection the registered TreeSelectionListeners are notified.
 | |
|    *
 | |
|    * The lead path is changed to the added path. This also happen if the
 | |
|    * passed path was already selected before.
 | |
|    *
 | |
|    * @param path the path to add to the selection
 | |
|    */
 | |
|   public void addSelectionPath(TreePath path)
 | |
|   {
 | |
|     if (path != null)
 | |
|       {
 | |
|         TreePath[] add = new TreePath[]{ path };
 | |
|         addSelectionPaths(add);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Adds the paths to the list of selected paths. This method checks if the
 | |
|    * paths are already selected and doesn't add the same path twice. If this
 | |
|    * changes the selection the registered TreeSelectionListeners are notified.
 | |
|    *
 | |
|    * @param paths the paths to add to the selection
 | |
|    */
 | |
|   public void addSelectionPaths(TreePath[] paths)
 | |
|   {
 | |
|     int length = paths != null ? paths.length : 0;
 | |
|     if (length > 0)
 | |
|       {
 | |
|         if (selectionMode == SINGLE_TREE_SELECTION)
 | |
|           setSelectionPaths(paths);
 | |
|         else if (selectionMode == CONTIGUOUS_TREE_SELECTION
 | |
|                  &&  ! canPathsBeAdded(paths))
 | |
|           {
 | |
|             if (arePathsContiguous(paths))
 | |
|               setSelectionPaths(paths);
 | |
|             else
 | |
|               setSelectionPaths(new TreePath[] { paths[0] });
 | |
|           }
 | |
|         else
 | |
|           {
 | |
|             Vector<PathPlaceHolder> changedPaths = null;
 | |
|             tmpPaths.clear();
 | |
|             int validPaths = 0;
 | |
|             TreePath oldLeadPath = leadPath;
 | |
|             int oldPaths = 0;
 | |
|             if (selection != null)
 | |
|               oldPaths = selection.length;
 | |
|             int i;
 | |
|             for (i = 0; i < length; i++)
 | |
|               {
 | |
|                 if (paths[i] != null)
 | |
|                   {
 | |
|                     if (! selectedPaths.contains(paths[i]))
 | |
|                       {
 | |
|                         validPaths++;
 | |
|                         if (changedPaths == null)
 | |
|                           changedPaths = new Vector<PathPlaceHolder>();
 | |
|                         changedPaths.add(new PathPlaceHolder(paths[i], true));
 | |
|                         selectedPaths.add(paths[i]);
 | |
|                         tmpPaths.add(paths[i]);
 | |
|                       }
 | |
|                     leadPath = paths[i];
 | |
|                   }
 | |
|               }
 | |
|             if (validPaths > 0)
 | |
|               {
 | |
|                 TreePath[] newSelection = new TreePath[oldPaths + validPaths];
 | |
|                 if (oldPaths > 0)
 | |
|                   System.arraycopy(selection, 0, newSelection, 0, oldPaths);
 | |
|                 if (validPaths != paths.length)
 | |
|                   {
 | |
|                     // Some of the paths are already selected, put together
 | |
|                     // the new selection carefully.
 | |
|                     Iterator<TreePath> newPaths = tmpPaths.iterator();
 | |
|                     i = oldPaths;
 | |
|                     while (newPaths.hasNext())
 | |
|                       {
 | |
|                         newSelection[i] = newPaths.next();
 | |
|                         i++;
 | |
|                       }
 | |
|                   }
 | |
|                 else
 | |
|                   System.arraycopy(paths, 0, newSelection, oldPaths,
 | |
|                                    validPaths);
 | |
|                 selection = newSelection;
 | |
|                 insureUniqueness();
 | |
|                 updateLeadIndex();
 | |
|                 resetRowSelection();
 | |
|                 if (changedPaths != null && changedPaths.size() > 0)
 | |
|                   notifyPathChange(changedPaths, oldLeadPath);
 | |
|               }
 | |
|             else
 | |
|               leadPath = oldLeadPath;
 | |
|             tmpPaths.clear();
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Removes the path from the selection. If this changes the selection the
 | |
|    * registered TreeSelectionListeners are notified.
 | |
|    *
 | |
|    * @param path the path to remove
 | |
|    */
 | |
|   public void removeSelectionPath(TreePath path)
 | |
|   {
 | |
|     if (path != null)
 | |
|       removeSelectionPaths(new TreePath[]{ path });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Removes the paths from the selection. If this changes the selection the
 | |
|    * registered TreeSelectionListeners are notified.
 | |
|    *
 | |
|    * @param paths the paths to remove
 | |
|    */
 | |
|   public void removeSelectionPaths(TreePath[] paths)
 | |
|   {
 | |
|     if (paths != null && selection != null && paths.length > 0)
 | |
|       {
 | |
|         if (! canPathsBeRemoved(paths))
 | |
|           clearSelection();
 | |
|         else
 | |
|           {
 | |
|             Vector<PathPlaceHolder> pathsToRemove = null;
 | |
|             for (int i = paths.length - 1; i >= 0; i--)
 | |
|               {
 | |
|                 if (paths[i] != null && selectedPaths.contains(paths[i]))
 | |
|                   {
 | |
|                     if (pathsToRemove == null)
 | |
|                       pathsToRemove = new Vector<PathPlaceHolder>();
 | |
|                     selectedPaths.remove(paths[i]);
 | |
|                     pathsToRemove.add(new PathPlaceHolder(paths[i],
 | |
|                                                           false));
 | |
|                   }
 | |
|               }
 | |
|             if (pathsToRemove != null)
 | |
|               {
 | |
|                 int numRemove = pathsToRemove.size();
 | |
|                 TreePath oldLead = leadPath;
 | |
|                 if (numRemove == selection.length)
 | |
|                   selection = null;
 | |
|                 else
 | |
|                   {
 | |
|                     selection = new TreePath[selection.length - numRemove];
 | |
|                     Iterator<TreePath> keep = selectedPaths.iterator();
 | |
|                     for (int valid = 0; keep.hasNext(); valid++)
 | |
|                       selection[valid] = keep.next();
 | |
|                   }
 | |
|                 // Update lead path.
 | |
|                 if (leadPath != null && ! selectedPaths.contains(leadPath))
 | |
|                   {
 | |
|                     if (selection != null)
 | |
|                       leadPath = selection[selection.length - 1];
 | |
|                     else
 | |
|                       leadPath = null;
 | |
|                   }
 | |
|                 else if (selection != null)
 | |
|                   leadPath = selection[selection.length - 1];
 | |
|                 else
 | |
|                   leadPath = null;
 | |
|                 updateLeadIndex();
 | |
|                 resetRowSelection();
 | |
|                 notifyPathChange(pathsToRemove, oldLead);
 | |
|               }
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the first path in the selection. This is especially useful when the
 | |
|    * selectionMode is {@link #SINGLE_TREE_SELECTION}.
 | |
|    *
 | |
|    * @return the first path in the selection
 | |
|    */
 | |
|   public TreePath getSelectionPath()
 | |
|   {
 | |
|     if ((selection == null) || (selection.length == 0))
 | |
|       return null;
 | |
|     else
 | |
|       return selection[0];
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the complete selection.
 | |
|    *
 | |
|    * @return the complete selection
 | |
|    */
 | |
|   public TreePath[] getSelectionPaths()
 | |
|   {
 | |
|     return selection;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the number of paths in the selection.
 | |
|    *
 | |
|    * @return the number of paths in the selection
 | |
|    */
 | |
|   public int getSelectionCount()
 | |
|   {
 | |
|     if (selection == null)
 | |
|       return 0;
 | |
|     else
 | |
|       return selection.length;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Checks if a given path is in the selection.
 | |
|    *
 | |
|    * @param path the path to check
 | |
|    * @return <code>true</code> if the path is in the selection,
 | |
|    *         <code>false</code> otherwise
 | |
|    */
 | |
|   public boolean isPathSelected(TreePath path)
 | |
|   {
 | |
|     if (selection == null)
 | |
|       return false;
 | |
| 
 | |
|     for (int i = 0; i < selection.length; i++)
 | |
|       {
 | |
|         if (selection[i].equals(path))
 | |
|           return true;
 | |
|       }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Checks if the selection is empty.
 | |
|    *
 | |
|    * @return <code>true</code> if the selection is empty, <code>false</code>
 | |
|    *         otherwise
 | |
|    */
 | |
|   public boolean isSelectionEmpty()
 | |
|   {
 | |
|     return (selection == null) || (selection.length == 0);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Removes all paths from the selection. Fire the unselection event.
 | |
|    */
 | |
|   public void clearSelection()
 | |
|   {
 | |
|     if (selection != null)
 | |
|       {
 | |
|         int selectionLength = selection.length;
 | |
|         boolean[] news = new boolean[selectionLength];
 | |
|         Arrays.fill(news, false);
 | |
|         TreeSelectionEvent event = new TreeSelectionEvent(this, selection,
 | |
|                                                           news, leadPath,
 | |
|                                                           null);
 | |
|         leadPath = null;
 | |
|         leadIndex = 0;
 | |
|         leadRow = 0;
 | |
|         selectedPaths.clear();
 | |
|         selection = null;
 | |
|         resetRowSelection();
 | |
|         fireValueChanged(event);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Adds a <code>TreeSelectionListener</code> object to this model.
 | |
|    *
 | |
|    * @param listener the listener to add
 | |
|    */
 | |
|   public void addTreeSelectionListener(TreeSelectionListener listener)
 | |
|   {
 | |
|     listenerList.add(TreeSelectionListener.class, listener);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Removes a <code>TreeSelectionListener</code> object from this model.
 | |
|    *
 | |
|    * @param listener the listener to remove
 | |
|    */
 | |
|   public void removeTreeSelectionListener(TreeSelectionListener listener)
 | |
|   {
 | |
|     listenerList.remove(TreeSelectionListener.class, listener);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns all <code>TreeSelectionListener</code> added to this model.
 | |
|    *
 | |
|    * @return an array of listeners
 | |
|    * @since 1.4
 | |
|    */
 | |
|   public TreeSelectionListener[] getTreeSelectionListeners()
 | |
|   {
 | |
|     return (TreeSelectionListener[]) getListeners(TreeSelectionListener.class);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * fireValueChanged
 | |
|    *
 | |
|    * @param event the event to fire.
 | |
|    */
 | |
|   protected void fireValueChanged(TreeSelectionEvent event)
 | |
|   {
 | |
|     TreeSelectionListener[] listeners = getTreeSelectionListeners();
 | |
| 
 | |
|     for (int i = 0; i < listeners.length; ++i)
 | |
|       listeners[i].valueChanged(event);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns all added listeners of a special type.
 | |
|    *
 | |
|    * @param listenerType the listener type
 | |
|    * @return an array of listeners
 | |
|    * @since 1.3
 | |
|    */
 | |
|   public <T extends EventListener> T[] getListeners(Class<T> listenerType)
 | |
|   {
 | |
|     return listenerList.getListeners(listenerType);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the currently selected rows.
 | |
|    *
 | |
|    * @return the currently selected rows
 | |
|    */
 | |
|   public int[] getSelectionRows()
 | |
|   {
 | |
|     int[] rows = null;
 | |
|     if (rowMapper != null && selection != null)
 | |
|       {
 | |
|         rows = rowMapper.getRowsForPaths(selection);
 | |
|         if (rows != null)
 | |
|           {
 | |
|             // Find invisible rows.
 | |
|             int invisible = 0;
 | |
|             for (int i = rows.length - 1; i >= 0; i--)
 | |
|               {
 | |
|                 if (rows[i] == -1)
 | |
|                   invisible++;
 | |
| 
 | |
|               }
 | |
|             // Clean up invisible rows.
 | |
|             if (invisible > 0)
 | |
|               {
 | |
|                 if (invisible == rows.length)
 | |
|                   rows = null;
 | |
|                 else
 | |
|                   {
 | |
|                     int[] newRows = new int[rows.length - invisible];
 | |
|                     int visCount = 0;
 | |
|                     for (int i = rows.length - 1; i >= 0; i--)
 | |
|                       {
 | |
|                         if (rows[i] != -1)
 | |
|                           {
 | |
|                             newRows[visCount] = rows[i];
 | |
|                             visCount++;
 | |
|                           }
 | |
|                       }
 | |
|                     rows = newRows;
 | |
|                   }
 | |
|               }
 | |
|           }
 | |
|       }
 | |
|     return rows;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the smallest row index from the selection.
 | |
|    *
 | |
|    * @return the smallest row index from the selection
 | |
|    */
 | |
|   public int getMinSelectionRow()
 | |
|   {
 | |
|     return listSelectionModel.getMinSelectionIndex();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the largest row index from the selection.
 | |
|    *
 | |
|    * @return the largest row index from the selection
 | |
|    */
 | |
|   public int getMaxSelectionRow()
 | |
|   {
 | |
|     return listSelectionModel.getMaxSelectionIndex();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Checks if a particular row is selected.
 | |
|    *
 | |
|    * @param row the index of the row to check
 | |
|    * @return <code>true</code> if the row is in this selection,
 | |
|    *         <code>false</code> otherwise
 | |
|    * @throws NullPointerException if the row mapper is not set (can only happen
 | |
|    *           if the user has plugged in the custom incorrect TreeUI
 | |
|    *           implementation.
 | |
|    */
 | |
|   public boolean isRowSelected(int row)
 | |
|   {
 | |
|     return listSelectionModel.isSelectedIndex(row);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Updates the mappings from TreePaths to row indices.
 | |
|    */
 | |
|   public void resetRowSelection()
 | |
|   {
 | |
|     listSelectionModel.clearSelection();
 | |
|     if (selection != null && rowMapper != null)
 | |
|       {
 | |
|         int[] rows = rowMapper.getRowsForPaths(selection);
 | |
|         // Update list selection model.
 | |
|         for (int i = 0; i < rows.length; i++)
 | |
|           {
 | |
|             int row = rows[i];
 | |
|             if (row != -1)
 | |
|               listSelectionModel.addSelectionInterval(row, row);
 | |
|           }
 | |
|         // Update lead selection.
 | |
|         if (leadIndex != -1 && rows != null)
 | |
|           leadRow = rows[leadIndex];
 | |
|         else if (leadPath != null)
 | |
|           {
 | |
|             TreePath[] tmp = new TreePath[]{ leadPath };
 | |
|             rows = rowMapper.getRowsForPaths(tmp);
 | |
|             leadRow = rows != null ? rows[0] : -1;
 | |
|           }
 | |
|         else
 | |
|           leadRow = -1;
 | |
|         insureRowContinuity();
 | |
|       }
 | |
|     else
 | |
|       leadRow = -1;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * getLeadSelectionRow
 | |
|    *
 | |
|    * @return int
 | |
|    */
 | |
|   public int getLeadSelectionRow()
 | |
|   {
 | |
|     return leadRow;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * getLeadSelectionPath
 | |
|    *
 | |
|    * @return TreePath
 | |
|    */
 | |
|   public TreePath getLeadSelectionPath()
 | |
|   {
 | |
|     return leadPath;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Adds a <code>PropertyChangeListener</code> object to this model.
 | |
|    *
 | |
|    * @param listener the listener to add.
 | |
|    */
 | |
|   public void addPropertyChangeListener(PropertyChangeListener listener)
 | |
|   {
 | |
|     if (changeSupport == null)
 | |
|       changeSupport = new SwingPropertyChangeSupport(this);
 | |
|     changeSupport.addPropertyChangeListener(listener);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Removes a <code>PropertyChangeListener</code> object from this model.
 | |
|    *
 | |
|    * @param listener the listener to remove.
 | |
|    */
 | |
|   public void removePropertyChangeListener(PropertyChangeListener listener)
 | |
|   {
 | |
|     if (changeSupport != null)
 | |
|       changeSupport.removePropertyChangeListener(listener);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns all added <code>PropertyChangeListener</code> objects.
 | |
|    *
 | |
|    * @return an array of listeners.
 | |
|    * @since 1.4
 | |
|    */
 | |
|   public PropertyChangeListener[] getPropertyChangeListeners()
 | |
|   {
 | |
|     PropertyChangeListener[] listeners = null;
 | |
|     if (changeSupport != null)
 | |
|       listeners = changeSupport.getPropertyChangeListeners();
 | |
|     else
 | |
|       listeners = new PropertyChangeListener[0];
 | |
|     return listeners;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Makes sure the currently selected paths are valid according to the current
 | |
|    * selectionMode. If the selectionMode is set to
 | |
|    * {@link #CONTIGUOUS_TREE_SELECTION} and the selection isn't contiguous then
 | |
|    * the selection is reset to the first set of contguous paths. If the
 | |
|    * selectionMode is set to {@link #SINGLE_TREE_SELECTION} and the selection
 | |
|    * has more than one path, the selection is reset to the contain only the
 | |
|    * first path.
 | |
|    */
 | |
|   protected void insureRowContinuity()
 | |
|   {
 | |
|     if (selectionMode == CONTIGUOUS_TREE_SELECTION && selection != null
 | |
|         && rowMapper != null)
 | |
|       {
 | |
|         int min = listSelectionModel.getMinSelectionIndex();
 | |
|         if (min != -1)
 | |
|           {
 | |
|             int max = listSelectionModel.getMaxSelectionIndex();
 | |
|             for (int i = min; i <= max; i++)
 | |
|               {
 | |
|                 if (! listSelectionModel.isSelectedIndex(i))
 | |
|                   {
 | |
|                     if (i == min)
 | |
|                       clearSelection();
 | |
|                     else
 | |
|                       {
 | |
|                         TreePath[] newSelection = new TreePath[i - min];
 | |
|                         int[] rows = rowMapper.getRowsForPaths(selection);
 | |
|                         for (int j = 0; j < rows.length; j++)
 | |
|                           {
 | |
|                             if (rows[j] < i)
 | |
|                               newSelection[rows[j] - min] = selection[j];
 | |
|                           }
 | |
|                         setSelectionPaths(newSelection);
 | |
|                         break;
 | |
|                       }
 | |
|                   }
 | |
|               }
 | |
|           }
 | |
|       }
 | |
|     else if (selectionMode == SINGLE_TREE_SELECTION && selection != null
 | |
|         && selection.length > 1)
 | |
|       setSelectionPath(selection[0]);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns <code>true</code> if the paths are contiguous (take subsequent
 | |
|    * rows in the diplayed tree view. The method returns <code>true</code> if
 | |
|    * we have no RowMapper assigned.
 | |
|    *
 | |
|    * @param paths the paths to check for continuity
 | |
|    * @return <code>true</code> if the paths are contiguous or we have no
 | |
|    *         RowMapper assigned
 | |
|    */
 | |
|   protected boolean arePathsContiguous(TreePath[] paths)
 | |
|   {
 | |
|     if (rowMapper == null || paths.length < 2)
 | |
|       return true;
 | |
| 
 | |
|     int length = paths.length;
 | |
|     TreePath[] tmp = new TreePath[1];
 | |
|     tmp[0] = paths[0];
 | |
|     int min = rowMapper.getRowsForPaths(tmp)[0];
 | |
|     BitSet selected = new BitSet();
 | |
|     int valid = 0;
 | |
|     for (int i = 0; i < length; i++)
 | |
|       {
 | |
|         if (paths[i] != null)
 | |
|           {
 | |
|             tmp[0] = paths[i];
 | |
|             int[] rows = rowMapper.getRowsForPaths(tmp);
 | |
|             if (rows == null)
 | |
|               return false; // No row mapping yet, can't be selected.
 | |
|             int row = rows[0];
 | |
|             if (row == -1 || row < (min - length) || row > (min + length))
 | |
|               return false; // Not contiguous.
 | |
|             min = Math.min(min, row);
 | |
|             if (! selected.get(row))
 | |
|               {
 | |
|                 selected.set(row);
 | |
|                 valid++;
 | |
|               }
 | |
| 
 | |
|           }
 | |
|       }
 | |
|     int max = valid + min;
 | |
|     for (int i = min; i < max; i++)
 | |
|       if (! selected.get(i))
 | |
|         return false; // Not contiguous.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Checks if the paths can be added. This returns <code>true</code> if:
 | |
|    * <ul>
 | |
|    * <li><code>paths</code> is <code>null</code> or empty</li>
 | |
|    * <li>we have no RowMapper assigned</li>
 | |
|    * <li>nothing is currently selected</li>
 | |
|    * <li>selectionMode is {@link #DISCONTIGUOUS_TREE_SELECTION}</li>
 | |
|    * <li>adding the paths to the selection still results in a contiguous set of
 | |
|    * paths</li>
 | |
|    *
 | |
|    * @param paths the paths to check
 | |
|    * @return <code>true</code> if the paths can be added with respect to the
 | |
|    *         selectionMode
 | |
|    */
 | |
|   protected boolean canPathsBeAdded(TreePath[] paths)
 | |
|   {
 | |
|     if (paths == null || paths.length == 0 || rowMapper == null
 | |
|         || selection == null || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
 | |
|       return true;
 | |
| 
 | |
|     BitSet selected = new BitSet();
 | |
|     int min = listSelectionModel.getMinSelectionIndex();
 | |
|     int max = listSelectionModel.getMaxSelectionIndex();
 | |
|     TreePath[] tmp = new TreePath[1];
 | |
|     if (min != -1)
 | |
|       {
 | |
|         // Set the bitmask of selected elements.
 | |
|         for (int i = min; i <= max; i++)
 | |
|           selected.set(i);
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         tmp[0] = paths[0];
 | |
|         min = rowMapper.getRowsForPaths(tmp)[0];
 | |
|         max = min;
 | |
|       }
 | |
|     // Mark new paths as selected.
 | |
|     for (int i = paths.length - 1; i >= 0; i--)
 | |
|       {
 | |
|         if (paths[i] != null)
 | |
|           {
 | |
|             tmp[0] = paths[i];
 | |
|             int[] rows = rowMapper.getRowsForPaths(tmp);
 | |
|             if (rows == null)
 | |
|               return false; // Now row mapping yet, can't be selected.
 | |
|             int row = rows[0];
 | |
|             if (row == -1)
 | |
|               return false; // Now row mapping yet, can't be selected.
 | |
|             min = Math.min(min, row);
 | |
|             max = Math.max(max, row);
 | |
|             selected.set(row);
 | |
|           }
 | |
|       }
 | |
|     // Now look if the new selection would be contiguous.
 | |
|     for (int i = min; i <= max; i++)
 | |
|       if (! selected.get(i))
 | |
|         return false;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Checks if the paths can be removed without breaking the continuity of the
 | |
|    * selection according to selectionMode.
 | |
|    *
 | |
|    * @param paths the paths to check
 | |
|    * @return <code>true</code> if the paths can be removed with respect to the
 | |
|    *         selectionMode
 | |
|    */
 | |
|   protected boolean canPathsBeRemoved(TreePath[] paths)
 | |
|   {
 | |
|     if (rowMapper == null || isSelectionEmpty()
 | |
|         || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
 | |
|       return true;
 | |
| 
 | |
|     HashSet<TreePath> set = new HashSet<TreePath>();
 | |
|     for (int i = 0; i < selection.length; i++)
 | |
|       set.add(selection[i]);
 | |
| 
 | |
|     for (int i = 0; i < paths.length; i++)
 | |
|       set.remove(paths[i]);
 | |
| 
 | |
|     TreePath[] remaining = new TreePath[set.size()];
 | |
|     Iterator<TreePath> iter = set.iterator();
 | |
| 
 | |
|     for (int i = 0; i < remaining.length; i++)
 | |
|       remaining[i] = iter.next();
 | |
| 
 | |
|     return arePathsContiguous(remaining);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Notify the installed listeners that the given patches have changed. This
 | |
|    * method will call listeners if invoked, but it is not called from the
 | |
|    * implementation of this class.
 | |
|    *
 | |
|    * @param vPaths the vector of the changed patches
 | |
|    * @param oldLeadSelection the old selection index
 | |
|    */
 | |
|   protected void notifyPathChange(Vector<PathPlaceHolder> vPaths,
 | |
|                                   TreePath oldLeadSelection)
 | |
|   {
 | |
| 
 | |
|     int numChangedPaths = vPaths.size();
 | |
|     boolean[] news = new boolean[numChangedPaths];
 | |
|     TreePath[] paths = new TreePath[numChangedPaths];
 | |
|     for (int i = 0; i < numChangedPaths; i++)
 | |
|       {
 | |
|         PathPlaceHolder p = vPaths.get(i);
 | |
|         news[i] = p.isNew;
 | |
|         paths[i] = p.path;
 | |
|       }
 | |
| 
 | |
|     TreeSelectionEvent event = new TreeSelectionEvent(this, paths, news,
 | |
|                                                       oldLeadSelection,
 | |
|                                                       leadPath);
 | |
|     fireValueChanged(event);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Updates the lead selection row number after changing the lead selection
 | |
|    * path.
 | |
|    */
 | |
|   protected void updateLeadIndex()
 | |
|   {
 | |
|     leadIndex = -1;
 | |
|     if (leadPath != null)
 | |
|       {
 | |
|         leadRow = -1;
 | |
|         if (selection == null)
 | |
|           leadPath = null;
 | |
|         else
 | |
|           {
 | |
|             for (int i = selection.length - 1; i >= 0 && leadIndex == -1; i--)
 | |
|               {
 | |
|                 if (selection[i] == leadPath)
 | |
|                   leadIndex = i;
 | |
|               }
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * This method exists due historical reasons and returns without action
 | |
|    * (unless overridden). For compatibility with the applications that override
 | |
|    * it, it is still called from the {@link #setSelectionPaths(TreePath[])} and
 | |
|    * {@link #addSelectionPaths(TreePath[])}.
 | |
|    */
 | |
|   protected void insureUniqueness()
 | |
|   {
 | |
|     // Following the API 1.4, the method should return without action.
 | |
|   }
 | |
| }
 |