0N/A/*
2362N/A * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/Apackage javax.swing.text.html;
0N/A
0N/Aimport javax.swing.*;
0N/Aimport javax.swing.event.*;
0N/Aimport java.util.EventListener;
0N/Aimport java.util.BitSet;
0N/Aimport java.io.Serializable;
0N/A
0N/A
0N/A/**
0N/A * This class extends DefaultListModel, and also implements
0N/A * the ListSelectionModel interface, allowing for it to store state
0N/A * relevant to a SELECT form element which is implemented as a List.
0N/A * If SELECT has a size attribute whose value is greater than 1,
0N/A * or if allows multiple selection then a JList is used to
0N/A * represent it and the OptionListModel is used as its model.
0N/A * It also stores the initial state of the JList, to ensure an
0N/A * accurate reset, if the user requests a reset of the form.
0N/A *
0N/A @author Sunita Mani
0N/A */
0N/A
0N/Aclass OptionListModel extends DefaultListModel implements ListSelectionModel, Serializable {
0N/A
0N/A
0N/A private static final int MIN = -1;
0N/A private static final int MAX = Integer.MAX_VALUE;
0N/A private int selectionMode = SINGLE_SELECTION;
0N/A private int minIndex = MAX;
0N/A private int maxIndex = MIN;
0N/A private int anchorIndex = -1;
0N/A private int leadIndex = -1;
0N/A private int firstChangedIndex = MAX;
0N/A private int lastChangedIndex = MIN;
0N/A private boolean isAdjusting = false;
0N/A private BitSet value = new BitSet(32);
0N/A private BitSet initialValue = new BitSet(32);
0N/A protected EventListenerList listenerList = new EventListenerList();
0N/A
0N/A protected boolean leadAnchorNotificationEnabled = true;
0N/A
0N/A public int getMinSelectionIndex() { return isSelectionEmpty() ? -1 : minIndex; }
0N/A
0N/A public int getMaxSelectionIndex() { return maxIndex; }
0N/A
0N/A public boolean getValueIsAdjusting() { return isAdjusting; }
0N/A
0N/A public int getSelectionMode() { return selectionMode; }
0N/A
0N/A public void setSelectionMode(int selectionMode) {
0N/A switch (selectionMode) {
0N/A case SINGLE_SELECTION:
0N/A case SINGLE_INTERVAL_SELECTION:
0N/A case MULTIPLE_INTERVAL_SELECTION:
0N/A this.selectionMode = selectionMode;
0N/A break;
0N/A default:
0N/A throw new IllegalArgumentException("invalid selectionMode");
0N/A }
0N/A }
0N/A
0N/A public boolean isSelectedIndex(int index) {
0N/A return ((index < minIndex) || (index > maxIndex)) ? false : value.get(index);
0N/A }
0N/A
0N/A public boolean isSelectionEmpty() {
0N/A return (minIndex > maxIndex);
0N/A }
0N/A
0N/A public void addListSelectionListener(ListSelectionListener l) {
0N/A listenerList.add(ListSelectionListener.class, l);
0N/A }
0N/A
0N/A public void removeListSelectionListener(ListSelectionListener l) {
0N/A listenerList.remove(ListSelectionListener.class, l);
0N/A }
0N/A
0N/A /**
0N/A * Returns an array of all the <code>ListSelectionListener</code>s added
0N/A * to this OptionListModel with addListSelectionListener().
0N/A *
0N/A * @return all of the <code>ListSelectionListener</code>s added or an empty
0N/A * array if no listeners have been added
0N/A * @since 1.4
0N/A */
0N/A public ListSelectionListener[] getListSelectionListeners() {
611N/A return listenerList.getListeners(ListSelectionListener.class);
0N/A }
0N/A
0N/A /**
0N/A * Notify listeners that we are beginning or ending a
0N/A * series of value changes
0N/A */
0N/A protected void fireValueChanged(boolean isAdjusting) {
0N/A fireValueChanged(getMinSelectionIndex(), getMaxSelectionIndex(), isAdjusting);
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Notify ListSelectionListeners that the value of the selection,
0N/A * in the closed interval firstIndex,lastIndex, has changed.
0N/A */
0N/A protected void fireValueChanged(int firstIndex, int lastIndex) {
0N/A fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
0N/A }
0N/A
0N/A /**
0N/A * @param firstIndex The first index in the interval.
611N/A * @param lastIndex The last index in the interval.
0N/A * @param isAdjusting True if this is the final change in a series of them.
0N/A * @see EventListenerList
0N/A */
0N/A protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting)
0N/A {
0N/A Object[] listeners = listenerList.getListenerList();
0N/A ListSelectionEvent e = null;
0N/A
0N/A for (int i = listeners.length - 2; i >= 0; i -= 2) {
0N/A if (listeners[i] == ListSelectionListener.class) {
0N/A if (e == null) {
0N/A e = new ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting);
0N/A }
0N/A ((ListSelectionListener)listeners[i+1]).valueChanged(e);
0N/A }
0N/A }
0N/A }
0N/A
0N/A private void fireValueChanged() {
0N/A if (lastChangedIndex == MIN) {
0N/A return;
0N/A }
0N/A /* Change the values before sending the event to the
0N/A * listeners in case the event causes a listener to make
0N/A * another change to the selection.
0N/A */
0N/A int oldFirstChangedIndex = firstChangedIndex;
0N/A int oldLastChangedIndex = lastChangedIndex;
0N/A firstChangedIndex = MAX;
0N/A lastChangedIndex = MIN;
0N/A fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex);
0N/A }
0N/A
0N/A
0N/A // Update first and last change indices
0N/A private void markAsDirty(int r) {
0N/A firstChangedIndex = Math.min(firstChangedIndex, r);
0N/A lastChangedIndex = Math.max(lastChangedIndex, r);
0N/A }
0N/A
0N/A // Set the state at this index and update all relevant state.
0N/A private void set(int r) {
0N/A if (value.get(r)) {
0N/A return;
0N/A }
0N/A value.set(r);
0N/A Option option = (Option)get(r);
0N/A option.setSelection(true);
0N/A markAsDirty(r);
0N/A
0N/A // Update minimum and maximum indices
0N/A minIndex = Math.min(minIndex, r);
0N/A maxIndex = Math.max(maxIndex, r);
0N/A }
0N/A
0N/A // Clear the state at this index and update all relevant state.
0N/A private void clear(int r) {
0N/A if (!value.get(r)) {
0N/A return;
0N/A }
0N/A value.clear(r);
0N/A Option option = (Option)get(r);
0N/A option.setSelection(false);
0N/A markAsDirty(r);
0N/A
0N/A // Update minimum and maximum indices
0N/A /*
0N/A If (r > minIndex) the minimum has not changed.
0N/A The case (r < minIndex) is not possible because r'th value was set.
0N/A We only need to check for the case when lowest entry has been cleared,
0N/A and in this case we need to search for the first value set above it.
0N/A */
0N/A if (r == minIndex) {
0N/A for(minIndex = minIndex + 1; minIndex <= maxIndex; minIndex++) {
0N/A if (value.get(minIndex)) {
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A /*
0N/A If (r < maxIndex) the maximum has not changed.
0N/A The case (r > maxIndex) is not possible because r'th value was set.
0N/A We only need to check for the case when highest entry has been cleared,
0N/A and in this case we need to search for the first value set below it.
0N/A */
0N/A if (r == maxIndex) {
0N/A for(maxIndex = maxIndex - 1; minIndex <= maxIndex; maxIndex--) {
0N/A if (value.get(maxIndex)) {
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A /* Performance note: This method is called from inside a loop in
0N/A changeSelection() but we will only iterate in the loops
0N/A above on the basis of one iteration per deselected cell - in total.
0N/A Ie. the next time this method is called the work of the previous
0N/A deselection will not be repeated.
0N/A
0N/A We also don't need to worry about the case when the min and max
0N/A values are in their unassigned states. This cannot happen because
0N/A this method's initial check ensures that the selection was not empty
0N/A and therefore that the minIndex and maxIndex had 'real' values.
0N/A
0N/A If we have cleared the whole selection, set the minIndex and maxIndex
0N/A to their cannonical values so that the next set command always works
0N/A just by using Math.min and Math.max.
0N/A */
0N/A if (isSelectionEmpty()) {
0N/A minIndex = MAX;
0N/A maxIndex = MIN;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Sets the value of the leadAnchorNotificationEnabled flag.
0N/A * @see #isLeadAnchorNotificationEnabled()
0N/A */
0N/A public void setLeadAnchorNotificationEnabled(boolean flag) {
0N/A leadAnchorNotificationEnabled = flag;
0N/A }
0N/A
0N/A /**
0N/A * Returns the value of the leadAnchorNotificationEnabled flag.
0N/A * When leadAnchorNotificationEnabled is true the model
0N/A * generates notification events with bounds that cover all the changes to
0N/A * the selection plus the changes to the lead and anchor indices.
0N/A * Setting the flag to false causes a norrowing of the event's bounds to
0N/A * include only the elements that have been selected or deselected since
0N/A * the last change. Either way, the model continues to maintain the lead
0N/A * and anchor variables internally. The default is true.
0N/A * @return the value of the leadAnchorNotificationEnabled flag
0N/A * @see #setLeadAnchorNotificationEnabled(boolean)
0N/A */
0N/A public boolean isLeadAnchorNotificationEnabled() {
0N/A return leadAnchorNotificationEnabled;
0N/A }
0N/A
0N/A private void updateLeadAnchorIndices(int anchorIndex, int leadIndex) {
0N/A if (leadAnchorNotificationEnabled) {
0N/A if (this.anchorIndex != anchorIndex) {
0N/A if (this.anchorIndex != -1) { // The unassigned state.
0N/A markAsDirty(this.anchorIndex);
0N/A }
0N/A markAsDirty(anchorIndex);
0N/A }
0N/A
0N/A if (this.leadIndex != leadIndex) {
0N/A if (this.leadIndex != -1) { // The unassigned state.
0N/A markAsDirty(this.leadIndex);
0N/A }
0N/A markAsDirty(leadIndex);
0N/A }
0N/A }
0N/A this.anchorIndex = anchorIndex;
0N/A this.leadIndex = leadIndex;
0N/A }
0N/A
0N/A private boolean contains(int a, int b, int i) {
0N/A return (i >= a) && (i <= b);
0N/A }
0N/A
0N/A private void changeSelection(int clearMin, int clearMax,
0N/A int setMin, int setMax, boolean clearFirst) {
0N/A for(int i = Math.min(setMin, clearMin); i <= Math.max(setMax, clearMax); i++) {
0N/A
0N/A boolean shouldClear = contains(clearMin, clearMax, i);
0N/A boolean shouldSet = contains(setMin, setMax, i);
0N/A
0N/A if (shouldSet && shouldClear) {
0N/A if (clearFirst) {
0N/A shouldClear = false;
0N/A }
0N/A else {
0N/A shouldSet = false;
0N/A }
0N/A }
0N/A
0N/A if (shouldSet) {
0N/A set(i);
0N/A }
0N/A if (shouldClear) {
0N/A clear(i);
0N/A }
0N/A }
0N/A fireValueChanged();
0N/A }
0N/A
0N/A /* Change the selection with the effect of first clearing the values
0N/A * in the inclusive range [clearMin, clearMax] then setting the values
0N/A * in the inclusive range [setMin, setMax]. Do this in one pass so
0N/A * that no values are cleared if they would later be set.
0N/A */
0N/A private void changeSelection(int clearMin, int clearMax, int setMin, int setMax) {
0N/A changeSelection(clearMin, clearMax, setMin, setMax, true);
0N/A }
0N/A
0N/A public void clearSelection() {
0N/A removeSelectionInterval(minIndex, maxIndex);
0N/A }
0N/A
0N/A public void setSelectionInterval(int index0, int index1) {
0N/A if (index0 == -1 || index1 == -1) {
0N/A return;
0N/A }
0N/A
0N/A if (getSelectionMode() == SINGLE_SELECTION) {
0N/A index0 = index1;
0N/A }
0N/A
0N/A updateLeadAnchorIndices(index0, index1);
0N/A
0N/A int clearMin = minIndex;
0N/A int clearMax = maxIndex;
0N/A int setMin = Math.min(index0, index1);
0N/A int setMax = Math.max(index0, index1);
0N/A changeSelection(clearMin, clearMax, setMin, setMax);
0N/A }
0N/A
0N/A public void addSelectionInterval(int index0, int index1)
0N/A {
0N/A if (index0 == -1 || index1 == -1) {
0N/A return;
0N/A }
0N/A
0N/A if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {
0N/A setSelectionInterval(index0, index1);
0N/A return;
0N/A }
0N/A
0N/A updateLeadAnchorIndices(index0, index1);
0N/A
0N/A int clearMin = MAX;
0N/A int clearMax = MIN;
0N/A int setMin = Math.min(index0, index1);
0N/A int setMax = Math.max(index0, index1);
0N/A changeSelection(clearMin, clearMax, setMin, setMax);
0N/A }
0N/A
0N/A
0N/A public void removeSelectionInterval(int index0, int index1)
0N/A {
0N/A if (index0 == -1 || index1 == -1) {
0N/A return;
0N/A }
0N/A
0N/A updateLeadAnchorIndices(index0, index1);
0N/A
0N/A int clearMin = Math.min(index0, index1);
0N/A int clearMax = Math.max(index0, index1);
0N/A int setMin = MAX;
0N/A int setMax = MIN;
0N/A changeSelection(clearMin, clearMax, setMin, setMax);
0N/A }
0N/A
0N/A private void setState(int index, boolean state) {
0N/A if (state) {
0N/A set(index);
0N/A }
0N/A else {
0N/A clear(index);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Insert length indices beginning before/after index. If the value
0N/A * at index is itself selected, set all of the newly inserted
0N/A * items, otherwise leave them unselected. This method is typically
0N/A * called to sync the selection model with a corresponding change
0N/A * in the data model.
0N/A */
0N/A public void insertIndexInterval(int index, int length, boolean before)
0N/A {
0N/A /* The first new index will appear at insMinIndex and the last
0N/A * one will appear at insMaxIndex
0N/A */
0N/A int insMinIndex = (before) ? index : index + 1;
0N/A int insMaxIndex = (insMinIndex + length) - 1;
0N/A
0N/A /* Right shift the entire bitset by length, beginning with
0N/A * index-1 if before is true, index+1 if it's false (i.e. with
0N/A * insMinIndex).
0N/A */
0N/A for(int i = maxIndex; i >= insMinIndex; i--) {
0N/A setState(i + length, value.get(i));
0N/A }
0N/A
0N/A /* Initialize the newly inserted indices.
0N/A */
0N/A boolean setInsertedValues = value.get(index);
0N/A for(int i = insMinIndex; i <= insMaxIndex; i++) {
0N/A setState(i, setInsertedValues);
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Remove the indices in the interval index0,index1 (inclusive) from
0N/A * the selection model. This is typically called to sync the selection
0N/A * model width a corresponding change in the data model. Note
0N/A * that (as always) index0 can be greater than index1.
0N/A */
0N/A public void removeIndexInterval(int index0, int index1)
0N/A {
0N/A int rmMinIndex = Math.min(index0, index1);
0N/A int rmMaxIndex = Math.max(index0, index1);
0N/A int gapLength = (rmMaxIndex - rmMinIndex) + 1;
0N/A
0N/A /* Shift the entire bitset to the left to close the index0, index1
0N/A * gap.
0N/A */
0N/A for(int i = rmMinIndex; i <= maxIndex; i++) {
0N/A setState(i, value.get(i + gapLength));
0N/A }
0N/A }
0N/A
0N/A
0N/A public void setValueIsAdjusting(boolean isAdjusting) {
0N/A if (isAdjusting != this.isAdjusting) {
0N/A this.isAdjusting = isAdjusting;
0N/A this.fireValueChanged(isAdjusting);
0N/A }
0N/A }
0N/A
0N/A
0N/A public String toString() {
0N/A String s = ((getValueIsAdjusting()) ? "~" : "=") + value.toString();
0N/A return getClass().getName() + " " + Integer.toString(hashCode()) + " " + s;
0N/A }
0N/A
0N/A /**
0N/A * Returns a clone of the receiver with the same selection.
0N/A * <code>listenerLists</code> are not duplicated.
0N/A *
0N/A * @return a clone of the receiver
0N/A * @exception CloneNotSupportedException if the receiver does not
0N/A * both (a) implement the <code>Cloneable</code> interface
0N/A * and (b) define a <code>clone</code> method
0N/A */
0N/A public Object clone() throws CloneNotSupportedException {
0N/A OptionListModel clone = (OptionListModel)super.clone();
0N/A clone.value = (BitSet)value.clone();
0N/A clone.listenerList = new EventListenerList();
0N/A return clone;
0N/A }
0N/A
0N/A public int getAnchorSelectionIndex() {
0N/A return anchorIndex;
0N/A }
0N/A
0N/A public int getLeadSelectionIndex() {
0N/A return leadIndex;
0N/A }
0N/A
0N/A /**
0N/A * Set the anchor selection index, leaving all selection values unchanged.
0N/A *
0N/A * @see #getAnchorSelectionIndex
0N/A * @see #setLeadSelectionIndex
0N/A */
0N/A public void setAnchorSelectionIndex(int anchorIndex) {
0N/A this.anchorIndex = anchorIndex;
0N/A }
0N/A
0N/A /**
0N/A * Set the lead selection index, ensuring that values between the
0N/A * anchor and the new lead are either all selected or all deselected.
0N/A * If the value at the anchor index is selected, first clear all the
0N/A * values in the range [anchor, oldLeadIndex], then select all the values
0N/A * values in the range [anchor, newLeadIndex], where oldLeadIndex is the old
0N/A * leadIndex and newLeadIndex is the new one.
0N/A * <p>
0N/A * If the value at the anchor index is not selected, do the same thing in reverse,
0N/A * selecting values in the old range and deslecting values in the new one.
0N/A * <p>
0N/A * Generate a single event for this change and notify all listeners.
0N/A * For the purposes of generating minimal bounds in this event, do the
0N/A * operation in a single pass; that way the first and last index inside the
0N/A * ListSelectionEvent that is broadcast will refer to cells that actually
0N/A * changed value because of this method. If, instead, this operation were
0N/A * done in two steps the effect on the selection state would be the same
0N/A * but two events would be generated and the bounds around the changed values
0N/A * would be wider, including cells that had been first cleared and only
0N/A * to later be set.
0N/A * <p>
0N/A * This method can be used in the mouseDragged() method of a UI class
0N/A * to extend a selection.
0N/A *
0N/A * @see #getLeadSelectionIndex
0N/A * @see #setAnchorSelectionIndex
0N/A */
0N/A public void setLeadSelectionIndex(int leadIndex) {
0N/A int anchorIndex = this.anchorIndex;
0N/A if (getSelectionMode() == SINGLE_SELECTION) {
0N/A anchorIndex = leadIndex;
0N/A }
0N/A
611N/A int oldMin = Math.min(this.anchorIndex, this.leadIndex);
611N/A int oldMax = Math.max(this.anchorIndex, this.leadIndex);
0N/A int newMin = Math.min(anchorIndex, leadIndex);
0N/A int newMax = Math.max(anchorIndex, leadIndex);
0N/A if (value.get(this.anchorIndex)) {
0N/A changeSelection(oldMin, oldMax, newMin, newMax);
0N/A }
0N/A else {
0N/A changeSelection(newMin, newMax, oldMin, oldMax, false);
0N/A }
0N/A this.anchorIndex = anchorIndex;
0N/A this.leadIndex = leadIndex;
0N/A }
0N/A
0N/A
0N/A /**
0N/A * This method is responsible for storing the state
0N/A * of the initial selection. If the selectionMode
0N/A * is the default, i.e allowing only for SINGLE_SELECTION,
0N/A * then the very last OPTION that has the selected
0N/A * attribute set wins.
0N/A */
0N/A public void setInitialSelection(int i) {
0N/A if (initialValue.get(i)) {
0N/A return;
0N/A }
0N/A if (selectionMode == SINGLE_SELECTION) {
0N/A // reset to empty
0N/A initialValue.and(new BitSet());
0N/A }
0N/A initialValue.set(i);
0N/A }
0N/A
0N/A /**
0N/A * Fetches the BitSet that represents the initial
0N/A * set of selected items in the list.
0N/A */
0N/A public BitSet getInitialSelection() {
0N/A return initialValue;
0N/A }
0N/A}