package org.marketcetera.photon.commons.ui.table;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.Comparator;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.marketcetera.util.misc.ClassVersion;
/* $License$ */
/**
* Utility class that provides configurable common functionality for a JFace {@link TableViewer}.
* <p>
* Features:
* <ul>
* <li>Sortable Columns</li>
* <li>Bean aware content and label providers</li>
* <ul>
*
* @see TableConfiguration
* @see ColumnConfiguration
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: TableSupport.java 16154 2012-07-14 16:34:05Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: TableSupport.java 16154 2012-07-14 16:34:05Z colin $")
public class TableSupport {
/**
* Creates a {@link TableSupport} with the provided configuration.
*
* @param configuration
* table configuration
* @return the TableSupport
*/
public static TableSupport create(TableConfiguration configuration) {
return new TableSupport(configuration);
}
private final TableConfiguration mConfiguration;
private boolean mControlCreated;
private TableViewer mTableViewer;
private TableSupport(TableConfiguration configuration) {
mConfiguration = configuration;
}
/**
* Creates the table on the provided parent composite.
*
* @param parent
* the parent widget
*/
public void createTable(Composite parent) {
Composite container = new Composite(parent, SWT.NONE);
TableColumnLayout layout = new TableColumnLayout();
container.setLayout(layout);
final Table table = new Table(container, mConfiguration.getTableStyle());
mTableViewer = new TableViewer(table);
final ColumnComparator comparator = new ColumnComparator();
SelectionListener listener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// determine new sort column and direction
TableColumn sortColumn = table.getSortColumn();
TableColumn currentColumn = (TableColumn) e.widget;
final int index = table.indexOf(currentColumn);
int dir = table.getSortDirection();
if (sortColumn == currentColumn) {
dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
} else {
table.setSortColumn(currentColumn);
dir = SWT.UP;
}
table.setSortDirection(dir);
comparator.setSort(dir == SWT.UP ? 1 : -1);
comparator.setIndex(index);
mTableViewer.refresh();
}
};
ColumnConfiguration[] descriptors = mConfiguration.getColumns();
String[] beanProperties = new String[descriptors.length];
for (int i = 0; i < descriptors.length; i++) {
ColumnConfiguration descriptor = descriptors[i];
TableColumn c = new TableColumn(table, descriptor.getStyle());
if (mConfiguration.isHeaderVisible() && descriptor.getHeading() != null) {
c.setText(descriptor.getHeading());
}
c.setResizable(descriptor.isResizable());
c.setMoveable(descriptor.isMovable());
if (descriptor.isSortable()) {
c.addSelectionListener(listener);
}
layout.setColumnData(c, descriptor.getLayoutData());
beanProperties[i] = descriptor.getBeanProperty();
new TableViewerColumn(mTableViewer, c);
}
mTableViewer.getTable().setHeaderVisible(mConfiguration.isHeaderVisible());
// if no class was provided, don't set up comparator, content, or label providers, assume
// the users of this class will handle manually
if (mConfiguration.getItemClass() != null) {
mTableViewer.setComparator(comparator);
ObservableListContentProvider contentProvider = new ObservableListContentProvider();
mTableViewer.setContentProvider(contentProvider);
final IObservableMap[] columnMaps;
if (mConfiguration.isDynamicColumns()) {
columnMaps = BeansObservables.observeMaps(contentProvider.getKnownElements(),
mConfiguration.getItemClass(), beanProperties);
} else {
columnMaps = PojoObservables.observeMaps(contentProvider.getKnownElements(),
mConfiguration.getItemClass(), beanProperties);
}
mTableViewer.setLabelProvider(new ObservableMapLabelProvider(columnMaps));
}
mControlCreated = true;
}
public void setInput(IObservableList input) {
mTableViewer.setInput(input);
}
/**
* Returns the {@link TableViewer} managed by this object.
*
* @return the TableViewer managed by this object
* @throws IllegalStateException
* if createTable has not been called
*/
public TableViewer getTableViewer() {
if (!mControlCreated) throw new IllegalStateException();
return mTableViewer;
}
/**
* Focuses on the table managed by this object.
*
* @throws IllegalStateException
* if createTable has not been called
*/
public void setFocus() {
if (!mControlCreated) throw new IllegalStateException();
mTableViewer.getTable().setFocus();
}
/**
* Handles column sorting.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: TableSupport.java 16154 2012-07-14 16:34:05Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: TableSupport.java 16154 2012-07-14 16:34:05Z colin $")
private class ColumnComparator extends ViewerComparator {
private int mIndex = -1;
private int mDirection = 0;
/**
* Sets the sort column index.
*
* @param index
* index of sort column
*/
void setIndex(int index) {
mIndex = index;
}
/**
* Sets the sort direction.
*
* @param direction
* sort direction, 1 for ascending and -1 for descending
*/
void setSort(int direction) {
mDirection = direction;
}
@SuppressWarnings("unchecked")
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
if (mIndex == -1) return 0;
ColumnConfiguration descriptor = mConfiguration.getColumns()[mIndex];
Object item1 = getColumnObject(e1);
Object item2 = getColumnObject(e2);
int compare = 0;
if (item1 == null && item2 == null) {
return 0;
} else if (item1 == null) {
compare = -1;
} else if (item2 == null) {
compare = 1;
} else {
Comparator comparator = descriptor.getComparator();
if (comparator != null) {
compare = comparator.compare(item1, item2);
} else if (item1 instanceof Comparable) {
compare = ((Comparable) item1).compareTo(item2);
} else {
compare = item1.toString().compareTo(item2.toString());
}
}
return mDirection * compare;
}
private Object getColumnObject(Object row) {
PropertyDescriptor property = getPropertyDescriptor(mConfiguration.getItemClass(),
mConfiguration.getColumns()[mIndex].getBeanProperty());
try {
return property.getReadMethod().invoke(row);
} catch (Exception e) {
throw new IllegalArgumentException(row.getClass().getName(), e);
}
}
private PropertyDescriptor getPropertyDescriptor(Class<?> beanClass, String propertyName) {
BeanInfo beanInfo;
try {
beanInfo = Introspector.getBeanInfo(beanClass);
} catch (IntrospectionException e) {
// cannot introspect, give up
throw new IllegalArgumentException(beanClass.getName(), e);
}
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; i++) {
PropertyDescriptor descriptor = propertyDescriptors[i];
if (descriptor.getName().equals(propertyName)) {
return descriptor;
}
}
throw new IllegalArgumentException(
"Could not find property with name " + propertyName + " in class " + beanClass); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}