blob: ffd1a0f04e3a4d38df4cc52b69ee9c1f870495c1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2019 Ericsson, École Polytechnique de Montréal and others
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IAction;
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.dialogs.IDialogSettings;
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.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGBA;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
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.common.core.log.TraceCompassLogUtils;
import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLog;
import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLogBuilder;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filter.parser.FilterCu;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filter.parser.IFilterStrings;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TmfFilterAppliedSignal;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TraceCompassFilter;
import org.eclipse.tracecompass.internal.tmf.core.markers.MarkerConfigXmlParser;
import org.eclipse.tracecompass.internal.tmf.core.markers.MarkerSet;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.internal.tmf.ui.markers.MarkerUtils;
import org.eclipse.tracecompass.internal.tmf.ui.util.TimeGraphStyleUtil;
import org.eclipse.tracecompass.internal.tmf.ui.views.ITmfTimeNavigationProvider;
import org.eclipse.tracecompass.internal.tmf.ui.views.ITmfTimeZoomProvider;
import org.eclipse.tracecompass.internal.tmf.ui.views.ITmfZoomToSelectionProvider;
import org.eclipse.tracecompass.internal.tmf.ui.views.timegraph.TimeEventFilterDialog;
import org.eclipse.tracecompass.tmf.core.model.timegraph.IElementResolver;
import org.eclipse.tracecompass.tmf.core.model.timegraph.IFilterProperty;
import org.eclipse.tracecompass.tmf.core.resources.ITmfMarker;
import org.eclipse.tracecompass.tmf.core.signal.TmfDataModelSelectedSignal;
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.TmfSignalManager;
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.TmfTraceUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimePreferencesConstants;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimePreferences;
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.ITmfAllowMultiple;
import org.eclipse.tracecompass.tmf.ui.views.ITmfPinnable;
import org.eclipse.tracecompass.tmf.ui.views.ITmfTimeAligned;
import org.eclipse.tracecompass.tmf.ui.views.SaveImageUtil;
import org.eclipse.tracecompass.tmf.ui.views.TmfView;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.BaseDataProviderTimeGraphView.TraceEntry;
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.ITimeGraphPresentationProvider;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphSelectionListener;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphBookmarkEvent;
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.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.NullTimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry.Sampling;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
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.IActionBars;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableMultimap.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
/**
* An abstract view all time graph views can inherit
*
* This view contains a time graph viewer.
*/
public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeAligned, ITmfAllowMultiple, ITmfPinnable, IResourceChangeListener{
private static final String TIMEGRAPH_UI_CONTEXT = "org.eclipse.tracecompass.tmf.ui.view.timegraph.context"; //$NON-NLS-1$
private static final String TMF_VIEW_UI_CONTEXT = "org.eclipse.tracecompass.tmf.ui.view.context"; //$NON-NLS-1$
private static final String DIRTY_UNDERFLOW_ERROR = "Dirty underflow error"; //$NON-NLS-1$
/**
* 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 @NonNull Logger LOGGER = TraceCompassLog.getLogger(AbstractTimeGraphView.class);
private static final int DEFAULT_BUFFER_SIZE = 3;
private static final String HIDE_LABELS_KEY = "hide.labels"; //$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 @Nullable IFile fEditorFile;
/** The timegraph entry list */
private List<TimeGraphEntry> fEntryList;
/** The trace to entry list hash map */
private final Map<ITmfTrace, List<@NonNull 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 ITimeGraphLegendProvider fLegendProvider;
private int fAutoExpandLevel = ALL_LEVELS;
private @Nullable TimeFormat fTimeFormat = null;
/** 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;
/**
* Set of visible entries to zoom on.
*/
private @NonNull Set<@NonNull TimeGraphEntry> fVisibleEntries = Collections.emptySet();
/**
* The width of the last time space that was zoomed on.
*/
private int fPrevTimeSpace = -1;
/**
* 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;
/** The marker set menu */
private MenuManager fMarkerSetMenu;
/** The original view title */
private String fOriginalTabLabel;
/** The timegraph event filter action */
private Action fTimeEventFilterAction;
/** The time graph event filter dialog */
private TimeEventFilterDialog fTimeEventFilterDialog;
private TimeGraphPartListener2 fPartListener2;
private IContextService fContextService;
private List<IContextActivation> fActiveContexts = new ArrayList<>();
/** Listener that handles a click on an entry in the FusedVM View */
private final ITimeGraphSelectionListener fMetadataSelectionListener = event -> {
ITimeGraphEntry entry = event.getSelection();
if (entry instanceof IElementResolver) {
Multimap<@NonNull String, @NonNull Object> metadata = ((IElementResolver) entry).getMetadata();
if (!metadata.isEmpty()) {
broadcast(new TmfDataModelSelectedSignal(AbstractTimeGraphView.this, metadata));
}
}
};
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// 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) {
// do nothing
}
@Override
public void dispose() {
// do nothing
}
@Override
public boolean isLabelProperty(Object element, String property) {
return false;
}
@Override
public void removeListener(ILabelProviderListener listener) {
// do nothing
}
@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;
private final @NonNull FlowScopeLog fScope;
public BuildRunnable(final @NonNull ITmfTrace trace, final @NonNull ITmfTrace parentTrace, final @NonNull FlowScopeLog log) {
fBuildTrace = trace;
fParentTrace = parentTrace;
fScope = log;
}
public void run(IProgressMonitor monitor) {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "TimeGraphView:BuildThread", "trace", fBuildTrace.getName()).setParentScope(fScope).build()) { //$NON-NLS-1$ //$NON-NLS-2$
buildEntryList(fBuildTrace, fParentTrace, NonNullUtils.checkNotNull(monitor));
synchronized (fBuildJobMap) {
fBuildJobMap.remove(fBuildTrace);
}
}
}
}
/**
* Zoom thread
*
* @since 1.1
*/
protected abstract class ZoomThread extends Thread {
private final long fZoomStartTime;
private final long fZoomEndTime;
private final long fResolution;
private int fScopeId = -1;
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() {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "TimeGraphView:ZoomThread", "start", fZoomStartTime, "end", fZoomEndTime).setCategoryAndId(getViewId(), fScopeId).build()) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
doRun();
} finally {
if (fDirty.decrementAndGet() < 0) {
Activator.getDefault().logError(DIRTY_UNDERFLOW_ERROR, new Throwable());
}
}
}
/**
* Applies the results of the ZoomThread calculations asynchronously on
* the UI thread.
* <p>
* 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) {
AbstractTimeGraphView.this.applyResults(runnable);
}
/**
* Run the zoom operation.
*
* @since 2.0
*/
public abstract void doRun();
/**
* Set the ID of the calling flow scope. This data will allow to determine the
* causality between the zoom thread and its caller if tracing is enabled.
*
* @param scopeId
* The ID of the calling flow scope
* @since 3.0
*/
public void setScopeId(int scopeId) {
fScopeId = scopeId;
}
}
/**
* Applies the results of the ZoomThread calculations asynchronously on the
* UI thread.
* <p>
* 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 3.1
*/
protected void applyResults(Runnable runnable) {
synchronized (fZoomThreadResultLock) {
if (Thread.currentThread() == fZoomThread) {
Display.getDefault().asyncExec(runnable);
}
}
}
private class ZoomThreadByEntry extends ZoomThread {
private final @NonNull Collection<@NonNull TimeGraphEntry> fEntries;
public ZoomThreadByEntry(@NonNull Collection<@NonNull TimeGraphEntry> entries, long startTime, long endTime, long resolution) {
super(startTime, endTime, resolution);
fEntries = entries;
}
@Override
public void doRun() {
Sampling sampling = new Sampling(getZoomStartTime(), getZoomEndTime(), getResolution());
boolean isFilterActive = !getRegexes().values().isEmpty();
try (TraceCompassLogUtils.ScopeLog log = new TraceCompassLogUtils.ScopeLog(LOGGER, Level.FINER, "ZoomThread:GettingStates")) { //$NON-NLS-1$
boolean isFilterCleared = !isFilterActive && getTimeGraphViewer().isTimeEventFilterActive();
getTimeGraphViewer().setTimeEventFilterApplied(isFilterActive);
boolean hasSavedFilter = fTimeEventFilterDialog != null && fTimeEventFilterDialog.hasActiveSavedFilters();
getTimeGraphViewer().setSavedFilterStatus(hasSavedFilter);
Iterable<@NonNull TimeGraphEntry> incorrectSample = Iterables.filter(fEntries, entry -> isFilterActive || isFilterCleared || !sampling.equals(entry.getSampling()));
zoomEntries(incorrectSample, getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor());
}
List<ILinkEvent> computedLinks;
try (TraceCompassLogUtils.ScopeLog linkLog = new TraceCompassLogUtils.ScopeLog(LOGGER, Level.FINER, "ZoomThread:GettingLinks")) { //$NON-NLS-1$
/* Refresh the arrows when zooming */
computedLinks = getLinkList(getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor());
TimeEventFilterDialog filterDialog = getTimeEventFilterDialog();
if (filterDialog != null && computedLinks != null) {
if (filterDialog.hasActiveSavedFilters()) {
computedLinks = Collections.emptyList();
} else {
computedLinks.forEach(link -> link.setProperty(IFilterProperty.DIMMED, filterDialog.isFilterActive()));
}
}
}
List<ILinkEvent> links = computedLinks;
/* Refresh the view-specific markers when zooming */
try (TraceCompassLogUtils.ScopeLog markerLoglog = new TraceCompassLogUtils.ScopeLog(LOGGER, Level.FINER, "ZoomThread:GettingMarkers")) { //$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 (links != null) {
fTimeGraphViewer.setLinks(links);
}
fTimeGraphViewer.setMarkerCategories(getMarkerCategories());
fTimeGraphViewer.setMarkers(markers);
});
synchronized (fZoomThreadResultLock) {
if (Thread.currentThread() == fZoomThread) {
refresh();
}
}
}
if (isFilterActive && Thread.currentThread() == fZoomThread) {
/* Do a full filter search as a second pass */
try (TraceCompassLogUtils.ScopeLog log = new TraceCompassLogUtils.ScopeLog(LOGGER, Level.FINER, "ZoomThread:GettingStatesFullSearch")) { //$NON-NLS-1$
for (TimeGraphEntry entry : fEntries) {
if (getMonitor().isCanceled()) {
return;
}
zoomEntries(Collections.singleton(entry), getZoomStartTime(), getZoomEndTime(), getResolution(), true, getMonitor());
refresh();
}
}
}
}
}
/**
* Add events from the queried time range to the queried entries.
* <p>
* Called from the ZoomThread for every entry to update the zoomed event list.
* <p>
* The implementation should call {@link TimeGraphEntry#setSampling(Sampling)}
* if the zoomed event list is successfully set.
*
* @param entries
* List of entries to zoom on.
* @param zoomStartTime
* Start of the time range
* @param zoomEndTime
* End of the time range
* @param resolution
* The resolution
* @param monitor
* The progress monitor object
* @since 3.2
*/
protected void zoomEntries(@NonNull Iterable<@NonNull TimeGraphEntry> entries,
long zoomStartTime, long zoomEndTime, long resolution, @NonNull IProgressMonitor monitor) {
zoomEntries(entries, zoomStartTime, zoomEndTime, resolution, false, monitor);
}
/**
* Add events from the queried time range to the queried entries.
* <p>
* Called from the ZoomThread for every entry to update the zoomed event
* list.
* <p>
* The implementation should call
* {@link TimeGraphEntry#setSampling(Sampling)} if the zoomed event list is
* successfully set.
* <p>
* When a full search is requested, the gaps in between samples of the
* queried time range should be searched and at least one event matching the
* regex filter should be included per gap, if any is found.
*
* @param entries
* List of entries to zoom on.
* @param zoomStartTime
* Start of the time range
* @param zoomEndTime
* End of the time range
* @param resolution
* The resolution
* @param fullSearch
* True to perform a full search
* @param monitor
* The progress monitor object
* @since 5.2
*/
protected void zoomEntries(@NonNull Iterable<@NonNull TimeGraphEntry> entries,
long zoomStartTime, long zoomEndTime, long resolution, boolean fullSearch, @NonNull IProgressMonitor monitor) {
try {
Map<Integer, Predicate<Map<String, String>>> predicates = computeRegexPredicate();
for (TimeGraphEntry entry : entries) {
List<ITimeEvent> zoomedEventList = getEventList(entry, zoomStartTime, zoomEndTime, fullSearch ? 1L : resolution, monitor);
if (monitor.isCanceled()) {
return;
}
if (zoomedEventList != null) {
doFilterEvents(entry, zoomedEventList, predicates);
applyResults(() -> entry.setZoomedEventList(zoomedEventList));
}
}
redraw();
} catch (PatternSyntaxException e) {
Activator.getDefault().logInfo("Invalid regex"); //$NON-NLS-1$
}
}
/**
* Filter the given eventList using the predicates
*
* @param entry
* The timegraph entry
* @param eventList
* The event list at this zoom level
* @param predicates
* The predicate for the filter dialog text box
* @since 4.0
*/
@NonNullByDefault
protected void doFilterEvents(TimeGraphEntry entry, List<ITimeEvent> eventList, Map<Integer, Predicate<Map<String, String>>> predicates) {
if (!predicates.isEmpty()) {
// For each event in the events list, test each predicates and set the
// status of the property associated to the predicate
eventList.forEach(te -> {
for (Map.Entry<Integer, Predicate<Map<String, String>>> mapEntry : predicates.entrySet()) {
Predicate<Map<String, String>> value = Objects.requireNonNull(mapEntry.getValue());
Map<String, String> toTest = new HashMap<>(getPresentationProvider().getFilterInput(te));
toTest.putAll(te.computeData());
boolean status = value.test(toTest);
Integer property = mapEntry.getKey();
if (property == IFilterProperty.DIMMED || property == IFilterProperty.EXCLUDE) {
te.setProperty(property, !status);
} else {
te.setProperty(property, status);
}
}
});
}
fillWithNullEvents(entry, eventList);
}
/**
* Fill the gaps between non-excluded events with null time events
*
* @param entry
* The entry
* @param eventList
* The entry event list
*/
private void fillWithNullEvents(TimeGraphEntry entry, List<ITimeEvent> eventList) {
List<ITimeEvent> filtered = new ArrayList<>();
if (!eventList.isEmpty()
&& getTimeEventFilterDialog() != null && getTimeEventFilterDialog().hasActiveSavedFilters()) {
eventList.forEach(te -> {
// Keep only the events that do not have the 'exclude' property activated
if (!te.isPropertyActive(IFilterProperty.EXCLUDE)) {
filtered.add(te);
}
});
long prevTime = eventList.get(0).getTime();
long endTime = eventList.get(eventList.size() - 1).getTime() + eventList.get(eventList.size() - 1).getDuration();
eventList.clear();
// Replace unused events with null time events to fill gaps
for (ITimeEvent event : filtered) {
if (prevTime < event.getTime()) {
NullTimeEvent nullTimeEvent = new NullTimeEvent(entry, prevTime, event.getTime() - prevTime);
nullTimeEvent.setProperty(IFilterProperty.DIMMED, true);
nullTimeEvent.setProperty(IFilterProperty.EXCLUDE, true);
eventList.add(nullTimeEvent);
}
eventList.add(event);
prevTime = event.getTime() + event.getDuration();
}
if (prevTime < endTime) {
NullTimeEvent nullTimeEvent = new NullTimeEvent(entry, prevTime, endTime - prevTime);
nullTimeEvent.setProperty(IFilterProperty.DIMMED, true);
nullTimeEvent.setProperty(IFilterProperty.EXCLUDE, true);
eventList.add(nullTimeEvent);
}
}
}
/**
* Compute the predicate for every property regexes
*
* @return A map of time event filters predicate by property
* @since 4.0
* @deprecated Use {@link #generateRegexPredicate()}
*/
@Deprecated
@NonNullByDefault
protected Map<Integer, Predicate<Map<String, String>>> computeRegexPredicate() {
Multimap<Integer, String> regexes = getRegexes();
Map<@NonNull Integer, @NonNull Predicate<@NonNull Map<@NonNull String, @NonNull String>>> predicates = new HashMap<>();
for (Entry<Integer, Collection<String>> entry : regexes.asMap().entrySet()) {
String regex = IFilterStrings.mergeFilters(entry.getValue());
FilterCu cu = FilterCu.compile(regex);
Predicate<@NonNull Map<@NonNull String, @NonNull String>> predicate = cu != null ? multiToMapPredicate(cu.generate()) : null;
if (predicate != null) {
predicates.put(entry.getKey(), predicate);
}
}
return predicates;
}
private static Predicate<@NonNull Map<@NonNull String, @NonNull String>> multiToMapPredicate(@NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>> predicate) {
return map -> {
Builder<@NonNull String, @NonNull Object> builder = ImmutableMultimap.builder();
map.forEach((key, value) -> builder.put(key, value));
return predicate.test(Objects.requireNonNull(builder.build()));
};
}
/**
* Generate the predicate for every property from the regexes
*
* @return A map of predicate by property
* @since 5.0
*/
@NonNullByDefault
protected Map<Integer, Predicate<Multimap<String, Object>>> generateRegexPredicate() {
Multimap<Integer, String> regexes = getRegexes();
Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>>> predicates = new HashMap<>();
for (Entry<Integer, Collection<String>> entry : regexes.asMap().entrySet()) {
String regex = IFilterStrings.mergeFilters(entry.getValue());
FilterCu cu = FilterCu.compile(regex);
Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>> predicate = cu != null ? cu.generate() : null;
if (predicate != null) {
predicates.put(entry.getKey(), predicate);
}
}
return predicates;
}
// ------------------------------------------------------------------------
// 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();
}
@Override
protected IAction createSaveAction() {
return SaveImageUtil.createSaveAction(getName(), this::getTimeGraphViewer);
}
// ------------------------------------------------------------------------
// Getters and setters
// ------------------------------------------------------------------------
/**
* Getter for the time graph viewer
*
* @return The time graph viewer
* @since 4.1
*/
public TimeGraphViewer getTimeGraphViewer() {
return fTimeGraphViewer;
}
/**
* Getter for the presentation provider
*
* @return The time graph presentation provider
* @since 5.0
*/
protected ITimeGraphPresentationProvider 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 != null) ? Arrays.copyOf(columns, columns.length) : columns;
fColumnComparators = (comparators != null) ? Arrays.copyOf(comparators, comparators.length) : 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 = Arrays.copyOf(weights, weights.length);
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 = Arrays.copyOf(filterColumns, filterColumns.length);
}
/**
* 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;
}
/**
* @since 3.2
*/
@Override
public 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);
}
}
/**
* Sets the time format, or null to use the Time Format 'Date and Time
* format' preference (default).
*
* @param timeFormat
* the {@link TimeFormat} used to display timestamps
* @since 5.0
*/
protected void setTimeFormat(TimeFormat timeFormat) {
fTimeFormat = timeFormat;
if (fTimeGraphViewer != null) {
updateTimeFormat();
fTimeGraphViewer.refresh();
}
}
/**
* Gets the entry list for a trace
*
* @param trace
* the trace
*
* @return the entry list map
*/
protected @Nullable List<@NonNull 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<@NonNull 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<@NonNull TimeGraphEntry> list) {
synchronized (fEntryListMap) {
List<TimeGraphEntry> entryList = fEntryListMap.get(trace);
if (entryList == null) {
fEntryListMap.put(trace, new CopyOnWriteArrayList<>(list));
} else {
for (TimeGraphEntry entry : list) {
if (!entryList.contains(entry)) {
entryList.add(entry);
}
}
}
}
}
/**
* 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;
}
/**
* Sets the legend provider
*
* @param legendProvider
* the legend provider
* @since 3.3
*/
public void setLegendProvider(ITimeGraphLegendProvider legendProvider) {
fLegendProvider = legendProvider;
}
// ------------------------------------------------------------------------
// ViewPart
// ------------------------------------------------------------------------
@Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
TimeGraphViewer timeGraphViewer = new TimeGraphViewer(parent, SWT.NONE);
fTimeGraphViewer = timeGraphViewer;
if (fLabelProvider != null) {
timeGraphViewer.setTimeGraphLabelProvider(fLabelProvider);
}
if (fLegendProvider != null) {
timeGraphViewer.setLegendProvider(fLegendProvider);
}
if (fColumns != null) {
timeGraphViewer.setColumns(fColumns);
if (fColumnComparators != null) {
createColumnSelectionListener(timeGraphViewer.getTree());
}
}
timeGraphViewer.setTimeGraphContentProvider(fTimeGraphContentProvider);
timeGraphViewer.setFilterContentProvider(fFilterContentProvider != null ? fFilterContentProvider : fTimeGraphContentProvider);
timeGraphViewer.setFilterLabelProvider(fFilterLabelProvider);
timeGraphViewer.setFilterColumns(fFilterColumns);
timeGraphViewer.addSelectionListener(fMetadataSelectionListener);
ITimeGraphPresentationProvider presentationProvider = getPresentationProvider();
timeGraphViewer.setTimeGraphProvider(presentationProvider);
presentationProvider.addColorListener(stateItems -> TimeGraphStyleUtil.loadValues(getPresentationProvider()));
presentationProvider.refresh();
timeGraphViewer.setAutoExpandLevel(fAutoExpandLevel);
timeGraphViewer.setWeights(fWeight);
TimeGraphControl timeGraphControl = timeGraphViewer.getTimeGraphControl();
Action timeEventFilterAction = new Action() {
@Override
public void run() {
int xCoord = timeGraphControl.toControl(timeGraphControl.getDisplay().getCursorLocation()).x;
if ((timeGraphViewer.getNameSpace() < xCoord) && (xCoord < timeGraphControl.getSize().x)) {
if (fTimeEventFilterDialog != null) {
fTimeEventFilterDialog.close();
fTimeEventFilterDialog = null;
}
fTimeEventFilterDialog = new TimeEventFilterDialog(timeGraphControl.getShell(), AbstractTimeGraphView.this, getTimeGraphViewer().getTimeGraphControl());
fTimeEventFilterDialog.open();
}
}
};
fTimeEventFilterAction = timeEventFilterAction;
timeGraphViewer.addRangeListener(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, fTrace));
startZoomThread(startTime, endTime);
});
timeGraphViewer.addTimeListener(event -> {
ITmfTimestamp startTime = TmfTimestamp.fromNanos(event.getBeginTime());
ITmfTimestamp endTime = TmfTimestamp.fromNanos(event.getEndTime());
broadcast(new TmfSelectionRangeUpdatedSignal(AbstractTimeGraphView.this, startTime, endTime, fTrace));
});
timeGraphViewer.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();
IFile editorFile = fEditorFile;
if (editorFile == null) {
return;
}
IMarker marker = editorFile.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();
IFile editorFile = fEditorFile;
if (editorFile == null) {
return;
}
IMarker[] markers = editorFile.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());
}
}
});
timeGraphControl.addPaintListener(new PaintListener() {
/**
* This paint control allows the virtual time graph refresh to occur on paint
* events instead of just scrolling the time axis or zooming. To avoid
* refreshing the model on every paint event, we use a TmfUiRefreshHandler to
* coalesce requests and only execute the last one, we also check if the entries
* have changed to avoid useless model refresh.
*
* @param e
* paint event on the visible area
*/
@Override
public void paintControl(PaintEvent e) {
TmfUiRefreshHandler.getInstance().queueUpdate(this, () -> {
if (timeGraphControl.isDisposed()) {
return;
}
int timeSpace = getTimeGraphViewer().getTimeSpace();
Set<@NonNull TimeGraphEntry> newSet = getVisibleItems(DEFAULT_BUFFER_SIZE);
if (fPrevTimeSpace != timeSpace || !fVisibleEntries.equals(newSet)) {
/*
* Start a zoom thread if the set of visible entries has changed. We do not use
* lists as the order is not important. We cannot use the start index / size of
* the visible entries as we can collapse / reorder events.
*/
fVisibleEntries = newSet;
fPrevTimeSpace = timeSpace;
startZoomThread(getTimeGraphViewer().getTime0(), getTimeGraphViewer().getTime1());
}
});
}
});
IStatusLineManager statusLineManager = getViewSite().getActionBars().getStatusLineManager();
timeGraphControl.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
IWorkbenchPartSite site = getSite();
site.setSelectionProvider(timeGraphViewer.getSelectionProvider());
ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
createContextMenu();
fPartListener = new TimeGraphPartListener();
site.getPage().addPartListener(fPartListener);
fPartListener2 = new TimeGraphPartListener2();
site.getPage().addPartListener(fPartListener2);
fOriginalTabLabel = getPartName();
fContextService = site.getWorkbenchWindow().getService(IContextService.class);
if (timeGraphControl.isInFocus()) {
activateContextService();
}
timeGraphControl.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
deactivateContextService();
}
@Override
public void focusGained(FocusEvent e) {
activateContextService();
}
});
updateTimeFormat();
}
private void activateContextService() {
if (fActiveContexts.isEmpty()) {
fActiveContexts.add(fContextService.activateContext(TIMEGRAPH_UI_CONTEXT));
fActiveContexts.add(fContextService.activateContext(TMF_VIEW_UI_CONTEXT));
}
}
private void deactivateContextService() {
fContextService.deactivateContexts(fActiveContexts);
fActiveContexts.clear();
}
/**
* Get the set of items that are currently visible in the view, according to
* the visible area height, the vertical scroll position and the expanded
* state of the items. A buffer of items above and below can be included.
*
* @param buffer
* number of items above and below the current visible area that
* should be included
* @return a set of visible items in the view with buffer above and below
* @since 4.2
*/
protected @NonNull Set<@NonNull TimeGraphEntry> getVisibleItems(int buffer) {
TimeGraphControl timeGraphControl = fTimeGraphViewer.getTimeGraphControl();
if (timeGraphControl.isDisposed()) {
return Collections.emptySet();
}
int start = Integer.max(0, fTimeGraphViewer.getTopIndex() - buffer);
int end = Integer.min(fTimeGraphViewer.getExpandedElementCount() - 1,
fTimeGraphViewer.getTopIndex() + timeGraphControl.countPerPage() + buffer);
Set<@NonNull TimeGraphEntry> visible = new HashSet<>(end - start + 1);
for (int i = start; i <= end; i++) {
/*
* Use the getExpandedElement by index to avoid creating a copy of all the the
* elements.
*/
TimeGraphEntry element = (TimeGraphEntry) timeGraphControl.getExpandedElement(i);
if (element != null) {
visible.add(element);
}
}
return visible;
}
@Override
public void setFocus() {
fTimeGraphViewer.setFocus();
/* Force activation because focus gained event is not always received */
activateContextService();
}
@Override
public void dispose() {
super.dispose();
synchronized (fBuildJobMap) {
fBuildJobMap.values().forEach(Job::cancel);
}
if (fZoomThread != null) {
fZoomThread.cancel();
}
deactivateContextService();
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
IWorkbenchPage page = getSite().getPage();
if (fPartListener != null) {
page.removePartListener(fPartListener);
}
if (fPartListener2 != null) {
page.removePartListener(fPartListener2);
}
}
/**
* @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);
if (isPinned()) {
setPinned(null);
} else {
refresh();
}
}
}
/**
* Trace is updated: update the view range
*
* @param signal
* the signal received
* @since 3.1
*/
@TmfSignalHandler
public void traceUpdated(final TmfTraceUpdatedSignal signal) {
if (signal.getTrace() == fTrace) {
setTimeBoundsAndRefresh();
}
}
/**
* Handler for the selection range signal.
*
* @param signal
* The signal that's received
* @since 1.0
*/
@TmfSignalHandler
public void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) {
final ITmfTrace trace = fTrace;
if (signal.getSource() == this || trace == null) {
return;
}
ITmfTrace signalTrace = signal.getTrace();
if (signalTrace != null && !TmfTraceManager.getInstance().isSynchronized(trace, signalTrace)) {
return;
}
TmfTraceContext ctx = TmfTraceManager.getInstance().getTraceContext(trace);
long beginTime = ctx.getSelectionRange().getStartTime().toNanos();
long endTime = ctx.getSelectionRange().getEndTime().toNanos();
Display.getDefault().asyncExec(() -> {
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;
}
TmfTraceContext ctx = TmfTraceManager.getInstance().getTraceContext(checkNotNull(fTrace));
final long startTime = ctx.getWindowRange().getStartTime().toNanos();
final long endTime = ctx.getWindowRange().getEndTime().toNanos();
Display.getDefault().asyncExec(() -> {
if (fTimeGraphViewer.getControl().isDisposed()) {
return;
}
if (startTime == fTimeGraphViewer.getTime0() && endTime == fTimeGraphViewer.getTime1()) {
return;
}
fTimeGraphViewer.setStartFinishTime(startTime, endTime);
startZoomThread(startTime, endTime);
});
}
/**
* @param signal
* the format of the timestamps was updated.
*/
@TmfSignalHandler
public void updateTimeFormat(final TmfTimestampFormatUpdateSignal signal) {
updateTimeFormat();
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 updateTimeFormat() {
if (fTimeFormat == null) {
String datime = TmfTimePreferences.getPreferenceMap().get(ITmfTimePreferencesConstants.DATIME);
if (ITmfTimePreferencesConstants.TIME_ELAPSED_FMT.equals(datime)) {
fTimeGraphViewer.setTimeFormat(TimeFormat.RELATIVE);
} else {
fTimeGraphViewer.setTimeFormat(TimeFormat.CALENDAR);
}
} else {
fTimeGraphViewer.setTimeFormat(fTimeFormat);
}
}
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(), fTimeGraphViewer.getAllCollapsedElements()));
}
fTrace = trace;
TraceCompassLogUtils.traceInstant(LOGGER, Level.FINE, "TimeGraphView:LoadingTrace", "trace", trace.getName(), "viewId", getViewId()); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
restoreViewContext();
fEditorFile = TmfTraceManager.getInstance().getTraceEditorFile(trace);
synchronized (fEntryListMap) {
fEntryList = fEntryListMap.get(fTrace);
loadingTrace(trace);
if (fEntryList == null) {
rebuild();
} else {
setTimeBoundsAndRefresh();
}
}
getPresentationProvider().refresh();
}
private void setTimeBoundsAndRefresh() {
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() {
try (FlowScopeLog parentLogger = new FlowScopeLogBuilder(LOGGER, Level.FINE, "TimeGraphView:Rebuilding").setCategory(getViewId()).build()) { //$NON-NLS-1$
setTimeBoundsAndRefresh();
ITmfTrace viewTrace = fTrace;
if (viewTrace == null) {
return;
}
resetView(viewTrace);
List<IMarkerEventSource> markerEventSources = new ArrayList<>();
synchronized (fBuildJobMap) {
// Run the build jobs through the site progress service if available
IWorkbenchSiteProgressService service = null;
IWorkbenchPartSite site = getSite();
if (site != null) {
service = site.getService(IWorkbenchSiteProgressService.class);
}
for (ITmfTrace trace : getTracesToBuild(viewTrace)) {
if (trace == null) {
break;
}
List<@NonNull IMarkerEventSource> adapters = TmfTraceAdapterManager.getAdapters(trace, IMarkerEventSource.class);
markerEventSources.addAll(adapters);
Job buildJob = new Job(getTitle() + Messages.AbstractTimeGraphView_BuildJob) {
@Override
protected IStatus run(IProgressMonitor monitor) {
new BuildRunnable(trace, viewTrace, parentLogger).run(monitor);
monitor.done();
return Status.OK_STATUS;
}
};
fBuildJobMap.put(trace, buildJob);
if (service != null) {
service.schedule(buildJob);
} else {
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) {
}
/**
* Method called when the view is being loaded with a trace, ie when a trace
* becomes active in the view because it was opened, selected, pinned, etc.
* When this is called, the trace is already loaded in the base class, but
* it has not been redrawn or entries built yet. Implementing classes can
* add any logic here for a specific trace.
*
* Unlike the {@link #resetView(ITmfTrace)} method, this one is called even
* if entries already exist for the trace. And if entries need to be
* rebuilt, both {@link #resetView(ITmfTrace)} and this method will be
* called
*
* @param trace
* The trace that is being loaded
* @since 5.1
*/
protected void loadingTrace(@NonNull ITmfTrace trace) {
// To be implemented by children classes
}
/**
* 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 @Nullable List<@NonNull ITimeEvent> getEventList(@NonNull TimeGraphEntry entry,
long startTime, long endTime, long resolution, @NonNull IProgressMonitor monitor) {
return new ArrayList<>();
}
/**
* 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)) {
markers.addAll(markerEventSource.getMarkerList(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;
}
/**
* Gets the trace to viewer filters map.
*
* @return The trace to viewer filters map
* @since 3.3
*/
protected @NonNull Map<ITmfTrace, ViewerFilter[]> getFiltersMap() {
return checkNotNull(fFiltersMap);
}
/**
* Refresh the display
*/
protected void refresh() {
try (FlowScopeLog parentLogger = new FlowScopeLogBuilder(LOGGER, Level.FINE, "RefreshRequested").setCategory(getViewId()).build()) { //$NON-NLS-1$
final boolean isZoomThread = Thread.currentThread() instanceof ZoomThread;
TmfUiRefreshHandler.getInstance().queueUpdate(this, () -> {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "TimeGraphView:Refresh").setParentScope(parentLogger).build()) { //$NON-NLS-1$
if (fTimeGraphViewer.getControl().isDisposed()) {
return;
}
fDirty.incrementAndGet();
try {
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);
ITmfTrace trace = fTrace;
TmfTraceContext ctx = (trace == null) ? null : TmfTraceManager.getInstance().getTraceContext(trace);
long selectionBeginTime = ctx == null ? SWT.DEFAULT : ctx.getSelectionRange().getStartTime().toNanos();
long selectionEndTime = ctx == null ? SWT.DEFAULT : ctx.getSelectionRange().getEndTime().toNanos();
long startTime = ctx == null ? SWT.DEFAULT : ctx.getWindowRange().getStartTime().toNanos();
long endTime = ctx == null ? SWT.DEFAULT : ctx.getWindowRange().getEndTime().toNanos();
if (fStartTime > fEndTime) {
startTime = SWT.DEFAULT;
endTime = SWT.DEFAULT;
} else {
startTime = Math.min(Math.max(startTime, fStartTime), fEndTime);
endTime = Math.min(Math.max(endTime, fStartTime), fEndTime);
}
fTimeGraphViewer.setSelectionRange(selectionBeginTime, selectionEndTime, false);
fTimeGraphViewer.setStartFinishTime(startTime, endTime);
if (inputChanged && selectionBeginTime != SWT.DEFAULT) {
synchingToTime(selectionBeginTime);
}
ZoomThread zoomThread = fZoomThread;
if (!isZoomThread ||
(zoomThread != null && (zoomThread.getZoomStartTime() != startTime || zoomThread.getZoomEndTime() != endTime))) {
startZoomThread(startTime, endTime);
}
} finally {
if (fDirty.decrementAndGet() < 0) {
Activator.getDefault().logError(DIRTY_UNDERFLOW_ERROR, new Throwable());
}
}
}
});
}
}
/**
* Redraw the canvas
*/
protected void redraw() {
synchronized (fSyncObj) {
if (fRedrawState == State.IDLE) {
fRedrawState = State.BUSY;
} else {
fRedrawState = State.PENDING;
return;
}
}
try (FlowScopeLog flowParent = new FlowScopeLogBuilder(LOGGER, Level.FINE, "RedrawRequested").setCategory(getViewId()).build()) { //$NON-NLS-1$
Display.getDefault().asyncExec(() -> {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "TimeGraphView:Redraw").setParentScope(flowParent).build()) { //$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;
}
}
}
});
}
}
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. This function needs to be called in the
* UI thread.
*
* @param startTime
* the zoom start time
* @param endTime
* the zoom end time
* @since 2.0
*/
protected final void startZoomThread(long startTime, long endTime) {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "TimeGraphView:ZoomThreadCreated").setCategory(getViewId()).build()) { //$NON-NLS-1$
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;
ZoomThread zoomThread = fZoomThread;
if (zoomThread != null) {
zoomThread.cancel();
if (zoomThread.fZoomStartTime == clampedStartTime && zoomThread.fZoomEndTime == clampedEndTime) {
restart = true;
}
}
// This line requires to be in the UI thread
int timeSpace = getTimeGraphViewer().getTimeSpace();
if (timeSpace > 0) {
long resolution = Long.max(1, (clampedEndTime - clampedStartTime) / timeSpace);
zoomThread = createZoomThread(clampedStartTime, clampedEndTime, resolution, restart);
} else {
zoomThread = null;
}
fZoomThread = zoomThread;
if (zoomThread != null) {
zoomThread.setScopeId(log.getId());
/*
* 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) {
zoomThread.start();
// zoomThread decrements, so we increment here
fDirty.incrementAndGet();
}
}
} finally {
if (fDirty.decrementAndGet() < 0) {
Activator.getDefault().logError(DIRTY_UNDERFLOW_ERROR, new Throwable());
}
}
}
/**
* 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) {
return new ZoomThreadByEntry(getVisibleItems(DEFAULT_BUFFER_SIZE), startTime, endTime, resolution);
}
private void makeActions() {
fPreviousResourceAction = fTimeGraphViewer.getPreviousItemAction();
fPreviousResourceAction.setText(getPrevText());
fPreviousResourceAction.setToolTipText(getPrevTooltip());
fNextResourceAction = fTimeGraphViewer.getNextItemAction();
fNextResourceAction.setText(getNextText());
fNextResourceAction.setToolTipText(getNextTooltip());
}
/**
* Returns an action that toggles the display of labels
*
* @return the action
* @since 5.2
*/
protected Action getShowLabelsAction() {
final Action showLabelsAction = new Action(Messages.AbstractTimeGraphView_ShowLabelsActionText, IAction.AS_CHECK_BOX) {
@Override
public void run() {
boolean showLabels = isChecked();
getTimeGraphViewer().setLabelsVisible(showLabels);
redraw();
IDialogSettings dialogSettings = getDialogSettings(true);
dialogSettings.put(HIDE_LABELS_KEY, !showLabels);
}
};
boolean showLabels = true;
IDialogSettings dialogSettings = getDialogSettings(false);
if (dialogSettings != null) {
showLabels = !dialogSettings.getBoolean(HIDE_LABELS_KEY);
}
showLabelsAction.setChecked(showLabels);
getTimeGraphViewer().setLabelsVisible(showLabels);
return showLabelsAction;
}
/**
* Get the dialog settings for this view
*
* @param force
* true to create the section if it doesn't exist
* @return the dialog settings
*/
private IDialogSettings getDialogSettings(boolean force) {
IDialogSettings settings = Activator.getDefault().getDialogSettings();
String sectionName = getViewSite().getId();
IDialogSettings section = settings.getSection(sectionName);
if (section == null && force) {
section = settings.addNewSection(sectionName);
}
return section;
}
private class MarkerSetAction extends Action {
private MarkerSet fMarkerSet;
public MarkerSetAction(MarkerSet markerSet) {
super(markerSet == null ? Messages.AbstractTimeGraphView_MarkerSetNoneActionText : markerSet.getName(), IAction.AS_RADIO_BUTTON);
fMarkerSet = markerSet;
}
@Override
public void runWithEvent(Event event) {
if (isChecked()) {
MarkerUtils.setDefaultMarkerSet(fMarkerSet);
broadcast(new TmfMarkerEventSourceUpdatedSignal(AbstractTimeGraphView.this));
}
}
}
/**
* Get the marker set menu
*
* @return the menu manager object
* @since 3.0
*/
protected MenuManager getMarkerSetMenu() {
if (fMarkerSetMenu != null) {
return fMarkerSetMenu;
}
fMarkerSetMenu = new MenuManager(Messages.AbstractTimeGraphView_MarkerSetMenuText);
fMarkerSetMenu.setRemoveAllWhenShown(true);
fMarkerSetMenu.addMenuListener(mgr -> {
Action noneAction = new MarkerSetAction(null);
MarkerSet defaultMarkerSet = MarkerUtils.getDefaultMarkerSet();
String defaultMarkerSetId = (defaultMarkerSet == null) ? null : defaultMarkerSet.getId();
noneAction.setChecked(defaultMarkerSetId == null);
mgr.add(noneAction);
List<MarkerSet> markerSets = MarkerConfigXmlParser.getMarkerSets();
for (MarkerSet markerSet : markerSets) {
Action action = new MarkerSetAction(markerSet);
action.setChecked(markerSet.getId().equals(defaultMarkerSetId));
mgr.add(action);
}
mgr.add(new Separator());
mgr.add(new Action(Messages.AbstractTimeGraphView_MarkerSetEditActionText) {
@Override
public void run() {
MarkerConfigXmlParser.initMarkerSets();
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
IFileStore fileStore = EFS.getLocalFileSystem().getStore(MarkerConfigXmlParser.MARKER_CONFIG_PATH);
try {
IDE.openEditorOnFileStore(page, fileStore);
} catch (PartInitException e) {
Activator.getDefault().logError("Error opening editor on " + MarkerConfigXmlParser.MARKER_CONFIG_PATH, e); //$NON-NLS-1$
}
}
});
});
return fMarkerSetMenu;
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalToolBar(bars.getToolBarManager());
fillLocalMenu(bars.getMenuManager());
}
/**
* Add contributions to the local tool bar manager. The contributions should be
* appended to the {@link IWorkbenchActionConstants#MB_ADDITIONS} group.
*
* @param manager
* the tool bar manager
*/
protected void fillLocalToolBar(IToolBarManager manager) {
if (fFilterColumns != null && fFilterLabelProvider != null && fFilterColumns.length > 0) {
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getShowFilterDialogAction());
}
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getShowLegendAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getResetScaleAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getPreviousEventAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getNextEventAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getToggleBookmarkAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getPreviousMarkerAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getNextMarkerAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fPreviousResourceAction);
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fNextResourceAction);
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getZoomInAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fTimeGraphViewer.getZoomOutAction());
manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator());
}
/**
* Add contributions to the local menu manager.
*
* @param manager
* the tool bar manager
* @since 2.0
*/
protected void fillLocalMenu(IMenuManager manager) {
manager.add(fTimeGraphViewer.getGridlinesMenu());
manager.add(getShowLabelsAction());
manager.add(fTimeGraphViewer.getMarkersMenu());
manager.add(getMarkerSetMenu());
}
/**
* @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);
}
}
@Override
public synchronized void setPinned(ITmfTrace trace) {
if (trace != null) {
/* Ignore relevant inbound signals */
TmfSignalManager.addIgnoredInboundSignal(this, TmfTraceOpenedSignal.class);
TmfSignalManager.addIgnoredInboundSignal(this, TmfTraceSelectedSignal.class);
setPartName(String.format("%s <%s>", fOriginalTabLabel, TmfTraceManager.getInstance().getTraceUniqueName(trace))); //$NON-NLS-1$
if (!trace.equals(fTrace)) {
loadTrace(trace);
}
} else {
/* Handle relevant inbound signals */
TmfSignalManager.removeIgnoredInboundSignal(this, TmfTraceOpenedSignal.class);
TmfSignalManager.removeIgnoredInboundSignal(this, TmfTraceSelectedSignal.class);
setPartName(fOriginalTabLabel);
ITmfTrace activeTrace = TmfTraceManager.getInstance().getActiveTrace();
if (activeTrace != null && !activeTrace.equals(fTrace)) {
loadTrace(activeTrace);
} else {
refresh();
}
}
if (fPinAction != null) {
fPinAction.setPinnedTrace(trace);
}
}
/**
* 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.remove(fTrace);
applyExpandedStateContext(viewContext);
if (fColumnComparators != null) {
final Tree tree = fTimeGraphViewer.getTree();
final TreeColumn column = tree.getColumn(fCurrentSortColumn);
tree.setSortDirection(fSortDirection);
tree.setSortColumn(column);
}
// restore and reveal selection
if ((viewContext != null) && (viewContext.getSelection() != null)) {
fTimeGraphViewer.setSelection(viewContext.getSelection(), true);
}
}
private void applyExpandedStateContext(ViewContext viewContext) {
if (viewContext != null) {
fTimeGraphViewer.expandAll();
fTimeGraphViewer.setExpandedState(viewContext.getCollapsedEntries(), false);
}
}
private static class ViewContext {
private final int fSortColumnIndex;
private final int fSortDirection;
private final @Nullable ITimeGraphEntry fSelection;
private final @NonNull Set<@NonNull ITimeGraphEntry> fCollapsedEntries;
ViewContext(int sortColunm, int sortDirection, ITimeGraphEntry selection, @NonNull Set<@NonNull ITimeGraphEntry> collapsedEntries) {
fSortColumnIndex = sortColunm;
fSortDirection = sortDirection;
fSelection = selection;
fCollapsedEntries = ImmutableSet.copyOf(collapsedEntries);
}
/**
* @return the sortColumn
*/
public int getSortColumn() {
return fSortColumnIndex;
}
/**
* @return the sortDirection
*/
public int getSortDirection() {
return fSortDirection;
}
/**
* @return the selection
*/
public ITimeGraphEntry getSelection() {
return fSelection;
}
/**
* Get the set of collapsed entries
*
* @return The set of collapsed entries
*/
public @NonNull Set<@NonNull ITimeGraphEntry> getCollapsedEntries() {
return fCollapsedEntries;
}
}
/**
* 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(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(manager -> {
fillTimeGraphEntryContextMenu(fEntryMenuManager);
fEntryMenuManager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
});
getSite().registerContextMenu(fEntryMenuManager, fTimeGraphViewer.getSelectionProvider());
}
/**
* This method build the multimap of regexes by property that will be used to
* filter the timegraph states
*
* Override this method to add other regexes with their properties. The data
* provider should handle everything after.
*
* @return The multimap of regexes by property
* @since 4.0
*/
protected @NonNull Multimap<@NonNull Integer, @NonNull String> getRegexes() {
Multimap<@NonNull Integer, @NonNull String> regexes = HashMultimap.create();
@NonNull String dialogRegex = fTimeEventFilterDialog != null ? fTimeEventFilterDialog.getTextBoxRegex() : ""; //$NON-NLS-1$
if (!dialogRegex.isEmpty()) {
regexes.put(IFilterProperty.DIMMED, dialogRegex);
}
Set<@NonNull String> savedFilters = fTimeEventFilterDialog != null ? fTimeEventFilterDialog.getSavedFilters() : Collections.emptySet();
for (String savedFilter : savedFilters) {
regexes.put(IFilterProperty.EXCLUDE, savedFilter);
regexes.put(IFilterProperty.DIMMED, savedFilter);
}
ITmfTrace trace = getTrace();
if (trace == null) {
return regexes;
}
TraceCompassFilter globalFilter = TraceCompassFilter.getFilterForTrace(trace);
if (globalFilter == null) {
return regexes;
}
regexes.putAll(IFilterProperty.DIMMED, globalFilter.getRegexes());
return regexes;
}
private @Nullable TimeGraphControl getTimeGraphControl() {
TimeGraphViewer viewer = getTimeGraphViewer();
if (viewer != null) {
TimeGraphControl control = viewer.getTimeGraphControl();
if (control != null) {
return control;
}
}
return null;
}
/**
* get the time event filter dialog
*
* @return The time event filter dialog. Could be null.
* @since 4.0
*/
protected TimeEventFilterDialog getTimeEventFilterDialog() {
return fTimeEventFilterDialog;
}
/**
* Fill context menu
*
* @param menuManager
* a menuManager to fill
* @since 2.0
*/
protected void fillTimeGraphEntryContextMenu(@NonNull IMenuManager menuManager) {
// do nothing
}
/**
* Cancel and restart the zoom thread.
*
* @since 4.0
*/
public void restartZoomThread() {
ZoomThread zoomThread = fZoomThread;
if (zoomThread != null) {
// Make sure that the zoom thread is not a restart (resume of the previous)
zoomThread.cancel();
fZoomThread = null;
}
Runnable runnable = () -> {
startZoomThread(getTimeGraphViewer().getTime0(), getTimeGraphViewer().getTime1());
};
Display display = PlatformUI.getWorkbench().getDisplay();
if (display == Display.getCurrent()) {
runnable.run();
} else {
Display.getDefault().asyncExec(runnable);
}
}
/**
* Add a paint listener to the view
*
* @param listener
* The paint listener
* @since 4.0
*/
public void addPaintListener(PaintListener listener) {
Control tgControl = getTimeGraphViewer().getTimeGraphControl();
tgControl.addPaintListener(listener);
}
/**
* Gets the time event filter action
*
* @return the timeEventFilterAction
* @since 4.1
*/
public Action getTimeEventFilterAction() {
return fTimeEventFilterAction;
}
/*
* 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();
}
public String[] getColumnTexts(@NonNull ITimeGraphEntry entry) {
String[] texts = null;
if (fColumns != null) {
texts = new String[fColumns.length];
for (int i = 0; i < fColumns.length; i++) {
texts[i] = fLabelProvider.getColumnText(entry, i);
}
} else {
texts = new String[1];
texts[0] = entry.getName();
}
return texts;
}
}
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) {
// do nothing
}
@Override
public void partClosed(IWorkbenchPart part) {
// do nothing
}
@Override
public void partOpened(IWorkbenchPart part) {
// do nothing
}
}
class TimeGraphPartListener2 implements IPartListener2 {
@Override
public void partActivated(IWorkbenchPartReference partRef) {
// do nothing
}
@Override
public void partBroughtToTop(IWorkbenchPartReference partRef) {
// do nothing
}
@Override
public void partClosed(IWorkbenchPartReference partRef) {
// do nothing
}
@Override
public void partDeactivated(IWorkbenchPartReference partRef) {
// do nothing
}
@Override
public void partOpened(IWorkbenchPartReference partRef) {
// do nothing
}
@Override
public void partHidden(IWorkbenchPartReference partRef) {
IWorkbenchPart part = partRef.getPart(false);
if (part != null && part == AbstractTimeGraphView.this) {
Display.getDefault().asyncExec(() -> {
if (fTimeEventFilterDialog != null) {
fTimeEventFilterDialog.close();
}
});
}
}
@Override
public void partVisible(IWorkbenchPartReference partRef) {
IWorkbenchPart part = partRef.getPart(false);
if (part != null && part == AbstractTimeGraphView.this) {
Display.getDefault().asyncExec(() -> {
if (fTimeEventFilterDialog != null && fTimeEventFilterDialog.isFilterActive()) {
fTimeEventFilterDialog.open();
}
});
}
}
@Override
public void partInputChanged(IWorkbenchPartReference partRef) {
// do nothing
}
}
/**
* Set or remove the global regex filter value
*
* @param signal
* the signal carrying the regex value
* @since 4.2
*/
@TmfSignalHandler
public void regexFilterApplied(TmfFilterAppliedSignal signal) {
// Restart the zoom thread to apply the new filter
Display.getDefault().asyncExec(() -> restartZoomThread());
}
/**
* Signal indicating a data model element was selected somewhere
*
* @param signal
* the signal carrying the select data model metadata
* @since 4.2
*/
@TmfSignalHandler
public void selectionChanged(TmfDataModelSelectedSignal signal) {
// Ignore signal from self
if (signal.getSource() == this) {
return;
}
Multimap<@NonNull String, @NonNull Object> metadata = signal.getMetadata();
// See if the current selection intersects the metadata
ITimeGraphEntry selection = getTimeGraphViewer().getSelection();
if (selection instanceof IElementResolver &&
IElementResolver.commonIntersect(metadata, ((IElementResolver) selection).getMetadata())) {
return;
}
// See if an entry intersects the metadata
List<TimeGraphEntry> traceEntries = getEntryList(getTrace());
if (traceEntries == null) {
return;
}
for (TraceEntry traceEntry : Iterables.filter(traceEntries, TraceEntry.class)) {
Iterable<TimeGraphEntry> unfiltered = Utils.flatten(traceEntry);
for (TimeGraphEntry entry : unfiltered) {
if (IElementResolver.commonIntersect(metadata, entry.getMetadata())) {
getTimeGraphViewer().setSelection(entry, true);
}
}
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == ITmfTimeNavigationProvider.class) {
return (T) getTimeNavigator();
}
if (adapter == ITmfTimeZoomProvider.class) {
return (T) getTimeZoomProvider();
}
if (adapter == ITmfZoomToSelectionProvider.class ) {
return (T) getZoomToSelectionProvider();
}
return super.getAdapter(adapter);
}
private ITmfTimeNavigationProvider getTimeNavigator() {
return left -> {
TimeGraphControl control = getTimeGraphControl();
if (control != null) {
control.horizontalScroll(left);
}
};
}
private ITmfTimeZoomProvider getTimeZoomProvider() {
return (zoomIn, useMousePosition) -> {
TimeGraphControl control = getTimeGraphControl();
TimeGraphViewer viewer = getTimeGraphViewer();
if (control != null && viewer != null) {
if (useMousePosition) {
control.zoom(zoomIn);
} else {
int xCoord = control.toControl(control.getDisplay().getCursorLocation()).x;
if ((viewer.getNameSpace() <= xCoord) && (xCoord < control.getSize().x)) {
if (zoomIn) {
control.zoomIn();
} else {
control.zoomOut();
}
}
}
}
};
}
private ITmfZoomToSelectionProvider getZoomToSelectionProvider() {
return () -> {
TimeGraphViewer viewer = getTimeGraphViewer();
if (viewer != null) {
long selBegin = viewer.getSelectionBegin();
long selEnd = viewer.getSelectionEnd();
if (selBegin != selEnd) {
viewer.setStartFinishTimeNotify(selBegin, selEnd);
}
}
};
}
}