1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
|
/* 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 ca.odell.glazedlists.gui.AdvancedTableFormat;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.gui.WritableTableFormat;
import java.awt.EventQueue;
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}.
*
* <p>The EventTableModel class is <strong>not thread-safe</strong>. 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
* GlazedListsSwing#swingThreadProxyList(EventList).</p>
*
* @see <a href="http://publicobject.com/glazedlists/tutorial/">Glazed Lists Tutorial</a>
*
* @see GlazedListsSwing#swingThreadProxyList(EventList)
* @see SwingUtilities#invokeAndWait(Runnable)
* @see <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=112">Bug 112</a>
* @see <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=146">Bug 146</a>
* @see <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=177">Bug 177</a>
*
* @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
*/
public class DefaultEventTableModel<E> extends AbstractTableModel implements AdvancedTableModel<E>, ListEventListener<E> {
/** the source of data for this TableModel, which may or may not be {@link #swingThreadSource} */
protected EventList<E> source;
/** indicator to dispose source list */
private boolean disposeSource;
/** specifies how column data is extracted from each row object */
private TableFormat<? super E> 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
* <code>source</code> using the the given <code>tableFormat</code>.
*
* @param source the EventList that provides the row objects
* @param tableFormat the object responsible for extracting column data
* from the row objects
*/
public DefaultEventTableModel(EventList<E> source, TableFormat<? super E> tableFormat) {
this(source, false, tableFormat);
}
/**
* Creates a new table model that extracts column data from the given
* <code>source</code> using the the given <code>tableFormat</code>.
*
* @param source the EventList that provides the row objects
* @param diposeSource <code>true</code> if the source list should be disposed when disposing
* this model, <code>false</code> otherwise
* @param tableFormat the object responsible for extracting column data
* from the row objects
*/
protected DefaultEventTableModel(EventList<E> source, boolean disposeSource, TableFormat<? super E> tableFormat) {
this.source = source;
this.disposeSource = disposeSource;
this.tableFormat = tableFormat;
source.addListEventListener(this);
}
/**
* {@inheritDoc}
*/
public TableFormat<? super E> getTableFormat() {
return tableFormat;
}
/**
* {@inheritDoc}
*/
public void setTableFormat(TableFormat<? super E> tableFormat) {
this.tableFormat = tableFormat;
tableModelEvent.setStructureChanged();
fireTableChanged(tableModelEvent);
}
/**
* {@inheritDoc}
*/
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<E> 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<E> 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");
// 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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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<E>) tableFormat).isEditable(toEdit, column);
} finally {
source.getReadWriteLock().readLock().unlock();
}
}
/**
* Attempts to update the object for the given row with the
* <code>editedValue</code>. 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
* <code>row</code> with the <code>editedValue</code> which was in the
* given <code>column</code>.
*/
@Override
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<E> writableTableFormat = (WritableTableFormat<E>) 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();
}
}
/**
* {@inheritDoc}
*/
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;
}
}
|