tmf.ui: bug 505695 fix time graph views with GTK
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / TimeGraphCombo.java
index 3af99530f334861de5ddd3d92ee48ccebbcabb63..0c27d2515b296c2fc8907e5f5217a580e6fa2457 100644 (file)
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2012, 2015 Ericsson, others
+ * Copyright (c) 2012, 2016 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
@@ -41,13 +41,17 @@ 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.KeyEvent;
+import org.eclipse.swt.events.MouseAdapter;
 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.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
@@ -71,6 +75,10 @@ import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.dialogs.ITimeGraphEntry
 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.ITimeDataProvider;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphColorScheme;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphMarkerAxis;
 
 import com.google.common.collect.Iterables;
 
@@ -86,7 +94,9 @@ public class TimeGraphCombo extends Composite {
     // Constants
     // ------------------------------------------------------------------------
 
-    /** Constant indicating that all levels of the time graph should be expanded */
+    /**
+     * Constant indicating that all levels of the time graph should be expanded
+     */
     public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS;
 
     private static final Object FILLER = new Object();
@@ -105,7 +115,7 @@ public class TimeGraphCombo extends Composite {
     private final Map<ITimeGraphSelectionListener, SelectionListenerWrapper> fSelectionListenerMap = new HashMap<>();
 
     /** The map of viewer filters to viewer filter wrappers */
-    private final Map<ViewerFilter, ViewerFilter> fViewerFilterMap = new HashMap<>();
+    private final Map<@NonNull ViewerFilter, @NonNull ViewerFilter> fViewerFilterMap = new HashMap<>();
 
     /**
      * Flag to block the tree selection changed listener when triggered by the
@@ -133,13 +143,149 @@ public class TimeGraphCombo extends Composite {
 
     private final boolean fScrollBarsInTreeWorkaround;
 
+    private Font fTreeFont;
+
     // ------------------------------------------------------------------------
     // Classes
     // ------------------------------------------------------------------------
 
     /**
-     * The TreeContentProviderWrapper is used to insert filler items after
-     * the elements of the tree's real content provider.
+     * 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(() -> {
+                        if (isDisposed()) {
+                            return;
+                        }
+                        super.setElementPosition(entry, y);
+                        alignTreeItems(false);
+                    });
+                }
+            };
+        }
+
+        private class TimeGraphMarkerAxisExtension extends TimeGraphMarkerAxis {
+            private int fMargin = 0;
+
+            public TimeGraphMarkerAxisExtension(Composite parent, @NonNull TimeGraphColorScheme colorScheme, @NonNull ITimeDataProvider timeProvider) {
+                super(parent, colorScheme, timeProvider);
+            }
+
+            @Override
+            public Point computeSize(int wHint, int hHint, boolean changed) {
+                Point size = super.computeSize(wHint, hHint, changed);
+                if (size.y > 0) {
+                    size.y += fMargin;
+                }
+                return size;
+            }
+
+            @Override
+            public void redraw() {
+                super.redraw();
+                fTreeViewer.getControl().redraw();
+            }
+
+            @Override
+            protected void drawMarkerAxis(Rectangle bounds, int nameSpace, GC gc) {
+                super.drawMarkerAxis(bounds, nameSpace, gc);
+            }
+
+            private Rectangle getAxisBounds() {
+                Tree tree = fTreeViewer.getTree();
+                Rectangle axisBounds = getBounds();
+                Rectangle treeClientArea = tree.getClientArea();
+                if (axisBounds.isEmpty()) {
+                    treeClientArea.y += treeClientArea.height;
+                    treeClientArea.height = 0;
+                    return treeClientArea;
+                }
+                Composite axisParent = getParent();
+                Point axisDisplayCoordinates = axisParent.toDisplay(axisBounds.x, axisBounds.y);
+                Point axisTreeCoordinates = tree.toControl(axisDisplayCoordinates);
+                int height = treeClientArea.y + treeClientArea.height - axisTreeCoordinates.y;
+                int margin = Math.max(0, axisBounds.height - height);
+                if (fMargin != margin) {
+                    fMargin = margin;
+                    getParent().layout();
+                    redraw();
+                    axisTreeCoordinates.y -= margin;
+                    height += margin;
+                }
+                return new Rectangle(treeClientArea.x, axisTreeCoordinates.y, treeClientArea.width, height);
+            }
+        }
+
+        @Override
+        protected TimeGraphMarkerAxis createTimeGraphMarkerAxis(Composite parent, @NonNull TimeGraphColorScheme colorScheme, @NonNull ITimeDataProvider timeProvider) {
+            TimeGraphMarkerAxisExtension timeGraphMarkerAxis = new TimeGraphMarkerAxisExtension(parent, colorScheme, timeProvider);
+            Tree tree = fTreeViewer.getTree();
+            tree.addPaintListener(e -> {
+                /*
+                 * Draw the marker axis over the tree. Only the name area will
+                 * be drawn, since the time area has zero width.
+                 */
+                Rectangle bounds = timeGraphMarkerAxis.getAxisBounds();
+                e.gc.setBackground(timeGraphMarkerAxis.getBackground());
+                timeGraphMarkerAxis.drawMarkerAxis(bounds, bounds.width, e.gc);
+            });
+            tree.addMouseListener(new MouseAdapter() {
+                @Override
+                public void mouseDown(MouseEvent e) {
+                    Rectangle bounds = timeGraphMarkerAxis.getAxisBounds();
+                    if (bounds.contains(e.x, e.y)) {
+                        timeGraphMarkerAxis.mouseDown(e, bounds, bounds.width);
+                    }
+                }
+            });
+            tree.getHorizontalBar().addSelectionListener(new SelectionAdapter() {
+                @Override
+                public void widgetSelected(SelectionEvent e) {
+                    tree.redraw();
+                }
+            });
+            return timeGraphMarkerAxis;
+        }
+    }
+
+    /**
+     * 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;
@@ -195,10 +341,10 @@ public class TimeGraphCombo extends Composite {
     }
 
     /**
-     * The TreeLabelProviderWrapper is used to intercept the filler items
-     * from the calls to the tree's real label 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) {
@@ -286,11 +432,11 @@ public class TimeGraphCombo extends Composite {
     }
 
     /**
-     * The ViewerFilterWrapper is used to intercept the filler items from
-     * the time graph combo's real ViewerFilters. These filler items should
-     * always be visible.
+     * 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 static class ViewerFilterWrapper extends ViewerFilter {
 
         private ViewerFilter fWrappedFilter;
 
@@ -314,11 +460,14 @@ public class TimeGraphCombo extends Composite {
     // ------------------------------------------------------------------------
 
     /**
-     * Constructs a new instance of this class given its parent
-     * and a style value describing its behavior and appearance.
+     * 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 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);
@@ -358,11 +507,7 @@ public class TimeGraphCombo extends Composite {
         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);
+        fTimeGraphViewer = new TimeGraphViewerExtension(fSashForm, SWT.NONE, tree);
 
         if (fScrollBarsInTreeWorkaround) {
             // Feature in Windows. The tree vertical bar reappears when
@@ -387,7 +532,8 @@ public class TimeGraphCombo extends Composite {
         // This work around used to be done on control resized but the header
         // height was not initialized on the initial resize on GTK3.
         tree.addPaintListener(new PaintListener() {
-            @Override
+
+    @Override
             public void paintControl(PaintEvent e) {
                 int headerHeight = tree.getHeaderHeight();
                 if (headerHeight > 0) {
@@ -397,6 +543,12 @@ public class TimeGraphCombo extends Composite {
             }
         });
 
+        tree.addDisposeListener(e -> {
+            if (fTreeFont != null) {
+                fTreeFont.dispose();
+            }
+        });
+
         // ensure synchronization of expanded items between tree and time graph
         fTreeViewer.addTreeListener(new ITreeViewerListener() {
             @Override
@@ -458,69 +610,75 @@ public class TimeGraphCombo extends Composite {
         });
 
         // 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);
-                }
+        tree.addListener(SWT.MouseDown, event -> {
+            List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
+            if (treeItems.isEmpty()) {
+                event.doit = false;
+                fTreeViewer.setSelection(new StructuredSelection());
+                fTimeGraphViewer.setSelection(null);
+                return;
+            }
+            TreeItem lastTreeItem = treeItems.get(treeItems.size() - 1);
+            if (event.y >= lastTreeItem.getBounds().y + lastTreeItem.getBounds().height) {
+                event.doit = false;
+                // 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);
+        tree.addListener(SWT.MouseWheel, event -> {
+            event.doit = false;
+            if (event.count == 0) {
+                return;
             }
+            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);
+        tree.addListener(SWT.KeyDown, 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, 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()));
+            } else {
+                fTreeViewer.setSelection(new StructuredSelection());
             }
+            alignTreeItems(false);
         });
 
         // ensure alignment of top item between tree and time graph
@@ -532,53 +690,40 @@ public class TimeGraphCombo extends Composite {
         });
 
         // 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);
+        fTreeViewer.addSelectionChangedListener(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);
-            }
+        fTimeGraphViewer.addSelectionListener(event -> {
+            ITimeGraphEntry entry = fTimeGraphViewer.getSelection();
+            setSelectionInTree(entry);
         });
 
         // ensure alignment of top item between tree and time graph
         fTimeGraphViewer.getVerticalBar().addSelectionListener(new SelectionAdapter() {
-            @Override
+
+    @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);
+        fTimeGraphViewer.getTimeGraphControl().addMouseWheelListener(e -> {
+            if (e.count == 0) {
+                return;
             }
+            alignTreeItems(false);
         });
 
         // ensure the tree has focus control when mouse is over it if the time graph had control
@@ -612,7 +757,7 @@ public class TimeGraphCombo extends Composite {
         // 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);
 
@@ -643,6 +788,40 @@ public class TimeGraphCombo extends Composite {
         });
     }
 
+    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()));
     }
@@ -673,7 +852,7 @@ public class TimeGraphCombo extends Composite {
      * Get the show filter dialog action.
      *
      * @return The Action object
-     * @since 2.0
+     * @since 1.2
      */
     public ShowFilterDialogAction getShowFilterDialogAction() {
         if (fShowFilterDialogAction == null) {
@@ -710,6 +889,12 @@ public class TimeGraphCombo extends Composite {
         super.redraw();
     }
 
+    @Override
+    public void update() {
+        fTimeGraphViewer.getControl().update();
+        super.update();
+    }
+
     // ------------------------------------------------------------------------
     // Operations
     // ------------------------------------------------------------------------
@@ -717,7 +902,8 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets the tree content provider used by this time graph combo.
      *
-     * @param contentProvider the tree content provider
+     * @param contentProvider
+     *            the tree content provider
      */
     public void setTreeContentProvider(ITreeContentProvider contentProvider) {
         fTreeViewer.setContentProvider(new TreeContentProviderWrapper(contentProvider));
@@ -726,7 +912,8 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets the tree label provider used by this time graph combo.
      *
-     * @param labelProvider the tree label provider
+     * @param labelProvider
+     *            the tree label provider
      */
     public void setTreeLabelProvider(ITableLabelProvider labelProvider) {
         fTreeViewer.setLabelProvider(new TreeLabelProviderWrapper(labelProvider));
@@ -735,7 +922,8 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets the tree content provider used by the filter dialog
      *
-     * @param contentProvider the tree content provider
+     * @param contentProvider
+     *            the tree content provider
      */
     public void setFilterContentProvider(ITreeContentProvider contentProvider) {
         getShowFilterDialogAction().getFilterDialog().setContentProvider(contentProvider);
@@ -744,7 +932,8 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets the tree label provider used by the filter dialog
      *
-     * @param labelProvider the tree label provider
+     * @param labelProvider
+     *            the tree label provider
      */
     public void setFilterLabelProvider(ITableLabelProvider labelProvider) {
         getShowFilterDialogAction().getFilterDialog().setLabelProvider(labelProvider);
@@ -775,12 +964,14 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets the tree columns for this time graph combo.
      *
-     * @param columnNames the tree column names
+     * @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.setMoveable(true);
             column.setText(columnName);
             column.pack();
         }
@@ -789,7 +980,8 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets the tree columns for this time graph combo's filter dialog.
      *
-     * @param columnNames the tree column names
+     * @param columnNames
+     *            the tree column names
      */
     public void setFilterColumns(String[] columnNames) {
         getShowFilterDialogAction().getFilterDialog().setColumnNames(columnNames);
@@ -808,7 +1000,8 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets the time graph presentation provider used by this time graph combo.
      *
-     * @param timeGraphProvider the time graph provider
+     * @param timeGraphProvider
+     *            the time graph provider
      */
     public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) {
         fTimeGraphViewer.setTimeGraphProvider(timeGraphProvider);
@@ -817,7 +1010,9 @@ public class TimeGraphCombo extends Composite {
     /**
      * 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
+     * @param input
+     *            the input of this time graph combo, or <code>null</code> if
+     *            none
      */
     public void setInput(Object input) {
         fInhibitTreeSelection = true;
@@ -831,15 +1026,19 @@ public class TimeGraphCombo extends Composite {
             fTreeViewer.getTree().getVerticalBar().setVisible(false);
         }
         fTimeGraphViewer.setInput(input);
-        fTimeGraphViewer.setItemHeight(getItemHeight(fTreeViewer.getTree()));
+        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
         getDisplay().asyncExec(new Runnable() {
             @Override
             public void run() {
+                if (isDisposed()) {
+                    return;
+                }
                 alignTreeItems(true);
-            }});
+            }
+        });
     }
 
     /**
@@ -854,16 +1053,18 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets or clears the list of links to display on this combo
      *
-     * @param links the links to display in this time graph combo
+     * @param links
+     *            the links to display in this time graph combo
      */
     public void setLinks(List<ILinkEvent> links) {
         fTimeGraphViewer.setLinks(links);
     }
 
     /**
-     * @param filter The filter object to be attached to the view
+     * @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);
@@ -874,9 +1075,10 @@ public class TimeGraphCombo extends Composite {
     }
 
     /**
-     * @param filter The filter object to be removed from the view
+     * @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);
@@ -890,9 +1092,9 @@ public class TimeGraphCombo extends Composite {
      * Returns this viewer's filters.
      *
      * @return an array of viewer filters
-     * @since 2.0
+     * @since 1.2
      */
-    public ViewerFilter[] getFilters() {
+    public @NonNull ViewerFilter[] getFilters() {
         return fTimeGraphViewer.getFilters();
     }
 
@@ -902,9 +1104,9 @@ public class TimeGraphCombo extends Composite {
      *
      * @param filters
      *            an array of viewer filters, or null
-     * @since 2.0
+     * @since 1.2
      */
-    public void setFilters(ViewerFilter[] filters) {
+    public void setFilters(@NonNull ViewerFilter[] filters) {
         fInhibitTreeSelection = true;
         fViewerFilterMap.clear();
         if (filters == null) {
@@ -923,7 +1125,8 @@ public class TimeGraphCombo extends Composite {
     }
 
     /**
-     * Refreshes this time graph completely with information freshly obtained from its model.
+     * Refreshes this time graph completely with information freshly obtained
+     * from its model.
      */
     public void refresh() {
         fInhibitTreeSelection = true;
@@ -942,7 +1145,8 @@ public class TimeGraphCombo extends Composite {
     /**
      * Adds a listener for selection changes in this time graph combo.
      *
-     * @param listener a selection listener
+     * @param listener
+     *            a selection listener
      */
     public void addSelectionListener(ITimeGraphSelectionListener listener) {
         SelectionListenerWrapper listenerWrapper = new SelectionListenerWrapper(listener);
@@ -954,7 +1158,8 @@ public class TimeGraphCombo extends Composite {
     /**
      * Removes the given selection listener from this time graph combo.
      *
-     * @param listener a selection changed listener
+     * @param listener
+     *            a selection changed listener
      */
     public void removeSelectionListener(ITimeGraphSelectionListener listener) {
         SelectionListenerWrapper listenerWrapper = fSelectionListenerMap.remove(listener);
@@ -965,11 +1170,36 @@ public class TimeGraphCombo extends Composite {
     /**
      * Sets the current selection for this time graph combo.
      *
-     * @param selection the new selection
+     * @param selection
+     *            the new selection
      */
     public void setSelection(ITimeGraphEntry selection) {
         fTimeGraphViewer.setSelection(selection);
-        fInhibitTreeSelection = true; // block the tree selection changed listener
+        setSelectionInTree(selection);
+    }
+
+    /**
+     * Sets the current selection for this time graph combo and reveal it if
+     * needed.
+     *
+     * @param selection
+     *            The new selection
+     * @since 2.0
+     */
+    public void selectAndReveal(@NonNull ITimeGraphEntry selection) {
+        fTimeGraphViewer.selectAndReveal(selection);
+        setSelectionInTree(selection);
+    }
+
+    /**
+     * Select the entry in the tree structure
+     *
+     * @param selection
+     *            The new selection
+     */
+    private void setSelectionInTree(ITimeGraphEntry selection) {
+        fInhibitTreeSelection = true; // block the tree selection changed
+                                      // listener
         if (selection != null) {
             StructuredSelection structuredSelection = new StructuredSelection(selection);
             fTreeViewer.setSelection(structuredSelection);
@@ -1014,6 +1244,18 @@ public class TimeGraphCombo extends Composite {
         return fTimeGraphViewer.getAutoExpandLevel();
     }
 
+    /**
+     * Get the expanded state of an entry.
+     *
+     * @param entry
+     *            The entry
+     * @return true if the entry is expanded, false if collapsed
+     * @since 2.0
+     */
+    public boolean getExpandedState(ITimeGraphEntry entry) {
+        return fTimeGraphViewer.getExpandedState(entry);
+    }
+
     /**
      * Set the expanded state of an entry
      *
@@ -1077,12 +1319,13 @@ public class TimeGraphCombo extends Composite {
         }
     }
 
-    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.
+         * 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;
             }
 
@@ -1110,12 +1353,14 @@ public class TimeGraphCombo extends Composite {
                 tree.addPaintListener(paintListener);
             }
         } else {
-            fLinuxItemHeight = -1; // Not Linux, don't perform os.name check anymore
+            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);
@@ -1125,30 +1370,81 @@ public class TimeGraphCombo extends Composite {
         }
         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();
+            }
+        });
+        /* 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) {
-                ITimeGraphEntry entry = (ITimeGraphEntry) item.getData();
-                fTimeGraphViewer.getTimeGraphControl().setItemHeight(entry, 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;
     }
 
     /**
This page took 0.040457 seconds and 5 git commands to generate.