/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing.tree;
import java.beans.
PropertyChangeListener;
import java.io.*;
import java.util.
ArrayList;
import java.util.
BitSet;
import java.util.
Enumeration;
import java.util.
EventListener;
import java.util.
Hashtable;
import java.util.
List;
import java.util.
Vector;
import javax.swing.event.*;
import javax.swing.
DefaultListSelectionModel;
/**
* Default implementation of TreeSelectionModel. Listeners are notified
* whenever
* the paths in the selection change, not the rows. In order
* to be able to track row changes you may wish to become a listener
* for expansion events on the tree and test for changes from there.
* <p>resetRowSelection is called from any of the methods that update
* the selected paths. If you subclass any of these methods to
* filter what is allowed to be selected, be sure and message
* <code>resetRowSelection</code> if you do not message super.
*
* <strong>Warning:</strong>
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @see javax.swing.JTree
*
* @author Scott Violet
*/
@
SuppressWarnings("serial")
public class
DefaultTreeSelectionModel implements
Cloneable,
Serializable,
TreeSelectionModel
{
/** Property name for selectionMode. */
public static final
String SELECTION_MODE_PROPERTY = "selectionMode";
/** Used to messaged registered listeners. */
protected
SwingPropertyChangeSupport changeSupport;
/** Paths that are currently selected. Will be null if nothing is
* currently selected. */
protected
TreePath[]
selection;
/** Event listener list. */
protected
EventListenerList listenerList = new
EventListenerList();
/** Provides a row for a given path. */
transient protected
RowMapper rowMapper;
/** Handles maintaining the list selection model. The RowMapper is used
* to map from a TreePath to a row, and the value is then placed here. */
protected
DefaultListSelectionModel listSelectionModel;
/** Mode for the selection, will be either SINGLE_TREE_SELECTION,
* CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
*/
protected int
selectionMode;
/** Last path that was added. */
protected
TreePath leadPath;
/** Index of the lead path in selection. */
protected int
leadIndex;
/** Lead row. */
protected int
leadRow;
/** Used to make sure the paths are unique, will contain all the paths
* in <code>selection</code>.
*/
private
Hashtable<
TreePath,
Boolean>
uniquePaths;
private
Hashtable<
TreePath,
Boolean>
lastPaths;
private
TreePath[]
tempPaths;
/**
* Creates a new instance of DefaultTreeSelectionModel that is
* empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
*/
public
DefaultTreeSelectionModel() {
listSelectionModel = new
DefaultListSelectionModel();
selectionMode =
DISCONTIGUOUS_TREE_SELECTION;
leadIndex =
leadRow = -1;
uniquePaths = new
Hashtable<
TreePath,
Boolean>();
lastPaths = new
Hashtable<
TreePath,
Boolean>();
tempPaths = new
TreePath[1];
}
/**
* Sets the RowMapper instance. This instance is used to determine
* the row for a particular TreePath.
*/
public void
setRowMapper(
RowMapper newMapper) {
rowMapper =
newMapper;
resetRowSelection();
}
/**
* Returns the RowMapper instance that is able to map a TreePath to a
* row.
*/
public
RowMapper getRowMapper() {
return
rowMapper;
}
/**
* Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
* CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
* is not one of the defined value,
* <code>DISCONTIGUOUS_TREE_SELECTION</code> is assumed.
* <p>This may change the selection if the current selection is not valid
* for the new mode. For example, if three TreePaths are
* selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>,
* only one TreePath will remain selected. It is up to the particular
* implementation to decide what TreePath remains selected.
* <p>
* Setting the mode to something other than the defined types will
* result in the mode becoming <code>DISCONTIGUOUS_TREE_SELECTION</code>.
*/
public void
setSelectionMode(int
mode) {
int
oldMode =
selectionMode;
selectionMode =
mode;
if(
selectionMode !=
TreeSelectionModel.
SINGLE_TREE_SELECTION &&
selectionMode !=
TreeSelectionModel.
CONTIGUOUS_TREE_SELECTION &&
selectionMode !=
TreeSelectionModel.
DISCONTIGUOUS_TREE_SELECTION)
selectionMode =
TreeSelectionModel.
DISCONTIGUOUS_TREE_SELECTION;
if(
oldMode !=
selectionMode &&
changeSupport != null)
changeSupport.
firePropertyChange(
SELECTION_MODE_PROPERTY,
Integer.
valueOf(
oldMode),
Integer.
valueOf(
selectionMode));
}
/**
* Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>,
* <code>DISCONTIGUOUS_TREE_SELECTION</code> or
* <code>CONTIGUOUS_TREE_SELECTION</code>.
*/
public int
getSelectionMode() {
return
selectionMode;
}
/**
* Sets the selection to path. If this represents a change, then
* the TreeSelectionListeners are notified. If <code>path</code> is
* null, this has the same effect as invoking <code>clearSelection</code>.
*
* @param path new path to select
*/
public void
setSelectionPath(
TreePath path) {
if(
path == null)
setSelectionPaths(null);
else {
TreePath[]
newPaths = new
TreePath[1];
newPaths[0] =
path;
setSelectionPaths(
newPaths);
}
}
/**
* Sets the selection. Whether the supplied paths are taken as the
* new selection depends upon the selection mode. If the supplied
* array is {@code null}, or empty, the selection is cleared. If
* the selection mode is {@code SINGLE_TREE_SELECTION}, only the
* first path in {@code pPaths} is used. If the selection
* mode is {@code CONTIGUOUS_TREE_SELECTION} and the supplied paths
* are not contiguous, then only the first path in {@code pPaths} is
* used. If the selection mode is
* {@code DISCONTIGUOUS_TREE_SELECTION}, then all paths are used.
* <p>
* All {@code null} paths in {@code pPaths} are ignored.
* <p>
* If this represents a change, all registered {@code
* TreeSelectionListener}s are notified.
* <p>
* The lead path is set to the last unique path.
* <p>
* The paths returned from {@code getSelectionPaths} are in the same
* order as those supplied to this method.
*
* @param pPaths the new selection
*/
public void
setSelectionPaths(
TreePath[]
pPaths) {
int
newCount,
newCounter,
oldCount,
oldCounter;
TreePath[]
paths =
pPaths;
if(
paths == null)
newCount = 0;
else
newCount =
paths.length;
if(
selection == null)
oldCount = 0;
else
oldCount =
selection.length;
if((
newCount +
oldCount) != 0) {
if(
selectionMode ==
TreeSelectionModel.
SINGLE_TREE_SELECTION) {
/* If single selection and more than one path, only allow
first. */
if(
newCount > 1) {
paths = new
TreePath[1];
paths[0] =
pPaths[0];
newCount = 1;
}
}
else if(
selectionMode ==
TreeSelectionModel.
CONTIGUOUS_TREE_SELECTION) {
/* If contiguous selection and paths aren't contiguous,
only select the first path item. */
if(
newCount > 0 && !
arePathsContiguous(
paths)) {
paths = new
TreePath[1];
paths[0] =
pPaths[0];
newCount = 1;
}
}
TreePath beginLeadPath =
leadPath;
Vector<
PathPlaceHolder>
cPaths = new
Vector<
PathPlaceHolder>(
newCount +
oldCount);
List<
TreePath>
newSelectionAsList =
new
ArrayList<
TreePath>(
newCount);
lastPaths.
clear();
leadPath = null;
/* Find the paths that are new. */
for(
newCounter = 0;
newCounter <
newCount;
newCounter++) {
TreePath path =
paths[
newCounter];
if (
path != null &&
lastPaths.
get(
path) == null) {
lastPaths.
put(
path,
Boolean.
TRUE);
if (
uniquePaths.
get(
path) == null) {
cPaths.
addElement(new
PathPlaceHolder(
path, true));
}
leadPath =
path;
newSelectionAsList.
add(
path);
}
}
TreePath[]
newSelection =
newSelectionAsList.
toArray(
new
TreePath[
newSelectionAsList.
size()]);
/* Get the paths that were selected but no longer selected. */
for(
oldCounter = 0;
oldCounter <
oldCount;
oldCounter++)
if(
selection[
oldCounter] != null &&
lastPaths.
get(
selection[
oldCounter]) == null)
cPaths.
addElement(new
PathPlaceHolder
(
selection[
oldCounter], false));
selection =
newSelection;
Hashtable<
TreePath,
Boolean>
tempHT =
uniquePaths;
uniquePaths =
lastPaths;
lastPaths =
tempHT;
lastPaths.
clear();
// No reason to do this now, but will still call it.
insureUniqueness();
updateLeadIndex();
resetRowSelection();
/* Notify of the change. */
if(
cPaths.
size() > 0)
notifyPathChange(
cPaths,
beginLeadPath);
}
}
/**
* Adds path to the current selection. If path is not currently
* in the selection the TreeSelectionListeners are notified. This has
* no effect if <code>path</code> is null.
*
* @param path the new path to add to the current selection
*/
public void
addSelectionPath(
TreePath path) {
if(
path != null) {
TreePath[]
toAdd = new
TreePath[1];
toAdd[0] =
path;
addSelectionPaths(
toAdd);
}
}
/**
* Adds paths to the current selection. If any of the paths in
* paths are not currently in the selection the TreeSelectionListeners
* are notified. This has
* no effect if <code>paths</code> is null.
* <p>The lead path is set to the last element in <code>paths</code>.
* <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>,
* and adding the new paths would make the selection discontiguous.
* Then two things can result: if the TreePaths in <code>paths</code>
* are contiguous, then the selection becomes these TreePaths,
* otherwise the TreePaths aren't contiguous and the selection becomes
* the first TreePath in <code>paths</code>.
*
* @param paths the new path to add to the current selection
*/
public void
addSelectionPaths(
TreePath[]
paths) {
int
newPathLength = ((
paths == null) ? 0 :
paths.length);
if(
newPathLength > 0) {
if(
selectionMode ==
TreeSelectionModel.
SINGLE_TREE_SELECTION) {
setSelectionPaths(
paths);
}
else if(
selectionMode ==
TreeSelectionModel.
CONTIGUOUS_TREE_SELECTION && !
canPathsBeAdded(
paths)) {
if(
arePathsContiguous(
paths)) {
setSelectionPaths(
paths);
}
else {
TreePath[]
newPaths = new
TreePath[1];
newPaths[0] =
paths[0];
setSelectionPaths(
newPaths);
}
}
else {
int
counter,
validCount;
int
oldCount;
TreePath beginLeadPath =
leadPath;
Vector<
PathPlaceHolder>
cPaths = null;
if(
selection == null)
oldCount = 0;
else
oldCount =
selection.length;
/* Determine the paths that aren't currently in the
selection. */
lastPaths.
clear();
for(
counter = 0,
validCount = 0;
counter <
newPathLength;
counter++) {
if(
paths[
counter] != null) {
if (
uniquePaths.
get(
paths[
counter]) == null) {
validCount++;
if(
cPaths == null)
cPaths = new
Vector<
PathPlaceHolder>();
cPaths.
addElement(new
PathPlaceHolder
(
paths[
counter], true));
uniquePaths.
put(
paths[
counter],
Boolean.
TRUE);
lastPaths.
put(
paths[
counter],
Boolean.
TRUE);
}
leadPath =
paths[
counter];
}
}
if(
leadPath == null) {
leadPath =
beginLeadPath;
}
if(
validCount > 0) {
TreePath newSelection[] = new
TreePath[
oldCount +
validCount];
/* And build the new selection. */
if(
oldCount > 0)
System.
arraycopy(
selection, 0,
newSelection, 0,
oldCount);
if(
validCount !=
paths.length) {
/* Some of the paths in paths are already in
the selection. */
Enumeration<
TreePath>
newPaths =
lastPaths.
keys();
counter =
oldCount;
while (
newPaths.
hasMoreElements()) {
newSelection[
counter++] =
newPaths.
nextElement();
}
}
else {
System.
arraycopy(
paths, 0,
newSelection,
oldCount,
validCount);
}
selection =
newSelection;
insureUniqueness();
updateLeadIndex();
resetRowSelection();
notifyPathChange(
cPaths,
beginLeadPath);
}
else
leadPath =
beginLeadPath;
lastPaths.
clear();
}
}
}
/**
* Removes path from the selection. If path is in the selection
* The TreeSelectionListeners are notified. This has no effect if
* <code>path</code> is null.
*
* @param path the path to remove from the selection
*/
public void
removeSelectionPath(
TreePath path) {
if(
path != null) {
TreePath[]
rPath = new
TreePath[1];
rPath[0] =
path;
removeSelectionPaths(
rPath);
}
}
/**
* Removes paths from the selection. If any of the paths in paths
* are in the selection the TreeSelectionListeners are notified.
* This has no effect if <code>paths</code> is null.
*
* @param paths the paths to remove from the selection
*/
public void
removeSelectionPaths(
TreePath[]
paths) {
if (
paths != null &&
selection != null &&
paths.length > 0) {
if(!
canPathsBeRemoved(
paths)) {
/* Could probably do something more interesting here! */
clearSelection();
}
else {
Vector<
PathPlaceHolder>
pathsToRemove = null;
/* Find the paths that can be removed. */
for (int
removeCounter =
paths.length - 1;
removeCounter >= 0;
removeCounter--) {
if(
paths[
removeCounter] != null) {
if (
uniquePaths.
get(
paths[
removeCounter]) != null) {
if(
pathsToRemove == null)
pathsToRemove = new
Vector<
PathPlaceHolder>(
paths.length);
uniquePaths.
remove(
paths[
removeCounter]);
pathsToRemove.
addElement(new
PathPlaceHolder
(
paths[
removeCounter], false));
}
}
}
if(
pathsToRemove != null) {
int
removeCount =
pathsToRemove.
size();
TreePath beginLeadPath =
leadPath;
if(
removeCount ==
selection.length) {
selection = null;
}
else {
Enumeration<
TreePath>
pEnum =
uniquePaths.
keys();
int
validCount = 0;
selection = new
TreePath[
selection.length -
removeCount];
while (
pEnum.
hasMoreElements()) {
selection[
validCount++] =
pEnum.
nextElement();
}
}
if (
leadPath != null &&
uniquePaths.
get(
leadPath) == null) {
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,
beginLeadPath);
}
}
}
}
/**
* Returns the first path in the selection. This is useful if there
* if only one item currently selected.
*/
public
TreePath getSelectionPath() {
if (
selection != null &&
selection.length > 0) {
return
selection[0];
}
return null;
}
/**
* Returns the selection.
*
* @return the selection
*/
public
TreePath[]
getSelectionPaths() {
if(
selection != null) {
int
pathSize =
selection.length;
TreePath[]
result = new
TreePath[
pathSize];
System.
arraycopy(
selection, 0,
result, 0,
pathSize);
return
result;
}
return new
TreePath[0];
}
/**
* Returns the number of paths that are selected.
*/
public int
getSelectionCount() {
return (
selection == null) ? 0 :
selection.length;
}
/**
* Returns true if the path, <code>path</code>,
* is in the current selection.
*/
public boolean
isPathSelected(
TreePath path) {
return (
path != null) ? (
uniquePaths.
get(
path) != null) : false;
}
/**
* Returns true if the selection is currently empty.
*/
public boolean
isSelectionEmpty() {
return (
selection == null ||
selection.length == 0);
}
/**
* Empties the current selection. If this represents a change in the
* current selection, the selection listeners are notified.
*/
public void
clearSelection() {
if (
selection != null &&
selection.length > 0) {
int
selSize =
selection.length;
boolean[]
newness = new boolean[
selSize];
for(int
counter = 0;
counter <
selSize;
counter++)
newness[
counter] = false;
TreeSelectionEvent event = new
TreeSelectionEvent
(this,
selection,
newness,
leadPath, null);
leadPath = null;
leadIndex =
leadRow = -1;
uniquePaths.
clear();
selection = null;
resetRowSelection();
fireValueChanged(
event);
}
}
/**
* Adds x to the list of listeners that are notified each time the
* set of selected TreePaths changes.
*
* @param x the new listener to be added
*/
public void
addTreeSelectionListener(
TreeSelectionListener x) {
listenerList.
add(
TreeSelectionListener.class,
x);
}
/**
* Removes x from the list of listeners that are notified each time
* the set of selected TreePaths changes.
*
* @param x the listener to remove
*/
public void
removeTreeSelectionListener(
TreeSelectionListener x) {
listenerList.
remove(
TreeSelectionListener.class,
x);
}
/**
* Returns an array of all the tree selection listeners
* registered on this model.
*
* @return all of this model's <code>TreeSelectionListener</code>s
* or an empty
* array if no tree selection listeners are currently registered
*
* @see #addTreeSelectionListener
* @see #removeTreeSelectionListener
*
* @since 1.4
*/
public
TreeSelectionListener[]
getTreeSelectionListeners() {
return
listenerList.
getListeners(
TreeSelectionListener.class);
}
/**
* Notifies all listeners that are registered for
* tree selection events on this object.
* @see #addTreeSelectionListener
* @see EventListenerList
*/
protected void
fireValueChanged(
TreeSelectionEvent e) {
// Guaranteed to return a non-null array
Object[]
listeners =
listenerList.
getListenerList();
// TreeSelectionEvent e = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
TreeSelectionListener.class) {
// Lazily create the event:
// if (e == null)
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
((
TreeSelectionListener)
listeners[
i+1]).
valueChanged(
e);
}
}
}
/**
* Returns an array of all the objects currently registered
* as <code><em>Foo</em>Listener</code>s
* upon this model.
* <code><em>Foo</em>Listener</code>s are registered using the
* <code>add<em>Foo</em>Listener</code> method.
*
* <p>
*
* You can specify the <code>listenerType</code> argument
* with a class literal,
* such as
* <code><em>Foo</em>Listener.class</code>.
* For example, you can query a
* <code>DefaultTreeSelectionModel</code> <code>m</code>
* for its tree selection listeners with the following code:
*
* <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
*
* If no such listeners exist, this method returns an empty array.
*
* @param listenerType the type of listeners requested; this parameter
* should specify an interface that descends from
* <code>java.util.EventListener</code>
* @return an array of all objects registered as
* <code><em>Foo</em>Listener</code>s on this component,
* or an empty array if no such
* listeners have been added
* @exception ClassCastException if <code>listenerType</code>
* doesn't specify a class or interface that implements
* <code>java.util.EventListener</code>
*
* @see #getTreeSelectionListeners
* @see #getPropertyChangeListeners
*
* @since 1.3
*/
public <T extends
EventListener> T[]
getListeners(
Class<T>
listenerType) {
return
listenerList.
getListeners(
listenerType);
}
/**
* Returns the selection in terms of rows. There is not
* necessarily a one-to-one mapping between the {@code TreePath}s
* returned from {@code getSelectionPaths} and this method. In
* particular, if a {@code TreePath} is not viewable (the {@code
* RowMapper} returns {@code -1} for the row corresponding to the
* {@code TreePath}), then the corresponding row is not included
* in the returned array. For example, if the selection consists
* of two paths, {@code A} and {@code B}, with {@code A} at row
* {@code 10}, and {@code B} not currently viewable, then this method
* returns an array with the single entry {@code 10}.
*
* @return the selection in terms of rows
*/
public int[]
getSelectionRows() {
// This is currently rather expensive. Needs
// to be better support from ListSelectionModel to speed this up.
if (
rowMapper != null &&
selection != null &&
selection.length > 0) {
int[]
rows =
rowMapper.
getRowsForPaths(
selection);
if (
rows != null) {
int
invisCount = 0;
for (int
counter =
rows.length - 1;
counter >= 0;
counter--) {
if (
rows[
counter] == -1) {
invisCount++;
}
}
if (
invisCount > 0) {
if (
invisCount ==
rows.length) {
rows = null;
}
else {
int[]
tempRows = new int[
rows.length -
invisCount];
for (int
counter =
rows.length - 1,
visCounter = 0;
counter >= 0;
counter--) {
if (
rows[
counter] != -1) {
tempRows[
visCounter++] =
rows[
counter];
}
}
rows =
tempRows;
}
}
}
return
rows;
}
return new int[0];
}
/**
* Returns the smallest value obtained from the RowMapper for the
* current set of selected TreePaths. If nothing is selected,
* or there is no RowMapper, this will return -1.
*/
public int
getMinSelectionRow() {
return
listSelectionModel.
getMinSelectionIndex();
}
/**
* Returns the largest value obtained from the RowMapper for the
* current set of selected TreePaths. If nothing is selected,
* or there is no RowMapper, this will return -1.
*/
public int
getMaxSelectionRow() {
return
listSelectionModel.
getMaxSelectionIndex();
}
/**
* Returns true if the row identified by <code>row</code> is selected.
*/
public boolean
isRowSelected(int
row) {
return
listSelectionModel.
isSelectedIndex(
row);
}
/**
* Updates this object's mapping from TreePath to rows. This should
* be invoked when the mapping from TreePaths to integers has changed
* (for example, a node has been expanded).
* <p>You do not normally have to call this, JTree and its associated
* Listeners will invoke this for you. If you are implementing your own
* View class, then you will have to invoke this.
* <p>This will invoke <code>insureRowContinuity</code> to make sure
* the currently selected TreePaths are still valid based on the
* selection mode.
*/
public void
resetRowSelection() {
listSelectionModel.
clearSelection();
if(
selection != null &&
rowMapper != null) {
int
aRow;
int
validCount = 0;
int[]
rows =
rowMapper.
getRowsForPaths(
selection);
for(int
counter = 0,
maxCounter =
selection.length;
counter <
maxCounter;
counter++) {
aRow =
rows[
counter];
if(
aRow != -1) {
listSelectionModel.
addSelectionInterval(
aRow,
aRow);
}
}
if(
leadIndex != -1 &&
rows != null) {
leadRow =
rows[
leadIndex];
}
else if (
leadPath != null) {
// Lead selection path doesn't have to be in the selection.
tempPaths[0] =
leadPath;
rows =
rowMapper.
getRowsForPaths(
tempPaths);
leadRow = (
rows != null) ?
rows[0] : -1;
}
else {
leadRow = -1;
}
insureRowContinuity();
}
else
leadRow = -1;
}
/**
* Returns the lead selection index. That is the last index that was
* added.
*/
public int
getLeadSelectionRow() {
return
leadRow;
}
/**
* Returns the last path that was added. This may differ from the
* leadSelectionPath property maintained by the JTree.
*/
public
TreePath getLeadSelectionPath() {
return
leadPath;
}
/**
* Adds a PropertyChangeListener to the listener list.
* The listener is registered for all properties.
* <p>
* A PropertyChangeEvent will get fired when the selection mode
* changes.
*
* @param listener the PropertyChangeListener to be added
*/
public synchronized void
addPropertyChangeListener(
PropertyChangeListener listener) {
if (
changeSupport == null) {
changeSupport = new
SwingPropertyChangeSupport(this);
}
changeSupport.
addPropertyChangeListener(
listener);
}
/**
* Removes a PropertyChangeListener from the listener list.
* This removes a PropertyChangeListener that was registered
* for all properties.
*
* @param listener the PropertyChangeListener to be removed
*/
public synchronized void
removePropertyChangeListener(
PropertyChangeListener listener) {
if (
changeSupport == null) {
return;
}
changeSupport.
removePropertyChangeListener(
listener);
}
/**
* Returns an array of all the property change listeners
* registered on this <code>DefaultTreeSelectionModel</code>.
*
* @return all of this model's <code>PropertyChangeListener</code>s
* or an empty
* array if no property change listeners are currently registered
*
* @see #addPropertyChangeListener
* @see #removePropertyChangeListener
*
* @since 1.4
*/
public
PropertyChangeListener[]
getPropertyChangeListeners() {
if (
changeSupport == null) {
return new
PropertyChangeListener[0];
}
return
changeSupport.
getPropertyChangeListeners();
}
/**
* Makes sure the currently selected <code>TreePath</code>s are valid
* for the current selection mode.
* If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>
* and a <code>RowMapper</code> exists, this will make sure all
* the rows are contiguous, that is, when sorted all the rows are
* in order with no gaps.
* If the selection isn't contiguous, the selection is
* reset to contain the first set, when sorted, of contiguous rows.
* <p>
* If the selection mode is <code>SINGLE_TREE_SELECTION</code> and
* more than one TreePath is selected, the selection is reset to
* contain the first path currently selected.
*/
protected void
insureRowContinuity() {
if(
selectionMode ==
TreeSelectionModel.
CONTIGUOUS_TREE_SELECTION &&
selection != null &&
rowMapper != null) {
DefaultListSelectionModel lModel =
listSelectionModel;
int
min =
lModel.
getMinSelectionIndex();
if(
min != -1) {
for(int
counter =
min,
maxCounter =
lModel.
getMaxSelectionIndex();
counter <=
maxCounter;
counter++) {
if(!
lModel.
isSelectedIndex(
counter)) {
if(
counter ==
min) {
clearSelection();
}
else {
TreePath[]
newSel = new
TreePath[
counter -
min];
int
selectionIndex[] =
rowMapper.
getRowsForPaths(
selection);
// find the actual selection pathes corresponded to the
// rows of the new selection
for (int
i = 0;
i <
selectionIndex.length;
i++) {
if (
selectionIndex[
i]<
counter) {
newSel[
selectionIndex[
i]-
min] =
selection[
i];
}
}
setSelectionPaths(
newSel);
break;
}
}
}
}
}
else if(
selectionMode ==
TreeSelectionModel.
SINGLE_TREE_SELECTION &&
selection != null &&
selection.length > 1) {
setSelectionPath(
selection[0]);
}
}
/**
* Returns true if the paths are contiguous,
* or this object has no RowMapper.
*/
protected boolean
arePathsContiguous(
TreePath[]
paths) {
if(
rowMapper == null ||
paths.length < 2)
return true;
else {
BitSet bitSet = new
BitSet(32);
int
anIndex,
counter,
min;
int
pathCount =
paths.length;
int
validCount = 0;
TreePath[]
tempPath = new
TreePath[1];
tempPath[0] =
paths[0];
min =
rowMapper.
getRowsForPaths(
tempPath)[0];
for(
counter = 0;
counter <
pathCount;
counter++) {
if(
paths[
counter] != null) {
tempPath[0] =
paths[
counter];
int[]
rows =
rowMapper.
getRowsForPaths(
tempPath);
if (
rows == null) {
return false;
}
anIndex =
rows[0];
if(
anIndex == -1 ||
anIndex < (
min -
pathCount) ||
anIndex > (
min +
pathCount))
return false;
if(
anIndex <
min)
min =
anIndex;
if(!
bitSet.
get(
anIndex)) {
bitSet.
set(
anIndex);
validCount++;
}
}
}
int
maxCounter =
validCount +
min;
for(
counter =
min;
counter <
maxCounter;
counter++)
if(!
bitSet.
get(
counter))
return false;
}
return true;
}
/**
* Used to test if a particular set of <code>TreePath</code>s can
* be added. This will return true if <code>paths</code> is null (or
* empty), or this object has no RowMapper, or nothing is currently selected,
* or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or
* adding the paths to the current selection still results in a
* contiguous set of <code>TreePath</code>s.
*/
protected boolean
canPathsBeAdded(
TreePath[]
paths) {
if(
paths == null ||
paths.length == 0 ||
rowMapper == null ||
selection == null ||
selectionMode ==
TreeSelectionModel.
DISCONTIGUOUS_TREE_SELECTION)
return true;
else {
BitSet bitSet = new
BitSet();
DefaultListSelectionModel lModel =
listSelectionModel;
int
anIndex;
int
counter;
int
min =
lModel.
getMinSelectionIndex();
int
max =
lModel.
getMaxSelectionIndex();
TreePath[]
tempPath = new
TreePath[1];
if(
min != -1) {
for(
counter =
min;
counter <=
max;
counter++) {
if(
lModel.
isSelectedIndex(
counter))
bitSet.
set(
counter);
}
}
else {
tempPath[0] =
paths[0];
min =
max =
rowMapper.
getRowsForPaths(
tempPath)[0];
}
for(
counter =
paths.length - 1;
counter >= 0;
counter--) {
if(
paths[
counter] != null) {
tempPath[0] =
paths[
counter];
int[]
rows =
rowMapper.
getRowsForPaths(
tempPath);
if (
rows == null) {
return false;
}
anIndex =
rows[0];
min =
Math.
min(
anIndex,
min);
max =
Math.
max(
anIndex,
max);
if(
anIndex == -1)
return false;
bitSet.
set(
anIndex);
}
}
for(
counter =
min;
counter <=
max;
counter++)
if(!
bitSet.
get(
counter))
return false;
}
return true;
}
/**
* Returns true if the paths can be removed without breaking the
* continuity of the model.
* This is rather expensive.
*/
protected boolean
canPathsBeRemoved(
TreePath[]
paths) {
if(
rowMapper == null ||
selection == null ||
selectionMode ==
TreeSelectionModel.
DISCONTIGUOUS_TREE_SELECTION)
return true;
else {
BitSet bitSet = new
BitSet();
int
counter;
int
pathCount =
paths.length;
int
anIndex;
int
min = -1;
int
validCount = 0;
TreePath[]
tempPath = new
TreePath[1];
int[]
rows;
/* Determine the rows for the removed entries. */
lastPaths.
clear();
for (
counter = 0;
counter <
pathCount;
counter++) {
if (
paths[
counter] != null) {
lastPaths.
put(
paths[
counter],
Boolean.
TRUE);
}
}
for(
counter =
selection.length - 1;
counter >= 0;
counter--) {
if(
lastPaths.
get(
selection[
counter]) == null) {
tempPath[0] =
selection[
counter];
rows =
rowMapper.
getRowsForPaths(
tempPath);
if(
rows != null &&
rows[0] != -1 && !
bitSet.
get(
rows[0])) {
validCount++;
if(
min == -1)
min =
rows[0];
else
min =
Math.
min(
min,
rows[0]);
bitSet.
set(
rows[0]);
}
}
}
lastPaths.
clear();
/* Make sure they are contiguous. */
if(
validCount > 1) {
for(
counter =
min +
validCount - 1;
counter >=
min;
counter--)
if(!
bitSet.
get(
counter))
return false;
}
}
return true;
}
/**
* Notifies listeners of a change in path. changePaths should contain
* instances of PathPlaceHolder.
*
* @deprecated As of JDK version 1.7
*/
@
Deprecated
protected void
notifyPathChange(
Vector<?>
changedPaths,
TreePath oldLeadSelection) {
int
cPathCount =
changedPaths.
size();
boolean[]
newness = new boolean[
cPathCount];
TreePath[]
paths = new
TreePath[
cPathCount];
PathPlaceHolder placeholder;
for(int
counter = 0;
counter <
cPathCount;
counter++) {
placeholder = (
PathPlaceHolder)
changedPaths.
elementAt(
counter);
newness[
counter] =
placeholder.
isNew;
paths[
counter] =
placeholder.
path;
}
TreeSelectionEvent event = new
TreeSelectionEvent
(this,
paths,
newness,
oldLeadSelection,
leadPath);
fireValueChanged(
event);
}
/**
* Updates the leadIndex instance variable.
*/
protected void
updateLeadIndex() {
if(
leadPath != null) {
if(
selection == null) {
leadPath = null;
leadIndex =
leadRow = -1;
}
else {
leadRow =
leadIndex = -1;
for(int
counter =
selection.length - 1;
counter >= 0;
counter--) {
// Can use == here since we know leadPath came from
// selection
if(
selection[
counter] ==
leadPath) {
leadIndex =
counter;
break;
}
}
}
}
else {
leadIndex = -1;
}
}
/**
* This method is obsolete and its implementation is now a noop. It's
* still called by setSelectionPaths and addSelectionPaths, but only
* for backwards compatibility.
*/
protected void
insureUniqueness() {
}
/**
* Returns a string that displays and identifies this
* object's properties.
*
* @return a String representation of this object
*/
public
String toString() {
int
selCount =
getSelectionCount();
StringBuffer retBuffer = new
StringBuffer();
int[]
rows;
if(
rowMapper != null)
rows =
rowMapper.
getRowsForPaths(
selection);
else
rows = null;
retBuffer.
append(
getClass().
getName() + " " +
hashCode() + " [ ");
for(int
counter = 0;
counter <
selCount;
counter++) {
if(
rows != null)
retBuffer.
append(
selection[
counter].
toString() + "@" +
Integer.
toString(
rows[
counter])+ " ");
else
retBuffer.
append(
selection[
counter].
toString() + " ");
}
retBuffer.
append("]");
return
retBuffer.
toString();
}
/**
* Returns a clone of this object with the same selection.
* This method does not duplicate
* selection listeners and property listeners.
*
* @exception CloneNotSupportedException never thrown by instances of
* this class
*/
public
Object clone() throws
CloneNotSupportedException {
DefaultTreeSelectionModel clone = (
DefaultTreeSelectionModel)
super.clone();
clone.
changeSupport = null;
if(
selection != null) {
int
selLength =
selection.length;
clone.
selection = new
TreePath[
selLength];
System.
arraycopy(
selection, 0,
clone.
selection, 0,
selLength);
}
clone.
listenerList = new
EventListenerList();
clone.
listSelectionModel = (
DefaultListSelectionModel)
listSelectionModel.
clone();
clone.
uniquePaths = new
Hashtable<
TreePath,
Boolean>();
clone.
lastPaths = new
Hashtable<
TreePath,
Boolean>();
clone.
tempPaths = new
TreePath[1];
return
clone;
}
// Serialization support.
private void
writeObject(
ObjectOutputStream s) throws
IOException {
Object[]
tValues;
s.
defaultWriteObject();
// Save the rowMapper, if it implements Serializable
if(
rowMapper != null &&
rowMapper instanceof
Serializable) {
tValues = new
Object[2];
tValues[0] = "rowMapper";
tValues[1] =
rowMapper;
}
else
tValues = new
Object[0];
s.
writeObject(
tValues);
}
private void
readObject(
ObjectInputStream s)
throws
IOException,
ClassNotFoundException {
Object[]
tValues;
s.
defaultReadObject();
tValues = (
Object[])
s.
readObject();
if(
tValues.length > 0 &&
tValues[0].
equals("rowMapper"))
rowMapper = (
RowMapper)
tValues[1];
}
}
/**
* Holds a path and whether or not it is new.
*/
class
PathPlaceHolder {
protected boolean
isNew;
protected
TreePath path;
PathPlaceHolder(
TreePath path, boolean
isNew) {
this.
path =
path;
this.
isNew =
isNew;
}
}