/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
package de.cismet.cismap.commons.features;
import java.util.Collection;
import java.util.Vector;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* DOCUMENT ME!
*
* @author thorsten.hell@cismet.de
* @version $Revision$, $Date$
*/
public class FeatureCollectionAndListModel extends DefaultFeatureCollection implements ListSelectionModel {
//~ Static fields/initializers ---------------------------------------------
private static final int MIN = -1;
private static final int MAX = Integer.MAX_VALUE;
//~ Instance fields --------------------------------------------------------
protected Vector<ListSelectionListener> listSelectionListeners = new Vector<ListSelectionListener>();
protected int leadIndex = 0;
protected int anchorIndex = 0;
protected boolean valueIsAdjusting = false;
private final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(this.getClass());
private int selectionMode = MULTIPLE_INTERVAL_SELECTION;
private int firstChangedIndex = MAX;
private int lastChangedIndex = MIN;
private int firstAdjustedIndex = MAX;
private int lastAdjustedIndex = MIN;
//~ Methods ----------------------------------------------------------------
/**
* Set the selection mode. The following selectionMode values are allowed:
*
* <ul>
* <li> <code>SINGLE_SELECTION</code> Only one list index can be selected at a time. In this mode the
* setSelectionInterval and addSelectionInterval methods are equivalent, and only the second index argument (the
* "lead index") is used.</li>
* <li> <code>SINGLE_INTERVAL_SELECTION</code> One contiguous index interval can be selected at a time. In this
* mode setSelectionInterval and addSelectionInterval are equivalent.</li>
* <li> <code>MULTIPLE_INTERVAL_SELECTION</code> In this mode, there's no restriction on what can be selected.
* </li>
* </ul>
*
* @param selectionMode DOCUMENT ME!
*
* @throws IllegalArgumentException DOCUMENT ME!
*
* @see #getSelectionMode
*/
@Override
public void setSelectionMode(final int selectionMode) {
switch (selectionMode) {
case SINGLE_SELECTION:
case SINGLE_INTERVAL_SELECTION:
case MULTIPLE_INTERVAL_SELECTION: {
this.selectionMode = selectionMode;
break;
}
default: {
throw new IllegalArgumentException("invalid selectionMode"); // NOI18N
}
}
}
/**
* Set the lead selection index.
*
* @param index DOCUMENT ME!
*
* @see #getLeadSelectionIndex
*/
@Override
public void setLeadSelectionIndex(final int index) {
try {
leadIndex = index;
if (selectedFeatures.size() > 0) {
int from = -1;
int to = -1;
if (index > getAnchorSelectionIndex()) {
from = getAnchorSelectionIndex();
to = index;
} else {
from = index;
to = getAnchorSelectionIndex();
}
final Vector<Feature> v = new Vector<Feature>();
for (int i = from; i <= to; ++i) {
v.add((Feature)getAllFeatures().get(i));
}
select(v);
}
fireValueChanged();
} catch (Throwable t) {
log.error("Error in setLeadSelectionIndex", t); // NOI18N
}
}
/**
* Set the anchor selection index.
*
* @param index DOCUMENT ME!
*
* @see #getAnchorSelectionIndex
*/
@Override
public void setAnchorSelectionIndex(final int index) {
try {
anchorIndex = index;
if (index > -1) {
select(features.get(index));
fireValueChanged();
}
} catch (Throwable t) {
log.error("Error in setAnchorSelectionIndex", t); // NOI18N
}
}
/**
* Returns true if the specified index is selected.
*
* @param index DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
@Override
public boolean isSelectedIndex(final int index) {
try {
return isSelected((Feature)getAllFeatures().get(index));
} catch (Throwable t) {
log.error("Error in isSelectedIndex", t); // NOI18N
return false;
}
}
/**
* This property is true if upcoming changes to the value of the model should be considered a single event. For
* example if the model is being updated in response to a user drag, the value of the valueIsAdjusting property will
* be set to true when the drag is initiated and be set to false when the drag is finished. This property allows
* listeners to to update only when a change has been finalized, rather than always handling all of the intermediate
* values.
*
* @param valueIsAdjusting The new value of the property.
*
* @see #getValueIsAdjusting
*/
@Override
public void setValueIsAdjusting(final boolean valueIsAdjusting) {
try {
this.valueIsAdjusting = valueIsAdjusting;
} catch (Throwable t) {
log.error("Error in setValueIsAdjusting", t); // NOI18N
}
}
/**
* Remove a listener from the list that's notified each time a change to the selection occurs.
*
* @param x the ListSelectionListener
*
* @see #addListSelectionListener
*/
@Override
public void removeListSelectionListener(final ListSelectionListener x) {
listSelectionListeners.remove(x);
}
/**
* Add a listener to the list that's notified each time a change to the selection occurs.
*
* @param x the ListSelectionListener
*
* @see #removeListSelectionListener
* @see #setSelectionInterval
* @see #addSelectionInterval
* @see #removeSelectionInterval
* @see #clearSelection
* @see #insertIndexInterval
* @see #removeIndexInterval
*/
@Override
public void addListSelectionListener(final ListSelectionListener x) {
listSelectionListeners.add(x);
}
/**
* Insert length indices beginning before/after index. This is typically called to sync the selection model with a
* corresponding change in the data model.
*
* @param index DOCUMENT ME!
* @param length DOCUMENT ME!
* @param before DOCUMENT ME!
*/
@Override
public void insertIndexInterval(final int index, final int length, final boolean before) {
// Nothing to be done here, because the selected items are stored in a collection
}
/**
* Change the selection to be between index0 and index1 inclusive. If this represents a change to the current
* selection, then notify each ListSelectionListener. Note that index0 doesn't have to be less than or equal to
* index1.
*
* @param index0 one end of the interval.
* @param index1 other end of the interval
*
* @see #addListSelectionListener
*/
@Override
public void setSelectionInterval(final int index0, final int index1) {
try {
select(getVectorOfFeatures(index0, index1));
fireValueChanged();
} catch (Throwable t) {
log.error("Error in setSelectionInterval", t); // NOI18N
}
}
/**
* Change the selection to be the set difference of the current selection and the indices between index0 and index1
* inclusive. If this represents a change to the current selection, then notify each ListSelectionListener. Note
* that index0 doesn't have to be less than or equal to index1.
*
* @param index0 one end of the interval.
* @param index1 other end of the interval
*
* @see #addListSelectionListener
*/
@Override
public void removeSelectionInterval(final int index0, final int index1) {
try {
unselect(getVectorOfFeatures(index0, index1));
fireValueChanged();
} catch (Throwable t) {
log.error("Error in removeSelectionInterval", t); // NOI18N
}
}
/**
* Change the selection to be the set union of the current selection and the indices between index0 and index1
* inclusive. If this represents a change to the current selection, then notify each ListSelectionListener. Note
* that index0 doesn't have to be less than or equal to index1.
*
* @param index0 one end of the interval.
* @param index1 other end of the interval
*
* @see #addListSelectionListener
*/
@Override
public void addSelectionInterval(final int index0, final int index1) {
try {
addToSelection(getVectorOfFeatures(index0, index1));
} catch (Throwable t) {
log.error("Error in addSelectionInterval", t); // NOI18N
}
}
/**
* Change the selection to the empty set. If this represents a change to the current selection then notify each
* ListSelectionListener.
*
* @see #addListSelectionListener
*/
@Override
public void clearSelection() {
try {
unselectAll();
fireValueChanged();
} catch (Throwable t) {
log.error("Error in clearSelection", t); // NOI18N
}
}
/**
* Return the first index argument from the most recent call to setSelectionInterval(), addSelectionInterval() or
* removeSelectionInterval(). The most recent index0 is considered the "anchor" and the most recent index1 is
* considered the "lead". Some interfaces display these indices specially, e.g. Windows95 displays the lead index
* with a dotted yellow outline.
*
* @return DOCUMENT ME!
*
* @see #getLeadSelectionIndex
* @see #setSelectionInterval
* @see #addSelectionInterval
*/
@Override
public int getAnchorSelectionIndex() {
return anchorIndex;
}
/**
* Return the second index argument from the most recent call to setSelectionInterval(), addSelectionInterval() or
* removeSelectionInterval().
*
* @return DOCUMENT ME!
*
* @see #getAnchorSelectionIndex
* @see #setSelectionInterval
* @see #addSelectionInterval
*/
@Override
public int getLeadSelectionIndex() {
return leadIndex;
}
/**
* Returns the last selected index or -1 if the selection is empty.
*
* @return DOCUMENT ME!
*/
@Override
public int getMaxSelectionIndex() {
try {
if (selectedFeatures.isEmpty()) {
return -1;
} else {
int ret = -1;
final Collection<Feature> c = selectedFeatures;
for (final Feature f : c) {
final int index = getAllFeatures().indexOf(f);
if (index > ret) {
ret = index;
}
}
return ret;
}
} catch (Throwable t) {
log.error("Error in getMaxSelectionIndex", t); // NOI18N
return -1;
}
}
/**
* Returns the first selected index or -1 if the selection is empty.
*
* @return DOCUMENT ME!
*/
@Override
public int getMinSelectionIndex() {
try {
if (selectedFeatures.isEmpty()) {
return -1;
} else {
int ret = features.size();
final Collection<Feature> c = selectedFeatures;
for (final Feature f : c) {
final int index = getAllFeatures().indexOf(f);
if (index < ret) {
ret = index;
}
}
return ret;
}
} catch (Throwable t) {
log.error("Error in getMinSelectionIndex", t); // NOI18N
return -1;
}
}
/**
* Returns the current selection mode.
*
* @return The value of the selectionMode property.
*
* @see #setSelectionMode
*/
@Override
public int getSelectionMode() {
return selectionMode;
}
/**
* Returns true if the value is undergoing a series of changes.
*
* @return true if the value is currently adjusting
*
* @see #setValueIsAdjusting
*/
@Override
public boolean getValueIsAdjusting() {
return valueIsAdjusting;
}
/**
* Returns true if no indices are selected.
*
* @return DOCUMENT ME!
*/
@Override
public boolean isSelectionEmpty() {
try {
return selectedFeatures.isEmpty();
} catch (Throwable t) {
log.error("Error in isSelectionEmpty", t); // NOI18N
return true;
}
}
/**
* Remove the indices in the interval index0,index1 (inclusive) from the selection model. This is typically called
* to sync the selection model width a corresponding change in the data model.
*
* @param index0 DOCUMENT ME!
* @param index1 DOCUMENT ME!
*/
@Override
public void removeIndexInterval(final int index0, final int index1) {
try {
removeFeatures(getVectorOfFeatures(index0, index1));
fireValueChanged();
} catch (Throwable t) {
log.error("Error in ", t); // NOI18N
}
}
/**
* DOCUMENT ME!
*
* @param index0 DOCUMENT ME!
* @param index1 DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private Vector<Feature> getVectorOfFeatures(final int index0, final int index1) {
try {
int from = -1;
int to = -1;
if (index0 < index1) {
from = index0;
to = index1;
} else {
from = index1;
to = index0;
}
anchorIndex = from;
leadIndex = to;
final Vector<Feature> v = new Vector<Feature>();
for (int i = from; i <= to; ++i) {
v.add((Feature)getAllFeatures().get(i));
}
return v;
} catch (Throwable t) {
log.error("Error in getVectorOfFeatures", t); // NOI18N
return null;
}
}
/**
* Notifies listeners that we have ended a series of adjustments.
*
* @param isAdjusting DOCUMENT ME!
*/
protected void fireValueChanged(final boolean isAdjusting) {
if (lastChangedIndex == MIN) {
return;
}
/* Change the values before sending the event to the
* listeners in case the event causes a listener to make another change to the selection.
*/
final int oldFirstChangedIndex = firstChangedIndex;
final int oldLastChangedIndex = lastChangedIndex;
firstChangedIndex = MAX;
lastChangedIndex = MIN;
fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex, isAdjusting);
}
/**
* Notifies <code>ListSelectionListeners</code> that the value of the selection, in the closed interval <code>
* firstIndex</code>, <code>lastIndex</code>, has changed.
*
* @param firstIndex DOCUMENT ME!
* @param lastIndex DOCUMENT ME!
*/
protected void fireValueChanged(final int firstIndex, final int lastIndex) {
fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
}
/**
* DOCUMENT ME!
*
* @param firstIndex the first index in the interval
* @param lastIndex the last index in the interval
* @param isAdjusting true if this is the final change in a series of adjustments
*
* @see EventListenerList
*/
protected void fireValueChanged(final int firstIndex, final int lastIndex, final boolean isAdjusting) {
for (final ListSelectionListener l : listSelectionListeners) {
final ListSelectionEvent e = new ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting);
l.valueChanged(e);
}
}
/**
* DOCUMENT ME!
*/
private void fireValueChanged() {
if (lastAdjustedIndex == MIN) {
return;
}
/* If getValueAdjusting() is true, (eg. during a drag opereration)
* record the bounds of the changes so that, when the drag finishes (and setValueAdjusting(false) is called) we
* can post a single event with bounds covering all of these individual adjustments.
*/
if (getValueIsAdjusting()) {
firstChangedIndex = Math.min(firstChangedIndex, firstAdjustedIndex);
lastChangedIndex = Math.max(lastChangedIndex, lastAdjustedIndex);
}
/* Change the values before sending the event to the
* listeners in case the event causes a listener to make another change to the selection.
*/
final int oldFirstAdjustedIndex = firstAdjustedIndex;
final int oldLastAdjustedIndex = lastAdjustedIndex;
firstAdjustedIndex = MAX;
lastAdjustedIndex = MIN;
fireValueChanged(oldFirstAdjustedIndex, oldLastAdjustedIndex);
}
}