/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
// TODO javadoc - remove this comment only when the class and all non-public
// methods and fields are documented
package org.beanfabrics.swing.table;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.beanfabrics.event.ElementChangedEvent;
import org.beanfabrics.event.ElementsAddedEvent;
import org.beanfabrics.event.ElementsDeselectedEvent;
import org.beanfabrics.event.ElementsRemovedEvent;
import org.beanfabrics.event.ElementsReplacedEvent;
import org.beanfabrics.event.ElementsSelectedEvent;
import org.beanfabrics.event.ListListener;
import org.beanfabrics.event.WeakListListener;
import org.beanfabrics.model.IListPM;
import org.beanfabrics.model.PresentationModel;
/**
* The <code>BnTableSelectionModel</code> is a {@link ListSelectionModel} that
* decorates a {@link IListPM}.
*
* @author Michael Karneim
*/
public class BnTableSelectionModel implements ListSelectionModel {
private IListPM<? extends PresentationModel> list;
private ListListener listener = new WeakListListener() {
/* TODO (mk) currently we don't care about the selection mode
* when receiving and forwarding events from the IListPM
* to the JTable. So it't possible to have multiple selections
* even if the mode is SINGLE_SELECTION.
* This might annoy some people. We have to think it over.
*/
public void elementsDeselected(ElementsDeselectedEvent evt) {
//fireValueChanged(evt.getBeginIndex(), evt.getLength() );
int index0 = Math.min(leadSelectionIndex, anchorSelectionIndex);
index0 = Math.min(index0, evt.getBeginIndex());
int index1 = Math.max(leadSelectionIndex, anchorSelectionIndex);
index1 = Math.max(index1, evt.getBeginIndex() + evt.getLength() - 1);
fireValueChanged(index0, index1 - index0 + 1);
}
public void elementsSelected(ElementsSelectedEvent evt) {
//fireValueChanged(evt.getBeginIndex(), evt.getLength() );
int index0 = Math.min(leadSelectionIndex, anchorSelectionIndex);
index0 = Math.min(index0, evt.getBeginIndex());
int index1 = Math.max(leadSelectionIndex, anchorSelectionIndex);
index1 = Math.max(index1, evt.getBeginIndex() + evt.getLength() - 1);
if (leadSelectionIndex == -1) {
leadSelectionIndex = evt.getBeginIndex();
}
fireValueChanged(index0, index1 - index0 + 1);
}
public void elementChanged(ElementChangedEvent evt) {
// we can ignore that
}
public void elementsAdded(ElementsAddedEvent evt) {
// TODO (mk) Do we need to do this here ?
if (leadSelectionIndex >= evt.getBeginIndex()) {
leadSelectionIndex += evt.getLength();
}
}
public void elementsRemoved(ElementsRemovedEvent evt) {
// TODO (mk) Do we need to do this here ?
if (leadSelectionIndex >= evt.getBeginIndex() && leadSelectionIndex <= evt.getBeginIndex() + evt.getLength()) {
leadSelectionIndex = -1;
}
}
public void elementsReplaced(ElementsReplacedEvent evt) {
// we can ignore that
}
};
private final CopyOnWriteArrayList<ListSelectionListener> eventListeners = new CopyOnWriteArrayList<ListSelectionListener>();
private int selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
private boolean valueIsAdjusting = false;
// Holding the first and the second index argument from the most recent call to
// setSelectionInterval(), addSelectionInterval() or removeSelectionInterval().
private int anchorSelectionIndex = -1;
private int leadSelectionIndex = -1;
// Dirty indices for adjusting mode
private int minLastChangedIndex = -1;
private int maxLastChangedIndex = -1;
public BnTableSelectionModel(IListPM<? extends PresentationModel> pModel) {
if (pModel == null) {
throw new IllegalArgumentException("pModel must not be null");
}
this.list = pModel;
this.list.addListListener(this.listener);
}
/**
* Disconnect <code>this</code> object from the underlying IListPM.
*/
public void dismiss() {
this.list.removeListListener(this.listener);
}
/** {@inheritDoc} */
public void addListSelectionListener(ListSelectionListener l) {
if (l == null) {
throw new IllegalArgumentException("l must not be null.");
}
eventListeners.add(l);
}
/** {@inheritDoc} */
public void removeListSelectionListener(ListSelectionListener l) {
if (l == null) {
throw new IllegalArgumentException("l must not be null.");
}
eventListeners.remove(l);
}
/** {@inheritDoc} */
public int getSelectionMode() {
return this.selectionMode;
}
/** {@inheritDoc} */
public void setSelectionMode(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");
}
}
/** {@inheritDoc} */
public boolean getValueIsAdjusting() {
return valueIsAdjusting;
}
/** {@inheritDoc} */
public void setValueIsAdjusting(boolean valueIsAdjusting) {
// out("BnTableSelectionModel.setValueIsAdjusting("+valueIsAdjusting+")");
if (valueIsAdjusting == this.valueIsAdjusting) {
return;
}
this.valueIsAdjusting = valueIsAdjusting;
int index0 = minLastChangedIndex;
int index1 = maxLastChangedIndex;
minLastChangedIndex = -1;
maxLastChangedIndex = -1;
if (this.valueIsAdjusting == false && index0 != -1 && index1 != -1) {
fireValueChangedBetween(index0, index1);
//fireValueChangedBetween(0, Integer.MAX_VALUE);
}
}
/** {@inheritDoc} */
public int getMaxSelectionIndex() {
return list.getSelection().getMaxIndex();
}
/** {@inheritDoc} */
public int getMinSelectionIndex() {
return list.getSelection().getMinIndex();
}
/** {@inheritDoc} */
public int getAnchorSelectionIndex() {
// out("BnTableSelectionModel.getAnchorSelectionIndex() "+anchorSelectionIndex);
// Return the first index argument from the most recent call to
// setSelectionInterval(), addSelectionInterval() or removeSelectionInterval().
return anchorSelectionIndex;
}
/** {@inheritDoc} */
public void setAnchorSelectionIndex(int index) {
// out("BnTableSelectionModel.setAnchorSelectionIndex("+index+")###");
int oldAnchor = this.anchorSelectionIndex;
this.anchorSelectionIndex = index;
fireValueChangedBetween(oldAnchor, anchorSelectionIndex);
}
/** {@inheritDoc} */
public int getLeadSelectionIndex() {
// out("BnTableSelectionModel.getLeadSelectionIndex() "+leadSelectionIndex);
return leadSelectionIndex;
}
/** {@inheritDoc} */
public void setLeadSelectionIndex(int index) {
// out("BnTableSelectionModel.setLeadSelectionIndex("+index+")###");
int oldLead = this.leadSelectionIndex;
this.leadSelectionIndex = index;
fireValueChangedBetween(oldLead, anchorSelectionIndex);
}
/** {@inheritDoc} */
public boolean isSelectedIndex(int index) {
return list.getSelection().contains(index);
}
/** {@inheritDoc} */
public boolean isSelectionEmpty() {
return list.getSelection().isEmpty();
}
/** {@inheritDoc} */
public void clearSelection() {
list.getSelection().clear();
}
/** {@inheritDoc} */
public void setSelectionInterval(int index0, int index1) {
// out("BnTableSelectionModel.setSelectionInterval("+index0+","+index1+")");
// To be compatible with standard Swing:
if (getSelectionMode() == SINGLE_SELECTION) {
index0 = index1;
}
int beginIndex = Math.min(index0, index1);
int endIndex = Math.max(index0, index1);
updateLeadAnchorIndices(index0, index1);
if (index0 == -1) {
list.getSelection().clear();
} else {
list.getSelection().setInterval(beginIndex, endIndex);
}
}
/** {@inheritDoc} */
public void addSelectionInterval(int index0, int index1) {
// out("BnTableSelectionModel.addSelectionInterval("+index0+","+index1+")");
if (index0 == -1 || index1 == -1) {
return;
}
int beginIndex = Math.min(index0, index1);
int endIndex = Math.max(index0, index1);
// To be compatible with standard Swing:
// if we only allow a single selection, channel through
// setSelectionInterval() to enforce the rule.
if (getSelectionMode() == SINGLE_SELECTION) {
setSelectionInterval(index0, index1);
return;
}
// If we only allow a single interval and this would result
// in multiple intervals, then set the selection to be just
// the new range.
if (getSelectionMode() == SINGLE_INTERVAL_SELECTION && list.getSelection().isEmpty() == false) {
if (list.getSelection().getMaxIndex() < beginIndex || endIndex < list.getSelection().getMinIndex()) {
setSelectionInterval(index0, index1);
return;
}
}
updateLeadAnchorIndices(index0, index1);
list.getSelection().addInterval(beginIndex, endIndex);
}
/** {@inheritDoc} */
public void removeSelectionInterval(int index0, int index1) {
// out("BnTableSelectionModel.removeSelectionInterval("+index0+","+index1+")");
int beginIndex = Math.min(index0, index1);
int endIndex = Math.max(index0, index1);
// To be compatible with standard Swing:
// if the removal would produce to two disjoint selections in a mode
// that only allows one, extend the removal to the end of the selection.
if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION && list.getSelection().isEmpty() == false) {
if (list.getSelection().getMinIndex() < beginIndex && endIndex < list.getSelection().getMaxIndex()) {
endIndex = Math.max(endIndex, list.getSelection().getMaxIndex());
}
}
updateLeadAnchorIndices(index0, index1);
list.getSelection().removeInterval(beginIndex, endIndex);
}
/** {@inheritDoc} */
public void insertIndexInterval(int index, int length, boolean before) {
// Within beanfabrics we don't need to do anything here.
}
/** {@inheritDoc} */
public void removeIndexInterval(int index0, int index1) {
// Within beanfabrics we don't need to do anything here.
}
protected void fireValueChangedBetween(int index0, int index1) {
int beginIndex = Math.min(index0, index1);
int endIndex = Math.max(index0, index1);
int len = endIndex - beginIndex;
fireValueChanged(beginIndex, len);
}
protected void fireValueChanged(int beginIndex, int length) {
if (valueIsAdjusting) {
markDirty(beginIndex);
markDirty(beginIndex + length - 1);
}
if (eventListeners.isEmpty()) {
return;
}
ListSelectionEvent evt = new ListSelectionEvent(this, beginIndex, beginIndex + length - 1, this.valueIsAdjusting);
for (ListSelectionListener l : eventListeners) {
l.valueChanged(evt);
}
}
protected void updateLeadAnchorIndices(int index0, int index1) {
if (index0 != index1 && valueIsAdjusting) {
if (anchorSelectionIndex == -1) {
anchorSelectionIndex = index0;
}
leadSelectionIndex = index1;
} else {
anchorSelectionIndex = index0;
leadSelectionIndex = index1;
}
}
protected void markDirty(int index) {
if (valueIsAdjusting) {
if (minLastChangedIndex == -1 || minLastChangedIndex > index) {
minLastChangedIndex = index;
}
if (maxLastChangedIndex == -1 || maxLastChangedIndex < index) {
maxLastChangedIndex = index;
}
}
}
// long lasttime = 0;
// String lasttext = "";
// private void out( String text) {
// long now = System.currentTimeMillis();
// if ( now - lasttime > 1000) {
// System.out.println();
// }
// lasttime = now;
// if ( lasttext.equals(text)) {
// System.out.print(".");
// } else {
// lasttext = text;
// System.out.print("\n"+text);
// }
// System.out.flush();
// }
}