KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > html > OptionListModel


1 /*
2  * @(#)OptionListModel.java 1.11 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text.html;
8
9 import javax.swing.*;
10 import javax.swing.event.*;
11 import java.util.EventListener JavaDoc;
12 import java.util.BitSet JavaDoc;
13 import java.io.Serializable JavaDoc;
14
15
16 /**
17  * This class extends DefaultListModel, and also implements
18  * the ListSelectionModel interface, allowing for it to store state
19  * relevant to a SELECT form element which is implemented as a List.
20  * If SELECT has a size attribute whose value is greater than 1,
21  * or if allows multiple selection then a JList is used to
22  * represent it and the OptionListModel is used as its model.
23  * It also stores the initial state of the JList, to ensure an
24  * accurate reset, if the user requests a reset of the form.
25  *
26   @author Sunita Mani
27   @version 1.11 12/19/03
28  */

29
30 class OptionListModel extends DefaultListModel implements ListSelectionModel, Serializable JavaDoc {
31
32   
33     private static final int MIN = -1;
34     private static final int MAX = Integer.MAX_VALUE;
35     private int selectionMode = SINGLE_SELECTION;
36     private int minIndex = MAX;
37     private int maxIndex = MIN;
38     private int anchorIndex = -1;
39     private int leadIndex = -1;
40     private int firstChangedIndex = MAX;
41     private int lastChangedIndex = MIN;
42     private boolean isAdjusting = false;
43     private BitSet JavaDoc value = new BitSet JavaDoc(32);
44     private BitSet JavaDoc initialValue = new BitSet JavaDoc(32);
45     protected EventListenerList listenerList = new EventListenerList();
46
47     protected boolean leadAnchorNotificationEnabled = true;
48
49     public int getMinSelectionIndex() { return isSelectionEmpty() ? -1 : minIndex; }
50
51     public int getMaxSelectionIndex() { return maxIndex; }
52
53     public boolean getValueIsAdjusting() { return isAdjusting; }
54
55     public int getSelectionMode() { return selectionMode; }
56
57     public void setSelectionMode(int selectionMode) {
58     switch (selectionMode) {
59     case SINGLE_SELECTION:
60     case SINGLE_INTERVAL_SELECTION:
61     case MULTIPLE_INTERVAL_SELECTION:
62         this.selectionMode = selectionMode;
63         break;
64     default:
65         throw new IllegalArgumentException JavaDoc("invalid selectionMode");
66     }
67     }
68
69     public boolean isSelectedIndex(int index) {
70     return ((index < minIndex) || (index > maxIndex)) ? false : value.get(index);
71     }
72
73     public boolean isSelectionEmpty() {
74     return (minIndex > maxIndex);
75     }
76
77     public void addListSelectionListener(ListSelectionListener l) {
78     listenerList.add(ListSelectionListener.class, l);
79     }
80
81     public void removeListSelectionListener(ListSelectionListener l) {
82     listenerList.remove(ListSelectionListener.class, l);
83     }
84
85     /**
86      * Returns an array of all the <code>ListSelectionListener</code>s added
87      * to this OptionListModel with addListSelectionListener().
88      *
89      * @return all of the <code>ListSelectionListener</code>s added or an empty
90      * array if no listeners have been added
91      * @since 1.4
92      */

93     public ListSelectionListener[] getListSelectionListeners() {
94         return (ListSelectionListener[])listenerList.getListeners(
95                 ListSelectionListener.class);
96     }
97
98     /**
99      * Notify listeners that we are beginning or ending a
100      * series of value changes
101      */

102     protected void fireValueChanged(boolean isAdjusting) {
103     fireValueChanged(getMinSelectionIndex(), getMaxSelectionIndex(), isAdjusting);
104     }
105
106
107     /**
108      * Notify ListSelectionListeners that the value of the selection,
109      * in the closed interval firstIndex,lastIndex, has changed.
110      */

111     protected void fireValueChanged(int firstIndex, int lastIndex) {
112     fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
113     }
114
115     /**
116      * @param firstIndex The first index in the interval.
117      * @param index1 The last index in the interval.
118      * @param isAdjusting True if this is the final change in a series of them.
119      * @see EventListenerList
120      */

121     protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting)
122     {
123     Object JavaDoc[] listeners = listenerList.getListenerList();
124     ListSelectionEvent e = null;
125
126     for (int i = listeners.length - 2; i >= 0; i -= 2) {
127         if (listeners[i] == ListSelectionListener.class) {
128         if (e == null) {
129             e = new ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting);
130         }
131         ((ListSelectionListener)listeners[i+1]).valueChanged(e);
132         }
133     }
134     }
135
136     private void fireValueChanged() {
137     if (lastChangedIndex == MIN) {
138         return;
139     }
140     /* Change the values before sending the event to the
141      * listeners in case the event causes a listener to make
142      * another change to the selection.
143      */

144     int oldFirstChangedIndex = firstChangedIndex;
145     int oldLastChangedIndex = lastChangedIndex;
146     firstChangedIndex = MAX;
147     lastChangedIndex = MIN;
148         fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex);
149     }
150
151
152     // Update first and last change indices
153
private void markAsDirty(int r) {
154         firstChangedIndex = Math.min(firstChangedIndex, r);
155     lastChangedIndex = Math.max(lastChangedIndex, r);
156     }
157
158     // Set the state at this index and update all relevant state.
159
private void set(int r) {
160     if (value.get(r)) {
161         return;
162     }
163     value.set(r);
164     Option JavaDoc option = (Option JavaDoc)get(r);
165     option.setSelection(true);
166         markAsDirty(r);
167
168         // Update minimum and maximum indices
169
minIndex = Math.min(minIndex, r);
170         maxIndex = Math.max(maxIndex, r);
171     }
172
173     // Clear the state at this index and update all relevant state.
174
private void clear(int r) {
175     if (!value.get(r)) {
176         return;
177     }
178     value.clear(r);
179     Option JavaDoc option = (Option JavaDoc)get(r);
180     option.setSelection(false);
181         markAsDirty(r);
182
183         // Update minimum and maximum indices
184
/*
185            If (r > minIndex) the minimum has not changed.
186            The case (r < minIndex) is not possible because r'th value was set.
187            We only need to check for the case when lowest entry has been cleared,
188            and in this case we need to search for the first value set above it.
189     */

190     if (r == minIndex) {
191         for(minIndex = minIndex + 1; minIndex <= maxIndex; minIndex++) {
192             if (value.get(minIndex)) {
193                 break;
194             }
195         }
196     }
197         /*
198            If (r < maxIndex) the maximum has not changed.
199            The case (r > maxIndex) is not possible because r'th value was set.
200            We only need to check for the case when highest entry has been cleared,
201            and in this case we need to search for the first value set below it.
202     */

203     if (r == maxIndex) {
204         for(maxIndex = maxIndex - 1; minIndex <= maxIndex; maxIndex--) {
205             if (value.get(maxIndex)) {
206                 break;
207             }
208         }
209     }
210     /* Performance note: This method is called from inside a loop in
211        changeSelection() but we will only iterate in the loops
212        above on the basis of one iteration per deselected cell - in total.
213        Ie. the next time this method is called the work of the previous
214        deselection will not be repeated.
215
216        We also don't need to worry about the case when the min and max
217        values are in their unassigned states. This cannot happen because
218        this method's initial check ensures that the selection was not empty
219        and therefore that the minIndex and maxIndex had 'real' values.
220
221        If we have cleared the whole selection, set the minIndex and maxIndex
222        to their cannonical values so that the next set command always works
223        just by using Math.min and Math.max.
224     */

225         if (isSelectionEmpty()) {
226             minIndex = MAX;
227             maxIndex = MIN;
228         }
229     }
230
231     /**
232      * Sets the value of the leadAnchorNotificationEnabled flag.
233      * @see #isLeadAnchorNotificationEnabled()
234      */

235     public void setLeadAnchorNotificationEnabled(boolean flag) {
236         leadAnchorNotificationEnabled = flag;
237     }
238
239     /**
240      * Returns the value of the leadAnchorNotificationEnabled flag.
241      * When leadAnchorNotificationEnabled is true the model
242      * generates notification events with bounds that cover all the changes to
243      * the selection plus the changes to the lead and anchor indices.
244      * Setting the flag to false causes a norrowing of the event's bounds to
245      * include only the elements that have been selected or deselected since
246      * the last change. Either way, the model continues to maintain the lead
247      * and anchor variables internally. The default is true.
248      * @return the value of the leadAnchorNotificationEnabled flag
249      * @see #setLeadAnchorNotificationEnabled(boolean)
250      */

251     public boolean isLeadAnchorNotificationEnabled() {
252         return leadAnchorNotificationEnabled;
253     }
254
255     private void updateLeadAnchorIndices(int anchorIndex, int leadIndex) {
256         if (leadAnchorNotificationEnabled) {
257             if (this.anchorIndex != anchorIndex) {
258                 if (this.anchorIndex != -1) { // The unassigned state.
259
markAsDirty(this.anchorIndex);
260                 }
261                 markAsDirty(anchorIndex);
262             }
263
264             if (this.leadIndex != leadIndex) {
265                 if (this.leadIndex != -1) { // The unassigned state.
266
markAsDirty(this.leadIndex);
267                 }
268                 markAsDirty(leadIndex);
269             }
270         }
271         this.anchorIndex = anchorIndex;
272         this.leadIndex = leadIndex;
273     }
274
275     private boolean contains(int a, int b, int i) {
276         return (i >= a) && (i <= b);
277     }
278
279     private void changeSelection(int clearMin, int clearMax,
280                                  int setMin, int setMax, boolean clearFirst) {
281         for(int i = Math.min(setMin, clearMin); i <= Math.max(setMax, clearMax); i++) {
282
283             boolean shouldClear = contains(clearMin, clearMax, i);
284             boolean shouldSet = contains(setMin, setMax, i);
285
286             if (shouldSet && shouldClear) {
287                 if (clearFirst) {
288                     shouldClear = false;
289                 }
290                 else {
291                     shouldSet = false;
292                 }
293             }
294
295             if (shouldSet) {
296                 set(i);
297             }
298             if (shouldClear) {
299                 clear(i);
300             }
301         }
302         fireValueChanged();
303     }
304
305    /* Change the selection with the effect of first clearing the values
306     * in the inclusive range [clearMin, clearMax] then setting the values
307     * in the inclusive range [setMin, setMax]. Do this in one pass so
308     * that no values are cleared if they would later be set.
309     */

310     private void changeSelection(int clearMin, int clearMax, int setMin, int setMax) {
311         changeSelection(clearMin, clearMax, setMin, setMax, true);
312     }
313
314     public void clearSelection() {
315     removeSelectionInterval(minIndex, maxIndex);
316     }
317
318     public void setSelectionInterval(int index0, int index1) {
319         if (index0 == -1 || index1 == -1) {
320             return;
321         }
322
323         if (getSelectionMode() == SINGLE_SELECTION) {
324             index0 = index1;
325         }
326
327         updateLeadAnchorIndices(index0, index1);
328
329         int clearMin = minIndex;
330         int clearMax = maxIndex;
331     int setMin = Math.min(index0, index1);
332     int setMax = Math.max(index0, index1);
333         changeSelection(clearMin, clearMax, setMin, setMax);
334     }
335
336     public void addSelectionInterval(int index0, int index1)
337     {
338         if (index0 == -1 || index1 == -1) {
339             return;
340         }
341
342         if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {
343             setSelectionInterval(index0, index1);
344             return;
345         }
346
347         updateLeadAnchorIndices(index0, index1);
348
349         int clearMin = MAX;
350         int clearMax = MIN;
351     int setMin = Math.min(index0, index1);
352     int setMax = Math.max(index0, index1);
353         changeSelection(clearMin, clearMax, setMin, setMax);
354     }
355
356
357     public void removeSelectionInterval(int index0, int index1)
358     {
359         if (index0 == -1 || index1 == -1) {
360             return;
361         }
362
363         updateLeadAnchorIndices(index0, index1);
364
365     int clearMin = Math.min(index0, index1);
366     int clearMax = Math.max(index0, index1);
367     int setMin = MAX;
368     int setMax = MIN;
369         changeSelection(clearMin, clearMax, setMin, setMax);
370     }
371
372     private void setState(int index, boolean state) {
373         if (state) {
374         set(index);
375     }
376     else {
377         clear(index);
378     }
379     }
380
381     /**
382      * Insert length indices beginning before/after index. If the value
383      * at index is itself selected, set all of the newly inserted
384      * items, otherwise leave them unselected. This method is typically
385      * called to sync the selection model with a corresponding change
386      * in the data model.
387      */

388     public void insertIndexInterval(int index, int length, boolean before)
389     {
390     /* The first new index will appear at insMinIndex and the last
391      * one will appear at insMaxIndex
392      */

393     int insMinIndex = (before) ? index : index + 1;
394     int insMaxIndex = (insMinIndex + length) - 1;
395
396     /* Right shift the entire bitset by length, beginning with
397      * index-1 if before is true, index+1 if it's false (i.e. with
398      * insMinIndex).
399      */

400     for(int i = maxIndex; i >= insMinIndex; i--) {
401         setState(i + length, value.get(i));
402     }
403
404     /* Initialize the newly inserted indices.
405      */

406         boolean setInsertedValues = value.get(index);
407     for(int i = insMinIndex; i <= insMaxIndex; i++) {
408         setState(i, setInsertedValues);
409     }
410     }
411
412
413     /**
414      * Remove the indices in the interval index0,index1 (inclusive) from
415      * the selection model. This is typically called to sync the selection
416      * model width a corresponding change in the data model. Note
417      * that (as always) index0 can be greater than index1.
418      */

419     public void removeIndexInterval(int index0, int index1)
420     {
421     int rmMinIndex = Math.min(index0, index1);
422     int rmMaxIndex = Math.max(index0, index1);
423     int gapLength = (rmMaxIndex - rmMinIndex) + 1;
424
425     /* Shift the entire bitset to the left to close the index0, index1
426      * gap.
427      */

428     for(int i = rmMinIndex; i <= maxIndex; i++) {
429         setState(i, value.get(i + gapLength));
430     }
431     }
432
433
434     public void setValueIsAdjusting(boolean isAdjusting) {
435     if (isAdjusting != this.isAdjusting) {
436         this.isAdjusting = isAdjusting;
437         this.fireValueChanged(isAdjusting);
438     }
439     }
440
441
442     public String JavaDoc toString() {
443     String JavaDoc s = ((getValueIsAdjusting()) ? "~" : "=") + value.toString();
444     return getClass().getName() + " " + Integer.toString(hashCode()) + " " + s;
445     }
446
447     /**
448      * Returns a clone of the receiver with the same selection.
449      * <code>listenerLists</code> are not duplicated.
450      *
451      * @return a clone of the receiver
452      * @exception CloneNotSupportedException if the receiver does not
453      * both (a) implement the <code>Cloneable</code> interface
454      * and (b) define a <code>clone</code> method
455      */

456     public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
457     OptionListModel JavaDoc clone = (OptionListModel JavaDoc)super.clone();
458     clone.value = (BitSet JavaDoc)value.clone();
459     clone.listenerList = new EventListenerList();
460     return clone;
461     }
462
463     public int getAnchorSelectionIndex() {
464         return anchorIndex;
465     }
466
467     public int getLeadSelectionIndex() {
468         return leadIndex;
469     }
470
471     /**
472      * Set the anchor selection index, leaving all selection values unchanged.
473      *
474      * @see #getAnchorSelectionIndex
475      * @see #setLeadSelectionIndex
476      */

477     public void setAnchorSelectionIndex(int anchorIndex) {
478         this.anchorIndex = anchorIndex;
479     }
480
481     /**
482      * Set the lead selection index, ensuring that values between the
483      * anchor and the new lead are either all selected or all deselected.
484      * If the value at the anchor index is selected, first clear all the
485      * values in the range [anchor, oldLeadIndex], then select all the values
486      * values in the range [anchor, newLeadIndex], where oldLeadIndex is the old
487      * leadIndex and newLeadIndex is the new one.
488      * <p>
489      * If the value at the anchor index is not selected, do the same thing in reverse,
490      * selecting values in the old range and deslecting values in the new one.
491      * <p>
492      * Generate a single event for this change and notify all listeners.
493      * For the purposes of generating minimal bounds in this event, do the
494      * operation in a single pass; that way the first and last index inside the
495      * ListSelectionEvent that is broadcast will refer to cells that actually
496      * changed value because of this method. If, instead, this operation were
497      * done in two steps the effect on the selection state would be the same
498      * but two events would be generated and the bounds around the changed values
499      * would be wider, including cells that had been first cleared and only
500      * to later be set.
501      * <p>
502      * This method can be used in the mouseDragged() method of a UI class
503      * to extend a selection.
504      *
505      * @see #getLeadSelectionIndex
506      * @see #setAnchorSelectionIndex
507      */

508     public void setLeadSelectionIndex(int leadIndex) {
509         int anchorIndex = this.anchorIndex;
510         if (getSelectionMode() == SINGLE_SELECTION) {
511             anchorIndex = leadIndex;
512         }
513
514         int oldMin = Math.min(this.anchorIndex, this.leadIndex);;
515         int oldMax = Math.max(this.anchorIndex, this.leadIndex);;
516     int newMin = Math.min(anchorIndex, leadIndex);
517     int newMax = Math.max(anchorIndex, leadIndex);
518         if (value.get(this.anchorIndex)) {
519             changeSelection(oldMin, oldMax, newMin, newMax);
520         }
521         else {
522             changeSelection(newMin, newMax, oldMin, oldMax, false);
523         }
524         this.anchorIndex = anchorIndex;
525         this.leadIndex = leadIndex;
526     }
527
528
529     /**
530      * This method is responsible for storing the state
531      * of the initial selection. If the selectionMode
532      * is the default, i.e allowing only for SINGLE_SELECTION,
533      * then the very last OPTION that has the selected
534      * attribute set wins.
535      */

536     public void setInitialSelection(int i) {
537     if (initialValue.get(i)) {
538         return;
539     }
540     if (selectionMode == SINGLE_SELECTION) {
541         // reset to empty
542
initialValue.and(new BitSet JavaDoc());
543     }
544     initialValue.set(i);
545     }
546
547     /**
548      * Fetches the BitSet that represents the initial
549      * set of selected items in the list.
550      */

551     public BitSet JavaDoc getInitialSelection() {
552     return initialValue;
553     }
554 }
555
556
557
Popular Tags