- /*
- * @(#)OptionListModel.java 1.11 03/12/19
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing.text.html;
-
- import javax.swing.*;
- import javax.swing.event.*;
- import java.util.EventListener;
- import java.util.BitSet;
- import java.io.Serializable;
-
-
- /**
- * This class extends DefaultListModel, and also implements
- * the ListSelectionModel interface, allowing for it to store state
- * relevant to a SELECT form element which is implemented as a List.
- * If SELECT has a size attribute whose value is greater than 1,
- * or if allows multiple selection then a JList is used to
- * represent it and the OptionListModel is used as its model.
- * It also stores the initial state of the JList, to ensure an
- * accurate reset, if the user requests a reset of the form.
- *
- @author Sunita Mani
- @version 1.11 12/19/03
- */
-
- class OptionListModel extends DefaultListModel implements ListSelectionModel, Serializable {
-
-
- private static final int MIN = -1;
- private static final int MAX = Integer.MAX_VALUE;
- private int selectionMode = SINGLE_SELECTION;
- private int minIndex = MAX;
- private int maxIndex = MIN;
- private int anchorIndex = -1;
- private int leadIndex = -1;
- private int firstChangedIndex = MAX;
- private int lastChangedIndex = MIN;
- private boolean isAdjusting = false;
- private BitSet value = new BitSet(32);
- private BitSet initialValue = new BitSet(32);
- protected EventListenerList listenerList = new EventListenerList();
-
- protected boolean leadAnchorNotificationEnabled = true;
-
- public int getMinSelectionIndex() { return isSelectionEmpty() ? -1 : minIndex; }
-
- public int getMaxSelectionIndex() { return maxIndex; }
-
- public boolean getValueIsAdjusting() { return isAdjusting; }
-
- public int getSelectionMode() { return selectionMode; }
-
- 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");
- }
- }
-
- public boolean isSelectedIndex(int index) {
- return ((index < minIndex) || (index > maxIndex)) ? false : value.get(index);
- }
-
- public boolean isSelectionEmpty() {
- return (minIndex > maxIndex);
- }
-
- public void addListSelectionListener(ListSelectionListener l) {
- listenerList.add(ListSelectionListener.class, l);
- }
-
- public void removeListSelectionListener(ListSelectionListener l) {
- listenerList.remove(ListSelectionListener.class, l);
- }
-
- /**
- * Returns an array of all the <code>ListSelectionListener</code>s added
- * to this OptionListModel with addListSelectionListener().
- *
- * @return all of the <code>ListSelectionListener</code>s added or an empty
- * array if no listeners have been added
- * @since 1.4
- */
- public ListSelectionListener[] getListSelectionListeners() {
- return (ListSelectionListener[])listenerList.getListeners(
- ListSelectionListener.class);
- }
-
- /**
- * Notify listeners that we are beginning or ending a
- * series of value changes
- */
- protected void fireValueChanged(boolean isAdjusting) {
- fireValueChanged(getMinSelectionIndex(), getMaxSelectionIndex(), isAdjusting);
- }
-
-
- /**
- * Notify ListSelectionListeners that the value of the selection,
- * in the closed interval firstIndex,lastIndex, has changed.
- */
- protected void fireValueChanged(int firstIndex, int lastIndex) {
- fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
- }
-
- /**
- * @param firstIndex The first index in the interval.
- * @param index1 The last index in the interval.
- * @param isAdjusting True if this is the final change in a series of them.
- * @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 (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);
- }
-
-
- // Update first and last change indices
- private void markAsDirty(int r) {
- firstChangedIndex = Math.min(firstChangedIndex, r);
- lastChangedIndex = Math.max(lastChangedIndex, r);
- }
-
- // Set the state at this index and update all relevant state.
- private void set(int r) {
- if (value.get(r)) {
- return;
- }
- value.set(r);
- Option option = (Option)get(r);
- option.setSelection(true);
- markAsDirty(r);
-
- // Update minimum and maximum indices
- minIndex = Math.min(minIndex, r);
- maxIndex = Math.max(maxIndex, r);
- }
-
- // Clear the state at this index and update all relevant state.
- private void clear(int r) {
- if (!value.get(r)) {
- return;
- }
- value.clear(r);
- Option option = (Option)get(r);
- option.setSelection(false);
- 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 leadAnchorNotificationEnabled flag.
- * When leadAnchorNotificationEnabled 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 norrowing 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.
- * @return the value of the leadAnchorNotificationEnabled 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);
- }
-
- public void clearSelection() {
- removeSelectionInterval(minIndex, maxIndex);
- }
-
- 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);
- }
-
- public void addSelectionInterval(int index0, int index1)
- {
- if (index0 == -1 || index1 == -1) {
- return;
- }
-
- if (getSelectionMode() != MULTIPLE_INTERVAL_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);
- changeSelection(clearMin, clearMax, setMin, setMax);
- }
-
-
- public void removeSelectionInterval(int index0, int index1)
- {
- if (index0 == -1 || index1 == -1) {
- return;
- }
-
- updateLeadAnchorIndices(index0, index1);
-
- int clearMin = Math.min(index0, index1);
- int clearMax = Math.max(index0, index1);
- int setMin = MAX;
- int setMax = MIN;
- 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, set all of the newly inserted
- * items, 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 = value.get(index);
- for(int i = insMinIndex; i <= insMaxIndex; i++) {
- setState(i, setInsertedValues);
- }
- }
-
-
- /**
- * 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 can be greater than 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));
- }
- }
-
-
- public void setValueIsAdjusting(boolean isAdjusting) {
- if (isAdjusting != this.isAdjusting) {
- this.isAdjusting = isAdjusting;
- this.fireValueChanged(isAdjusting);
- }
- }
-
-
- public String toString() {
- String s = ((getValueIsAdjusting()) ? "~" : "=") + value.toString();
- return getClass().getName() + " " + Integer.toString(hashCode()) + " " + s;
- }
-
- /**
- * Returns a clone of the receiver with the same selection.
- * <code>listenerLists</code> are not duplicated.
- *
- * @return a clone of the receiver
- * @exception CloneNotSupportedException if the receiver does not
- * both (a) implement the <code>Cloneable</code> interface
- * and (b) define a <code>clone</code> method
- */
- public Object clone() throws CloneNotSupportedException {
- OptionListModel clone = (OptionListModel)super.clone();
- clone.value = (BitSet)value.clone();
- clone.listenerList = new EventListenerList();
- return clone;
- }
-
- public int getAnchorSelectionIndex() {
- return anchorIndex;
- }
-
- public int getLeadSelectionIndex() {
- return leadIndex;
- }
-
- /**
- * Set the anchor selection index, leaving all selection values unchanged.
- *
- * @see #getAnchorSelectionIndex
- * @see #setLeadSelectionIndex
- */
- public void setAnchorSelectionIndex(int anchorIndex) {
- this.anchorIndex = anchorIndex;
- }
-
- /**
- * Set 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 and only
- * to later be set.
- * <p>
- * This method can be used in the mouseDragged() method of a UI class
- * to extend a selection.
- *
- * @see #getLeadSelectionIndex
- * @see #setAnchorSelectionIndex
- */
- public void setLeadSelectionIndex(int leadIndex) {
- int anchorIndex = this.anchorIndex;
- if (getSelectionMode() == SINGLE_SELECTION) {
- anchorIndex = leadIndex;
- }
-
- 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);
- if (value.get(this.anchorIndex)) {
- changeSelection(oldMin, oldMax, newMin, newMax);
- }
- else {
- changeSelection(newMin, newMax, oldMin, oldMax, false);
- }
- this.anchorIndex = anchorIndex;
- this.leadIndex = leadIndex;
- }
-
-
- /**
- * This method is responsible for storing the state
- * of the initial selection. If the selectionMode
- * is the default, i.e allowing only for SINGLE_SELECTION,
- * then the very last OPTION that has the selected
- * attribute set wins.
- */
- public void setInitialSelection(int i) {
- if (initialValue.get(i)) {
- return;
- }
- if (selectionMode == SINGLE_SELECTION) {
- // reset to empty
- initialValue.and(new BitSet());
- }
- initialValue.set(i);
- }
-
- /**
- * Fetches the BitSet that represents the initial
- * set of selected items in the list.
- */
- public BitSet getInitialSelection() {
- return initialValue;
- }
- }
-
-