| /* 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. | |
| } | |
| } |