| /* 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.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 |
| { |
| |
| /** |
| * 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; |
| |
| /** |
| * Constructs a new DefaultTreeSelectionModel. |
| */ |
| public DefaultTreeSelectionModel() |
| { |
| setSelectionMode(DISCONTIGUOUS_TREE_SELECTION); |
| listenerList = new EventListenerList(); |
| } |
| |
| /** |
| * 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(); |
| |
| // Clone the selection and the list selection model. |
| cloned.selection = (TreePath[]) selection.clone(); |
| if (listSelectionModel != null) |
| cloned.listSelectionModel |
| = (DefaultListSelectionModel) listSelectionModel.clone(); |
| 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; |
| } |
| |
| /** |
| * 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) |
| { |
| selectionMode = mode; |
| insureRowContinuity(); |
| } |
| |
| /** |
| * 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) |
| { |
| // The most frequently only one cell in the tree is selected. |
| TreePath[] ose = selection; |
| selection = new TreePath[] { path }; |
| TreePath oldLead = leadPath; |
| leadIndex = 0; |
| leadRow = getRow(path); |
| leadPath = path; |
| |
| TreeSelectionEvent event; |
| |
| if (ose != null && ose.length > 0) |
| { |
| // The first item in the path list is the selected path. |
| // The remaining items are unselected pathes. |
| TreePath[] changed = new TreePath[ose.length + 1]; |
| boolean[] news = new boolean[changed.length]; |
| news[0] = true; |
| changed[0] = path; |
| System.arraycopy(ose, 0, changed, 1, ose.length); |
| event = new TreeSelectionEvent(this, changed, news, oldLead, path); |
| } |
| else |
| { |
| event = new TreeSelectionEvent(this, path, true, oldLead, path); |
| } |
| fireValueChanged(event); |
| } |
| |
| /** |
| * 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 |
| { |
| // Generic non optimized implementation. |
| int[] rows = mapper.getRowsForPaths(new TreePath[] { path }); |
| if (rows.length == 0) |
| return - 1; |
| else |
| return rows[0]; |
| } |
| } |
| |
| /** |
| * 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) |
| { |
| // Must be called, as defined in JDK API 1.4. |
| insureUniqueness(); |
| clearSelection(); |
| addSelectionPaths(paths); |
| } |
| |
| /** |
| * 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 (! isPathSelected(path)) |
| { |
| if (selectionMode == SINGLE_TREE_SELECTION || isSelectionEmpty() |
| || ! canPathBeAdded(path)) |
| setSelectionPath(path); |
| else |
| { |
| TreePath[] temp = new TreePath[selection.length + 1]; |
| System.arraycopy(selection, 0, temp, 0, selection.length); |
| temp[temp.length - 1] = path; |
| selection = new TreePath[temp.length]; |
| System.arraycopy(temp, 0, selection, 0, temp.length); |
| } |
| } |
| |
| if (path != leadPath) |
| { |
| TreePath oldLead = leadPath; |
| leadPath = path; |
| leadRow = getRow(path); |
| leadIndex = selection.length - 1; |
| fireValueChanged(new TreeSelectionEvent(this, path, true, oldLead, |
| leadPath)); |
| } |
| } |
| |
| /** |
| * 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) |
| { |
| // Must be called, as defined in JDK API 1.4. |
| insureUniqueness(); |
| |
| if (paths != null) |
| { |
| TreePath v0 = null; |
| for (int i = 0; i < paths.length; i++) |
| { |
| v0 = paths[i]; |
| if (! isPathSelected(v0)) |
| { |
| if (isSelectionEmpty()) |
| setSelectionPath(v0); |
| else |
| { |
| TreePath[] temp = new TreePath[selection.length + 1]; |
| System.arraycopy(selection, 0, temp, 0, selection.length); |
| temp[temp.length - 1] = v0; |
| selection = new TreePath[temp.length]; |
| System.arraycopy(temp, 0, selection, 0, temp.length); |
| } |
| TreePath oldLead = leadPath; |
| leadPath = paths[paths.length - 1]; |
| leadRow = getRow(leadPath); |
| leadIndex = selection.length - 1; |
| |
| fireValueChanged(new TreeSelectionEvent(this, v0, true, |
| oldLead, leadPath)); |
| } |
| } |
| insureRowContinuity(); |
| } |
| } |
| |
| /** |
| * 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 (isSelectionEmpty()) |
| return; |
| |
| int index = - 1; |
| if (isPathSelected(path)) |
| { |
| for (int i = 0; i < selection.length; i++) |
| { |
| if (selection[i].equals(path)) |
| { |
| index = i; |
| break; |
| } |
| } |
| TreePath[] temp = new TreePath[selection.length - 1]; |
| System.arraycopy(selection, 0, temp, 0, index); |
| System.arraycopy(selection, index + 1, temp, index, selection.length |
| - index - 1); |
| selection = new TreePath[temp.length]; |
| System.arraycopy(temp, 0, selection, 0, temp.length); |
| |
| // If the removed path was the lead path, set the lead path to null. |
| TreePath oldLead = leadPath; |
| if (path != null && leadPath != null && path.equals(leadPath)) |
| leadPath = null; |
| |
| fireValueChanged(new TreeSelectionEvent(this, path, false, oldLead, |
| leadPath)); |
| insureRowContinuity(); |
| } |
| } |
| |
| /** |
| * 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 (isSelectionEmpty()) |
| return; |
| if (paths != null) |
| { |
| int index = - 1; |
| TreePath v0 = null; |
| TreePath oldLead = leadPath; |
| for (int i = 0; i < paths.length; i++) |
| { |
| v0 = paths[i]; |
| if (isPathSelected(v0)) |
| { |
| for (int x = 0; x < selection.length; x++) |
| { |
| if (selection[i].equals(v0)) |
| { |
| index = x; |
| break; |
| } |
| if (leadPath != null && leadPath.equals(v0)) |
| leadPath = null; |
| } |
| TreePath[] temp = new TreePath[selection.length - 1]; |
| System.arraycopy(selection, 0, temp, 0, index); |
| System.arraycopy(selection, index + 1, temp, index, |
| selection.length - index - 1); |
| selection = new TreePath[temp.length]; |
| System.arraycopy(temp, 0, selection, 0, temp.length); |
| |
| fireValueChanged(new TreeSelectionEvent(this, v0, false, |
| oldLead, leadPath)); |
| } |
| } |
| insureRowContinuity(); |
| } |
| } |
| |
| /** |
| * 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 (! isSelectionEmpty()) |
| { |
| TreeSelectionEvent event = new TreeSelectionEvent( |
| this, selection, new boolean[selection.length], leadPath, null); |
| leadPath = null; |
| selection = null; |
| fireValueChanged(event); |
| } |
| else |
| { |
| leadPath = null; |
| selection = null; |
| } |
| } |
| |
| /** |
| * 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 EventListener[] getListeners(Class listenerType) |
| { |
| return listenerList.getListeners(listenerType); |
| } |
| |
| /** |
| * Returns the currently selected rows. |
| * |
| * @return the currently selected rows |
| */ |
| public int[] getSelectionRows() |
| { |
| if (rowMapper == null) |
| return null; |
| else |
| return rowMapper.getRowsForPaths(selection); |
| } |
| |
| /** |
| * Returns the smallest row index from the selection. |
| * |
| * @return the smallest row index from the selection |
| */ |
| public int getMinSelectionRow() |
| { |
| if ((rowMapper == null) || (selection == null) || (selection.length == 0)) |
| return - 1; |
| else |
| { |
| int[] rows = rowMapper.getRowsForPaths(selection); |
| int minRow = Integer.MAX_VALUE; |
| for (int index = 0; index < rows.length; index++) |
| minRow = Math.min(minRow, rows[index]); |
| return minRow; |
| } |
| } |
| |
| /** |
| * Returns the largest row index from the selection. |
| * |
| * @return the largest row index from the selection |
| */ |
| public int getMaxSelectionRow() |
| { |
| if ((rowMapper == null) || (selection == null) || (selection.length == 0)) |
| return - 1; |
| else |
| { |
| int[] rows = rowMapper.getRowsForPaths(selection); |
| int maxRow = - 1; |
| for (int index = 0; index < rows.length; index++) |
| maxRow = Math.max(maxRow, rows[index]); |
| return maxRow; |
| } |
| } |
| |
| /** |
| * 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 false if nothing is selected. |
| if (isSelectionEmpty()) |
| return false; |
| |
| RowMapper mapper = getRowMapper(); |
| |
| if (mapper instanceof AbstractLayoutCache) |
| { |
| // The absolute majority of cases, unless the TreeUI is very |
| // seriously rewritten |
| AbstractLayoutCache ama = (AbstractLayoutCache) mapper; |
| TreePath path = ama.getPathForRow(row); |
| return isPathSelected(path); |
| } |
| else |
| { |
| // Generic non optimized implementation. |
| int[] rows = mapper.getRowsForPaths(selection); |
| for (int i = 0; i < rows.length; i++) |
| if (rows[i] == row) |
| return true; |
| return false; |
| } |
| } |
| |
| /** |
| * Updates the mappings from TreePaths to row indices. |
| */ |
| public void resetRowSelection() |
| { |
| // Nothing to do here. |
| } |
| |
| /** |
| * 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) |
| { |
| changeSupport.addPropertyChangeListener(listener); |
| } |
| |
| /** |
| * Removes a <code>PropertyChangeListener</code> object from this model. |
| * |
| * @param listener the listener to remove. |
| */ |
| public void removePropertyChangeListener(PropertyChangeListener listener) |
| { |
| changeSupport.removePropertyChangeListener(listener); |
| } |
| |
| /** |
| * Returns all added <code>PropertyChangeListener</code> objects. |
| * |
| * @return an array of listeners. |
| * @since 1.4 |
| */ |
| public PropertyChangeListener[] getPropertyChangeListeners() |
| { |
| return changeSupport.getPropertyChangeListeners(); |
| } |
| |
| /** |
| * 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 (selection == null || selection.length < 2) |
| return; |
| else if (selectionMode == CONTIGUOUS_TREE_SELECTION) |
| { |
| if (rowMapper == null) |
| // This is the best we can do without the row mapper: |
| selectOne(); |
| else |
| { |
| int[] rows = rowMapper.getRowsForPaths(selection); |
| Arrays.sort(rows); |
| int i; |
| for (i = 1; i < rows.length; i++) |
| { |
| if (rows[i - 1] != rows[i] - 1) |
| // Break if no longer continuous. |
| break; |
| } |
| |
| if (i < rows.length) |
| { |
| TreePath[] ns = new TreePath[i]; |
| for (int j = 0; j < ns.length; j++) |
| ns[i] = getPath(j); |
| setSelectionPaths(ns); |
| } |
| } |
| } |
| else if (selectionMode == SINGLE_TREE_SELECTION) |
| selectOne(); |
| } |
| |
| /** |
| * Keep only one (normally last or leading) path in the selection. |
| */ |
| private void selectOne() |
| { |
| if (leadIndex > 0 && leadIndex < selection.length) |
| setSelectionPath(selection[leadIndex]); |
| else |
| setSelectionPath(selection[selection.length - 1]); |
| } |
| |
| /** |
| * Get path for the given row that must be in the current selection. |
| */ |
| private TreePath getPath(int row) |
| { |
| if (rowMapper instanceof AbstractLayoutCache) |
| return ((AbstractLayoutCache) rowMapper).getPathForRow(row); |
| else |
| { |
| int[] rows = rowMapper.getRowsForPaths(selection); |
| for (int i = 0; i < rows.length; i++) |
| if (rows[i] == row) |
| return selection[i]; |
| } |
| throw new InternalError(row + " not in selection"); |
| } |
| |
| /** |
| * 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[] rows = rowMapper.getRowsForPaths(paths); |
| |
| // The patches may not be sorted. |
| Arrays.sort(rows); |
| |
| for (int i = 1; i < rows.length; i++) |
| { |
| if (rows[i - 1] != rows[i] - 1) |
| return false; |
| } |
| 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 (rowMapper == null || isSelectionEmpty() |
| || selectionMode == DISCONTIGUOUS_TREE_SELECTION) |
| return true; |
| |
| TreePath [] all = new TreePath[paths.length + selection.length]; |
| System.arraycopy(paths, 0, all, 0, paths.length); |
| System.arraycopy(selection, 0, all, paths.length, selection.length); |
| |
| return arePathsContiguous(all); |
| } |
| |
| /** |
| * Checks if the single path can be added to selection. |
| */ |
| private boolean canPathBeAdded(TreePath path) |
| { |
| if (rowMapper == null || isSelectionEmpty() |
| || selectionMode == DISCONTIGUOUS_TREE_SELECTION) |
| return true; |
| |
| TreePath[] all = new TreePath[selection.length + 1]; |
| System.arraycopy(selection, 0, all, 0, selection.length); |
| all[all.length - 1] = path; |
| |
| return arePathsContiguous(all); |
| } |
| |
| /** |
| * 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 vPathes the vector of the changed patches |
| * @param oldLeadSelection the old selection index |
| */ |
| protected void notifyPathChange(Vector vPathes, TreePath oldLeadSelection) |
| { |
| TreePath[] pathes = new TreePath[vPathes.size()]; |
| for (int i = 0; i < pathes.length; i++) |
| pathes[i] = (TreePath) vPathes.get(i); |
| |
| boolean[] news = new boolean[pathes.length]; |
| for (int i = 0; i < news.length; i++) |
| news[i] = isPathSelected(pathes[i]); |
| |
| TreeSelectionEvent event = new TreeSelectionEvent(this, pathes, news, |
| oldLeadSelection, |
| leadPath); |
| fireValueChanged(event); |
| } |
| |
| /** |
| * Updates the lead selection row number after changing the lead selection |
| * path. |
| */ |
| protected void updateLeadIndex() |
| { |
| if (isSelectionEmpty()) |
| { |
| leadRow = leadIndex = - 1; |
| } |
| else |
| { |
| leadRow = getRow(leadPath); |
| for (int i = 0; i < selection.length; i++) |
| { |
| if (selection[i].equals(leadPath)) |
| { |
| leadIndex = i; |
| break; |
| } |
| } |
| leadIndex = leadRow; |
| } |
| } |
| |
| /** |
| * 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. |
| } |
| } |