/* | 
|
 * Copyright (c) 1998, 2013, 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.text.html;  | 
|
import javax.swing.*;  | 
|
import javax.swing.event.*;  | 
|
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  | 
|
*/  | 
|
class OptionListModel<E> extends DefaultListModel<E> 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 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 lastIndex 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 deselecting 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;  | 
|
}  | 
|
}  |