ca.odell.glazedlists.swt.TableCheckFilterList Maven / Gradle / Ivy
/* Glazed Lists (c) 2003-2006 */
/* O'Dell Engineering Ltd.*/
/* http://publicobject.com/glazedlists/ publicobject.com,*/
package ca.odell.glazedlists.swt;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.gui.CheckableTableFormat;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.matchers.AbstractMatcherEditor;
import ca.odell.glazedlists.matchers.Matcher;
import ca.odell.glazedlists.matchers.Matchers;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
/**
* A FilterList for elements that are checked in the EventTableViewer.
*
* The TableCheckFilterList must be used as the source list to
* the EventTableViewer. This is because the TableCheckFilterList uses methods on
* the Table that depend on table indices.
*
*
This class is not thread safe. It must be used exclusively with the SWT
* event handler thread.
*
* @author Jesse Wilson
*/
final class TableCheckFilterList extends TransformedList implements SelectionListener {
/** filter out unchecked elements */
private final CheckMatcherEditor checkMatcherEditor = new CheckMatcherEditor();
/** the table that checkboxes are displayed in */
private final Table table;
/** for checking table items */
private final CheckableTableFormat checkableTableFormat;
/** Retain a reference to the CheckableWrapperList, if we create one, so it can be disposed */
private CheckableWrapperList checkableWrapperList;
/** Retain a reference to the FilterList so it can be disposed */
private FilterList filterList;
/** Retain a reference to the TableChecker, if we create one, so it can be disposed */
private TableChecker tableChecker;
/**
* Creates a new filter that filters elements depending on whether they are
* checked in the table.
*
* @param source the items to decorate with checkability
* @param table the checkable table
* @param tableFormat if this class implements {@link CheckableTableFormat}
* it will be used to store check state. Otherwise check state will be
* stored transiently within this class' state.
*/
public TableCheckFilterList(EventList source, Table table, TableFormat tableFormat) {
this(table, tableFormat, tableFormat instanceof CheckableTableFormat ? source : new CheckableWrapperList(source));
}
/**
* A hidden constructor that allows us to store a reference to the source of
* the FilterList, which we may have created and must dispose later.
*
* @param table the checkable table
* @param tableFormat the format of the checkable table
* @param filterListSource the source of the FilterList that backs this TableCheckFilterList
*/
private TableCheckFilterList(Table table, TableFormat tableFormat, EventList filterListSource) {
this(new FilterList(filterListSource, Matchers.trueMatcher()), table, tableFormat);
// if a CheckableWrapperList was created, store a reference so it can be disposed later
if (filterListSource instanceof CheckableWrapperList)
checkableWrapperList = (CheckableWrapperList) filterListSource;
}
/**
* A hidden constructor that allows us to store a reference to the
* FilterList, which we created and must dispose later.
*
* @param filterList the FilterList that backs this TableCheckFilterList
* @param table the checkable table
* @param tableFormat the format of the checkable table
*/
private TableCheckFilterList(FilterList filterList, Table table, TableFormat tableFormat) {
super(filterList);
this.filterList = filterList;
this.table = table;
this.checkableTableFormat = tableFormat instanceof CheckableTableFormat ? (CheckableTableFormat) tableFormat : null;
// listen for changes in checkedness
table.addSelectionListener(this);
// prepare the filter
filterList.setMatcherEditor(checkMatcherEditor);
// handle changes
filterList.addListEventListener(this);
}
/**
* @return false; TableCheckFilterList is readonly
*/
@Override
protected boolean isWritable() {
return false;
}
/**
* Set the specified list element in the source list as checked.
*/
private void setChecked(Object element, boolean checked) {
if(checkableTableFormat != null) {
checkableTableFormat.setChecked(element, checked);
} else {
((CheckWrapped)element).setChecked(checked);
}
}
/**
* Set the specified index in the filtered list as checked.
*/
private void setChecked(int index, boolean checked) {
setChecked(source.get(getSourceIndex(index)), checked);
}
/**
* Get whether the specified element in the source list is checked.
*/
private boolean getChecked(Object element) {
if(checkableTableFormat != null) {
return checkableTableFormat.getChecked(element);
} else {
return ((CheckWrapped)element).getChecked();
}
}
/**
* Get whether the specified index in the filtered list is checked.
*/
private boolean getChecked(int index) {
return getChecked(source.get(getSourceIndex(index)));
}
/**
* Match checked elements.
*/
private class CheckMatcherEditor extends AbstractMatcherEditor {
private boolean checkedOnly = false;
private void setCheckedOnly(boolean checkedOnly) {
if(checkedOnly == this.checkedOnly) return;
if(checkedOnly) fireConstrained(new CheckMatcher());
else fireMatchAll();
}
private boolean getCheckedOnly() {
return checkedOnly;
}
private class CheckMatcher implements Matcher {
public boolean matches(V element) {
return getChecked(element);
}
}
}
/**
* Gets a static snapshot of the checked Objects in this list.
*/
public List getAllChecked() {
final List result = new ArrayList();
for(int i = 0, n = size(); i < n; i++) {
if(getChecked(i)) {
result.add(get(i));
}
}
return result;
}
/**
* Set whether this filter list displays all elements, or only checked elements.
*/
public void setCheckedOnly(boolean checkedOnly) {
checkMatcherEditor.setCheckedOnly(checkedOnly);
}
/**
* Get whether this filter list displays all elements, or only checked elements.
*/
public boolean getCheckedOnly() {
return checkMatcherEditor.getCheckedOnly();
}
/**
* Returns the element at the specified position in this list. This unwraps
* a {@link CheckWrapped} object from the source if necessary.
*/
@Override
public E get(int index) {
if(checkableTableFormat != null) {
return super.get(index);
} else {
CheckWrapped checkWrapped = (CheckWrapped)super.get(index);
return checkWrapped.getWrapped();
}
}
/** {@inheritDoc} */
@Override
public void listChanged(ListEvent listChanges) {
updates.forwardEvent(listChanges);
}
/**
* Sent when selection occurs in the control.
*/
public void widgetSelected(SelectionEvent e) {
if(e.detail == SWT.CHECK) {
getReadWriteLock().writeLock().lock();
try {
updateItemChecked((TableItem)e.item);
} finally {
getReadWriteLock().writeLock().unlock();
}
}
}
public void widgetDefaultSelected(SelectionEvent e) {
if(e.detail == SWT.CHECK) {
getReadWriteLock().writeLock().lock();
try {
updateItemChecked((TableItem)e.item);
} finally {
getReadWriteLock().writeLock().unlock();
}
}
}
/**
* When the check status of a table item is changed, this changes the
* checked property of the corresponding CheckWrapped object.
*
* This uses a very lame hack to get the index of the specified TableItem.
* It first uses the Table.indexOf(TableItem) method, and then attempts to get
* the unfiltered index of that. This is only guaranteed to work in the case
* where the TableCheckFilterList is the source of the EventTableViewer.
* Otherwise everything may blow up because the list indices cannot be looked up.
*/
private void updateItemChecked(TableItem updated) {
if(updated == null) return;
// set the checked property on the proper element
int index = table.indexOf(updated);
boolean checked = updated.getChecked();
setChecked(index, checked);
// force an update event
int sourceIndex = getSourceIndex(index);
source.set(sourceIndex, source.get(sourceIndex));
}
@Override
public void addListEventListener(ListEventListener listChangeListener) {
super.addListEventListener(listChangeListener);
// also adjust the table's checked rows
if(listChangeListener instanceof DefaultEventTableViewer) {
tableChecker = new TableChecker();
super.addListEventListener(tableChecker);
}
}
/** {@inheritDoc} */
@Override
public void dispose() {
if (checkableWrapperList != null)
checkableWrapperList.dispose();
if (tableChecker != null)
removeListEventListener(tableChecker);
filterList.dispose();
super.dispose();
}
/**
* The TableChecker checks the table rows after they have been updated.
*/
private class TableChecker implements ListEventListener {
public TableChecker() {
for(int i = 0; i < size(); i++) {
boolean checked = getChecked(i);
table.getItem(i).setChecked(checked);
}
}
public void listChanged(ListEvent listChanges) {
while(listChanges.next()) {
int changeIndex = listChanges.getIndex();
int changeType = listChanges.getType();
if(changeType == ListEvent.INSERT || changeType == ListEvent.UPDATE) {
boolean checked = getChecked(changeIndex);
table.getItem(changeIndex).setChecked(checked);
}
}
}
}
}
/**
* A simple wrapper list that makes all elements Checkable.
*
* This maintains a separate collection that mirrors the source collection.
* This collection contains only CheckWrapped objects. For each element in the
* source list at index i, that element is wrapped in the mirror list at index i.
*
*
The mirror collection is maintained in the listChanged() method only.
* All get() calls return from the mirror collection rather than from the source
* list.
*/
class CheckableWrapperList extends TransformedList> {
/** wrapped list contains CheckWrapped elements only */
private List> wrappedSource = new ArrayList>();
public CheckableWrapperList(EventList source) {
super(source);
prepareElements();
source.addListEventListener(this);
}
/**
* @return false; CheckableWrapperList is readonly
*/
@Override
protected boolean isWritable() {
return false;
}
/**
* The CheckableWrapperList supports only one write operation, which is to
* force an update on a specified value. This requires that the parameter
* value is an instance of CheckWrapped, which is the only value that this
* list supports.
*/
@Override
public CheckWrapped set(int index, CheckWrapped value) {
source.set(index, value.getWrapped());
return value;
}
private void prepareElements() {
for(int i = 0, n = source.size(); i < n; i++) {
wrappedSource.add(i, new CheckWrapped(source.get(i)));
}
}
@Override
public CheckWrapped get(int index) {
return wrappedSource.get(index);
}
@Override
public void listChanged(ListEvent listChanges) {
updates.beginEvent();
while (listChanges.next()) {
final int changeIndex = listChanges.getIndex();
final int changeType = listChanges.getType();
switch (changeType) {
case ListEvent.INSERT: wrappedSource.add(changeIndex, new CheckWrapped(source.get(changeIndex))); break;
case ListEvent.UPDATE: wrappedSource.get(changeIndex).setWrapped(source.get(changeIndex)); break;
case ListEvent.DELETE: wrappedSource.remove(changeIndex); break;
}
updates.addChange(changeType, changeIndex);
}
updates.commitEvent();
}
}
/**
* A simple wrapper that adds a checked property to an Object.
*/
class CheckWrapped {
private boolean checked = false;
private E wrapped = null;
public CheckWrapped(E wrapped) {
this.wrapped = wrapped;
}
public E getWrapped() { return wrapped; }
public void setWrapped(E wrapped) { this.wrapped = wrapped; }
public boolean getChecked() { return checked; }
public void setChecked(boolean checked) { this.checked = checked; }
@Override
public String toString() {
if(checked) return "[*] " + wrapped;
else return "[ ] " + wrapped;
}
}