lttng: Add Next/Previous TID event action in CFV
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / widgets / TimeGraphControl.java
index 7b3c95d104b1afa7e4868fb43ee5d4f469f2c201..2f5c35895a97bfd14661edb75131ff0076479f36 100644 (file)
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * Copyright (c) 2007, 2015 Intel Corporation and others
+ * Copyright (c) 2007, 2016 Intel Corporation and others
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
 
 package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
 
+import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
 
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jface.action.IStatusLineManager;
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.resource.LocalResourceManager;
@@ -35,6 +41,7 @@ import org.eclipse.jface.viewers.AbstractTreeViewer;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.ViewerFilter;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
@@ -56,6 +63,8 @@ import org.eclipse.swt.events.TraverseListener;
 import org.eclipse.swt.events.TypedEvent;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Cursor;
+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;
@@ -64,6 +73,7 @@ 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.Menu;
 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
@@ -119,6 +129,11 @@ public class TimeGraphControl extends TimeGraphBaseControl
 
     private static final int MAX_LABEL_LENGTH = 256;
 
+    private static final int PPI = 72; // points per inch
+    private static final int DPI = Display.getDefault().getDPI().y;
+
+    private static final int VERTICAL_ZOOM_DELAY = 400;
+
     /** Resource manager */
     private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
 
@@ -132,6 +147,8 @@ public class TimeGraphControl extends TimeGraphBaseControl
     private boolean fIsInFocus = false;
     private boolean fMouseOverSplitLine = false;
     private int fGlobalItemHeight = CUSTOM_ITEM_HEIGHT;
+    private int fHeightAdjustment = 0;
+    private Map<Integer, Font> fFonts = new HashMap<>();
     private boolean fBlendSubPixelEvents = false;
     private int fMinimumItemWidth = 0;
     private int fTopIndex = 0;
@@ -140,13 +157,13 @@ public class TimeGraphControl extends TimeGraphBaseControl
     private int fDragButton;
     private int fDragX0 = 0;
     private int fDragX = 0;
+    private boolean fHasNamespaceFocus = false;
     private long fDragTime0 = 0; // used to preserve accuracy of modified selection
     private int fIdealNameSpace = 0;
     private long fTime0bak;
     private long fTime1bak;
     private ITimeGraphPresentationProvider fTimeGraphProvider = null;
     private ItemData fItemData = null;
-    private List<IMarkerEvent> fBookmarks = null;
     private List<IMarkerEvent> fMarkers = null;
     private boolean fMarkersVisible = true;
     private List<SelectionListener> fSelectionListeners;
@@ -159,13 +176,14 @@ public class TimeGraphControl extends TimeGraphBaseControl
     private final Cursor fResizeCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_IBEAM);
     private final Cursor fWaitCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_WAIT);
     private final Cursor fZoomCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_SIZEWE);
-    private final List<ViewerFilter> fFilters = new ArrayList<>();
+    private final List<@NonNull ViewerFilter> fFilters = new ArrayList<>();
     private MenuDetectEvent fPendingMenuDetectEvent = null;
     private boolean fGridLinesVisible = true;
     private Color fGridLineColor = Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
     private boolean fHideArrows = false;
     private int fAutoExpandLevel = ALL_LEVELS;
-
+    private Entry<ITimeGraphEntry, Integer> fVerticalZoomAlignEntry = null;
+    private long fVerticalZoomAlignTime = 0;
     private int fBorderWidth = 0;
     private int fHeaderHeight = 0;
 
@@ -192,12 +210,12 @@ public class TimeGraphControl extends TimeGraphBaseControl
         addKeyListener(this);
         addMenuDetectListener(this);
         addListener(SWT.MouseWheel, this);
-    }
-
-    @Override
-    public void dispose() {
-        super.dispose();
-        fResourceManager.dispose();
+        addDisposeListener((e) -> {
+            fResourceManager.dispose();
+            for (Font font : fFonts.values()) {
+                font.dispose();
+            }
+        });
     }
 
     /**
@@ -226,6 +244,15 @@ public class TimeGraphControl extends TimeGraphBaseControl
         return fTimeGraphProvider;
     }
 
+    /**
+     * Gets the time data provider used by this viewer.
+     *
+     * @return The time data provider, or <code>null</code> if not set
+     */
+    public ITimeDataProvider getTimeDataProvider() {
+        return fTimeProvider;
+    }
+
     /**
      * Gets the color map used by this timegraph viewer.
      *
@@ -308,6 +335,12 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 listener.widgetSelected(null);
             }
         }
+
+        if (null != fSelectionChangedListeners) {
+            for (ISelectionChangedListener listener : fSelectionChangedListeners) {
+                listener.selectionChanged(new SelectionChangedEvent(this, getSelection()));
+            }
+        }
     }
 
     /**
@@ -472,6 +505,36 @@ public class TimeGraphControl extends TimeGraphBaseControl
         redraw();
     }
 
+    /**
+     * Set the top index so that the requested element is at the specified
+     * position.
+     *
+     * @param entry
+     *            the time graph entry to be positioned
+     * @param y
+     *            the requested y-coordinate
+     * @since 2.0
+     */
+    public void setElementPosition(ITimeGraphEntry entry, int y) {
+        Item item = fItemData.fItemMap.get(entry);
+        if (item == null || item.fExpandedIndex == -1) {
+            return;
+        }
+        int index = item.fExpandedIndex;
+        Rectangle itemRect = getItemRect(getClientArea(), index);
+        int delta = itemRect.y + itemRect.height - y;
+        int topIndex = getItemIndexAtY(delta);
+        if (topIndex != -1) {
+            setTopIndex(topIndex);
+        } else {
+            if (delta < 0) {
+                setTopIndex(0);
+            } else {
+                setTopIndex(getExpandedElementCount());
+            }
+        }
+    }
+
     /**
      * Sets the auto-expand level to be used for new entries discovered when
      * calling {@link #refreshData()} or {@link #refreshData(ITimeGraphEntry[])}
@@ -532,6 +595,108 @@ public class TimeGraphControl extends TimeGraphBaseControl
         }
     }
 
+    /**
+     * Set the expanded state of a given entry to certain relative level.
+     * It will call fireTreeEvent() for each changed entry. At the end
+     * it will call redraw().
+     *
+     * @param entry
+     *            The entry
+     * @param level
+     *            level to expand to or negative for all levels
+     * @param expanded
+     *            True if expanded, false if collapsed
+     */
+    private void setExpandedState(ITimeGraphEntry entry, int level, boolean expanded) {
+        setExpandedStateInt(entry, level, expanded);
+        redraw();
+    }
+
+    /**
+     * Set the expanded state of a given entry and its children to the first
+     * level that has one collapsed entry.
+     *
+     * @param entry
+     *            The entry
+     */
+    private void setExpandedStateLevel(ITimeGraphEntry entry) {
+        int level = findExpandedLevel(entry);
+        if (level >= 0) {
+            setExpandedStateInt(entry, level, true);
+            redraw();
+        }
+    }
+
+    /*
+     * Inner class for finding relative level with at least one
+     * collapsed entry.
+     */
+    private class SearchNode {
+        SearchNode(ITimeGraphEntry e, int l) {
+            entry = e;
+            level = l;
+        }
+        ITimeGraphEntry entry;
+        int level;
+    }
+
+    /**
+     * Finds the relative level with at least one collapsed entry.
+     *
+     * @param entry
+     *            the start entry
+     * @return the found level or -1 if all levels are already expanded.
+     */
+    private int findExpandedLevel(ITimeGraphEntry entry) {
+        Queue<SearchNode> queue = new LinkedList<>();
+        SearchNode root = new SearchNode(entry, 0);
+        SearchNode node = root;
+        queue.add(root);
+
+        while (!queue.isEmpty()) {
+            node = queue.remove();
+            if (node.entry.hasChildren() && !getExpandedState(node.entry)) {
+                return node.level;
+            }
+            for (ITimeGraphEntry e : node.entry.getChildren()) {
+                if (e.hasChildren()) {
+                    SearchNode n = new SearchNode(e, node.level + 1);
+                    queue.add(n);
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Set the expanded state of a given entry to certain relative level.
+     * It will call fireTreeEvent() for each changed entry. No redraw is done.
+     *
+     * @param entry
+     *            The entry
+     * @param level
+     *            level to expand to or negative for all levels
+     * @param expanded
+     *            True if expanded, false if collapsed
+     */
+    private void setExpandedStateInt(ITimeGraphEntry entry, int aLevel, boolean expanded) {
+        int level = aLevel;
+        if ((level > 0) || (level < 0)) {
+            level--;
+            if (entry.hasChildren()) {
+                for (ITimeGraphEntry e : entry.getChildren()) {
+                    setExpandedStateInt(e, level, expanded);
+                }
+            }
+        }
+        Item item = fItemData.findItem(entry);
+        if (item != null && item.fExpanded != expanded) {
+            item.fExpanded = expanded;
+            fItemData.updateExpandedItems();
+            fireTreeEvent(item.fEntry, item.fExpanded);
+        }
+    }
+
     /**
      * Collapses all nodes of the viewer's tree, starting with the root.
      */
@@ -669,6 +834,14 @@ public class TimeGraphControl extends TimeGraphBaseControl
         }
     }
 
+    @Override
+    public boolean setFocus() {
+        if ((fTimeProvider != null) && fTimeProvider.getNameSpace() > 0) {
+            fHasNamespaceFocus = true;
+        }
+        return super.setFocus();
+    }
+
     @Override
     public ISelection getSelection() {
         TimeGraphSelection sel = new TimeGraphSelection();
@@ -676,10 +849,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
         if (null != trace && null != fTimeProvider) {
             long selectedTime = fTimeProvider.getSelectionBegin();
             ITimeEvent event = Utils.findEvent(trace, selectedTime, 0);
+            sel.add(trace);
             if (event != null) {
                 sel.add(event);
-            } else {
-                sel.add(trace);
             }
         }
         return sel;
@@ -789,14 +961,14 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 nextTime = nextEvent.getTime() + nextEvent.getDuration();
             }
             if (extend) {
-                fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), nextTime);
+                fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), nextTime, true);
             } else {
                 fTimeProvider.setSelectedTimeNotify(nextTime, true);
             }
             fireSelectionChanged();
         } else if (n == 1) {
             if (extend) {
-                fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), endTime);
+                fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), endTime, true);
             } else {
                 fTimeProvider.setSelectedTimeNotify(endTime, true);
             }
@@ -957,6 +1129,34 @@ public class TimeGraphControl extends TimeGraphBaseControl
         fTimeProvider.setStartFinishTimeNotify(time0, time1);
     }
 
+    /**
+     * Zoom vertically.
+     *
+     * @param zoomIn
+     *            true to zoom in, false to zoom out
+     * @since 2.0
+     */
+    public void verticalZoom(boolean zoomIn) {
+        if (zoomIn) {
+            fHeightAdjustment++;
+        } else {
+            fHeightAdjustment--;
+        }
+        fItemData.refreshData();
+        redraw();
+    }
+
+    /**
+     * Reset the vertical zoom to default.
+     *
+     * @since 2.0
+     */
+    public void resetVerticalZoom() {
+        fHeightAdjustment = 0;
+        fItemData.refreshData();
+        redraw();
+    }
+
     /**
      * Set the grid lines visibility. The default is true.
      *
@@ -999,18 +1199,6 @@ public class TimeGraphControl extends TimeGraphBaseControl
         return fGridLineColor;
     }
 
-    /**
-     * Set the bookmarks list.
-     *
-     * @param bookmarks
-     *            The bookmarks list, or null
-     * @since 2.0
-     */
-    public void setBookmarks(List<IMarkerEvent> bookmarks) {
-        fBookmarks = bookmarks;
-        fTimeGraphScale.setBookmarks(bookmarks);
-    }
-
     /**
      * Set the markers list.
      *
@@ -1020,7 +1208,6 @@ public class TimeGraphControl extends TimeGraphBaseControl
      */
     public void setMarkers(List<IMarkerEvent> markers) {
         fMarkers = markers;
-        fTimeGraphScale.setMarkers(markers);
     }
 
     /**
@@ -1042,7 +1229,6 @@ public class TimeGraphControl extends TimeGraphBaseControl
      */
     public void setMarkersVisible(boolean visible) {
         fMarkersVisible = visible;
-        fTimeGraphScale.setMarkersVisible(visible);
     }
 
     /**
@@ -1082,7 +1268,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 selectItem(link.getDestinationEntry(), false);
                 if (link.getDuration() != 0) {
                     if (extend) {
-                        fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime() + link.getDuration());
+                        fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime() + link.getDuration(), true);
                     } else {
                         fTimeProvider.setSelectedTimeNotify(link.getTime() + link.getDuration(), true);
                     }
@@ -1115,7 +1301,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 selectItem(link.getEntry(), false);
                 if (link.getDuration() != 0) {
                     if (extend) {
-                        fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime());
+                        fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime(), true);
                     } else {
                         fTimeProvider.setSelectedTimeNotify(link.getTime(), true);
                     }
@@ -1184,14 +1370,20 @@ public class TimeGraphControl extends TimeGraphBaseControl
      * @return the index of the item at the given location, of -1 if none.
      */
     protected int getItemIndexAtY(int y) {
-        if (y < 0) {
-            return -1;
-        }
         int ySum = 0;
-        for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
-            ySum += fItemData.fExpandedItems[idx].fItemHeight;
-            if (y < ySum) {
-                return idx;
+        if (y < 0) {
+            for (int idx = fTopIndex - 1; idx >= 0; idx--) {
+                ySum -= fItemData.fExpandedItems[idx].fItemHeight;
+                if (y >= ySum) {
+                    return idx;
+                }
+            }
+        } else {
+            for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
+                ySum += fItemData.fExpandedItems[idx].fItemHeight;
+                if (y < ySum) {
+                    return idx;
+                }
             }
         }
         return -1;
@@ -1212,8 +1404,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
      *            a point in the widget
      * @return the {@link ITimeGraphEntry} at this point, or <code>null</code>
      *         if none.
+     * @since 2.0
      */
-    protected ITimeGraphEntry getEntry(Point pt) {
+    public ITimeGraphEntry getEntry(Point pt) {
         int idx = getItemIndexAtY(pt.y);
         return idx >= 0 ? fItemData.fExpandedItems[idx].fEntry : null;
     }
@@ -1383,6 +1576,21 @@ public class TimeGraphControl extends TimeGraphBaseControl
         return elements.toArray(new ITimeGraphEntry[0]);
     }
 
+    /**
+     * Get the expanded (visible) element at the specified index.
+     *
+     * @param index
+     *            the element index
+     * @return The expanded (visible) element or null if out of range
+     * @since 2.0
+     */
+    public ITimeGraphEntry getExpandedElement(int index) {
+        if (index < 0 || index >= fItemData.fExpandedItems.length) {
+            return null;
+        }
+        return fItemData.fExpandedItems[index].fEntry;
+    }
+
     Rectangle getNameRect(Rectangle bounds, int idx, int nameWidth) {
         Rectangle rect = getItemRect(bounds, idx);
         rect.width = nameWidth;
@@ -1432,18 +1640,12 @@ public class TimeGraphControl extends TimeGraphBaseControl
         // draw the background markers
         drawMarkers(bounds, fTimeProvider, fMarkers, false, nameSpace, gc);
 
-        // draw the background bookmarks
-        drawMarkers(bounds, fTimeProvider, fBookmarks, false, nameSpace, gc);
-
         // draw the items
         drawItems(bounds, fTimeProvider, fItemData.fExpandedItems, fTopIndex, nameSpace, gc);
 
         // draw the foreground markers
         drawMarkers(bounds, fTimeProvider, fMarkers, true, nameSpace, gc);
 
-        // draw the foreground bookmarks
-        drawMarkers(bounds, fTimeProvider, fBookmarks, true, nameSpace, gc);
-
         // draw the links (arrows)
         drawLinks(bounds, fTimeProvider, fItemData.fLinks, nameSpace, gc);
 
@@ -1657,14 +1859,15 @@ public class TimeGraphControl extends TimeGraphBaseControl
         rect.x = Math.max(nameSpace, Math.min(bounds.width, x0));
         rect.width = Math.max(1, Math.min(bounds.width, x1) - rect.x);
 
-        gc.setBackground(marker.getColor());
-        gc.setAlpha(marker.getColor().getAlpha());
+        Color color = getColorScheme().getColor(marker.getColor());
+        gc.setBackground(color);
+        gc.setAlpha(color.getAlpha());
         gc.fillRectangle(rect);
         gc.setAlpha(255);
         String label = marker.getLabel();
         if (label != null && marker.getEntry() != null) {
             label = label.substring(0, Math.min(label.indexOf('\n') != -1 ? label.indexOf('\n') : label.length(), MAX_LABEL_LENGTH));
-            gc.setForeground(marker.getColor());
+            gc.setForeground(color);
             Utils.drawText(gc, label, rect.x - gc.textExtent(label).x, rect.y, true);
         }
     }
@@ -1733,14 +1936,14 @@ public class TimeGraphControl extends TimeGraphBaseControl
         if (item.fEntry.hasTimeEvents()) {
             gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
             fillSpace(rect, gc, selected);
-            /*
-             * State rectangle is smaller than item bounds. Use a margin height
-             * of 3 pixels, keep at least 3 pixels for the state, but not more
-             * than the item height. Favor the top margin for the remainder.
-             */
-            int height = Math.min(rect.height, Math.max(3, rect.height - 6));
-            int margin = (rect.height - height + 1) / 2;
-            Rectangle stateRect = new Rectangle(rect.x, rect.y + margin, rect.width, height);
+
+            int margins = getMarginForHeight(rect.height);
+            int height = rect.height - margins;
+            int topMargin = (margins + 1) / 2;
+            Rectangle stateRect = new Rectangle(rect.x, rect.y + topMargin, rect.width, height);
+
+            /* Set the font for this item */
+            setFontForHeight(height, gc);
 
             long maxDuration = (timeProvider.getTimeSpace() == 0) ? Long.MAX_VALUE : 1 * (time1 - time0) / timeProvider.getTimeSpace();
             Iterator<ITimeEvent> iterator = entry.getTimeEventsIterator(time0, time1, maxDuration);
@@ -1935,6 +2138,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
             return;
         }
 
+        int height = bounds.height - getMarginForHeight(bounds.height);
+        setFontForHeight(height, gc);
+
         int leftMargin = MARGIN + item.fLevel * EXPAND_SIZE;
         if (item.fHasChildren) {
             gc.setForeground(getColorScheme().getFgColorGroup(false, false));
@@ -2102,6 +2308,34 @@ public class TimeGraphControl extends TimeGraphBaseControl
         gc.drawLine(rect.x, midy, rect.x + rect.width, midy);
     }
 
+    private static int getMarginForHeight(int height) {
+        /*
+         * State rectangle is smaller than the item bounds when height is > 4.
+         * Don't use any margin if the height is below or equal that threshold.
+         * Use a maximum of 6 pixels for both margins, otherwise try to use 13
+         * pixels for the state height, but with a minimum margin of 1.
+         */
+        final int MARGIN_THRESHOLD = 4;
+        final int PREFERRED_HEIGHT = 13;
+        final int MIN_MARGIN = 1;
+        final int MAX_MARGIN = 6;
+        return height <= MARGIN_THRESHOLD ? 0 :
+            Math.max(Math.min(height - PREFERRED_HEIGHT, MAX_MARGIN), MIN_MARGIN);
+    }
+
+    private void setFontForHeight(int pixels, GC gc) {
+        /* convert font height from pixels to points */
+        int height = Math.max(pixels * PPI / DPI, 1);
+        Font font = fFonts.get(height);
+        if (font == null) {
+            FontData fontData = gc.getFont().getFontData()[0];
+            fontData.setHeight(height);
+            font = new Font(gc.getDevice(), fontData);
+            fFonts.put(height, font);
+        }
+        gc.setFont(font);
+    }
+
     @Override
     public void keyTraversed(TraverseEvent e) {
         if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) {
@@ -2169,6 +2403,47 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 }
             }
             idx = -1;
+        } else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) != 0)) {
+            fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
+            verticalZoom(true);
+            if (fVerticalZoomAlignEntry != null) {
+                setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
+            }
+        } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) != 0)) {
+            fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
+            verticalZoom(false);
+            if (fVerticalZoomAlignEntry != null) {
+                setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
+            }
+        } else if (e.character == '0' && ((e.stateMask & SWT.CTRL) != 0)) {
+            fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
+            resetVerticalZoom();
+            if (fVerticalZoomAlignEntry != null) {
+                setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
+            }
+        } else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) == 0)) {
+            if (fHasNamespaceFocus) {
+                ITimeGraphEntry entry = getSelectedTrace();
+                setExpandedState(entry, 0, true);
+            } else {
+                zoomIn();
+            }
+        } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) == 0)) {
+            if (fHasNamespaceFocus) {
+                ITimeGraphEntry entry = getSelectedTrace();
+                if ((entry != null) && entry.hasChildren()) {
+                    setExpandedState(entry, -1, false);
+                }
+            } else {
+                zoomOut();
+            }
+        } else if ((e.character == '*') && ((e.stateMask & SWT.CTRL) == 0)) {
+            if (fHasNamespaceFocus) {
+                ITimeGraphEntry entry = getSelectedTrace();
+                if ((entry != null) && entry.hasChildren()) {
+                    setExpandedStateLevel(entry);
+                }
+            }
         }
         if (idx >= 0) {
             selectItem(idx, false);
@@ -2255,6 +2530,15 @@ public class TimeGraphControl extends TimeGraphBaseControl
         }
     }
 
+    /**
+     * Update the status line following a change of selection.
+     *
+     * @since 2.0
+     */
+    public void updateStatusLine() {
+        updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
+    }
+
     private void updateStatusLine(int x) {
         // use the time provider of the time graph scale for the status line
         ITimeDataProvider tdp = fTimeGraphScale.getTimeProvider();
@@ -2361,6 +2645,12 @@ public class TimeGraphControl extends TimeGraphBaseControl
             }
             fMouseOverSplitLine = mouseOverSplitLine;
         }
+
+        if (e.x >= fTimeProvider.getNameSpace()) {
+            fHasNamespaceFocus = false;
+        } else {
+            fHasNamespaceFocus = true;
+        }
         updateCursor(e.x, e.stateMask);
         updateStatusLine(e.x);
     }
@@ -2482,15 +2772,21 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 updateCursor(e.x, e.stateMask);
             }
         } else if (3 == e.button) {
-            setCapture(true);
-            fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), getSize().x - RIGHT_MARGIN);
-            fDragX0 = fDragX;
-            fDragTime0 = getTimeAtX(fDragX0);
-            fDragState = DRAG_ZOOM;
-            fDragButton = e.button;
-            redraw();
-            updateCursor(e.x, e.stateMask);
-            fTimeGraphScale.setDragRange(fDragX0, fDragX);
+            if (e.x >= fTimeProvider.getNameSpace()) {
+                setCapture(true);
+                fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), getSize().x - RIGHT_MARGIN);
+                fDragX0 = fDragX;
+                fDragTime0 = getTimeAtX(fDragX0);
+                fDragState = DRAG_ZOOM;
+                fDragButton = e.button;
+                redraw();
+                updateCursor(e.x, e.stateMask);
+                fTimeGraphScale.setDragRange(fDragX0, fDragX);
+            } else {
+              idx = getItemIndexAtY(e.y);
+              selectItem(idx, false);
+              fireSelectionChanged();
+            }
         }
     }
 
@@ -2516,7 +2812,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 } else {
                     long time0 = fDragBeginMarker ? getTimeAtX(fDragX0) : fDragTime0;
                     long time1 = fDragBeginMarker ? fDragTime0 : getTimeAtX(fDragX);
-                    fTimeProvider.setSelectionRangeNotify(time0, time1);
+                    fTimeProvider.setSelectionRangeNotify(time0, time1, false);
                 }
                 fDragState = DRAG_NONE;
                 redraw();
@@ -2561,57 +2857,131 @@ public class TimeGraphControl extends TimeGraphBaseControl
 
     @Override
     public void mouseScrolled(MouseEvent e) {
-        if (fDragState != DRAG_NONE) {
+        if (fDragState != DRAG_NONE || e.count == 0) {
             return;
         }
-        boolean zoomScroll = false;
+
+        /*
+         * On some platforms the mouse scroll event is sent to the
+         * control that has focus even if it is not under the cursor.
+         * Handle the event only if over the time graph control.
+         */
+        Point size = getSize();
+        Rectangle bounds = new Rectangle(0, 0, size.x, size.y);
+        if (!bounds.contains(e.x, e.y)) {
+            return;
+        }
+
+        boolean horizontalZoom = false;
         boolean horizontalScroll = false;
-        Point p = getParent().toControl(getDisplay().getCursorLocation());
-        Point parentSize = getParent().getSize();
-        if (p.x >= 0 && p.x < parentSize.x && p.y >= 0 && p.y < parentSize.y) {
-            // over the parent control
-            if (e.x > getSize().x) {
-                // over the vertical scroll bar
-                zoomScroll = false;
-            } else if (e.y < 0) {
-                // over the time scale
-                zoomScroll = true;
-            } else if (e.y >= getSize().y) {
-                // over the horizontal scroll bar
-                if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
-                    zoomScroll = true;
-                } else {
-                    horizontalScroll = true;
-                }
+        boolean verticalZoom = false;
+        boolean verticalScroll = false;
+
+        // over the time graph control
+        if ((e.stateMask & SWT.MODIFIER_MASK) == (SWT.SHIFT | SWT.CTRL)) {
+            verticalZoom = true;
+        } else if (e.x < fTimeProvider.getNameSpace()) {
+            // over the name space
+            verticalScroll = true;
+        } else {
+            // over the state area
+            if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
+                // over the state area, CTRL pressed
+                horizontalZoom = true;
+            } else if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
+                // over the state area, SHIFT pressed
+                horizontalScroll = true;
             } else {
-                if (e.x < fTimeProvider.getNameSpace()) {
-                    // over the name space
-                    zoomScroll = false;
-                } else {
-                    // over the state area
-                    if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
-                        // over the state area, CTRL pressed
-                        zoomScroll = true;
-                    } else {
-                        // over the state area, CTRL not pressed
-                        zoomScroll = false;
-                    }
-                }
+                // over the state area, no modifier pressed
+                verticalScroll = true;
             }
         }
-        if (zoomScroll && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
-            if (e.count > 0) {
-                zoom(true);
-            } else if (e.count < 0) {
-                zoom(false);
+        if (verticalZoom) {
+            fVerticalZoomAlignEntry = getVerticalZoomAlignCursor(e.y);
+            verticalZoom(e.count > 0);
+            if (fVerticalZoomAlignEntry != null) {
+                setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
             }
+        } else if (horizontalZoom && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
+            zoom(e.count > 0);
         } else if (horizontalScroll) {
             horizontalScroll(e.count > 0);
-        } else {
+        } else if (verticalScroll){
             setTopIndex(getTopIndex() - e.count);
         }
     }
 
+    /**
+     * Get the vertical zoom alignment entry and position based on the current
+     * selection. If there is no selection or if the selection is not visible,
+     * return an alignment entry with a null time graph entry.
+     *
+     * @return a map entry where the key is the selection's time graph entry and
+     *         the value is the center y-coordinate of that entry, or null
+     */
+    private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignSelection() {
+        Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing();
+        if (alignEntry != null) {
+            return alignEntry;
+        }
+        int index = getSelectedIndex();
+        if (index == -1 || index >= getExpandedElementCount()) {
+            return new SimpleEntry<>(null, 0);
+        }
+        Rectangle bounds = getClientArea();
+        Rectangle itemRect = getItemRect(bounds, index);
+        if (itemRect.y < bounds.y || itemRect.y > bounds.y + bounds.height) {
+            /* selection is not visible */
+            return new SimpleEntry<>(null, 0);
+        }
+        ITimeGraphEntry entry = getExpandedElement(index);
+        int y = itemRect.y + itemRect.height / 2;
+        return new SimpleEntry<>(entry, y);
+    }
+
+    /**
+     * Get the vertical zoom alignment entry and position at the specified
+     * cursor position.
+     *
+     * @param y
+     *            the cursor y-coordinate
+     * @return a map entry where the key is the time graph entry under the
+     *         cursor and the value is the cursor y-coordinate
+     */
+    private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignCursor(int y) {
+        Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing();
+        if (alignEntry != null) {
+            return alignEntry;
+        }
+        int index = getItemIndexAtY(y);
+        if (index == -1) {
+            index = getExpandedElementCount() - 1;
+        }
+        ITimeGraphEntry entry = getExpandedElement(index);
+        return new SimpleEntry<>(entry, y);
+    }
+
+    /**
+     * Get the vertical zoom alignment entry and position if there is an ongoing
+     * one and we are within the vertical zoom delay, or otherwise return null.
+     *
+     * @return a map entry where the key is a time graph entry and the value is
+     *         a y-coordinate, or null
+     */
+    private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignOngoing() {
+        long currentTimeMillis = System.currentTimeMillis();
+        if (currentTimeMillis < fVerticalZoomAlignTime + VERTICAL_ZOOM_DELAY) {
+            /*
+             * If the vertical zoom is triggered repeatedly in a short amount of
+             * time, use the initial event's entry and position.
+             */
+            fVerticalZoomAlignTime = currentTimeMillis;
+            return fVerticalZoomAlignEntry;
+        }
+        fVerticalZoomAlignTime = currentTimeMillis;
+        return null;
+    }
+
     @Override
     public void handleEvent(Event event) {
         if (event.type == SWT.MouseWheel) {
@@ -2751,7 +3121,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
     /**
      * @param filter The filter object to be attached to the view
      */
-    public void addFilter(ViewerFilter filter) {
+    public void addFilter(@NonNull ViewerFilter filter) {
         if (!fFilters.contains(filter)) {
             fFilters.add(filter);
         }
@@ -2760,7 +3130,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
     /**
      * @param filter The filter object to be attached to the view
      */
-    public void removeFilter(ViewerFilter filter) {
+    public void removeFilter(@NonNull ViewerFilter filter) {
         fFilters.remove(filter);
     }
 
@@ -2768,9 +3138,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
      * Returns this control's filters.
      *
      * @return an array of viewer filters
-     * @since 2.0
+     * @since 1.2
      */
-    public ViewerFilter[] getFilters() {
+    public @NonNull ViewerFilter[] getFilters() {
         return Iterables.toArray(fFilters, ViewerFilter.class);
     }
 
@@ -2779,9 +3149,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
      *
      * @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) {
         fFilters.clear();
         if (filters != null) {
             fFilters.addAll(Arrays.asList(filters));
@@ -2859,6 +3229,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
             } else {
                 item.fItemHeight = fGlobalItemHeight;
             }
+            item.fItemHeight = Math.max(1, item.fItemHeight + fHeightAdjustment);
             itemMap.put(entry, item);
             if (entry.hasChildren()) {
                 Item oldItem = fItemMap.get(entry);
@@ -2963,6 +3334,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
         if (null == fTimeProvider) {
             return;
         }
+        Point p = toControl(e.x, e.y);
         if (e.detail == SWT.MENU_MOUSE) {
             if (fPendingMenuDetectEvent == null) {
                 /* Feature in Linux. The MenuDetectEvent is received before mouseDown.
@@ -2970,10 +3342,16 @@ public class TimeGraphControl extends TimeGraphBaseControl
                  * This allows for the method to detect if mouse is used to drag zoom.
                  */
                 fPendingMenuDetectEvent = e;
+                /*
+                 *  Prevent the platform to show the menu when returning. The
+                 *  menu will be shown (see below) when this method is called
+                 *  again during mouseup().
+                 */
+                e.doit = false;
                 return;
             }
             fPendingMenuDetectEvent = null;
-            if (fDragState != DRAG_ZOOM || fDragX != fDragX0) {
+            if ((p.x >= fTimeProvider.getNameSpace()) && (fDragState != DRAG_ZOOM || fDragX != fDragX0)) {
                 return;
             }
         } else {
@@ -2981,21 +3359,34 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 return;
             }
         }
-        Point p = toControl(e.x, e.y);
         int idx = getItemIndexAtY(p.y);
         if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
+            e.doit = true;
             Item item = fItemData.fExpandedItems[idx];
             ITimeGraphEntry entry = item.fEntry;
+
+            /* Send menu event for the time graph entry */
+            e.doit = true;
+            e.data = entry;
+            fireMenuEventOnTimeGraphEntry(e);
+            Menu menu = getMenu();
+            if (e.doit && (menu != null)) {
+                menu.setVisible(true);
+            }
+
+            /* Send menu event for time event */
             if (entry.hasTimeEvents()) {
                 ITimeEvent event = Utils.findEvent(entry, getTimeAtX(p.x), 2);
                 if (event != null) {
+                    e.doit = true;
                     e.data = event;
                     fireMenuEventOnTimeEvent(e);
-                    return;
+                    menu = getMenu();
+                    if (e.doit && (menu != null)) {
+                        menu.setVisible(true);
+                    }
                 }
             }
-            e.data = entry;
-            fireMenuEventOnTimeGraphEntry(e);
         }
     }
 
This page took 0.046054 seconds and 5 git commands to generate.