| /* 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 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 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 tmpPaths; |
| |
| /** |
| * Constructs a new DefaultTreeSelectionModel. |
| */ |
| public DefaultTreeSelectionModel() |
| { |
| setSelectionMode(DISCONTIGUOUS_TREE_SELECTION); |
| listSelectionModel = new DefaultListSelectionModel(); |
| listenerList = new EventListenerList(); |
| leadIndex = -1; |
| tmpPaths = new HashSet(); |
| selectedPaths = new HashSet(); |
| } |
| |
| /** |
| * 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(); |
| cloned.tmpPaths = new HashSet(); |
| |
| 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 |
| { |
| StringBuffer b = new StringBuffer("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 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(); |
| 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 newPaths = tmpPaths.iterator(); |
| validPaths = 0; |
| for (int i = 0; newPaths.hasNext(); i++) |
| newSelection[i] = (TreePath) 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(); |
| changedPaths.add(new PathPlaceHolder(selection[i], false)); |
| } |
| } |
| |
| // Perform changes and notification. |
| selection = newSelection; |
| HashSet 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 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(); |
| 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 newPaths = tmpPaths.iterator(); |
| i = oldPaths; |
| while (newPaths.hasNext()) |
| { |
| newSelection[i] = (TreePath) 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 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(); |
| 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 keep = selectedPaths.iterator(); |
| for (int valid = 0; keep.hasNext(); valid++) |
| selection[valid] = (TreePath) 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 set = new HashSet(); |
| 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 iter = set.iterator(); |
| |
| for (int i = 0; i < remaining.length; i++) |
| remaining[i] = (TreePath) 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 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 = (PathPlaceHolder) 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. |
| } |
| } |