/*******************************************************************************
* Copyright (c) 2012, 2014 Ericsson, others
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Patrick Tasse - Initial API and implementation
* François Rajotte - Filter implementation
* Geneviève Bastien - Add event links between entries
*******************************************************************************/
package fr.inria.linuxtools.tmf.ui.widgets.timegraph;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import fr.inria.linuxtools.internal.tmf.ui.Activator;
import fr.inria.linuxtools.internal.tmf.ui.ITmfImageConstants;
import fr.inria.linuxtools.internal.tmf.ui.Messages;
import fr.inria.linuxtools.tmf.ui.widgets.timegraph.dialogs.TimeGraphFilterDialog;
import fr.inria.linuxtools.tmf.ui.widgets.timegraph.model.ILinkEvent;
import fr.inria.linuxtools.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
/**
* Time graph "combo" view (with the list/tree on the left and the gantt chart
* on the right)
*
* @version 1.0
* @author Patrick Tasse
*/
public class TimeGraphCombo extends Composite {
// ------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------
private static final Object FILLER = new Object();
private static final String ITEM_HEIGHT = "$height$"; //$NON-NLS-1$
// ------------------------------------------------------------------------
// Fields
// ------------------------------------------------------------------------
/** The tree viewer */
private TreeViewer fTreeViewer;
/** The time viewer */
private TimeGraphViewer fTimeGraphViewer;
/** The selection listener map */
private final Map<ITimeGraphSelectionListener, SelectionListenerWrapper> fSelectionListenerMap = new HashMap<>();
/** The map of viewer filters */
private final Map<ViewerFilter, ViewerFilter> fViewerFilterMap = new HashMap<>();
/**
* Flag to block the tree selection changed listener when triggered by the
* time graph combo
*/
private boolean fInhibitTreeSelection = false;
/** Number of filler rows used by the tree content provider */
private int fNumFillerRows;
/** Calculated item height for Linux workaround */
private int fLinuxItemHeight = 0;
/** The button that opens the filter dialog */
private Action showFilterAction;
/** The filter dialog */
private TimeGraphFilterDialog fFilterDialog;
/** The filter generated from the filter dialog */
private RawViewerFilter fFilter;
/** Default weight of each part of the sash */
private static final int[] DEFAULT_WEIGHTS = { 1, 1 };
/** List of all expanded items whose parents are also expanded */
private List<TreeItem> fVisibleExpandedItems = null;
// ------------------------------------------------------------------------
// Classes
// ------------------------------------------------------------------------
/**
* The TreeContentProviderWrapper is used to insert filler items after the
* elements of the tree's real content provider.
*/
private class TreeContentProviderWrapper implements ITreeContentProvider {
private final ITreeContentProvider contentProvider;
public TreeContentProviderWrapper(ITreeContentProvider contentProvider) {
this.contentProvider = contentProvider;
}
@Override
public void dispose() {
contentProvider.dispose();
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
contentProvider.inputChanged(viewer, oldInput, newInput);
}
@Override
public Object[] getElements(Object inputElement) {
Object[] elements = contentProvider.getElements(inputElement);
// add filler elements to ensure alignment with time analysis viewer
Object[] oElements = Arrays.copyOf(elements, elements.length + fNumFillerRows, Object[].class);
for (int i = 0; i < fNumFillerRows; i++) {
oElements[elements.length + i] = FILLER;
}
return oElements;
}
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof ITimeGraphEntry) {
return contentProvider.getChildren(parentElement);
}
return new Object[0];
}
@Override
public Object getParent(Object element) {
if (element instanceof ITimeGraphEntry) {
return contentProvider.getParent(element);
}
return null;
}
@Override
public boolean hasChildren(Object element) {
if (element instanceof ITimeGraphEntry) {
return contentProvider.hasChildren(element);
}
return false;
}
}
/**
* The TreeLabelProviderWrapper is used to intercept the filler items from
* the calls to the tree's real label provider.
*/
private class TreeLabelProviderWrapper implements ITableLabelProvider {
private final ITableLabelProvider labelProvider;
public TreeLabelProviderWrapper(ITableLabelProvider labelProvider) {
this.labelProvider = labelProvider;
}
@Override
public void addListener(ILabelProviderListener listener) {
labelProvider.addListener(listener);
}
@Override
public void dispose() {
labelProvider.dispose();
}
@Override
public boolean isLabelProperty(Object element, String property) {
if (element instanceof ITimeGraphEntry) {
return labelProvider.isLabelProperty(element, property);
}
return false;
}
@Override
public void removeListener(ILabelProviderListener listener) {
labelProvider.removeListener(listener);
}
@Override
public Image getColumnImage(Object element, int columnIndex) {
if (element instanceof ITimeGraphEntry) {
return labelProvider.getColumnImage(element, columnIndex);
}
return null;
}
@Override
public String getColumnText(Object element, int columnIndex) {
if (element instanceof ITimeGraphEntry) {
return labelProvider.getColumnText(element, columnIndex);
}
return null;
}
}
/**
* The SelectionListenerWrapper is used to intercept the filler items from
* the time graph combo's real selection listener, and to prevent double
* notifications from being sent when selection changes in both tree and
* time graph at the same time.
*/
private class SelectionListenerWrapper implements ISelectionChangedListener, ITimeGraphSelectionListener {
private final ITimeGraphSelectionListener listener;
private ITimeGraphEntry selection = null;
public SelectionListenerWrapper(ITimeGraphSelectionListener listener) {
this.listener = listener;
}
@Override
public void selectionChanged(SelectionChangedEvent event) {
if (fInhibitTreeSelection) {
return;
}
Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
if (element instanceof ITimeGraphEntry) {
ITimeGraphEntry entry = (ITimeGraphEntry) element;
if (entry != selection) {
selection = entry;
listener.selectionChanged(new TimeGraphSelectionEvent(event.getSource(), selection));
}
}
}
@Override
public void selectionChanged(TimeGraphSelectionEvent event) {
ITimeGraphEntry entry = event.getSelection();
if (entry != selection) {
selection = entry;
listener.selectionChanged(new TimeGraphSelectionEvent(event.getSource(), selection));
}
}
}
/**
* The ViewerFilterWrapper is used to intercept the filler items from the
* time graph combo's real ViewerFilters. These filler items should always
* be visible.
*/
private class ViewerFilterWrapper extends ViewerFilter {
private ViewerFilter fWrappedFilter;
ViewerFilterWrapper(ViewerFilter filter) {
super();
this.fWrappedFilter = filter;
}
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (element instanceof ITimeGraphEntry) {
return fWrappedFilter.select(viewer, parentElement, element);
}
return true;
}
}
/**
* This filter simply keeps a list of elements that should be filtered out.
* All the other elements will be shown. By default and when the list is set
* to null, all elements are shown.
*/
private class RawViewerFilter extends ViewerFilter {
private List<Object> fFiltered = null;
public void setFiltered(List<Object> objects) {
fFiltered = objects;
}
public List<Object> getFiltered() {
return fFiltered;
}
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (fFiltered == null) {
return true;
}
return !fFiltered.contains(element);
}
}
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
/**
* Constructs a new instance of this class given its parent and a style
* value describing its behavior and appearance.
*
* @param parent
* a widget which will be the parent of the new instance (cannot
* be null)
* @param style
* the style of widget to construct
*/
public TimeGraphCombo(Composite parent, int style) {
this(parent, style, DEFAULT_WEIGHTS);
}
/**
* Constructs a new instance of this class given its parent and a style
* value describing its behavior and appearance.
*
* @param parent
* a widget which will be the parent of the new instance (cannot
* be null)
* @param style
* the style of widget to construct
* @param weights
* The relative weights of each side of the sash form
* @since 2.1
*/
public TimeGraphCombo(Composite parent, int style, int[] weights) {
super(parent, style);
setLayout(new FillLayout());
final SashForm sash = new SashForm(this, SWT.NONE);
fTreeViewer = new TreeViewer(sash, SWT.FULL_SELECTION | SWT.H_SCROLL);
fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
final Tree tree = fTreeViewer.getTree();
tree.setHeaderVisible(true);
tree.setLinesVisible(true);
fTimeGraphViewer = new TimeGraphViewer(sash, SWT.NONE);
fTimeGraphViewer.setItemHeight(getItemHeight(tree));
fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
fTimeGraphViewer.setBorderWidth(tree.getBorderWidth());
fTimeGraphViewer.setNameWidthPref(0);
fFilter = new RawViewerFilter();
addFilter(fFilter);
fFilterDialog = new TimeGraphFilterDialog(getShell());
// Feature in Windows. The tree vertical bar reappears when
// the control is resized so we need to hide it again.
// Bug in Linux. The tree header height is 0 in constructor,
// so we need to reset it later when the control is resized.
tree.addControlListener(new ControlAdapter() {
private int depth = 0;
@Override
public void controlResized(ControlEvent e) {
if (depth == 0) {
depth++;
tree.getVerticalBar().setEnabled(false);
// this can trigger controlResized recursively
tree.getVerticalBar().setVisible(false);
depth--;
}
fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
}
});
// TODO propose patch
// double click @Framesoc
fTreeViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
ITimeGraphEntry node = (ITimeGraphEntry) selection.getFirstElement();
if (node.hasChildren()) {
boolean expanded = fTreeViewer.getExpandedState(node);
fTreeViewer.setExpandedState(node, !expanded);
if (expanded) {
collapseTimeGraphTree(node);
} else {
expandTimeGraphTree(node);
}
}
}
});
// ensure synchronization of expanded items between tree and time graph
fTreeViewer.addTreeListener(new ITreeViewerListener() {
@Override
public void treeCollapsed(TreeExpansionEvent event) {
fTimeGraphViewer.setExpandedState((ITimeGraphEntry) event.getElement(), false);
// @Framesoc
collapseTimeGraphTree((ITimeGraphEntry) event.getElement());
}
@Override
public void treeExpanded(TreeExpansionEvent event) {
ITimeGraphEntry entry = (ITimeGraphEntry) event.getElement();
// @Framesoc
expandTimeGraphTree(entry);
}
});
// ensure synchronization of expanded items between tree and time graph
fTimeGraphViewer.addTreeListener(new ITimeGraphTreeListener() {
@Override
public void treeCollapsed(TimeGraphTreeExpansionEvent event) {
fTreeViewer.setExpandedState(event.getEntry(), false);
alignTreeItems(true);
}
@Override
public void treeExpanded(TimeGraphTreeExpansionEvent event) {
ITimeGraphEntry entry = event.getEntry();
fTreeViewer.setExpandedState(entry, true);
Set<Object> expandedElements = new HashSet<>(Arrays.asList(fTreeViewer.getExpandedElements()));
for (ITimeGraphEntry child : entry.getChildren()) {
if (child.hasChildren()) {
boolean expanded = expandedElements.contains(child);
fTimeGraphViewer.setExpandedState(child, expanded);
}
}
alignTreeItems(true);
}
});
// prevent mouse button from selecting a filler tree item
tree.addListener(SWT.MouseDown, new Listener() {
@Override
public void handleEvent(Event event) {
TreeItem treeItem = tree.getItem(new Point(event.x, event.y));
if (treeItem == null || treeItem.getData() == FILLER) {
event.doit = false;
List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
if (treeItems.size() == 0) {
fTreeViewer.setSelection(new StructuredSelection());
fTimeGraphViewer.setSelection(null);
return;
}
// this prevents from scrolling up when selecting
// the partially visible tree item at the bottom
tree.select(treeItems.get(treeItems.size() - 1));
fTreeViewer.setSelection(new StructuredSelection());
fTimeGraphViewer.setSelection(null);
}
}
});
// prevent mouse wheel from scrolling down into filler tree items
tree.addListener(SWT.MouseWheel, new Listener() {
@Override
public void handleEvent(Event event) {
event.doit = false;
Slider scrollBar = fTimeGraphViewer.getVerticalBar();
fTimeGraphViewer.setTopIndex(scrollBar.getSelection() - event.count);
alignTreeItems(false);
}
});
// prevent key stroke from selecting a filler tree item
tree.addListener(SWT.KeyDown, new Listener() {
@Override
public void handleEvent(Event event) {
List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
if (treeItems.size() == 0) {
fTreeViewer.setSelection(new StructuredSelection());
event.doit = false;
return;
}
if (event.keyCode == SWT.ARROW_DOWN) {
int index = Math.min(fTimeGraphViewer.getSelectionIndex() + 1, treeItems.size() - 1);
fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
event.doit = false;
} else if (event.keyCode == SWT.PAGE_DOWN) {
int height = tree.getSize().y - tree.getHeaderHeight() - tree.getHorizontalBar().getSize().y;
int countPerPage = height / getItemHeight(tree);
int index = Math.min(fTimeGraphViewer.getSelectionIndex() + countPerPage - 1, treeItems.size() - 1);
fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
event.doit = false;
} else if (event.keyCode == SWT.END) {
fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(treeItems.size() - 1).getData());
event.doit = false;
}
if (fTimeGraphViewer.getSelectionIndex() >= 0) {
fTreeViewer.setSelection(new StructuredSelection(fTimeGraphViewer.getSelection()));
} else {
fTreeViewer.setSelection(new StructuredSelection());
}
alignTreeItems(false);
}
});
// ensure alignment of top item between tree and time graph
fTimeGraphViewer.getTimeGraphControl().addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
alignTreeItems(false);
}
});
// ensure synchronization of selected item between tree and time graph
fTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
if (fInhibitTreeSelection) {
return;
}
if (event.getSelection() instanceof IStructuredSelection) {
Object selection = ((IStructuredSelection) event.getSelection()).getFirstElement();
if (selection instanceof ITimeGraphEntry) {
fTimeGraphViewer.setSelection((ITimeGraphEntry) selection);
}
alignTreeItems(false);
}
}
});
// ensure synchronization of selected item between tree and time graph
fTimeGraphViewer.addSelectionListener(new ITimeGraphSelectionListener() {
@Override
public void selectionChanged(TimeGraphSelectionEvent event) {
ITimeGraphEntry entry = fTimeGraphViewer.getSelection();
fInhibitTreeSelection = true; // block the tree selection
// changed listener
if (entry != null) {
StructuredSelection selection = new StructuredSelection(entry);
fTreeViewer.setSelection(selection);
} else {
fTreeViewer.setSelection(new StructuredSelection());
}
fInhibitTreeSelection = false;
alignTreeItems(false);
}
});
// ensure alignment of top item between tree and time graph
fTimeGraphViewer.getVerticalBar().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
alignTreeItems(false);
}
});
// ensure alignment of top item between tree and time graph
fTimeGraphViewer.getTimeGraphControl().addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseScrolled(MouseEvent e) {
alignTreeItems(false);
}
});
// ensure the tree has focus control when mouse is over it if the time
// graph had control
fTreeViewer.getControl().addMouseTrackListener(new MouseTrackAdapter() {
@Override
public void mouseEnter(MouseEvent e) {
if (fTimeGraphViewer.getTimeGraphControl().isFocusControl()) {
fTreeViewer.getControl().setFocus();
}
}
});
// ensure the time graph has focus control when mouse is over it if the
// tree had control
fTimeGraphViewer.getTimeGraphControl().addMouseTrackListener(new MouseTrackAdapter() {
@Override
public void mouseEnter(MouseEvent e) {
if (fTreeViewer.getControl().isFocusControl()) {
fTimeGraphViewer.getTimeGraphControl().setFocus();
}
}
});
fTimeGraphViewer.getTimeGraphScale().addMouseTrackListener(new MouseTrackAdapter() {
@Override
public void mouseEnter(MouseEvent e) {
if (fTreeViewer.getControl().isFocusControl()) {
fTimeGraphViewer.getTimeGraphControl().setFocus();
}
}
});
// The filler rows are required to ensure alignment when the tree does
// not have a visible horizontal scroll bar. The tree does not allow its
// top item to be set to a value that would cause blank space to be
// drawn at the bottom of the tree.
fNumFillerRows = Display.getDefault().getBounds().height / getItemHeight(tree);
sash.setWeights(weights);
}
// @Framesoc
private void collapseTimeGraphTree(ITimeGraphEntry entry) {
fTimeGraphViewer.setExpandedState(entry, false);
// queue the alignment update because the tree items may only be
// actually collapsed after the listeners have been notified
fVisibleExpandedItems = null; // invalidate the cache
getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
alignTreeItems(true);
}
});
}
// @Framesoc
private void expandTimeGraphTree(ITimeGraphEntry entry) {
fTimeGraphViewer.setExpandedState(entry, true);
Set<Object> expandedElements = new HashSet<>(Arrays.asList(fTreeViewer.getExpandedElements()));
for (ITimeGraphEntry child : entry.getChildren()) {
if (child.hasChildren()) {
boolean expanded = expandedElements.contains(child);
fTimeGraphViewer.setExpandedState(child, expanded);
}
}
// queue the alignment update because the tree items may only be
// actually expanded after the listeners have been notified
fVisibleExpandedItems = null; // invalidate the cache
getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
alignTreeItems(true);
}
});
}
// ------------------------------------------------------------------------
// Accessors
// ------------------------------------------------------------------------
/**
* Returns this time graph combo's tree viewer.
*
* @return the tree viewer
*/
public TreeViewer getTreeViewer() {
return fTreeViewer;
}
/**
* Returns this time graph combo's time graph viewer.
*
* @return the time graph viewer
*/
public TimeGraphViewer getTimeGraphViewer() {
return fTimeGraphViewer;
}
/**
* @Framesoc Returns this time graph producer filter dialog
* @return the time graph producer filter dialog
*/
public TimeGraphFilterDialog getFilterDialog() {
return fFilterDialog;
}
/**
* Callback for the show filter action
*
* @since 2.0
*/
public void showFilterDialog() {
ITimeGraphEntry[] topInput = fTimeGraphViewer.getTimeGraphContentProvider().getElements(fTimeGraphViewer.getInput());
if (topInput != null) {
List<? extends ITimeGraphEntry> allElements = listAllInputs(Arrays.asList(topInput));
fFilterDialog.setInput(fTimeGraphViewer.getInput());
fFilterDialog.setTitle(Messages.TmfTimeFilterDialog_WINDOW_TITLE);
fFilterDialog.setMessage(Messages.TmfTimeFilterDialog_MESSAGE);
fFilterDialog.setExpandedElements(allElements.toArray());
if (fFilter.getFiltered() != null) {
ArrayList<? extends ITimeGraphEntry> nonFilteredElements = new ArrayList<>(allElements);
nonFilteredElements.removeAll(fFilter.getFiltered());
fFilterDialog.setInitialElementSelections(nonFilteredElements);
} else {
fFilterDialog.setInitialElementSelections(allElements);
}
fFilterDialog.create();
// reset checked status, managed manually @Framesoc
showFilterAction.setChecked(!showFilterAction.isChecked());
fFilterDialog.open();
// Process selected elements
if (fFilterDialog.getResult() != null) {
fInhibitTreeSelection = true;
if (fFilterDialog.getResult().length != allElements.size()) {
// @Framesoc
checkProducerFilter(true);
ArrayList<Object> filteredElements = new ArrayList<Object>(allElements);
filteredElements.removeAll(Arrays.asList(fFilterDialog.getResult()));
fFilter.setFiltered(filteredElements);
} else {
// @Framesoc
checkProducerFilter(false);
fFilter.setFiltered(null);
}
fTreeViewer.refresh();
fTreeViewer.expandAll();
fTimeGraphViewer.refresh();
fInhibitTreeSelection = false;
alignTreeItems(true);
// Reset selection
if (fFilterDialog.getResult().length > 0) {
setSelection(null);
}
}
}
}
// @Framesoc
private void checkProducerFilter(boolean check) {
if (check) {
showFilterAction.setChecked(true);
showFilterAction.setToolTipText(Messages.TmfTimeGraphCombo_FilterActionToolTipText
+ " (filter applied)"); //$NON-NLS-1$
} else {
showFilterAction.setChecked(false);
}
}
/**
* Get the filtered entries
*
* @return the filtered entries
* @Framesoc
*/
public List<Object> getFilteredEntries() {
return (fFilter.getFiltered() != null) ? fFilter.getFiltered() : new ArrayList<>();
}
/**
* Set the filtered entries
*
* @param filtered
* the filtered entries to set
* @Framesoc
*/
public void setFilteredEntries(List<Object> filtered) {
checkProducerFilter(!filtered.isEmpty());
fFilter.setFiltered(filtered);
}
// /**
// * Filter the entry and its children
// *
// * @param entry
// * entry to filter
// * @Framesoc
// */
// public void filterEntry(ITimeGraphEntry entry) {
// fInhibitTreeSelection = true;
// addFiltered(entry);
// fTreeViewer.refresh();
// fTreeViewer.expandAll();
// fTimeGraphViewer.refresh();
// fInhibitTreeSelection = false;
// alignTreeItems(true);
// setSelection(null);
// }
/**
* Add the entry and all its children to the filtered entries.
*
* @param entry
* entry to filter
* @Framesoc
*/
public void addFiltered(ITimeGraphEntry entry) {
if (fFilter.getFiltered() == null) {
fFilter.setFiltered(new ArrayList<>());
}
fFilter.getFiltered().add(entry);
for (ITimeGraphEntry e : entry.getChildren()) {
addFiltered(e);
}
checkProducerFilter(true);
}
/**
* Get the show filter action.
*
* @return The Action object
* @since 2.0
*/
public Action getShowFilterAction() {
if (showFilterAction == null) {
// showFilter
showFilterAction = new Action("", IAction.AS_CHECK_BOX) { // @Framesoc //$NON-NLS-1$
@Override
public void run() {
showFilterDialog();
}
};
showFilterAction.setText(Messages.TmfTimeGraphCombo_FilterActionNameText);
showFilterAction.setToolTipText(Messages.TmfTimeGraphCombo_FilterActionToolTipText);
// TODO find a nice, distinctive icon
showFilterAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_FILTERS));
}
return showFilterAction;
}
// ------------------------------------------------------------------------
// Control
// ------------------------------------------------------------------------
@Override
public void redraw() {
fTimeGraphViewer.getControl().redraw();
super.redraw();
}
// ------------------------------------------------------------------------
// Operations
// ------------------------------------------------------------------------
/**
* Sets the tree content provider used by this time graph combo.
*
* @param contentProvider
* the tree content provider
*/
public void setTreeContentProvider(ITreeContentProvider contentProvider) {
fTreeViewer.setContentProvider(new TreeContentProviderWrapper(contentProvider));
}
/**
* Sets the tree label provider used by this time graph combo.
*
* @param labelProvider
* the tree label provider
*/
public void setTreeLabelProvider(ITableLabelProvider labelProvider) {
fTreeViewer.setLabelProvider(new TreeLabelProviderWrapper(labelProvider));
}
/**
* Sets the tree content provider used by the filter dialog
*
* @param contentProvider
* the tree content provider
* @since 2.0
*/
public void setFilterContentProvider(ITreeContentProvider contentProvider) {
fFilterDialog.setContentProvider(contentProvider);
}
/**
* Sets the tree label provider used by the filter dialog
*
* @param labelProvider
* the tree label provider
* @since 2.0
*/
public void setFilterLabelProvider(ITableLabelProvider labelProvider) {
fFilterDialog.setLabelProvider(labelProvider);
}
/**
* Sets the tree columns for this time graph combo.
*
* @param columnNames
* the tree column names
*/
public void setTreeColumns(String[] columnNames) {
final Tree tree = fTreeViewer.getTree();
for (String columnName : columnNames) {
TreeColumn column = new TreeColumn(tree, SWT.LEFT);
column.setText(columnName);
column.pack();
}
}
/**
* Sets the tree columns for this time graph combo's filter dialog.
*
* @param columnNames
* the tree column names
* @since 2.0
*/
public void setFilterColumns(String[] columnNames) {
fFilterDialog.setColumnNames(columnNames);
}
/**
* Sets the time graph content provider used by this time graph combo.
*
* @param timeGraphContentProvider
* the time graph content provider
*
* @since 3.0
*/
public void setTimeGraphContentProvider(ITimeGraphContentProvider timeGraphContentProvider) {
fTimeGraphViewer.setTimeGraphContentProvider(timeGraphContentProvider);
}
/**
* Sets the time graph presentation provider used by this time graph combo.
*
* @param timeGraphProvider
* the time graph provider
*/
public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) {
fTimeGraphViewer.setTimeGraphProvider(timeGraphProvider);
}
/**
* Sets or clears the input for this time graph combo.
*
* @param input
* the input of this time graph combo, or <code>null</code> if
* none
*
* @since 3.0
*/
public void setInput(Object input) {
fFilter.setFiltered(null);
fInhibitTreeSelection = true;
fTreeViewer.setInput(input);
for (SelectionListenerWrapper listenerWrapper : fSelectionListenerMap.values()) {
listenerWrapper.selection = null;
}
fInhibitTreeSelection = false;
fTreeViewer.getTree().getVerticalBar().setEnabled(false);
fTreeViewer.getTree().getVerticalBar().setVisible(false);
fTimeGraphViewer.setItemHeight(getItemHeight(fTreeViewer.getTree()));
fTimeGraphViewer.setInput(input);
// queue the alignment update because in Linux the item bounds are not
// set properly until the tree has been painted at least once
fVisibleExpandedItems = null; // invalidate the cache
getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
alignTreeItems(true);
}
});
}
/**
* Gets the input for this time graph combo.
*
* @return The input of this time graph combo, or <code>null</code> if none
*
* @since 3.0
*/
public Object getInput() {
return fTreeViewer.getInput();
}
/**
* Sets or clears the list of links to display on this combo
*
* @param links
* the links to display in this time graph combo
* @since 2.1
*/
public void setLinks(List<ILinkEvent> links) {
fTimeGraphViewer.setLinks(links);
}
/**
* @param filter
* The filter object to be attached to the view
* @since 2.0
*/
public void addFilter(ViewerFilter filter) {
ViewerFilter wrapper = new ViewerFilterWrapper(filter);
fTreeViewer.addFilter(wrapper);
fTimeGraphViewer.addFilter(wrapper);
fViewerFilterMap.put(filter, wrapper);
alignTreeItems(true);
}
/**
* @param filter
* The filter object to be removed from the view
* @since 2.0
*/
public void removeFilter(ViewerFilter filter) {
ViewerFilter wrapper = fViewerFilterMap.get(filter);
fTreeViewer.removeFilter(wrapper);
fTimeGraphViewer.removeFilter(wrapper);
fViewerFilterMap.remove(filter);
alignTreeItems(true);
}
/**
* Refreshes this time graph completely with information freshly obtained
* from its model.
*/
public void refresh() {
fInhibitTreeSelection = true;
Tree tree = fTreeViewer.getTree();
tree.setRedraw(false);
fTreeViewer.refresh();
fTreeViewer.expandAll();
tree.setRedraw(true);
fTimeGraphViewer.refresh();
alignTreeItems(true);
fInhibitTreeSelection = false;
}
/**
* Adds a listener for selection changes in this time graph combo.
*
* @param listener
* a selection listener
*/
public void addSelectionListener(ITimeGraphSelectionListener listener) {
SelectionListenerWrapper listenerWrapper = new SelectionListenerWrapper(listener);
fTreeViewer.addSelectionChangedListener(listenerWrapper);
fSelectionListenerMap.put(listener, listenerWrapper);
fTimeGraphViewer.addSelectionListener(listenerWrapper);
}
/**
* Removes the given selection listener from this time graph combo.
*
* @param listener
* a selection changed listener
*/
public void removeSelectionListener(ITimeGraphSelectionListener listener) {
SelectionListenerWrapper listenerWrapper = fSelectionListenerMap.remove(listener);
fTreeViewer.removeSelectionChangedListener(listenerWrapper);
fTimeGraphViewer.removeSelectionListener(listenerWrapper);
}
/**
* Sets the current selection for this time graph combo.
*
* @param selection
* the new selection
*/
public void setSelection(ITimeGraphEntry selection) {
fTimeGraphViewer.setSelection(selection);
fInhibitTreeSelection = true; // block the tree selection changed
// listener
if (selection != null) {
StructuredSelection structuredSelection = new StructuredSelection(selection);
fTreeViewer.setSelection(structuredSelection);
} else {
fTreeViewer.setSelection(new StructuredSelection());
}
fInhibitTreeSelection = false;
alignTreeItems(false);
}
/**
* Set the expanded state of an entry
*
* @param entry
* The entry to expand/collapse
* @param expanded
* True for expanded, false for collapsed
*
* @since 2.0
*/
public void setExpandedState(ITimeGraphEntry entry, boolean expanded) {
fTimeGraphViewer.setExpandedState(entry, expanded);
fTreeViewer.setExpandedState(entry, expanded);
alignTreeItems(true);
}
/**
* Collapses all nodes of the viewer's tree, starting with the root.
*
* @since 2.0
*/
public void collapseAll() {
fTimeGraphViewer.collapseAll();
fTreeViewer.collapseAll();
alignTreeItems(true);
}
/**
* Expands all nodes of the viewer's tree, starting with the root.
*
* @since 2.0
*/
public void expandAll() {
fTimeGraphViewer.expandAll();
fTreeViewer.expandAll();
alignTreeItems(true);
}
// ------------------------------------------------------------------------
// Internal
// ------------------------------------------------------------------------
private List<TreeItem> getVisibleExpandedItems(Tree tree, boolean refresh) {
if (fVisibleExpandedItems == null || refresh) {
ArrayList<TreeItem> items = new ArrayList<>();
for (TreeItem item : tree.getItems()) {
if (item.getData() == FILLER) {
break;
}
items.add(item);
if (item.getExpanded()) {
addVisibleExpandedItems(items, item);
}
}
fVisibleExpandedItems = items;
}
return fVisibleExpandedItems;
}
private void addVisibleExpandedItems(List<TreeItem> items, TreeItem treeItem) {
for (TreeItem item : treeItem.getItems()) {
items.add(item);
if (item.getExpanded()) {
addVisibleExpandedItems(items, item);
}
}
}
/**
* Explores the list of top-level inputs and returns all the inputs
*
* @param inputs
* The top-level inputs
* @return All the inputs
*/
private List<? extends ITimeGraphEntry> listAllInputs(List<? extends ITimeGraphEntry> inputs) {
ArrayList<ITimeGraphEntry> items = new ArrayList<>();
for (ITimeGraphEntry entry : inputs) {
items.add(entry);
if (entry.hasChildren()) {
items.addAll(listAllInputs(entry.getChildren()));
}
}
return items;
}
private int getItemHeight(final Tree tree) {
/*
* Bug in Linux. The method getItemHeight doesn't always return the
* correct value.
*/
if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
if (fLinuxItemHeight != 0) {
return fLinuxItemHeight;
}
List<TreeItem> treeItems = getVisibleExpandedItems(tree, true);
if (treeItems.size() > 1) {
final TreeItem treeItem0 = treeItems.get(0);
final TreeItem treeItem1 = treeItems.get(1);
PaintListener paintListener = new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
tree.removePaintListener(this);
int y0 = treeItem0.getBounds().y;
int y1 = treeItem1.getBounds().y;
int itemHeight = y1 - y0;
if (itemHeight > 0) {
fLinuxItemHeight = itemHeight;
fTimeGraphViewer.setItemHeight(itemHeight);
}
}
};
tree.addPaintListener(paintListener);
}
} else {
fLinuxItemHeight = -1; // Not Linux, don't perform os.name check
// anymore
}
return tree.getItemHeight();
}
private void alignTreeItems(boolean refreshExpandedItems) {
// align the tree top item with the time graph top item
Tree tree = fTreeViewer.getTree();
List<TreeItem> treeItems = getVisibleExpandedItems(tree, refreshExpandedItems);
int topIndex = fTimeGraphViewer.getTopIndex();
if (topIndex >= treeItems.size()) {
return;
}
TreeItem item = treeItems.get(topIndex);
tree.setTopItem(item);
// ensure the time graph item heights are equal to the tree item heights
int treeHeight = fTreeViewer.getTree().getBounds().height;
int index = topIndex;
Rectangle bounds = item.getBounds();
while (index < treeItems.size() - 1) {
if (bounds.y > treeHeight) {
break;
}
/*
* Bug in Linux. The method getBounds doesn't always return the
* correct height. Use the difference of y position between items to
* calculate the height.
*/
TreeItem nextItem = treeItems.get(index + 1);
Rectangle nextBounds = nextItem.getBounds();
Integer itemHeight = nextBounds.y - bounds.y;
if (itemHeight > 0 && !itemHeight.equals(item.getData(ITEM_HEIGHT))) {
ITimeGraphEntry entry = (ITimeGraphEntry) item.getData();
if (fTimeGraphViewer.getTimeGraphControl().setItemHeight(entry, itemHeight)) {
// @Framesoc: with this line uncommented we get alignment
// issues on Ubuntu
// XXX Investigate
// item.setData(ITEM_HEIGHT, itemHeight);
}
}
index++;
item = nextItem;
bounds = nextBounds;
}
}
}