001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.adapter;
032:
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035:
036: import javax.swing.ListSelectionModel;
037: import javax.swing.event.EventListenerList;
038: import javax.swing.event.ListSelectionEvent;
039: import javax.swing.event.ListSelectionListener;
040:
041: import com.jgoodies.binding.value.ValueModel;
042:
043: /**
044: * A {@link ListSelectionModel} implementation that has the list index
045: * bound to a {@link ValueModel}. Therefore this class supports only
046: * the <code>SINGLE_SELECTION</code> mode where only one list index
047: * can be selected at a time. In this mode the setSelectionInterval
048: * and addSelectionInterval methods are equivalent, and only
049: * the second index argument (the "lead index") is used.<p>
050: *
051: * <strong>Example:</strong><pre>
052: * SelectionInList selectionInList = new SelectionInList(...);
053: * JList list = new JList();
054: * list.setModel(selectionInList);
055: * list.setSelectionModel(new SingleListSelectionAdapter(
056: * selectionInList.getSelectionIndexHolder()));
057: * </pre>
058: *
059: * @author Karsten Lentzsch
060: * @author Jeanette Winzenburg
061: * @version $Revision: 1.8 $
062: *
063: * @see ValueModel
064: * @see javax.swing.JList
065: * @see javax.swing.JTable
066: */
067: public final class SingleListSelectionAdapter implements
068: ListSelectionModel {
069:
070: private static final int MIN = -1;
071: private static final int MAX = Integer.MAX_VALUE;
072:
073: private int firstAdjustedIndex = MAX;
074: private int lastAdjustedIndex = MIN;
075: private int firstChangedIndex = MAX;
076: private int lastChangedIndex = MIN;
077:
078: /**
079: * Refers to the selection index holder.
080: */
081: private final ValueModel selectionIndexHolder;
082:
083: /**
084: * Indicates if the selection is undergoing a series of changes.
085: */
086: private boolean valueIsAdjusting;
087:
088: /**
089: * Holds the List of ListSelectionListener.
090: *
091: * @see #addListSelectionListener(ListSelectionListener)
092: * @see #removeListSelectionListener(ListSelectionListener)
093: */
094: private final EventListenerList listenerList = new EventListenerList();
095:
096: // Instance Creation ****************************************************
097:
098: /**
099: * Constructs a <code>SingleListSelectionAdapter</code> with
100: * the given selection index holder.
101: *
102: * @param selectionIndexHolder holds the selection index
103: */
104: public SingleListSelectionAdapter(ValueModel selectionIndexHolder) {
105: this .selectionIndexHolder = selectionIndexHolder;
106: this .selectionIndexHolder
107: .addValueChangeListener(new SelectionIndexChangeHandler());
108: }
109:
110: // Accessing the Selection Index ****************************************
111:
112: /**
113: * Returns the selection index.
114: *
115: * @return the selection index
116: */
117: private int getSelectionIndex() {
118: Object value = selectionIndexHolder.getValue();
119: return (value == null) ? MIN : ((Integer) value).intValue();
120: }
121:
122: /**
123: * Sets a new selection index. Does nothing if it is the same as before.
124: * If this represents a change to the current selection then
125: * notify each ListSelectionListener.
126: *
127: * @param newSelectionIndex the selection index to be set
128: */
129: private void setSelectionIndex(int newSelectionIndex) {
130: setSelectionIndex(getSelectionIndex(), newSelectionIndex);
131: }
132:
133: private void setSelectionIndex(int oldSelectionIndex,
134: int newSelectionIndex) {
135: if (oldSelectionIndex == newSelectionIndex) {
136: return;
137: }
138: markAsDirty(oldSelectionIndex);
139: markAsDirty(newSelectionIndex);
140: selectionIndexHolder.setValue(Integer
141: .valueOf(newSelectionIndex));
142: fireValueChanged();
143: }
144:
145: // ListSelectionModel Implementation ************************************
146:
147: /**
148: * Sets the selection index to <code>index1</code>. Since this model
149: * supports only a single selection index, the index0 is ignored.
150: * This is the behavior the <code>DefaultListSelectionModel</code>
151: * uses in single selection mode.<p>
152: *
153: * If this represents a change to the current selection, then
154: * notify each ListSelectionListener. Note that index0 doesn't have
155: * to be less than or equal to index1.
156: *
157: * @param index0 one end of the interval.
158: * @param index1 other end of the interval
159: * @see #addListSelectionListener(ListSelectionListener)
160: */
161: public void setSelectionInterval(int index0, int index1) {
162: if ((index0 == -1) || (index1 == -1)) {
163: return;
164: }
165: setSelectionIndex(index1);
166: }
167:
168: /**
169: * Sets the selection interval using the given indices.<p>
170: *
171: * If this represents a change to the current selection, then
172: * notify each ListSelectionListener. Note that index0 doesn't have
173: * to be less than or equal to index1.
174: *
175: * @param index0 one end of the interval.
176: * @param index1 other end of the interval
177: * @see #addListSelectionListener(ListSelectionListener)
178: */
179: public void addSelectionInterval(int index0, int index1) {
180: setSelectionInterval(index0, index1);
181: }
182:
183: /**
184: * Clears the selection if it is equals to index0. Since this model
185: * supports only a single selection index, the index1 can be ignored
186: * for the selection.<p>
187: *
188: * If this represents a change to the current selection, then
189: * notify each ListSelectionListener. Note that index0 doesn't have
190: * to be less than or equal to index1.
191: *
192: * @param index0 one end of the interval.
193: * @param index1 other end of the interval
194: * @see #addListSelectionListener(ListSelectionListener)
195: */
196: public void removeSelectionInterval(int index0, int index1) {
197: if ((index0 == -1) || (index1 == -1)) {
198: return;
199: }
200: int max = Math.max(index0, index1);
201: int min = Math.min(index0, index1);
202: if ((min <= getSelectionIndex())
203: && (getSelectionIndex() <= max)) {
204: clearSelection();
205: }
206: }
207:
208: /**
209: * Returns the selection index.
210: *
211: * @return the selection index
212: * @see #getMinSelectionIndex()
213: * @see #setSelectionInterval(int, int)
214: * @see #addSelectionInterval(int, int)
215: */
216: public int getMinSelectionIndex() {
217: return getSelectionIndex();
218: }
219:
220: /**
221: * Returns the selection index.
222: *
223: * @return the selection index
224: * @see #getMinSelectionIndex()
225: * @see #setSelectionInterval(int, int)
226: * @see #addSelectionInterval(int, int)
227: */
228: public int getMaxSelectionIndex() {
229: return getSelectionIndex();
230: }
231:
232: /**
233: * Checks and answers if the given index is selected or not.
234: *
235: * @param index the index to be checked
236: * @return true if selected, false if deselected
237: * @see javax.swing.ListSelectionModel#isSelectedIndex(int)
238: */
239: public boolean isSelectedIndex(int index) {
240: return index < 0 ? false : index == getSelectionIndex();
241: }
242:
243: /**
244: * Returns the selection index.
245: *
246: * @return the selection index
247: * @see #getAnchorSelectionIndex()
248: * @see #setSelectionInterval(int, int)
249: * @see #addSelectionInterval(int, int)
250: */
251: public int getAnchorSelectionIndex() {
252: return getSelectionIndex();
253: }
254:
255: /**
256: * Sets the selection index.
257: *
258: * @param newSelectionIndex the new selection index
259: * @see #getLeadSelectionIndex()
260: */
261: public void setAnchorSelectionIndex(int newSelectionIndex) {
262: setSelectionIndex(newSelectionIndex);
263: }
264:
265: /**
266: * Returns the selection index.
267: *
268: * @return the selection index
269: * @see #getAnchorSelectionIndex()
270: * @see #setSelectionInterval(int, int)
271: * @see #addSelectionInterval(int, int)
272: */
273: public int getLeadSelectionIndex() {
274: return getSelectionIndex();
275: }
276:
277: /**
278: * Sets the selection index.
279: *
280: * @param newSelectionIndex the new selection index
281: * @see #getLeadSelectionIndex()
282: */
283: public void setLeadSelectionIndex(int newSelectionIndex) {
284: setSelectionIndex(newSelectionIndex);
285: }
286:
287: /**
288: * Changes the selection to have no index selected. If this represents
289: * a change to the current selection then notify each ListSelectionListener.
290: *
291: * @see #addListSelectionListener(ListSelectionListener)
292: */
293: public void clearSelection() {
294: setSelectionIndex(-1);
295: }
296:
297: /**
298: * Returns true if no index is selected.
299: *
300: * @return true if no index is selected
301: */
302: public boolean isSelectionEmpty() {
303: return getSelectionIndex() == -1;
304: }
305:
306: /**
307: * Inserts length indices beginning before/after index. If the value
308: * This method is typically called to synchronize the selection model
309: * with a corresponding change in the data model.
310: *
311: * @param index the index to start the insertion
312: * @param length the length of the inserted interval
313: * @param before true to insert before the start index
314: */
315: public void insertIndexInterval(int index, int length,
316: boolean before) {
317: /*
318: * The first new index will appear at insMinIndex and the last one will
319: * appear at insMaxIndex
320: */
321: if (isSelectionEmpty())
322: return;
323:
324: int insMinIndex = (before) ? index : index + 1;
325: int selectionIndex = getSelectionIndex();
326: // If the added elements are after the index; do nothing.
327: if (selectionIndex >= insMinIndex) {
328: setSelectionIndex(selectionIndex + length);
329: }
330: }
331:
332: /**
333: * Remove the indices in the interval index0,index1 (inclusive) from
334: * the selection model. This is typically called to sync the selection
335: * model width a corresponding change in the data model.<p>
336: *
337: * Clears the selection if it is in the specified interval.
338: *
339: * @param index0 the first index to remove from the selection
340: * @param index1 the last index to remove from the selection
341: * @throws IndexOutOfBoundsException if either <code>index0</code>
342: * or <code>index1</code> are less than -1
343: * @see ListSelectionModel#removeSelectionInterval(int, int)
344: * @see #setSelectionInterval(int, int)
345: * @see #addSelectionInterval(int, int)
346: * @see #addListSelectionListener(ListSelectionListener)
347: */
348: public void removeIndexInterval(int index0, int index1) {
349: if ((index0 < -1) || (index1 < -1)) {
350: throw new IndexOutOfBoundsException(
351: "Both indices must be greater or equals to -1.");
352: }
353: if (isSelectionEmpty())
354: return;
355: int lower = Math.min(index0, index1);
356: int upper = Math.max(index0, index1);
357: int selectionIndex = getSelectionIndex();
358:
359: if ((lower <= selectionIndex) && (selectionIndex <= upper)) {
360: clearSelection();
361: } else if (upper < selectionIndex) {
362: int translated = selectionIndex - (upper - lower + 1);
363: setSelectionInterval(translated, translated);
364: }
365: }
366:
367: /**
368: * This property is true if upcoming changes to the value
369: * of the model should be considered a single event. For example
370: * if the model is being updated in response to a user drag,
371: * the value of the valueIsAdjusting property will be set to true
372: * when the drag is initiated and be set to false when
373: * the drag is finished. This property allows listeners to
374: * to update only when a change has been finalized, rather
375: * than always handling all of the intermediate values.
376: *
377: * @param newValueIsAdjusting The new value of the property.
378: * @see #getValueIsAdjusting()
379: */
380: public void setValueIsAdjusting(boolean newValueIsAdjusting) {
381: boolean oldValueIsAdjusting = valueIsAdjusting;
382: if (oldValueIsAdjusting == newValueIsAdjusting) {
383: return;
384: }
385: valueIsAdjusting = newValueIsAdjusting;
386: fireValueChanged(newValueIsAdjusting);
387: }
388:
389: /**
390: * Returns true if the value is undergoing a series of changes.
391: * @return true if the value is currently adjusting
392: * @see #setValueIsAdjusting(boolean)
393: */
394: public boolean getValueIsAdjusting() {
395: return valueIsAdjusting;
396: }
397:
398: /**
399: * Sets the selection mode. Only <code>SINGLE_SELECTION</code> is allowed
400: * in this implementation. Other modes are not supported and will throw
401: * an <code>IllegalArgumentException</code>.<p>
402: *
403: * With <code>SINGLE_SELECTION</code> only one list index
404: * can be selected at a time. In this mode the setSelectionInterval
405: * and addSelectionInterval methods are equivalent, and only
406: * the second index argument (the "lead index") is used.
407: *
408: * @param selectionMode the mode to be set
409: * @see #getSelectionMode()
410: * @see javax.swing.ListSelectionModel#setSelectionMode(int)
411: */
412: public void setSelectionMode(int selectionMode) {
413: if (selectionMode != SINGLE_SELECTION) {
414: throw new UnsupportedOperationException(
415: "The SingleListSelectionAdapter must be used in single selection mode.");
416: }
417: }
418:
419: /**
420: * Returns the fixed selection mode <code>SINGLE_SELECTION</code>.
421: *
422: * @return <code>SINGLE_SELECTION</code>
423: * @see #setSelectionMode(int)
424: */
425: public int getSelectionMode() {
426: return ListSelectionModel.SINGLE_SELECTION;
427: }
428:
429: /**
430: * Add a listener to the list that's notified each time a change
431: * to the selection occurs.
432: *
433: * @param listener the ListSelectionListener
434: * @see #removeListSelectionListener(ListSelectionListener)
435: * @see #setSelectionInterval(int, int)
436: * @see #addSelectionInterval(int, int)
437: * @see #removeSelectionInterval(int, int)
438: * @see #clearSelection()
439: * @see #insertIndexInterval(int, int, boolean)
440: * @see #removeIndexInterval(int, int)
441: */
442: public void addListSelectionListener(ListSelectionListener listener) {
443: listenerList.add(ListSelectionListener.class, listener);
444: }
445:
446: /**
447: * Remove a listener from the list that's notified each time a
448: * change to the selection occurs.
449: *
450: * @param listener the ListSelectionListener
451: * @see #addListSelectionListener(ListSelectionListener)
452: */
453: public void removeListSelectionListener(
454: ListSelectionListener listener) {
455: listenerList.remove(ListSelectionListener.class, listener);
456: }
457:
458: /**
459: * Returns an array of all the list selection listeners
460: * registered on this <code>DefaultListSelectionModel</code>.
461: *
462: * @return all of this model's <code>ListSelectionListener</code>s
463: * or an empty
464: * array if no list selection listeners are currently registered
465: *
466: * @see #addListSelectionListener(ListSelectionListener)
467: * @see #removeListSelectionListener(ListSelectionListener)
468: */
469: public ListSelectionListener[] getListSelectionListeners() {
470: return listenerList.getListeners(ListSelectionListener.class);
471: }
472:
473: // Change Handling and Listener Notification ****************************
474:
475: // Updates first and last change indices
476: private void markAsDirty(int index) {
477: if (index < 0)
478: return;
479: firstAdjustedIndex = Math.min(firstAdjustedIndex, index);
480: lastAdjustedIndex = Math.max(lastAdjustedIndex, index);
481: }
482:
483: /**
484: * Notifies listeners that we have ended a series of adjustments.
485: *
486: * @param isAdjusting true if there are multiple changes
487: */
488: private void fireValueChanged(boolean isAdjusting) {
489: if (lastChangedIndex == MIN) {
490: return;
491: }
492:
493: /* Change the values before sending the event to the
494: * listeners in case the event causes a listener to make
495: * another change to the selection.
496: */
497: int oldFirstChangedIndex = firstChangedIndex;
498: int oldLastChangedIndex = lastChangedIndex;
499: firstChangedIndex = MAX;
500: lastChangedIndex = MIN;
501: fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex,
502: isAdjusting);
503: }
504:
505: /**
506: * Notifies <code>ListSelectionListeners</code> that the value
507: * of the selection, in the closed interval <code>firstIndex</code>,
508: * <code>lastIndex</code>, has changed.
509: *
510: * @param firstIndex the first index that has changed
511: * @param lastIndex the last index that has changed
512: */
513: private void fireValueChanged(int firstIndex, int lastIndex) {
514: fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
515: }
516:
517: /**
518: * Notifies all registered listeners that the selection has changed.
519: *
520: * @param firstIndex the first index in the interval
521: * @param lastIndex the last index in the interval
522: * @param isAdjusting true if this is the final change in a series of
523: * adjustments
524: * @see EventListenerList
525: */
526: private void fireValueChanged(int firstIndex, int lastIndex,
527: boolean isAdjusting) {
528: Object[] listeners = listenerList.getListenerList();
529: ListSelectionEvent e = null;
530: for (int i = listeners.length - 2; i >= 0; i -= 2) {
531: if (listeners[i] == ListSelectionListener.class) {
532: if (e == null) {
533: e = new ListSelectionEvent(this , firstIndex,
534: lastIndex, isAdjusting);
535: }
536: ((ListSelectionListener) listeners[i + 1])
537: .valueChanged(e);
538: }
539: }
540: }
541:
542: private void fireValueChanged() {
543: if (lastAdjustedIndex == MIN) {
544: return;
545: }
546:
547: /* If getValueAdjusting() is true, (eg. during a drag opereration)
548: * record the bounds of the changes so that, when the drag finishes (and
549: * setValueAdjusting(false) is called) we can post a single event
550: * with bounds covering all of these individual adjustments.
551: */
552: if (getValueIsAdjusting()) {
553: firstChangedIndex = Math.min(firstChangedIndex,
554: firstAdjustedIndex);
555: lastChangedIndex = Math.max(lastChangedIndex,
556: lastAdjustedIndex);
557: }
558:
559: /* Change the values before sending the event to the
560: * listeners in case the event causes a listener to make
561: * another change to the selection.
562: */
563: int oldFirstAdjustedIndex = firstAdjustedIndex;
564: int oldLastAdjustedIndex = lastAdjustedIndex;
565: firstAdjustedIndex = MAX;
566: lastAdjustedIndex = MIN;
567: fireValueChanged(oldFirstAdjustedIndex, oldLastAdjustedIndex);
568: }
569:
570: // Helper Classes *******************************************************
571:
572: /**
573: * Listens to changes of the selection index.
574: */
575: private final class SelectionIndexChangeHandler implements
576: PropertyChangeListener {
577:
578: /**
579: * The selection index has been changed.
580: * Notifies all registered listeners about the change.
581: *
582: * @param evt the property change event to be handled
583: */
584: public void propertyChange(PropertyChangeEvent evt) {
585: Object oldValue = evt.getOldValue();
586: Object newValue = evt.getNewValue();
587: int oldIndex = (oldValue == null) ? MIN
588: : ((Integer) oldValue).intValue();
589: int newIndex = (newValue == null) ? MIN
590: : ((Integer) newValue).intValue();
591: setSelectionIndex(oldIndex, newIndex);
592: }
593: }
594:
595: }
|