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

source.ca.odell.glazedlists.swing.EventTableModel Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
/* 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.GlazedLists;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.gui.AdvancedTableFormat;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.gui.WritableTableFormat;

import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;

/**
 * A {@link TableModel} that holds an {@link EventList}. Each element of the list
 * corresponds to a row in the {@link TableModel}. The columns of the table are
 * specified using a {@link TableFormat}.
 *
 * 

The EventTableModel 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 Glazed Lists Tutorial * * @see SwingUtilities#invokeAndWait(Runnable) * @see Bug 112 * @see Bug 146 * @see Bug 177 * * @author Jesse Wilson */ public class EventTableModel extends AbstractTableModel implements ListEventListener { /** the proxy moves events to the Swing Event Dispatch thread */ protected TransformedList swingThreadSource; /** the source of data for this TableModel, which may or may not be {@link #swingThreadSource} */ protected EventList source; /** specifies how column data is extracted from each row object */ private TableFormat tableFormat; /** reusable TableModelEvent for broadcasting changes */ private final MutableTableModelEvent tableModelEvent = new MutableTableModelEvent(this); /** * Creates a new table model that extracts column data from the given * source using the the given tableFormat. * * @param source the EventList that provides the row objects * @param tableFormat the object responsible for extracting column data * from the row objects */ public EventTableModel(EventList source, TableFormat tableFormat) { // lock the source list for reading since we want to prevent writes // from occurring until we fully initialize this EventTableModel source.getReadWriteLock().readLock().lock(); try { final TransformedList decorated = createSwingThreadProxyList(source); // if the create method actually returned a decorated form of the source, // record it so it may later be disposed if (decorated != null && decorated != source) this.source = swingThreadSource = decorated; else this.source = source; this.tableFormat = tableFormat; // prepare listeners this.source.addListEventListener(this); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Creates a new table that renders the specified list with an automatically * generated {@link TableFormat}. It uses JavaBeans and reflection to create * a {@link TableFormat} as specified. * *

Note that the classes which will be obfuscated may not work with * reflection. In this case, implement a {@link TableFormat} manually. * * @param source the EventList that provides the row objects * @param propertyNames an array of property names in the JavaBeans format. * For example, if your list contains Objects with the methods getFirstName(), * setFirstName(String), getAge(), setAge(Integer), then this array should * contain the two strings "firstName" and "age". This format is specified * by the JavaBeans {@link java.beans.PropertyDescriptor}. * @param columnLabels the corresponding column names for the listed property * names. For example, if your columns are "firstName" and "age", then * your labels might be "First Name" and "Age". * @param writable an array of booleans specifying which of the columns in * your table are writable. */ public EventTableModel(EventList source, String[] propertyNames, String[] columnLabels, boolean[] writable) { this(source, GlazedLists.tableFormat(propertyNames, columnLabels, writable)); } /** * This method exists as a hook for subclasses that may have custom * threading needs within their EventTableModels. By default, this method * will wrap the given source in a SwingThreadProxyList if it * is not already a SwingThreadProxyList. Subclasses may replace this logic * and return either a custom ThreadProxyEventList of their choosing, or * return null or the source unchanged in order * to indicate that NO ThreadProxyEventList is desired. * In these cases it is expected that some external mechanism will ensure * that threading is handled correctly. * * @param source the EventList that provides the row objects * @return the source wrapped in some sort of ThreadProxyEventList if * Thread-proxying is desired, or either null or the * source unchanged to indicate that NO * Thread-proxying is desired */ protected TransformedList createSwingThreadProxyList(EventList source) { return GlazedListsSwing.isSwingThreadProxyList(source) ? null : GlazedListsSwing.swingThreadProxyList(source); } /** * Gets the Table Format. */ public TableFormat getTableFormat() { return tableFormat; } /** * Sets the {@link TableFormat} that will extract column data from each * element. This has some very important consequences. Any cell selections * will be lost - this is due to the fact that the TableFormats may have * different numbers of columns, and JTable has no event to specify columns * changing without rows. */ public void setTableFormat(TableFormat tableFormat) { this.tableFormat = tableFormat; tableModelEvent.setStructureChanged(); fireTableChanged(tableModelEvent); } /** * Retrieves the value at the specified location from the table. * *

This may be used by renderers to paint the cells of a row differently * based on the entire value for that row. * * @see #getValueAt(int,int) */ public E getElementAt(int index) { source.getReadWriteLock().readLock().lock(); try { return source.get(index); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * For implementing the ListEventListener interface. This sends changes * to the table which repaints the table cells. Because this class is * backed by {@link GlazedListsSwing#swingThreadProxyList}, all natural * calls to this method are guaranteed to occur on the Swing EDT. */ public void listChanged(ListEvent listChanges) { handleListChange(listChanges); } /** * Default implementation for converting a {@link ListEvent} to * TableModelEvents. There will be one TableModelEvent per ListEvent block. * Subclasses may choose to implement a different conversion. * * @param listChanges ListEvent to translate */ protected void handleListChange(ListEvent listChanges) { // for all changes, one block at a time while (listChanges.nextBlock()) { // get the current change info int startIndex = listChanges.getBlockStartIndex(); int endIndex = listChanges.getBlockEndIndex(); int changeType = listChanges.getType(); // create a table model event for this block tableModelEvent.setValues(startIndex, endIndex, changeType); fireTableChanged(tableModelEvent); } } /** * @return reusable TableModelEvent for broadcasting changes */ protected final MutableTableModelEvent getMutableTableModelEvent() { return tableModelEvent; } /** * Fetch the name for the specified column. */ public String getColumnName(int column) { return tableFormat.getColumnName(column); } /** * The number of rows equals the number of entries in the source event list. */ public int getRowCount() { source.getReadWriteLock().readLock().lock(); try { return source.size(); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Get the column count as specified by the table format. */ public int getColumnCount() { return tableFormat.getColumnCount(); } /** * Gets the class of elements in the specified column. This behaviour can be * customized by implementing the {@link AdvancedTableFormat} interface. */ public Class getColumnClass(int columnIndex) { // See if the TableFormat is specifies a column class if(tableFormat instanceof AdvancedTableFormat) { return ((AdvancedTableFormat)tableFormat).getColumnClass(columnIndex); // If not, use the default... } else { return super.getColumnClass(columnIndex); } } /** * Retrieves the value at the specified location of the table. */ public Object getValueAt(int row, int column) { source.getReadWriteLock().readLock().lock(); try { return tableFormat.getColumnValue(source.get(row), column); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Delegates the question of whether the cell is editable or not to the * backing TableFormat if it is a {@link WritableTableFormat}. Otherwise, * the column is assumed to be uneditable. */ public boolean isCellEditable(int row, int column) { if (!(tableFormat instanceof WritableTableFormat)) return false; source.getReadWriteLock().readLock().lock(); try { final E toEdit = source.get(row); return ((WritableTableFormat) tableFormat).isEditable(toEdit, column); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Attempts to update the object for the given row with the * editedValue. This requires the backing TableFormat * be a {@link WritableTableFormat}. {@link WritableTableFormat#setColumnValue} * is expected to contain the logic for updating the object at the given * row with the editedValue which was in the * given column. */ public void setValueAt(Object editedValue, int row, int column) { // ensure this is a writable table if (!(tableFormat instanceof WritableTableFormat)) throw new UnsupportedOperationException("Unexpected setValueAt() on read-only table"); source.getReadWriteLock().writeLock().lock(); try { // get the object being edited from the source list final E baseObject = source.get(row); // tell the table format to set the value based final WritableTableFormat writableTableFormat = (WritableTableFormat) tableFormat; final E updatedObject = writableTableFormat.setColumnValue(baseObject, editedValue, column); // if the edit was discarded we have nothing to do if (updatedObject != null) { // check if updating the baseObject has caused it to be removed from this // TableModel (FilterList) or moved to another location (SortedList) final boolean baseObjectHasNotMoved = row < getRowCount() && source.get(row) == baseObject; // if the row is still present in its original location, update it to induce a // TableModelEvent that will redraw that row in the table if (baseObjectHasNotMoved) source.set(row, updatedObject); } } finally { source.getReadWriteLock().writeLock().unlock(); } } /** * Releases the resources consumed by this {@link EventTableModel} so that it * may eventually be garbage collected. * *

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

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





© 2015 - 2025 Weber Informatics LLC | Privacy Policy