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

ca.odell.glazedlists.swt.DefaultEventTableViewer Maven / Gradle / Ivy

The newest version!
/* Glazed Lists                                                 (c) 2003-2012 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swt;

import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.impl.adt.Barcode;
import ca.odell.glazedlists.impl.adt.BarcodeIterator;

import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.*;


/**
 * A view helper that displays an EventList in an SWT table.
 *
 * 

This class is not thread safe. It must be used exclusively with the SWT * event handler thread. * * @author Jesse Wilson * @author Holger Brands */ public class DefaultEventTableViewer implements ListEventListener { /** the heavyweight table */ private Table table; /** Enables check support */ private TableCheckFilterList checkFilterList; /** the original source EventList to which this EventTableViewer is listening */ private EventList originalSource; /** the actual EventList to which this EventTableViewer is listening */ protected EventList source; /** to manipulate Tables in a generic way */ private TableHandler tableHandler; /** Specifies how to render table headers and sort */ private TableFormat tableFormat; /** Specifies how to render column values represented by TableItems. */ private TableItemConfigurer tableItemConfigurer; /** For selection management */ private SelectionManager selection; /** * Creates a new viewer for the given {@link Table} that updates the table * contents in response to changes on the specified {@link EventList}. The * {@link Table} is formatted with the specified {@link TableFormat}. * * @param source the EventList that provides the row objects * @param table the Table viewing the source objects * @param tableFormat the object responsible for extracting column data * from the row objects */ public DefaultEventTableViewer(EventList source, Table table, TableFormat tableFormat) { this(source, table, tableFormat, TableItemConfigurer.DEFAULT); } /** * Creates a new viewer for the given {@link Table} that updates the table * contents in response to changes on the specified {@link EventList}. The * {@link Table} is formatted with the specified {@link TableFormat}. * * @param source the EventList that provides the row objects * @param table the Table viewing the source objects * @param tableFormat the object responsible for extracting column data * from the row objects * @param tableItemConfigurer responsible for configuring table items */ public DefaultEventTableViewer(EventList source, Table table, TableFormat tableFormat, TableItemConfigurer tableItemConfigurer) { this(source, table, tableFormat, tableItemConfigurer, false); } /** * Creates a new viewer for the given {@link Table} that updates the table * contents in response to changes on the specified {@link EventList}. The * {@link Table} is formatted with the specified {@link TableFormat}. * * @param source the EventList that provides the row objects * @param table the Table viewing the source objects * @param tableFormat the object responsible for extracting column data * from the row objects * @param tableItemConfigurer responsible for configuring table items * @param diposeSource true if the source list should be disposed when disposing * this model, false otherwise */ protected DefaultEventTableViewer(EventList source, Table table, TableFormat tableFormat, TableItemConfigurer tableItemConfigurer, boolean disposeSource) { // check for valid arguments early if (source == null) throw new IllegalArgumentException("source list may not be null"); if (table == null) throw new IllegalArgumentException("Table may not be null"); if (tableFormat == null) throw new IllegalArgumentException("TableFormat may not be null"); if (tableItemConfigurer == null) throw new IllegalArgumentException("TableItemConfigurer may not be null"); // lock the source list for reading since we want to prevent writes // from occurring until we fully initialize this EventTableViewer source.getReadWriteLock().writeLock().lock(); try { this.source = source; if (disposeSource) { this.originalSource = source; } // insert a checked source if supported by the table if ((table.getStyle() & SWT.CHECK) == SWT.CHECK) { this.source = checkFilterList = new TableCheckFilterList(this.source, table, tableFormat); } this.table = table; this.tableFormat = tableFormat; this.tableItemConfigurer = tableItemConfigurer; // enable the selection lists selection = new SelectionManager(this.source, new SelectableTable()); // configure how the Table will be manipulated if(isTableVirtual()) { tableHandler = new VirtualTableHandler(); } else { tableHandler = new DefaultTableHandler(); } // setup the Table with initial values initTable(); tableHandler.populateTable(); // prepare listeners this.source.addListEventListener(this); // indicate the dependency between this EventTableViewer & the SelectionManager's ListSelection // (this is crucial because it ensures the ListEventPublisher delivers events to this EventTableViewer // *before* the SelectionManager's ListSelection, which is the correct relative order of notification) this.source.getPublisher().setRelatedListener(selection.getSelectionList(), this); } finally { source.getReadWriteLock().writeLock().unlock(); } } /** * Gets wether the table is virtual or not. */ private boolean isTableVirtual() { return ((table.getStyle() & SWT.VIRTUAL) == SWT.VIRTUAL); } /** * Builds the columns and headers for the {@link Table} */ private void initTable() { table.setHeaderVisible(true); final TableColumnConfigurer configurer = getTableColumnConfigurer(); for(int c = 0; c < tableFormat.getColumnCount(); c++) { TableColumn column = new TableColumn(table, SWT.LEFT, c); column.setText(tableFormat.getColumnName(c)); column.setWidth(80); if (configurer != null) { configurer.configure(column, c); } } } /** Removes existing columns from the table. */ private void removeTableColumns() { while (table.getColumnCount() > 0) { table.getColumns()[0].dispose(); } } /** * Sets all of the column values on a {@link TableItem}. */ private void renderTableItem(TableItem item, E value, int row) { for(int i = 0; i < tableFormat.getColumnCount(); i++) { final Object cellValue = tableFormat.getColumnValue(value, i); tableItemConfigurer.configure(item, value, cellValue, row, i); } } /** * Gets the {@link TableFormat}. */ public TableFormat getTableFormat() { return tableFormat; } /** * Sets this {@link Table} to be formatted by a different {@link TableFormat}. * @param tableFormat the new TableFormat * @throws IllegalArgumentException if tableFormat is null */ public void setTableFormat(TableFormat tableFormat) { if (tableFormat == null) throw new IllegalArgumentException("TableFormat may not be null"); this.tableFormat = tableFormat; table.setRedraw(false); removeTableColumns(); initTable(); this.tableHandler.redraw(); table.setRedraw(true); } /** * Gets the {@link TableItemConfigurer}. */ public TableItemConfigurer getTableItemConfigurer() { return tableItemConfigurer; } /** * Sets a new {@link TableItemConfigurer}. The cell values of existing, * non-virtual table items will be reconfigured with the specified configurer. */ public void setTableItemConfigurer(TableItemConfigurer tableItemConfigurer) { if (tableItemConfigurer == null) throw new IllegalArgumentException("TableItemConfigurer may not be null"); this.tableItemConfigurer = tableItemConfigurer; // determine the index of the last, non-virtual table item final int maxIndex = tableHandler.getLastIndex(); if (maxIndex < 0) return; // Disable redraws so that the table is updated in bulk table.setRedraw(false); source.getReadWriteLock().readLock().lock(); try { // reprocess all table items between indexes 0 and maxIndex for (int i = 0; i <= maxIndex; i++) { // System.out.println("setTableItemConfigurer: Reconfigure Item " + i); final E rowValue = source.get(i); for (int c = 0; c < tableFormat.getColumnCount(); c++) { final Object columnValue = tableFormat.getColumnValue(rowValue, c); tableItemConfigurer.configure(table.getItem(i), rowValue, columnValue, i, c); } } } finally { source.getReadWriteLock().readLock().unlock(); } // Re-enable redraws to update the table table.setRedraw(true); } /** * Gets the {@link TableColumnConfigurer} or null if not * available. */ private TableColumnConfigurer getTableColumnConfigurer() { if (tableFormat instanceof TableColumnConfigurer) { return (TableColumnConfigurer) tableFormat; } return null; } /** * Gets the {@link Table} that is being managed by this * {@link DefaultEventTableViewer}. */ public Table getTable() { return table; } /** * Set whether this shall show only checked elements. */ public void setCheckedOnly(boolean checkedOnly) { checkFilterList.setCheckedOnly(checkedOnly); } /** * Get whether this is showing only checked elements. */ public boolean getCheckedOnly() { return checkFilterList.getCheckedOnly(); } /** * Gets all checked items. */ public List getAllChecked() { checkFilterList.getReadWriteLock().readLock().lock(); try { return checkFilterList.getAllChecked(); } finally { checkFilterList.getReadWriteLock().readLock().unlock(); } } /** * Get the source of this {@link DefaultEventTableViewer}. */ public EventList getSourceList() { return source; } /** * Provides access to an {@link EventList} that contains items from the * viewed {@link Table} that are not currently selected. */ public EventList getDeselected() { source.getReadWriteLock().readLock().lock(); try { return selection.getSelectionList().getDeselected(); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Gets an {@link EventList} that contains only deselected values and * modifies the selection state on mutation. * * Adding an item to this list deselects it and removing an item selects it. * If an item not in the source list is added an * {@link IllegalArgumentException} is thrown */ public EventList getTogglingDeselected() { source.getReadWriteLock().readLock().lock(); try { return selection.getSelectionList().getTogglingDeselected(); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Provides access to an {@link EventList} that contains items from the * viewed {@link Table} that are currently selected. */ public EventList getSelected() { source.getReadWriteLock().readLock().lock(); try { return selection.getSelectionList().getSelected(); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Gets an {@link EventList} that contains only selected * values and modifies the selection state on mutation. * * Adding an item to this list selects it and removing an item deselects it. * If an item not in the source list is added an * {@link IllegalArgumentException} is thrown. */ public EventList getTogglingSelected() { source.getReadWriteLock().readLock().lock(); try { return selection.getSelectionList().getTogglingSelected(); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * When the source list is changed, this forwards the change to the * displayed {@link Table}. */ public void listChanged(ListEvent listChanges) { // if the table is no longer available, we don't want to do anything as // it will result in a "Widget is disposed" exception if (table.isDisposed()) return; Barcode deletes = new Barcode(); deletes.addWhite(0, source.size()); int firstChange = source.size(); // Disable redraws so that the table is updated in bulk table.setRedraw(false); // Apply changes to the list while (listChanges.next()) { int changeIndex = listChanges.getIndex(); int adjustedIndex = deletes.getIndex(changeIndex, Barcode.WHITE); int changeType = listChanges.getType(); // Insert a new element in the Table and the Barcode if (changeType == ListEvent.INSERT) { deletes.addWhite(adjustedIndex, 1); tableHandler.addRow(adjustedIndex, source.get(changeIndex)); firstChange = Math.min(changeIndex, firstChange); // Update the element in the Table } else if (changeType == ListEvent.UPDATE) { tableHandler.updateRow(adjustedIndex, source.get(changeIndex)); // Just mark the element as deleted in the Barcode } else if (changeType == ListEvent.DELETE) { deletes.setBlack(adjustedIndex, 1); firstChange = Math.min(changeIndex, firstChange); } } // Process the deletes as a single Table change if (deletes.blackSize() > 0) { int[] deletedIndices = new int[deletes.blackSize()]; for (BarcodeIterator i = deletes.iterator(); i.hasNextBlack();) { i.nextBlack(); deletedIndices[i.getBlackIndex()] = i.getIndex(); } tableHandler.removeAll(deletedIndices); } // Re-enable redraws to update the table table.setRedraw(true); } /** * Inverts the current selection. */ public void invertSelection() { selection.getSelectionList().invertSelection(); } /** * Releases the resources consumed by this {@link DefaultEventTableViewer} so that it * may eventually be garbage collected. * *

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

Warning: It is an error * to call any method on a {@link DefaultEventTableViewer} after it has been disposed. */ public void dispose() { tableHandler.dispose(); selection.dispose(); source.removeListEventListener(this); source.getPublisher().clearRelatedListener(selection.getSelectionList(), this); // if we created the checkFilterList then we must also dispose it if (checkFilterList != null) checkFilterList.dispose(); if (originalSource != null) originalSource.dispose(); // this encourages exceptions to be thrown if this model is incorrectly accessed again checkFilterList = null; tableHandler = null; selection = null; source = null; originalSource = null; } /** * To use common selectable widget logic in a widget unaware fashion. */ private final class SelectableTable implements Selectable { /** {@inheritDoc} */ public void addSelectionListener(SelectionListener listener) { table.addSelectionListener(listener); } /** {@inheritDoc} */ public void removeSelectionListener(SelectionListener listener) { table.removeSelectionListener(listener); } /** {@inheritDoc} */ public int getSelectionIndex() { return table.getSelectionIndex(); } /** {@inheritDoc} */ public int[] getSelectionIndices() { return table.getSelectionIndices(); } /** {@inheritDoc} */ public int getStyle() { return table.getStyle(); } /** {@inheritDoc} */ public void select(int index) { table.select(index); } /** {@inheritDoc} */ public void deselect(int index) { table.deselect(index); } } /** * Defines how Tables will be manipulated. */ private interface TableHandler { /** * Populate the Table with data. */ public void populateTable(); /** * Add a row with the given value. */ public void addRow(int row, E value); /** * Update a row with the given value. */ public void updateRow(int row, E value); /** * Removes a set of rows in a single call */ public void removeAll(int[] rows); /** * Disposes of this TableHandler */ public void dispose(); /** * Gets the last real, non-virtual row index. -1 means empty or * completely virtual table */ public int getLastIndex(); /** * redraws the table */ public void redraw(); } /** * Allows manipulation of standard SWT Tables. */ private final class DefaultTableHandler implements TableHandler { /** * Populate the Table with initial data. */ public void populateTable() { for(int i = 0, n = source.size(); i < n; i++) { addRow(i, source.get(i)); } } /** * Adds a row with the given value. */ public void addRow(int row, E value) { TableItem item = new TableItem(table, 0, row); renderTableItem(item, value, row); } /** * Updates a row with the given value. */ public void updateRow(int row, E value) { TableItem item = table.getItem(row); renderTableItem(item, value, row); } /** * Removes a set of rows in a single call */ public void removeAll(int[] rows) { table.remove(rows); } /** * Disposes of this TableHandler. */ public void dispose() { // no-op for default Tables } /** {@inheritedDoc} */ public int getLastIndex() { return table.getItemCount() - 1; } /** {@inheritedDoc} */ public void redraw() { for (int i=0; i, Listener { /** to keep track of what's been requested */ private final Barcode requested = new Barcode(); /** * Create a new VirtualTableHandler. */ public VirtualTableHandler() { requested.addWhite(0, source.size()); table.addListener(SWT.SetData, this); } /** * Populate the Table with initial data. */ public void populateTable() { table.setItemCount(source.size()); } /** * Adds a row with the given value. */ public void addRow(int row, E value) { // Adding before the last non-Virtual value if(row <= getLastIndex()) { requested.addBlack(row, 1); TableItem item = new TableItem(table, 0, row); renderTableItem(item, value, row); // Adding in the Virtual values at the end } else { requested.addWhite(requested.size(), 1); table.setItemCount(table.getItemCount() + 1); } } /** * Updates a row with the given value. */ public void updateRow(int row, E value) { // Only set a row if it is NOT Virtual if(!isVirtual(row)) { requested.setBlack(row, 1); TableItem item = table.getItem(row); renderTableItem(item, value, row); } } /** * Removes a set of rows in a single call */ public void removeAll(int[] rows) { // Sync the requested barcode to clear values that have been removed for(int i = 0; i < rows.length; i++) { requested.remove(rows[i] - i, 1); } table.remove(rows); } /** * Returns the highest index that has been requested or -1 if the * Table is entirely Virtual. */ public int getLastIndex() { // Everything is Virtual if(requested.blackSize() == 0) return -1; // Return the last index else return requested.getIndex(requested.blackSize() - 1, Barcode.BLACK); } /** * Returns whether a particular row is Virtual in the Table. */ private boolean isVirtual(int rowIndex) { return requested.getBlackIndex(rowIndex) == -1; } /** * Respond to requests for values to fill Virtual rows. */ public void handleEvent(Event e) { // Get the TableItem from the Table TableItem item = (TableItem)e.item; // Calculate the index that should be requested because the Table // might be sending incorrectly indexed TableItems in the event. int whiteIndex = requested.getWhiteIndex(table.indexOf(item), false); int index = requested.getIndex(whiteIndex, Barcode.WHITE); // System.out.println("ETV.handleEvent: e.index|index|topindex|lastindex=" + e.index + "|" // + index + "|" + table.getTopIndex() + "|" + getLastIndex()); // Set the value on the Virtual element requested.setBlack(index, 1); renderTableItem(item, source.get(index), index); } /** * Allows this handler to clean up after itself. */ public void dispose() { table.removeListener(SWT.SetData, this); } /** {@inheritedDoc} */ public void redraw() { this.requested.clear(); table.setItemCount(0); requested.addWhite(0, source.size()); table.setItemCount(source.size()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy