package org.openlca.app.util.trees;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Consumer;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
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.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.openlca.app.components.IModelDropHandler;
import org.openlca.app.components.ModelTransfer;
import org.openlca.app.util.UI;
import org.openlca.app.util.viewers.Sorter;
import org.openlca.core.model.descriptors.BaseDescriptor;
/**
* A helper class for creating trees, tree viewers and related resources.
*/
public class Trees {
public static TreeViewer createViewer(Composite parent, String... headers) {
return createViewer(parent, headers, null);
}
public static TreeViewer createViewer(Composite parent, IBaseLabelProvider label) {
return createViewer(parent, null, label);
}
public static TreeViewer createViewer(Composite parent, String[] headers, IBaseLabelProvider label) {
TreeViewer viewer = new TreeViewer(parent, SWT.BORDER | SWT.FULL_SELECTION | SWT.VIRTUAL | SWT.MULTI);
Tree tree = viewer.getTree();
boolean hasColumns = headers != null && headers.length > 0;
tree.setLinesVisible(hasColumns);
tree.setHeaderVisible(hasColumns);
if (hasColumns) {
createColumns(viewer, headers, label);
}
if (label != null) {
viewer.setLabelProvider(label);
}
GridData data = UI.gridData(tree, true, true);
data.minimumHeight = 150;
return viewer;
}
private static void createColumns(TreeViewer viewer, String[] labels, IBaseLabelProvider labelProvider) {
if (labelProvider instanceof CellLabelProvider) {
ColumnViewerToolTipSupport.enableFor(viewer, ToolTip.NO_RECREATE);
}
for (String label : labels) {
TreeViewerColumn c = new TreeViewerColumn(viewer, SWT.NULL);
c.getColumn().setText(label);
if (labelProvider instanceof CellLabelProvider) {
c.setLabelProvider((CellLabelProvider) labelProvider);
}
}
for (TreeColumn c : viewer.getTree().getColumns()) {
c.pack();
}
}
public static void addDropSupport(TreeViewer tree,
final IModelDropHandler handler) {
final Transfer transfer = ModelTransfer.getInstance();
DropTarget dropTarget = new DropTarget(tree.getTree(), DND.DROP_COPY
| DND.DROP_MOVE | DND.DROP_DEFAULT);
dropTarget.setTransfer(new Transfer[] { transfer });
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void drop(DropTargetEvent event) {
if (!transfer.isSupportedType(event.currentDataType))
return;
List<BaseDescriptor> list = ModelTransfer
.getBaseDescriptors(event.data);
handler.handleDrop(list);
}
});
}
/**
* Binds the given percentage values (values between 0 and 1) to the column
* widths of the given tree
*/
public static void bindColumnWidths(final Tree tree, final double... percents) {
bindColumnWidths(tree, 0, percents);
}
public static void bindColumnWidths(final Tree tree, int minimum, final double... percents) {
if (tree == null || percents == null)
return;
TreeResizeListener treeListener = new TreeResizeListener(tree, minimum, percents);
ColumnResizeListener columnListener = new ColumnResizeListener(treeListener);
for (TreeColumn column : tree.getColumns())
column.addControlListener(columnListener);
tree.addControlListener(treeListener);
}
/** Add an event handler for double clicks on the given tree viewer. */
public static void onDoubleClick(TreeViewer viewer,
Consumer<MouseEvent> handler) {
if (viewer == null || viewer.getTree() == null || handler == null)
return;
viewer.getTree().addMouseListener(new MouseAdapter() {
@Override
public void mouseDoubleClick(MouseEvent e) {
handler.accept(e);
}
});
}
/**
* Get the tree item where the given event occurred. Returns null if the
* event occurred in the empty tree area.
*/
public static TreeItem getItem(TreeViewer viewer, MouseEvent event) {
if (viewer == null || event == null)
return null;
Tree tree = viewer.getTree();
if (tree == null)
return null;
return tree.getItem(new Point(event.x, event.y));
}
public static void onDeletePressed(TreeViewer viewer,
Consumer<Event> handler) {
if (viewer == null || viewer.getTree() == null || handler == null)
return;
viewer.getTree().addListener(SWT.KeyUp, (event) -> {
if (event.keyCode == SWT.DEL) {
handler.accept(event);
}
});
}
public static void addSorter(TreeViewer viewer, Sorter<?> sorter) {
Tree tree = viewer.getTree();
if (sorter.column >= tree.getColumnCount())
return;
TreeColumn column = tree.getColumn(sorter.column);
column.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
TreeColumn current = tree.getSortColumn();
if (column == current)
sorter.ascending = !sorter.ascending;
else
sorter.ascending = true;
int direction = sorter.ascending ? SWT.UP : SWT.DOWN;
tree.setSortDirection(direction);
tree.setSortColumn(column);
viewer.setSorter(sorter);
viewer.refresh();
}
});
}
// In order to be able to resize columns manually, we must know if a column
// was resized before, and in those cases, don't resize the columns
// automatically.
private static class ColumnResizeListener extends ControlAdapter {
private TreeResizeListener depending;
private boolean enabled = true;
private boolean initialized;
private ColumnResizeListener(TreeResizeListener depending) {
this.depending = depending;
}
@Override
public void controlResized(ControlEvent e) {
if (!enabled)
return;
if (!initialized) {
initialized = true;
return;
}
depending.enabled = false;
enabled = false;
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
depending.enabled = true;
enabled = true;
}
}, 100);
}
}
private static class TreeResizeListener extends ControlAdapter {
private Tree tree;
private double[] percents;
private int minimum;
private boolean enabled = true;
private boolean initialized;
private TreeResizeListener(Tree tree, int minimum, double[] percents) {
this.tree = tree;
this.minimum = minimum;
this.percents = percents;
}
@Override
public void controlResized(ControlEvent e) {
if (!enabled && initialized)
return;
double width = tree.getSize().x - 25;
if (width <= 0)
return;
TreeColumn[] columns = tree.getColumns();
int indexOfLargest = -1;
double max = 0;
double diff = 0;
for (int i = 0; i < columns.length; i++) {
if (i >= percents.length)
break;
double colWidth = percents[i] * width;
if (max < colWidth) {
max = colWidth;
indexOfLargest = i;
}
if (colWidth < minimum) {
colWidth = minimum;
diff += minimum - colWidth;
}
columns[i].setWidth((int) colWidth);
}
if (diff > 0) {
columns[indexOfLargest].setWidth((int) (max - diff));
}
initialized = true;
}
}
}