| /* |
| * Copyright (c) 1997, 2008, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.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 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) { |
| if (r == -1) { |
| return; |
| } |
| |
| 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) { |
| markAsDirty(this.anchorIndex); |
| markAsDirty(anchorIndex); |
| } |
| |
| if (this.leadIndex != leadIndex) { |
| 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); |
| } |
| } |
| } |