c2072462fb
Add the Transient annotation and support it (JSR-273) Reviewed-by: peterz, loneid
851 lines
31 KiB
Java
851 lines
31 KiB
Java
/*
|
|
* Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Sun designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Sun in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code 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
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
|
|
* CA 95054 USA or visit www.sun.com if you need additional information or
|
|
* have any questions.
|
|
*/
|
|
|
|
package javax.swing;
|
|
|
|
import java.util.EventListener;
|
|
import java.util.BitSet;
|
|
import java.io.Serializable;
|
|
import java.beans.Transient;
|
|
|
|
import javax.swing.event.*;
|
|
|
|
|
|
/**
|
|
* Default data model for list selections.
|
|
* <p>
|
|
* <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<sup><font size="-2">TM</font></sup>
|
|
* has been added to the <code>java.beans</code> package.
|
|
* Please see {@link java.beans.XMLEncoder}.
|
|
*
|
|
* @author Philip Milne
|
|
* @author Hans Muller
|
|
* @see ListSelectionModel
|
|
*/
|
|
|
|
public class DefaultListSelectionModel implements ListSelectionModel, Cloneable, Serializable
|
|
{
|
|
private static final int MIN = -1;
|
|
private static final int MAX = Integer.MAX_VALUE;
|
|
private int selectionMode = MULTIPLE_INTERVAL_SELECTION;
|
|
private int minIndex = MAX;
|
|
private int maxIndex = MIN;
|
|
private int anchorIndex = -1;
|
|
private int leadIndex = -1;
|
|
private int firstAdjustedIndex = MAX;
|
|
private int lastAdjustedIndex = MIN;
|
|
private boolean isAdjusting = false;
|
|
|
|
private int firstChangedIndex = MAX;
|
|
private int lastChangedIndex = MIN;
|
|
|
|
private BitSet value = new BitSet(32);
|
|
protected EventListenerList listenerList = new EventListenerList();
|
|
|
|
protected boolean leadAnchorNotificationEnabled = true;
|
|
|
|
/** {@inheritDoc} */
|
|
public int getMinSelectionIndex() { return isSelectionEmpty() ? -1 : minIndex; }
|
|
|
|
/** {@inheritDoc} */
|
|
public int getMaxSelectionIndex() { return maxIndex; }
|
|
|
|
/** {@inheritDoc} */
|
|
public boolean getValueIsAdjusting() { return isAdjusting; }
|
|
|
|
/** {@inheritDoc} */
|
|
public int getSelectionMode() { return selectionMode; }
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @throws IllegalArgumentException {@inheritDoc}
|
|
*/
|
|
public void setSelectionMode(int selectionMode) {
|
|
switch (selectionMode) {
|
|
case SINGLE_SELECTION:
|
|
case SINGLE_INTERVAL_SELECTION:
|
|
case MULTIPLE_INTERVAL_SELECTION:
|
|
this.selectionMode = selectionMode;
|
|
break;
|
|
default:
|
|
throw new IllegalArgumentException("invalid selectionMode");
|
|
}
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public boolean isSelectedIndex(int index) {
|
|
return ((index < minIndex) || (index > maxIndex)) ? false : value.get(index);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public boolean isSelectionEmpty() {
|
|
return (minIndex > maxIndex);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void addListSelectionListener(ListSelectionListener l) {
|
|
listenerList.add(ListSelectionListener.class, l);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void removeListSelectionListener(ListSelectionListener l) {
|
|
listenerList.remove(ListSelectionListener.class, l);
|
|
}
|
|
|
|
/**
|
|
* Returns an array of all the list selection listeners
|
|
* registered on this <code>DefaultListSelectionModel</code>.
|
|
*
|
|
* @return all of this model's <code>ListSelectionListener</code>s
|
|
* or an empty
|
|
* array if no list selection listeners are currently registered
|
|
*
|
|
* @see #addListSelectionListener
|
|
* @see #removeListSelectionListener
|
|
*
|
|
* @since 1.4
|
|
*/
|
|
public ListSelectionListener[] getListSelectionListeners() {
|
|
return (ListSelectionListener[])listenerList.getListeners(
|
|
ListSelectionListener.class);
|
|
}
|
|
|
|
/**
|
|
* Notifies listeners that we have ended a series of adjustments.
|
|
*/
|
|
protected void fireValueChanged(boolean isAdjusting) {
|
|
if (lastChangedIndex == MIN) {
|
|
return;
|
|
}
|
|
/* Change the values before sending the event to the
|
|
* listeners in case the event causes a listener to make
|
|
* another change to the selection.
|
|
*/
|
|
int oldFirstChangedIndex = firstChangedIndex;
|
|
int oldLastChangedIndex = lastChangedIndex;
|
|
firstChangedIndex = MAX;
|
|
lastChangedIndex = MIN;
|
|
fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex, isAdjusting);
|
|
}
|
|
|
|
|
|
/**
|
|
* Notifies <code>ListSelectionListeners</code> that the value
|
|
* of the selection, in the closed interval <code>firstIndex</code>,
|
|
* <code>lastIndex</code>, has changed.
|
|
*/
|
|
protected void fireValueChanged(int firstIndex, int lastIndex) {
|
|
fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
|
|
}
|
|
|
|
/**
|
|
* @param firstIndex the first index in the interval
|
|
* @param lastIndex the last index in the interval
|
|
* @param isAdjusting true if this is the final change in a series of
|
|
* adjustments
|
|
* @see EventListenerList
|
|
*/
|
|
protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting)
|
|
{
|
|
Object[] listeners = listenerList.getListenerList();
|
|
ListSelectionEvent e = null;
|
|
|
|
for (int i = listeners.length - 2; i >= 0; i -= 2) {
|
|
if (listeners[i] == ListSelectionListener.class) {
|
|
if (e == null) {
|
|
e = new ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting);
|
|
}
|
|
((ListSelectionListener)listeners[i+1]).valueChanged(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void fireValueChanged() {
|
|
if (lastAdjustedIndex == MIN) {
|
|
return;
|
|
}
|
|
/* If getValueAdjusting() is true, (eg. during a drag opereration)
|
|
* record the bounds of the changes so that, when the drag finishes (and
|
|
* setValueAdjusting(false) is called) we can post a single event
|
|
* with bounds covering all of these individual adjustments.
|
|
*/
|
|
if (getValueIsAdjusting()) {
|
|
firstChangedIndex = Math.min(firstChangedIndex, firstAdjustedIndex);
|
|
lastChangedIndex = Math.max(lastChangedIndex, lastAdjustedIndex);
|
|
}
|
|
/* Change the values before sending the event to the
|
|
* listeners in case the event causes a listener to make
|
|
* another change to the selection.
|
|
*/
|
|
int oldFirstAdjustedIndex = firstAdjustedIndex;
|
|
int oldLastAdjustedIndex = lastAdjustedIndex;
|
|
firstAdjustedIndex = MAX;
|
|
lastAdjustedIndex = MIN;
|
|
|
|
fireValueChanged(oldFirstAdjustedIndex, oldLastAdjustedIndex);
|
|
}
|
|
|
|
/**
|
|
* 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>DefaultListSelectionModel</code>
|
|
* instance <code>m</code>
|
|
* for its list selection listeners
|
|
* with the following code:
|
|
*
|
|
* <pre>ListSelectionListener[] lsls = (ListSelectionListener[])(m.getListeners(ListSelectionListener.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 model,
|
|
* 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 #getListSelectionListeners
|
|
*
|
|
* @since 1.3
|
|
*/
|
|
public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
|
|
return listenerList.getListeners(listenerType);
|
|
}
|
|
|
|
// Updates first and last change indices
|
|
private void markAsDirty(int r) {
|
|
firstAdjustedIndex = Math.min(firstAdjustedIndex, r);
|
|
lastAdjustedIndex = Math.max(lastAdjustedIndex, r);
|
|
}
|
|
|
|
// Sets the state at this index and update all relevant state.
|
|
private void set(int r) {
|
|
if (value.get(r)) {
|
|
return;
|
|
}
|
|
value.set(r);
|
|
markAsDirty(r);
|
|
|
|
// Update minimum and maximum indices
|
|
minIndex = Math.min(minIndex, r);
|
|
maxIndex = Math.max(maxIndex, r);
|
|
}
|
|
|
|
// Clears the state at this index and update all relevant state.
|
|
private void clear(int r) {
|
|
if (!value.get(r)) {
|
|
return;
|
|
}
|
|
value.clear(r);
|
|
markAsDirty(r);
|
|
|
|
// Update minimum and maximum indices
|
|
/*
|
|
If (r > minIndex) the minimum has not changed.
|
|
The case (r < minIndex) is not possible because r'th value was set.
|
|
We only need to check for the case when lowest entry has been cleared,
|
|
and in this case we need to search for the first value set above it.
|
|
*/
|
|
if (r == minIndex) {
|
|
for(minIndex = minIndex + 1; minIndex <= maxIndex; minIndex++) {
|
|
if (value.get(minIndex)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
If (r < maxIndex) the maximum has not changed.
|
|
The case (r > maxIndex) is not possible because r'th value was set.
|
|
We only need to check for the case when highest entry has been cleared,
|
|
and in this case we need to search for the first value set below it.
|
|
*/
|
|
if (r == maxIndex) {
|
|
for(maxIndex = maxIndex - 1; minIndex <= maxIndex; maxIndex--) {
|
|
if (value.get(maxIndex)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* Performance note: This method is called from inside a loop in
|
|
changeSelection() but we will only iterate in the loops
|
|
above on the basis of one iteration per deselected cell - in total.
|
|
Ie. the next time this method is called the work of the previous
|
|
deselection will not be repeated.
|
|
|
|
We also don't need to worry about the case when the min and max
|
|
values are in their unassigned states. This cannot happen because
|
|
this method's initial check ensures that the selection was not empty
|
|
and therefore that the minIndex and maxIndex had 'real' values.
|
|
|
|
If we have cleared the whole selection, set the minIndex and maxIndex
|
|
to their cannonical values so that the next set command always works
|
|
just by using Math.min and Math.max.
|
|
*/
|
|
if (isSelectionEmpty()) {
|
|
minIndex = MAX;
|
|
maxIndex = MIN;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the value of the leadAnchorNotificationEnabled flag.
|
|
* @see #isLeadAnchorNotificationEnabled()
|
|
*/
|
|
public void setLeadAnchorNotificationEnabled(boolean flag) {
|
|
leadAnchorNotificationEnabled = flag;
|
|
}
|
|
|
|
/**
|
|
* Returns the value of the <code>leadAnchorNotificationEnabled</code> flag.
|
|
* When <code>leadAnchorNotificationEnabled</code> is true the model
|
|
* generates notification events with bounds that cover all the changes to
|
|
* the selection plus the changes to the lead and anchor indices.
|
|
* Setting the flag to false causes a narrowing of the event's bounds to
|
|
* include only the elements that have been selected or deselected since
|
|
* the last change. Either way, the model continues to maintain the lead
|
|
* and anchor variables internally. The default is true.
|
|
* <p>
|
|
* Note: It is possible for the lead or anchor to be changed without a
|
|
* change to the selection. Notification of these changes is often
|
|
* important, such as when the new lead or anchor needs to be updated in
|
|
* the view. Therefore, caution is urged when changing the default value.
|
|
*
|
|
* @return the value of the <code>leadAnchorNotificationEnabled</code> flag
|
|
* @see #setLeadAnchorNotificationEnabled(boolean)
|
|
*/
|
|
public boolean isLeadAnchorNotificationEnabled() {
|
|
return leadAnchorNotificationEnabled;
|
|
}
|
|
|
|
private void updateLeadAnchorIndices(int anchorIndex, int leadIndex) {
|
|
if (leadAnchorNotificationEnabled) {
|
|
if (this.anchorIndex != anchorIndex) {
|
|
if (this.anchorIndex != -1) { // The unassigned state.
|
|
markAsDirty(this.anchorIndex);
|
|
}
|
|
markAsDirty(anchorIndex);
|
|
}
|
|
|
|
if (this.leadIndex != leadIndex) {
|
|
if (this.leadIndex != -1) { // The unassigned state.
|
|
markAsDirty(this.leadIndex);
|
|
}
|
|
markAsDirty(leadIndex);
|
|
}
|
|
}
|
|
this.anchorIndex = anchorIndex;
|
|
this.leadIndex = leadIndex;
|
|
}
|
|
|
|
private boolean contains(int a, int b, int i) {
|
|
return (i >= a) && (i <= b);
|
|
}
|
|
|
|
private void changeSelection(int clearMin, int clearMax,
|
|
int setMin, int setMax, boolean clearFirst) {
|
|
for(int i = Math.min(setMin, clearMin); i <= Math.max(setMax, clearMax); i++) {
|
|
|
|
boolean shouldClear = contains(clearMin, clearMax, i);
|
|
boolean shouldSet = contains(setMin, setMax, i);
|
|
|
|
if (shouldSet && shouldClear) {
|
|
if (clearFirst) {
|
|
shouldClear = false;
|
|
}
|
|
else {
|
|
shouldSet = false;
|
|
}
|
|
}
|
|
|
|
if (shouldSet) {
|
|
set(i);
|
|
}
|
|
if (shouldClear) {
|
|
clear(i);
|
|
}
|
|
}
|
|
fireValueChanged();
|
|
}
|
|
|
|
/**
|
|
* Change the selection with the effect of first clearing the values
|
|
* in the inclusive range [clearMin, clearMax] then setting the values
|
|
* in the inclusive range [setMin, setMax]. Do this in one pass so
|
|
* that no values are cleared if they would later be set.
|
|
*/
|
|
private void changeSelection(int clearMin, int clearMax, int setMin, int setMax) {
|
|
changeSelection(clearMin, clearMax, setMin, setMax, true);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void clearSelection() {
|
|
removeSelectionIntervalImpl(minIndex, maxIndex, false);
|
|
}
|
|
|
|
/**
|
|
* Changes the selection to be between {@code index0} and {@code index1}
|
|
* inclusive. {@code index0} doesn't have to be less than or equal to
|
|
* {@code index1}.
|
|
* <p>
|
|
* In {@code SINGLE_SELECTION} selection mode, only the second index
|
|
* is used.
|
|
* <p>
|
|
* If this represents a change to the current selection, then each
|
|
* {@code ListSelectionListener} is notified of the change.
|
|
* <p>
|
|
* If either index is {@code -1}, this method does nothing and returns
|
|
* without exception. Otherwise, if either index is less than {@code -1},
|
|
* an {@code IndexOutOfBoundsException} is thrown.
|
|
*
|
|
* @param index0 one end of the interval.
|
|
* @param index1 other end of the interval
|
|
* @throws IndexOutOfBoundsException if either index is less than {@code -1}
|
|
* (and neither index is {@code -1})
|
|
* @see #addListSelectionListener
|
|
*/
|
|
public void setSelectionInterval(int index0, int index1) {
|
|
if (index0 == -1 || index1 == -1) {
|
|
return;
|
|
}
|
|
|
|
if (getSelectionMode() == SINGLE_SELECTION) {
|
|
index0 = index1;
|
|
}
|
|
|
|
updateLeadAnchorIndices(index0, index1);
|
|
|
|
int clearMin = minIndex;
|
|
int clearMax = maxIndex;
|
|
int setMin = Math.min(index0, index1);
|
|
int setMax = Math.max(index0, index1);
|
|
changeSelection(clearMin, clearMax, setMin, setMax);
|
|
}
|
|
|
|
/**
|
|
* Changes the selection to be the set union of the current selection
|
|
* and the indices between {@code index0} and {@code index1} inclusive.
|
|
* <p>
|
|
* In {@code SINGLE_SELECTION} selection mode, this is equivalent
|
|
* to calling {@code setSelectionInterval}, and only the second index
|
|
* is used. In {@code SINGLE_INTERVAL_SELECTION} selection mode, this
|
|
* method behaves like {@code setSelectionInterval}, unless the given
|
|
* interval is immediately adjacent to or overlaps the existing selection,
|
|
* and can therefore be used to grow it.
|
|
* <p>
|
|
* If this represents a change to the current selection, then each
|
|
* {@code ListSelectionListener} is notified of the change. Note that
|
|
* {@code index0} doesn't have to be less than or equal to {@code index1}.
|
|
* <p>
|
|
* If either index is {@code -1}, this method does nothing and returns
|
|
* without exception. Otherwise, if either index is less than {@code -1},
|
|
* an {@code IndexOutOfBoundsException} is thrown.
|
|
*
|
|
* @param index0 one end of the interval.
|
|
* @param index1 other end of the interval
|
|
* @throws IndexOutOfBoundsException if either index is less than {@code -1}
|
|
* (and neither index is {@code -1})
|
|
* @see #addListSelectionListener
|
|
* @see #setSelectionInterval
|
|
*/
|
|
public void addSelectionInterval(int index0, int index1)
|
|
{
|
|
if (index0 == -1 || index1 == -1) {
|
|
return;
|
|
}
|
|
|
|
// If we only allow a single selection, channel through
|
|
// setSelectionInterval() to enforce the rule.
|
|
if (getSelectionMode() == SINGLE_SELECTION) {
|
|
setSelectionInterval(index0, index1);
|
|
return;
|
|
}
|
|
|
|
updateLeadAnchorIndices(index0, index1);
|
|
|
|
int clearMin = MAX;
|
|
int clearMax = MIN;
|
|
int setMin = Math.min(index0, index1);
|
|
int setMax = Math.max(index0, index1);
|
|
|
|
// If we only allow a single interval and this would result
|
|
// in multiple intervals, then set the selection to be just
|
|
// the new range.
|
|
if (getSelectionMode() == SINGLE_INTERVAL_SELECTION &&
|
|
(setMax < minIndex - 1 || setMin > maxIndex + 1)) {
|
|
|
|
setSelectionInterval(index0, index1);
|
|
return;
|
|
}
|
|
|
|
changeSelection(clearMin, clearMax, setMin, setMax);
|
|
}
|
|
|
|
|
|
/**
|
|
* Changes the selection to be the set difference of the current selection
|
|
* and the indices between {@code index0} and {@code index1} inclusive.
|
|
* {@code index0} doesn't have to be less than or equal to {@code index1}.
|
|
* <p>
|
|
* In {@code SINGLE_INTERVAL_SELECTION} selection mode, if the removal
|
|
* would produce two disjoint selections, the removal is extended through
|
|
* the greater end of the selection. For example, if the selection is
|
|
* {@code 0-10} and you supply indices {@code 5,6} (in any order) the
|
|
* resulting selection is {@code 0-4}.
|
|
* <p>
|
|
* If this represents a change to the current selection, then each
|
|
* {@code ListSelectionListener} is notified of the change.
|
|
* <p>
|
|
* If either index is {@code -1}, this method does nothing and returns
|
|
* without exception. Otherwise, if either index is less than {@code -1},
|
|
* an {@code IndexOutOfBoundsException} is thrown.
|
|
*
|
|
* @param index0 one end of the interval
|
|
* @param index1 other end of the interval
|
|
* @throws IndexOutOfBoundsException if either index is less than {@code -1}
|
|
* (and neither index is {@code -1})
|
|
* @see #addListSelectionListener
|
|
*/
|
|
public void removeSelectionInterval(int index0, int index1)
|
|
{
|
|
removeSelectionIntervalImpl(index0, index1, true);
|
|
}
|
|
|
|
// private implementation allowing the selection interval
|
|
// to be removed without affecting the lead and anchor
|
|
private void removeSelectionIntervalImpl(int index0, int index1,
|
|
boolean changeLeadAnchor) {
|
|
|
|
if (index0 == -1 || index1 == -1) {
|
|
return;
|
|
}
|
|
|
|
if (changeLeadAnchor) {
|
|
updateLeadAnchorIndices(index0, index1);
|
|
}
|
|
|
|
int clearMin = Math.min(index0, index1);
|
|
int clearMax = Math.max(index0, index1);
|
|
int setMin = MAX;
|
|
int setMax = MIN;
|
|
|
|
// If the removal would produce to two disjoint selections in a mode
|
|
// that only allows one, extend the removal to the end of the selection.
|
|
if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION &&
|
|
clearMin > minIndex && clearMax < maxIndex) {
|
|
clearMax = maxIndex;
|
|
}
|
|
|
|
changeSelection(clearMin, clearMax, setMin, setMax);
|
|
}
|
|
|
|
private void setState(int index, boolean state) {
|
|
if (state) {
|
|
set(index);
|
|
}
|
|
else {
|
|
clear(index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert length indices beginning before/after index. If the value
|
|
* at index is itself selected and the selection mode is not
|
|
* SINGLE_SELECTION, set all of the newly inserted items as selected.
|
|
* Otherwise leave them unselected. This method is typically
|
|
* called to sync the selection model with a corresponding change
|
|
* in the data model.
|
|
*/
|
|
public void insertIndexInterval(int index, int length, boolean before)
|
|
{
|
|
/* The first new index will appear at insMinIndex and the last
|
|
* one will appear at insMaxIndex
|
|
*/
|
|
int insMinIndex = (before) ? index : index + 1;
|
|
int insMaxIndex = (insMinIndex + length) - 1;
|
|
|
|
/* Right shift the entire bitset by length, beginning with
|
|
* index-1 if before is true, index+1 if it's false (i.e. with
|
|
* insMinIndex).
|
|
*/
|
|
for(int i = maxIndex; i >= insMinIndex; i--) {
|
|
setState(i + length, value.get(i));
|
|
}
|
|
|
|
/* Initialize the newly inserted indices.
|
|
*/
|
|
boolean setInsertedValues = ((getSelectionMode() == SINGLE_SELECTION) ?
|
|
false : value.get(index));
|
|
for(int i = insMinIndex; i <= insMaxIndex; i++) {
|
|
setState(i, setInsertedValues);
|
|
}
|
|
|
|
int leadIndex = this.leadIndex;
|
|
if (leadIndex > index || (before && leadIndex == index)) {
|
|
leadIndex = this.leadIndex + length;
|
|
}
|
|
int anchorIndex = this.anchorIndex;
|
|
if (anchorIndex > index || (before && anchorIndex == index)) {
|
|
anchorIndex = this.anchorIndex + length;
|
|
}
|
|
if (leadIndex != this.leadIndex || anchorIndex != this.anchorIndex) {
|
|
updateLeadAnchorIndices(anchorIndex, leadIndex);
|
|
}
|
|
|
|
fireValueChanged();
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove the indices in the interval index0,index1 (inclusive) from
|
|
* the selection model. This is typically called to sync the selection
|
|
* model width a corresponding change in the data model. Note
|
|
* that (as always) index0 need not be <= index1.
|
|
*/
|
|
public void removeIndexInterval(int index0, int index1)
|
|
{
|
|
int rmMinIndex = Math.min(index0, index1);
|
|
int rmMaxIndex = Math.max(index0, index1);
|
|
int gapLength = (rmMaxIndex - rmMinIndex) + 1;
|
|
|
|
/* Shift the entire bitset to the left to close the index0, index1
|
|
* gap.
|
|
*/
|
|
for(int i = rmMinIndex; i <= maxIndex; i++) {
|
|
setState(i, value.get(i + gapLength));
|
|
}
|
|
|
|
int leadIndex = this.leadIndex;
|
|
if (leadIndex == 0 && rmMinIndex == 0) {
|
|
// do nothing
|
|
} else if (leadIndex > rmMaxIndex) {
|
|
leadIndex = this.leadIndex - gapLength;
|
|
} else if (leadIndex >= rmMinIndex) {
|
|
leadIndex = rmMinIndex - 1;
|
|
}
|
|
|
|
int anchorIndex = this.anchorIndex;
|
|
if (anchorIndex == 0 && rmMinIndex == 0) {
|
|
// do nothing
|
|
} else if (anchorIndex > rmMaxIndex) {
|
|
anchorIndex = this.anchorIndex - gapLength;
|
|
} else if (anchorIndex >= rmMinIndex) {
|
|
anchorIndex = rmMinIndex - 1;
|
|
}
|
|
|
|
if (leadIndex != this.leadIndex || anchorIndex != this.anchorIndex) {
|
|
updateLeadAnchorIndices(anchorIndex, leadIndex);
|
|
}
|
|
|
|
fireValueChanged();
|
|
}
|
|
|
|
|
|
/** {@inheritDoc} */
|
|
public void setValueIsAdjusting(boolean isAdjusting) {
|
|
if (isAdjusting != this.isAdjusting) {
|
|
this.isAdjusting = isAdjusting;
|
|
this.fireValueChanged(isAdjusting);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a string that displays and identifies this
|
|
* object's properties.
|
|
*
|
|
* @return a <code>String</code> representation of this object
|
|
*/
|
|
public String toString() {
|
|
String s = ((getValueIsAdjusting()) ? "~" : "=") + value.toString();
|
|
return getClass().getName() + " " + Integer.toString(hashCode()) + " " + s;
|
|
}
|
|
|
|
/**
|
|
* Returns a clone of this selection model with the same selection.
|
|
* <code>listenerLists</code> are not duplicated.
|
|
*
|
|
* @exception CloneNotSupportedException if the selection model does not
|
|
* both (a) implement the Cloneable interface and (b) define a
|
|
* <code>clone</code> method.
|
|
*/
|
|
public Object clone() throws CloneNotSupportedException {
|
|
DefaultListSelectionModel clone = (DefaultListSelectionModel)super.clone();
|
|
clone.value = (BitSet)value.clone();
|
|
clone.listenerList = new EventListenerList();
|
|
return clone;
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
@Transient
|
|
public int getAnchorSelectionIndex() {
|
|
return anchorIndex;
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
@Transient
|
|
public int getLeadSelectionIndex() {
|
|
return leadIndex;
|
|
}
|
|
|
|
/**
|
|
* Set the anchor selection index, leaving all selection values unchanged.
|
|
* If leadAnchorNotificationEnabled is true, send a notification covering
|
|
* the old and new anchor cells.
|
|
*
|
|
* @see #getAnchorSelectionIndex
|
|
* @see #setLeadSelectionIndex
|
|
*/
|
|
public void setAnchorSelectionIndex(int anchorIndex) {
|
|
updateLeadAnchorIndices(anchorIndex, this.leadIndex);
|
|
fireValueChanged();
|
|
}
|
|
|
|
/**
|
|
* Set the lead selection index, leaving all selection values unchanged.
|
|
* If leadAnchorNotificationEnabled is true, send a notification covering
|
|
* the old and new lead cells.
|
|
*
|
|
* @param leadIndex the new lead selection index
|
|
*
|
|
* @see #setAnchorSelectionIndex
|
|
* @see #setLeadSelectionIndex
|
|
* @see #getLeadSelectionIndex
|
|
*
|
|
* @since 1.5
|
|
*/
|
|
public void moveLeadSelectionIndex(int leadIndex) {
|
|
// disallow a -1 lead unless the anchor is already -1
|
|
if (leadIndex == -1) {
|
|
if (this.anchorIndex != -1) {
|
|
return;
|
|
}
|
|
|
|
/* PENDING(shannonh) - The following check is nice, to be consistent with
|
|
setLeadSelectionIndex. However, it is not absolutely
|
|
necessary: One could work around it by setting the anchor
|
|
to something valid, modifying the lead, and then moving
|
|
the anchor back to -1. For this reason, there's no sense
|
|
in adding it at this time, as that would require
|
|
updating the spec and officially committing to it.
|
|
|
|
// otherwise, don't do anything if the anchor is -1
|
|
} else if (this.anchorIndex == -1) {
|
|
return;
|
|
*/
|
|
|
|
}
|
|
|
|
updateLeadAnchorIndices(this.anchorIndex, leadIndex);
|
|
fireValueChanged();
|
|
}
|
|
|
|
/**
|
|
* Sets the lead selection index, ensuring that values between the
|
|
* anchor and the new lead are either all selected or all deselected.
|
|
* If the value at the anchor index is selected, first clear all the
|
|
* values in the range [anchor, oldLeadIndex], then select all the values
|
|
* values in the range [anchor, newLeadIndex], where oldLeadIndex is the old
|
|
* leadIndex and newLeadIndex is the new one.
|
|
* <p>
|
|
* If the value at the anchor index is not selected, do the same thing in
|
|
* reverse selecting values in the old range and deslecting values in the
|
|
* new one.
|
|
* <p>
|
|
* Generate a single event for this change and notify all listeners.
|
|
* For the purposes of generating minimal bounds in this event, do the
|
|
* operation in a single pass; that way the first and last index inside the
|
|
* ListSelectionEvent that is broadcast will refer to cells that actually
|
|
* changed value because of this method. If, instead, this operation were
|
|
* done in two steps the effect on the selection state would be the same
|
|
* but two events would be generated and the bounds around the changed
|
|
* values would be wider, including cells that had been first cleared only
|
|
* to later be set.
|
|
* <p>
|
|
* This method can be used in the <code>mouseDragged</code> method
|
|
* of a UI class to extend a selection.
|
|
*
|
|
* @see #getLeadSelectionIndex
|
|
* @see #setAnchorSelectionIndex
|
|
*/
|
|
public void setLeadSelectionIndex(int leadIndex) {
|
|
int anchorIndex = this.anchorIndex;
|
|
|
|
// only allow a -1 lead if the anchor is already -1
|
|
if (leadIndex == -1) {
|
|
if (anchorIndex == -1) {
|
|
updateLeadAnchorIndices(anchorIndex, leadIndex);
|
|
fireValueChanged();
|
|
}
|
|
|
|
return;
|
|
// otherwise, don't do anything if the anchor is -1
|
|
} else if (anchorIndex == -1) {
|
|
return;
|
|
}
|
|
|
|
if (this.leadIndex == -1) {
|
|
this.leadIndex = leadIndex;
|
|
}
|
|
|
|
boolean shouldSelect = value.get(this.anchorIndex);
|
|
|
|
if (getSelectionMode() == SINGLE_SELECTION) {
|
|
anchorIndex = leadIndex;
|
|
shouldSelect = true;
|
|
}
|
|
|
|
int oldMin = Math.min(this.anchorIndex, this.leadIndex);
|
|
int oldMax = Math.max(this.anchorIndex, this.leadIndex);
|
|
int newMin = Math.min(anchorIndex, leadIndex);
|
|
int newMax = Math.max(anchorIndex, leadIndex);
|
|
|
|
updateLeadAnchorIndices(anchorIndex, leadIndex);
|
|
|
|
if (shouldSelect) {
|
|
changeSelection(oldMin, oldMax, newMin, newMax);
|
|
}
|
|
else {
|
|
changeSelection(newMin, newMax, oldMin, oldMax, false);
|
|
}
|
|
}
|
|
}
|