001 /*
002 * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025 package javax.swing.text.html;
026
027 import javax.swing.*;
028 import javax.swing.event.*;
029 import java.util.EventListener;
030 import java.util.BitSet;
031 import java.io.Serializable;
032
033 /**
034 * This class extends DefaultListModel, and also implements
035 * the ListSelectionModel interface, allowing for it to store state
036 * relevant to a SELECT form element which is implemented as a List.
037 * If SELECT has a size attribute whose value is greater than 1,
038 * or if allows multiple selection then a JList is used to
039 * represent it and the OptionListModel is used as its model.
040 * It also stores the initial state of the JList, to ensure an
041 * accurate reset, if the user requests a reset of the form.
042 *
043 @author Sunita Mani
044 @version 1.18 05/05/07
045 */
046
047 class OptionListModel extends DefaultListModel implements
048 ListSelectionModel, Serializable {
049
050 private static final int MIN = -1;
051 private static final int MAX = Integer.MAX_VALUE;
052 private int selectionMode = SINGLE_SELECTION;
053 private int minIndex = MAX;
054 private int maxIndex = MIN;
055 private int anchorIndex = -1;
056 private int leadIndex = -1;
057 private int firstChangedIndex = MAX;
058 private int lastChangedIndex = MIN;
059 private boolean isAdjusting = false;
060 private BitSet value = new BitSet(32);
061 private BitSet initialValue = new BitSet(32);
062 protected EventListenerList listenerList = new EventListenerList();
063
064 protected boolean leadAnchorNotificationEnabled = true;
065
066 public int getMinSelectionIndex() {
067 return isSelectionEmpty() ? -1 : minIndex;
068 }
069
070 public int getMaxSelectionIndex() {
071 return maxIndex;
072 }
073
074 public boolean getValueIsAdjusting() {
075 return isAdjusting;
076 }
077
078 public int getSelectionMode() {
079 return selectionMode;
080 }
081
082 public void setSelectionMode(int selectionMode) {
083 switch (selectionMode) {
084 case SINGLE_SELECTION:
085 case SINGLE_INTERVAL_SELECTION:
086 case MULTIPLE_INTERVAL_SELECTION:
087 this .selectionMode = selectionMode;
088 break;
089 default:
090 throw new IllegalArgumentException("invalid selectionMode");
091 }
092 }
093
094 public boolean isSelectedIndex(int index) {
095 return ((index < minIndex) || (index > maxIndex)) ? false
096 : value.get(index);
097 }
098
099 public boolean isSelectionEmpty() {
100 return (minIndex > maxIndex);
101 }
102
103 public void addListSelectionListener(ListSelectionListener l) {
104 listenerList.add(ListSelectionListener.class, l);
105 }
106
107 public void removeListSelectionListener(ListSelectionListener l) {
108 listenerList.remove(ListSelectionListener.class, l);
109 }
110
111 /**
112 * Returns an array of all the <code>ListSelectionListener</code>s added
113 * to this OptionListModel with addListSelectionListener().
114 *
115 * @return all of the <code>ListSelectionListener</code>s added or an empty
116 * array if no listeners have been added
117 * @since 1.4
118 */
119 public ListSelectionListener[] getListSelectionListeners() {
120 return (ListSelectionListener[]) listenerList
121 .getListeners(ListSelectionListener.class);
122 }
123
124 /**
125 * Notify listeners that we are beginning or ending a
126 * series of value changes
127 */
128 protected void fireValueChanged(boolean isAdjusting) {
129 fireValueChanged(getMinSelectionIndex(),
130 getMaxSelectionIndex(), isAdjusting);
131 }
132
133 /**
134 * Notify ListSelectionListeners that the value of the selection,
135 * in the closed interval firstIndex,lastIndex, has changed.
136 */
137 protected void fireValueChanged(int firstIndex, int lastIndex) {
138 fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
139 }
140
141 /**
142 * @param firstIndex The first index in the interval.
143 * @param index1 The last index in the interval.
144 * @param isAdjusting True if this is the final change in a series of them.
145 * @see EventListenerList
146 */
147 protected void fireValueChanged(int firstIndex, int lastIndex,
148 boolean isAdjusting) {
149 Object[] listeners = listenerList.getListenerList();
150 ListSelectionEvent e = null;
151
152 for (int i = listeners.length - 2; i >= 0; i -= 2) {
153 if (listeners[i] == ListSelectionListener.class) {
154 if (e == null) {
155 e = new ListSelectionEvent(this , firstIndex,
156 lastIndex, isAdjusting);
157 }
158 ((ListSelectionListener) listeners[i + 1])
159 .valueChanged(e);
160 }
161 }
162 }
163
164 private void fireValueChanged() {
165 if (lastChangedIndex == MIN) {
166 return;
167 }
168 /* Change the values before sending the event to the
169 * listeners in case the event causes a listener to make
170 * another change to the selection.
171 */
172 int oldFirstChangedIndex = firstChangedIndex;
173 int oldLastChangedIndex = lastChangedIndex;
174 firstChangedIndex = MAX;
175 lastChangedIndex = MIN;
176 fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex);
177 }
178
179 // Update first and last change indices
180 private void markAsDirty(int r) {
181 firstChangedIndex = Math.min(firstChangedIndex, r);
182 lastChangedIndex = Math.max(lastChangedIndex, r);
183 }
184
185 // Set the state at this index and update all relevant state.
186 private void set(int r) {
187 if (value.get(r)) {
188 return;
189 }
190 value.set(r);
191 Option option = (Option) get(r);
192 option.setSelection(true);
193 markAsDirty(r);
194
195 // Update minimum and maximum indices
196 minIndex = Math.min(minIndex, r);
197 maxIndex = Math.max(maxIndex, r);
198 }
199
200 // Clear the state at this index and update all relevant state.
201 private void clear(int r) {
202 if (!value.get(r)) {
203 return;
204 }
205 value.clear(r);
206 Option option = (Option) get(r);
207 option.setSelection(false);
208 markAsDirty(r);
209
210 // Update minimum and maximum indices
211 /*
212 If (r > minIndex) the minimum has not changed.
213 The case (r < minIndex) is not possible because r'th value was set.
214 We only need to check for the case when lowest entry has been cleared,
215 and in this case we need to search for the first value set above it.
216 */
217 if (r == minIndex) {
218 for (minIndex = minIndex + 1; minIndex <= maxIndex; minIndex++) {
219 if (value.get(minIndex)) {
220 break;
221 }
222 }
223 }
224 /*
225 If (r < maxIndex) the maximum has not changed.
226 The case (r > maxIndex) is not possible because r'th value was set.
227 We only need to check for the case when highest entry has been cleared,
228 and in this case we need to search for the first value set below it.
229 */
230 if (r == maxIndex) {
231 for (maxIndex = maxIndex - 1; minIndex <= maxIndex; maxIndex--) {
232 if (value.get(maxIndex)) {
233 break;
234 }
235 }
236 }
237 /* Performance note: This method is called from inside a loop in
238 changeSelection() but we will only iterate in the loops
239 above on the basis of one iteration per deselected cell - in total.
240 Ie. the next time this method is called the work of the previous
241 deselection will not be repeated.
242
243 We also don't need to worry about the case when the min and max
244 values are in their unassigned states. This cannot happen because
245 this method's initial check ensures that the selection was not empty
246 and therefore that the minIndex and maxIndex had 'real' values.
247
248 If we have cleared the whole selection, set the minIndex and maxIndex
249 to their cannonical values so that the next set command always works
250 just by using Math.min and Math.max.
251 */
252 if (isSelectionEmpty()) {
253 minIndex = MAX;
254 maxIndex = MIN;
255 }
256 }
257
258 /**
259 * Sets the value of the leadAnchorNotificationEnabled flag.
260 * @see #isLeadAnchorNotificationEnabled()
261 */
262 public void setLeadAnchorNotificationEnabled(boolean flag) {
263 leadAnchorNotificationEnabled = flag;
264 }
265
266 /**
267 * Returns the value of the leadAnchorNotificationEnabled flag.
268 * When leadAnchorNotificationEnabled is true the model
269 * generates notification events with bounds that cover all the changes to
270 * the selection plus the changes to the lead and anchor indices.
271 * Setting the flag to false causes a norrowing of the event's bounds to
272 * include only the elements that have been selected or deselected since
273 * the last change. Either way, the model continues to maintain the lead
274 * and anchor variables internally. The default is true.
275 * @return the value of the leadAnchorNotificationEnabled flag
276 * @see #setLeadAnchorNotificationEnabled(boolean)
277 */
278 public boolean isLeadAnchorNotificationEnabled() {
279 return leadAnchorNotificationEnabled;
280 }
281
282 private void updateLeadAnchorIndices(int anchorIndex, int leadIndex) {
283 if (leadAnchorNotificationEnabled) {
284 if (this .anchorIndex != anchorIndex) {
285 if (this .anchorIndex != -1) { // The unassigned state.
286 markAsDirty(this .anchorIndex);
287 }
288 markAsDirty(anchorIndex);
289 }
290
291 if (this .leadIndex != leadIndex) {
292 if (this .leadIndex != -1) { // The unassigned state.
293 markAsDirty(this .leadIndex);
294 }
295 markAsDirty(leadIndex);
296 }
297 }
298 this .anchorIndex = anchorIndex;
299 this .leadIndex = leadIndex;
300 }
301
302 private boolean contains(int a, int b, int i) {
303 return (i >= a) && (i <= b);
304 }
305
306 private void changeSelection(int clearMin, int clearMax,
307 int setMin, int setMax, boolean clearFirst) {
308 for (int i = Math.min(setMin, clearMin); i <= Math.max(setMax,
309 clearMax); i++) {
310
311 boolean shouldClear = contains(clearMin, clearMax, i);
312 boolean shouldSet = contains(setMin, setMax, i);
313
314 if (shouldSet && shouldClear) {
315 if (clearFirst) {
316 shouldClear = false;
317 } else {
318 shouldSet = false;
319 }
320 }
321
322 if (shouldSet) {
323 set(i);
324 }
325 if (shouldClear) {
326 clear(i);
327 }
328 }
329 fireValueChanged();
330 }
331
332 /* Change the selection with the effect of first clearing the values
333 * in the inclusive range [clearMin, clearMax] then setting the values
334 * in the inclusive range [setMin, setMax]. Do this in one pass so
335 * that no values are cleared if they would later be set.
336 */
337 private void changeSelection(int clearMin, int clearMax,
338 int setMin, int setMax) {
339 changeSelection(clearMin, clearMax, setMin, setMax, true);
340 }
341
342 public void clearSelection() {
343 removeSelectionInterval(minIndex, maxIndex);
344 }
345
346 public void setSelectionInterval(int index0, int index1) {
347 if (index0 == -1 || index1 == -1) {
348 return;
349 }
350
351 if (getSelectionMode() == SINGLE_SELECTION) {
352 index0 = index1;
353 }
354
355 updateLeadAnchorIndices(index0, index1);
356
357 int clearMin = minIndex;
358 int clearMax = maxIndex;
359 int setMin = Math.min(index0, index1);
360 int setMax = Math.max(index0, index1);
361 changeSelection(clearMin, clearMax, setMin, setMax);
362 }
363
364 public void addSelectionInterval(int index0, int index1) {
365 if (index0 == -1 || index1 == -1) {
366 return;
367 }
368
369 if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {
370 setSelectionInterval(index0, index1);
371 return;
372 }
373
374 updateLeadAnchorIndices(index0, index1);
375
376 int clearMin = MAX;
377 int clearMax = MIN;
378 int setMin = Math.min(index0, index1);
379 int setMax = Math.max(index0, index1);
380 changeSelection(clearMin, clearMax, setMin, setMax);
381 }
382
383 public void removeSelectionInterval(int index0, int index1) {
384 if (index0 == -1 || index1 == -1) {
385 return;
386 }
387
388 updateLeadAnchorIndices(index0, index1);
389
390 int clearMin = Math.min(index0, index1);
391 int clearMax = Math.max(index0, index1);
392 int setMin = MAX;
393 int setMax = MIN;
394 changeSelection(clearMin, clearMax, setMin, setMax);
395 }
396
397 private void setState(int index, boolean state) {
398 if (state) {
399 set(index);
400 } else {
401 clear(index);
402 }
403 }
404
405 /**
406 * Insert length indices beginning before/after index. If the value
407 * at index is itself selected, set all of the newly inserted
408 * items, otherwise leave them unselected. This method is typically
409 * called to sync the selection model with a corresponding change
410 * in the data model.
411 */
412 public void insertIndexInterval(int index, int length,
413 boolean before) {
414 /* The first new index will appear at insMinIndex and the last
415 * one will appear at insMaxIndex
416 */
417 int insMinIndex = (before) ? index : index + 1;
418 int insMaxIndex = (insMinIndex + length) - 1;
419
420 /* Right shift the entire bitset by length, beginning with
421 * index-1 if before is true, index+1 if it's false (i.e. with
422 * insMinIndex).
423 */
424 for (int i = maxIndex; i >= insMinIndex; i--) {
425 setState(i + length, value.get(i));
426 }
427
428 /* Initialize the newly inserted indices.
429 */
430 boolean setInsertedValues = value.get(index);
431 for (int i = insMinIndex; i <= insMaxIndex; i++) {
432 setState(i, setInsertedValues);
433 }
434 }
435
436 /**
437 * Remove the indices in the interval index0,index1 (inclusive) from
438 * the selection model. This is typically called to sync the selection
439 * model width a corresponding change in the data model. Note
440 * that (as always) index0 can be greater than index1.
441 */
442 public void removeIndexInterval(int index0, int index1) {
443 int rmMinIndex = Math.min(index0, index1);
444 int rmMaxIndex = Math.max(index0, index1);
445 int gapLength = (rmMaxIndex - rmMinIndex) + 1;
446
447 /* Shift the entire bitset to the left to close the index0, index1
448 * gap.
449 */
450 for (int i = rmMinIndex; i <= maxIndex; i++) {
451 setState(i, value.get(i + gapLength));
452 }
453 }
454
455 public void setValueIsAdjusting(boolean isAdjusting) {
456 if (isAdjusting != this .isAdjusting) {
457 this .isAdjusting = isAdjusting;
458 this .fireValueChanged(isAdjusting);
459 }
460 }
461
462 public String toString() {
463 String s = ((getValueIsAdjusting()) ? "~" : "=")
464 + value.toString();
465 return getClass().getName() + " "
466 + Integer.toString(hashCode()) + " " + s;
467 }
468
469 /**
470 * Returns a clone of the receiver with the same selection.
471 * <code>listenerLists</code> are not duplicated.
472 *
473 * @return a clone of the receiver
474 * @exception CloneNotSupportedException if the receiver does not
475 * both (a) implement the <code>Cloneable</code> interface
476 * and (b) define a <code>clone</code> method
477 */
478 public Object clone() throws CloneNotSupportedException {
479 OptionListModel clone = (OptionListModel) super .clone();
480 clone.value = (BitSet) value.clone();
481 clone.listenerList = new EventListenerList();
482 return clone;
483 }
484
485 public int getAnchorSelectionIndex() {
486 return anchorIndex;
487 }
488
489 public int getLeadSelectionIndex() {
490 return leadIndex;
491 }
492
493 /**
494 * Set the anchor selection index, leaving all selection values unchanged.
495 *
496 * @see #getAnchorSelectionIndex
497 * @see #setLeadSelectionIndex
498 */
499 public void setAnchorSelectionIndex(int anchorIndex) {
500 this .anchorIndex = anchorIndex;
501 }
502
503 /**
504 * Set the lead selection index, ensuring that values between the
505 * anchor and the new lead are either all selected or all deselected.
506 * If the value at the anchor index is selected, first clear all the
507 * values in the range [anchor, oldLeadIndex], then select all the values
508 * values in the range [anchor, newLeadIndex], where oldLeadIndex is the old
509 * leadIndex and newLeadIndex is the new one.
510 * <p>
511 * If the value at the anchor index is not selected, do the same thing in reverse,
512 * selecting values in the old range and deslecting values in the new one.
513 * <p>
514 * Generate a single event for this change and notify all listeners.
515 * For the purposes of generating minimal bounds in this event, do the
516 * operation in a single pass; that way the first and last index inside the
517 * ListSelectionEvent that is broadcast will refer to cells that actually
518 * changed value because of this method. If, instead, this operation were
519 * done in two steps the effect on the selection state would be the same
520 * but two events would be generated and the bounds around the changed values
521 * would be wider, including cells that had been first cleared and only
522 * to later be set.
523 * <p>
524 * This method can be used in the mouseDragged() method of a UI class
525 * to extend a selection.
526 *
527 * @see #getLeadSelectionIndex
528 * @see #setAnchorSelectionIndex
529 */
530 public void setLeadSelectionIndex(int leadIndex) {
531 int anchorIndex = this .anchorIndex;
532 if (getSelectionMode() == SINGLE_SELECTION) {
533 anchorIndex = leadIndex;
534 }
535
536 int oldMin = Math.min(this .anchorIndex, this .leadIndex);
537 ;
538 int oldMax = Math.max(this .anchorIndex, this .leadIndex);
539 ;
540 int newMin = Math.min(anchorIndex, leadIndex);
541 int newMax = Math.max(anchorIndex, leadIndex);
542 if (value.get(this .anchorIndex)) {
543 changeSelection(oldMin, oldMax, newMin, newMax);
544 } else {
545 changeSelection(newMin, newMax, oldMin, oldMax, false);
546 }
547 this .anchorIndex = anchorIndex;
548 this .leadIndex = leadIndex;
549 }
550
551 /**
552 * This method is responsible for storing the state
553 * of the initial selection. If the selectionMode
554 * is the default, i.e allowing only for SINGLE_SELECTION,
555 * then the very last OPTION that has the selected
556 * attribute set wins.
557 */
558 public void setInitialSelection(int i) {
559 if (initialValue.get(i)) {
560 return;
561 }
562 if (selectionMode == SINGLE_SELECTION) {
563 // reset to empty
564 initialValue.and(new BitSet());
565 }
566 initialValue.set(i);
567 }
568
569 /**
570 * Fetches the BitSet that represents the initial
571 * set of selected items in the list.
572 */
573 public BitSet getInitialSelection() {
574 return initialValue;
575 }
576 }
|