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)
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
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
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. */
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
;
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
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
,
66 private static final long serialVersionUID
= -5718799865110415860L;
68 /** The list of ListSelectionListeners subscribed to this selection model. */
69 protected EventListenerList listenerList
= new EventListenerList();
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
;
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
88 int leadSelectionIndex
= -1;
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
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
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;
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.
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 a The new value of the property
167 public void setSelectionMode(int a
)
173 * Gets the value of the {@link #anchorSelectionIndex} property.
175 * @return The current property value
177 * @see #setAnchorSelectionIndex
179 public int getAnchorSelectionIndex()
181 return anchorSelectionIndex
;
185 * Sets the value of the {@link #anchorSelectionIndex} property.
187 * @param anchorIndex The new property value
189 * @see #getAnchorSelectionIndex
191 public void setAnchorSelectionIndex(int anchorIndex
)
193 anchorSelectionIndex
= anchorIndex
;
197 * Gets the value of the {@link #leadSelectionIndex} property.
199 * @return The current property value
201 * @see #setLeadSelectionIndex
203 public int getLeadSelectionIndex()
205 return leadSelectionIndex
;
209 * <p>Sets the value of the {@link #anchorSelectionIndex} property. As a
210 * side effect, alters the selection status of two ranges of indices. Let
211 * <code>OL</code> be the old lead selection index, <code>NL</code> be
212 * the new lead selection index, and <code>A</code> be the anchor
213 * selection index. Then if <code>A</code> is a valid selection index,
214 * one of two things happens depending on the seleciton status of
215 * <code>A</code>:</p>
219 * <li><code>isSelectedIndex(A) == true</code>: set <code>[A,OL]</code>
220 * to <em>deselected</em>, then set <code>[A,NL]</code> to
221 * <em>selected</em>.</li>
223 * <li><code>isSelectedIndex(A) == false</code>: set <code>[A,OL]</code>
224 * to <em>selected</em>, then set <code>[A,NL]</code> to
225 * <em>deselected</em>.</li>
229 * <p>This method generates at most a single {@link ListSelectionEvent}
230 * despite changing multiple ranges. The range of values provided to the
231 * {@link ListSelectionEvent} includes only the minimum range of values
232 * which changed selection status between the beginning and end of the
235 * @param leadIndex The new property value
237 * @see #getAnchorSelectionIndex
239 public void setLeadSelectionIndex(int leadIndex
)
241 // Only set the lead selection index to < 0 if anchorSelectionIndex < 0.
244 if (anchorSelectionIndex
< 0)
245 leadSelectionIndex
= -1;
250 // Only touch the lead selection index if the anchor is >= 0.
251 if (anchorSelectionIndex
< 0)
254 if (selectionMode
== SINGLE_SELECTION
)
255 setSelectionInterval (leadIndex
, leadIndex
);
257 int oldLeadIndex
= leadSelectionIndex
;
258 if (oldLeadIndex
== -1)
259 oldLeadIndex
= leadIndex
;
260 if (setLeadCalledFromAdd
== false)
261 oldSel
= sel
.clone();
262 leadSelectionIndex
= leadIndex
;
264 if (anchorSelectionIndex
== -1)
267 int R1
= Math
.min(anchorSelectionIndex
, oldLeadIndex
);
268 int R2
= Math
.max(anchorSelectionIndex
, oldLeadIndex
);
269 int S1
= Math
.min(anchorSelectionIndex
, leadIndex
);
270 int S2
= Math
.max(anchorSelectionIndex
, leadIndex
);
272 int lo
= Math
.min(R1
, S1
);
273 int hi
= Math
.max(R2
, S2
);
275 if (isSelectedIndex(anchorSelectionIndex
))
286 int beg
= sel
.nextSetBit(0), end
= -1;
287 for(int i
=beg
; i
>= 0; i
=sel
.nextSetBit(i
+1))
289 if (sel
.equals(oldSel
) == false)
290 fireValueChanged(beg
, end
, valueIsAdjusting
);
294 * Moves the lead selection index to <code>leadIndex</code> without
295 * changing the selection values.
297 * If leadAnchorNotificationEnabled is true, send a notification covering the
298 * old and new lead cells.
300 * @param leadIndex the new lead selection index
303 public void moveLeadSelectionIndex (int leadIndex
)
305 if (leadSelectionIndex
== leadIndex
)
308 leadSelectionIndex
= leadIndex
;
309 if (isLeadAnchorNotificationEnabled())
310 fireValueChanged(Math
.min(leadSelectionIndex
, leadIndex
),
311 Math
.max(leadSelectionIndex
, leadIndex
));
315 * Gets the value of the {@link #leadAnchorNotificationEnabled} property.
317 * @return The current property value
319 * @see #setLeadAnchorNotificationEnabled
321 public boolean isLeadAnchorNotificationEnabled()
323 return leadAnchorNotificationEnabled
;
327 * Sets the value of the {@link #leadAnchorNotificationEnabled} property.
329 * @param l The new property value
331 * @see #isLeadAnchorNotificationEnabled
333 public void setLeadAnchorNotificationEnabled(boolean l
)
335 leadAnchorNotificationEnabled
= l
;
339 * Gets the value of the {@link #valueIsAdjusting} property.
341 * @return The current property value
343 * @see #setValueIsAdjusting
345 public boolean getValueIsAdjusting()
347 return valueIsAdjusting
;
351 * Sets the value of the {@link #valueIsAdjusting} property.
353 * @param v The new property value
355 * @see #getValueIsAdjusting
357 public void setValueIsAdjusting(boolean v
)
359 valueIsAdjusting
= v
;
363 * Determines whether the selection is empty.
365 * @return <code>true</code> if the selection is empty, otherwise
368 public boolean isSelectionEmpty()
370 return sel
.isEmpty();
374 * Gets the smallest index which is currently a member of a selection
377 * @return The least integer <code>i</code> such that <code>i >=
378 * 0</code> and <code>i</code> is a member of a selected interval, or
379 * <code>-1</code> if there are no selected intervals
381 * @see #getMaxSelectionIndex
383 public int getMinSelectionIndex()
385 if (isSelectionEmpty())
388 return sel
.nextSetBit(0);
392 * Gets the largest index which is currently a member of a selection
395 * @return The greatest integer <code>i</code> such that <code>i >=
396 * 0</code> and <code>i</code> is a member of a selected interval, or
397 * <code>-1</code> if there are no selected intervals
399 * @see #getMinSelectionIndex
401 public int getMaxSelectionIndex()
403 if (isSelectionEmpty())
407 for(int i
=sel
.nextSetBit(0); i
>= 0; i
=sel
.nextSetBit(i
+1))
415 * Determines whether a particular index is a member of a selection
418 * @param a The index to search for
420 * @return <code>true</code> if the index is a member of a selection interval,
421 * otherwise <code>false</code>
423 public boolean isSelectedIndex(int a
)
425 // TODO: Probably throw an exception here?
426 if (a
>= sel
.length() || a
< 0)
432 * If the {@link #selectionMode} property is equal to
433 * <code>SINGLE_SELECTION</code> equivalent to calling
434 * <code>setSelectionInterval(index1, index2)</code>;
435 * If the {@link #selectionMode} property is equal to
436 * <code>SINGLE_INTERVAL_SELECTION</code> and the interval being
437 * added is not adjacent to an already selected interval,
438 * equivalent to <code>setSelectionInterval(index1, index2)</code>.
439 * Otherwise adds the range <code>[index0, index1]</code>
440 * to the selection interval set.
442 * @param index0 The beginning of the range of indices to select
443 * @param index1 The end of the range of indices to select
445 * @see #setSelectionInterval
446 * @see #removeSelectionInterval
448 public void addSelectionInterval(int index0
, int index1
)
450 if (index0
== -1 || index1
== -1)
453 int lo
= Math
.min(index0
, index1
);
454 int hi
= Math
.max(index0
, index1
);
455 oldSel
= sel
.clone();
457 if (selectionMode
== SINGLE_SELECTION
)
458 setSelectionInterval(index0
, index1
);
460 // COMPAT: Like Sun (but not like IBM), we allow calls to
461 // addSelectionInterval when selectionMode is
462 // SINGLE_SELECTION_INTERVAL iff the interval being added
463 // is adjacent to an already selected interval
464 if (selectionMode
== SINGLE_INTERVAL_SELECTION
)
465 if (!(isSelectedIndex(index0
) ||
466 isSelectedIndex(index1
) ||
467 isSelectedIndex(Math
.max(lo
-1,0)) ||
468 isSelectedIndex(Math
.min(hi
+1,sel
.size()))))
471 // We have to update the anchorSelectionIndex and leadSelectionIndex
474 // The next if statements breaks down to "if this selection is adjacent
475 // to the previous selection and going in the same direction"
476 if ((isSelectedIndex(leadSelectionIndex
))
477 && ((index0
- 1 == leadSelectionIndex
478 && (index1
>= index0
)
479 && (leadSelectionIndex
>= anchorSelectionIndex
))
480 || (index0
+ 1 == leadSelectionIndex
&& (index1
<= index0
)
481 && (leadSelectionIndex
<= anchorSelectionIndex
)))
482 && (anchorSelectionIndex
!= -1 || leadSelectionIndex
!= -1))
484 // setting setLeadCalledFromAdd to true tells setLeadSelectionIndex
485 // not to update oldSel
486 setLeadCalledFromAdd
= true;
487 setLeadSelectionIndex(index1
);
488 setLeadCalledFromAdd
= false;
492 leadSelectionIndex
= index1
;
493 anchorSelectionIndex
= index0
;
495 if (sel
.equals(oldSel
) == false)
496 fireValueChanged(lo
, hi
, valueIsAdjusting
);
502 * Deselects all indices in the inclusive range
503 * <code>[index0,index1]</code>.
505 * @param index0 The beginning of the range of indices to deselect
506 * @param index1 The end of the range of indices to deselect
508 * @see #addSelectionInterval
509 * @see #setSelectionInterval
511 public void removeSelectionInterval(int index0
,
514 if (index0
== -1 || index1
== -1)
517 oldSel
= sel
.clone();
518 int lo
= Math
.min(index0
, index1
);
519 int hi
= Math
.max(index0
, index1
);
521 // if selectionMode is SINGLE_INTERVAL_SELECTION and removing the interval
522 // (index0,index1) would leave two disjoint selection intervals, remove all
523 // selected indices from lo to the last selected index
524 if (getMinSelectionIndex() > 0 && getMinSelectionIndex() < lo
&&
525 selectionMode
== SINGLE_INTERVAL_SELECTION
)
529 //update anchorSelectionIndex and leadSelectionIndex variables
530 //TODO: will probably need MouseDragged to test properly and know if this works
531 setAnchorSelectionIndex(index0
);
532 leadSelectionIndex
= index1
;
533 if (sel
.equals(oldSel
) == false)
534 fireValueChanged(lo
, hi
, valueIsAdjusting
);
538 * Removes all intervals in the selection set.
540 public void clearSelection()
542 oldSel
= sel
.clone();
545 if (sel
.equals(oldSel
) == false)
546 fireValueChanged(0, sz
, valueIsAdjusting
);
550 * Clears the current selection and marks a given interval as
551 * "selected". If the current selection mode is
552 * <code>SINGLE_SELECTION</code> only the index <code>index2</code> is
555 * @param index0 The low end of the new selection
556 * @param index1 The high end of the new selection
558 public void setSelectionInterval(int index0
, int index1
)
560 if (index0
== -1 || index1
== -1)
563 oldSel
= sel
.clone();
565 if (selectionMode
== SINGLE_SELECTION
)
568 int lo
= Math
.min(index0
, index1
);
569 int hi
= Math
.max(index0
, index1
);
571 // update the anchorSelectionIndex and leadSelectionIndex variables
572 setAnchorSelectionIndex(index0
);
573 leadSelectionIndex
=index1
;
574 if (sel
.equals(oldSel
) == false)
575 fireValueChanged(lo
, hi
, valueIsAdjusting
);
579 * Inserts a number of indices either before or after a particular
580 * position in the set of indices. Renumbers all indices after the
581 * inserted range. The new indices in the inserted range are not
582 * selected. This method is typically called to synchronize the selection
583 * model with an inserted range of elements in a {@link ListModel}.
585 * @param index The position to insert indices at
586 * @param length The number of indices to insert
587 * @param before Indicates whether to insert the indices before the index
590 public void insertIndexInterval(int index
,
599 BitSet tmp
= sel
.get(index
, sel
.size());
600 sel
.clear(index
, sel
.size());
602 for (int i
= 0; i
< n
; ++i
)
603 sel
.set(index
+ length
+ i
, tmp
.get(i
));
607 * Removes a range from the set of indices. Renumbers all indices after
608 * the removed range. This method is typically called to synchronize the
609 * selection model with a deleted range of elements in a {@link
612 * @param index0 The first index to remove (inclusive)
613 * @param index1 The last index to remove (inclusive)
615 public void removeIndexInterval(int index0
,
618 int lo
= Math
.min(index0
, index1
);
619 int hi
= Math
.max(index0
, index1
);
621 BitSet tmp
= sel
.get(hi
, sel
.size());
622 sel
.clear(lo
, sel
.size());
624 for (int i
= 0; i
< n
; ++i
)
625 sel
.set(lo
+ i
, tmp
.get(i
));
629 * Fires a {@link ListSelectionEvent} to all the listeners of type {@link
630 * ListSelectionListener} registered with this selection model to
631 * indicate that a series of adjustment has just ended.
633 * The values of {@link #getMinSelectionIndex} and
634 * {@link #getMaxSelectionIndex} are used in the {@link ListSelectionEvent}
637 * @param isAdjusting <code>true</code> if this is the final change
638 * in a series of adjustments, <code>false/code> otherwise
640 protected void fireValueChanged(boolean isAdjusting
)
642 fireValueChanged(getMinSelectionIndex(), getMaxSelectionIndex(),
647 * Fires a {@link ListSelectionEvent} to all the listeners of type {@link
648 * ListSelectionListener} registered with this selection model.
650 * @param firstIndex The low index of the changed range
651 * @param lastIndex The high index of the changed range
653 protected void fireValueChanged(int firstIndex
, int lastIndex
)
655 fireValueChanged(firstIndex
, lastIndex
, getValueIsAdjusting());
659 * Fires a {@link ListSelectionEvent} to all the listeners of type {@link
660 * ListSelectionListener} registered with this selection model.
662 * @param firstIndex The low index of the changed range
663 * @param lastIndex The high index of the changed range
664 * @param isAdjusting Whether this change is part of a seqence of adjustments
665 * made to the selection, such as during interactive scrolling
667 protected void fireValueChanged(int firstIndex
, int lastIndex
,
670 ListSelectionEvent evt
= new ListSelectionEvent(this, firstIndex
,
671 lastIndex
, isAdjusting
);
672 ListSelectionListener
[] listeners
= getListSelectionListeners();
673 for (int i
= 0; i
< listeners
.length
; ++i
)
674 listeners
[i
].valueChanged(evt
);
680 * @param listener The listener to add
682 * @see #removeListSelectionListener
683 * @see #getListSelectionListeners
685 public void addListSelectionListener(ListSelectionListener listener
)
687 listenerList
.add(ListSelectionListener
.class, listener
);
691 * Removes a registered listener.
693 * @param listener The listener to remove
695 * @see #addListSelectionListener
696 * @see #getListSelectionListeners
698 public void removeListSelectionListener(ListSelectionListener listener
)
700 listenerList
.remove(ListSelectionListener
.class, listener
);
704 * Returns an array of all registerers listeners.
706 * @param listenerType The type of listener to retrieve
710 * @see #getListSelectionListeners
713 public EventListener
[] getListeners(Class listenerType
)
715 return listenerList
.getListeners(listenerType
);
719 * Returns an array of all registerd list selection listeners.
723 * @see #addListSelectionListener
724 * @see #removeListSelectionListener
728 public ListSelectionListener
[] getListSelectionListeners()
730 return (ListSelectionListener
[]) getListeners(ListSelectionListener
.class);
734 * Returns a clone of this object.
735 * <code>listenerList</code> don't gets duplicated.
737 * @return the cloned object
739 * @throws CloneNotSupportedException if an error occurs
741 public Object
clone()
742 throws CloneNotSupportedException
744 DefaultListSelectionModel model
=
745 (DefaultListSelectionModel
) super.clone();
746 model
.sel
= (BitSet
) sel
.clone();