package rocks.inspectit.ui.rcp.editor.tree;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.lang.ArrayUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.forms.widgets.FormToolkit;
import rocks.inspectit.ui.rcp.editor.AbstractSubView;
import rocks.inspectit.ui.rcp.editor.ISubView;
import rocks.inspectit.ui.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent;
import rocks.inspectit.ui.rcp.editor.preferences.PreferenceId;
import rocks.inspectit.ui.rcp.editor.root.FormRootEditor;
import rocks.inspectit.ui.rcp.editor.root.SubViewClassificationController.SubViewClassification;
import rocks.inspectit.ui.rcp.editor.search.ISearchExecutor;
import rocks.inspectit.ui.rcp.editor.search.criteria.SearchCriteria;
import rocks.inspectit.ui.rcp.editor.search.criteria.SearchResult;
import rocks.inspectit.ui.rcp.editor.search.helper.DeferredTreeViewerSearchHelper;
import rocks.inspectit.ui.rcp.editor.tooltip.ColumnAwareToolTipSupport;
import rocks.inspectit.ui.rcp.editor.tooltip.IColumnToolTipProvider;
import rocks.inspectit.ui.rcp.editor.tree.input.TreeInputController;
import rocks.inspectit.ui.rcp.handlers.ShowHideColumnsHandler;
import rocks.inspectit.ui.rcp.menu.ShowHideMenuManager;
import rocks.inspectit.ui.rcp.util.SafeExecutor;
/**
* Sub-view which is used to create a tree.
*
* @author Patrice Bouillet
*
*/
public class TreeSubView extends AbstractSubView implements ISearchExecutor {
/**
* The referenced input controller.
*/
private final TreeInputController treeInputController;
/**
* The created tree viewer.
*/
private TreeViewer treeViewer;
/**
* Defines if a job is currently already executing.
*/
private volatile boolean jobInSchedule = false;
/**
* {@link DeferredTreeViewerSearchHelper}.
*/
private DeferredTreeViewerSearchHelper searchHelper;
/**
* Default constructor which needs a tree input controller to create all the content etc.
*
* @param treeInputController
* The tree input controller.
*/
public TreeSubView(TreeInputController treeInputController) {
Assert.isNotNull(treeInputController);
this.treeInputController = treeInputController;
}
/**
* {@inheritDoc}
*/
@Override
public void init() {
treeInputController.setInputDefinition(getRootEditor().getInputDefinition());
}
/**
* {@inheritDoc}
*/
@Override
public void createPartControl(Composite parent, FormToolkit toolkit) {
final Tree tree = toolkit.createTree(parent, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
tree.setHeaderVisible(true);
treeViewer = new DeferredTreeViewer(tree);
treeInputController.createColumns(treeViewer);
treeViewer.setUseHashlookup(true);
treeViewer.setContentProvider(treeInputController.getContentProvider());
IBaseLabelProvider labelProvider = treeInputController.getLabelProvider();
treeViewer.setLabelProvider(labelProvider);
if (labelProvider instanceof IColumnToolTipProvider) {
ColumnAwareToolTipSupport.enableFor(treeViewer);
}
treeViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
treeInputController.doubleClick(event);
TreeSelection selection = (TreeSelection) event.getSelection();
TreePath path = selection.getPaths()[0];
if (null != path) {
boolean expanded = treeViewer.getExpandedState(path);
if (treeInputController.changeExpandedState(expanded, path)) {
if (expanded) {
treeViewer.collapseToLevel(path, 1);
} else {
treeViewer.expandToLevel(path, 1);
}
}
}
}
});
treeViewer.setComparator(treeInputController.getComparator());
if (null != treeViewer.getComparator()) {
TreeColumn[] treeColumns = treeViewer.getTree().getColumns();
for (TreeColumn treeColumn : treeColumns) {
treeColumn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
treeViewer.refresh();
}
});
}
}
if (ArrayUtils.isNotEmpty(treeInputController.getFilters())) {
treeViewer.setFilters(treeInputController.getFilters());
}
// add show hide columns support
MenuManager headerMenuManager = new ShowHideMenuManager(treeViewer, treeInputController.getClass());
headerMenuManager.setRemoveAllWhenShown(false);
// normal selection menu manager
MenuManager selectionMenuManager = new MenuManager();
selectionMenuManager.setRemoveAllWhenShown(true);
getRootEditor().getSite().registerContextMenu(FormRootEditor.ID + ".treesubview", selectionMenuManager, treeViewer);
final Menu selectionMenu = selectionMenuManager.createContextMenu(tree);
final Menu headerMenu = headerMenuManager.createContextMenu(tree);
tree.addListener(SWT.MenuDetect, new Listener() {
@Override
public void handleEvent(Event event) {
Point pt = Display.getDefault().map(null, tree, new Point(event.x, event.y));
Rectangle clientArea = tree.getClientArea();
boolean header = (clientArea.y <= pt.y) && (pt.y < (clientArea.y + tree.getHeaderHeight()));
if (header) {
tree.setMenu(headerMenu);
} else {
tree.setMenu(selectionMenu);
}
}
});
/**
* IMPORTANT: Only the menu set in the setMenu() will be disposed automatically.
*/
tree.addListener(SWT.Dispose, new Listener() {
@Override
public void handleEvent(Event event) {
if (!headerMenu.isDisposed()) {
headerMenu.dispose();
}
if (!selectionMenu.isDisposed()) {
selectionMenu.dispose();
}
}
});
Object input = treeInputController.getTreeInput();
treeViewer.setInput(input);
ControlAdapter columnResizeListener = new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
if (e.widget instanceof TreeColumn) {
TreeColumn column = (TreeColumn) e.widget;
if (column.getWidth() > 0) {
ShowHideColumnsHandler.registerNewColumnWidth(treeInputController.getClass(), column.getText(), column.getWidth());
}
}
}
@Override
public void controlMoved(ControlEvent e) {
ShowHideColumnsHandler.setColumnOrder(treeInputController.getClass(), treeViewer.getTree().getColumnOrder());
}
};
for (TreeColumn column : tree.getColumns()) {
if (treeInputController.canAlterColumnWidth(column)) {
Integer rememberedWidth = ShowHideColumnsHandler.getRememberedColumnWidth(treeInputController.getClass(), column.getText());
boolean isColumnHidden = ShowHideColumnsHandler.isColumnHidden(treeInputController.getClass(), column.getText());
if ((rememberedWidth != null) && !isColumnHidden) {
column.setWidth(rememberedWidth.intValue());
column.setResizable(true);
} else if (isColumnHidden) {
column.setWidth(0);
column.setResizable(false);
}
}
column.addControlListener(columnResizeListener);
}
// update the order of columns if the order was defined for the class, and no new columns
// were added
int[] columnOrder = ShowHideColumnsHandler.getColumnOrder(treeInputController.getClass());
if ((null != columnOrder) && (columnOrder.length == tree.getColumns().length)) {
tree.setColumnOrder(columnOrder);
} else if (null != columnOrder) {
// if the order exists, but length is not same, then update with the default order
ShowHideColumnsHandler.setColumnOrder(treeInputController.getClass(), tree.getColumnOrder());
}
// create search helper
searchHelper = new DeferredTreeViewerSearchHelper((DeferredTreeViewer) treeViewer, treeInputController, getRootEditor().getInputDefinition().getRepositoryDefinition());
}
/**
* {@inheritDoc}
*/
@Override
public void doRefresh() {
if (!jobInSchedule) {
jobInSchedule = true;
Job job = new Job(getDataLoadingJobName()) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
treeInputController.doRefresh(monitor, getRootEditor());
SafeExecutor.asyncExec(new Runnable() {
@Override
public void run() {
if (checkDisposed()) {
return;
}
// refresh should only influence the master sub views
if (treeInputController.getSubViewClassification() == SubViewClassification.MASTER) {
Object input = treeInputController.getTreeInput();
treeViewer.setInput(input);
if (treeViewer.getTree().isVisible()) {
treeViewer.refresh();
treeViewer.expandToLevel(treeInputController.getExpandLevel());
}
}
}
}, treeViewer.getTree());
} catch (Throwable throwable) { // NOPMD
throw new RuntimeException("Unknown exception occurred trying to refresh the view.", throwable);
} finally {
jobInSchedule = false;
}
return Status.OK_STATUS;
}
};
job.schedule();
}
}
/**
* {@inheritDoc}
*/
@Override
public void setDataInput(List<? extends Object> data) {
if (checkDisposed()) {
return;
}
if (treeInputController.canOpenInput(data)) {
treeViewer.setInput(data);
treeViewer.expandToLevel(treeInputController.getExpandLevel());
// i will comment this out because tree viewer is not refreshing if it is not visible,
// meaning when i clear buffer only tree views that are visible are cleared.
// if (treeViewer.getControl().isVisible()) {
treeViewer.refresh();
// }
}
}
/**
* {@inheritDoc}
*/
@Override
public Control getControl() {
return treeViewer.getControl();
}
/**
* {@inheritDoc}
*/
@Override
public ISelectionProvider getSelectionProvider() {
return treeViewer;
}
/**
* {@inheritDoc}
*/
@Override
public Set<PreferenceId> getPreferenceIds() {
return treeInputController.getPreferenceIds();
}
/**
* {@inheritDoc}
*/
@Override
public void preferenceEventFired(PreferenceEvent preferenceEvent) {
if (checkDisposed()) {
return;
}
treeInputController.preferenceEventFired(preferenceEvent);
switch (preferenceEvent.getPreferenceId()) {
case FILTERDATATYPE:
case INVOCFILTEREXCLUSIVETIME:
case INVOCFILTERTOTALTIME:
// we have to re-apply the filter if there is one
if (ArrayUtils.isNotEmpty(treeInputController.getFilters())) {
treeViewer.setFilters(treeInputController.getFilters());
}
break;
case CLEAR_BUFFER:
if (treeInputController.getPreferenceIds().contains(PreferenceId.CLEAR_BUFFER)) {
treeViewer.refresh();
treeViewer.expandToLevel(treeInputController.getExpandLevel());
}
break;
case TIME_RESOLUTION:
if (treeInputController.getPreferenceIds().contains(PreferenceId.TIME_RESOLUTION)) {
treeViewer.refresh();
treeViewer.expandToLevel(treeInputController.getExpandLevel());
}
break;
case INVOCATION_SUBVIEW_MODE:
if (treeInputController.getPreferenceIds().contains(PreferenceId.INVOCATION_SUBVIEW_MODE)) {
treeViewer.refresh();
treeViewer.expandToLevel(treeInputController.getExpandLevel());
}
break;
default:
break;
}
}
/**
* Returns the tree viewer.
*
* @return The tree viewer.
*/
public TreeViewer getTreeViewer() {
return treeViewer;
}
/**
* Returns the tree input controller.
*
* @return The tree input controller.
*/
public TreeInputController getTreeInputController() {
return treeInputController;
}
/**
* Return the names of all columns in the tree. Not visible columns names will also be included.
* The order of the names will be same to the initial tree column order, thus not reflecting the
* current state of the table if the columns were moved.
*
* @return List of column names.
*/
public List<String> getColumnNames() {
List<String> names = new ArrayList<>();
for (TreeColumn column : treeViewer.getTree().getColumns()) {
names.add(column.getText());
}
return names;
}
/**
*
* @return The list of integers representing the column order in the tree. Note that only
* columns that are currently visible will be included in the list.
* @see org.eclipse.swt.widgets.Table#getColumnOrder()
*/
public List<Integer> getColumnOrder() {
int[] order = treeViewer.getTree().getColumnOrder();
List<Integer> orderWithoutHidden = new ArrayList<>();
for (int index : order) {
if (treeViewer.getTree().getColumns()[index].getWidth() > 0) {
orderWithoutHidden.add(index);
}
}
return orderWithoutHidden;
}
/**
* {@inheritDoc}
*/
@Override
public ISubView getSubViewWithInputController(Class<?> inputControllerClass) {
if (Objects.equals(inputControllerClass, treeInputController.getClass())) {
return this;
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public SearchResult executeSearch(SearchCriteria searchCriteria) {
return searchHelper.executeSearch(searchCriteria);
}
/**
* {@inheritDoc}
*/
@Override
public SearchResult next() {
return searchHelper.next();
}
/**
* {@inheritDoc}
*/
@Override
public SearchResult previous() {
return searchHelper.previous();
}
/**
* {@inheritDoc}
*/
@Override
public void clearSearch() {
searchHelper.clearSearch();
}
/**
* Returns true if the tree in the sub-view is disposed. False otherwise.
*
* @return Returns true if the tree in the sub-view is disposed. False otherwise.
*/
private boolean checkDisposed() {
return treeViewer.getTree().isDisposed();
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
treeInputController.dispose();
}
}