import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jface.action.Action;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
-import org.eclipse.tracecompass.internal.tmf.ui.Activator;
-import org.eclipse.tracecompass.internal.tmf.ui.ITmfImageConstants;
-import org.eclipse.tracecompass.internal.tmf.ui.Messages;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
import org.eclipse.tracecompass.tmf.ui.views.ITmfTimeAligned;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.dialogs.ITimeGraphEntryActiveProvider;
-import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.dialogs.TimeGraphFilterDialog;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.dialogs.ShowFilterDialogAction;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphColorScheme;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
+
+import com.google.common.collect.Iterables;
/**
* Time graph "combo" view (with the list/tree on the left and the gantt chart
private static final Object FILLER = new Object();
- private static final String ITEM_HEIGHT = "$height$"; //$NON-NLS-1$
-
// ------------------------------------------------------------------------
// Fields
// ------------------------------------------------------------------------
/** 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<>();
+ /** The map of viewer filters to viewer filter wrappers */
+ private final Map<@NonNull ViewerFilter, @NonNull ViewerFilter> fViewerFilterMap = new HashMap<>();
/**
* Flag to block the tree selection changed listener when triggered by the
/** 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;
+ /** The action that opens the filter dialog */
+ private ShowFilterDialogAction fShowFilterDialogAction;
/** Default weight of each part of the sash */
private static final int[] DEFAULT_WEIGHTS = { 1, 1 };
private Listener fSashDragListener;
private SashForm fSashForm;
+ private final boolean fScrollBarsInTreeWorkaround;
+
+ private Font fTreeFont;
+
// ------------------------------------------------------------------------
// Classes
// ------------------------------------------------------------------------
+ /**
+ * The TimeGraphViewerExtension is used to set appropriate values and to
+ * override methods that could be called directly by the user and that must
+ * be handled by the time graph combo.
+ */
+ private class TimeGraphViewerExtension extends TimeGraphViewer {
+
+ private TimeGraphViewerExtension(Composite parent, int style, Tree tree) {
+ super(parent, style);
+ setItemHeight(TimeGraphCombo.this.getItemHeight(tree, true));
+ setHeaderHeight(tree.getHeaderHeight());
+ setBorderWidth(tree.getBorderWidth());
+ setNameWidthPref(0);
+ }
+
+ @Override
+ public ShowFilterDialogAction getShowFilterDialogAction() {
+ return TimeGraphCombo.this.getShowFilterDialogAction();
+ }
+
+ @Override
+ protected TimeGraphControl createTimeGraphControl(Composite composite, TimeGraphColorScheme colors) {
+ return new TimeGraphControl(composite, colors) {
+ @Override
+ public void verticalZoom(boolean zoomIn) {
+ TimeGraphCombo.this.verticalZoom(zoomIn);
+ }
+
+ @Override
+ public void resetVerticalZoom() {
+ TimeGraphCombo.this.resetVerticalZoom();
+ }
+
+ @Override
+ public void setElementPosition(ITimeGraphEntry entry, int y) {
+ /*
+ * Queue the update to make sure the time graph combo has finished
+ * updating the item heights.
+ */
+ getDisplay().asyncExec(() -> {
+ super.setElementPosition(entry, y);
+ alignTreeItems(false);
+ });
+ }
+ };
+ }
+ }
+
/**
* The TreeContentProviderWrapper is used to insert filler items after
* the elements of the tree's real content provider.
* 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 static class TreeLabelProviderWrapper implements ITableLabelProvider {
private final ITableLabelProvider labelProvider;
public TreeLabelProviderWrapper(ITableLabelProvider labelProvider) {
* the time graph combo's real ViewerFilters. These filler items should
* always be visible.
*/
- private class ViewerFilterWrapper extends ViewerFilter {
+ private static class ViewerFilterWrapper extends ViewerFilter {
private ViewerFilter fWrappedFilter;
}
- /**
- * 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
// ------------------------------------------------------------------------
fSashForm = new SashForm(this, SWT.NONE);
- fTreeViewer = new TreeViewer(fSashForm, SWT.FULL_SELECTION | SWT.H_SCROLL);
+ /*
+ * In Windows, SWT.H_SCROLL | SWT.NO_SCROLL is not properly supported,
+ * both scroll bars are always created. See Tree.checkStyle: "Even when
+ * WS_HSCROLL or WS_VSCROLL is not specified, Windows creates trees and
+ * tables with scroll bars."
+ */
+ fScrollBarsInTreeWorkaround = "win32".equals(SWT.getPlatform()); //$NON-NLS-1$
+
+ int scrollBarStyle = fScrollBarsInTreeWorkaround ? SWT.H_SCROLL : SWT.H_SCROLL | SWT.NO_SCROLL;
+
+ fTreeViewer = new TreeViewer(fSashForm, SWT.FULL_SELECTION | scrollBarStyle);
fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
final Tree tree = fTreeViewer.getTree();
tree.setHeaderVisible(true);
tree.setLinesVisible(true);
- fTimeGraphViewer = new TimeGraphViewer(fSashForm, SWT.NONE);
- fTimeGraphViewer.setItemHeight(getItemHeight(tree));
- fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
- fTimeGraphViewer.setBorderWidth(tree.getBorderWidth());
- fTimeGraphViewer.setNameWidthPref(0);
-
- fFilter = new RawViewerFilter();
- addFilter(fFilter);
+ fTimeGraphViewer = new TimeGraphViewerExtension(fSashForm, SWT.NONE, tree);
- fFilterDialog = new TimeGraphFilterDialog(getShell());
+ if (fScrollBarsInTreeWorkaround) {
+ // Feature in Windows. The tree vertical bar reappears when
+ // the control is resized so we need to hide it again.
+ tree.addControlListener(new ControlAdapter() {
+ private int depth = 0;
- // Feature in Windows. The tree vertical bar reappears when
- // the control is resized so we need to hide it again.
- 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--;
+ @Override
+ public void controlResized(ControlEvent e) {
+ if (depth == 0) {
+ depth++;
+ tree.getVerticalBar().setEnabled(false);
+ // this can trigger controlResized recursively
+ tree.getVerticalBar().setVisible(false);
+ depth--;
+ }
}
- }
- });
+ });
+ }
// Bug in Linux. The tree header height is 0 in constructor,
// so we need to reset it later when the control is painted.
// This work around used to be done on control resized but the header
}
});
+ tree.addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(DisposeEvent e) {
+ if (fTreeFont != null) {
+ fTreeFont.dispose();
+ }
+ }
+ });
+
// ensure synchronization of expanded items between tree and time graph
fTreeViewer.addTreeListener(new ITreeViewerListener() {
@Override
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 countPerPage = height / getItemHeight(tree, false);
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;
+ } else if ((event.character == '+' || event.character == '=') && ((event.stateMask & SWT.CTRL) != 0)) {
+ fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
+ return;
+ } else if (event.character == '-' && ((event.stateMask & SWT.CTRL) != 0)) {
+ fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
+ return;
+ } else if (event.character == '0' && ((event.stateMask & SWT.CTRL) != 0)) {
+ fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
+ return;
+ } else {
+ return;
}
if (fTimeGraphViewer.getSelectionIndex() >= 0) {
fTreeViewer.setSelection(new StructuredSelection(fTimeGraphViewer.getSelection()));
// 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);
+ fNumFillerRows = Display.getDefault().getBounds().height / getItemHeight(tree, false);
fSashForm.setWeights(weights);
});
}
+ private void verticalZoom(boolean zoomIn) {
+ Tree tree = fTreeViewer.getTree();
+ FontData fontData = tree.getFont().getFontData()[0];
+ int height = fontData.getHeight() + (zoomIn ? 1 : -1);
+ if (height <= 0) {
+ return;
+ }
+ fontData.setHeight(height);
+ if (fTreeFont != null) {
+ fTreeFont.dispose();
+ }
+ fTreeFont = new Font(tree.getDisplay(), fontData);
+ tree.setFont(fTreeFont);
+ redraw();
+ update();
+ fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
+ fTimeGraphViewer.setItemHeight(getItemHeight(tree, true));
+ alignTreeItems(false);
+ }
+
+ private void resetVerticalZoom() {
+ Tree tree = fTreeViewer.getTree();
+ if (fTreeFont != null) {
+ fTreeFont.dispose();
+ fTreeFont = null;
+ }
+ tree.setFont(null);
+ redraw();
+ update();
+ fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
+ fTimeGraphViewer.setItemHeight(getItemHeight(tree, true));
+ alignTreeItems(false);
+ }
+
private void sendTimeViewAlignmentChanged() {
TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(fSashForm, getTimeViewAlignmentInfo()));
}
}
/**
- * Callback for the show filter action
+ * Get the show filter dialog action.
+ *
+ * @return The Action object
+ * @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();
- fFilterDialog.open();
- // Process selected elements
- if (fFilterDialog.getResult() != null) {
- fInhibitTreeSelection = true;
- if (fFilterDialog.getResult().length != allElements.size()) {
- ArrayList<Object> filteredElements = new ArrayList<Object>(allElements);
- filteredElements.removeAll(Arrays.asList(fFilterDialog.getResult()));
- fFilter.setFiltered(filteredElements);
- } else {
- fFilter.setFiltered(null);
+ public ShowFilterDialogAction getShowFilterDialogAction() {
+ if (fShowFilterDialogAction == null) {
+ fShowFilterDialogAction = new ShowFilterDialogAction(fTimeGraphViewer) {
+ @Override
+ protected void addFilter(ViewerFilter filter) {
+ /* add filter to the combo instead of the viewer */
+ TimeGraphCombo.this.addFilter(filter);
}
- fTreeViewer.refresh();
- fTreeViewer.expandAll();
- fTimeGraphViewer.refresh();
- fTimeGraphViewer.expandAll();
- fInhibitTreeSelection = false;
- alignTreeItems(true);
- // Reset selection
- if (fFilterDialog.getResult().length > 0) {
- setSelection(null);
+
+ @Override
+ protected void removeFilter(ViewerFilter filter) {
+ /* remove filter from the combo instead of the viewer */
+ TimeGraphCombo.this.removeFilter(filter);
}
- }
- }
- }
- /**
- * Get the show filter action.
- *
- * @return The Action object
- */
- public Action getShowFilterAction() {
- if (showFilterAction == null) {
- // showFilter
- showFilterAction = new Action() {
@Override
- public void run() {
- showFilterDialog();
+ protected void refresh() {
+ /* refresh the combo instead of the viewer */
+ TimeGraphCombo.this.refresh();
}
};
- 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;
+ return fShowFilterDialogAction;
}
// ------------------------------------------------------------------------
super.redraw();
}
+ @Override
+ public void update() {
+ fTimeGraphViewer.getControl().update();
+ super.update();
+ }
+
// ------------------------------------------------------------------------
// Operations
// ------------------------------------------------------------------------
* @param contentProvider the tree content provider
*/
public void setFilterContentProvider(ITreeContentProvider contentProvider) {
- fFilterDialog.setContentProvider(contentProvider);
+ getShowFilterDialogAction().getFilterDialog().setContentProvider(contentProvider);
}
/**
* @param labelProvider the tree label provider
*/
public void setFilterLabelProvider(ITableLabelProvider labelProvider) {
- fFilterDialog.setLabelProvider(labelProvider);
+ getShowFilterDialogAction().getFilterDialog().setLabelProvider(labelProvider);
}
/**
* @since 1.0
*/
public void addTimeGraphFilterCheckActiveButton(ITimeGraphEntryActiveProvider activeProvider) {
- fFilterDialog.addTimeGraphFilterCheckActiveButton(activeProvider);
+ getShowFilterDialogAction().getFilterDialog().addTimeGraphFilterCheckActiveButton(activeProvider);
}
/**
* @since 1.0
*/
public void addTimeGraphFilterUncheckInactiveButton(ITimeGraphEntryActiveProvider inactiveProvider) {
- fFilterDialog.addTimeGraphFilterUncheckInactiveButton(inactiveProvider);
+ getShowFilterDialogAction().getFilterDialog().addTimeGraphFilterUncheckInactiveButton(inactiveProvider);
}
/**
final Tree tree = fTreeViewer.getTree();
for (String columnName : columnNames) {
TreeColumn column = new TreeColumn(tree, SWT.LEFT);
+ column.setMoveable(true);
column.setText(columnName);
column.pack();
}
* @param columnNames the tree column names
*/
public void setFilterColumns(String[] columnNames) {
- fFilterDialog.setColumnNames(columnNames);
+ getShowFilterDialogAction().getFilterDialog().setColumnNames(columnNames);
}
/**
* @param input the input of this time graph combo, or <code>null</code> if none
*/
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()));
+ if (fScrollBarsInTreeWorkaround) {
+ fTreeViewer.getTree().getVerticalBar().setEnabled(false);
+ fTreeViewer.getTree().getVerticalBar().setVisible(false);
+ }
fTimeGraphViewer.setInput(input);
+ fTimeGraphViewer.setItemHeight(getItemHeight(fTreeViewer.getTree(), false));
// 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
/**
* @param filter The filter object to be attached to the view
*/
- public void addFilter(ViewerFilter filter) {
+ public void addFilter(@NonNull ViewerFilter filter) {
+ fInhibitTreeSelection = true;
ViewerFilter wrapper = new ViewerFilterWrapper(filter);
fTreeViewer.addFilter(wrapper);
- fTimeGraphViewer.addFilter(wrapper);
+ fTimeGraphViewer.addFilter(filter);
fViewerFilterMap.put(filter, wrapper);
alignTreeItems(true);
+ fInhibitTreeSelection = false;
}
/**
* @param filter The filter object to be removed from the view
*/
- public void removeFilter(ViewerFilter filter) {
+ public void removeFilter(@NonNull ViewerFilter filter) {
+ fInhibitTreeSelection = true;
ViewerFilter wrapper = fViewerFilterMap.get(filter);
fTreeViewer.removeFilter(wrapper);
- fTimeGraphViewer.removeFilter(wrapper);
+ fTimeGraphViewer.removeFilter(filter);
fViewerFilterMap.remove(filter);
alignTreeItems(true);
+ fInhibitTreeSelection = false;
+ }
+
+ /**
+ * Returns this viewer's filters.
+ *
+ * @return an array of viewer filters
+ * @since 2.0
+ */
+ public @NonNull ViewerFilter[] getFilters() {
+ return fTimeGraphViewer.getFilters();
+ }
+
+ /**
+ * Sets the filters, replacing any previous filters, and triggers
+ * refiltering of the elements.
+ *
+ * @param filters
+ * an array of viewer filters, or null
+ * @since 2.0
+ */
+ public void setFilters(@NonNull ViewerFilter[] filters) {
+ fInhibitTreeSelection = true;
+ fViewerFilterMap.clear();
+ if (filters == null) {
+ fTreeViewer.resetFilters();
+ } else {
+ for (ViewerFilter filter : filters) {
+ ViewerFilter wrapper = new ViewerFilterWrapper(filter);
+ fViewerFilterMap.put(filter, wrapper);
+ }
+ ViewerFilter[] wrappers = Iterables.toArray(fViewerFilterMap.values(), ViewerFilter.class);
+ fTreeViewer.setFilters(wrappers);
+ }
+ fTimeGraphViewer.setFilters(filters);
+ alignTreeItems(true);
+ fInhibitTreeSelection = false;
}
/**
try {
tree.setRedraw(false);
fTreeViewer.refresh();
- fTreeViewer.expandToLevel(fTreeViewer.getAutoExpandLevel());
} finally {
tree.setRedraw(true);
}
}
/**
- * Sets the auto-expand level to be used when the input of the viewer is set
- * using {@link #setInput(Object)}. The value 0 means that there is no
- * auto-expand; 1 means that top-level elements are expanded, but not their
- * children; 2 means that top-level elements are expanded, and their
- * children, but not grand-children; and so on.
+ * Sets the auto-expand level to be used for new entries discovered when
+ * calling {@link #setInput(Object)} or {@link #refresh()}. The value 0
+ * means that there is no auto-expand; 1 means that top-level entries are
+ * expanded, but not their children; 2 means that top-level entries are
+ * expanded, and their children, but not grand-children; and so on.
* <p>
* The value {@link #ALL_LEVELS} means that all subtrees should be expanded.
* </p>
+ *
* @param level
* non-negative level, or <code>ALL_LEVELS</code> to expand all
* levels of the tree
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;
+ List<TreeItem> visibleExpandedItems = new ArrayList<>();
+ addVisibleExpandedItems(visibleExpandedItems, tree.getItems());
+ fVisibleExpandedItems = visibleExpandedItems;
}
return fVisibleExpandedItems;
}
- private void addVisibleExpandedItems(List<TreeItem> items, TreeItem treeItem) {
- for (TreeItem item : treeItem.getItems()) {
- items.add(item);
- if (item.getExpanded()) {
- addVisibleExpandedItems(items, item);
+ private void addVisibleExpandedItems(List<TreeItem> visibleExpandedItems, TreeItem[] items) {
+ for (TreeItem item : items) {
+ Object data = item.getData();
+ if (data == FILLER) {
+ break;
}
- }
- }
-
- /**
- * 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()));
+ visibleExpandedItems.add(item);
+ boolean expandedState = fTimeGraphViewer.getExpandedState((ITimeGraphEntry) data);
+ if (item.getExpanded() != expandedState) {
+ /* synchronize the expanded state of both viewers */
+ fTreeViewer.setExpandedState(data, expandedState);
+ }
+ if (expandedState) {
+ addVisibleExpandedItems(visibleExpandedItems, item.getItems());
}
}
- return items;
}
- private int getItemHeight(final Tree tree) {
+ private int getItemHeight(final Tree tree, boolean force) {
/*
* 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) {
+ if (fLinuxItemHeight != 0 && !force) {
return fLinuxItemHeight;
}
- List<TreeItem> treeItems = getVisibleExpandedItems(tree, true);
- if (treeItems.size() > 1) {
- final TreeItem treeItem0 = treeItems.get(0);
- final TreeItem treeItem1 = treeItems.get(1);
+
+ if (getVisibleExpandedItems(tree, true).size() > 1) {
PaintListener paintListener = new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
+ // get the treeItems here to have all items
+ List<TreeItem> treeItems = getVisibleExpandedItems(tree, true);
+ if (treeItems.size() < 2) {
+ return;
+ }
+ final TreeItem treeItem0 = treeItems.get(0);
+ final TreeItem treeItem1 = treeItems.get(1);
tree.removePaintListener(this);
int y0 = treeItem0.getBounds().y;
int y1 = treeItem1.getBounds().y;
TreeItem item = treeItems.get(topIndex);
tree.setTopItem(item);
+ /*
+ * In GTK3, the bounds of the tree items are only sure to be correct
+ * after the tree has been painted.
+ */
+ tree.addPaintListener(new PaintListener() {
+ @Override
+ public void paintControl(PaintEvent e) {
+ tree.removePaintListener(this);
+ doAlignTreeItems();
+ redraw();
+ /*
+ * Bug in GTK. Calling setTopItem() can scroll to the wrong item
+ * when the 'tree view' is dirty. Set it again once it is clean.
+ */
+ if (SWT.getPlatform().equals("gtk")) { //$NON-NLS-1$
+ TreeItem topItem = tree.getTopItem();
+ tree.getDisplay().asyncExec(() -> {
+ if (!tree.isDisposed() && !topItem.isDisposed()) {
+ tree.setTopItem(topItem);
+ }
+ });
+ }
+ }
+ });
+ /* Make sure the paint event is triggered. */
+ tree.redraw();
+ }
+
+ private void doAlignTreeItems() {
+ Tree tree = fTreeViewer.getTree();
+ List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
+ int topIndex = fTimeGraphViewer.getTopIndex();
+ if (topIndex >= treeItems.size()) {
+ return;
+ }
+ TreeItem item = treeItems.get(topIndex);
+
+ // get the first filler item so we can calculate the last item's height
+ TreeItem fillerItem = null;
+ for (TreeItem treeItem : fTreeViewer.getTree().getItems()) {
+ if (treeItem.getData() == FILLER) {
+ fillerItem = treeItem;
+ break;
+ }
+ }
+
// 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) {
+ while (index < treeItems.size()) {
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)) {
- item.setData(ITEM_HEIGHT, itemHeight);
- }
- }
+ TreeItem nextItem = (index + 1 == treeItems.size()) ? fillerItem : treeItems.get(index + 1);
+ Rectangle nextBounds = alignTreeItem(item, bounds, nextItem);
index++;
item = nextItem;
bounds = nextBounds;
}
+
+ /*
+ * When an item's height in the time graph changes, it is possible that
+ * the time graph readjusts its top index to fill empty space at the
+ * bottom of the viewer. Calling method setTopIndex() triggers this
+ * adjustment, if needed. In that case, we need to make sure that the
+ * newly visible items at the top of the viewer are also aligned.
+ */
+ fTimeGraphViewer.setTopIndex(topIndex);
+ if (fTimeGraphViewer.getTopIndex() != topIndex) {
+ alignTreeItems(false);
+ }
+ }
+
+ private Rectangle alignTreeItem(TreeItem item, Rectangle bounds, TreeItem nextItem) {
+ /*
+ * 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.
+ */
+ Rectangle nextBounds = nextItem.getBounds();
+ Integer itemHeight = nextBounds.y - bounds.y;
+ if (itemHeight > 0) {
+ ITimeGraphEntry entry = (ITimeGraphEntry) item.getData();
+ fTimeGraphViewer.getTimeGraphControl().setItemHeight(entry, itemHeight);
+ }
+ return nextBounds;
}
/**
int timeAxisOffset = Math.min(offset, total);
int width1 = Math.max(0, timeAxisOffset - fSashForm.getSashWidth());
int width2 = total - timeAxisOffset;
- fSashForm.setWeights(new int[] { width1, width2 });
- fSashForm.layout();
+ if (width1 >= 0 && width2 > 0 || width1 > 0 && width2 >= 0) {
+ fSashForm.setWeights(new int[] { width1, width2 });
+ fSashForm.layout();
+ }
Composite composite = fTimeGraphViewer.getTimeAlignedComposite();
GridLayout layout = (GridLayout) composite.getLayout();