/******************************************************************************* * 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; } } }