tmf: Fix IllegalArgumentException in TimeGraphCombo
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / TimeGraphCombo.java
index 0ff8946f41616cf7b079d240cef7d4589a2111d1..42d45a01a110fe36828a6ee73dfc784431725ec7 100644 (file)
@@ -41,6 +41,8 @@ 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.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseTrackAdapter;
 import org.eclipse.swt.events.MouseWheelListener;
@@ -48,6 +50,8 @@ 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.Image;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
@@ -71,6 +75,8 @@ 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.TimeGraphColorScheme;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
 
 import com.google.common.collect.Iterables;
 
@@ -105,7 +111,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
@@ -131,10 +137,50 @@ public class TimeGraphCombo extends Composite {
     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();
+                }
+            };
+        }
+    }
+
     /**
      * The TreeContentProviderWrapper is used to insert filler items after
      * the elements of the tree's real content provider.
@@ -340,33 +386,42 @@ public class TimeGraphCombo extends Composite {
 
         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);
+        fTimeGraphViewer = new TimeGraphViewerExtension(fSashForm, SWT.NONE, tree);
 
-        // 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--;
+        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;
+
+                @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
@@ -382,6 +437,15 @@ public class TimeGraphCombo extends Composite {
             }
         });
 
+        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
@@ -491,13 +555,21 @@ public class TimeGraphCombo extends Composite {
                     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)) {
+                    verticalZoom(true);
+                } else if (event.character == '-' && ((event.stateMask & SWT.CTRL) != 0)) {
+                    verticalZoom(false);
+                } else if (event.character == '0' && ((event.stateMask & SWT.CTRL) != 0)) {
+                    resetVerticalZoom();
+                } else {
+                    return;
                 }
                 if (fTimeGraphViewer.getSelectionIndex() >= 0) {
                     fTreeViewer.setSelection(new StructuredSelection(fTimeGraphViewer.getSelection()));
@@ -597,7 +669,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);
 
@@ -628,6 +700,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()));
     }
@@ -695,6 +801,12 @@ public class TimeGraphCombo extends Composite {
         super.redraw();
     }
 
+    @Override
+    public void update() {
+        fTimeGraphViewer.getControl().update();
+        super.update();
+    }
+
     // ------------------------------------------------------------------------
     // Operations
     // ------------------------------------------------------------------------
@@ -811,10 +923,12 @@ public class TimeGraphCombo extends Composite {
             listenerWrapper.selection = null;
         }
         fInhibitTreeSelection = false;
-        fTreeViewer.getTree().getVerticalBar().setEnabled(false);
-        fTreeViewer.getTree().getVerticalBar().setVisible(false);
+        if (fScrollBarsInTreeWorkaround) {
+            fTreeViewer.getTree().getVerticalBar().setEnabled(false);
+            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
@@ -846,7 +960,7 @@ public class TimeGraphCombo extends Composite {
     /**
      * @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);
@@ -859,7 +973,7 @@ public class TimeGraphCombo extends Composite {
     /**
      * @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);
@@ -875,7 +989,7 @@ public class TimeGraphCombo extends Composite {
      * @return an array of viewer filters
      * @since 2.0
      */
-    public ViewerFilter[] getFilters() {
+    public @NonNull ViewerFilter[] getFilters() {
         return fTimeGraphViewer.getFilters();
     }
 
@@ -887,7 +1001,7 @@ public class TimeGraphCombo extends Composite {
      *            an array of viewer filters, or null
      * @since 2.0
      */
-    public void setFilters(ViewerFilter[] filters) {
+    public void setFilters(@NonNull ViewerFilter[] filters) {
         fInhibitTreeSelection = true;
         fViewerFilterMap.clear();
         if (filters == null) {
@@ -1060,12 +1174,12 @@ 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.
          */
         if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
-            if (fLinuxItemHeight != 0) {
+            if (fLinuxItemHeight != 0 && !force) {
                 return fLinuxItemHeight;
             }
 
@@ -1109,29 +1223,92 @@ 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();
+                /*
+                 * 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) {
-                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;
     }
 
     /**
@@ -1183,8 +1360,10 @@ public class TimeGraphCombo extends Composite {
         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();
This page took 0.036738 seconds and 5 git commands to generate.