package org.openlca.app.viewers.table;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.forms.widgets.Section;
import org.openlca.app.components.ModelTransfer;
import org.openlca.app.util.Actions;
import org.openlca.app.util.tables.TableClipboard;
import org.openlca.app.util.tables.Tables;
import org.openlca.app.util.viewers.Viewers;
import org.openlca.app.viewers.AbstractViewer;
import org.openlca.app.viewers.table.modify.ModifySupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract implementation of AbstractViewer for SWT table viewer.
*
* There are three extensions that can be implemented by annotating the methods
* of implementing classes. To enable creation and removal actions use
* annotations {@link OnAdd} and {@link OnRemove}. The run methods of each
* action will call all annotated methods. Implementations are responsible to
* update the input. To enable drop feature use {@link OnDrop} and specify the
* type of accepted elements by the input parameter of the annotated method.
*/
public class AbstractTableViewer<T> extends AbstractViewer<T, TableViewer> {
private Logger log = LoggerFactory.getLogger(getClass());
private List<Action> actions;
private ModifySupport<T> cellModifySupport;
protected AbstractTableViewer(Composite parent) {
super(parent);
}
@Override
protected TableViewer createViewer(Composite parent) {
TableViewer viewer = Tables.createViewer(parent, getColumnHeaders(), getLabelProvider());
createActions(viewer);
if (supports(OnDrop.class))
addDropSupport(viewer);
if (useColumnHeaders())
cellModifySupport = new ModifySupport<>(viewer);
return viewer;
}
private void createActions(TableViewer viewer) {
actions = new ArrayList<>();
if (supports(OnAdd.class))
actions.add(Actions.onAdd(() -> call(OnAdd.class)));
if (supports(OnRemove.class)) {
actions.add(Actions.onRemove(() -> call(OnRemove.class)));
Tables.onDeletePressed(viewer, (e) -> call(OnRemove.class));
}
// we have to create this array, because we do not want to have the copy
// action in the section menu
List<Action> additionalActions = getAdditionalActions();
Action[] tableActions = new Action[actions.size() + additionalActions.size() + 1];
for (int i = 0; i < actions.size(); i++)
tableActions[i] = actions.get(i);
for (int i = 0; i < additionalActions.size(); i++)
tableActions[i + actions.size()] = additionalActions.get(i);
tableActions[tableActions.length - 1] = TableClipboard.onCopy(viewer);
Actions.bind(viewer, tableActions);
}
protected List<Action> getAdditionalActions() {
return Collections.emptyList();
}
private void addDropSupport(TableViewer viewer) {
final Transfer transferType = ModelTransfer.getInstance();
DropTarget dropTarget = new DropTarget(viewer.getTable(), DND.DROP_COPY
| DND.DROP_MOVE | DND.DROP_DEFAULT);
dropTarget.setTransfer(new Transfer[] { transferType });
final AbstractTableViewer<T> thisObject = this;
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void drop(DropTargetEvent event) {
if (transferType.isSupportedType(event.currentDataType))
if (event.data != null)
for (Method method : getMethods(OnDrop.class))
tryInvoke(method, event.data);
}
private void tryInvoke(Method method, Object value) {
Class<?> parameterType = method.getParameterTypes().length > 0 ? method
.getParameterTypes()[0]
: null;
Class<?> dataType = value.getClass();
if (dataType.isArray()) {
for (Object object : (Object[]) value)
if (parameterType == object.getClass()) {
try {
boolean accessible = method.isAccessible();
method.setAccessible(true);
method.invoke(thisObject, object);
method.setAccessible(accessible);
} catch (Exception e) {
log.error("Error invoking OnDrop method", e);
}
}
} else {
if (parameterType == dataType)
try {
method.invoke(thisObject, value);
} catch (Exception e) {
log.error("Error invoking OnDrop method", e);
}
}
}
});
}
protected ModifySupport<T> getModifySupport() {
return cellModifySupport;
}
/**
* Subclasses may override this for support of column headers for the table
* combo, if null or empty array is returned, the headers are not visible
* and the combo behaves like a standard combo
*/
protected String[] getColumnHeaders() {
return null;
}
private boolean useColumnHeaders() {
return getColumnHeaders() != null && getColumnHeaders().length > 0;
}
/**
* Binds the create and remove actions of the table viewer to the given
* section.
*/
public void bindTo(Section section) {
Actions.bind(section, actions.toArray(new Action[actions.size()]));
}
@SuppressWarnings("unchecked")
public List<T> getAllSelected() {
List<Object> list = Viewers.getAllSelected(getViewer());
List<T> result = new ArrayList<>();
for (Object value : list)
if (!(value instanceof AbstractViewer.Null))
result.add((T) value);
return result;
}
private boolean supports(Class<? extends Annotation> clazz) {
for (Method method : this.getClass().getDeclaredMethods())
if (method.isAnnotationPresent(clazz))
return true;
return false;
}
private void call(Class<? extends Annotation> clazz) {
for (Method method : getMethods(clazz))
try {
method.setAccessible(true);
method.invoke(this);
} catch (Exception e) {
log.error("Cannot call method for " + clazz.getSimpleName(), e);
}
}
private List<Method> getMethods(Class<? extends Annotation> clazz) {
List<Method> methods = new ArrayList<>();
for (Method method : this.getClass().getDeclaredMethods())
if (method.isAnnotationPresent(clazz))
methods.add(method);
return methods;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnAdd {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnRemove {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnDrop {
}
}