tmf: Rename "Next/Previous Event" action to "Next/Previous State Change"
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / TimeGraphViewer.java
index 3cf12cda9075f41cb8bc50639e542ce5c8c688b6..2696a1d5b23e21e82d2255a7afb13e2976f5f7f1 100644 (file)
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * Copyright (c) 2007, 2015 Intel Corporation, Ericsson, others
+ * Copyright (c) 2007, 2016 Intel Corporation, Ericsson, others
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
 package org.eclipse.tracecompass.tmf.ui.widgets.timegraph;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
 import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuCreator;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.viewers.AbstractTreeViewer;
 import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.ITableLabelProvider;
 import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.jface.window.Window;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ControlAdapter;
 import org.eclipse.swt.events.ControlEvent;
@@ -38,6 +50,8 @@ import org.eclipse.swt.events.MouseWheelListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGBA;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridData;
@@ -47,32 +61,39 @@ import org.eclipse.swt.widgets.Control;
 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.Slider;
 import org.eclipse.tracecompass.internal.tmf.ui.Activator;
 import org.eclipse.tracecompass.internal.tmf.ui.ITmfImageConstants;
 import org.eclipse.tracecompass.internal.tmf.ui.Messages;
+import org.eclipse.tracecompass.internal.tmf.ui.dialogs.AddBookmarkDialog;
 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
 import org.eclipse.tracecompass.tmf.ui.views.ITmfTimeAligned;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.dialogs.ShowFilterDialogAction;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.dialogs.TimeGraphLegend;
 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.model.MarkerEvent;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.IMarkerAxisListener;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.ITimeDataProvider;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeDataProviderCyclesConverter;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphColorScheme;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphMarkerAxis;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphScale;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphTooltipHandler;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat;
+import org.eclipse.ui.PlatformUI;
 
 /**
  * Generic time graph viewer implementation
  *
  * @author Patrick Tasse, and others
  */
-public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
+public class TimeGraphViewer implements ITimeDataProvider, IMarkerAxisListener, SelectionListener {
 
     /** Constant indicating that all levels of the time graph should be expanded */
     public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS;
@@ -85,6 +106,11 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
     private static final long DEFAULT_FREQUENCY = 1000000000L;
     private static final int H_SCROLLBAR_MAX = Integer.MAX_VALUE - 1;
 
+    private static final ImageDescriptor ADD_BOOKMARK = Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_ADD_BOOKMARK);
+    private static final ImageDescriptor NEXT_BOOKMARK = Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_NEXT_BOOKMARK);
+    private static final ImageDescriptor PREVIOUS_BOOKMARK = Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_PREVIOUS_BOOKMARK);
+    private static final ImageDescriptor REMOVE_BOOKMARK = Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_REMOVE_BOOKMARK);
+
     private long fMinTimeInterval;
     private ITimeGraphEntry fSelectedEntry;
     private long fBeginTime = SWT.DEFAULT; // The user-specified bounds start time
@@ -105,18 +131,20 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
 
     private TimeGraphControl fTimeGraphCtrl;
     private TimeGraphScale fTimeScaleCtrl;
+    private TimeGraphMarkerAxis fMarkerAxisCtrl;
     private Slider fHorizontalScrollBar;
     private Slider fVerticalScrollBar;
-    private TimeGraphColorScheme fColorScheme;
+    private @NonNull TimeGraphColorScheme fColorScheme = new TimeGraphColorScheme();
     private Object fInputElement;
     private ITimeGraphContentProvider fTimeGraphContentProvider;
     private ITimeGraphPresentationProvider fTimeGraphProvider;
-    private ITimeDataProvider fTimeDataProvider = this;
+    private @NonNull ITimeDataProvider fTimeDataProvider = this;
     private TimeGraphTooltipHandler fToolTipHandler;
 
     private List<ITimeGraphSelectionListener> fSelectionListeners = new ArrayList<>();
     private List<ITimeGraphTimeListener> fTimeListeners = new ArrayList<>();
     private List<ITimeGraphRangeListener> fRangeListeners = new ArrayList<>();
+    private List<ITimeGraphBookmarkListener> fBookmarkListeners = new ArrayList<>();
 
     // Time format, using Epoch reference, Relative time format(default),
     // Number, or Cycles
@@ -138,6 +166,25 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
     private Action fFollowArrowFwdAction;
     private Action fFollowArrowBwdAction;
     private ShowFilterDialogAction fShowFilterDialogAction;
+    private Action fToggleBookmarkAction;
+    private Action fNextMarkerAction;
+    private Action fPreviousMarkerAction;
+    private MenuManager fMarkersMenu;
+
+    /** The list of bookmarks */
+    private final List<IMarkerEvent> fBookmarks = new ArrayList<>();
+
+    /** The list of marker categories */
+    private final List<String> fMarkerCategories = new ArrayList<>();
+
+    /** The set of hidden marker categories */
+    private final Set<String> fHiddenMarkerCategories = new HashSet<>();
+
+    /** The set of skipped marker categories */
+    private final Set<String> fSkippedMarkerCategories = new HashSet<>();
+
+    /** The list of markers */
+    private final List<IMarkerEvent> fMarkers = new ArrayList<>();
 
     private ListenerNotifier fListenerNotifier;
 
@@ -211,6 +258,17 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         }
     }
 
+    private final static class MarkerComparator implements Comparator<IMarkerEvent> {
+        @Override
+        public int compare(IMarkerEvent o1, IMarkerEvent o2) {
+            int res = Long.compare(o1.getTime(), o2.getTime());
+            if (res != 0) {
+                return res;
+            }
+            return Long.compare(o1.getDuration(), o2.getDuration());
+        }
+    }
+
     /**
      * Standard constructor.
      * <p>
@@ -262,7 +320,7 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      * Sets the tree columns for this time graph combo's filter dialog.
      *
      * @param columnNames the tree column names
-     * @since 2.0
+     * @since 1.2
      */
     public void setFilterColumns(String[] columnNames) {
         getShowFilterDialogAction().getFilterDialog().setColumnNames(columnNames);
@@ -272,7 +330,7 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      * Sets the tree content provider used by the filter dialog
      *
      * @param contentProvider the tree content provider
-     * @since 2.0
+     * @since 1.2
      */
     public void setFilterContentProvider(ITreeContentProvider contentProvider) {
         getShowFilterDialogAction().getFilterDialog().setContentProvider(contentProvider);
@@ -282,7 +340,7 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      * Sets the tree label provider used by the filter dialog
      *
      * @param labelProvider the tree label provider
-     * @since 2.0
+     * @since 1.2
      */
     public void setFilterLabelProvider(ITableLabelProvider labelProvider) {
         getShowFilterDialogAction().getFilterDialog().setLabelProvider(labelProvider);
@@ -304,6 +362,7 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
             setTopIndex(0);
             fSelectionBegin = SWT.DEFAULT;
             fSelectionEnd = SWT.DEFAULT;
+            updateMarkerActions();
             fSelectedEntry = null;
             refreshAllData(input);
         }
@@ -396,15 +455,20 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      */
     protected Control createDataViewer(Composite parent, int style) {
         loadOptions();
-        fColorScheme = new TimeGraphColorScheme();
         fDataViewer = new Composite(parent, style) {
             @Override
             public void redraw() {
                 fTimeScaleCtrl.redraw();
                 fTimeGraphCtrl.redraw();
+                fMarkerAxisCtrl.redraw();
                 super.redraw();
             }
         };
+        fDataViewer.addDisposeListener((e) -> {
+            if (fMarkersMenu != null) {
+                fMarkersMenu.dispose();
+            }
+        });
         GridLayout gl = new GridLayout(2, false);
         gl.marginHeight = fBorderWidth;
         gl.marginWidth = 0;
@@ -434,7 +498,14 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         fTimeScaleCtrl.addMouseWheelListener(new MouseWheelListener() {
             @Override
             public void mouseScrolled(MouseEvent e) {
-                fTimeGraphCtrl.zoom(e.count > 0);
+                if (e.count == 0) {
+                    return;
+                }
+                if ((e.stateMask & SWT.CTRL) != 0) {
+                    fTimeGraphCtrl.zoom(e.count > 0);
+                } else {
+                    fTimeGraphCtrl.horizontalScroll(e.count > 0);
+                }
             }
         });
 
@@ -447,21 +518,72 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         fTimeGraphCtrl.addMouseWheelListener(new MouseWheelListener() {
             @Override
             public void mouseScrolled(MouseEvent e) {
-                adjustVerticalScrollBar();
+                if (e.count == 0) {
+                    return;
+                }
+                /*
+                 * 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 not over the time graph control.
+                 */
+                Point ctrlParentCoords = fTimeAlignedComposite.toControl(fTimeGraphCtrl.toDisplay(e.x, e.y));
+                Point scrollBarParentCoords = fDataViewer.toControl(fTimeGraphCtrl.toDisplay(e.x, e.y));
+                if (fTimeGraphCtrl.getBounds().contains(ctrlParentCoords)) {
+                    /* the time graph control handles the event */
+                    adjustVerticalScrollBar();
+                } else if (fTimeScaleCtrl.getBounds().contains(ctrlParentCoords)
+                        || fMarkerAxisCtrl.getBounds().contains(ctrlParentCoords)
+                        || fHorizontalScrollBar.getBounds().contains(scrollBarParentCoords)) {
+                    if ((e.stateMask & SWT.CTRL) != 0) {
+                        fTimeGraphCtrl.zoom(e.count > 0);
+                    } else {
+                        fTimeGraphCtrl.horizontalScroll(e.count > 0);
+                    }
+                } else {
+                    /* over the vertical scroll bar or outside of the viewer */
+                    setTopIndex(getTopIndex() - e.count);
+                }
             }
         });
         fTimeGraphCtrl.addKeyListener(new KeyAdapter() {
             @Override
             public void keyPressed(KeyEvent e) {
-                if (e.character == '+') {
-                    zoomIn();
-                } else if (e.character == '-') {
-                    zoomOut();
+                if (e.keyCode == '.') {
+                    boolean extend = (e.stateMask & SWT.SHIFT) != 0;
+                    if (extend) {
+                        extendToNextMarker();
+                    } else {
+                        selectNextMarker();
+                    }
+                } else if (e.keyCode == ',') {
+                    boolean extend = (e.stateMask & SWT.SHIFT) != 0;
+                    if (extend) {
+                        extendToPrevMarker();
+                    } else {
+                        selectPrevMarker();
+                    }
                 }
                 adjustVerticalScrollBar();
             }
         });
 
+        fMarkerAxisCtrl = createTimeGraphMarkerAxis(fTimeAlignedComposite, fColorScheme, this);
+        fMarkerAxisCtrl.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, false));
+        fMarkerAxisCtrl.addMarkerAxisListener(this);
+        fMarkerAxisCtrl.addMouseWheelListener(new MouseWheelListener() {
+            @Override
+            public void mouseScrolled(MouseEvent e) {
+                if (e.count == 0) {
+                    return;
+                }
+                if ((e.stateMask & SWT.CTRL) != 0) {
+                    fTimeGraphCtrl.zoom(e.count > 0);
+                } else {
+                    fTimeGraphCtrl.horizontalScroll(e.count > 0);
+                }
+            }
+        });
+
         fVerticalScrollBar = new Slider(fDataViewer, SWT.VERTICAL | SWT.NO_FOCUS);
         fVerticalScrollBar.setLayoutData(new GridData(SWT.DEFAULT, SWT.FILL, false, true, 1, 1));
         fVerticalScrollBar.addSelectionListener(new SelectionAdapter() {
@@ -476,13 +598,16 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         fHorizontalScrollBar.addListener(SWT.MouseWheel, new Listener() {
             @Override
             public void handleEvent(Event event) {
-                if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
-                    getTimeGraphControl().zoom(event.count > 0);
-                } else {
-                    getTimeGraphControl().horizontalScroll(event.count > 0);
-                }
                 // don't handle the immediately following SWT.Selection event
                 event.doit = false;
+                if (event.count == 0) {
+                    return;
+                }
+                if ((event.stateMask & SWT.CTRL) != 0) {
+                    fTimeGraphCtrl.zoom(event.count > 0);
+                } else {
+                    fTimeGraphCtrl.horizontalScroll(event.count > 0);
+                }
             }
         });
         fHorizontalScrollBar.addListener(SWT.Selection, new Listener() {
@@ -519,17 +644,20 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         fDataViewer.update();
         adjustHorizontalScrollBar();
         adjustVerticalScrollBar();
+
+        fDataViewer.addDisposeListener((e) -> {
+            saveOptions();
+            fColorScheme.dispose();
+        });
+
         return fDataViewer;
     }
 
     /**
-     * Dispose the view.
+     * Dispose the time graph viewer.
      */
     public void dispose() {
-        saveOptions();
-        fTimeGraphCtrl.dispose();
         fDataViewer.dispose();
-        fColorScheme.dispose();
     }
 
     /**
@@ -546,6 +674,23 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         return new TimeGraphControl(parent, colors);
     }
 
+    /**
+     * Create a new time graph marker axis.
+     *
+     * @param parent
+     *            The parent composite object
+     * @param colorScheme
+     *            The color scheme to use
+     * @param timeProvider
+     *            The time data provider
+     * @return The new TimeGraphMarkerAxis
+     * @since 2.0
+     */
+    protected TimeGraphMarkerAxis createTimeGraphMarkerAxis(Composite parent,
+            @NonNull TimeGraphColorScheme colorScheme, @NonNull ITimeDataProvider timeProvider) {
+        return new TimeGraphMarkerAxis(parent, colorScheme, timeProvider);
+    }
+
     /**
      * Resize the controls
      */
@@ -662,6 +807,8 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         }
         fTimeGraphCtrl.refreshData(traces);
         fTimeScaleCtrl.redraw();
+        fMarkerAxisCtrl.redraw();
+        updateMarkerActions();
         adjustVerticalScrollBar();
     }
 
@@ -733,6 +880,11 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         }
         fTimeGraphCtrl.redraw();
         fTimeScaleCtrl.redraw();
+        fMarkerAxisCtrl.redraw();
+        /* force update the controls to keep them aligned */
+        fTimeScaleCtrl.update();
+        fMarkerAxisCtrl.update();
+        fTimeGraphCtrl.update();
     }
 
     @Override
@@ -814,6 +966,11 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         adjustHorizontalScrollBar();
         fTimeGraphCtrl.redraw();
         fTimeScaleCtrl.redraw();
+        fMarkerAxisCtrl.redraw();
+        /* force update the controls to keep them aligned */
+        fTimeScaleCtrl.update();
+        fMarkerAxisCtrl.update();
+        fTimeGraphCtrl.update();
     }
 
     @Override
@@ -822,6 +979,19 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         fTimeRangeFixed = false;
     }
 
+    /**
+     * @since 2.0
+     */
+    @Override
+    public void resetStartFinishTime(boolean notify) {
+        if (notify) {
+            setStartFinishTimeNotify(fTime0Bound, fTime1Bound);
+        } else {
+            setStartFinishTime(fTime0Bound, fTime1Bound);
+        }
+        fTimeRangeFixed = false;
+    }
+
     @Override
     public void setSelectedTimeNotify(long time, boolean ensureVisible) {
         setSelectedTimeInt(time, ensureVisible, true);
@@ -836,57 +1006,53 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         setSelectedTimeInt(time, ensureVisible, false);
     }
 
+    private void setSelectedTimeInt(long time, boolean ensureVisible, boolean doNotify) {
+        setSelectionRangeInt(time, time, ensureVisible, doNotify);
+    }
+
+    /**
+     * @since 1.2
+     */
     @Override
-    public void setSelectionRangeNotify(long beginTime, long endTime) {
-        long time0 = fTime0;
-        long time1 = fTime1;
-        long selectionBegin = fSelectionBegin;
-        long selectionEnd = fSelectionEnd;
-        fSelectionBegin = Math.max(fTime0Bound, Math.min(fTime1Bound, beginTime));
-        fSelectionEnd = Math.max(fTime0Bound, Math.min(fTime1Bound, endTime));
-        boolean changed = (selectionBegin != fSelectionBegin || selectionEnd != fSelectionEnd);
-        ensureVisible(fSelectionEnd);
-        fTimeGraphCtrl.redraw();
-        fTimeScaleCtrl.redraw();
-        if ((time0 != fTime0) || (time1 != fTime1)) {
-            notifyRangeListeners();
-        }
-        if (changed) {
-            notifyTimeListeners();
-        }
+    public void setSelectionRangeNotify(long beginTime, long endTime, boolean ensureVisible) {
+        setSelectionRangeInt(beginTime, endTime, ensureVisible, true);
     }
 
+    /**
+     * @since 1.2
+     */
     @Override
-    public void setSelectionRange(long beginTime, long endTime) {
+    public void setSelectionRange(long beginTime, long endTime, boolean ensureVisible) {
         /* if there is a pending time selection, ignore this one */
         if (fListenerNotifier != null && fListenerNotifier.hasTimeSelected()) {
             return;
         }
-        fSelectionBegin = Math.max(fTime0Bound, Math.min(fTime1Bound, beginTime));
-        fSelectionEnd = Math.max(fTime0Bound, Math.min(fTime1Bound, endTime));
-        fTimeGraphCtrl.redraw();
-        fTimeScaleCtrl.redraw();
+        setSelectionRangeInt(beginTime, endTime, ensureVisible, false);
     }
 
-    private void setSelectedTimeInt(long time, boolean ensureVisible, boolean doNotify) {
-        long selection = Math.max(fTime0Bound, Math.min(fTime1Bound, time));
+    private void setSelectionRangeInt(long beginTime, long endTime, boolean ensureVisible, boolean doNotify) {
         long time0 = fTime0;
         long time1 = fTime1;
+        long selectionBegin = fSelectionBegin;
+        long selectionEnd = fSelectionEnd;
+        fSelectionBegin = Math.max(fTime0Bound, Math.min(fTime1Bound, beginTime));
+        fSelectionEnd = Math.max(fTime0Bound, Math.min(fTime1Bound, endTime));
+        boolean changed = (selectionBegin != fSelectionBegin || selectionEnd != fSelectionEnd);
+
         if (ensureVisible) {
-            ensureVisible(selection);
+            ensureVisible(selectionBegin != fSelectionBegin ? fSelectionBegin : fSelectionEnd);
         }
+
         fTimeGraphCtrl.redraw();
         fTimeScaleCtrl.redraw();
-
-        boolean notifySelectedTime = (selection != fSelectionBegin || selection != fSelectionEnd);
-        fSelectionBegin = selection;
-        fSelectionEnd = selection;
+        fMarkerAxisCtrl.redraw();
+        updateMarkerActions();
 
         if ((time0 != fTime0) || (time1 != fTime1)) {
             notifyRangeListeners();
         }
 
-        if (doNotify && notifySelectedTime) {
+        if (doNotify && changed) {
             notifyTimeListeners();
         }
     }
@@ -1115,6 +1281,130 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         }
     }
 
+    /**
+     * Add a bookmark listener
+     *
+     * @param listener
+     *            The listener to add
+     * @since 2.0
+     */
+    public void addBookmarkListener(ITimeGraphBookmarkListener listener) {
+        fBookmarkListeners.add(listener);
+    }
+
+    /**
+     * Remove a bookmark listener
+     *
+     * @param listener
+     *            The listener to remove
+     * @since 2.0
+     */
+    public void removeBookmarkListener(ITimeGraphBookmarkListener listener) {
+        fBookmarkListeners.remove(listener);
+    }
+
+    private void fireBookmarkAdded(IMarkerEvent bookmark) {
+        TimeGraphBookmarkEvent event = new TimeGraphBookmarkEvent(this, bookmark);
+
+        for (ITimeGraphBookmarkListener listener : fBookmarkListeners) {
+            listener.bookmarkAdded(event);
+        }
+    }
+
+    private void fireBookmarkRemoved(IMarkerEvent bookmark) {
+        TimeGraphBookmarkEvent event = new TimeGraphBookmarkEvent(this, bookmark);
+
+        for (ITimeGraphBookmarkListener listener : fBookmarkListeners) {
+            listener.bookmarkRemoved(event);
+        }
+    }
+
+    /**
+     * Set the bookmarks list.
+     *
+     * @param bookmarks
+     *            The bookmarks list, or null
+     * @since 2.0
+     */
+    public void setBookmarks(List<IMarkerEvent> bookmarks) {
+        fBookmarks.clear();
+        if (bookmarks != null) {
+            fBookmarks.addAll(bookmarks);
+        }
+        updateMarkerList();
+        updateMarkerActions();
+    }
+
+    /**
+     * Get the bookmarks list.
+     *
+     * @return The bookmarks list
+     * @since 2.0
+     */
+    public List<IMarkerEvent> getBookmarks() {
+        return Collections.unmodifiableList(fBookmarks);
+    }
+
+    /**
+     * Set the list of marker categories.
+     *
+     * @param categories
+     *            The list of marker categories, or null
+     * @since 2.0
+     */
+    public void setMarkerCategories(List<String> categories) {
+        fMarkerCategories.clear();
+        if (categories != null) {
+            fMarkerCategories.addAll(categories);
+        }
+        fMarkerCategories.add(IMarkerEvent.BOOKMARKS);
+        fMarkerAxisCtrl.setMarkerCategories(fMarkerCategories);
+    }
+
+    /**
+     * @since 2.0
+     */
+    @Override
+    public void setMarkerCategoryVisible(String category, boolean visible) {
+        boolean changed = false;
+        if (visible) {
+            changed = fHiddenMarkerCategories.remove(category);
+        } else {
+            changed = fHiddenMarkerCategories.add(category);
+        }
+        if (changed) {
+            updateMarkerList();
+            updateMarkerActions();
+            getControl().redraw();
+        }
+    }
+
+    /**
+     * Set the markers list.
+     *
+     * @param markers
+     *            The markers list, or null
+     * @since 2.0
+     */
+    public void setMarkers(List<IMarkerEvent> markers) {
+        fMarkers.clear();
+        if (markers != null) {
+            fMarkers.addAll(markers);
+        }
+        updateMarkerList();
+        updateMarkerActions();
+    }
+
+    /**
+     * Get the markers list.
+     *
+     * @return The markers list, or null
+     * @since 2.0
+     */
+    public List<IMarkerEvent> getMarkers() {
+        return Collections.unmodifiableList(fMarkers);
+    }
+
     /**
      * Callback to set a selected event in the view
      *
@@ -1499,7 +1789,7 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      * @param entry
      *            The entry
      * @return true if the entry is expanded, false if collapsed
-     * @since 2.0
+     * @since 1.1
      */
     public boolean getExpandedState(ITimeGraphEntry entry) {
         return fTimeGraphCtrl.getExpandedState(entry);
@@ -1534,6 +1824,23 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         adjustVerticalScrollBar();
     }
 
+    /**
+     * Select an entry and reveal it
+     *
+     * @param entry
+     *            The entry to select
+     * @since 2.0
+     */
+    public void selectAndReveal(@NonNull ITimeGraphEntry entry) {
+        final ITimeGraphEntry parent = entry.getParent();
+        if (parent != null) {
+            fTimeGraphCtrl.setExpandedState(parent, true);
+        }
+        fSelectedEntry = entry;
+        fTimeGraphCtrl.selectItem(entry, false);
+        adjustVerticalScrollBar();
+    }
+
     /**
      * Get the number of expanded (visible) time graph entries. This includes
      * leafs and does not include filtered-out entries.
@@ -1632,9 +1939,9 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
                 }
             };
 
-            fNextEventAction.setText(Messages.TmfTimeGraphViewer_NextEventActionNameText);
-            fNextEventAction.setToolTipText(Messages.TmfTimeGraphViewer_NextEventActionToolTipText);
-            fNextEventAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_NEXT_EVENT));
+            fNextEventAction.setText(Messages.TmfTimeGraphViewer_NextStateChangeActionNameText);
+            fNextEventAction.setToolTipText(Messages.TmfTimeGraphViewer_NextStateChangeActionToolTipText);
+            fNextEventAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_NEXT_STATE_CHANGE));
         }
 
         return fNextEventAction;
@@ -1655,9 +1962,9 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
                 }
             };
 
-            fPrevEventAction.setText(Messages.TmfTimeGraphViewer_PreviousEventActionNameText);
-            fPrevEventAction.setToolTipText(Messages.TmfTimeGraphViewer_PreviousEventActionToolTipText);
-            fPrevEventAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_PREV_EVENT));
+            fPrevEventAction.setText(Messages.TmfTimeGraphViewer_PreviousStateChangeActionNameText);
+            fPrevEventAction.setToolTipText(Messages.TmfTimeGraphViewer_PreviousStateChangeActionToolTipText);
+            fPrevEventAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_PREV_STATE_CHANGE));
         }
 
         return fPrevEventAction;
@@ -1844,7 +2151,7 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      * Get the show filter dialog action.
      *
      * @return The Action object
-     * @since 2.0
+     * @since 1.2
      */
     public ShowFilterDialogAction getShowFilterDialogAction() {
         if (fShowFilterDialogAction == null) {
@@ -1853,6 +2160,310 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         return fShowFilterDialogAction;
     }
 
+    /**
+     * Get the toggle bookmark action.
+     *
+     * @return The Action object
+     * @since 2.0
+     */
+    public Action getToggleBookmarkAction() {
+        if (fToggleBookmarkAction == null) {
+            fToggleBookmarkAction = new Action() {
+                @Override
+                public void runWithEvent(Event event) {
+                    IMarkerEvent selectedBookmark = getBookmarkAtSelection();
+                    if (selectedBookmark == null) {
+                        final long time = Math.min(fSelectionBegin, fSelectionEnd);
+                        final long duration = Math.max(fSelectionBegin, fSelectionEnd) - time;
+                        final AddBookmarkDialog dialog = new AddBookmarkDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), null);
+                        if (dialog.open() == Window.OK) {
+                            final String label = dialog.getValue();
+                            final RGBA rgba = dialog.getColorValue();
+                            IMarkerEvent bookmark = new MarkerEvent(null, time, duration, IMarkerEvent.BOOKMARKS, rgba, label, true);
+                            fBookmarks.add(bookmark);
+                            updateMarkerList();
+                            updateMarkerActions();
+                            getControl().redraw();
+                            fireBookmarkAdded(bookmark);
+                        }
+                    } else {
+                        fBookmarks.remove(selectedBookmark);
+                        updateMarkerList();
+                        updateMarkerActions();
+                        getControl().redraw();
+                        fireBookmarkRemoved(selectedBookmark);
+                    }
+                }
+            };
+            fToggleBookmarkAction.setText(Messages.TmfTimeGraphViewer_BookmarkActionAddText);
+            fToggleBookmarkAction.setToolTipText(Messages.TmfTimeGraphViewer_BookmarkActionAddText);
+            fToggleBookmarkAction.setImageDescriptor(ADD_BOOKMARK);
+        }
+        return fToggleBookmarkAction;
+    }
+
+    /**
+     * Get the next marker action.
+     *
+     * @return The Action object
+     * @since 2.0
+     */
+    public Action getNextMarkerAction() {
+        if (fNextMarkerAction == null) {
+            fNextMarkerAction = new Action(Messages.TmfTimeGraphViewer_NextMarkerActionText, IAction.AS_DROP_DOWN_MENU) {
+                @Override
+                public void runWithEvent(Event event) {
+                    boolean extend = (event.stateMask & SWT.SHIFT) != 0;
+                    if (extend) {
+                        extendToNextMarker();
+                    } else {
+                        selectNextMarker();
+                    }
+                }
+            };
+            fNextMarkerAction.setToolTipText(Messages.TmfTimeGraphViewer_NextMarkerActionText);
+            fNextMarkerAction.setImageDescriptor(NEXT_BOOKMARK);
+            fNextMarkerAction.setMenuCreator(new IMenuCreator () {
+                Menu menu = null;
+                @Override
+                public void dispose() {
+                    if (menu != null) {
+                        menu.dispose();
+                        menu = null;
+                    }
+                }
+
+                @Override
+                public Menu getMenu(Control parent) {
+                    if (menu != null) {
+                        menu.dispose();
+                    }
+                    menu = new Menu(parent);
+                    for (String category : fMarkerCategories) {
+                        final Action action = new Action(category, IAction.AS_CHECK_BOX) {
+                            @Override
+                            public void runWithEvent(Event event) {
+                                if (isChecked()) {
+                                    fSkippedMarkerCategories.remove(getText());
+                                } else {
+                                    fSkippedMarkerCategories.add(getText());
+                                }
+                                updateMarkerActions();
+                            }
+                        };
+                        action.setEnabled(!fHiddenMarkerCategories.contains(category));
+                        action.setChecked(action.isEnabled() && !fSkippedMarkerCategories.contains(category));
+                        new ActionContributionItem(action).fill(menu, -1);
+                    }
+                    return menu;
+                }
+
+                @Override
+                public Menu getMenu(Menu parent) {
+                    return null;
+                }
+            });
+        }
+        return fNextMarkerAction;
+    }
+
+    /**
+     * Get the previous marker action.
+     *
+     * @return The Action object
+     * @since 2.0
+     */
+    public Action getPreviousMarkerAction() {
+        if (fPreviousMarkerAction == null) {
+            fPreviousMarkerAction = new Action() {
+                @Override
+                public void runWithEvent(Event event) {
+                    boolean extend = (event.stateMask & SWT.SHIFT) != 0;
+                    if (extend) {
+                        extendToPrevMarker();
+                    } else {
+                        selectPrevMarker();
+                    }
+                }
+            };
+            fPreviousMarkerAction.setText(Messages.TmfTimeGraphViewer_PreviousMarkerActionText);
+            fPreviousMarkerAction.setToolTipText(Messages.TmfTimeGraphViewer_PreviousMarkerActionText);
+            fPreviousMarkerAction.setImageDescriptor(PREVIOUS_BOOKMARK);
+        }
+        return fPreviousMarkerAction;
+    }
+
+    /**
+     * Get the show markers menu.
+     *
+     * @return The menu manager object
+     * @since 2.0
+     */
+    public MenuManager getMarkersMenu() {
+        if (fMarkersMenu == null) {
+            fMarkersMenu = new MenuManager(Messages.TmfTimeGraphViewer_ShowMarkersMenuText);
+            fMarkersMenu.setRemoveAllWhenShown(true);
+            fMarkersMenu.addMenuListener(new IMenuListener() {
+                @Override
+                public void menuAboutToShow(IMenuManager manager) {
+                    for (String category : fMarkerCategories) {
+                        final Action action = new Action(category, IAction.AS_CHECK_BOX) {
+                            @Override
+                            public void runWithEvent(Event event) {
+                                setMarkerCategoryVisible(getText(), isChecked());
+                            }
+                        };
+                        action.setChecked(!fHiddenMarkerCategories.contains(category));
+                        manager.add(action);
+                    }
+                }
+            });
+        }
+        return fMarkersMenu;
+    }
+
+    /**
+     * Select the next marker that begins at or after the current selection
+     * begin time. Markers that begin at the same time are ordered by end time.
+     */
+    private void selectNextMarker() {
+        List<IMarkerEvent> markers = getTimeGraphControl().getMarkers();
+        if (markers == null) {
+            return;
+        }
+        for (IMarkerEvent marker : markers) {
+            final long time = Math.min(fSelectionBegin, fSelectionEnd);
+            final long duration = Math.max(fSelectionBegin, fSelectionEnd) - time;
+            if ((marker.getTime() > time ||
+                    (marker.getTime() == time && marker.getDuration() > duration))
+                    && !fSkippedMarkerCategories.contains(marker.getCategory())) {
+                setSelectionRangeNotify(marker.getTime(), marker.getTime() + marker.getDuration(), false);
+                ensureVisible(marker.getTime());
+                notifyRangeListeners();
+                fTimeGraphCtrl.updateStatusLine();
+                return;
+            }
+        }
+    }
+
+    /**
+     * Select the previous marker that begins at or before the current selection
+     * begin time. Markers that begin at the same time are ordered by end time.
+     */
+    private void selectPrevMarker() {
+        List<IMarkerEvent> markers = getTimeGraphControl().getMarkers();
+        if (markers == null) {
+            return;
+        }
+        final long time = Math.min(fSelectionBegin, fSelectionEnd);
+        final long duration = Math.max(fSelectionBegin, fSelectionEnd) - time;
+        for (int i = markers.size() - 1; i >= 0; i--) {
+            IMarkerEvent marker = markers.get(i);
+            if ((marker.getTime() < time ||
+                    (marker.getTime() == time && marker.getDuration() < duration))
+                    && !fSkippedMarkerCategories.contains(marker.getCategory())) {
+                setSelectionRangeNotify(marker.getTime(), marker.getTime() + marker.getDuration(), false);
+                ensureVisible(marker.getTime());
+                notifyRangeListeners();
+                fTimeGraphCtrl.updateStatusLine();
+                return;
+            }
+        }
+    }
+
+    /**
+     * Extend the selection to the closest next marker end time.
+     */
+    private void extendToNextMarker() {
+        List<IMarkerEvent> markers = getTimeGraphControl().getMarkers();
+        if (markers == null) {
+            return;
+        }
+        IMarkerEvent nextMarker = null;
+        for (IMarkerEvent marker : markers) {
+            if (marker.getTime() + marker.getDuration() > fSelectionEnd
+                    && !fSkippedMarkerCategories.contains(marker.getCategory())
+                    && (nextMarker == null || marker.getTime() + marker.getDuration() < nextMarker.getTime() + nextMarker.getDuration())) {
+                nextMarker = marker;
+            }
+        }
+        if (nextMarker != null) {
+            setSelectionRangeNotify(fSelectionBegin, nextMarker.getTime() + nextMarker.getDuration(), true);
+            fTimeGraphCtrl.updateStatusLine();
+        }
+    }
+
+    /**
+     * Extend the selection to the closest previous marker start time.
+     */
+    private void extendToPrevMarker() {
+        List<IMarkerEvent> markers = getTimeGraphControl().getMarkers();
+        if (markers == null) {
+            return;
+        }
+        for (int i = markers.size() - 1; i >= 0; i--) {
+            IMarkerEvent marker = markers.get(i);
+            if (marker.getTime() < fSelectionEnd
+                    && !fSkippedMarkerCategories.contains(marker.getCategory())) {
+                setSelectionRangeNotify(fSelectionBegin, marker.getTime(), true);
+                fTimeGraphCtrl.updateStatusLine();
+                return;
+            }
+        }
+    }
+
+    private IMarkerEvent getBookmarkAtSelection() {
+        final long time = Math.min(fSelectionBegin, fSelectionEnd);
+        final long duration = Math.max(fSelectionBegin, fSelectionEnd) - time;
+        for (IMarkerEvent bookmark : fBookmarks) {
+            if (bookmark.getTime() == time && bookmark.getDuration() == duration) {
+                return bookmark;
+            }
+        }
+        return null;
+    }
+
+    private void updateMarkerActions() {
+        boolean enabled = fTime0Bound != SWT.DEFAULT || fTime1Bound != SWT.DEFAULT;
+        if (fToggleBookmarkAction != null) {
+            if (getBookmarkAtSelection() != null) {
+                fToggleBookmarkAction.setText(Messages.TmfTimeGraphViewer_BookmarkActionRemoveText);
+                fToggleBookmarkAction.setToolTipText(Messages.TmfTimeGraphViewer_BookmarkActionRemoveText);
+                fToggleBookmarkAction.setImageDescriptor(REMOVE_BOOKMARK);
+            } else {
+                fToggleBookmarkAction.setText(Messages.TmfTimeGraphViewer_BookmarkActionAddText);
+                fToggleBookmarkAction.setToolTipText(Messages.TmfTimeGraphViewer_BookmarkActionAddText);
+                fToggleBookmarkAction.setImageDescriptor(ADD_BOOKMARK);
+            }
+            fToggleBookmarkAction.setEnabled(enabled);
+        }
+        List<IMarkerEvent> markers = getTimeGraphControl().getMarkers();
+        if (markers == null) {
+            markers = Collections.emptyList();
+        }
+        if (fPreviousMarkerAction != null) {
+            fPreviousMarkerAction.setEnabled(enabled && !markers.isEmpty());
+        }
+        if (fNextMarkerAction != null) {
+            fNextMarkerAction.setEnabled(enabled && !markers.isEmpty());
+        }
+    }
+
+    private void updateMarkerList() {
+        List<IMarkerEvent> markers = new ArrayList<>();
+        for (IMarkerEvent marker : fMarkers) {
+            if (!fHiddenMarkerCategories.contains(marker.getCategory())) {
+                markers.add(marker);
+            }
+        }
+        if (!fHiddenMarkerCategories.contains(IMarkerEvent.BOOKMARKS)) {
+            markers.addAll(fBookmarks);
+        }
+        Collections.sort(markers, new MarkerComparator());
+        fTimeGraphCtrl.setMarkers(markers);
+        fMarkerAxisCtrl.setMarkers(markers);
+    }
+
     private void adjustHorizontalScrollBar() {
         long time0 = getTime0();
         long time1 = getTime1();
@@ -1927,7 +2538,7 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      * @param filter
      *            The filter object to be attached to the view
      */
-    public void addFilter(ViewerFilter filter) {
+    public void addFilter(@NonNull ViewerFilter filter) {
         fTimeGraphCtrl.addFilter(filter);
         refresh();
     }
@@ -1936,7 +2547,7 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      * @param filter
      *            The filter object to be attached to the view
      */
-    public void removeFilter(ViewerFilter filter) {
+    public void removeFilter(@NonNull ViewerFilter filter) {
         fTimeGraphCtrl.removeFilter(filter);
         refresh();
     }
@@ -1945,9 +2556,9 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      * Returns this viewer's filters.
      *
      * @return an array of viewer filters
-     * @since 2.0
+     * @since 1.2
      */
-    public ViewerFilter[] getFilters() {
+    public @NonNull ViewerFilter[] getFilters() {
         return fTimeGraphCtrl.getFilters();
     }
 
@@ -1957,9 +2568,9 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
      *
      * @param filters
      *            an array of viewer filters, or null
-     * @since 2.0
+     * @since 1.2
      */
-    public void setFilters(ViewerFilter[] filters) {
+    public void setFilters(@NonNull ViewerFilter[] filters) {
         fTimeGraphCtrl.setFilters(filters);
         refresh();
     }
This page took 0.035628 seconds and 5 git commands to generate.