blob: e3d555a3ba59cf14ed29f1427e1e8064eb5771ad [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2020 Draeger, Auriga
*
* 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
**********************************************************************/
package org.eclipse.tracecompass.incubator.internal.tmf.ui.multiview.ui.view.timegraph;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.ArrayList;
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.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.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.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.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
import org.eclipse.tracecompass.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.incubator.internal.tmf.ui.multiview.ui.view.IMultiViewer;
import org.eclipse.tracecompass.incubator.internal.tmf.ui.multiview.ui.view.MultiView;
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.TraceCompassFilter;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.internal.tmf.ui.util.TimeGraphStyleUtil;
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.viewers.TmfViewer;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.ITimeGraphEntryComparator;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.ITimeGraphLegendProvider;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.Messages;
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.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.TimeFormat;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IViewSite;
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.IWorkbenchWindow;
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.progress.IWorkbenchSiteProgressService;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
/**
* An abstract viewer all multi time graph viewers can inherit. This viewer
* contains a time graph viewer.
* <p>
* The viewer corresponds (copy-pasted actually) to
* {@link AbstractTimeGraphView} to be used with the {@link MultiView}.
*
* @author Ivan Grinenko
*
*/
@SuppressWarnings("restriction")
public abstract class AbstractTimeGraphMultiViewer extends TmfViewer implements IMultiViewer, IResourceChangeListener {
private static final @NonNull Logger LOGGER = TraceCompassLog.getLogger(AbstractTimeGraphMultiViewer.class);
private static final String TIMEGRAPH_UI_CONTEXT = "org.eclipse.tracecompass.incubator.internal.tmf.ui.multiview.ui.view.timegraph.context"; //$NON-NLS-1$
private static final String TMF_VIEW_UI_CONTEXT = "org.eclipse.tracecompass.incubator.internal.tmf.ui.multiview.context"; //$NON-NLS-1$
private static final String DIRTY_UNDERFLOW_ERROR = "Dirty underflow error"; //$NON-NLS-1$
private static final int DEFAULT_BUFFER_SIZE = 3;
private static final Pattern RGBA_PATTERN = Pattern.compile("RGBA \\{(\\d+), (\\d+), (\\d+), (\\d+)\\}"); //$NON-NLS-1$
/**
* Constant indicating that all levels of the time graph should be expanded
*/
protected static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS;
/**
* Redraw state enum
*/
private enum State {
IDLE, BUSY, PENDING
}
private final String fName;
/** The time graph viewer */
private TimeGraphViewer fTimeGraphViewer;
/** 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;
/**
* 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 ITimeGraphPresentationProvider fPresentation;
/** The tree column label array, or null for a single default column */
private String[] fColumns;
private Comparator<ITimeGraphEntry>[] fColumnComparators;
/** The default column index for sorting */
private int fInitialSortColumn = 0;
/** The default column index for sorting */
private int fCurrentSortColumn = 0;
private @Nullable TimeFormat fTimeFormat = null;
/** The current sort direction */
private int fSortDirection = SWT.DOWN;
/**
* The width of the last time space that was zoomed on.
*/
private int fPrevTimeSpace = -1;
/**
* Set of visible entries to zoom on.
*/
private @NonNull Set<@NonNull TimeGraphEntry> fVisibleEntries = Collections.emptySet();
/** Flag to indicate to reveal selection */
private volatile boolean fIsRevealSelection = false;
/** A comparator class */
private Comparator<ITimeGraphEntry> fEntryComparator = null;
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 viewer context hash map */
private final Map<ITmfTrace, ViewerContext> fViewerContext = 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 timegraph event filter action */
private Action fTimeEventFilterAction;
/** The time graph event filter dialog */
private TimeEventFilterDialog fTimeEventFilterDialog;
private final IWorkbenchPartSite fSite;
/** Time Graph Viewer part listener */
private TimeGraphPartListener fPartListener;
private TimeGraphPartListener2 fPartListener2;
private IContextService fContextService;
private List<IContextActivation> fActiveContexts = new ArrayList<>();
/**
* 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();
/**
* 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;
/** 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()) {
TmfSignalManager.dispatchSignal(new TmfDataModelSelectedSignal(AbstractTimeGraphMultiViewer.this, metadata));
}
}
};
/**
* ID of the viewer.
*/
public static final @NonNull String ID = "org.eclipse.tracecompass.incubator.internal.tmf.ui.multiview.ui.view.MultiTimeGraphViewer"; //$NON-NLS-1$
/**
* Constructor.
*
* @param parent
* parent for the viewer
* @param pres
* presentation provider for time graph
* @param site
* workbench part site
*/
public AbstractTimeGraphMultiViewer(Composite parent, ITimeGraphPresentationProvider pres, IWorkbenchPartSite site) {
super(parent);
fPresentation = pres;
fSite = site;
fName = AbstractTimeGraphMultiViewer.class.getSimpleName();
fDisplayWidth = Display.getDefault().getBounds().width;
TimeGraphViewer timeGraphViewer = new TimeGraphViewer(parent, SWT.NONE);
timeGraphViewer.setTimeGraphScaleVisible(false);
timeGraphViewer.setMarkerAxisControlVisible(false);
timeGraphViewer.setHorizontalScrollBarVisible(false);
fTimeGraphViewer = timeGraphViewer;
}
/**
* Initializes the viewer in the
* {@link AbstractTimeGraphView#createPartControl(Composite)} manner.
*/
public void init() {
if (fLabelProvider != null) {
fTimeGraphViewer.setTimeGraphLabelProvider(fLabelProvider);
}
if (fLegendProvider != null) {
fTimeGraphViewer.setLegendProvider(fLegendProvider);
}
if (fColumns != null) {
fTimeGraphViewer.setColumns(fColumns);
if (fColumnComparators != null) {
createColumnSelectionListener(fTimeGraphViewer.getTree());
}
}
fTimeGraphViewer.setTimeGraphContentProvider(fTimeGraphContentProvider);
fTimeGraphViewer.setFilterContentProvider(
fFilterContentProvider != null ? fFilterContentProvider : fTimeGraphContentProvider);
fTimeGraphViewer.setFilterLabelProvider(fFilterLabelProvider);
fTimeGraphViewer.setFilterColumns(fFilterColumns);
fTimeGraphViewer.addSelectionListener(fMetadataSelectionListener);
ITimeGraphPresentationProvider presentationProvider = getPresentationProvider();
fTimeGraphViewer.setTimeGraphProvider(presentationProvider);
presentationProvider.addColorListener(stateItems -> TimeGraphStyleUtil.loadValues(getPresentationProvider()));
presentationProvider.refresh();
fTimeGraphViewer.setAutoExpandLevel(fAutoExpandLevel);
fTimeGraphViewer.setWeights(fWeight);
TimeGraphControl timeGraphControl = fTimeGraphViewer.getTimeGraphControl();
Action timeEventFilterAction = new Action() {
@Override
public void run() {
int xCoord = timeGraphControl.toControl(timeGraphControl.getDisplay().getCursorLocation()).x;
if ((fTimeGraphViewer.getNameSpace() < xCoord) && (xCoord < timeGraphControl.getSize().x)) {
if (fTimeEventFilterDialog != null) {
fTimeEventFilterDialog.close();
fTimeEventFilterDialog = null;
}
fTimeEventFilterDialog = new TimeEventFilterDialog(timeGraphControl.getShell(),
AbstractTimeGraphMultiViewer.this, getTimeGraphViewer().getTimeGraphControl());
fTimeEventFilterDialog.open();
}
}
};
fTimeEventFilterAction = timeEventFilterAction;
fTimeGraphViewer.addRangeListener(event -> {
final long startTime = event.getStartTime();
final long endTime = event.getEndTime();
TmfTimeRange range = new TmfTimeRange(TmfTimestamp.fromNanos(startTime), TmfTimestamp.fromNanos(endTime));
TmfSignalManager.dispatchSignal(
new TmfWindowRangeUpdatedSignal(AbstractTimeGraphMultiViewer.this, range, fTrace));
startZoomThread(startTime, endTime);
});
fTimeGraphViewer.addTimeListener(event -> {
ITmfTimestamp startTime = TmfTimestamp.fromNanos(event.getBeginTime());
ITmfTimestamp endTime = TmfTimestamp.fromNanos(event.getEndTime());
TmfSignalManager.dispatchSignal(new TmfSelectionRangeUpdatedSignal(
AbstractTimeGraphMultiViewer.this, startTime, endTime, fTrace));
});
fTimeGraphViewer.addBookmarkListener(new ITimeGraphBookmarkListener() {
@Override
public void bookmarkAdded(final TimeGraphBookmarkEvent event) {
try {
ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
IMarkerEvent bookmark = event.getBookmark();
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());
}
});
}
});
if (fSite instanceof IViewSite) {
IStatusLineManager statusLineManager = ((IViewSite) fSite).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(fTimeGraphViewer.getSelectionProvider());
ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
createContextMenu();
fPartListener = new TimeGraphPartListener();
site.getPage().addPartListener(fPartListener);
fPartListener2 = new TimeGraphPartListener2();
site.getPage().addPartListener(fPartListener2);
contextServiceInit(timeGraphControl, site);
updateTimeFormat();
TmfSignalManager.register(this);
}
private void contextServiceInit(TimeGraphControl timeGraphControl, IWorkbenchPartSite site) {
@Nullable
IWorkbenchWindow workbenchWindow = site.getWorkbenchWindow();
if (workbenchWindow == null) {
return;
}
fContextService = workbenchWindow.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();
}
});
}
@Override
public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() {
if (fTimeGraphViewer == null) {
return null;
}
return fTimeGraphViewer.getTimeViewAlignmentInfo();
}
@Override
public int getAvailableWidth(int requestedOffset) {
if (fTimeGraphViewer == null) {
return 0;
}
return fTimeGraphViewer.getAvailableWidth(requestedOffset);
}
@Override
public void performAlign(int offset, int width) {
if (fTimeGraphViewer != null) {
fTimeGraphViewer.performAlign(offset, width);
}
}
@Override
public void dispose() {
fTimeGraphViewer.dispose();
TmfSignalManager.deregister(this);
synchronized (fBuildJobMap) {
fBuildJobMap.values().forEach(Job::cancel);
}
if (fZoomThread != null) {
fZoomThread.cancel();
}
deactivateContextService();
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
IWorkbenchPage page = fSite.getPage();
if (fPartListener != null) {
page.removePartListener(fPartListener);
}
if (fPartListener2 != null) {
page.removePartListener(fPartListener2);
}
}
@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;
}
}
}
@Override
public String getName() {
return fName;
}
/**
* @return ID of the viewer
*/
public String getViewerId() {
return ID;
}
/**
* Getter for the time graph viewer
*
* @return The time graph viewer
*/
public TimeGraphViewer getTimeGraphViewer() {
return fTimeGraphViewer;
}
/**
* @return workbench part site
*/
public IWorkbenchPartSite getSite() {
return fSite;
}
private void activateContextService() {
if (fContextService == null) {
return;
}
if (fActiveContexts.isEmpty()) {
fActiveContexts.add(fContextService.activateContext(TIMEGRAPH_UI_CONTEXT));
fActiveContexts.add(fContextService.activateContext(TMF_VIEW_UI_CONTEXT));
}
}
private void deactivateContextService() {
if (fContextService == null) {
return;
}
fContextService.deactivateContexts(fActiveContexts);
fActiveContexts.clear();
}
// ------------------------------------------------------------------------
// 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 viewer
*
* @param signal
* the signal received
*/
@TmfSignalHandler
public void traceClosed(final TmfTraceClosedSignal signal) {
resetViewer(signal.getTrace());
if (signal.getTrace() == fTrace) {
fTrace = null;
fEditorFile = null;
setStartTime(SWT.DEFAULT);
setEndTime(SWT.DEFAULT);
refresh();
}
}
/**
* Trace is updated: update the viewer range
*
* @param signal
* the signal received
*/
@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
*/
@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
*/
@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
*/
@TmfSignalHandler
public void markerEventSourceUpdated(final TmfMarkerEventSourceUpdatedSignal signal) {
getTimeGraphViewer().setMarkerCategories(getMarkerCategories());
getTimeGraphViewer().setMarkers(null);
refresh();
}
/**
* Getter for the presentation provider
*
* @return The time graph presentation provider
*/
protected ITimeGraphPresentationProvider getPresentationProvider() {
return fPresentation;
}
/**
* 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;
}
/**
* Method called when the viewer is being loaded with a trace, ie when a
* trace becomes active in the viewer 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 #resetViewer(ITmfTrace)} method, this one is called
* even if entries already exist for the trace. And if entries need to be
* rebuilt, both {@link #resetViewer(ITmfTrace)} and this method will be
* called
*
* @param trace
* The trace that is being loaded
*/
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 viewer. 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);
}
/**
* Get the set of items that are currently visible in the viewer, 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 viewer with buffer above and below
*/
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;
}
/**
* 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
*/
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 viewer 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 view-specific marker categories. Default implementation
* returns an empty list.
*
* @return The list of marker categories
*/
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
*/
protected @NonNull List<IMarkerEvent> getViewMarkerList(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 viewer-specific marker categories. Default
* implementation returns an empty list.
*
* @return The list of marker categories
*/
protected @NonNull List<String> getViewerMarkerCategories() {
return new ArrayList<>();
}
/**
* Gets the list of viewer-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
*/
protected @NonNull List<IMarkerEvent> getViewerMarkerList(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
*/
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
*/
protected @NonNull List<String> getMarkerCategories() {
Set<String> categories = new LinkedHashSet<>(getViewerMarkerCategories());
for (IMarkerEventSource markerEventSource : getMarkerEventSources(fTrace)) {
categories.addAll(markerEventSource.getMarkerCategories());
}
return new ArrayList<>(categories);
}
/**
* Gets the display width
*
* @return the display width
*/
protected int getDisplayWidth() {
return fDisplayWidth;
}
/**
* Gets the trace displayed in the view.
*
* @return the trace
*/
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;
}
/**
* 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);
}
}
}
/**
* Gets the list of marker event sources for a given trace.
*
* @param trace
* The trace
* @return The list of marker event sources
*/
private @NonNull List<IMarkerEventSource> getMarkerEventSources(ITmfTrace trace) {
List<IMarkerEventSource> markerEventSources = fMarkerEventSourcesMap.get(trace);
if (markerEventSources == null) {
markerEventSources = Collections.emptyList();
}
return markerEventSources;
}
// ------------------------------------------------------------------------
// 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());
fViewerContext.put(fTrace, new ViewerContext(fCurrentSortColumn, fSortDirection, fTimeGraphViewer.getSelection(), fTimeGraphViewer.getAllCollapsedElements()));
}
fTrace = trace;
TraceCompassLogUtils.traceInstant(LOGGER, Level.FINE, "MultiTimeGraphViewer:LoadingTrace", "trace", trace.getName(), "viewerId", getViewerId()); //$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, "MultiTimeGraphViewer:Rebuilding").setCategory(getViewerId()).build()) { //$NON-NLS-1$
setTimeBoundsAndRefresh();
ITmfTrace viewerTrace = fTrace;
if (viewerTrace == null) {
return;
}
resetViewer(viewerTrace);
List<IMarkerEventSource> markerEventSources = new ArrayList<>();
synchronized (fBuildJobMap) {
// Run the build jobs through the site progress service if
// available
IWorkbenchSiteProgressService service = null;
if (fSite != null) {
service = fSite.getService(IWorkbenchSiteProgressService.class);
}
for (ITmfTrace trace : getTracesToBuild(viewerTrace)) {
if (trace == null) {
break;
}
List<@NonNull IMarkerEventSource> adapters = TmfTraceAdapterManager.getAdapters(trace, IMarkerEventSource.class);
markerEventSources.addAll(adapters);
Job buildJob = new Job(getName() + Messages.AbstractTimeGraphView_BuildJob) {
@Override
protected IStatus run(IProgressMonitor monitor) {
new BuildRunnable(trace, viewerTrace, parentLogger).run(monitor);
monitor.done();
return Status.OK_STATUS;
}
};
fBuildJobMap.put(trace, buildJob);
if (service != null) {
service.schedule(buildJob);
} else {
buildJob.schedule();
}
}
}
fMarkerEventSourcesMap.put(viewerTrace, markerEventSources);
}
}
/**
* Method called when synching to a given timestamp. Inheriting classes can
* perform actions here to update the viewer at the given timestamp.
*
* @param time
* The currently selected time
*/
protected void synchingToTime(long time) {
}
@Override
public void refresh() {
try (FlowScopeLog parentLogger = new FlowScopeLogBuilder(LOGGER, Level.FINE, "RefreshRequested").setCategory(getViewerId()).build()) { //$NON-NLS-1$
final boolean isZoomThread = Thread.currentThread() instanceof ZoomThread;
TmfUiRefreshHandler.getInstance().queueUpdate(this, () -> {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "MultiTimeGraphViewer: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(getViewerId()).build()) { //$NON-NLS-1$
Display.getDefault().asyncExec(() -> {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "MultiTimeGraphViewer: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;
}
}
}
});
}
}
/**
* 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
*/
protected final void startZoomThread(long startTime, long endTime) {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "MultiTimeGraphViewer:ZoomThreadCreated").setCategory(getViewerId()).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
*/
protected @Nullable ZoomThread createZoomThread(long startTime, long endTime, long resolution, boolean restart) {
return new ZoomThreadByEntry(getVisibleItems(DEFAULT_BUFFER_SIZE), startTime, endTime, resolution);
}
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 sortChildren(ITimeGraphEntry entry, Comparator<ITimeGraphEntry> comparator) {
if (entry instanceof TimeGraphEntry) {
((TimeGraphEntry) entry).sortChildren(comparator);
}
for (ITimeGraphEntry child : entry.getChildren()) {
sortChildren(child, comparator);
}
}
private void restoreViewContext() {
ViewerContext viewerContext = fViewerContext.get(fTrace);
if (fColumnComparators != null) {
// restore sort settings
fSortDirection = SWT.DOWN;
fCurrentSortColumn = fInitialSortColumn;
if (viewerContext != null) {
fSortDirection = viewerContext.getSortDirection();
fCurrentSortColumn = viewerContext.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() {
ViewerContext viewContext = fViewerContext.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(ViewerContext viewContext) {
if (viewContext != null) {
fTimeGraphViewer.expandAll();
fTimeGraphViewer.setExpandedState(viewContext.getCollapsedEntries(), false);
}
}
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;
}
// 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, "MultiTimeGraphViewer: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
*/
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(AbstractTimeGraphMultiViewer.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, "MultiTimeGraphViewer:ZoomThread", "start", fZoomStartTime, "end", fZoomEndTime).setCategoryAndId(getViewerId(), 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
*/
protected void applyResults(Runnable runnable) {
AbstractTimeGraphMultiViewer.this.applyResults(runnable);
}
/**
* Run the zoom operation.
*
*/
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
*/
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
*/
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 viewer-specific markers when zooming */
try (TraceCompassLogUtils.ScopeLog markerLoglog = new TraceCompassLogUtils.ScopeLog(LOGGER, Level.FINER, "ZoomThread:GettingMarkers")) { //$NON-NLS-1$
List<IMarkerEvent> markers = new ArrayList<>(getViewerMarkerList(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
*/
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
*/
protected void zoomEntries(@NonNull Iterable<@NonNull TimeGraphEntry> entries,
long zoomStartTime, long zoomEndTime, long resolution, boolean fullSearch, @NonNull IProgressMonitor monitor) {
try {
Map<Integer, Predicate<Multimap<String, Object>>> predicates = generateRegexPredicate();
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
*/
@NonNullByDefault
protected void doFilterEvents(TimeGraphEntry entry, List<ITimeEvent> eventList, Map<Integer, Predicate<Multimap<String, Object>>> 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<Multimap<String, Object>>> mapEntry : predicates.entrySet()) {
Predicate<Multimap<String, Object>> value = Objects.requireNonNull(mapEntry.getValue());
Multimap<String, Object> toTest = HashMultimap.create();
getPresentationProvider().getFilterInput(te).forEach((k, e) -> toTest.put(k, e));
te.computeData().forEach((k, e) -> toTest.put(k, e));
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);
}
}
}
/**
* Generate the predicate for every property from the regexes
*
* @return A map of predicate by property
*/
@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;
}
/**
* 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();
}
}
class TimeGraphPartListener implements IPartListener {
@Override
public void partActivated(IWorkbenchPart part) {
if (part == AbstractTimeGraphMultiViewer.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 == AbstractTimeGraphMultiViewer.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 == AbstractTimeGraphMultiViewer.this) {
Display.getDefault().asyncExec(() -> {
if (fTimeEventFilterDialog != null) {
fTimeEventFilterDialog.close();
}
});
}
}
@Override
public void partVisible(IWorkbenchPartReference partRef) {
IWorkbenchPart part = partRef.getPart(false);
if (part != null && part == AbstractTimeGraphMultiViewer.this) {
Display.getDefault().asyncExec(() -> {
if (fTimeEventFilterDialog != null && fTimeEventFilterDialog.isFilterActive()) {
fTimeEventFilterDialog.open();
}
});
}
}
@Override
public void partInputChanged(IWorkbenchPartReference partRef) {
// do nothing
}
}
private static class ViewerContext {
private final int fSortColumnIndex;
private final int fSortDirection;
private final @Nullable ITimeGraphEntry fSelection;
private final @NonNull Set<@NonNull ITimeGraphEntry> fCollapsedEntries;
ViewerContext(int sortColumn, int sortDirection, ITimeGraphEntry selection, @NonNull Set<@NonNull ITimeGraphEntry> collapsedEntries) {
fSortColumnIndex = sortColumn;
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 viewer internal data for a given trace.
*
* When overriding this method make sure to call the super implementation.
*
* @param viewerTrace
* trace to reset the viewer for.
*/
protected void resetViewer(ITmfTrace viewerTrace) {
if (viewerTrace == null) {
return;
}
synchronized (fBuildJobMap) {
for (ITmfTrace trace : getTracesToBuild(viewerTrace)) {
Job buildJob = fBuildJobMap.remove(trace);
if (buildJob != null) {
buildJob.cancel();
}
}
}
synchronized (fEntryListMap) {
fEntryListMap.remove(viewerTrace);
}
fViewerContext.remove(viewerTrace);
fFiltersMap.remove(viewerTrace);
fMarkerEventSourcesMap.remove(viewerTrace);
if (viewerTrace == 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 builds 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
*/
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 = fTrace;
if (trace == null) {
return regexes;
}
TraceCompassFilter globalFilter = TraceCompassFilter.getFilterForTrace(trace);
if (globalFilter == null) {
return regexes;
}
regexes.putAll(IFilterProperty.DIMMED, globalFilter.getRegexes());
return regexes;
}
/**
* get the time event filter dialog
*
* @return The time event filter dialog. Could be null.
*/
protected TimeEventFilterDialog getTimeEventFilterDialog() {
return fTimeEventFilterDialog;
}
/**
* Action to show a time graph find dialog to search for a
* {@link ITimeGraphEntry}
*
* @author Jean-Christian Kouame
*/
static class ShowFindDialogAction extends Action {
// private static TimeGraphFindDialog fDialog;
// private FindTarget fFindTarget;
/**
* Constructor
*/
public ShowFindDialogAction() {
// TODO: decide if needed
}
@Override
public void run() {
// TODO: decide if needed
}
/**
* Checks if the dialogs shell is the same as the given
* <code>shell</code> and if not clears the stub and closes the dialog.
*
* @param target
* the target that owns the shell to check
*/
public void checkShell(FindTarget target) {
// TODO: decide if needed
}
/**
* Define what to do when a part is activated.
*
* @param part
* The activated workbenchPart
*/
public synchronized void partActivated(IWorkbenchPart part) {
// TODO: decide if needed
}
}
/**
* Cancel and restart the zoom thread.
*/
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);
}
}
/**
* Gets the time event filter action
*
* @return the timeEventFilterAction
*/
public Action getTimeEventFilterAction() {
return fTimeEventFilterAction;
}
/**
* Fill context menu
*
* @param menuManager
* a menuManager to fill
*/
protected void fillTimeGraphEntryContextMenu(@NonNull IMenuManager menuManager) {
// do nothing
}
/*
* 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 fSite.getShell();
}
}
}