tmf: Fix some critical and major Sonar warnings
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / widgets / TimeGraphControl.java
index 7977bb2162d23dd8c3236e0f6045f7e0103a15ee..59ee111f75e5eebacf888b7aa5cf51c8165d5ae2 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,9 +41,15 @@ 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.IStructuredSelection;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.ViewerFilter;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
 import org.eclipse.swt.events.FocusEvent;
 import org.eclipse.swt.events.FocusListener;
 import org.eclipse.swt.events.KeyEvent;
@@ -56,6 +68,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 +78,10 @@ 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.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.tracecompass.common.core.math.SaturatedArithmetic;
 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
@@ -77,11 +95,14 @@ import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.StateItem;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTimeEvent;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTreeExpansionEvent;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.Resolution;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat;
 
+import com.google.common.collect.Iterables;
+
 /**
  * Time graph control implementation
  *
@@ -96,6 +117,8 @@ public class TimeGraphControl extends TimeGraphBaseControl
     /** Constant indicating that all levels of the time graph should be expanded */
     public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS;
 
+    private static final int DRAG_MARGIN = 5;
+
     private static final int DRAG_NONE = 0;
     private static final int DRAG_TRACE_ITEM = 1;
     private static final int DRAG_SPLIT_LINE = 2;
@@ -108,12 +131,23 @@ public class TimeGraphControl extends TimeGraphBaseControl
     private static final double ZOOM_IN_FACTOR = 0.8;
     private static final double ZOOM_OUT_FACTOR = 1.25;
 
-    private static final int SNAP_WIDTH = 2;
+    private static final int SNAP_WIDTH = 3;
     private static final int ARROW_HOVER_MAX_DIST = 5;
 
+    private static final double ARROW_RATIO = Math.sqrt(3) / 2; // base to height ratio
+
     private static final int NO_STATUS = -1;
     private static final int STATUS_WITHOUT_CURSOR_TIME = -2;
 
+    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;
+
+    private static final String PREFERRED_WIDTH = "width"; //$NON-NLS-1$
+
     /** Resource manager */
     private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
 
@@ -121,12 +155,17 @@ public class TimeGraphControl extends TimeGraphBaseControl
     private Color[] fEventColorMap = null;
 
     private ITimeDataProvider fTimeProvider;
+    private ITableLabelProvider fLabelProvider;
     private IStatusLineManager fStatusLineManager = null;
+    private Tree fTree = null;
     private TimeGraphScale fTimeGraphScale = null;
 
     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;
     private int fDragState = DRAG_NONE;
@@ -134,12 +173,16 @@ 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 boolean fAutoResizeColumns = true;
     private long fTime0bak;
     private long fTime1bak;
     private ITimeGraphPresentationProvider fTimeGraphProvider = null;
     private ItemData fItemData = null;
+    private List<IMarkerEvent> fMarkers = null;
+    private boolean fMarkersVisible = true;
     private List<SelectionListener> fSelectionListeners;
     private List<ITimeGraphTimeListener> fDragSelectionListeners;
     private final List<ISelectionChangedListener> fSelectionChangedListeners = new ArrayList<>();
@@ -150,11 +193,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;
 
@@ -181,12 +227,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();
+            }
+        });
     }
 
     /**
@@ -215,6 +261,16 @@ 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
+     * @since 2.1
+     */
+    public ITimeDataProvider getTimeDataProvider() {
+        return fTimeProvider;
+    }
+
     /**
      * Gets the color map used by this timegraph viewer.
      *
@@ -235,6 +291,28 @@ public class TimeGraphControl extends TimeGraphBaseControl
         redraw();
     }
 
+    /**
+     * Set the label provider for the name space
+     *
+     * @param labelProvider
+     *            The label provider
+     * @since 2.3
+     */
+    public void setLabelProvider(ITableLabelProvider labelProvider) {
+        fLabelProvider = labelProvider;
+        redraw();
+    }
+
+    /**
+     * Get the label provider for the name space
+     *
+     * @return The label provider
+     * @since 2.3
+     */
+    public ITableLabelProvider getLabelProvider() {
+        return fLabelProvider;
+    }
+
     /**
      * Assign the status line manager
      *
@@ -248,6 +326,63 @@ public class TimeGraphControl extends TimeGraphBaseControl
         fStatusLineManager = statusLineManager;
     }
 
+    /**
+     * Assign the tree that represents the name space header
+     *
+     * @param tree
+     *            The tree
+     * @since 2.3
+     */
+    public void setTree(Tree tree) {
+        fTree = tree;
+    }
+
+    /**
+     * Returns the tree control associated with this time graph control. The
+     * tree is only used for column handling of the name space and contains no
+     * tree items.
+     *
+     * @return the tree control
+     * @since 2.3
+     */
+    public Tree getTree() {
+        return fTree;
+    }
+
+    /**
+     * Sets the columns for this time graph control's name space.
+     *
+     * @param columnNames
+     *            the column names
+     * @since 2.3
+     */
+    public void setColumns(String[] columnNames) {
+        for (TreeColumn column : fTree.getColumns()) {
+            column.dispose();
+        }
+        ControlListener controlListener = new ControlListener() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                if (fAutoResizeColumns && ((TreeColumn) e.widget).getWidth() < (Integer) e.widget.getData(PREFERRED_WIDTH)) {
+                    fAutoResizeColumns = false;
+                }
+                redraw();
+            }
+            @Override
+            public void controlMoved(ControlEvent e) {
+                redraw();
+            }
+        };
+        for (String columnName : columnNames) {
+            TreeColumn column = new TreeColumn(fTree, SWT.LEFT);
+            column.setMoveable(true);
+            column.setText(columnName);
+            column.pack();
+            column.setData(PREFERRED_WIDTH, column.getWidth());
+            column.addControlListener(controlListener);
+        }
+    }
+
     /**
      * Assign the time graph scale
      *
@@ -297,6 +432,12 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 listener.widgetSelected(null);
             }
         }
+
+        if (null != fSelectionChangedListeners) {
+            for (ISelectionChangedListener listener : fSelectionChangedListeners) {
+                listener.selectionChanged(new SelectionChangedEvent(this, getSelection()));
+            }
+        }
     }
 
     /**
@@ -377,15 +518,6 @@ public class TimeGraphControl extends TimeGraphBaseControl
         return fItemData.getEntries();
     }
 
-    /**
-     * Get the on/off trace filters
-     *
-     * @return The array of filters
-     */
-    public boolean[] getTraceFilter() {
-        return fItemData.getEntryFilter();
-    }
-
     /**
      * Refresh the data for the thing
      */
@@ -419,7 +551,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
      *
      * @return The unmodifiable link event list
      *
-     * @since 2.0
+     * @since 1.1
      */
     public List<ILinkEvent> getArrows() {
         return Collections.unmodifiableList(fItemData.fLinks);
@@ -471,15 +603,46 @@ public class TimeGraphControl extends TimeGraphBaseControl
     }
 
     /**
-     * Sets the auto-expand level to be used when the entries are refreshed
-     * using {@link #refreshData()} or {@link #refreshData(ITimeGraphEntry[])}.
-     * The value 0 means that there is no auto-expand; 1 means that top-level
+     * 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[])}
+     * . The value 0 means that there is no auto-expand; 1 means that top-level
      * entries are expanded, but not their children; 2 means that top-level
      * entries are expanded, and their children, but not grand-children; and so
      * on.
      * <p>
      * The value {@link #ALL_LEVELS} means that all subtrees should be expanded.
      * </p>
+     *
      * @param level
      *            non-negative level, or <code>ALL_LEVELS</code> to expand all
      *            levels of the tree
@@ -499,6 +662,19 @@ public class TimeGraphControl extends TimeGraphBaseControl
         return fAutoExpandLevel;
     }
 
+    /**
+     * Get the expanded state of a given entry.
+     *
+     * @param entry
+     *            The entry
+     * @return true if the entry is expanded, false if collapsed
+     * @since 1.1
+     */
+    public boolean getExpandedState(ITimeGraphEntry entry) {
+        Item item = fItemData.fItemMap.get(entry);
+        return (item != null ? item.fExpanded : false);
+    }
+
     /**
      * Set the expanded state of a given entry
      *
@@ -516,6 +692,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.
      */
@@ -653,20 +931,33 @@ public class TimeGraphControl extends TimeGraphBaseControl
         }
     }
 
+    @Override
+    public boolean setFocus() {
+        if ((fTimeProvider != null) && fTimeProvider.getNameSpace() > 0) {
+            fHasNamespaceFocus = true;
+        }
+        return super.setFocus();
+    }
+
+    /**
+     * Returns the current selection for this time graph. If a time graph entry
+     * is selected, it will be the first element in the selection. If a time
+     * event is selected, it will be the second element in the selection.
+     *
+     * @return the current selection
+     */
     @Override
     public ISelection getSelection() {
-        TimeGraphSelection sel = new TimeGraphSelection();
-        ITimeGraphEntry trace = getSelectedTrace();
-        if (null != trace && null != fTimeProvider) {
+        ITimeGraphEntry entry = getSelectedTrace();
+        if (null != entry && null != fTimeProvider) {
             long selectedTime = fTimeProvider.getSelectionBegin();
-            ITimeEvent event = Utils.findEvent(trace, selectedTime, 0);
-            if (event != null) {
-                sel.add(event);
-            } else {
-                sel.add(trace);
+            ITimeEvent event = Utils.findEvent(entry, selectedTime, 0);
+            if (event == null) {
+                return new StructuredSelection(entry);
             }
+            return new StructuredSelection(new Object[] { entry, event });
         }
-        return sel;
+        return StructuredSelection.EMPTY;
     }
 
     /**
@@ -675,12 +966,11 @@ public class TimeGraphControl extends TimeGraphBaseControl
      * @return The selection
      */
     public ISelection getSelectionTrace() {
-        TimeGraphSelection sel = new TimeGraphSelection();
-        ITimeGraphEntry trace = getSelectedTrace();
-        if (null != trace) {
-            sel.add(trace);
+        ITimeGraphEntry entry = getSelectedTrace();
+        if (null != entry) {
+            return new StructuredSelection(entry);
         }
-        return sel;
+        return StructuredSelection.EMPTY;
     }
 
     /**
@@ -773,14 +1063,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);
             }
@@ -941,6 +1231,118 @@ 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.
+     *
+     * @param visible
+     *            true to show the grid lines, false otherwise
+     * @since 2.0
+     */
+    public void setGridLinesVisible(boolean visible) {
+        fGridLinesVisible = visible;
+    }
+
+    /**
+     * Get the grid lines visibility.
+     *
+     * @return true if the grid lines are visible, false otherwise
+     * @since 2.0
+     */
+    public boolean getGridLinesVisible() {
+        return fGridLinesVisible;
+    }
+
+    /**
+     * Set the grid line color. The default is SWT.COLOR_GRAY.
+     *
+     * @param color
+     *            the grid line color
+     * @since 2.0
+     */
+    public void setGridLineColor(Color color) {
+        fGridLineColor = color;
+    }
+
+    /**
+     * Get the grid line color.
+     *
+     * @return the grid line color
+     * @since 2.0
+     */
+    public Color getGridLineColor() {
+        return fGridLineColor;
+    }
+
+    /**
+     * Set the markers list.
+     *
+     * @param markers
+     *            The markers list, or null
+     * @since 2.0
+     */
+    public void setMarkers(List<IMarkerEvent> markers) {
+        fMarkers = markers;
+    }
+
+    /**
+     * Get the markers list.
+     *
+     * @return The markers list, or null
+     * @since 2.0
+     */
+    public List<IMarkerEvent> getMarkers() {
+        return fMarkers;
+    }
+
+    /**
+     * Set the markers visibility. The default is true.
+     *
+     * @param visible
+     *            true to show the markers, false otherwise
+     * @since 2.0
+     */
+    public void setMarkersVisible(boolean visible) {
+        fMarkersVisible = visible;
+    }
+
+    /**
+     * Get the markers visibility.
+     *
+     * @return true if the markers are visible, false otherwise
+     * @since 2.0
+     */
+    public boolean getMarkersVisible() {
+        return fMarkersVisible;
+    }
+
     /**
      * Hide arrows
      *
@@ -968,7 +1370,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);
                     }
@@ -1001,7 +1403,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);
                     }
@@ -1070,14 +1472,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;
@@ -1088,9 +1496,13 @@ public class TimeGraphControl extends TimeGraphBaseControl
             return false;
         }
         int nameWidth = fTimeProvider.getNameSpace();
-        return Math.abs(x - nameWidth) < SNAP_WIDTH;
+        return Math.abs(x - nameWidth) <= SNAP_WIDTH;
     }
 
+    boolean isOverTimeSpace(int x, int y) {
+        Point size = getSize();
+        return x >= fTimeProvider.getNameSpace() && x < size.x && y >= 0 && y < size.y;
+    }
     /**
      * Gets the {@link ITimeGraphEntry} at the given location.
      *
@@ -1098,8 +1510,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;
     }
@@ -1148,7 +1561,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
         int width = getSize().x;
         int nameSpace = fTimeProvider.getNameSpace();
         double pixelsPerNanoSec = (width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (width - nameSpace - RIGHT_MARGIN) / (time1 - time0);
-        int x = getBounds().x + nameSpace + (int) ((time - time0) * pixelsPerNanoSec);
+        int x = SaturatedArithmetic.add(getBounds().x + nameSpace, (int) ((time - time0) * pixelsPerNanoSec));
         return x;
     }
 
@@ -1177,6 +1590,10 @@ public class TimeGraphControl extends TimeGraphBaseControl
     }
 
     void selectItem(int idx, boolean addSelection) {
+        selectItem(idx, addSelection, true);
+    }
+
+    void selectItem(int idx, boolean addSelection, boolean reveal) {
         boolean changed = false;
         if (addSelection) {
             if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
@@ -1193,25 +1610,46 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 item.fSelected = i == idx;
             }
         }
-        changed |= ensureVisibleItem(idx, true);
+        if (reveal) {
+            changed |= ensureVisibleItem(idx, true);
+        }
         if (changed) {
             redraw();
         }
     }
 
     /**
-     * Callback for item selection
+     * Select an entry and make it visible
      *
-     * @param trace
-     *            The entry matching the trace
+     * @param entry
+     *            The entry to select
      * @param addSelection
-     *            If the selection is added or removed
+     *            <code>true</code> to add the entry to the current selection,
+     *            or <code>false</code> to set a new selection
      */
-    public void selectItem(ITimeGraphEntry trace, boolean addSelection) {
-        int idx = fItemData.findItemIndex(trace);
+    public void selectItem(ITimeGraphEntry entry, boolean addSelection) {
+        int idx = fItemData.findItemIndex(entry);
         selectItem(idx, addSelection);
     }
 
+    /**
+     * Select an entry
+     *
+     * @param entry
+     *            The entry to select
+     * @param addSelection
+     *            <code>true</code> to add the entry to the current selection,
+     *            or <code>false</code> to set a new selection
+     * @param reveal
+     *            <code>true</code> if the selection is to be made visible, and
+     *            <code>false</code> otherwise
+     * @since 2.3
+     */
+    public void selectItem(ITimeGraphEntry entry, boolean addSelection, boolean reveal) {
+        int idx = fItemData.findItemIndex(entry);
+        selectItem(idx, addSelection, reveal);
+    }
+
     /**
      * Retrieve the number of entries shown per page.
      *
@@ -1248,18 +1686,18 @@ public class TimeGraphControl extends TimeGraphBaseControl
     }
 
     /**
-     * Get the number of expanded items
+     * Get the number of expanded (visible) items
      *
-     * @return The count of expanded items
+     * @return The count of expanded (visible) items
      */
     public int getExpandedElementCount() {
         return fItemData.fExpandedItems.length;
     }
 
     /**
-     * Get an array of all expanded elements
+     * Get an array of all expanded (visible) elements
      *
-     * @return The expanded elements
+     * @return The expanded (visible) elements
      */
     public ITimeGraphEntry[] getExpandedElements() {
         ArrayList<ITimeGraphEntry> elements = new ArrayList<>();
@@ -1269,16 +1707,50 @@ public class TimeGraphControl extends TimeGraphBaseControl
         return elements.toArray(new ITimeGraphEntry[0]);
     }
 
-    Rectangle getNameRect(Rectangle bound, int idx, int nameWidth) {
-        Rectangle rect = getStatesRect(bound, idx, nameWidth);
-        rect.x = bound.x;
+    /**
+     * 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;
+    }
+
+    /**
+     * Get the bounds of the specified entry
+     *
+     * @param entry the time graph entry
+     * @return the bounds of the entry, or null if the entry is not visible
+     * @since 2.3
+     */
+    public Rectangle getItemBounds(ITimeGraphEntry entry) {
+        int idx = fItemData.findItemIndex(entry);
+        if (idx >= 0) {
+            return getItemRect(getBounds(), idx);
+        }
+        return null;
+    }
+
+    Rectangle getNameRect(Rectangle bounds, int idx, int nameWidth) {
+        Rectangle rect = getItemRect(bounds, idx);
         rect.width = nameWidth;
         return rect;
     }
 
-    Rectangle getStatesRect(Rectangle bound, int idx, int nameWidth) {
-        int x = bound.x + nameWidth;
-        int width = bound.width - x;
+    Rectangle getStatesRect(Rectangle bounds, int idx, int nameWidth) {
+        Rectangle rect = getItemRect(bounds, idx);
+        rect.x += nameWidth;
+        rect.width -= nameWidth;
+        return rect;
+    }
+
+    Rectangle getItemRect(Rectangle bounds, int idx) {
         int ySum = 0;
         if (idx >= fTopIndex) {
             for (int i = fTopIndex; i < idx; i++) {
@@ -1289,16 +1761,14 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 ySum -= fItemData.fExpandedItems[i].fItemHeight;
             }
         }
-        int y = bound.y + ySum;
+        int y = bounds.y + ySum;
         int height = fItemData.fExpandedItems[idx].fItemHeight;
-        return new Rectangle(x, y, width, height);
+        return new Rectangle(bounds.x, y, bounds.width, height);
     }
 
     @Override
     void paint(Rectangle bounds, PaintEvent e) {
         GC gc = e.gc;
-        gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.BACKGROUND));
-        drawBackground(gc, bounds.x, bounds.y, bounds.width, bounds.height);
 
         if (bounds.width < 2 || bounds.height < 2 || null == fTimeProvider) {
             return;
@@ -1307,13 +1777,24 @@ public class TimeGraphControl extends TimeGraphBaseControl
         fIdealNameSpace = 0;
         int nameSpace = fTimeProvider.getNameSpace();
 
-        // draw empty name space background
-        gc.setBackground(getColorScheme().getBkColor(false, false, true));
-        drawBackground(gc, bounds.x, bounds.y, nameSpace, bounds.height);
+        // draw the background layer
+        drawBackground(bounds, nameSpace, gc);
+
+        // draw the grid lines
+        drawGridLines(bounds, gc);
 
-        // draw items
+        // draw the background markers
+        drawMarkers(bounds, fTimeProvider, fMarkers, 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 links (arrows)
         drawLinks(bounds, fTimeProvider, fItemData.fLinks, nameSpace, gc);
+
         fTimeGraphProvider.postDrawControl(bounds, gc);
 
         int alpha = gc.getAlpha();
@@ -1324,8 +1805,8 @@ public class TimeGraphControl extends TimeGraphBaseControl
         long selectionBegin = fTimeProvider.getSelectionBegin();
         long selectionEnd = fTimeProvider.getSelectionEnd();
         double pixelsPerNanoSec = (bounds.width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (bounds.width - nameSpace - RIGHT_MARGIN) / (time1 - time0);
-        int x0 = bounds.x + nameSpace + (int) ((selectionBegin - time0) * pixelsPerNanoSec);
-        int x1 = bounds.x + nameSpace + (int) ((selectionEnd - time0) * pixelsPerNanoSec);
+        int x0 = SaturatedArithmetic.add(bounds.x + nameSpace, (int) ((selectionBegin - time0) * pixelsPerNanoSec));
+        int x1 = SaturatedArithmetic.add(bounds.x + nameSpace, (int) ((selectionEnd - time0) * pixelsPerNanoSec));
 
         // draw selection lines
         if (fDragState != DRAG_SELECTION) {
@@ -1362,11 +1843,16 @@ public class TimeGraphControl extends TimeGraphBaseControl
             }
         }
 
-        // draw drag line
-        if (DRAG_SPLIT_LINE == fDragState) {
-            gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.BLACK));
-            gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1);
-        } else if (DRAG_ZOOM == fDragState && Math.max(fDragX, fDragX0) > nameSpace) {
+        // draw split line
+        if (DRAG_SPLIT_LINE == fDragState ||
+                (DRAG_NONE == fDragState && fMouseOverSplitLine && fTimeProvider.getNameSpace() > 0)) {
+            gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.DARK_GRAY));
+        } else {
+            gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.GRAY));
+        }
+        gc.fillRectangle(bounds.x + nameSpace - SNAP_WIDTH, bounds.y, SNAP_WIDTH, bounds.height);
+
+        if (DRAG_ZOOM == fDragState && Math.max(fDragX, fDragX0) > nameSpace) {
             gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.TOOL_FOREGROUND));
             gc.drawLine(fDragX0, bounds.y, fDragX0, bounds.y + bounds.height - 1);
             if (fDragX != fDragX0) {
@@ -1378,29 +1864,175 @@ public class TimeGraphControl extends TimeGraphBaseControl
             if (fDragX != fDragX0) {
                 gc.drawLine(fDragX, bounds.y, fDragX, bounds.y + bounds.height - 1);
             }
-        } else if (DRAG_NONE == fDragState && fMouseOverSplitLine && fTimeProvider.getNameSpace() > 0) {
-            gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.RED));
-            gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1);
         }
 
         gc.setAlpha(alpha);
     }
 
     /**
-     * Draw many items at once
+     * Draw the background layer. Fills the background of the control's name
+     * space and states space, updates the background of items if necessary,
+     * and draws the item's name text and middle line.
+     *
+     * @param bounds
+     *            The bounds of the control
+     * @param nameSpace
+     *            The name space width
+     * @param gc
+     *            Graphics context
+     * @since 2.0
+     */
+    protected void drawBackground(Rectangle bounds, int nameSpace, GC gc) {
+        // draw empty name space background
+        gc.setBackground(getColorScheme().getBkColor(false, false, true));
+        drawBackground(gc, bounds.x, bounds.y, nameSpace, bounds.height);
+
+        // draw empty states space background
+        gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.BACKGROUND));
+        drawBackground(gc, bounds.x + nameSpace, bounds.y, bounds.width - nameSpace, bounds.height);
+
+        for (int i = fTopIndex; i < fItemData.fExpandedItems.length; i++) {
+            Rectangle itemRect = getItemRect(bounds, i);
+            if (itemRect.y >= bounds.y + bounds.height) {
+                break;
+            }
+            Item item = fItemData.fExpandedItems[i];
+            // draw the background of selected item and items with no time events
+            if (! item.fEntry.hasTimeEvents()) {
+                gc.setBackground(getColorScheme().getBkColorGroup(item.fSelected, fIsInFocus));
+                gc.fillRectangle(itemRect);
+            } else if (item.fSelected) {
+                gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, true));
+                gc.fillRectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height);
+                gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, false));
+                gc.fillRectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
+            }
+            // draw the name space
+            Rectangle nameRect = new Rectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height);
+            drawName(item, nameRect, gc);
+            if (item.fEntry.hasTimeEvents()) {
+                Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
+                drawMidLine(rect, gc);
+            }
+        }
+    }
+
+    /**
+     * Draw the grid lines
+     *
+     * @param bounds
+     *            The bounds of the control
+     * @param gc
+     *            Graphics context
+     * @since 2.0
+     */
+    public void drawGridLines(Rectangle bounds, GC gc) {
+        if (!fGridLinesVisible) {
+            return;
+        }
+        gc.setForeground(fGridLineColor);
+        gc.setAlpha(fGridLineColor.getAlpha());
+        for (int x : fTimeGraphScale.getTickList()) {
+            gc.drawLine(x, bounds.y, x, bounds.y + bounds.height);
+        }
+        gc.setAlpha(255);
+    }
+
+    /**
+     * Draw the markers
      *
      * @param bounds
      *            The rectangle of the area
      * @param timeProvider
      *            The time provider
+     * @param markers
+     *            The list of markers
+     * @param foreground
+     *            true to draw the foreground markers, false otherwise
+     * @param nameSpace
+     *            The width reserved for the names
+     * @param gc
+     *            Reference to the SWT GC object
+     * @since 2.0
+     */
+    protected void drawMarkers(Rectangle bounds, ITimeDataProvider timeProvider, List<IMarkerEvent> markers, boolean foreground, int nameSpace, GC gc) {
+        if (!fMarkersVisible || markers == null || markers.isEmpty()) {
+            return;
+        }
+        gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
+        /* the list can grow concurrently but cannot shrink */
+        for (int i = 0; i < markers.size(); i++) {
+            IMarkerEvent marker = markers.get(i);
+            if (marker.isForeground() == foreground) {
+                drawMarker(marker, bounds, timeProvider, nameSpace, gc);
+            }
+        }
+        gc.setClipping((Rectangle) null);
+    }
+
+    /**
+     * Draw a single marker
+     *
+     * @param marker
+     *            The marker event
+     * @param bounds
+     *            The bounds of the control
+     * @param timeProvider
+     *            The time provider
+     * @param nameSpace
+     *            The width reserved for the name
+     * @param gc
+     *            Reference to the SWT GC object
+     * @since 2.0
+     */
+    protected void drawMarker(IMarkerEvent marker, Rectangle bounds, ITimeDataProvider timeProvider, int nameSpace, GC gc) {
+        Rectangle rect = Utils.clone(bounds);
+        if (marker.getEntry() != null) {
+            int index = fItemData.findItemIndex(marker.getEntry());
+            if (index == -1) {
+                return;
+            }
+            rect = getStatesRect(bounds, index, nameSpace);
+            if (rect.y < 0 || rect.y > bounds.height) {
+                return;
+            }
+        }
+        int x0 = getXForTime(marker.getTime());
+        int x1 = getXForTime(marker.getTime() + marker.getDuration());
+        if (x0 > bounds.width || x1 < nameSpace) {
+            return;
+        }
+        rect.x = Math.max(nameSpace, Math.min(bounds.width, x0));
+        rect.width = Math.max(1, Math.min(bounds.width, x1) - rect.x);
+
+        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(color);
+            Utils.drawText(gc, label, rect.x - gc.textExtent(label).x, rect.y, true);
+        }
+    }
+
+    /**
+     * Draw many items at once
+     *
+     * @param bounds
+     *            The bounds of the control
+     * @param timeProvider
+     *            The time provider
      * @param items
      *            The array items to draw
      * @param topIndex
      *            The index of the first element to draw
      * @param nameSpace
-     *            The width reserved for the names
+     *            The name space width
      * @param gc
-     *            Reference to the SWT GC object
+     *            Graphics context
      */
     public void drawItems(Rectangle bounds, ITimeDataProvider timeProvider,
             Item[] items, int topIndex, int nameSpace, GC gc) {
@@ -1413,45 +2045,36 @@ public class TimeGraphControl extends TimeGraphBaseControl
     /**
      * Draws the item
      *
-     * @param item the item to draw
-     * @param bounds the container rectangle
-     * @param timeProvider Time provider
-     * @param i the item index
-     * @param nameSpace the name space
-     * @param gc Graphics context
+     * @param item
+     *            The item to draw
+     * @param bounds
+     *            The bounds of the control
+     * @param timeProvider
+     *            The time provider
+     * @param i
+     *            The expanded item index
+     * @param nameSpace
+     *            The name space width
+     * @param gc
+     *            Graphics context
      */
     protected void drawItem(Item item, Rectangle bounds, ITimeDataProvider timeProvider, int i, int nameSpace, GC gc) {
+        Rectangle itemRect = getItemRect(bounds, i);
+        if (itemRect.y >= bounds.y + bounds.height) {
+            return;
+        }
+
         ITimeGraphEntry entry = item.fEntry;
         long time0 = timeProvider.getTime0();
         long time1 = timeProvider.getTime1();
         long selectedTime = fTimeProvider.getSelectionEnd();
 
-        Rectangle nameRect = getNameRect(bounds, i, nameSpace);
-        if (nameRect.y >= bounds.y + bounds.height) {
-            return;
-        }
-
-        if (! item.fEntry.hasTimeEvents()) {
-            Rectangle statesRect = getStatesRect(bounds, i, nameSpace);
-            nameRect.width += statesRect.width;
-            drawName(item, nameRect, gc);
-        } else {
-            drawName(item, nameRect, gc);
-        }
-        Rectangle rect = getStatesRect(bounds, i, nameSpace);
-        if (rect.isEmpty()) {
-            fTimeGraphProvider.postDrawEntry(entry, rect, gc);
-            return;
-        }
-        if (time1 <= time0) {
-            gc.setBackground(getColorScheme().getBkColor(false, false, false));
-            gc.fillRectangle(rect);
+        Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
+        if (rect.isEmpty() || (time1 <= time0)) {
             fTimeGraphProvider.postDrawEntry(entry, rect, gc);
             return;
         }
 
-        // Initialize _rect1 to same values as enclosing rectangle rect
-        Rectangle stateRect = Utils.clone(rect);
         boolean selected = item.fSelected;
         // K pixels per second
         double pixelsPerNanoSec = (rect.width <= RIGHT_MARGIN) ? 0 : (double) (rect.width - RIGHT_MARGIN) / (time1 - time0);
@@ -1459,9 +2082,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);
-            // Drawing rectangle is smaller than reserved space
-            stateRect.y += 3;
-            stateRect.height -= 6;
+
+            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);
@@ -1469,8 +2097,8 @@ public class TimeGraphControl extends TimeGraphBaseControl
             int lastX = -1;
             while (iterator.hasNext()) {
                 ITimeEvent event = iterator.next();
-                int x = rect.x + (int) ((event.getTime() - time0) * pixelsPerNanoSec);
-                int xEnd = rect.x + (int) ((event.getTime() + event.getDuration() - time0) * pixelsPerNanoSec);
+                int x = SaturatedArithmetic.add(rect.x, (int) ((event.getTime() - time0) * pixelsPerNanoSec));
+                int xEnd = SaturatedArithmetic.add(rect.x, (int) ((event.getTime() + event.getDuration() - time0) * pixelsPerNanoSec));
                 if (x >= rect.x + rect.width || xEnd < rect.x) {
                     // event is out of bounds
                     continue;
@@ -1488,7 +2116,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 }
                 boolean timeSelected = selectedTime >= event.getTime() && selectedTime < event.getTime() + event.getDuration();
                 if (drawState(getColorScheme(), event, stateRect, gc, selected, timeSelected)) {
-                    lastX = x;
+                    lastX = stateRect.x;
                 }
             }
             gc.setClipping((Rectangle) null);
@@ -1500,15 +2128,15 @@ public class TimeGraphControl extends TimeGraphBaseControl
      * Draw the links
      *
      * @param bounds
-     *            The rectangle of the area
+     *            The bounds of the control
      * @param timeProvider
      *            The time provider
      * @param links
-     *            The array items to draw
+     *            The list of link events
      * @param nameSpace
-     *            The width reserved for the names
+     *            The name space width
      * @param gc
-     *            Reference to the SWT GC object
+     *            Graphics context
      */
     public void drawLinks(Rectangle bounds, ITimeDataProvider timeProvider,
             List<ILinkEvent> links, int nameSpace, GC gc) {
@@ -1516,23 +2144,24 @@ public class TimeGraphControl extends TimeGraphBaseControl
             return;
         }
         gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
-        for (ILinkEvent event : links) {
-            drawLink(event, bounds, timeProvider, nameSpace, gc);
+        /* the list can grow concurrently but cannot shrink */
+        for (int i = 0; i < links.size(); i++) {
+            drawLink(links.get(i), bounds, timeProvider, nameSpace, gc);
         }
         gc.setClipping((Rectangle) null);
     }
 
     /**
-     * Draws the link type events of this item
+     * Draws a link type event
      *
      * @param event
-     *            the item to draw
+     *            The link event to draw
      * @param bounds
-     *            the container rectangle
+     *            The bounds of the control
      * @param timeProvider
-     *            Time provider
+     *            The time provider
      * @param nameSpace
-     *            the name space
+     *            The name space width
      * @param gc
      *            Graphics context
      */
@@ -1566,17 +2195,17 @@ public class TimeGraphControl extends TimeGraphBaseControl
     }
 
     /**
-     * Draw the state (color fill)
+     * Draw an arrow
      *
      * @param colors
      *            Color scheme
      * @param event
-     *            Time event for which we're drawing the state
+     *            Time event for which we're drawing the arrow
      * @param rect
-     *            Where to draw
+     *            The arrow rectangle
      * @param gc
      *            Graphics context
-     * @return true if the state was drawn
+     * @return true if the arrow was drawn
      */
     protected boolean drawArrow(TimeGraphColorScheme colors, ITimeEvent event,
             Rectangle rect, GC gc) {
@@ -1638,103 +2267,108 @@ public class TimeGraphControl extends TimeGraphBaseControl
     }
 
     /**
-     * Draw the name of an item.
+     * Draw the name space of an item.
      *
      * @param item
      *            Item object
      * @param bounds
-     *            Where to draw the name
+     *            The bounds of the item's name space
      * @param gc
      *            Graphics context
      */
     protected void drawName(Item item, Rectangle bounds, GC gc) {
-        boolean hasTimeEvents = item.fEntry.hasTimeEvents();
-        if (! hasTimeEvents) {
-            gc.setBackground(getColorScheme().getBkColorGroup(item.fSelected, fIsInFocus));
-            gc.fillRectangle(bounds);
-            if (item.fSelected && fIsInFocus) {
-                gc.setForeground(getColorScheme().getBkColor(item.fSelected, fIsInFocus, false));
-                gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
-            }
-        } else {
-            gc.setBackground(getColorScheme().getBkColor(item.fSelected, fIsInFocus, true));
-            gc.setForeground(getColorScheme().getFgColor(item.fSelected, fIsInFocus));
-            gc.fillRectangle(bounds);
-        }
-
-        // No name to be drawn
+        // No name space to be drawn
         if (fTimeProvider.getNameSpace() == 0) {
             return;
         }
 
-        int leftMargin = MARGIN + item.fLevel * EXPAND_SIZE;
-        if (item.fHasChildren) {
-            gc.setForeground(getColorScheme().getFgColorGroup(false, false));
-            gc.setBackground(getColorScheme().getBkColor(false, false, false));
-            Rectangle rect = Utils.clone(bounds);
-            rect.x += leftMargin;
-            rect.y += (bounds.height - EXPAND_SIZE) / 2;
-            rect.width = EXPAND_SIZE;
-            rect.height = EXPAND_SIZE;
-            gc.fillRectangle(rect);
-            gc.drawRectangle(rect.x, rect.y, rect.width - 1, rect.height - 1);
-            int midy = rect.y + rect.height / 2;
-            gc.drawLine(rect.x + 2, midy, rect.x + rect.width - 3, midy);
-            if (!item.fExpanded) {
-                int midx = rect.x + rect.width / 2;
-                gc.drawLine(midx, rect.y + 2, midx, rect.y + rect.height - 3);
-            }
-        }
-        leftMargin += EXPAND_SIZE + MARGIN;
-
-        Image img = fTimeGraphProvider.getItemImage(item.fEntry);
-        if (img != null) {
-            // draw icon
-            int imgHeight = img.getImageData().height;
-            int imgWidth = img.getImageData().width;
-            int x = leftMargin;
-            int y = bounds.y + (bounds.height - imgHeight) / 2;
-            gc.drawImage(img, x, y);
-            leftMargin += imgWidth + MARGIN;
-        }
-        String name = item.fName;
-        Point size = gc.stringExtent(name);
-        if (fIdealNameSpace < leftMargin + size.x + MARGIN) {
-            fIdealNameSpace = leftMargin + size.x + MARGIN;
-        }
+        boolean hasTimeEvents = item.fEntry.hasTimeEvents();
         if (hasTimeEvents) {
-            // cut long string with "..."
-            int width = bounds.width - leftMargin;
-            int cuts = 0;
-            while (size.x > width && name.length() > 1) {
-                cuts++;
-                name = name.substring(0, name.length() - 1);
-                size = gc.stringExtent(name + "..."); //$NON-NLS-1$
-            }
-            if (cuts > 0) {
-                name += "..."; //$NON-NLS-1$
-            }
+            gc.setClipping(bounds);
         }
+
+        int height = bounds.height - getMarginForHeight(bounds.height);
+        setFontForHeight(height, gc);
+
+        String name = fLabelProvider == null ? item.fName : fLabelProvider.getColumnText(item.fEntry, 0);
         Rectangle rect = Utils.clone(bounds);
-        rect.x += leftMargin;
-        rect.width -= leftMargin;
-        // draw text
-        if (rect.width > 0) {
-            rect.y += (bounds.height - gc.stringExtent(name).y) / 2;
+        rect.y += (bounds.height - gc.stringExtent(name).y) / 2;
+        TreeColumn[] columns = fTree.getColumns();
+        int idealNameSpace = 0;
+        for (int i = 0; i < columns.length; i++) {
+            int columnIndex = fTree.getColumnOrder()[i];
+            TreeColumn column = columns[columnIndex];
+            rect.width = column.getWidth();
+            gc.setClipping(rect.x, bounds.y, Math.min(rect.width, bounds.x + bounds.width - rect.x - SNAP_WIDTH), bounds.height);
+            int width = MARGIN;
+            if (i == 0) {
+                // first visible column
+                width += item.fLevel * EXPAND_SIZE;
+                if (item.fHasChildren) {
+                    // draw expand/collapse arrow
+                    gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.DARK_GRAY));
+                    int arrowHeightHint = (height < 4) ? height : (height < 6) ? height - 1 : height - 2;
+                    int arrowHalfHeight = Math.max(1, Math.min(arrowHeightHint, (int) Math.round((EXPAND_SIZE - 2) / ARROW_RATIO))) / 2;
+                    int arrowHalfWidth = (Math.max(1, Math.min(EXPAND_SIZE - 2, (int) Math.round(arrowHeightHint * ARROW_RATIO))) + 1) / 2;
+                    int x1 = bounds.x + width + 1;
+                    int x2 = x1 + 2 * arrowHalfWidth;
+                    int midy = bounds.y + bounds.height / 2;
+                    int y1 = midy - arrowHalfHeight;
+                    int y2 = midy + arrowHalfHeight;
+                    if (!item.fExpanded) { // >
+                        gc.fillPolygon(new int[] { x1, y1, x2, midy, x1, y2 });
+                    } else { // v
+                        int midx = x1 + arrowHalfWidth;
+                        gc.fillPolygon(new int[] { x1, y1, x2, y1, midx, y2 });
+                    }
+                }
+                width += EXPAND_SIZE + MARGIN;
+
+                Image img = fLabelProvider != null ? fLabelProvider.getColumnImage(item.fEntry, columnIndex)
+                        : columnIndex == 0 ? fTimeGraphProvider.getItemImage(item.fEntry) : null;
+                if (img != null) {
+                    // draw icon
+                    int imgHeight = img.getImageData().height;
+                    int imgWidth = img.getImageData().width;
+                    int dstHeight = Math.min(bounds.height, imgHeight);
+                    int dstWidth = dstHeight * imgWidth / imgHeight;
+                    int x = width;
+                    int y = bounds.y + (bounds.height - dstHeight) / 2;
+                    gc.drawImage(img, 0, 0, imgWidth, imgHeight, x, y, dstWidth, dstHeight);
+                    width += imgWidth + MARGIN;
+                }
+            } else {
+                if (fLabelProvider == null) {
+                    break;
+                }
+            }
+            String label = fLabelProvider != null ? fLabelProvider.getColumnText(item.fEntry, columnIndex)
+                    : columnIndex == 0 ? item.fName : ""; //$NON-NLS-1$
             gc.setForeground(getColorScheme().getFgColor(item.fSelected, fIsInFocus));
-            int textWidth = Utils.drawText(gc, name, rect, true);
-            leftMargin += textWidth + MARGIN;
-            rect.y -= 2;
-
-            if (hasTimeEvents) {
-                // draw middle line
-                int x = bounds.x + leftMargin;
-                int width = bounds.width - x;
-                int midy = bounds.y + bounds.height / 2;
-                gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE));
-                gc.drawLine(x, midy, x + width, midy);
+            Rectangle textRect = new Rectangle(rect.x + width, rect.y, rect.width - width, rect.height);
+            int textWidth = Utils.drawText(gc, label, textRect, true);
+            width += textWidth + MARGIN;
+            if (textWidth > 0) {
+                idealNameSpace = rect.x + width;
+            }
+            if (columns.length == 1) {
+                drawMidLine(new Rectangle(bounds.x + width, bounds.y, bounds.x + bounds.width, bounds.height), gc);
+            }
+            if (fAutoResizeColumns && width > column.getWidth()) {
+                column.setData(PREFERRED_WIDTH, width);
+                column.setWidth(width);
             }
+            gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE));
+            if (i < columns.length - 1) {
+                // not the last visible column: draw the vertical cell border
+                int x = rect.x + rect.width - 1;
+                gc.drawLine(x, bounds.y, x, bounds.y + bounds.height);
+            }
+            rect.x += rect.width;
         }
+        fIdealNameSpace = Math.max(fIdealNameSpace, idealNameSpace);
+
+        gc.setClipping((Rectangle) null);
     }
 
     /**
@@ -1745,7 +2379,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
      * @param event
      *            Time event for which we're drawing the state
      * @param rect
-     *            Where to draw
+     *            The state rectangle
      * @param gc
      *            Graphics context
      * @param selected
@@ -1763,37 +2397,45 @@ public class TimeGraphControl extends TimeGraphBaseControl
             return false;
         }
         boolean visible = rect.width == 0 ? false : true;
+        rect.width = Math.max(1, rect.width);
         Color black =  Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
         gc.setForeground(black);
 
-        if (visible) {
-            if (colorIdx == ITimeGraphPresentationProvider.TRANSPARENT) {
+        if (colorIdx == ITimeGraphPresentationProvider.TRANSPARENT) {
+            if (visible) {
                 // Only draw the top and bottom borders
                 gc.drawLine(rect.x, rect.y, rect.x + rect.width - 1, rect.y);
                 gc.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1);
                 if (rect.width == 1) {
                     gc.drawPoint(rect.x, rect.y - 2);
                 }
-                fTimeGraphProvider.postDrawEvent(event, rect, gc);
-                return false;
-            }
-            Color stateColor = null;
-            if (colorIdx < fEventColorMap.length) {
-                stateColor = fEventColorMap[colorIdx];
-            } else {
-                stateColor = black;
             }
+            fTimeGraphProvider.postDrawEvent(event, rect, gc);
+            return false;
+        }
+        Color stateColor = null;
+        if (colorIdx < fEventColorMap.length) {
+            stateColor = fEventColorMap[colorIdx];
+        } else {
+            stateColor = black;
+        }
 
-            boolean reallySelected = timeSelected && selected;
-            // fill all rect area
-            gc.setBackground(stateColor);
+        boolean reallySelected = timeSelected && selected;
+        // fill all rect area
+        gc.setBackground(stateColor);
+        if (visible) {
+            gc.fillRectangle(rect);
+        } else if (fBlendSubPixelEvents) {
+            gc.setAlpha(128);
             gc.fillRectangle(rect);
+            gc.setAlpha(255);
+        }
 
-            if (reallySelected) {
-                gc.drawLine(rect.x, rect.y - 1, rect.x + rect.width - 1, rect.y - 1);
-                gc.drawLine(rect.x, rect.y + rect.height, rect.x + rect.width - 1, rect.y + rect.height);
-            }
-        } else {
+        if (reallySelected) {
+            gc.drawLine(rect.x, rect.y - 1, rect.x + rect.width - 1, rect.y - 1);
+            gc.drawLine(rect.x, rect.y + rect.height, rect.x + rect.width - 1, rect.y + rect.height);
+        }
+        if (!visible) {
             gc.drawPoint(rect.x, rect.y - 2);
         }
         fTimeGraphProvider.postDrawEvent(event, rect, gc);
@@ -1801,32 +2443,61 @@ public class TimeGraphControl extends TimeGraphBaseControl
     }
 
     /**
-     * Fill the space between two contiguous time events
+     * Fill an item's states rectangle
      *
      * @param rect
-     *            Rectangle to fill
+     *            The states rectangle
      * @param gc
      *            Graphics context
      * @param selected
-     *            Is this time event selected or not
+     *            true if the item is selected
      */
     protected void fillSpace(Rectangle rect, GC gc, boolean selected) {
-        gc.setBackground(getColorScheme().getBkColor(selected, fIsInFocus, false));
-        gc.fillRectangle(rect);
-        if (fDragState == DRAG_ZOOM) {
-            gc.setBackground(getColorScheme().getBkColor(selected, fIsInFocus, true));
-            if (fDragX0 < fDragX) {
-                gc.fillRectangle(new Rectangle(fDragX0, rect.y, fDragX - fDragX0, rect.height));
-            } else if (fDragX0 > fDragX) {
-                gc.fillRectangle(new Rectangle(fDragX, rect.y, fDragX0 - fDragX, rect.height));
-            }
-        }
-        // draw middle line
+        /* Nothing to draw */
+    }
+
+    /**
+     * Draw a line at the middle height of a rectangle
+     *
+     * @param rect
+     *            The rectangle
+     * @param gc
+     *            Graphics context
+     */
+    private void drawMidLine(Rectangle rect, GC gc) {
         gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE));
         int midy = rect.y + rect.height / 2;
         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)) {
@@ -1894,6 +2565,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);
@@ -1980,6 +2692,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();
@@ -2086,6 +2807,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);
     }
@@ -2116,12 +2843,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
 
     @Override
     public void mouseDown(MouseEvent e) {
-        if (fDragState != DRAG_NONE || null == fTimeProvider ||
-                fTimeProvider.getTime0() == fTimeProvider.getTime1() ||
-                getSize().x - fTimeProvider.getNameSpace() <= 0) {
+        if (fDragState != DRAG_NONE) {
             return;
         }
-        int idx;
         if (1 == e.button && (e.stateMask & SWT.MODIFIER_MASK) == 0) {
             int nameSpace = fTimeProvider.getNameSpace();
             if (nameSpace != 0 && isOverSplitLine(e.x)) {
@@ -2129,13 +2853,17 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 fDragButton = e.button;
                 fDragX = e.x;
                 fDragX0 = fDragX;
-                fTime0bak = fTimeProvider.getTime0();
-                fTime1bak = fTimeProvider.getTime1();
                 redraw();
                 updateCursor(e.x, e.stateMask);
                 return;
             }
         }
+        if (fTimeProvider == null ||
+                fTimeProvider.getTime0() == fTimeProvider.getTime1() ||
+                getSize().x - fTimeProvider.getNameSpace() <= 0) {
+            return;
+        }
+        int idx;
         if (1 == e.button && ((e.stateMask & SWT.MODIFIER_MASK) == 0 || (e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT)) {
             int nameSpace = fTimeProvider.getNameSpace();
             idx = getItemIndexAtY(e.y);
@@ -2207,48 +2935,63 @@ 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();
+            }
         }
     }
 
     @Override
     public void mouseUp(MouseEvent e) {
         if (fPendingMenuDetectEvent != null && e.button == 3) {
+            if ((fDragState == DRAG_ZOOM) && isInDragZoomMargin()) {
+                // Select entry and time event for single click
+                long time = getTimeAtX(fDragX0);
+                fTimeProvider.setSelectionRangeNotify(time, time, false);
+                int idx = getItemIndexAtY(e.y);
+                selectItem(idx, false);
+                fireSelectionChanged();
+            }
             menuDetected(fPendingMenuDetectEvent);
         }
         if (DRAG_NONE != fDragState) {
             setCapture(false);
             if (e.button == fDragButton && DRAG_TRACE_ITEM == fDragState) {
+                fDragState = DRAG_NONE;
                 if (fDragX != fDragX0) {
                     fTimeProvider.notifyStartFinishTime();
                 }
-                fDragState = DRAG_NONE;
             } else if (e.button == fDragButton && DRAG_SPLIT_LINE == fDragState) {
                 fDragState = DRAG_NONE;
                 redraw();
             }  else if (e.button == fDragButton && DRAG_SELECTION == fDragState) {
+                fDragState = DRAG_NONE;
                 if (fDragX == fDragX0) { // click without selecting anything
                     long time = getTimeAtX(e.x);
                     fTimeProvider.setSelectedTimeNotify(time, false);
                 } 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();
                 fTimeGraphScale.setDragRange(-1, -1);
             } else if (e.button == fDragButton && DRAG_ZOOM == fDragState) {
+                fDragState = DRAG_NONE;
                 int nameWidth = fTimeProvider.getNameSpace();
-                if (Math.max(fDragX, fDragX0) > nameWidth && fDragX != fDragX0) {
+                if ((Math.max(fDragX, fDragX0) > nameWidth) && !isInDragZoomMargin()) {
                     long time0 = getTimeAtX(fDragX0);
                     long time1 = getTimeAtX(fDragX);
                     if (time0 < time1) {
@@ -2259,7 +3002,6 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 } else {
                     redraw();
                 }
-                fDragState = DRAG_NONE;
                 fTimeGraphScale.setDragRange(-1, -1);
             }
         }
@@ -2286,57 +3028,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) {
@@ -2393,6 +3209,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
      */
     public void setItemHeight(int rowHeight) {
         this.fGlobalItemHeight = rowHeight;
+        for (Item item : fItemData.fItems) {
+            item.fItemHeight = rowHeight;
+        }
     }
 
     /**
@@ -2430,10 +3249,17 @@ public class TimeGraphControl extends TimeGraphBaseControl
     }
 
     /**
-     * @return The entries that are currently filtered out
+     * Set whether all time events with a duration shorter than one pixel should
+     * be blended in. If false, only the first such time event will be drawn and
+     * the subsequent time events in the same pixel will be discarded. The
+     * default value is false.
+     *
+     * @param blend
+     *            true if sub-pixel events should be blended, false otherwise.
+     * @since 1.1
      */
-    public List<ITimeGraphEntry> getFilteredOut() {
-        return fItemData.getFilteredOut();
+    public void setBlendSubPixelEvents(boolean blend) {
+        fBlendSubPixelEvents = blend;
     }
 
     @Override
@@ -2452,12 +3278,10 @@ public class TimeGraphControl extends TimeGraphBaseControl
 
     @Override
     public void setSelection(ISelection selection) {
-        if (selection instanceof TimeGraphSelection) {
-            TimeGraphSelection sel = (TimeGraphSelection) selection;
-            Object ob = sel.getFirstElement();
+        if (selection instanceof IStructuredSelection) {
+            Object ob = ((IStructuredSelection) selection).getFirstElement();
             if (ob instanceof ITimeGraphEntry) {
-                ITimeGraphEntry trace = (ITimeGraphEntry) ob;
-                selectItem(trace, false);
+                selectItem((ITimeGraphEntry) ob, false);
             }
         }
 
@@ -2466,7 +3290,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);
         }
@@ -2475,10 +3299,34 @@ 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);
     }
 
+    /**
+     * Returns this control's filters.
+     *
+     * @return an array of viewer filters
+     * @since 1.2
+     */
+    public @NonNull ViewerFilter[] getFilters() {
+        return Iterables.toArray(fFilters, ViewerFilter.class);
+    }
+
+    /**
+     * Sets the filters, replacing any previous filters.
+     *
+     * @param filters
+     *            an array of viewer filters, or null
+     * @since 1.2
+     */
+    public void setFilters(@NonNull ViewerFilter[] filters) {
+        fFilters.clear();
+        if (filters != null) {
+            fFilters.addAll(Arrays.asList(filters));
+        }
+    }
+
     @Override
     public void colorSettingsChanged(StateItem[] stateItems) {
         /* Destroy previous colors from the resource manager */
@@ -2499,13 +3347,11 @@ public class TimeGraphControl extends TimeGraphBaseControl
     }
 
     private class ItemData {
-        private final Map<ITimeGraphEntry, Item> fItemMap = new LinkedHashMap<>();
+        private Map<ITimeGraphEntry, Item> fItemMap = new LinkedHashMap<>();
         private Item[] fExpandedItems = new Item[0];
         private Item[] fItems = new Item[0];
         private ITimeGraphEntry fRootEntries[] = new ITimeGraphEntry[0];
         private List<ILinkEvent> fLinks = new ArrayList<>();
-        private boolean fEntryFilter[] = new boolean[0];
-        private final ArrayList<ITimeGraphEntry> fFilteredOut = new ArrayList<>();
 
         public ItemData() {
         }
@@ -2523,13 +3369,13 @@ public class TimeGraphControl extends TimeGraphBaseControl
         }
 
         public void refreshData() {
-            fItemMap.clear();
-            fFilteredOut.clear();
             ITimeGraphEntry selection = getSelectedTrace();
+            Map<ITimeGraphEntry, Item> itemMap = new LinkedHashMap<>();
             for (int i = 0; i < fRootEntries.length; i++) {
                 ITimeGraphEntry entry = fRootEntries[i];
-                refreshData(fItemMap, null, 0, entry);
+                refreshData(itemMap, null, 0, entry);
             }
+            fItemMap = itemMap;
             fItems = fItemMap.values().toArray(new Item[0]);
             updateExpandedItems();
             if (selection != null) {
@@ -2552,9 +3398,17 @@ 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.fExpanded = fAutoExpandLevel == ALL_LEVELS || level < fAutoExpandLevel;
+                Item oldItem = fItemMap.get(entry);
+                if (oldItem != null && oldItem.fHasChildren && level == oldItem.fLevel && entry.getParent() == oldItem.fEntry.getParent()) {
+                    /* existing items keep their old expanded state */
+                    item.fExpanded = oldItem.fExpanded;
+                } else {
+                    /* new items set the expanded state according to auto-expand level */
+                    item.fExpanded = fAutoExpandLevel == ALL_LEVELS || level < fAutoExpandLevel;
+                }
                 item.fHasChildren = true;
                 for (ITimeGraphEntry child : entry.getChildren()) {
                     refreshData(itemMap, item, level + 1, child);
@@ -2598,15 +3452,8 @@ public class TimeGraphControl extends TimeGraphBaseControl
 
         public void refreshData(ITimeGraphEntry[] entries) {
             if (entries == null) {
-                fEntryFilter = null;
                 fRootEntries = null;
             } else {
-                if (entries.length == 0) {
-                    fEntryFilter = null;
-                } else if (fEntryFilter == null || entries.length != fEntryFilter.length) {
-                    fEntryFilter = new boolean[entries.length];
-                    java.util.Arrays.fill(fEntryFilter, true);
-                }
                 fRootEntries = Arrays.copyOf(entries, entries.length);
             }
 
@@ -2625,14 +3472,6 @@ public class TimeGraphControl extends TimeGraphBaseControl
         public ITimeGraphEntry[] getEntries() {
             return fRootEntries;
         }
-
-        public boolean[] getEntryFilter() {
-            return fEntryFilter;
-        }
-
-        public List<ITimeGraphEntry> getFilteredOut() {
-            return fFilteredOut;
-        }
     }
 
     private class Item {
@@ -2664,39 +3503,74 @@ public class TimeGraphControl extends TimeGraphBaseControl
         if (null == fTimeProvider) {
             return;
         }
-        if (e.detail == SWT.MENU_MOUSE) {
+        /*
+         * This flag indicates if menu was prevented from being shown below and
+         * therefore must be made visible on callback from mouseUp().
+         */
+        boolean pendingEventCallback = fPendingMenuDetectEvent != null;
+        Point p = toControl(e.x, e.y);
+        if (e.detail == SWT.MENU_MOUSE && isOverTimeSpace(p.x, p.y)) {
             if (fPendingMenuDetectEvent == null) {
                 /* Feature in Linux. The MenuDetectEvent is received before mouseDown.
                  * Store the event and trigger it later just before handling mouseUp.
                  * 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 (fDragState != DRAG_ZOOM || !isInDragZoomMargin()) {
+                /*
+                 * Don't show the menu on mouseUp() if a drag zoom is in
+                 * progress with a drag range outside of the drag zoom margin,
+                 * or if any other drag operation, or none, is in progress.
+                 */
+                e.doit = false;
                 return;
             }
         } else {
             if (fDragState != DRAG_NONE) {
+                /*
+                 * Don't show the menu on keyboard menu or mouse menu outside of
+                 * the time space if any drag operation is in progress.
+                 */
+                e.doit = false;
                 return;
             }
         }
-        Point p = toControl(e.x, e.y);
         int idx = getItemIndexAtY(p.y);
         if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
             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 (pendingEventCallback && 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 (pendingEventCallback && e.doit && (menu != null)) {
+                        menu.setVisible(true);
+                    }
                 }
             }
-            e.data = entry;
-            fireMenuEventOnTimeGraphEntry(e);
         }
     }
 
@@ -2726,6 +3600,8 @@ public class TimeGraphControl extends TimeGraphBaseControl
     public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() {
         return new TmfTimeViewAlignmentInfo(getShell(), toDisplay(0, 0), fTimeProvider.getNameSpace());
     }
-}
-
 
+    private boolean isInDragZoomMargin() {
+        return (Math.abs(fDragX - fDragX0) < DRAG_MARGIN);
+    }
+}
This page took 0.049672 seconds and 5 git commands to generate.