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

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;

// the core Glazed Lists packages
import ca.odell.glazedlists.*;
import ca.odell.glazedlists.gui.*;
import ca.odell.glazedlists.event.*;
// Swing toolkit stuff for displaying widgets
import javax.swing.*;
// tables for displaying lists
import javax.swing.table.*;

/**
 * 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 must
 * be 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; /** true indicates that disposing this TableModel should dispose of the swingThreadSource as well */ private final boolean disposeSwingThreadSource; /** specifies how to render table headers and sort */ private TableFormat tableFormat; /** reusable table event for broadcasting changes */ private final MutableTableModelEvent tableModelEvent = new MutableTableModelEvent(this); /** * Creates a new table that renders the specified list in the specified * format. */ 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 { disposeSwingThreadSource = !GlazedListsSwing.isSwingThreadProxyList(source); swingThreadSource = disposeSwingThreadSource ? GlazedListsSwing.swingThreadProxyList(source) : (TransformedList) source; this.tableFormat = tableFormat; // prepare listeners swingThreadSource.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 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, (TableFormat) GlazedLists.tableFormat(propertyNames, columnLabels, writable)); } /** * Gets the Table Format. */ public TableFormat getTableFormat() { return tableFormat; } /** * Sets this table to be rendered by a different table format. This has * some very important consequences. The selection will be lost - this is * due to the fact that the table formats 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) { swingThreadSource.getReadWriteLock().readLock().lock(); try { return swingThreadSource.get(index); } finally { swingThreadSource.getReadWriteLock().readLock().unlock(); } } /** * For implementing the ListEventListener interface. This sends changes * to the table which repaint 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) { swingThreadSource.getReadWriteLock().readLock().lock(); try { // 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); } } finally { swingThreadSource.getReadWriteLock().readLock().unlock(); } } /** * 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() { swingThreadSource.getReadWriteLock().readLock().lock(); try { return swingThreadSource.size(); } finally { swingThreadSource.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 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. */ public Object getValueAt(int row, int column) { swingThreadSource.getReadWriteLock().readLock().lock(); try { return tableFormat.getColumnValue(swingThreadSource.get(row), column); } finally { swingThreadSource.getReadWriteLock().readLock().unlock(); } } /** * The list table is not editable. For an editable list table, use the * WritableListTable instead. */ public boolean isCellEditable(int row, int column) { // ensure this is a writable table if(tableFormat instanceof WritableTableFormat) { WritableTableFormat writableTableFormat = (WritableTableFormat)tableFormat; swingThreadSource.getReadWriteLock().readLock().lock(); try { final E toEdit = swingThreadSource.get(row); return writableTableFormat.isEditable(toEdit, column); } finally { swingThreadSource.getReadWriteLock().readLock().unlock(); } // this is not a writable table } else { return false; } } /** * The list table is not editable. For an editable list table, use the * WritableListTable instead. */ public void setValueAt(Object editedValue, int row, int column) { // ensure this is a writable table if(tableFormat instanceof WritableTableFormat) { swingThreadSource.getReadWriteLock().writeLock().lock(); try { final WritableTableFormat writableTableFormat = (WritableTableFormat)tableFormat; // get the object being edited from the source list final E baseObject = swingThreadSource.get(row); // tell the table format to set the value based on what it knows final E updatedObject = writableTableFormat.setColumnValue(baseObject, editedValue, column); // try to update the list with the revised value 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 < this.getRowCount() && swingThreadSource.get(row) == baseObject; // if the row is still present, update it if(baseObjectHasNotMoved) swingThreadSource.set(row, updatedObject); } } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } // this is not a writable table } else { throw new UnsupportedOperationException("Unexpected setValueAt() on read-only table"); } } /** * 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. */ public void dispose() { swingThreadSource.removeListEventListener(this); // if we created the swingThreadSource then we must also dispose it if (disposeSwingThreadSource) swingThreadSource.dispose(); // this encourages exceptions to be thrown if this model is incorrectly accessed again swingThreadSource = null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy