/******************************************************************************* * Copyright (c) 2012, 2016 Ericsson, École Polytechnique de Montréal * * 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 * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Patrick Tasse - Initial API and implementation * Bernd Hufmann - Updated signal handling * Geneviève Bastien - Move code to provide base classes for time graph view * Marc-Andre Laperle - Add time zone preference * Geneviève Bastien - Add event links between entries *******************************************************************************/ package org.eclipse.tracecompass.tmf.ui.views.timegraph; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.GroupMarker; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.commands.ActionHandler; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MenuDetectListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGBA; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.tracecompass.common.core.NonNullUtils; import org.eclipse.tracecompass.common.core.log.TraceCompassLog; import org.eclipse.tracecompass.internal.tmf.ui.Activator; import org.eclipse.tracecompass.tmf.core.resources.ITmfMarker; import org.eclipse.tracecompass.tmf.core.signal.TmfMarkerEventSourceUpdatedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; import org.eclipse.tracecompass.tmf.core.signal.TmfTimestampFormatUpdateSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal; import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceAdapterManager; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceContext; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.ui.TmfUiRefreshHandler; import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo; import org.eclipse.tracecompass.tmf.ui.views.ITmfTimeAligned; import org.eclipse.tracecompass.tmf.ui.views.TmfView; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphBookmarkListener; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphContentProvider; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider2; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphRangeListener; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphTimeListener; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphBookmarkEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphCombo; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphContentProvider; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphPresentationProvider; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphRangeUpdateEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTimeEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphViewer; 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.IMarkerEventSource; 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.model.TimeGraphEntry; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.handlers.IHandlerActivation; import org.eclipse.ui.handlers.IHandlerService; /** * An abstract view all time graph views can inherit * * This view contains a time graph viewer. */ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeAligned, IResourceChangeListener { /** Constant indicating that all levels of the time graph should be expanded */ protected static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS; private static final Pattern RGBA_PATTERN = Pattern.compile("RGBA \\{(\\d+), (\\d+), (\\d+), (\\d+)\\}"); //$NON-NLS-1$ private static final Logger LOGGER = TraceCompassLog.getLogger(AbstractTimeGraphView.class); private static final String LOG_STRING_WITH_PARAM = "[TimeGraphView:%s] viewId=%s, %s"; //$NON-NLS-1$ private static final String LOG_STRING = "[TimeGraphView:%s] viewId=%s"; //$NON-NLS-1$ /** * Redraw state enum */ private enum State { IDLE, BUSY, PENDING } // ------------------------------------------------------------------------ // Fields // ------------------------------------------------------------------------ /** The time graph viewer */ private TimeGraphViewer fTimeGraphViewer; private AtomicInteger fDirty = new AtomicInteger(); private final Object fZoomThreadResultLock = new Object(); /** The selected trace */ private ITmfTrace fTrace; /** The selected trace editor file*/ private IFile fEditorFile; /** The timegraph entry list */ private List<TimeGraphEntry> fEntryList; /** The trace to entry list hash map */ private final Map<ITmfTrace, List<TimeGraphEntry>> fEntryListMap = new HashMap<>(); /** The trace to filters hash map */ private final Map<ITmfTrace, @NonNull ViewerFilter[]> fFiltersMap = new HashMap<>(); /** The trace to view context hash map */ private final Map<ITmfTrace, ViewContext> fViewContext = new HashMap<>(); /** The trace to marker event sources hash map */ private final Map<ITmfTrace, List<IMarkerEventSource>> fMarkerEventSourcesMap = new HashMap<>(); /** The trace to build thread hash map */ private final Map<ITmfTrace, Job> fBuildJobMap = new HashMap<>(); /** The start time */ private long fStartTime = SWT.DEFAULT; /** The end time */ private long fEndTime = SWT.DEFAULT; /** The display width */ private final int fDisplayWidth; /** The zoom thread */ private ZoomThread fZoomThread; /** The next resource action */ private Action fNextResourceAction; /** The previous resource action */ private Action fPreviousResourceAction; /** A comparator class */ private Comparator<ITimeGraphEntry> fEntryComparator = null; /** The redraw state used to prevent unnecessary queuing of display runnables */ private State fRedrawState = State.IDLE; /** The redraw synchronization object */ private final Object fSyncObj = new Object(); /** The presentation provider for this view */ private final TimeGraphPresentationProvider fPresentation; /** The tree column label array, or null for a single default column */ private String[] fColumns; private Comparator<ITimeGraphEntry>[] fColumnComparators; /** The time graph label provider */ private ITableLabelProvider fLabelProvider = new TreeLabelProvider(); /** The time graph content provider */ private @NonNull ITimeGraphContentProvider fTimeGraphContentProvider = new TimeGraphContentProvider(); /** The relative weight of the time graph viewer parts */ private int[] fWeight = { 1, 3 }; /** The filter column label array, or null if filter is not used */ private String[] fFilterColumns; /** The filter content provider, or null if filter is not used */ private ITreeContentProvider fFilterContentProvider; /** The filter label provider, or null if filter is not used */ private TreeLabelProvider fFilterLabelProvider; private int fAutoExpandLevel = ALL_LEVELS; /** The default column index for sorting */ private int fInitialSortColumn = 0; /** The default column index for sorting */ private int fCurrentSortColumn = 0; /** The current sort direction */ private int fSortDirection = SWT.DOWN; /** Flag to indicate to reveal selection */ private volatile boolean fIsRevealSelection = false; /** * Menu Manager for context-sensitive menu for time graph entries. * This will be used on the name space of the time graph viewer. */ private final @NonNull MenuManager fEntryMenuManager = new MenuManager(); /** Time Graph View part listener */ private TimeGraphPartListener fPartListener; /** Action for the find command. There is only one for all Time Graph views */ private static final ShowFindDialogAction FIND_ACTION = new ShowFindDialogAction(); /** The find action handler */ private ActionHandler fFindActionHandler; /** The find handler activation */ private IHandlerActivation fFindHandlerActivation; /** The find target to use */ private final FindTarget fFindTarget; // ------------------------------------------------------------------------ // Classes // ------------------------------------------------------------------------ /** * Base class to provide the labels for the tree viewer. Views extending * this class typically need to override the getColumnText method if they * have more than one column to display */ protected static class TreeLabelProvider implements ITableLabelProvider, ILabelProvider { @Override public void addListener(ILabelProviderListener listener) { } @Override public void dispose() { } @Override public boolean isLabelProperty(Object element, String property) { return false; } @Override public void removeListener(ILabelProviderListener listener) { } @Override public Image getColumnImage(Object element, int columnIndex) { return null; } @Override public String getColumnText(Object element, int columnIndex) { TimeGraphEntry entry = (TimeGraphEntry) element; if (columnIndex == 0) { return entry.getName(); } return new String(); } @Override public Image getImage(Object element) { return null; } @Override public String getText(Object element) { TimeGraphEntry entry = (TimeGraphEntry) element; return entry.getName(); } } // TODO: This can implement ICoreRunnable once support for Eclipse 4.5. is not necessary anymore. private class BuildRunnable { private final @NonNull ITmfTrace fBuildTrace; private final @NonNull ITmfTrace fParentTrace; public BuildRunnable(final @NonNull ITmfTrace trace, final @NonNull ITmfTrace parentTrace) { fBuildTrace = trace; fParentTrace = parentTrace; } public void run(IProgressMonitor monitor) { LOGGER.info(() -> getLogMessage("BuildThreadStart", "trace=" + fBuildTrace.getName())); //$NON-NLS-1$ //$NON-NLS-2$ buildEntryList(fBuildTrace, fParentTrace, NonNullUtils.checkNotNull(monitor)); synchronized (fBuildJobMap) { fBuildJobMap.remove(fBuildTrace); } LOGGER.info(() -> getLogMessage("BuildThreadEnd", null)); //$NON-NLS-1$ } } /** * Zoom thread * @since 1.1 */ protected abstract class ZoomThread extends Thread { private final long fZoomStartTime; private final long fZoomEndTime; private final long fResolution; private final @NonNull IProgressMonitor fMonitor; /** * Constructor * * @param startTime * the start time * @param endTime * the end time * @param resolution * the resolution */ public ZoomThread(long startTime, long endTime, long resolution) { super(AbstractTimeGraphView.this.getName() + " zoom"); //$NON-NLS-1$ fZoomStartTime = startTime; fZoomEndTime = endTime; fResolution = resolution; fMonitor = new NullProgressMonitor(); } /** * @return the zoom start time */ public long getZoomStartTime() { return fZoomStartTime; } /** * @return the zoom end time */ public long getZoomEndTime() { return fZoomEndTime; } /** * @return the resolution */ public long getResolution() { return fResolution; } /** * @return the monitor */ public @NonNull IProgressMonitor getMonitor() { return fMonitor; } /** * Cancel the zoom thread */ public void cancel() { fMonitor.setCanceled(true); } @Override public final void run() { LOGGER.info(() -> getLogMessage("ZoomThreadStart", "start=" + fZoomStartTime + ", end=" + fZoomEndTime)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ doRun(); fDirty.decrementAndGet(); LOGGER.info(() -> getLogMessage("ZoomThreadEnd", null)); //$NON-NLS-1$ } /** * Applies the results of the ZoomThread calculations. * * Note: This method makes sure that only the results of the last * created ZoomThread are applied. * * @param runnable * the code to run in order to apply the results * @since 2.0 */ protected void applyResults(Runnable runnable) { synchronized (fZoomThreadResultLock) { if (this == fZoomThread) { runnable.run(); } } } /** * Run the zoom operation. * @since 2.0 */ public abstract void doRun(); } private class ZoomThreadByEntry extends ZoomThread { private final @NonNull List<TimeGraphEntry> fZoomEntryList; public ZoomThreadByEntry(@NonNull List<TimeGraphEntry> entryList, long startTime, long endTime, long resolution) { super(startTime, endTime, resolution); fZoomEntryList = entryList; } @Override public void doRun() { LOGGER.config(() -> getLogMessage("ZoomThreadGettingStates", null)); //$NON-NLS-1$ for (TimeGraphEntry entry : fZoomEntryList) { if (getMonitor().isCanceled()) { LOGGER.info(() -> getLogMessage("ZoomThreadCanceled", null)); //$NON-NLS-1$ return; } if (entry == null) { break; } zoom(entry, getMonitor()); } /* Refresh the arrows when zooming */ LOGGER.config(() -> getLogMessage("ZoomThreadGettingLinks", null)); //$NON-NLS-1$ List<ILinkEvent> events = getLinkList(getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor()); /* Refresh the view-specific markers when zooming */ LOGGER.config(() -> getLogMessage("ZoomThreadGettingMarkers", null)); //$NON-NLS-1$ List<IMarkerEvent> markers = new ArrayList<>(getViewMarkerList(getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor())); /* Refresh the trace-specific markers when zooming */ markers.addAll(getTraceMarkerList(getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor())); applyResults(() -> { if (events != null) { fTimeGraphViewer.setLinks(events); } fTimeGraphViewer.setMarkerCategories(getMarkerCategories()); fTimeGraphViewer.setMarkers(markers); redraw(); }); } private void zoom(@NonNull TimeGraphEntry entry, @NonNull IProgressMonitor monitor) { if (getZoomStartTime() <= fStartTime && getZoomEndTime() >= fEndTime) { applyResults(() -> { entry.setZoomedEventList(null); }); } else { List<ITimeEvent> zoomedEventList = getEventList(entry, getZoomStartTime(), getZoomEndTime(), getResolution(), monitor); if (zoomedEventList != null) { applyResults(() -> { entry.setZoomedEventList(zoomedEventList); }); } } redraw(); for (TimeGraphEntry child : entry.getChildren()) { if (monitor.isCanceled()) { return; } zoom(child, monitor); } } } // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Constructs a time graph view that contains a time graph viewer. * * By default, the view uses a single default column in the name space that * shows the time graph entry name. To use multiple columns and/or * customized label texts, the subclass constructor must call * {@link #setTreeColumns(String[])} and/or * {@link #setTreeLabelProvider(TreeLabelProvider)}. * * @param id * The id of the view * @param pres * The presentation provider */ public AbstractTimeGraphView(String id, TimeGraphPresentationProvider pres) { super(id); fPresentation = pres; fDisplayWidth = Display.getDefault().getBounds().width; fFindTarget = new FindTarget(); } // ------------------------------------------------------------------------ // Getters and setters // ------------------------------------------------------------------------ /** * Getter for the time graph combo. No longer used. * * @return null * @deprecated Use {@link #getTimeGraphViewer()} instead. */ @Deprecated protected TimeGraphCombo getTimeGraphCombo() { return null; } /** * Getter for the time graph viewer * * @return The time graph viewer */ protected TimeGraphViewer getTimeGraphViewer() { return fTimeGraphViewer; } /** * Getter for the presentation provider * * @return The time graph presentation provider */ protected ITimeGraphPresentationProvider2 getPresentationProvider() { return fPresentation; } /** * Sets the tree column labels. * <p> * This should be called from the constructor. * * @param columns * The array of tree column labels */ protected void setTreeColumns(final String[] columns) { setTreeColumns(columns, null, 0); } /** * Sets the tree column labels. * <p> * This should be called from the constructor. * * @param columns * The array of tree column labels * @param comparators * An array of column comparators for sorting of columns when * clicking on column header * @param initialSortColumn * Index of column to sort initially * @since 2.0 */ protected void setTreeColumns(final String[] columns, final Comparator<ITimeGraphEntry>[] comparators, int initialSortColumn) { checkPartNotCreated(); fColumns = columns; fColumnComparators = comparators; fInitialSortColumn = initialSortColumn; } /** * Sets the tree label provider. * <p> * This should be called from the constructor. * * @param tlp * The tree label provider */ protected void setTreeLabelProvider(final TreeLabelProvider tlp) { checkPartNotCreated(); fLabelProvider = tlp; } /** * Sets the time graph content provider. * <p> * This should be called from the constructor. * * @param tgcp * The time graph content provider * @since 1.0 */ protected void setTimeGraphContentProvider(final @NonNull ITimeGraphContentProvider tgcp) { checkPartNotCreated(); fTimeGraphContentProvider = tgcp; } /** * Sets the relative weight of each part of the time graph viewer. The first * number is the name space width, and the second number is the time space * width. * * @param weights * The array of relative weights of each part of the viewer */ protected void setWeight(final int[] weights) { fWeight = weights; if (fTimeGraphViewer != null) { fTimeGraphViewer.setWeights(weights); } } /** * Sets the filter column labels. * <p> * This should be called from the constructor. * * @param filterColumns * The array of filter column labels */ protected void setFilterColumns(final String[] filterColumns) { checkPartNotCreated(); fFilterColumns = filterColumns; } /** * Sets the filter content provider. * <p> * This should be called from the constructor. * * @param contentProvider * The filter content provider * @since 1.2 */ protected void setFilterContentProvider(final ITreeContentProvider contentProvider) { checkPartNotCreated(); fFilterContentProvider = contentProvider; } /** * Sets the filter label provider. * <p> * This should be called from the constructor. * * @param labelProvider * The filter label provider */ protected void setFilterLabelProvider(final TreeLabelProvider labelProvider) { checkPartNotCreated(); fFilterLabelProvider = labelProvider; } private void checkPartNotCreated() { if (getParentComposite() != null) { throw new IllegalStateException("This method must be called before createPartControl."); //$NON-NLS-1$ } } /** * Gets the display width * * @return the display width */ protected int getDisplayWidth() { return fDisplayWidth; } /** * Gets the comparator for the entries * * @return The entry comparator */ protected Comparator<ITimeGraphEntry> getEntryComparator() { return fEntryComparator; } /** * Sets the comparator class for the entries. * <p> * This comparator will apply recursively to entries that implement * {@link TimeGraphEntry#sortChildren(Comparator)}. * * @param comparator * A comparator object */ protected void setEntryComparator(final Comparator<ITimeGraphEntry> comparator) { fEntryComparator = comparator; } /** * Gets the trace displayed in the view * * @return The trace */ protected ITmfTrace getTrace() { return fTrace; } /** * Gets the start time * * @return The start time */ protected long getStartTime() { return fStartTime; } /** * Sets the start time * * @param time * The start time */ protected void setStartTime(long time) { fStartTime = time; } /** * Gets the end time * * @return The end time */ protected long getEndTime() { return fEndTime; } /** * Sets the end time * * @param time * The end time */ protected void setEndTime(long time) { fEndTime = time; } /** * Sets the auto-expand level to be used for the input of the view. The * value 0 means that there is no auto-expand; 1 means that top-level * elements are expanded, but not their children; 2 means that top-level * elements 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 */ protected void setAutoExpandLevel(int level) { fAutoExpandLevel = level; if (fTimeGraphViewer != null) { fTimeGraphViewer.setAutoExpandLevel(level); } } /** * Gets the entry list for a trace * * @param trace * the trace * * @return the entry list map */ protected List<TimeGraphEntry> getEntryList(ITmfTrace trace) { synchronized (fEntryListMap) { return fEntryListMap.get(trace); } } /** * Adds a trace entry list to the entry list map * * @param trace * the trace to add * @param list * the list of time graph entries */ protected void putEntryList(ITmfTrace trace, List<TimeGraphEntry> list) { synchronized (fEntryListMap) { fEntryListMap.put(trace, new CopyOnWriteArrayList<>(list)); } } /** * Adds a list of entries to a trace's entry list * * @param trace * the trace * @param list * the list of time graph entries to add */ protected void addToEntryList(ITmfTrace trace, List<TimeGraphEntry> list) { synchronized (fEntryListMap) { List<TimeGraphEntry> entryList = fEntryListMap.get(trace); if (entryList == null) { fEntryListMap.put(trace, new CopyOnWriteArrayList<>(list)); } else { entryList.addAll(list); } } } /** * Removes a list of entries from a trace's entry list * * @param trace * the trace * @param list * the list of time graph entries to remove */ protected void removeFromEntryList(ITmfTrace trace, List<TimeGraphEntry> list) { synchronized (fEntryListMap) { List<TimeGraphEntry> entryList = fEntryListMap.get(trace); if (entryList != null) { entryList.removeAll(list); } } } /** * Text for the "next" button * * @return The "next" button text */ protected String getNextText() { return Messages.AbstractTimeGraphtView_NextText; } /** * Tooltip for the "next" button * * @return Tooltip for the "next" button */ protected String getNextTooltip() { return Messages.AbstractTimeGraphView_NextTooltip; } /** * Text for the "Previous" button * * @return The "Previous" button text */ protected String getPrevText() { return Messages.AbstractTimeGraphView_PreviousText; } /** * Tooltip for the "previous" button * * @return Tooltip for the "previous" button */ protected String getPrevTooltip() { return Messages.AbstractTimeGraphView_PreviousTooltip; } FindTarget getFindTarget() { return fFindTarget; } /** * Formats a log message for this class * * @param event * The event to log, that will be appended to the class name to * make the full event name * @param parameters * The string of extra parameters to add to the log message, in * the format name=value[, name=value]*, or <code>null</code> for * no params * @return The complete log message for this class */ private String getLogMessage(String event, @Nullable String parameters) { if (parameters == null) { return String.format(LOG_STRING, event, getViewId()); } return String.format(LOG_STRING_WITH_PARAM, event, getViewId(), parameters); } // ------------------------------------------------------------------------ // ViewPart // ------------------------------------------------------------------------ @Override public void createPartControl(Composite parent) { super.createPartControl(parent); fTimeGraphViewer = new TimeGraphViewer(parent, SWT.NONE); if (fLabelProvider != null) { fTimeGraphViewer.setTimeGraphLabelProvider(fLabelProvider); } if (fColumns != null) { fTimeGraphViewer.setColumns(fColumns); if (fColumnComparators != null) { createColumnSelectionListener(fTimeGraphViewer.getTree()); } } fTimeGraphViewer.setTimeGraphContentProvider(fTimeGraphContentProvider); fTimeGraphViewer.setFilterContentProvider(fFilterContentProvider != null ? fFilterContentProvider : fTimeGraphContentProvider); fTimeGraphViewer.setFilterLabelProvider(fFilterLabelProvider); fTimeGraphViewer.setFilterColumns(fFilterColumns); fTimeGraphViewer.setTimeGraphProvider(fPresentation); fTimeGraphViewer.setAutoExpandLevel(fAutoExpandLevel); fTimeGraphViewer.setWeights(fWeight); fTimeGraphViewer.addRangeListener(new ITimeGraphRangeListener() { @Override public void timeRangeUpdated(TimeGraphRangeUpdateEvent event) { final long startTime = event.getStartTime(); final long endTime = event.getEndTime(); TmfTimeRange range = new TmfTimeRange(TmfTimestamp.fromNanos(startTime), TmfTimestamp.fromNanos(endTime)); broadcast(new TmfWindowRangeUpdatedSignal(AbstractTimeGraphView.this, range)); startZoomThread(startTime, endTime); } }); fTimeGraphViewer.addTimeListener(new ITimeGraphTimeListener() { @Override public void timeSelected(TimeGraphTimeEvent event) { ITmfTimestamp startTime = TmfTimestamp.fromNanos(event.getBeginTime()); ITmfTimestamp endTime = TmfTimestamp.fromNanos(event.getEndTime()); broadcast(new TmfSelectionRangeUpdatedSignal(AbstractTimeGraphView.this, startTime, endTime)); } }); fTimeGraphViewer.addBookmarkListener(new ITimeGraphBookmarkListener() { @Override public void bookmarkAdded(final TimeGraphBookmarkEvent event) { try { ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() { @Override public void run(IProgressMonitor monitor) throws CoreException { IMarkerEvent bookmark = event.getBookmark(); IMarker marker = fEditorFile.createMarker(IMarker.BOOKMARK); marker.setAttribute(IMarker.MESSAGE, bookmark.getLabel()); marker.setAttribute(ITmfMarker.MARKER_TIME, Long.toString(bookmark.getTime())); if (bookmark.getDuration() > 0) { marker.setAttribute(ITmfMarker.MARKER_DURATION, Long.toString(bookmark.getDuration())); marker.setAttribute(IMarker.LOCATION, NLS.bind(org.eclipse.tracecompass.internal.tmf.ui.Messages.TmfMarker_LocationTimeRange, TmfTimestamp.fromNanos(bookmark.getTime()), TmfTimestamp.fromNanos(bookmark.getTime() + bookmark.getDuration()))); } else { marker.setAttribute(IMarker.LOCATION, NLS.bind(org.eclipse.tracecompass.internal.tmf.ui.Messages.TmfMarker_LocationTime, TmfTimestamp.fromNanos(bookmark.getTime()))); } marker.setAttribute(ITmfMarker.MARKER_COLOR, bookmark.getColor().toString()); } }, null); } catch (CoreException e) { Activator.getDefault().logError(e.getMessage()); } } @Override public void bookmarkRemoved(TimeGraphBookmarkEvent event) { try { IMarkerEvent bookmark = event.getBookmark(); IMarker[] markers = fEditorFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO); for (IMarker marker : markers) { if (bookmark.getLabel().equals(marker.getAttribute(IMarker.MESSAGE)) && Long.toString(bookmark.getTime()).equals(marker.getAttribute(ITmfMarker.MARKER_TIME, (String) null)) && Long.toString(bookmark.getDuration()).equals(marker.getAttribute(ITmfMarker.MARKER_DURATION, Long.toString(0))) && bookmark.getColor().toString().equals(marker.getAttribute(ITmfMarker.MARKER_COLOR))) { marker.delete(); break; } } } catch (CoreException e) { Activator.getDefault().logError(e.getMessage()); } } }); fTimeGraphViewer.setTimeFormat(TimeFormat.CALENDAR); IStatusLineManager statusLineManager = getViewSite().getActionBars().getStatusLineManager(); fTimeGraphViewer.getTimeGraphControl().setStatusLineManager(statusLineManager); // View Action Handling makeActions(); contributeToActionBars(); ITmfTrace trace = TmfTraceManager.getInstance().getActiveTrace(); if (trace != null) { traceSelected(new TmfTraceSelectedSignal(this, trace)); } // make selection available to other views getSite().setSelectionProvider(fTimeGraphViewer.getSelectionProvider()); ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); createContextMenu(); fPartListener = new TimeGraphPartListener(); getSite().getPage().addPartListener(fPartListener); } @Override public void setFocus() { fTimeGraphViewer.setFocus(); } @Override public void dispose() { super.dispose(); synchronized (fBuildJobMap) { fBuildJobMap.values().forEach(buildJob -> { buildJob.cancel(); }); } if (fZoomThread != null) { fZoomThread.cancel(); } ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); getSite().getPage().removePartListener(fPartListener); } /** * @since 2.0 */ @Override public void resourceChanged(final IResourceChangeEvent event) { for (final IMarkerDelta delta : event.findMarkerDeltas(IMarker.BOOKMARK, false)) { if (delta.getResource().equals(fEditorFile)) { fTimeGraphViewer.setBookmarks(refreshBookmarks(fEditorFile)); redraw(); return; } } } private static List<IMarkerEvent> refreshBookmarks(final IFile editorFile) { List<IMarkerEvent> bookmarks = new ArrayList<>(); if (editorFile == null || !editorFile.exists()) { return bookmarks; } try { IMarker[] markers = editorFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO); for (IMarker marker : markers) { String label = marker.getAttribute(IMarker.MESSAGE, (String) null); String time = marker.getAttribute(ITmfMarker.MARKER_TIME, (String) null); String duration = marker.getAttribute(ITmfMarker.MARKER_DURATION, Long.toString(0)); String rgba = marker.getAttribute(ITmfMarker.MARKER_COLOR, (String) null); if (label != null && time != null && rgba != null) { Matcher matcher = RGBA_PATTERN.matcher(rgba); if (matcher.matches()) { try { int red = Integer.valueOf(matcher.group(1)); int green = Integer.valueOf(matcher.group(2)); int blue = Integer.valueOf(matcher.group(3)); int alpha = Integer.valueOf(matcher.group(4)); RGBA color = new RGBA(red, green, blue, alpha); bookmarks.add(new MarkerEvent(null, Long.valueOf(time), Long.valueOf(duration), IMarkerEvent.BOOKMARKS, color, label, true)); } catch (NumberFormatException e) { Activator.getDefault().logError(e.getMessage()); } } } } } catch (CoreException e) { Activator.getDefault().logError(e.getMessage()); } return bookmarks; } // ------------------------------------------------------------------------ // Signal handlers // ------------------------------------------------------------------------ /** * Handler for the trace opened signal. * * @param signal * The incoming signal */ @TmfSignalHandler public void traceOpened(TmfTraceOpenedSignal signal) { loadTrace(signal.getTrace()); } /** * Handler for the trace selected signal * * @param signal * The incoming signal */ @TmfSignalHandler public void traceSelected(final TmfTraceSelectedSignal signal) { if (signal.getTrace() == fTrace) { return; } loadTrace(signal.getTrace()); } /** * Trace is closed: clear the data structures and the view * * @param signal * the signal received */ @TmfSignalHandler public void traceClosed(final TmfTraceClosedSignal signal) { resetView(signal.getTrace()); if (signal.getTrace() == fTrace) { fTrace = null; fEditorFile = null; setStartTime(SWT.DEFAULT); setEndTime(SWT.DEFAULT); refresh(); } } /** * Handler for the selection range signal. * * @param signal * The signal that's received * @since 1.0 */ @TmfSignalHandler public void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) { if (signal.getSource() == this || fTrace == null) { return; } final long beginTime = signal.getBeginTime().toNanos(); final long endTime = signal.getEndTime().toNanos(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (fTimeGraphViewer.getControl().isDisposed()) { return; } if (beginTime == endTime) { fTimeGraphViewer.setSelectedTime(beginTime, true); } else { fTimeGraphViewer.setSelectionRange(beginTime, endTime, true); } synchingToTime(fTimeGraphViewer.getSelectionBegin()); } }); } /** * Handler for the window range signal. * * @param signal * The signal that's received * @since 1.0 */ @TmfSignalHandler public void windowRangeUpdated(final TmfWindowRangeUpdatedSignal signal) { if (signal.getSource() == this || fTrace == null) { return; } if (signal.getCurrentRange().getIntersection(fTrace.getTimeRange()) == null) { return; } final long startTime = signal.getCurrentRange().getStartTime().toNanos(); final long endTime = signal.getCurrentRange().getEndTime().toNanos(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (fTimeGraphViewer.getControl().isDisposed()) { return; } fTimeGraphViewer.setStartFinishTime(startTime, endTime); startZoomThread(startTime, endTime); } }); } /** * @param signal the format of the timestamps was updated. */ @TmfSignalHandler public void updateTimeFormat( final TmfTimestampFormatUpdateSignal signal){ fTimeGraphViewer.refresh(); } /** * A marker event source has been updated * * @param signal * the signal * @since 2.1 */ @TmfSignalHandler public void markerEventSourceUpdated(final TmfMarkerEventSourceUpdatedSignal signal) { getTimeGraphViewer().setMarkerCategories(getMarkerCategories()); getTimeGraphViewer().setMarkers(null); refresh(); } // ------------------------------------------------------------------------ // Internal // ------------------------------------------------------------------------ private void loadTrace(final ITmfTrace trace) { if (fZoomThread != null) { fZoomThread.cancel(); fZoomThread = null; } if (fTrace != null) { /* save the filters of the previous trace */ fFiltersMap.put(fTrace, fTimeGraphViewer.getFilters()); fViewContext.put(fTrace, new ViewContext(fCurrentSortColumn, fSortDirection, fTimeGraphViewer.getSelection())); } fTrace = trace; LOGGER.info(() -> getLogMessage("LoadingTrace", "trace=" + trace.getName())); //$NON-NLS-1$ //$NON-NLS-2$ restoreViewContext(); fEditorFile = TmfTraceManager.getInstance().getTraceEditorFile(trace); synchronized (fEntryListMap) { fEntryList = fEntryListMap.get(fTrace); if (fEntryList == null) { rebuild(); } else { setStartTime(fTrace.getStartTime().toNanos()); setEndTime(fTrace.getEndTime().toNanos()); refresh(); } } } /** * Forces a rebuild of the entries list, even if entries already exist for this trace */ protected void rebuild() { setStartTime(Long.MAX_VALUE); setEndTime(Long.MIN_VALUE); refresh(); ITmfTrace viewTrace = fTrace; if (viewTrace == null) { return; } resetView(viewTrace); List<IMarkerEventSource> markerEventSources = new ArrayList<>(); synchronized (fBuildJobMap) { for (ITmfTrace trace : getTracesToBuild(viewTrace)) { if (trace == null) { break; } markerEventSources.addAll(TmfTraceAdapterManager.getAdapters(trace, IMarkerEventSource.class)); Job buildJob = new Job(getTitle() + Messages.AbstractTimeGraphView_BuildJob) { @Override protected IStatus run(IProgressMonitor monitor) { new BuildRunnable(trace, viewTrace).run(monitor); monitor.done(); return Status.OK_STATUS; } }; fBuildJobMap.put(trace, buildJob); buildJob.schedule(); } } fMarkerEventSourcesMap.put(viewTrace, markerEventSources); } /** * Method called when synching to a given timestamp. Inheriting classes can * perform actions here to update the view at the given timestamp. * * @param time * The currently selected time */ protected void synchingToTime(long time) { } /** * Return the list of traces whose data or analysis results will be used to * populate the view. By default, if the trace is an experiment, the traces * under it will be returned, otherwise, the trace itself is returned. * * A build thread will be started for each trace returned by this method, * some of which may receive events in live streaming mode. * * @param trace * The trace associated with this view, can be null * @return List of traces with data to display */ protected @NonNull Iterable<ITmfTrace> getTracesToBuild(@Nullable ITmfTrace trace) { return TmfTraceManager.getTraceSet(trace); } /** * Build the entry list to show in this time graph view. * <p> * Called from the BuildJob for each trace returned by * {@link #getTracesToBuild(ITmfTrace)}. * <p> * Root entries must be added to the entry list by calling the * {@link #addToEntryList(ITmfTrace, List)} method with the list of entries * to add and where the trace in parameter should be the parentTrace. * Entries that are children of other entries will be automatically picked * up after refreshing the root entries. * <p> * The full event list is also normally computed for every entry that is * created. It should be set for each entry by calling the * {@link TimeGraphEntry#setEventList(List)}. These full event lists will be * used to display something while the zoomed event lists are being * calculated when the window range is updated. Also, when fully zoomed out, * it is this list of events that is displayed. * <p> * Also, when all the entries have been added and their events set, this * method can finish by calling the refresh() method like this: * * <pre> * if (parentTrace.equals(getTrace())) { * refresh(); * } * </pre> * * @param trace * The trace being built * @param parentTrace * The parent of the trace set, or the trace itself * @param monitor * The progress monitor object * @since 2.0 */ protected abstract void buildEntryList(@NonNull ITmfTrace trace, @NonNull ITmfTrace parentTrace, @NonNull IProgressMonitor monitor); /** * Gets the list of event for an entry in a given time range. * <p> * Called from the ZoomThread for every entry to update the zoomed event * list. Can be an empty implementation if the view does not support zoomed * event lists. Can also be used to compute the full event list. * * @param entry * The entry to get events for * @param startTime * Start of the time range * @param endTime * End of the time range * @param resolution * The resolution * @param monitor * The progress monitor object * @return The list of events for the entry */ protected abstract @Nullable List<@NonNull ITimeEvent> getEventList(@NonNull TimeGraphEntry entry, long startTime, long endTime, long resolution, @NonNull IProgressMonitor monitor); /** * Gets the list of links (displayed as arrows) for a trace in a given * timerange. Default implementation returns an empty list. * * @param startTime * Start of the time range * @param endTime * End of the time range * @param resolution * The resolution * @param monitor * The progress monitor object * @return The list of link events */ protected @Nullable List<@NonNull ILinkEvent> getLinkList(long startTime, long endTime, long resolution, @NonNull IProgressMonitor monitor) { return new ArrayList<>(); } /** * Gets the list of view-specific marker categories. Default implementation * returns an empty list. * * @return The list of marker categories * @since 2.0 */ protected @NonNull List<String> getViewMarkerCategories() { return new ArrayList<>(); } /** * Gets the list of view-specific markers for a trace in a given time range. * Default implementation returns an empty list. * * @param startTime * Start of the time range * @param endTime * End of the time range * @param resolution * The resolution * @param monitor * The progress monitor object * @return The list of marker events * @since 2.0 */ protected @NonNull List<IMarkerEvent> getViewMarkerList(long startTime, long endTime, long resolution, @NonNull IProgressMonitor monitor) { return new ArrayList<>(); } /** * Gets the list of trace-specific markers for a trace in a given time range. * * @param startTime * Start of the time range * @param endTime * End of the time range * @param resolution * The resolution * @param monitor * The progress monitor object * @return The list of marker events * @since 2.0 */ protected @NonNull List<IMarkerEvent> getTraceMarkerList(long startTime, long endTime, long resolution, @NonNull IProgressMonitor monitor) { List<IMarkerEvent> markers = new ArrayList<>(); for (IMarkerEventSource markerEventSource : getMarkerEventSources(fTrace)) { for (String category : markerEventSource.getMarkerCategories()) { if (monitor.isCanceled()) { break; } markers.addAll(markerEventSource.getMarkerList(category, startTime, endTime, resolution, monitor)); } } return markers; } /** * Get the list of current marker categories. * * @return The list of marker categories * @since 2.1 */ protected @NonNull List<String> getMarkerCategories() { Set<String> categories = new LinkedHashSet<>(getViewMarkerCategories()); for (IMarkerEventSource markerEventSource : getMarkerEventSources(fTrace)) { categories.addAll(markerEventSource.getMarkerCategories()); } return new ArrayList<>(categories); } /** * Gets the list of marker event sources for a given trace. * * @param trace * The trace * @return The list of marker event sources * @since 2.0 */ private @NonNull List<IMarkerEventSource> getMarkerEventSources(ITmfTrace trace) { List<IMarkerEventSource> markerEventSources = fMarkerEventSourcesMap.get(trace); if (markerEventSources == null) { markerEventSources = Collections.emptyList(); } return markerEventSources; } /** * Refresh the display */ protected void refresh() { LOGGER.info(() -> getLogMessage("RefreshRequested", null)); //$NON-NLS-1$ final boolean zoomThread = Thread.currentThread() instanceof ZoomThread; TmfUiRefreshHandler.getInstance().queueUpdate(this, new Runnable() { @Override public void run() { LOGGER.info(() -> getLogMessage("RefreshStart", null)); //$NON-NLS-1$ if (fTimeGraphViewer.getControl().isDisposed()) { return; } fDirty.incrementAndGet(); synchronized (fEntryListMap) { fEntryList = fEntryListMap.get(fTrace); if (fEntryList == null) { fEntryList = new CopyOnWriteArrayList<>(); } else if (fEntryComparator != null) { List<TimeGraphEntry> list = new ArrayList<>(fEntryList); Collections.sort(list, fEntryComparator); for (ITimeGraphEntry entry : list) { sortChildren(entry, fEntryComparator); } fEntryList.clear(); fEntryList.addAll(list); } } boolean inputChanged = fEntryList != fTimeGraphViewer.getInput(); if (inputChanged) { fTimeGraphViewer.setInput(fEntryList); /* restore the previously saved filters, if any */ fTimeGraphViewer.setFilters(fFiltersMap.get(fTrace)); fTimeGraphViewer.setLinks(null); fTimeGraphViewer.setBookmarks(refreshBookmarks(fEditorFile)); fTimeGraphViewer.setMarkerCategories(getMarkerCategories()); fTimeGraphViewer.setMarkers(null); applyViewContext(); } else { fTimeGraphViewer.refresh(); } // reveal selection if (fIsRevealSelection) { fIsRevealSelection = false; fTimeGraphViewer.setSelection(fTimeGraphViewer.getSelection(), true); } long startBound = (fStartTime == Long.MAX_VALUE ? SWT.DEFAULT : fStartTime); long endBound = (fEndTime == Long.MIN_VALUE ? SWT.DEFAULT : fEndTime); fTimeGraphViewer.setTimeBounds(startBound, endBound); TmfTraceContext ctx = TmfTraceManager.getInstance().getCurrentTraceContext(); long selectionBeginTime = fTrace == null ? SWT.DEFAULT : ctx.getSelectionRange().getStartTime().toNanos(); long selectionEndTime = fTrace == null ? SWT.DEFAULT : ctx.getSelectionRange().getEndTime().toNanos(); long startTime = fTrace == null ? SWT.DEFAULT : ctx.getWindowRange().getStartTime().toNanos(); long endTime = fTrace == null ? SWT.DEFAULT : ctx.getWindowRange().getEndTime().toNanos(); startTime = (fStartTime == Long.MAX_VALUE ? SWT.DEFAULT : Math.max(startTime, fStartTime)); endTime = (fEndTime == Long.MIN_VALUE ? SWT.DEFAULT : Math.min(endTime, fEndTime)); fTimeGraphViewer.setSelectionRange(selectionBeginTime, selectionEndTime, false); fTimeGraphViewer.setStartFinishTime(startTime, endTime); if (inputChanged && selectionBeginTime != SWT.DEFAULT) { synchingToTime(selectionBeginTime); } if (!zoomThread) { startZoomThread(startTime, endTime); } fDirty.decrementAndGet(); LOGGER.info(() -> getLogMessage("RefreshEnd", null)); //$NON-NLS-1$ } }); } /** * Redraw the canvas */ protected void redraw() { synchronized (fSyncObj) { if (fRedrawState == State.IDLE) { fRedrawState = State.BUSY; } else { fRedrawState = State.PENDING; return; } } LOGGER.info(() -> getLogMessage("RedrawRequested", null)); //$NON-NLS-1$ Display.getDefault().asyncExec(new Runnable() { @Override public void run() { LOGGER.info(() -> getLogMessage("RedrawStart", null)); //$NON-NLS-1$ if (fTimeGraphViewer.getControl().isDisposed()) { return; } fTimeGraphViewer.getControl().redraw(); fTimeGraphViewer.getControl().update(); synchronized (fSyncObj) { if (fRedrawState == State.PENDING) { fRedrawState = State.IDLE; redraw(); } else { fRedrawState = State.IDLE; } } LOGGER.info(() -> getLogMessage("RedrawEnd", null)); //$NON-NLS-1$ } }); } private void sortChildren(ITimeGraphEntry entry, Comparator<ITimeGraphEntry> comparator) { if (entry instanceof TimeGraphEntry) { ((TimeGraphEntry) entry).sortChildren(comparator); } for (ITimeGraphEntry child : entry.getChildren()) { sortChildren(child, comparator); } } /** * Start or restart the zoom thread. * * @param startTime * the zoom start time * @param endTime * the zoom end time * @since 2.0 */ protected final void startZoomThread(long startTime, long endTime) { long clampedStartTime = (fStartTime == Long.MAX_VALUE ? startTime : Math.min(Math.max(startTime, fStartTime), fEndTime)); long clampedEndTime = (fEndTime == Long.MIN_VALUE ? endTime : Math.max(Math.min(endTime, fEndTime), fStartTime)); fDirty.incrementAndGet(); boolean restart = false; if (fZoomThread != null) { fZoomThread.cancel(); if (fZoomThread.fZoomStartTime == clampedStartTime && fZoomThread.fZoomEndTime == clampedEndTime) { restart = true; } } long resolution = Math.max(1, (clampedEndTime - clampedStartTime) / fDisplayWidth); fZoomThread = createZoomThread(clampedStartTime, clampedEndTime, resolution, restart); if (fZoomThread != null) { // Don't start a new thread right away if results are being applied // from an old ZoomThread. Otherwise, the old results might // overwrite the new results if it finishes after. synchronized (fZoomThreadResultLock) { fZoomThread.start(); } } else { fDirty.decrementAndGet(); } } /** * Create a zoom thread. * * @param startTime * the zoom start time * @param endTime * the zoom end time * @param resolution * the resolution * @param restart * true if restarting zoom for the same time range * @return a zoom thread * @since 1.1 */ protected @Nullable ZoomThread createZoomThread(long startTime, long endTime, long resolution, boolean restart) { final List<TimeGraphEntry> entryList = fEntryList; if (entryList == null) { return null; } return new ZoomThreadByEntry(entryList, startTime, endTime, resolution); } private void makeActions() { fPreviousResourceAction = fTimeGraphViewer.getPreviousItemAction(); fPreviousResourceAction.setText(getPrevText()); fPreviousResourceAction.setToolTipText(getPrevTooltip()); fNextResourceAction = fTimeGraphViewer.getNextItemAction(); fNextResourceAction.setText(getNextText()); fNextResourceAction.setToolTipText(getNextTooltip()); } private void contributeToActionBars() { IActionBars bars = getViewSite().getActionBars(); fillLocalToolBar(bars.getToolBarManager()); fillLocalMenu(bars.getMenuManager()); } /** * Add actions to local tool bar manager * * @param manager the tool bar manager */ protected void fillLocalToolBar(IToolBarManager manager) { if (fFilterColumns != null && fFilterLabelProvider != null && fFilterColumns.length > 0) { manager.add(fTimeGraphViewer.getShowFilterDialogAction()); } manager.add(fTimeGraphViewer.getShowLegendAction()); manager.add(new Separator()); manager.add(fTimeGraphViewer.getResetScaleAction()); manager.add(fTimeGraphViewer.getPreviousEventAction()); manager.add(fTimeGraphViewer.getNextEventAction()); manager.add(new Separator()); manager.add(fTimeGraphViewer.getToggleBookmarkAction()); manager.add(fTimeGraphViewer.getPreviousMarkerAction()); manager.add(fTimeGraphViewer.getNextMarkerAction()); manager.add(new Separator()); manager.add(fPreviousResourceAction); manager.add(fNextResourceAction); manager.add(fTimeGraphViewer.getZoomInAction()); manager.add(fTimeGraphViewer.getZoomOutAction()); manager.add(new Separator()); } /** * Add actions to local menu manager * * @param manager the tool bar manager * @since 2.0 */ protected void fillLocalMenu(IMenuManager manager) { manager.add(fTimeGraphViewer.getMarkersMenu()); } /** * @since 1.0 */ @Override public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() { if (fTimeGraphViewer == null) { return null; } return fTimeGraphViewer.getTimeViewAlignmentInfo(); } /** * @since 1.0 */ @Override public int getAvailableWidth(int requestedOffset) { if (fTimeGraphViewer == null) { return 0; } return fTimeGraphViewer.getAvailableWidth(requestedOffset); } /** * @since 1.0 */ @Override public void performAlign(int offset, int width) { if (fTimeGraphViewer != null) { fTimeGraphViewer.performAlign(offset, width); } } /** * Returns whether or not the time graph view is dirty. The time graph view * is considered dirty if it has yet to completely update its model. * * This method is meant to be used by tests in order to know when it is safe * to proceed. * * Note: If a trace is smaller than the initial window range (see * {@link ITmfTrace#getInitialRangeOffset}) this method will return true * forever. * * @return true if the time graph view has yet to completely update its * model, false otherwise * @since 2.0 */ public boolean isDirty() { if (fTrace == null) { return false; } TmfTraceContext ctx = TmfTraceManager.getInstance().getCurrentTraceContext(); long startTime = ctx.getWindowRange().getStartTime().toNanos(); long endTime = ctx.getWindowRange().getEndTime().toNanos(); // If the time graph control hasn't updated all the way to the end of // the window range then it's dirty. A refresh should happen later. if (fTimeGraphViewer.getTime0() != startTime || fTimeGraphViewer.getTime1() != endTime) { return true; } if (fZoomThread == null) { // The zoom thread is null but we might be just about to create it (refresh called). return fDirty.get() != 0; } // Dirty if the zoom thread is not done or if it hasn't zoomed all the // way to the end of the window range. In the latter case, there should be // a subsequent zoom thread that will be triggered. return fDirty.get() != 0 || fZoomThread.getZoomStartTime() != startTime || fZoomThread.getZoomEndTime() != endTime; } private void createColumnSelectionListener(Tree tree) { for (int i = 0; i < fColumnComparators.length; i++) { final int index = i; final Comparator<ITimeGraphEntry> comp = fColumnComparators[index]; final TreeColumn column = tree.getColumn(i); if (comp != null) { column.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { TreeColumn prevSortcolumn = tree.getSortColumn(); int direction = tree.getSortDirection(); if (prevSortcolumn == column) { direction = (direction == SWT.DOWN) ? SWT.UP : SWT.DOWN; } else { direction = SWT.DOWN; } tree.setSortColumn(column); tree.setSortDirection(direction); fSortDirection = direction; fCurrentSortColumn = index; Comparator<ITimeGraphEntry> comparator = comp; if (comparator instanceof ITimeGraphEntryComparator) { ((ITimeGraphEntryComparator) comparator).setDirection(direction); } if (direction != SWT.DOWN) { comparator = checkNotNull(Collections.reverseOrder(comparator)); } setEntryComparator(comparator); fIsRevealSelection = true; fTimeGraphViewer.getControl().setFocus(); refresh(); } }); } } } private void restoreViewContext() { ViewContext viewContext = fViewContext.get(fTrace); if (fColumnComparators != null) { // restore sort settings fSortDirection = SWT.DOWN; fCurrentSortColumn = fInitialSortColumn; if (viewContext != null) { fSortDirection = viewContext.getSortDirection(); fCurrentSortColumn = viewContext.getSortColumn(); } if ((fCurrentSortColumn < fColumnComparators.length) && (fColumnComparators[fCurrentSortColumn] != null)) { Comparator<ITimeGraphEntry> comparator = fColumnComparators[fCurrentSortColumn]; if (comparator instanceof ITimeGraphEntryComparator) { ((ITimeGraphEntryComparator) comparator).setDirection(fSortDirection); } if (fSortDirection != SWT.DOWN) { comparator = checkNotNull(Collections.reverseOrder(comparator)); } setEntryComparator(comparator); } } } private void applyViewContext() { ViewContext viewContext = fViewContext.get(fTrace); if (fColumnComparators != null) { final Tree tree = fTimeGraphViewer.getTree(); final TreeColumn column = tree.getColumn(fCurrentSortColumn); tree.setSortDirection(fSortDirection); tree.setSortColumn(column); } fTimeGraphViewer.getControl().setFocus(); // restore and reveal selection if ((viewContext != null) && (viewContext.getSelection() != null)) { fTimeGraphViewer.setSelection(viewContext.getSelection(), true); } fViewContext.remove(fTrace); } private static class ViewContext { private int fSortColumnIndex; private int fSortDirection; private @Nullable ITimeGraphEntry fSelection; ViewContext(int sortColunm, int sortDirection, ITimeGraphEntry selection) { fSortColumnIndex = sortColunm; fSortDirection = sortDirection; fSelection = selection; } /** * @return the sortColumn */ public int getSortColumn() { return fSortColumnIndex; } /** * @return the sortDirection */ public int getSortDirection() { return fSortDirection; } /** * @return the selection */ public ITimeGraphEntry getSelection() { return fSelection; } } /** * Method to reset the view internal data for a given trace. * * When overriding this method make sure to call the super * implementation. * * @param viewTrace * trace to reset the view for. * @since 2.0 */ protected void resetView(ITmfTrace viewTrace) { if (viewTrace == null) { return; } synchronized (fBuildJobMap) { for (ITmfTrace trace : getTracesToBuild(viewTrace)) { Job buildJob = fBuildJobMap.remove(trace); if (buildJob != null) { buildJob.cancel(); } } } synchronized (fEntryListMap) { fEntryListMap.remove(viewTrace); } fViewContext.remove(viewTrace); fFiltersMap.remove(viewTrace); fMarkerEventSourcesMap.remove(viewTrace); if (viewTrace == fTrace) { if (fZoomThread != null) { fZoomThread.cancel(); fZoomThread = null; } } } private void createContextMenu() { fEntryMenuManager.setRemoveAllWhenShown(true); TimeGraphControl timeGraphControl = getTimeGraphViewer().getTimeGraphControl(); final Menu entryMenu = fEntryMenuManager.createContextMenu(timeGraphControl); timeGraphControl.addTimeGraphEntryMenuListener(new MenuDetectListener() { @Override public void menuDetected(MenuDetectEvent event) { Point p = timeGraphControl.toControl(event.x, event.y); /* * The TimeGraphControl will call the TimeGraphEntryMenuListener * before the TimeEventMenuListener. If the event is triggered * on the name space then show the menu else clear the menu. */ if (p.x < getTimeGraphViewer().getNameSpace()) { timeGraphControl.setMenu(entryMenu); } else { timeGraphControl.setMenu(null); event.doit = false; } } }); fEntryMenuManager.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(IMenuManager manager) { fillTimeGraphEntryContextMenu(fEntryMenuManager); fEntryMenuManager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS)); } }); getSite().registerContextMenu(fEntryMenuManager, fTimeGraphViewer.getSelectionProvider()); } /** * Fill context menu * * @param menuManager * a menuManager to fill * @since 2.0 */ protected void fillTimeGraphEntryContextMenu (@NonNull IMenuManager menuManager) { } /* * Inner classes used for searching */ class FindTarget { public ITimeGraphEntry getSelection() { return fTimeGraphViewer.getSelection(); } public void selectAndReveal(@NonNull ITimeGraphEntry entry) { fTimeGraphViewer.selectAndReveal(entry); } public ITimeGraphEntry[] getEntries() { TimeGraphViewer viewer = getTimeGraphViewer(); return viewer.getTimeGraphContentProvider().getElements(viewer.getInput()); } public Shell getShell() { return getSite().getShell(); } } class TimeGraphPartListener implements IPartListener { @Override public void partActivated(IWorkbenchPart part) { if (part == AbstractTimeGraphView.this) { synchronized (FIND_ACTION) { if (fFindActionHandler == null) { fFindActionHandler = new ActionHandler(FIND_ACTION); } if (fFindHandlerActivation == null) { final Object service = PlatformUI.getWorkbench().getService(IHandlerService.class); fFindHandlerActivation = ((IHandlerService) service).activateHandler(ActionFactory.FIND.getCommandId(), fFindActionHandler); } } } // Notify action for all parts FIND_ACTION.partActivated(part); } @Override public void partDeactivated(IWorkbenchPart part) { if ((part == AbstractTimeGraphView.this) && (fFindHandlerActivation != null)) { final Object service = PlatformUI.getWorkbench().getService(IHandlerService.class); ((IHandlerService) service).deactivateHandler(fFindHandlerActivation); fFindHandlerActivation = null; } } @Override public void partBroughtToTop(IWorkbenchPart part) { } @Override public void partClosed(IWorkbenchPart part) { } @Override public void partOpened(IWorkbenchPart part) { } } }