source.ca.odell.glazedlists.swing.EventListModel Maven / Gradle / Ivy
Show all versions of glazedlists_java15 Show documentation
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import java.util.ArrayList;
import java.util.List;
/**
* An EventListModel adapts an EventList to the ListModel interface making it
* appropriate for use with a {@link JList}. Each element of the list
* corresponds to an element in the {@link ListModel}.
*
* The EventListModel class is not thread-safe. Unless
* otherwise noted, all methods are only safe to be called from the event
* dispatch thread. To do this programmatically, use
* {@link SwingUtilities#invokeAndWait(Runnable)}.
*
* @see Bug 14
* @see Bug 146
* @see Bug 177
* @see Bug 228
* @see SwingUtilities#invokeAndWait(Runnable)
*
* @author Jesse Wilson
*/
public class EventListModel implements ListEventListener, ListModel {
/** the proxy moves events to the Swing Event Dispatch thread */
private TransformedList swingSource;
/** whom to notify of data changes */
private final List listeners = new ArrayList();
/** recycle the list data event to prevent unnecessary object creation */
protected final MutableListDataEvent listDataEvent = new MutableListDataEvent(this);
/**
* Creates a new model that contains all objects located in the given
* source
and reacts to any changes in the given
* source
.
*/
public EventListModel(EventList source) {
// lock the source list for reading since we want to prevent writes
// from occurring until we fully initialize this EventListModel
source.getReadWriteLock().readLock().lock();
try {
swingSource = GlazedListsSwing.swingThreadProxyList(source);
swingSource.addListEventListener(this);
} finally {
source.getReadWriteLock().readLock().unlock();
}
}
/**
* For implementing the ListEventListener interface. This sends changes
* to the table which can repaint the table cells. Because this class uses
* a EventThreadProxy, it is guaranteed that all natural
* calls to this method use the Swing thread.
*
* This always sends discrete changes for the complete size of the list.
* It may be more efficient to implement a threshhold where a large list
* of changes are grouped together as a single change. This is how the
* ListTable accepts large change events.
*/
public void listChanged(ListEvent listChanges) {
// build an "optimized" ListDataEvent describing the precise range of rows in the first block
listChanges.nextBlock();
final int startIndex = listChanges.getBlockStartIndex();
final int endIndex = listChanges.getBlockEndIndex();
listDataEvent.setRange(startIndex, endIndex);
final int changeType = listChanges.getType();
switch (changeType) {
case ListEvent.INSERT: listDataEvent.setType(ListDataEvent.INTERVAL_ADDED); break;
case ListEvent.DELETE: listDataEvent.setType(ListDataEvent.INTERVAL_REMOVED); break;
case ListEvent.UPDATE: listDataEvent.setType(ListDataEvent.CONTENTS_CHANGED); break;
}
// if another block exists, fallback to using a generic "data changed" ListDataEvent
if (listChanges.nextBlock()) {
listDataEvent.setRange(0, Integer.MAX_VALUE);
listDataEvent.setType(ListDataEvent.CONTENTS_CHANGED);
}
fireListDataEvent(listDataEvent);
}
/**
* Retrieves the value at the specified location from the table.
*
* Before every get, we need to validate the row because there may be an
* update waiting in the event queue. For example, it is possible that
* the source list has been updated by a database thread. Such a change
* may have been sent as notification, but after this request in the
* event queue. In the case where a row is no longer available, null is
* returned. The value returned is insignificant in this case because the
* Event queue will very shortly be repainting (or removing) the row
* anyway.
*
* @see ca.odell.glazedlists.swing.EventTableModel#getValueAt(int,int) ListTable
*/
public Object getElementAt(int index) {
swingSource.getReadWriteLock().readLock().lock();
try {
return swingSource.get(index);
} finally {
swingSource.getReadWriteLock().readLock().unlock();
}
}
/**
* Gets the size of the list.
*/
public int getSize() {
swingSource.getReadWriteLock().readLock().lock();
try {
return swingSource.size();
} finally {
swingSource.getReadWriteLock().readLock().unlock();
}
}
/**
* Registers the specified ListDataListener to receive updates whenever
* this list changes.
*
* The specified ListDataListener must not save a
* reference to the ListDataEvent beyond the end of the notification
* method. This is because the ListDataEvent is re-used to increase
* the performance of this implementation.
*/
public void addListDataListener(ListDataListener listDataListener) {
listeners.add(listDataListener);
}
/**
* Deregisters the specified ListDataListener from receiving updates
* whenever this list changes.
*/
public void removeListDataListener(ListDataListener listDataListener) {
listeners.remove(listDataListener);
}
/**
* Notifies all ListDataListeners about one block of changes in the list.
*/
protected void fireListDataEvent(ListDataEvent listDataEvent) {
// notify all listeners about the event
for(int i = 0, n = listeners.size(); i < n; i++) {
ListDataListener listDataListener = listeners.get(i);
switch (listDataEvent.getType()) {
case ListDataEvent.CONTENTS_CHANGED: listDataListener.contentsChanged(listDataEvent); break;
case ListDataEvent.INTERVAL_ADDED: listDataListener.intervalAdded(listDataEvent); break;
case ListDataEvent.INTERVAL_REMOVED: listDataListener.intervalRemoved(listDataEvent); break;
}
}
}
/**
* Releases the resources consumed by this {@link EventListModel} so that it
* may eventually be garbage collected.
*
*
An {@link EventListModel} will be garbage collected without a call to
* {@link #dispose()}, but not before its source {@link EventList} is garbage
* collected. By calling {@link #dispose()}, you allow the {@link EventListModel}
* to be garbage collected before its source {@link EventList}. This is
* necessary for situations where an {@link EventListModel} is short-lived but
* its source {@link EventList} is long-lived.
*
*
Warning: It is an error
* to call any method on an {@link EventListModel} after it has been disposed.
* As such, this {@link EventListModel} should be detached from its
* corresponding Component before it is disposed.
*/
public void dispose() {
swingSource.removeListEventListener(this);
swingSource.dispose();
}
}