All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.odell.glazedlists.swing.DefaultEventListModel Maven / Gradle / Ivy

/* 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.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;

import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

/**
 * A DefaultEventListModel 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 DefaultEventListModel 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)} * and wrap the source list (or some part of the source list's pipeline) using * {@link GlazedListsSwing#swingThreadProxyList(EventList)}. * * @see Bug 14 * @see Bug 146 * @see Bug 177 * @see Bug 228 * @see SwingUtilities#invokeAndWait(Runnable) * * @author Jesse Wilson * @author Holger Brands */ public class DefaultEventListModel implements ListEventListener, ListModel { /** the source EventList */ protected EventList source; /** indicator to dispose source list */ private boolean disposeSource; /** 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. * * @param source the EventList that provides the elements */ public DefaultEventListModel(EventList source) { this(source, false); } /** * Creates a new model that contains all objects located in the given * source and reacts to any changes in the given * source. * * @param source the EventList that provides the elements * @param diposeSource true if the source list should be disposed when disposing * this model, false otherwise */ protected DefaultEventListModel(EventList source, boolean disposeSource) { this.source = source; this.disposeSource = disposeSource; this.source.addListEventListener(this); } /** * For implementing the ListEventListener interface. This sends changes * to the table which can repaint the table cells. It's checked that all natural * calls to this method arrive on 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) { if (!EventQueue.isDispatchThread()) throw new IllegalStateException("Events to " + this.getClass().getSimpleName() + " must arrive on the EDT - consider adding GlazedListsSwing.swingThreadProxyList(source) somewhere in your list pipeline"); // 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); } /** * Returns the value at the specified index. * * @param index the requested index * @return the value at index */ public Object getElementAt(int index) { source.getReadWriteLock().readLock().lock(); try { return source.get(index); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Gets the size of the list. */ public int getSize() { source.getReadWriteLock().readLock().lock(); try { return source.size(); } finally { source.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 DefaultEventListModel} so that it * may eventually be garbage collected. * *

An {@link DefaultEventListModel} 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 DefaultEventListModel} * to be garbage collected before its source {@link EventList}. This is * necessary for situations where an {@link DefaultEventListModel} is short-lived but * its source {@link EventList} is long-lived. * *

Warning: It is an error * to call any method on an {@link DefaultEventListModel} after it has been disposed. * As such, this {@link DefaultEventListModel} should be detached from its * corresponding Component before it is disposed. */ public void dispose() { source.removeListEventListener(this); if (disposeSource) source.dispose(); // this encourages exceptions to be thrown if this model is incorrectly accessed again source = null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy