Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / javax / swing / DefaultListSelectionModel.java
blob998aee45279696e2787145087edf6b345e477eb8
1 /* DefaultListSelectionModel.java --
2 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax.swing;
41 import java.io.Serializable;
42 import java.util.BitSet;
43 import java.util.EventListener;
45 import javax.swing.event.EventListenerList;
46 import javax.swing.event.ListSelectionEvent;
47 import javax.swing.event.ListSelectionListener;
49 /**
50 * The default implementation of {@link ListSelectionModel},
51 * which is used by {@link javax.swing.JList} and
52 * similar classes to manage the selection status of a number of data
53 * elements.
55 * <p>The class is organized <em>abstractly</em> as a set of intervals of
56 * integers. Each interval indicates an inclusive range of indices in a
57 * list -- held by some other object and unknown to this class -- which is
58 * considered "selected". There are various accessors for querying and
59 * modifying the set of intervals, with simplified forms accepting a single
60 * index, representing an interval with only one element. </p>
62 public class DefaultListSelectionModel implements Cloneable,
63 ListSelectionModel,
64 Serializable
66 private static final long serialVersionUID = -5718799865110415860L;
68 /** The list of ListSelectionListeners subscribed to this selection model. */
69 protected EventListenerList listenerList = new EventListenerList();
72 /**
73 * The current list selection mode. Must be one of the numeric constants
74 * <code>SINGLE_SELECTION</code>, <code>SINGLE_INTERVAL_SELECTION</code>
75 * or <code>MULTIPLE_INTERVAL_SELECTION</code> from {@link
76 * ListSelectionModel}. The default value is
77 * <code>MULTIPLE_INTERVAL_SELECTION</code>.
79 int selectionMode = MULTIPLE_INTERVAL_SELECTION;
81 /**
82 * The index of the "lead" of the most recent selection. The lead is the
83 * second argument in any call to {@link #setSelectionInterval}, {@link
84 * #addSelectionInterval} or {@link #removeSelectionInterval}. Generally
85 * the lead refers to the most recent position a user dragged their mouse
86 * over.
88 int leadSelectionIndex = -1;
90 /**
91 * The index of the "anchor" of the most recent selection. The anchor is
92 * the first argument in any call to {@link #setSelectionInterval},
93 * {@link #addSelectionInterval} or {@link
94 * #removeSelectionInterval}. Generally the anchor refers to the first
95 * recent position a user clicks when they begin to drag their mouse over
96 * a list.
98 * @see #getAnchorSelectionIndex
99 * @see #setAnchorSelectionIndex
101 int anchorSelectionIndex = -1;
104 * controls the range of indices provided in any {@link
105 * ListSelectionEvent} fired by the selectionModel. Let
106 * <code>[A,L]</code> be the range of indices between {@link
107 * #anchorSelectionIndex} and {@link #leadSelectionIndex} inclusive, and
108 * let <code>[i0,i1]</code> be the range of indices changed in a given
109 * call which generates a {@link ListSelectionEvent}. Then when this
110 * property is <code>true</code>, the {@link ListSelectionEvent} contains
111 * the range <code>[A,L] union [i0,i1]</code>; when <code>false</code> it
112 * will contain only <code>[i0,i1]</code>. The default is
113 * <code>true</code>.
115 * @see #isLeadAnchorNotificationEnabled
116 * @see #setLeadAnchorNotificationEnabled
118 protected boolean leadAnchorNotificationEnabled = true;
121 * Whether the selection is currently "adjusting". Any {@link
122 * ListSelectionEvent} events constructed in response to changes in this
123 * list selection model will have their {@link
124 * ListSelectionEvent#isAdjusting} field set to this value.
126 * @see #getValueIsAdjusting
127 * @see #setValueIsAdjusting
129 boolean valueIsAdjusting = false;
132 /**
133 * The current set of "intervals", represented simply by a {@link
134 * java.util.BitSet}. A set bit indicates a selected index, whereas a
135 * cleared bit indicates a non-selected index.
137 BitSet sel = new BitSet();
140 * A variable to store the previous value of sel.
141 * Used to make sure we only fireValueChanged when the BitSet
142 * actually does change.
144 Object oldSel;
147 * Whether this call of setLeadSelectionInterval was called locally
148 * from addSelectionInterval
150 boolean setLeadCalledFromAdd = false;
153 * Gets the value of the {@link #selectionMode} property.
155 * @return The current value of the property
157 public int getSelectionMode()
159 return selectionMode;
163 * Sets the value of the {@link #selectionMode} property.
165 * @param mode The new value of the property
167 public void setSelectionMode(int mode)
169 if (mode < ListSelectionModel.SINGLE_SELECTION
170 || mode > ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
171 throw new IllegalArgumentException("Unrecognised mode: " + mode);
172 selectionMode = mode;
176 * Gets the value of the {@link #anchorSelectionIndex} property.
178 * @return The current property value
180 * @see #setAnchorSelectionIndex
182 public int getAnchorSelectionIndex()
184 return anchorSelectionIndex;
188 * Sets the value of the {@link #anchorSelectionIndex} property.
190 * @param anchorIndex The new property value
192 * @see #getAnchorSelectionIndex
194 public void setAnchorSelectionIndex(int anchorIndex)
196 anchorSelectionIndex = anchorIndex;
200 * Gets the value of the {@link #leadSelectionIndex} property.
202 * @return The current property value
204 * @see #setLeadSelectionIndex
206 public int getLeadSelectionIndex()
208 return leadSelectionIndex;
212 * <p>Sets the value of the {@link #anchorSelectionIndex} property. As a
213 * side effect, alters the selection status of two ranges of indices. Let
214 * <code>OL</code> be the old lead selection index, <code>NL</code> be
215 * the new lead selection index, and <code>A</code> be the anchor
216 * selection index. Then if <code>A</code> is a valid selection index,
217 * one of two things happens depending on the seleciton status of
218 * <code>A</code>:</p>
220 * <ul>
222 * <li><code>isSelectedIndex(A) == true</code>: set <code>[A,OL]</code>
223 * to <em>deselected</em>, then set <code>[A,NL]</code> to
224 * <em>selected</em>.</li>
226 * <li><code>isSelectedIndex(A) == false</code>: set <code>[A,OL]</code>
227 * to <em>selected</em>, then set <code>[A,NL]</code> to
228 * <em>deselected</em>.</li>
230 * </ul>
232 * <p>This method generates at most a single {@link ListSelectionEvent}
233 * despite changing multiple ranges. The range of values provided to the
234 * {@link ListSelectionEvent} includes only the minimum range of values
235 * which changed selection status between the beginning and end of the
236 * method.</p>
238 * @param leadIndex The new property value
240 * @see #getAnchorSelectionIndex
242 public void setLeadSelectionIndex(int leadIndex)
244 // Only set the lead selection index to < 0 if anchorSelectionIndex < 0.
245 if (leadIndex < 0)
247 if (anchorSelectionIndex < 0)
248 leadSelectionIndex = -1;
249 else
250 return;
253 // Only touch the lead selection index if the anchor is >= 0.
254 if (anchorSelectionIndex < 0)
255 return;
257 if (selectionMode == SINGLE_SELECTION)
258 setSelectionInterval (leadIndex, leadIndex);
260 int oldLeadIndex = leadSelectionIndex;
261 if (oldLeadIndex == -1)
262 oldLeadIndex = leadIndex;
263 if (setLeadCalledFromAdd == false)
264 oldSel = sel.clone();
265 leadSelectionIndex = leadIndex;
267 if (anchorSelectionIndex == -1)
268 return;
270 int R1 = Math.min(anchorSelectionIndex, oldLeadIndex);
271 int R2 = Math.max(anchorSelectionIndex, oldLeadIndex);
272 int S1 = Math.min(anchorSelectionIndex, leadIndex);
273 int S2 = Math.max(anchorSelectionIndex, leadIndex);
275 int lo = Math.min(R1, S1);
276 int hi = Math.max(R2, S2);
278 if (isSelectedIndex(anchorSelectionIndex))
280 sel.clear(R1, R2+1);
281 sel.set(S1, S2+1);
283 else
285 sel.set(R1, R2+1);
286 sel.clear(S1, S2+1);
289 int beg = sel.nextSetBit(0), end = -1;
290 for(int i=beg; i >= 0; i=sel.nextSetBit(i+1))
291 end = i;
293 BitSet old = (BitSet) oldSel;
295 // The new and previous lead location requires repainting.
296 old.set(oldLeadIndex, !sel.get(oldLeadIndex));
297 old.set(leadSelectionIndex, !sel.get(leadSelectionIndex));
299 fireDifference(sel, old);
303 * Moves the lead selection index to <code>leadIndex</code> without
304 * changing the selection values.
306 * If leadAnchorNotificationEnabled is true, send a notification covering the
307 * old and new lead cells.
309 * @param leadIndex the new lead selection index
310 * @since 1.5
312 public void moveLeadSelectionIndex (int leadIndex)
314 if (leadSelectionIndex == leadIndex)
315 return;
317 leadSelectionIndex = leadIndex;
318 if (isLeadAnchorNotificationEnabled())
319 fireValueChanged(Math.min(leadSelectionIndex, leadIndex),
320 Math.max(leadSelectionIndex, leadIndex));
324 * Gets the value of the {@link #leadAnchorNotificationEnabled} property.
326 * @return The current property value
328 * @see #setLeadAnchorNotificationEnabled
330 public boolean isLeadAnchorNotificationEnabled()
332 return leadAnchorNotificationEnabled;
336 * Sets the value of the {@link #leadAnchorNotificationEnabled} property.
338 * @param l The new property value
340 * @see #isLeadAnchorNotificationEnabled
342 public void setLeadAnchorNotificationEnabled(boolean l)
344 leadAnchorNotificationEnabled = l;
348 * Gets the value of the {@link #valueIsAdjusting} property.
350 * @return The current property value
352 * @see #setValueIsAdjusting
354 public boolean getValueIsAdjusting()
356 return valueIsAdjusting;
360 * Sets the value of the {@link #valueIsAdjusting} property.
362 * @param v The new property value
364 * @see #getValueIsAdjusting
366 public void setValueIsAdjusting(boolean v)
368 valueIsAdjusting = v;
372 * Determines whether the selection is empty.
374 * @return <code>true</code> if the selection is empty, otherwise
375 * <code>false</code>
377 public boolean isSelectionEmpty()
379 return sel.isEmpty();
383 * Gets the smallest index which is currently a member of a selection
384 * interval.
386 * @return The least integer <code>i</code> such that <code>i >=
387 * 0</code> and <code>i</code> is a member of a selected interval, or
388 * <code>-1</code> if there are no selected intervals
390 * @see #getMaxSelectionIndex
392 public int getMinSelectionIndex()
394 if (isSelectionEmpty())
395 return -1;
397 return sel.nextSetBit(0);
401 * Gets the largest index which is currently a member of a selection
402 * interval.
404 * @return The greatest integer <code>i</code> such that <code>i >=
405 * 0</code> and <code>i</code> is a member of a selected interval, or
406 * <code>-1</code> if there are no selected intervals
408 * @see #getMinSelectionIndex
410 public int getMaxSelectionIndex()
412 if (isSelectionEmpty())
413 return -1;
415 int mx = -1;
416 for(int i=sel.nextSetBit(0); i >= 0; i=sel.nextSetBit(i+1))
418 mx = i;
420 return mx;
424 * Determines whether a particular index is a member of a selection
425 * interval.
427 * @param a The index to search for
429 * @return <code>true</code> if the index is a member of a selection interval,
430 * otherwise <code>false</code>
432 public boolean isSelectedIndex(int a)
434 // TODO: Probably throw an exception here?
435 if (a >= sel.length() || a < 0)
436 return false;
437 return sel.get(a);
441 * If the {@link #selectionMode} property is equal to
442 * <code>SINGLE_SELECTION</code> equivalent to calling
443 * <code>setSelectionInterval(index1, index2)</code>;
444 * If the {@link #selectionMode} property is equal to
445 * <code>SINGLE_INTERVAL_SELECTION</code> and the interval being
446 * added is not adjacent to an already selected interval,
447 * equivalent to <code>setSelectionInterval(index1, index2)</code>.
448 * Otherwise adds the range <code>[index0, index1]</code>
449 * to the selection interval set.
451 * @param index0 The beginning of the range of indices to select
452 * @param index1 The end of the range of indices to select
454 * @see #setSelectionInterval
455 * @see #removeSelectionInterval
457 public void addSelectionInterval(int index0, int index1)
459 if (index0 == -1 || index1 == -1)
460 return;
462 int lo = Math.min(index0, index1);
463 int hi = Math.max(index0, index1);
464 oldSel = sel.clone();
466 if (selectionMode == SINGLE_SELECTION)
467 setSelectionInterval(index0, index1);
469 // COMPAT: Like Sun (but not like IBM), we allow calls to
470 // addSelectionInterval when selectionMode is
471 // SINGLE_SELECTION_INTERVAL iff the interval being added
472 // is adjacent to an already selected interval
473 if (selectionMode == SINGLE_INTERVAL_SELECTION)
474 if (!(isSelectedIndex(index0) ||
475 isSelectedIndex(index1) ||
476 isSelectedIndex(Math.max(lo-1,0)) ||
477 isSelectedIndex(Math.min(hi+1,sel.size()))))
478 sel.clear();
480 // We have to update the anchorSelectionIndex and leadSelectionIndex
481 // variables
483 // The next if statements breaks down to "if this selection is adjacent
484 // to the previous selection and going in the same direction"
485 if ((isSelectedIndex(leadSelectionIndex))
486 && ((index0 - 1 == leadSelectionIndex
487 && (index1 >= index0)
488 && (leadSelectionIndex >= anchorSelectionIndex))
489 || (index0 + 1 == leadSelectionIndex && (index1 <= index0)
490 && (leadSelectionIndex <= anchorSelectionIndex)))
491 && (anchorSelectionIndex != -1 || leadSelectionIndex != -1))
493 // setting setLeadCalledFromAdd to true tells setLeadSelectionIndex
494 // not to update oldSel
495 setLeadCalledFromAdd = true;
496 setLeadSelectionIndex(index1);
497 setLeadCalledFromAdd = false;
499 else
501 leadSelectionIndex = index1;
502 anchorSelectionIndex = index0;
503 sel.set(lo, hi+1);
504 fireDifference(sel, (BitSet) oldSel);
510 * Deselects all indices in the inclusive range
511 * <code>[index0,index1]</code>.
513 * @param index0 The beginning of the range of indices to deselect
514 * @param index1 The end of the range of indices to deselect
516 * @see #addSelectionInterval
517 * @see #setSelectionInterval
519 public void removeSelectionInterval(int index0,
520 int index1)
522 if (index0 == -1 || index1 == -1)
523 return;
525 oldSel = sel.clone();
526 int lo = Math.min(index0, index1);
527 int hi = Math.max(index0, index1);
529 // if selectionMode is SINGLE_INTERVAL_SELECTION and removing the interval
530 // (index0,index1) would leave two disjoint selection intervals, remove all
531 // selected indices from lo to the last selected index
532 if (getMinSelectionIndex() > 0 && getMinSelectionIndex() < lo &&
533 selectionMode == SINGLE_INTERVAL_SELECTION)
534 hi = sel.size() - 1;
536 sel.clear(lo, hi+1);
537 //update anchorSelectionIndex and leadSelectionIndex variables
538 //TODO: will probably need MouseDragged to test properly and know if this works
539 setAnchorSelectionIndex(index0);
540 leadSelectionIndex = index1;
542 fireDifference(sel, (BitSet) oldSel);
546 * Removes all intervals in the selection set.
548 public void clearSelection()
550 // Find the selected interval.
551 int from = sel.nextSetBit(0);
552 if (from < 0)
553 return; // Empty selection - nothing to do.
554 int to = from;
556 int i;
558 for (i = from; i>=0; i=sel.nextSetBit(i+1))
559 to = i;
561 sel.clear();
562 fireValueChanged(from, to, valueIsAdjusting);
566 * Fire the change event, covering the difference between the two sets.
568 * @param current the current set
569 * @param x the previous set, the object will be reused.
571 private void fireDifference(BitSet current, BitSet x)
573 x.xor(current);
574 int from = x.nextSetBit(0);
575 if (from < 0)
576 return; // No difference.
577 int to = from;
578 int i;
580 for (i = from; i >= 0; i = x.nextSetBit(i+1))
581 to = i;
583 fireValueChanged(from, to, valueIsAdjusting);
587 * Clears the current selection and marks a given interval as "selected". If
588 * the current selection mode is <code>SINGLE_SELECTION</code> only the
589 * index <code>index2</code> is selected.
591 * @param index0 The low end of the new selection
592 * @param index1 The high end of the new selection
594 public void setSelectionInterval(int index0, int index1)
596 if (index0 == -1 || index1 == -1)
597 return;
599 BitSet oldSel = (BitSet) sel.clone();
600 sel.clear();
601 if (selectionMode == SINGLE_SELECTION)
602 index0 = index1;
604 int lo = Math.min(index0, index1);
605 int hi = Math.max(index0, index1);
606 sel.set(lo, hi+1);
607 // update the anchorSelectionIndex and leadSelectionIndex variables
608 setAnchorSelectionIndex(index0);
609 leadSelectionIndex=index1;
611 fireDifference(sel, oldSel);
615 * Inserts a number of indices either before or after a particular
616 * position in the set of indices. Renumbers all indices after the
617 * inserted range. The new indices in the inserted range are not
618 * selected. This method is typically called to synchronize the selection
619 * model with an inserted range of elements in a {@link ListModel}.
621 * @param index The position to insert indices at
622 * @param length The number of indices to insert
623 * @param before Indicates whether to insert the indices before the index
624 * or after it
626 public void insertIndexInterval(int index,
627 int length,
628 boolean before)
630 if (!before)
632 index++;
633 length--;
635 BitSet tmp = sel.get(index, sel.size());
636 sel.clear(index, sel.size());
637 int n = tmp.size();
638 for (int i = 0; i < n; ++i)
639 sel.set(index + length + i, tmp.get(i));
643 * Removes a range from the set of indices. Renumbers all indices after
644 * the removed range. This method is typically called to synchronize the
645 * selection model with a deleted range of elements in a {@link
646 * ListModel}.
648 * @param index0 The first index to remove (inclusive)
649 * @param index1 The last index to remove (inclusive)
651 public void removeIndexInterval(int index0,
652 int index1)
654 int lo = Math.min(index0, index1);
655 int hi = Math.max(index0, index1);
657 BitSet tmp = sel.get(hi, sel.size());
658 sel.clear(lo, sel.size());
659 int n = tmp.size();
660 for (int i = 0; i < n; ++i)
661 sel.set(lo + i, tmp.get(i));
665 * Fires a {@link ListSelectionEvent} to all the listeners of type {@link
666 * ListSelectionListener} registered with this selection model to
667 * indicate that a series of adjustment has just ended.
669 * The values of {@link #getMinSelectionIndex} and
670 * {@link #getMaxSelectionIndex} are used in the {@link ListSelectionEvent}
671 * that gets fired.
673 * @param isAdjusting <code>true</code> if this is the final change
674 * in a series of adjustments, <code>false/code> otherwise
676 protected void fireValueChanged(boolean isAdjusting)
678 fireValueChanged(getMinSelectionIndex(), getMaxSelectionIndex(),
679 isAdjusting);
683 * Fires a {@link ListSelectionEvent} to all the listeners of type {@link
684 * ListSelectionListener} registered with this selection model.
686 * @param firstIndex The low index of the changed range
687 * @param lastIndex The high index of the changed range
689 protected void fireValueChanged(int firstIndex, int lastIndex)
691 fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
695 * Fires a {@link ListSelectionEvent} to all the listeners of type {@link
696 * ListSelectionListener} registered with this selection model.
698 * @param firstIndex The low index of the changed range
699 * @param lastIndex The high index of the changed range
700 * @param isAdjusting Whether this change is part of a seqence of adjustments
701 * made to the selection, such as during interactive scrolling
703 protected void fireValueChanged(int firstIndex, int lastIndex,
704 boolean isAdjusting)
706 ListSelectionEvent evt = new ListSelectionEvent(this, firstIndex,
707 lastIndex, isAdjusting);
708 ListSelectionListener[] listeners = getListSelectionListeners();
709 for (int i = 0; i < listeners.length; ++i)
710 listeners[i].valueChanged(evt);
714 * Adds a listener.
716 * @param listener The listener to add
718 * @see #removeListSelectionListener
719 * @see #getListSelectionListeners
721 public void addListSelectionListener(ListSelectionListener listener)
723 listenerList.add(ListSelectionListener.class, listener);
727 * Removes a registered listener.
729 * @param listener The listener to remove
731 * @see #addListSelectionListener
732 * @see #getListSelectionListeners
734 public void removeListSelectionListener(ListSelectionListener listener)
736 listenerList.remove(ListSelectionListener.class, listener);
740 * Returns an array of all registerers listeners.
742 * @param listenerType The type of listener to retrieve
744 * @return The array
746 * @see #getListSelectionListeners
747 * @since 1.3
749 public EventListener[] getListeners(Class listenerType)
751 return listenerList.getListeners(listenerType);
755 * Returns an array of all registerd list selection listeners.
757 * @return the array
759 * @see #addListSelectionListener
760 * @see #removeListSelectionListener
761 * @see #getListeners
762 * @since 1.4
764 public ListSelectionListener[] getListSelectionListeners()
766 return (ListSelectionListener[]) getListeners(ListSelectionListener.class);
770 * Returns a clone of this object.
771 * <code>listenerList</code> don't gets duplicated.
773 * @return the cloned object
775 * @throws CloneNotSupportedException if an error occurs
777 public Object clone()
778 throws CloneNotSupportedException
780 DefaultListSelectionModel model =
781 (DefaultListSelectionModel) super.clone();
782 model.sel = (BitSet) sel.clone();
783 model.listenerList = new EventListenerList();
784 return model;