/******************************************************************************* * Copyright (c) 2010, 2016 Ericsson and others. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Francois Chouinard - Initial API and implementation, replaced Table by TmfVirtualTable * Patrick Tasse - Factored out from events view, * Filter implementation (inspired by www.eclipse.org/mat) * Ansgar Radermacher - Support navigation to model URIs (Bug 396956) * Bernd Hufmann - Updated call site and model URI implementation * Alexandre Montplaisir - Update to new column API * Matthew Khouzam - Add hide columns *******************************************************************************/ package org.eclipse.tracecompass.tmf.ui.viewers.events; import java.io.File; import java.io.FileNotFoundException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.NotEnabledException; import org.eclipse.core.commands.NotHandledException; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.core.commands.common.NotDefinedException; import org.eclipse.core.expressions.IEvaluationContext; 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.IResource; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EValidator; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ColorRegistry; import org.eclipse.jface.resource.FontRegistry; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.OpenStrategy; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.window.Window; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.TableEditor; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.FocusAdapter; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Text; import org.eclipse.tracecompass.common.core.NonNullUtils; import org.eclipse.tracecompass.internal.tmf.core.filter.TmfCollapseFilter; import org.eclipse.tracecompass.internal.tmf.ui.Activator; import org.eclipse.tracecompass.internal.tmf.ui.Messages; import org.eclipse.tracecompass.internal.tmf.ui.commands.CopyToClipboardOperation; import org.eclipse.tracecompass.internal.tmf.ui.commands.ExportToTextCommandHandler; import org.eclipse.tracecompass.internal.tmf.ui.dialogs.AddBookmarkDialog; import org.eclipse.tracecompass.tmf.core.component.ITmfEventProvider; import org.eclipse.tracecompass.tmf.core.component.TmfComponent; import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect; import org.eclipse.tracecompass.tmf.core.event.aspect.TmfContentFieldAspect; import org.eclipse.tracecompass.tmf.core.event.collapse.ITmfCollapsibleEvent; import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfCallsite; import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfModelLookup; import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfSourceLookup; import org.eclipse.tracecompass.tmf.core.filter.ITmfFilter; import org.eclipse.tracecompass.tmf.core.filter.model.ITmfFilterTreeNode; import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterMatchesNode; import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterNode; import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterObjectNode; import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterRootNode; import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType; import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest; import org.eclipse.tracecompass.tmf.core.resources.ITmfMarker; import org.eclipse.tracecompass.tmf.core.signal.TmfEventFilterAppliedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfEventSearchAppliedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfEventSelectedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceUpdatedSignal; import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; import org.eclipse.tracecompass.tmf.core.trace.ITmfContext; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation; import org.eclipse.tracecompass.tmf.core.util.Pair; import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsCache.CachedEvent; import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsTableHeader.IEventsTableHeaderListener; import org.eclipse.tracecompass.tmf.ui.viewers.events.columns.TmfEventTableColumn; import org.eclipse.tracecompass.tmf.ui.views.colors.ColorSetting; import org.eclipse.tracecompass.tmf.ui.views.colors.ColorSettingsManager; import org.eclipse.tracecompass.tmf.ui.views.colors.IColorSettingsListener; import org.eclipse.tracecompass.tmf.ui.views.filter.FilterManager; import org.eclipse.tracecompass.tmf.ui.widgets.rawviewer.TmfRawEventViewer; import org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.TmfVirtualTable; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.dialogs.ListDialog; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.ide.IGotoMarker; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.themes.ColorUtil; import org.eclipse.ui.themes.IThemeManager; import com.google.common.base.Joiner; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; /** * The generic TMF Events table * * This is a view that will list events that are read from a trace. * * @author Francois Chouinard * @author Patrick Tasse */ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorSettingsListener, ISelectionProvider, IPropertyChangeListener { /** * Empty string array, used by {@link #getItemStrings}. */ protected static final String @NonNull [] EMPTY_STRING_ARRAY = new String[0]; /** * Empty string */ protected static final @NonNull String EMPTY_STRING = ""; //$NON-NLS-1$ private static final boolean IS_LINUX = System.getProperty("os.name").contains("Linux") ? true : false; //$NON-NLS-1$ //$NON-NLS-2$ private static final String FONT_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.font.eventtable"; //$NON-NLS-1$ private static final String HIGHLIGHT_COLOR_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.color.eventtable.highlight"; //$NON-NLS-1$ private static final Image BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath( "icons/elcl16/bookmark_obj.gif"); //$NON-NLS-1$ private static final Image SEARCH_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/search.gif"); //$NON-NLS-1$ private static final Image SEARCH_MATCH_IMAGE = Activator.getDefault().getImageFromPath( "icons/elcl16/search_match.gif"); //$NON-NLS-1$ private static final Image SEARCH_MATCH_BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath( "icons/elcl16/search_match_bookmark.gif"); //$NON-NLS-1$ private static final Image FILTER_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/filter_items.gif"); //$NON-NLS-1$ private static final Image FILTER_ADD_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/filter_add.gif"); //$NON-NLS-1$ private static final Image STOP_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/stop.gif"); //$NON-NLS-1$ private static final String SEARCH_HINT = Messages.TmfEventsTable_SearchHint; private static final int MAX_CACHE_SIZE = 1000; private static final int MARGIN_COLUMN_INDEX = 0; private static final int FILTER_SUMMARY_INDEX = 1; private static final int EVENT_COLUMNS_START_INDEX = MARGIN_COLUMN_INDEX + 1; private final ISchedulingRule fTimeSelectMutexRule = new ISchedulingRule() { @Override public boolean isConflicting(ISchedulingRule rule) { return (rule == this); } @Override public boolean contains(ISchedulingRule rule) { return (rule == this); } }; private Job fTimeSelectJob = null; private final class ColumnListener extends ControlAdapter { /* * Make sure that the margin column is always first and keep the column * order variable up to date. */ @Override public void controlMoved(ControlEvent e) { int[] order = fTable.getColumnOrder(); if (order[0] != MARGIN_COLUMN_INDEX) { for (int i = order.length - 1; i > 0; i--) { if (order[i] == MARGIN_COLUMN_INDEX) { order[i] = order[i - 1]; order[i - 1] = MARGIN_COLUMN_INDEX; } } fTable.setColumnOrder(order); } fColumnOrder = order; fTable.layout(); } @Override public void controlResized(ControlEvent e) { TableColumn column = (TableColumn) e.widget; if (column.getResizable() && !isExpanded(column)) { int i = (int) column.getData(Key.INDEX); fColumnSize[i] = column.getWidth(); column.setData(Key.WIDTH, fColumnSize[i]); } } } private final class TableSelectionListener extends SelectionAdapter { @Override public void widgetSelected(final SelectionEvent e) { if (e.item == null) { return; } updateStatusLine(null); if (fTable.getSelectionIndices().length > 0) { if (e.item.getData(Key.RANK) instanceof Long) { fSelectedRank = (Long) e.item.getData(Key.RANK); fRawViewer.selectAndReveal((Long) e.item.getData(Key.RANK)); } else { fSelectedRank = -1; } if (fTable.getSelectionIndices().length == 1) { fSelectedBeginRank = fSelectedRank; } if (e.item.getData(Key.TIMESTAMP) instanceof ITmfTimestamp) { final ITmfTimestamp ts = NonNullUtils.checkNotNull((ITmfTimestamp) e.item.getData(Key.TIMESTAMP)); if (fTable.getSelectionIndices().length == 1) { fSelectedBeginTimestamp = ts; } ITmfTimestamp selectedBeginTimestamp = fSelectedBeginTimestamp; if (selectedBeginTimestamp != null) { broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, selectedBeginTimestamp, ts)); if (fTable.getSelectionIndices().length == 2) { updateStatusLine(ts.getDelta(selectedBeginTimestamp)); } } } else { if (fTable.getSelectionIndices().length == 1) { fSelectedBeginTimestamp = null; } } } if (e.item.getData() instanceof ITmfEvent) { broadcast(new TmfEventSelectedSignal(TmfEventsTable.this, (ITmfEvent) e.item.getData())); fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, new StructuredSelection(e.item.getData()))); } else { fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, StructuredSelection.EMPTY)); } } } private final class MouseDoubleClickListener extends MouseAdapter { @Override public void mouseDoubleClick(final MouseEvent event) { if (event.button != 1) { return; } // Identify the selected row final Point point = new Point(event.x, event.y); final TableItem item = fTable.getItem(point); if (item != null) { final Rectangle imageBounds = item.getImageBounds(0); imageBounds.width = BOOKMARK_IMAGE.getBounds().width; if (imageBounds.contains(point)) { final Long rank = (Long) item.getData(Key.RANK); if (rank != null) { toggleBookmark(rank); } } } } } private final class RawSelectionListener implements Listener { @Override public void handleEvent(final Event e) { if (fTrace == null) { return; } long rank; if (e.data instanceof Long) { rank = (Long) e.data; } else if (e.data instanceof ITmfLocation) { rank = findRank((ITmfLocation) e.data); } else { return; } int index = (int) rank; if (fTable.getData(Key.FILTER_OBJ) != null) { // +1 for top filter status row index = fCache.getFilteredEventIndex(rank) + 1; } // +1 for header row fTable.setSelection(index + 1); fSelectedRank = rank; fSelectedBeginRank = fSelectedRank; updateStatusLine(null); final TableItem[] selection = fTable.getSelection(); if ((selection != null) && (selection.length > 0)) { TableItem item = fTable.getSelection()[0]; final TmfTimestamp ts = (TmfTimestamp) item.getData(Key.TIMESTAMP); if (ts != null) { broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, ts)); } if (item.getData() instanceof ITmfEvent) { broadcast(new TmfEventSelectedSignal(TmfEventsTable.this, (ITmfEvent) item.getData())); fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, new StructuredSelection(item.getData()))); } else { fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, StructuredSelection.EMPTY)); } } } private long findRank(final ITmfLocation selectedLocation) { final double selectedRatio = fTrace.getLocationRatio(selectedLocation); long low = 0; long high = fTrace.getNbEvents(); long rank = high / 2; double ratio = -1; while (ratio != selectedRatio) { ITmfContext context = fTrace.seekEvent(rank); ratio = fTrace.getLocationRatio(context.getLocation()); context.dispose(); if (ratio < selectedRatio) { low = rank; rank = (rank + high) / 2; } else if (ratio > selectedRatio) { high = rank; rank = (rank + low) / 2; } if ((high - low) < 2) { break; } } return rank; } } private final class SetDataListener implements Listener { @Override public void handleEvent(final Event event) { final TableItem item = (TableItem) event.item; int index = event.index - 1; // -1 for the header row if (event.index == 0) { setHeaderRowItemData(item); return; } if (fTable.getData(Key.FILTER_OBJ) != null) { if ((event.index == 1) || (event.index == (fTable.getItemCount() - 1))) { setFilterStatusRowItemData(item); return; } /* -1 for top filter status row */ index = index - 1; } final CachedEvent cachedEvent = fCache.getEvent(index); if (cachedEvent != null) { setItemData(item, cachedEvent, cachedEvent.rank); return; } // Else, fill the cache asynchronously (and off the UI thread) event.doit = false; } } private static final class PainItemListener implements Listener { @Override public void handleEvent(Event event) { TableItem item = (TableItem) event.item; // we promised to paint the table item's foreground GC gc = event.gc; Image image = item.getImage(event.index); if (image != null) { Rectangle imageBounds = item.getImageBounds(event.index); /* * The image bounds don't match the default image position. */ if (IS_LINUX) { gc.drawImage(image, imageBounds.x + 1, imageBounds.y + 3); } else { gc.drawImage(image, imageBounds.x, imageBounds.y + 1); } } gc.setForeground(item.getForeground(event.index)); gc.setFont(item.getFont(event.index)); String text = item.getText(event.index); Rectangle textBounds = item.getTextBounds(event.index); /* * The text bounds don't match the default text position. */ if (IS_LINUX) { gc.drawText(text, textBounds.x + 1, textBounds.y + 3, true); } else { gc.drawText(text, textBounds.x - 1, textBounds.y + 2, true); } } } private static final class EraseItemListener implements Listener { @Override public void handleEvent(Event event) { TableItem item = (TableItem) event.item; List<?> styleRanges = (List<?>) item.getData(Key.STYLE_RANGES); GC gc = event.gc; Color background = item.getBackground(event.index); /* * Paint the background if it is not the default system color. In * Windows, if you let the widget draw the background, it will not * show the item's background color if the item is selected or hot. * If there are no style ranges and the item background is the * default system color, we do not want to paint it or otherwise we * would override the platform theme (e.g. alternating colors). */ if (styleRanges != null || !background.equals(item.getParent().getBackground())) { // we will paint the table item's background event.detail &= ~SWT.BACKGROUND; // paint the item's default background gc.setBackground(background); gc.fillRectangle(event.x, event.y, event.width, event.height); } /* * We will paint the table item's foreground. In Windows, if you * paint the background but let the widget draw the foreground, it * will override your background, unless the item is selected or * hot. */ event.detail &= ~SWT.FOREGROUND; // paint the highlighted background for all style ranges if (styleRanges != null) { Rectangle textBounds = item.getTextBounds(event.index); String text = item.getText(event.index); for (Object o : styleRanges) { if (o instanceof StyleRange) { StyleRange styleRange = (StyleRange) o; if (styleRange.data.equals(event.index)) { int startIndex = styleRange.start; int endIndex = startIndex + styleRange.length; int startX = gc.textExtent(text.substring(0, startIndex)).x; int endX = gc.textExtent(text.substring(0, endIndex)).x; gc.setBackground(styleRange.background); gc.fillRectangle(textBounds.x + startX, textBounds.y, (endX - startX), textBounds.height); } } } } } } private final class TooltipListener implements Listener { Shell tooltipShell = null; @Override public void handleEvent(final Event event) { switch (event.type) { case SWT.MouseHover: final TableItem item = fTable.getItem(new Point(event.x, event.y)); if (item == null) { return; } String text; if (fTable.indexOf(item) == 0) { if (fHeaderState == HeaderState.SEARCH && item.getBounds(0).contains(event.x, event.y)) { text = Messages.TmfEventsTable_AddAsFilterText; } else { return; } } else { final Long rank = (Long) item.getData(Key.RANK); if (rank == null) { return; } final String tooltipText = (String) item.getData(Key.BOOKMARK); final Rectangle bounds = item.getImageBounds(0); bounds.width = BOOKMARK_IMAGE.getBounds().width; if (!bounds.contains(event.x, event.y)) { return; } text = rank.toString() + (tooltipText != null ? ": " + tooltipText : EMPTY_STRING); //$NON-NLS-1$ } if ((tooltipShell != null) && !tooltipShell.isDisposed()) { tooltipShell.dispose(); } tooltipShell = new Shell(fTable.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); tooltipShell.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); final FillLayout layout = new FillLayout(); layout.marginWidth = 2; tooltipShell.setLayout(layout); final Label label = new Label(tooltipShell, SWT.WRAP); label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); label.setText(text); label.addListener(SWT.MouseExit, this); label.addListener(SWT.MouseDown, this); label.addListener(SWT.MouseWheel, this); final Point size = tooltipShell.computeSize(SWT.DEFAULT, SWT.DEFAULT); /* * Bug in Linux. The coordinates of the event have an origin * that excludes the table header but the method toDisplay() * expects coordinates relative to an origin that includes the * table header. */ int y = event.y; if (IS_LINUX) { y += fTable.getHeaderHeight(); } Point pt = fTable.toDisplay(event.x, y); pt.x += BOOKMARK_IMAGE.getBounds().width; pt.y += item.getBounds().height; tooltipShell.setBounds(pt.x, pt.y, size.x, size.y); tooltipShell.setVisible(true); break; case SWT.Dispose: case SWT.KeyDown: case SWT.MouseMove: case SWT.MouseExit: case SWT.MouseDown: case SWT.MouseWheel: if (tooltipShell != null) { tooltipShell.dispose(); tooltipShell = null; } break; default: break; } } } /** * The events table search/filter/data keys * * @author Patrick Tasse * @noimplement This interface only contains Event Table specific static * definitions. */ public interface Key { /** Search text */ String SEARCH_TXT = "$srch_txt"; //$NON-NLS-1$ /** Search object */ String SEARCH_OBJ = "$srch_obj"; //$NON-NLS-1$ /** Filter text */ String FILTER_TXT = "$fltr_txt"; //$NON-NLS-1$ /** Filter object */ String FILTER_OBJ = "$fltr_obj"; //$NON-NLS-1$ /** Timestamp */ String TIMESTAMP = "$time"; //$NON-NLS-1$ /** Rank */ String RANK = "$rank"; //$NON-NLS-1$ /** Bookmark indicator */ String BOOKMARK = "$bookmark"; //$NON-NLS-1$ /** Event aspect represented by this column */ String ASPECT = "$aspect"; //$NON-NLS-1$ /** * Table item list of style ranges * * @since 1.0 */ String STYLE_RANGES = "$style_ranges"; //$NON-NLS-1$ /** * The width of a table item * * @since 1.1 */ String WIDTH = "$width"; //$NON-NLS-1$ /** * The position of the column * * @since 2.1 */ String INDEX = "$index"; //$NON-NLS-1$ } /** * The events table search/filter state * * @version 1.0 * @author Patrick Tasse */ public static enum HeaderState { /** * No search filter is applied * * @since 2.0 */ NO_SEARCH, /** A search filter is applied */ SEARCH } interface Direction { int FORWARD = +1; int BACKWARD = -1; } // ------------------------------------------------------------------------ // Table data // ------------------------------------------------------------------------ /** The header bar */ private TmfEventsTableHeader fHeaderBar; /** The virtual event table */ protected TmfVirtualTable fTable; private Composite fComposite; private SashForm fSashForm; private Composite fTableComposite; private TmfRawEventViewer fRawViewer; private ITmfTrace fTrace; private volatile boolean fPackDone = false; private volatile boolean fPackMarginDone = false; private HeaderState fHeaderState = HeaderState.NO_SEARCH; private long fSelectedRank = -1; private long fSelectedBeginRank = -1; private ITmfTimestamp fSelectedBeginTimestamp = null; private IStatusLineManager fStatusLineManager = null; // Filter data private long fFilterMatchCount; private long fFilterCheckCount; private FilterThread fFilterThread; private boolean fFilterThreadResume = false; private final Object fFilterSyncObj = new Object(); private SearchThread fSearchThread; private final Object fSearchSyncObj = new Object(); private boolean fCollapseFilterEnabled = false; /** * List of selection change listeners (element type: * <code>ISelectionChangedListener</code>). * * @see #fireSelectionChanged */ private ListenerList selectionChangedListeners = new ListenerList(); // Bookmark map <Rank, MarkerId> private Multimap<Long, Long> fBookmarksMap = HashMultimap.create(); private IFile fBookmarksFile; private long fPendingGotoRank = -1; // SWT resources private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources()); private Color fGrayColor; private Color fGreenColor; private Color fHighlightColor; private Font fFont; private Font fBoldFont; private final List<TmfEventTableColumn> fColumns = new LinkedList<>(); // Event cache private final TmfEventsCache fCache; private boolean fCacheUpdateBusy = false; private boolean fCacheUpdatePending = false; private boolean fCacheUpdateCompleted = false; private final Object fCacheUpdateSyncObj = new Object(); // Keep track of column order, it is needed after table is disposed private int[] fColumnOrder; private boolean fDisposeOnClose; private Menu fHeaderMenu; private Menu fTablePopup; private Menu fRawTablePopup; private Point fLastMenuCursorLocation; private MenuManager fRawViewerPopupMenuManager; private MenuManager fTablePopupMenuManager; private MenuManager fHeaderPopupMenuManager; private boolean[] fColumnResizable; private int[] fColumnSize; // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Basic constructor, using the default set of columns * * @param parent * The parent composite UI object * @param cacheSize * The size of the event table cache */ public TmfEventsTable(final Composite parent, final int cacheSize) { this(parent, cacheSize, TmfTrace.BASE_ASPECTS); } /** * Legacy constructor, using ColumnData to define columns * * @param parent * The parent composite UI object * @param cacheSize * The size of the event table cache * @param columnData * The column data array * @deprecated Deprecated constructor, use * {@link #TmfEventsTable(Composite, int, Collection)} */ @Deprecated public TmfEventsTable(final Composite parent, int cacheSize, final org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) { /* * We'll do a "best-effort" to keep trace types still using this API to * keep working, by defining a TmfEventTableColumn for each ColumnData * they passed. */ this(parent, cacheSize, convertFromColumnData(columnData)); } @Deprecated private static @NonNull Iterable<ITmfEventAspect<?>> convertFromColumnData( org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) { ImmutableList.Builder<ITmfEventAspect<?>> builder = new ImmutableList.Builder<>(); for (org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData col : columnData) { String fieldName = col.header; if (fieldName != null) { builder.add(new TmfContentFieldAspect(fieldName, fieldName)); } } return builder.build(); } /** * Standard constructor, where we define which columns to use. * * @param parent * The parent composite UI object * @param cacheSize * The size of the event table cache * @param aspects * The event aspects to display in this table. One column per * aspect will be created. * <p> * The iteration order of this collection will correspond to the * initial ordering of the columns in the table. * </p> */ public TmfEventsTable(final Composite parent, int cacheSize, @NonNull Iterable<ITmfEventAspect<?>> aspects) { super("TmfEventsTable"); //$NON-NLS-1$ fComposite = new Composite(parent, SWT.NONE); GridLayout gl = new GridLayout(1, false); gl.marginHeight = 0; gl.marginWidth = 0; gl.verticalSpacing = 0; fComposite.setLayout(gl); fSashForm = new SashForm(fComposite, SWT.HORIZONTAL); fSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); // Create a composite for the table and its header bar fTableComposite = new Composite(fSashForm, SWT.NONE); fTableComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); gl = new GridLayout(1, false); gl.marginHeight = 0; gl.marginWidth = 0; gl.verticalSpacing = 0; fTableComposite.setLayout(gl); // Create an events table header bar fHeaderBar = new TmfEventsTableHeader(fTableComposite, SWT.NONE, new IEventsTableHeaderListener() { @Override public void filterSelected(ITmfFilter filter) { if (filter instanceof TmfFilterMatchesNode) { TmfFilterMatchesNode matchFilter = (TmfFilterMatchesNode) filter; for (TableColumn col : fTable.getColumns()) { if (col.getData(Key.ASPECT) == matchFilter.getEventAspect()) { col.setData(Key.FILTER_TXT, matchFilter.getRegex()); } else { col.setData(Key.FILTER_TXT, null); } } fTable.refresh(); fTable.redraw(); } } @Override public void filterRemoved(ITmfFilter filter) { for (TableColumn col : fTable.getColumns()) { col.setData(Key.FILTER_TXT, null); } removeFilter(filter); } }); // Create a virtual table final int style = SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION; fTable = new TmfVirtualTable(fTableComposite, style); // Set the table layout final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true); fTable.setLayoutData(layoutData); // Some cosmetic enhancements fTable.setHeaderVisible(true); fTable.setLinesVisible(true); // Setup the columns for (ITmfEventAspect<?> aspect : aspects) { if (aspect != null) { fColumns.add(new TmfEventTableColumn(aspect)); } } TmfMarginColumn collapseCol = new TmfMarginColumn(); fColumns.add(MARGIN_COLUMN_INDEX, collapseCol); fHeaderMenu = new Menu(fTable); fColumnSize = new int[fColumns.size()]; fColumnResizable = new boolean[fColumns.size()]; int i = 0; // Create the UI columns in the table for (TmfEventTableColumn col : fColumns) { TableColumn column = fTable.newTableColumn(SWT.LEFT); column.setText(col.getHeaderName()); column.setToolTipText(col.getHeaderTooltip()); column.setData(Key.ASPECT, col.getEventAspect()); column.setData(Key.INDEX, i); if (col instanceof TmfMarginColumn) { column.setResizable(false); } else { column.pack(); column.setMoveable(true); column.setData(Key.WIDTH, column.getWidth()); fColumnSize[i] = column.getWidth(); } column.addControlListener(new ColumnListener()); fColumnResizable[i] = column.getResizable(); i++; } fColumnOrder = fTable.getColumnOrder(); // Set the frozen row for header row fTable.setFrozenRowCount(1); // Create the header row cell editor createHeaderEditor(); // Handle the table item selection fTable.addSelectionListener(new TableSelectionListener()); int realCacheSize = Math.max(cacheSize, Display.getDefault().getBounds().height / fTable.getItemHeight()); realCacheSize = Math.min(realCacheSize, MAX_CACHE_SIZE); fCache = new TmfEventsCache(realCacheSize, this); // Handle the table item requests fTable.addListener(SWT.SetData, new SetDataListener()); fTable.addMenuDetectListener(event -> { fLastMenuCursorLocation = new Point(event.x, event.y); Point pt = fTable.getDisplay().map(null, fTable, fLastMenuCursorLocation); Rectangle clientArea = fTable.getClientArea(); boolean header = clientArea.y <= pt.y && pt.y < (clientArea.y + fTable.getHeaderHeight()); fTable.setMenu(header ? fHeaderMenu : fTablePopup); }); fTable.addMouseListener(new MouseDoubleClickListener()); final Listener tooltipListener = new TooltipListener(); fTable.addListener(SWT.MouseHover, tooltipListener); fTable.addListener(SWT.Dispose, tooltipListener); fTable.addListener(SWT.KeyDown, tooltipListener); fTable.addListener(SWT.MouseMove, tooltipListener); fTable.addListener(SWT.MouseExit, tooltipListener); fTable.addListener(SWT.MouseDown, tooltipListener); fTable.addListener(SWT.MouseWheel, tooltipListener); fTable.addListener(SWT.EraseItem, new EraseItemListener()); fTable.addListener(SWT.PaintItem, new PainItemListener()); // Create resources createResources(); initializeFonts(); initializeColors(); PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(this); ColorSettingsManager.addColorSettingsListener(this); fTable.setItemCount(1); // +1 for header row fRawViewer = new TmfRawEventViewer(fSashForm, SWT.H_SCROLL | SWT.V_SCROLL); fRawViewer.addSelectionListener(new RawSelectionListener()); fSashForm.setWeights(new int[] { 1, 1 }); fRawViewer.setVisible(false); createPopupMenu(); fComposite.addDisposeListener((e) -> { internalDispose(); }); } /** * Checked menu creator to make columns visible or not. * * @param parent * the parent menu * @param column * the column */ private IAction createHeaderAction(final TableColumn column) { final IAction columnMenuAction = new Action(column.getText(), IAction.AS_CHECK_BOX) { @Override public void run() { boolean isChecked = isChecked(); if (isChecked) { int width = (int) column.getData(Key.WIDTH); column.setResizable(true); if (width == 0) { column.pack(); } else { column.setWidth(width); } } else { column.setResizable(false); column.setWidth(0); } int pos = (int) column.getData(Key.INDEX); fColumnResizable[pos] = isChecked; } }; columnMenuAction.setChecked(column.getResizable()); return columnMenuAction; } private IAction createResetHeaderAction() { return new Action(Messages.TmfEventsTable_ShowAll) { @Override public void run() { for (TableColumn column : fTable.getColumns()) { int index = (int) column.getData(Key.INDEX); if (index != MARGIN_COLUMN_INDEX) { final int width = (int) column.getData(Key.WIDTH); column.setResizable(true); if (width == 0) { column.pack(); } else { column.setWidth(width); } fColumnResizable[index] = true; } } } }; } // ------------------------------------------------------------------------ // Operations // ------------------------------------------------------------------------ /** * Create a pop-up menu. */ private void createPopupMenu() { final IAction copyAction = new Action(Messages.TmfEventsTable_CopyToClipboardActionText) { @Override public void run() { ITmfTrace trace = fTrace; if (trace == null || (fSelectedRank == -1 && fSelectedBeginRank == -1)) { return; } List<TmfEventTableColumn> columns = new ArrayList<>(); for (int i : fTable.getColumnOrder()) { TableColumn column = fTable.getColumns()[i]; // Omit the margin column and hidden columns if (isVisibleEventColumn(column)) { columns.add(fColumns.get(i)); } } long start = Math.min(fSelectedBeginRank, fSelectedRank); long end = Math.max(fSelectedBeginRank, fSelectedRank); final ITmfFilter filter = (ITmfFilter) fTable.getData(Key.FILTER_OBJ); IRunnableWithProgress operation = new CopyToClipboardOperation(trace, filter, columns, start, end); try { PlatformUI.getWorkbench().getProgressService().busyCursorWhile(operation); } catch (InvocationTargetException e) { Activator.getDefault().logError("Invocation target exception copying to clipboard ", e); //$NON-NLS-1$ } catch (InterruptedException e) { /* ignored */ } } }; final IAction showTableAction = new Action(Messages.TmfEventsTable_ShowTableActionText) { @Override public void run() { fTableComposite.setVisible(true); fSashForm.layout(); } }; final IAction hideTableAction = new Action(Messages.TmfEventsTable_HideTableActionText) { @Override public void run() { fTableComposite.setVisible(false); fSashForm.layout(); } }; final IAction showRawAction = new Action(Messages.TmfEventsTable_ShowRawActionText) { @Override public void run() { fRawViewer.setVisible(true); fSashForm.layout(); final int index = fTable.getSelectionIndex(); if (index >= 1) { fRawViewer.selectAndReveal(index - 1); } } }; final IAction hideRawAction = new Action(Messages.TmfEventsTable_HideRawActionText) { @Override public void run() { fRawViewer.setVisible(false); fSashForm.layout(); } }; final IAction openCallsiteAction = new Action(Messages.TmfEventsTable_OpenSourceCodeActionText) { @Override public void run() { final TableItem items[] = fTable.getSelection(); if (items.length != 1) { return; } final TableItem item = items[0]; final Object data = item.getData(); if (!(data instanceof ITmfSourceLookup)) { return; } ITmfSourceLookup event = (ITmfSourceLookup) data; ITmfCallsite cs = event.getCallsite(); if (cs == null) { return; } Long lineNo = cs.getLineNo(); if (lineNo == null) { /* Not enough information to provide a full callsite */ return; } String fileName = cs.getFileName(); final String trimmedPath = fileName.replaceAll("\\.\\./", EMPTY_STRING); //$NON-NLS-1$ File fileToOpen = new File(trimmedPath); try { if (fileToOpen.exists() && fileToOpen.isFile()) { /* * The path points to a "real" file, attempt to open * that */ IFileStore fileStore = EFS.getLocalFileSystem().getStore(fileToOpen.toURI()); IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); IEditorPart editor = IDE.openEditorOnFileStore(page, fileStore); if (editor instanceof ITextEditor) { /* * Calculate the "document offset" corresponding to * the line number, then seek there. */ ITextEditor textEditor = (ITextEditor) editor; int lineNumber = lineNo.intValue(); IDocument document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()); IRegion region = document.getLineInformation(lineNumber - 1); if (region != null) { textEditor.selectAndReveal(region.getOffset(), region.getLength()); } } } else { /* * The file was not found on disk, attempt to find it in * the workspace instead. */ IMarker marker = null; final ArrayList<IFile> files = new ArrayList<>(); IPath p = new Path(trimmedPath); ResourcesPlugin.getWorkspace().getRoot().accept(new IResourceVisitor() { @Override public boolean visit(IResource resource) throws CoreException { if (resource instanceof IFile && resource.getFullPath().toString().endsWith(p.lastSegment())) { files.add((IFile) resource); } return true; } }); IFile file = null; if (files.size() > 1) { ListDialog dialog = new ListDialog(getTable().getShell()); dialog.setContentProvider(ArrayContentProvider.getInstance()); dialog.setLabelProvider(new LabelProvider() { @Override public String getText(Object element) { return ((IFile) element).getFullPath().toString(); } }); dialog.setInput(files); dialog.setTitle(Messages.TmfEventsTable_OpenSourceCodeSelectFileDialogTitle); dialog.setMessage(Messages.TmfEventsTable_OpenSourceCodeSelectFileDialogTitle + '\n' + cs.toString()); dialog.open(); Object[] result = dialog.getResult(); if (result != null && result.length > 0) { file = (IFile) result[0]; } } else if (files.size() == 1) { file = files.get(0); } if (file != null) { marker = file.createMarker(IMarker.MARKER); marker.setAttribute(IMarker.LINE_NUMBER, lineNo.intValue()); IDE.openEditor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), marker); marker.delete(); } else if (files.isEmpty()) { displayException(new FileNotFoundException('\'' + cs.toString() + '\'' + '\n' + Messages.TmfEventsTable_OpenSourceCodeNotFound)); } } } catch (BadLocationException | CoreException e) { displayException(e); } } }; final IAction openModelAction = new Action(Messages.TmfEventsTable_OpenModelActionText) { @Override public void run() { final TableItem items[] = fTable.getSelection(); if (items.length != 1) { return; } final TableItem item = items[0]; final Object eventData = item.getData(); if (eventData instanceof ITmfModelLookup) { String modelURI = ((ITmfModelLookup) eventData).getModelUri(); if (modelURI != null) { IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); IFile file = null; final URI uri = URI.createURI(modelURI); if (uri.isPlatformResource()) { IPath path = new Path(uri.toPlatformString(true)); file = ResourcesPlugin.getWorkspace().getRoot().getFile(path); } else if (uri.isFile() && !uri.isRelative()) { file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation( new Path(uri.toFileString())); } if (file != null) { try { /* * create a temporary validation marker on the * model file, remove it afterwards thus, * navigation works with all model editors * supporting the navigation to a marker */ IMarker marker = file.createMarker(EValidator.MARKER); marker.setAttribute(EValidator.URI_ATTRIBUTE, modelURI); marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO); IDE.openEditor(activePage, marker, OpenStrategy.activateOnOpen()); marker.delete(); } catch (CoreException e) { displayException(e); } } else { displayException(new FileNotFoundException('\'' + modelURI + '\'' + '\n' + Messages.TmfEventsTable_OpenModelUnsupportedURI)); } } } } }; final IAction exportToTextAction = new Action(Messages.TmfEventsTable_Export_to_text) { @Override public void run() { IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); Object handlerServiceObject = activePage.getActiveEditor().getSite().getService(IHandlerService.class); IHandlerService handlerService = (IHandlerService) handlerServiceObject; Object cmdServiceObject = activePage.getActiveEditor().getSite().getService(ICommandService.class); ICommandService cmdService = (ICommandService) cmdServiceObject; try { HashMap<String, Object> parameters = new HashMap<>(); Command command = cmdService.getCommand(ExportToTextCommandHandler.COMMAND_ID); ParameterizedCommand cmd = ParameterizedCommand.generateCommand(command, parameters); IEvaluationContext context = handlerService.getCurrentState(); List<TmfEventTableColumn> exportColumns = new ArrayList<>(); for (int i : fTable.getColumnOrder()) { TableColumn column = fTable.getColumns()[i]; // Omit the margin column and hidden columns if (isVisibleEventColumn(column)) { exportColumns.add(fColumns.get(i)); } } context.addVariable(ExportToTextCommandHandler.TMF_EVENT_TABLE_COLUMNS_ID, exportColumns); handlerService.executeCommandInContext(cmd, null, context); } catch (ExecutionException | NotDefinedException | NotEnabledException | NotHandledException e) { displayException(e); } } }; final IAction addAsFilterAction = new Action(Messages.TmfEventsTable_AddAsFilterText) { @Override public void run() { applySearchAsFilter(); } }; final IAction clearFiltersAction = new Action(Messages.TmfEventsTable_ClearFiltersActionText) { @Override public void run() { clearFilters(); } }; final IAction collapseAction = new Action(Messages.TmfEventsTable_CollapseFilterMenuName) { @Override public void run() { applyFilter(new TmfCollapseFilter()); } }; class ToggleBookmarkAction extends Action { Long fRank; public ToggleBookmarkAction(final String text, final Long rank) { super(text); fRank = rank; } @Override public void run() { toggleBookmark(fRank); } } fHeaderPopupMenuManager = new MenuManager(); fHeaderPopupMenuManager.setRemoveAllWhenShown(true); fHeaderPopupMenuManager.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(IMenuManager manager) { for (int index : fTable.getColumnOrder()) { if (fTable.getColumns()[index].getData(Key.WIDTH) != null) { fHeaderPopupMenuManager.add(createHeaderAction(fTable.getColumns()[index])); } } fHeaderPopupMenuManager.add(new Separator()); fHeaderPopupMenuManager.add(createResetHeaderAction()); } }); fTablePopupMenuManager = new MenuManager(); fTablePopupMenuManager.setRemoveAllWhenShown(true); fTablePopupMenuManager.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(final IMenuManager manager) { if (fTable.getSelectionIndices().length == 1 && fTable.getSelectionIndices()[0] == 0) { // Right-click on header row if (fHeaderState == HeaderState.SEARCH) { fTablePopupMenuManager.add(addAsFilterAction); } return; } final Point point = fTable.toControl(fLastMenuCursorLocation); final TableItem item = fTable.getSelection().length > 0 ? fTable.getSelection()[0] : null; if (item != null) { final Rectangle imageBounds = item.getImageBounds(0); imageBounds.width = BOOKMARK_IMAGE.getBounds().width; if (point.x <= (imageBounds.x + imageBounds.width)) { // Right-click on left margin final Long rank = (Long) item.getData(Key.RANK); if ((rank != null) && (fBookmarksFile != null)) { if (fBookmarksMap.containsKey(rank)) { fTablePopupMenuManager.add(new ToggleBookmarkAction( Messages.TmfEventsTable_RemoveBookmarkActionText, rank)); } else { fTablePopupMenuManager.add(new ToggleBookmarkAction( Messages.TmfEventsTable_AddBookmarkActionText, rank)); } } return; } } // Right-click on table if (fSelectedRank != -1 && fSelectedBeginRank != -1) { fTablePopupMenuManager.add(copyAction); fTablePopupMenuManager.add(new Separator()); } if (fTable.isVisible() && fRawViewer.isVisible()) { fTablePopupMenuManager.add(hideTableAction); fTablePopupMenuManager.add(hideRawAction); } else if (!fTable.isVisible()) { fTablePopupMenuManager.add(showTableAction); } else if (!fRawViewer.isVisible()) { fTablePopupMenuManager.add(showRawAction); } fTablePopupMenuManager.add(exportToTextAction); fTablePopupMenuManager.add(new Separator()); if (item != null) { final Object data = item.getData(); Separator separator = null; if (data instanceof ITmfSourceLookup) { ITmfSourceLookup event = (ITmfSourceLookup) data; if (event.getCallsite() != null) { fTablePopupMenuManager.add(openCallsiteAction); separator = new Separator(); } } if (data instanceof ITmfModelLookup) { ITmfModelLookup event = (ITmfModelLookup) data; if (event.getModelUri() != null) { fTablePopupMenuManager.add(openModelAction); separator = new Separator(); } if (separator != null) { fTablePopupMenuManager.add(separator); } } } /* * Only show collapse filter if at least one trace can be * collapsed. */ boolean isCollapsible = false; if (fTrace != null) { for (ITmfTrace trace : TmfTraceManager.getTraceSet(fTrace)) { Class<? extends ITmfEvent> eventClass = trace.getEventType(); isCollapsible = ITmfCollapsibleEvent.class.isAssignableFrom(eventClass); if (isCollapsible) { break; } } } if (isCollapsible && !fCollapseFilterEnabled) { fTablePopupMenuManager.add(collapseAction); fTablePopupMenuManager.add(new Separator()); } fTablePopupMenuManager.add(clearFiltersAction); final ITmfFilterTreeNode[] savedFilters = FilterManager.getSavedFilters(); if (savedFilters.length > 0) { final MenuManager subMenu = new MenuManager(Messages.TmfEventsTable_ApplyPresetFilterMenuName); for (final ITmfFilterTreeNode node : savedFilters) { if (node instanceof TmfFilterNode) { final TmfFilterNode filter = (TmfFilterNode) node; subMenu.add(new Action(filter.getFilterName()) { @Override public void run() { applyFilter(filter); } }); } } fTablePopupMenuManager.add(subMenu); } appendToTablePopupMenu(fTablePopupMenuManager, item); } }); fRawViewerPopupMenuManager = new MenuManager(); fRawViewerPopupMenuManager.setRemoveAllWhenShown(true); fRawViewerPopupMenuManager.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(final IMenuManager manager) { if (fTable.isVisible() && fRawViewer.isVisible()) { fRawViewerPopupMenuManager.add(hideTableAction); fRawViewerPopupMenuManager.add(hideRawAction); } else if (!fTable.isVisible()) { fRawViewerPopupMenuManager.add(showTableAction); } else if (!fRawViewer.isVisible()) { fRawViewerPopupMenuManager.add(showRawAction); } appendToRawPopupMenu(fRawViewerPopupMenuManager); } }); fHeaderMenu = fHeaderPopupMenuManager.createContextMenu(fTable); fTablePopup = fTablePopupMenuManager.createContextMenu(fTable); fTable.setMenu(fTablePopup); fRawTablePopup = fRawViewerPopupMenuManager.createContextMenu(fRawViewer); fRawViewer.setMenu(fRawTablePopup); } /** * Append an item to the event table's pop-up menu. * * @param tablePopupMenu * The menu manager * @param selectedItem * The item to append */ protected void appendToTablePopupMenu(final MenuManager tablePopupMenu, final TableItem selectedItem) { // override to append more actions } /** * Append an item to the raw viewer's pop-up menu. * * @param rawViewerPopupMenu * The menu manager */ protected void appendToRawPopupMenu(final MenuManager rawViewerPopupMenu) { // override to append more actions } @Override public void dispose() { fComposite.dispose(); } private void internalDispose() { stopSearchThread(); stopFilterThread(); PlatformUI.getWorkbench().getThemeManager().removePropertyChangeListener(this); ColorSettingsManager.removeColorSettingsListener(this); fCache.clear(); if ((fTrace != null) && fDisposeOnClose) { fTrace.dispose(); } fResourceManager.dispose(); if (fRawViewerPopupMenuManager != null) { fRawViewerPopupMenuManager.dispose(); } if (fHeaderPopupMenuManager != null) { fHeaderPopupMenuManager.dispose(); } if (fTablePopupMenuManager != null) { fTablePopupMenuManager.dispose(); } super.dispose(); } /** * Assign a layout data object to this view. * * @param layoutData * The layout data to assign */ public void setLayoutData(final Object layoutData) { fComposite.setLayoutData(layoutData); } /** * Get the virtual table contained in this event table. * * @return The TMF virtual table */ public TmfVirtualTable getTable() { return fTable; } /** * @param columnData * columnData * @deprecated The column headers are now set at the constructor, this * shouldn't be called anymore. */ @Deprecated protected void setColumnHeaders(final org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) { /* No-op */ } /** * Set a table item's data. * * @param item * The item to set * @param event * Which trace event to link with this entry * @param rank * Which rank this event has in the trace/experiment */ protected void setItemData(final TableItem item, final ITmfEvent event, final long rank) { String[] itemStrings = getItemStrings(fColumns, event); // Get the actual ITmfEvent from the CachedEvent ITmfEvent tmfEvent = event; if (event instanceof CachedEvent) { tmfEvent = ((CachedEvent) event).event; } item.setText(itemStrings); item.setData(tmfEvent); item.setData(Key.TIMESTAMP, tmfEvent.getTimestamp()); item.setData(Key.RANK, rank); final Collection<Long> markerIds = fBookmarksMap.get(rank); if (!markerIds.isEmpty()) { Joiner joiner = Joiner.on("\n -").skipNulls(); //$NON-NLS-1$ List<Object> parts = new ArrayList<>(); if (markerIds.size() > 1) { parts.add(Messages.TmfEventsTable_MultipleBookmarksToolTip); } try { for (long markerId : markerIds) { final IMarker marker = fBookmarksFile.findMarker(markerId); if (marker != null) { parts.add(marker.getAttribute(IMarker.MESSAGE)); } } } catch (CoreException e) { displayException(e); } item.setData(Key.BOOKMARK, joiner.join(parts)); } else { item.setData(Key.BOOKMARK, null); } boolean searchMatch = false; boolean searchNoMatch = false; final ITmfFilter searchFilter = (ITmfFilter) fTable.getData(Key.SEARCH_OBJ); if (searchFilter != null) { if (searchFilter.matches(tmfEvent)) { searchMatch = true; } else { searchNoMatch = true; } } final ColorSetting colorSetting = ColorSettingsManager.getColorSetting(tmfEvent); if (searchNoMatch) { item.setForeground(colorSetting.getDimmedForegroundColor()); item.setBackground(colorSetting.getDimmedBackgroundColor()); } else { item.setForeground(colorSetting.getForegroundColor()); item.setBackground(colorSetting.getBackgroundColor()); } item.setFont(fFont); if (searchMatch) { if (!markerIds.isEmpty()) { item.setImage(SEARCH_MATCH_BOOKMARK_IMAGE); } else { item.setImage(SEARCH_MATCH_IMAGE); } } else if (!markerIds.isEmpty()) { item.setImage(BOOKMARK_IMAGE); } else { item.setImage((Image) null); } List<StyleRange> styleRanges = new ArrayList<>(); for (int index = 0; index < fTable.getColumns().length; index++) { TableColumn column = fTable.getColumns()[index]; String regex = null; if (fHeaderState == HeaderState.SEARCH) { if (searchMatch) { regex = (String) column.getData(Key.SEARCH_TXT); } } else { regex = (String) column.getData(Key.FILTER_TXT); } if (regex != null) { String text = item.getText(index); try { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(text); while (matcher.find()) { int start = matcher.start(); int length = matcher.end() - start; Color foreground = colorSetting.getForegroundColor(); Color background = fHighlightColor; StyleRange styleRange = new StyleRange(start, length, foreground, background); styleRange.data = index; styleRanges.add(styleRange); } } catch (PatternSyntaxException e) { /* ignored */ } } } if (styleRanges.isEmpty()) { item.setData(Key.STYLE_RANGES, null); } else { item.setData(Key.STYLE_RANGES, styleRanges); } item.getParent().redraw(); if ((itemStrings[MARGIN_COLUMN_INDEX] != null) && !itemStrings[MARGIN_COLUMN_INDEX].isEmpty()) { packMarginColumn(); } } /** * Set the item data of the header row. * * @param item * The item to use as table header */ protected void setHeaderRowItemData(final TableItem item) { if (fHeaderState == HeaderState.NO_SEARCH) { item.setImage(SEARCH_IMAGE); } else if (fHeaderState == HeaderState.SEARCH) { item.setImage(FILTER_ADD_IMAGE); } item.setForeground(fGrayColor); // Ignore collapse and image column for (int i = EVENT_COLUMNS_START_INDEX; i < fTable.getColumns().length; i++) { final TableColumn column = fTable.getColumns()[i]; final String filter = (String) column.getData(Key.SEARCH_TXT); if (filter == null) { item.setText(i, SEARCH_HINT); item.setForeground(i, fGrayColor); item.setFont(i, fFont); } else { item.setText(i, filter); item.setForeground(i, fGreenColor); item.setFont(i, fBoldFont); } } if (!fPackMarginDone) { packMarginColumn(); fPackMarginDone = true; } } /** * Set the item data of the "filter status" row. * * @param item * The item to use as filter status row */ protected void setFilterStatusRowItemData(final TableItem item) { for (int i = 0; i < fTable.getColumns().length; i++) { if (i == MARGIN_COLUMN_INDEX) { if ((fTrace == null) || (fFilterCheckCount == fTrace.getNbEvents())) { item.setImage(FILTER_IMAGE); } else { item.setImage(STOP_IMAGE); } } if (i == FILTER_SUMMARY_INDEX) { item.setText(FILTER_SUMMARY_INDEX, fFilterMatchCount + "/" + fFilterCheckCount); //$NON-NLS-1$ } else { item.setText(i, EMPTY_STRING); } } item.setData(null); item.setData(Key.TIMESTAMP, null); item.setData(Key.RANK, null); item.setData(Key.STYLE_RANGES, null); item.setForeground(null); item.setBackground(null); item.setFont(fFont); } /** * Create an editor for the header. */ private void createHeaderEditor() { final TableEditor tableEditor = fTable.createTableEditor(); tableEditor.horizontalAlignment = SWT.LEFT; tableEditor.verticalAlignment = SWT.CENTER; tableEditor.grabHorizontal = true; tableEditor.minimumWidth = 50; // Handle the header row selection fTable.addMouseListener(new MouseAdapter() { int columnIndex; TableColumn column; TableItem item; @Override public void mouseDown(final MouseEvent event) { if (event.button != 1) { return; } // Identify the selected row final Point point = new Point(event.x, event.y); item = fTable.getItem(point); // Header row selected if ((item != null) && (fTable.indexOf(item) == 0)) { // Margin column selected if (item.getBounds(0).contains(point)) { if (fHeaderState == HeaderState.SEARCH) { applySearchAsFilter(); } return; } // Identify the selected column columnIndex = -1; for (int i = 0; i < fTable.getColumns().length; i++) { final Rectangle rect = item.getBounds(i); if (rect.contains(point)) { columnIndex = i; break; } } if (columnIndex == -1) { return; } column = fTable.getColumns()[columnIndex]; /* * The control that will be the editor must be a child of * the Table */ final Text newEditor = (Text) fTable.createTableEditorControl(Text.class); final String headerString = (String) column.getData(Key.SEARCH_TXT); if (headerString != null) { newEditor.setText(headerString); } newEditor.addFocusListener(new FocusAdapter() { @Override public void focusLost(final FocusEvent e) { final boolean changed = updateHeader(newEditor.getText()); if (changed) { applyHeader(); } } }); newEditor.addKeyListener(new KeyAdapter() { @Override public void keyPressed(final KeyEvent e) { if (e.character == SWT.CR) { updateHeader(newEditor.getText()); applyHeader(); if ((e.stateMask & SWT.CTRL) != 0) { applySearchAsFilter(); } /* * Set focus on the table so that the next * carriage return goes to the next result */ TmfEventsTable.this.getTable().setFocus(); } else if (e.character == SWT.ESC) { tableEditor.getEditor().dispose(); TmfEventsTable.this.getTable().setFocus(); } } }); newEditor.selectAll(); newEditor.setFocus(); tableEditor.setEditor(newEditor, item, columnIndex); } } /* * returns true is value was changed */ private boolean updateHeader(final String regex) { if (regex.length() > 0) { try { Pattern.compile(regex); if (regex.equals(column.getData(Key.SEARCH_TXT))) { tableEditor.getEditor().dispose(); return false; } final TmfFilterMatchesNode filter = new TmfFilterMatchesNode(null); ITmfEventAspect<?> aspect = (ITmfEventAspect<?>) column.getData(Key.ASPECT); filter.setEventAspect(aspect); filter.setRegex(regex); column.setData(Key.SEARCH_OBJ, filter); column.setData(Key.SEARCH_TXT, regex); } catch (final PatternSyntaxException ex) { tableEditor.getEditor().dispose(); MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), ex.getDescription(), ex.getMessage()); return false; } } else { if (column.getData(Key.SEARCH_TXT) == null) { tableEditor.getEditor().dispose(); return false; } column.setData(Key.SEARCH_OBJ, null); column.setData(Key.SEARCH_TXT, null); } return true; } private void applyHeader() { stopSearchThread(); final TmfFilterRootNode filter = new TmfFilterRootNode(); for (final TableColumn col : fTable.getColumns()) { final Object filterObj = col.getData(Key.SEARCH_OBJ); if (filterObj instanceof ITmfFilterTreeNode) { filter.addChild((ITmfFilterTreeNode) filterObj); } } if (filter.getChildrenCount() > 0) { fHeaderState = HeaderState.SEARCH; fTable.setData(Key.SEARCH_OBJ, filter); fTable.refresh(); searchNext(); fireSearchApplied(filter); } else { fHeaderState = HeaderState.NO_SEARCH; fTable.setData(Key.SEARCH_OBJ, null); fTable.refresh(); fireSearchApplied(null); } tableEditor.getEditor().dispose(); } }); fTable.addKeyListener(new KeyAdapter() { @Override public void keyPressed(final KeyEvent e) { e.doit = false; if (e.character == SWT.ESC) { stopFilterThread(); stopSearchThread(); fTable.refresh(); } else if (e.character == SWT.DEL) { if (fHeaderState == HeaderState.SEARCH) { fHeaderState = HeaderState.NO_SEARCH; stopSearchThread(); for (final TableColumn column : fTable.getColumns()) { column.setData(Key.SEARCH_OBJ, null); column.setData(Key.SEARCH_TXT, null); column.setData(Key.FILTER_TXT, null); } fTable.setData(Key.SEARCH_OBJ, null); fTable.refresh(); fireSearchApplied(null); } else { for (final TableColumn column : fTable.getColumns()) { column.setData(Key.FILTER_TXT, null); } fTable.refresh(); } } else if (e.character == SWT.CR) { if ((e.stateMask & SWT.CTRL) != 0) { if (fHeaderState == HeaderState.SEARCH) { applySearchAsFilter(); } } else if ((e.stateMask & SWT.SHIFT) == 0) { searchNext(); } else { searchPrevious(); } } } }); } /** * Apply the current search condition as a new filter. * * @since 2.0 */ protected void applySearchAsFilter() { Object searchObj = fTable.getData(Key.SEARCH_OBJ); if (searchObj instanceof ITmfFilter) { ITmfFilter filter = (ITmfFilter) searchObj; fTable.setData(Key.SEARCH_OBJ, null); fireSearchApplied(null); fHeaderState = HeaderState.NO_SEARCH; for (final TableColumn col : fTable.getColumns()) { col.setData(Key.FILTER_TXT, col.getData(Key.SEARCH_TXT)); col.setData(Key.SEARCH_TXT, null); col.setData(Key.SEARCH_OBJ, null); } applyFilter(filter); } } /** * Send an event indicating a filter has been applied. * * @param filter * The filter that was just applied */ protected void fireFilterApplied(final ITmfFilter filter) { broadcast(new TmfEventFilterAppliedSignal(this, fTrace, filter)); } /** * Send an event indicating that a search has been applied. * * @param filter * The search filter that was just applied */ protected void fireSearchApplied(final ITmfFilter filter) { broadcast(new TmfEventSearchAppliedSignal(this, fTrace, filter)); } /** * Start the filtering thread. */ protected void startFilterThread() { synchronized (fFilterSyncObj) { final ITmfFilterTreeNode filter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ); if (fFilterThread == null || fFilterThread.filter != filter) { if (fFilterThread != null) { fFilterThread.cancel(); fFilterThreadResume = false; } fFilterThread = new FilterThread(filter); fFilterThread.start(); } else { fFilterThreadResume = true; } } } /** * Stop the filtering thread. */ protected void stopFilterThread() { synchronized (fFilterSyncObj) { if (fFilterThread != null) { fFilterThread.cancel(); fFilterThread = null; fFilterThreadResume = false; } } } /** * Apply a filter. It is added to the existing filters. * * @param filter * The filter to apply */ protected void applyFilter(ITmfFilter filter) { stopFilterThread(); stopSearchThread(); fFilterMatchCount = 0; fFilterCheckCount = 0; ITmfFilterTreeNode rootFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ); if (rootFilter == null) { rootFilter = new TmfFilterRootNode(); } if (filter instanceof TmfFilterRootNode) { TmfFilterRootNode parentFilter = (TmfFilterRootNode) filter; for (ITmfFilterTreeNode child : parentFilter.getChildren()) { rootFilter.addChild(child); } } else if (filter instanceof TmfCollapseFilter) { fCollapseFilterEnabled = true; } else if (filter instanceof ITmfFilterTreeNode) { rootFilter.addChild((ITmfFilterTreeNode) filter); } else { rootFilter.addChild(new TmfFilterObjectNode(filter)); } fCache.applyFilter(rootFilter, fCollapseFilterEnabled); fHeaderBar.addFilter(filter); fTable.clearAll(); fTable.setData(Key.FILTER_OBJ, rootFilter); /* +1 for header row, +2 for top and bottom filter status rows */ fTable.setItemCount(3); startFilterThread(); fireFilterApplied(rootFilter); } /** * Remove a filter. Any other existing filters remain applied. * * @param filter * The filter to remove * @since 2.0 */ protected void removeFilter(ITmfFilter filter) { ITmfFilterTreeNode rootFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ); if (rootFilter == null) { return; } stopFilterThread(); stopSearchThread(); fFilterMatchCount = 0; fFilterCheckCount = 0; if (filter instanceof TmfCollapseFilter) { fCollapseFilterEnabled = false; } else if (filter instanceof ITmfFilterTreeNode) { rootFilter.removeChild((ITmfFilterTreeNode) filter); } else { for (ITmfFilterTreeNode child : rootFilter.getChildren()) { if (child instanceof TmfFilterObjectNode) { if (((TmfFilterObjectNode) child).getFilter().equals(filter)) { rootFilter.removeChild(child); break; } } } } if (!rootFilter.hasChildren() && !fCollapseFilterEnabled) { clearFilters(); return; } fCache.applyFilter(rootFilter, fCollapseFilterEnabled); fHeaderBar.removeFilter(filter); fTable.clearAll(); fTable.setData(Key.FILTER_OBJ, rootFilter); /* +1 for header row, +2 for top and bottom filter status rows */ fTable.setItemCount(3); startFilterThread(); fireFilterApplied(rootFilter); // Set original width fTable.getColumns()[MARGIN_COLUMN_INDEX].setWidth(0); packMarginColumn(); } /** * Clear all currently active filters. */ protected void clearFilters() { if (fTable.getData(Key.FILTER_OBJ) == null) { return; } stopFilterThread(); stopSearchThread(); fCache.clearFilter(); fHeaderBar.clearFilters(); fCollapseFilterEnabled = false; fTable.clearAll(); for (final TableColumn column : fTable.getColumns()) { column.setData(Key.FILTER_OBJ, null); column.setData(Key.FILTER_TXT, null); } fTable.setData(Key.FILTER_OBJ, null); if (fTrace != null) { /* +1 for header row */ fTable.setItemCount((int) fTrace.getNbEvents() + 1); } else { /* +1 for header row */ fTable.setItemCount(1); } fFilterMatchCount = 0; fFilterCheckCount = 0; if (fSelectedRank >= 0) { /* +1 for header row */ fTable.setSelection((int) fSelectedRank + 1); } else { fTable.setSelection(0); } fireFilterApplied(null); updateStatusLine(null); // Set original width fTable.getColumns()[MARGIN_COLUMN_INDEX].setWidth(0); packMarginColumn(); } /** * Wrapper Thread object for the filtering thread. */ protected class FilterThread extends Thread { private final ITmfFilterTreeNode filter; private TmfCollapseFilter collapseFilter = null; private TmfEventRequest request; private boolean refreshBusy = false; private boolean refreshPending = false; private final Object syncObj = new Object(); /** * Constructor. * * @param filter * The filter this thread will be processing */ public FilterThread(final ITmfFilterTreeNode filter) { super("Filter Thread"); //$NON-NLS-1$ this.filter = filter; } @Override public void run() { if (fTrace == null) { return; } if (fCollapseFilterEnabled) { collapseFilter = new TmfCollapseFilter(); } final int nbRequested = (int) (fTrace.getNbEvents() - fFilterCheckCount); if (nbRequested <= 0) { return; } request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY, (int) fFilterCheckCount, nbRequested, ExecutionType.BACKGROUND) { @Override public void handleData(final ITmfEvent event) { super.handleData(event); if (request.isCancelled()) { return; } boolean refresh = false; if (filter.matches(event)) { if (collapseFilter == null || collapseFilter.matches(event)) { final long rank = fFilterCheckCount; final int index = (int) fFilterMatchCount; fFilterMatchCount++; fCache.storeEvent(event, rank, index); } else if (collapseFilter != null) { fCache.updateCollapsedEvent((int) fFilterMatchCount - 1); } refresh = true; } if (refresh || (fFilterCheckCount % 100) == 0) { refreshTable(); } fFilterCheckCount++; } }; ((ITmfEventProvider) fTrace).sendRequest(request); try { request.waitForCompletion(); } catch (final InterruptedException e) { } refreshTable(); synchronized (fFilterSyncObj) { fFilterThread = null; if (fFilterThreadResume) { fFilterThreadResume = false; fFilterThread = new FilterThread(filter); fFilterThread.start(); } } } /** * Refresh the filter. */ public void refreshTable() { synchronized (syncObj) { if (refreshBusy) { refreshPending = true; return; } refreshBusy = true; } Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (request.isCancelled()) { return; } if (fTable.isDisposed()) { return; } /* * +1 for header row, +2 for top and bottom filter status * rows */ fTable.setItemCount((int) fFilterMatchCount + 3); fTable.refresh(); synchronized (syncObj) { refreshBusy = false; if (refreshPending) { refreshPending = false; refreshTable(); } } } }); } /** * Cancel this filtering thread. */ public void cancel() { if (request != null) { request.cancel(); } } } /** * Go to the next item of a search. */ protected void searchNext() { synchronized (fSearchSyncObj) { if (fSearchThread != null) { return; } final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ); if (searchFilter == null) { return; } final int selectionIndex = fTable.getSelectionIndex(); int startIndex; if (selectionIndex > 0) { /* -1 for header row, +1 for next event */ startIndex = selectionIndex; } else { /* * header row is selected, start at top event */ /* -1 for header row */ startIndex = Math.max(0, fTable.getTopIndex() - 1); } final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ); if (eventFilter != null) { // -1 for top filter status row startIndex = Math.max(0, startIndex - 1); } fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.FORWARD); fSearchThread.schedule(); } } /** * Go to the previous item of a search. */ protected void searchPrevious() { synchronized (fSearchSyncObj) { if (fSearchThread != null) { return; } final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ); if (searchFilter == null) { return; } final int selectionIndex = fTable.getSelectionIndex(); int startIndex; if (selectionIndex > 0) { /* -1 for header row, -1 for previous event */ startIndex = selectionIndex - 2; } else { /* * Header row is selected, start at precedent of top event */ /* -1 for header row, -1 for previous event */ startIndex = fTable.getTopIndex() - 2; } final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ); if (eventFilter != null) { /* -1 for top filter status row */ startIndex = startIndex - 1; } fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.BACKWARD); fSearchThread.schedule(); } } /** * Stop the search thread. */ protected void stopSearchThread() { fPendingGotoRank = -1; synchronized (fSearchSyncObj) { if (fSearchThread != null) { fSearchThread.cancel(); fSearchThread = null; } } } /** * Wrapper for the search thread. */ protected class SearchThread extends Job { private ITmfFilterTreeNode searchFilter; private ITmfFilterTreeNode eventFilter; private int startIndex; private int direction; private long rank; private long foundRank = -1; private TmfEventRequest request; private ITmfTimestamp foundTimestamp = null; /** * Constructor. * * @param searchFilter * The search filter * @param eventFilter * The event filter * @param startIndex * The index at which we should start searching * @param currentRank * The current rank * @param direction * In which direction should we search, forward or backwards */ public SearchThread(final ITmfFilterTreeNode searchFilter, final ITmfFilterTreeNode eventFilter, final int startIndex, final long currentRank, final int direction) { super(Messages.TmfEventsTable_SearchingJobName); this.searchFilter = searchFilter; this.eventFilter = eventFilter; this.startIndex = startIndex; this.rank = currentRank; this.direction = direction; } @Override protected IStatus run(final IProgressMonitor monitor) { final ITmfTrace trace = fTrace; if (trace == null) { return Status.OK_STATUS; } final Display display = Display.getDefault(); if (startIndex < 0) { rank = (int) trace.getNbEvents() - 1; /* * -1 for header row, -3 for header and top and bottom filter * status rows */ } else if (startIndex >= (fTable.getItemCount() - (eventFilter == null ? 1 : 3))) { rank = 0; } else { int idx = startIndex; while (foundRank == -1) { final CachedEvent event = fCache.peekEvent(idx); if (event == null) { break; } rank = event.rank; if (searchFilter.matches(event.event) && ((eventFilter == null) || eventFilter.matches(event.event))) { foundRank = event.rank; foundTimestamp = event.event.getTimestamp(); break; } if (direction == Direction.FORWARD) { idx++; } else { idx--; } } if (foundRank == -1) { if (direction == Direction.FORWARD) { rank++; if (rank > (trace.getNbEvents() - 1)) { rank = 0; } } else { rank--; if (rank < 0) { rank = (int) trace.getNbEvents() - 1; } } } } final int startRank = (int) rank; boolean wrapped = false; while (!monitor.isCanceled() && (foundRank == -1)) { int nbRequested = (direction == Direction.FORWARD ? Integer.MAX_VALUE : Math.min((int) rank + 1, trace.getCacheSize())); if (direction == Direction.BACKWARD) { rank = Math.max(0, rank - trace.getCacheSize() + 1); } request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY, (int) rank, nbRequested, ExecutionType.BACKGROUND) { long currentRank = rank; @Override public void handleData(final ITmfEvent event) { super.handleData(event); if (searchFilter.matches(event) && ((eventFilter == null) || eventFilter.matches(event))) { foundRank = currentRank; foundTimestamp = event.getTimestamp(); if (direction == Direction.FORWARD) { done(); return; } } currentRank++; } }; ((ITmfEventProvider) trace).sendRequest(request); try { request.waitForCompletion(); if (request.isCancelled()) { return Status.OK_STATUS; } } catch (final InterruptedException e) { synchronized (fSearchSyncObj) { fSearchThread = null; } return Status.OK_STATUS; } if (foundRank == -1) { if (direction == Direction.FORWARD) { if (rank == 0) { synchronized (fSearchSyncObj) { fSearchThread = null; } return Status.OK_STATUS; } nbRequested = (int) rank; rank = 0; wrapped = true; } else { rank--; if (rank < 0) { rank = (int) trace.getNbEvents() - 1; wrapped = true; } if ((rank <= startRank) && wrapped) { synchronized (fSearchSyncObj) { fSearchThread = null; } return Status.OK_STATUS; } } } } int index = (int) foundRank; if (eventFilter != null) { index = fCache.getFilteredEventIndex(foundRank); } /* +1 for header row, +1 for top filter status row */ final int selection = index + 1 + (eventFilter != null ? +1 : 0); display.asyncExec(new Runnable() { @Override public void run() { if (monitor.isCanceled()) { return; } if (fTable.isDisposed()) { return; } fTable.setSelection(selection); fSelectedRank = foundRank; fSelectedBeginRank = fSelectedRank; fRawViewer.selectAndReveal(fSelectedRank); if (foundTimestamp != null) { broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, foundTimestamp)); } fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, getSelection())); synchronized (fSearchSyncObj) { fSearchThread = null; } updateStatusLine(null); } }); return Status.OK_STATUS; } @Override protected void canceling() { if (request != null) { request.cancel(); } synchronized (fSearchSyncObj) { fSearchThread = null; } } } /** * Create the resources. */ private void createResources() { fGrayColor = fResourceManager.createColor(ColorUtil.blend(fTable.getBackground().getRGB(), fTable.getForeground().getRGB())); fGreenColor = PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN); } /** * Initialize the fonts. */ private void initializeFonts() { FontRegistry fontRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry(); fFont = fontRegistry.get(FONT_DEFINITION_ID); fBoldFont = fontRegistry.getBold(FONT_DEFINITION_ID); fTable.setFont(fFont); /* Column header font cannot be set. See Bug 63038 */ } /** * Initialize the colors. */ private void initializeColors() { ColorRegistry colorRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry(); fHighlightColor = colorRegistry.get(HIGHLIGHT_COLOR_DEFINITION_ID); } /** * @since 1.0 */ @Override public void propertyChange(PropertyChangeEvent event) { if ((IThemeManager.CHANGE_CURRENT_THEME.equals(event.getProperty())) || (FONT_DEFINITION_ID.equals(event.getProperty()))) { initializeFonts(); fTable.refresh(); } if ((IThemeManager.CHANGE_CURRENT_THEME.equals(event.getProperty())) || (HIGHLIGHT_COLOR_DEFINITION_ID.equals(event.getProperty()))) { initializeColors(); fTable.refresh(); } } /** * Pack the columns. * * @return Whether or not a pack was done in this call. Otherwise, it was * already done by a previous call * * @since 2.0 */ protected boolean packColumns() { if (fPackDone) { return false; } fTable.setRedraw(false); try { TableColumn tableColumns[] = fTable.getColumns(); for (int i = 0; i < tableColumns.length; i++) { final TableColumn column = tableColumns[i]; packSingleColumn(i, column); } } finally { // Make sure that redraw is always enabled. fTable.setRedraw(true); } fPackDone = true; return true; } private void packMarginColumn() { TableColumn[] columns = fTable.getColumns(); if (columns.length > 0) { packSingleColumn(0, columns[0]); } } private void packSingleColumn(int i, final TableColumn column) { if (i != MARGIN_COLUMN_INDEX && !column.getResizable()) { return; } Object data = column.getData(Key.WIDTH); final int headerWidth = data instanceof Integer ? (int) data : -1; column.pack(); /* * Workaround for Linux which doesn't consider the image width of * search/filter row in TableColumn.pack() after having executed * TableItem.setImage(null) for other rows than search/filter row. */ if (IS_LINUX && (i == MARGIN_COLUMN_INDEX) && fCollapseFilterEnabled) { column.setWidth(column.getWidth() + SEARCH_IMAGE.getBounds().width); } if (column.getWidth() < headerWidth) { column.setWidth(headerWidth); } else if (i != MARGIN_COLUMN_INDEX) { column.setData(Key.WIDTH, column.getWidth()); } } /** * Returns true if the column is a visible event column. * * @param column * the column * @return false if the column is the margin column or hidden, true * otherwise */ private static boolean isVisibleEventColumn(TableColumn column) { if (column.getData(Key.ASPECT) == TmfMarginColumn.MARGIN_ASPECT) { return false; } if (!column.getResizable() && column.getWidth() == 0) { return false; } return true; } /** * Returns true if the column is expanded to take extra available space. * This is the last non-zero-width visible column in the column order on * Linux. This column's width should not be persisted. * * @param column * the column * @return true if the column is expanded. */ private static boolean isExpanded(TableColumn column) { if (IS_LINUX) { Table table = column.getParent(); int[] order = table.getColumnOrder(); for (int i = order.length - 1; i >= 0; i--) { TableColumn col = table.getColumn(order[i]); if (col == column) { return true; } if (col.getWidth() > 0) { return false; } } } return false; } /** * Get the array of item strings (e.g., what to display in each cell of the * table row) corresponding to the columns and trace event passed in * parameter. The order of the Strings in the returned array will correspond * to the iteration order of 'columns'. * * <p> * To ensure consistent results, make sure only call this within a scope * synchronized on 'columns'! If the order of 'columns' changes right after * this method is called, the returned value won't be ordered correctly * anymore. */ private static String[] getItemStrings(List<TmfEventTableColumn> columns, ITmfEvent event) { if (event == null) { return EMPTY_STRING_ARRAY; } synchronized (columns) { List<String> itemStrings = new ArrayList<>(columns.size()); for (TmfEventTableColumn column : columns) { ITmfEvent passedEvent = event; if (!(column instanceof TmfMarginColumn) && (event instanceof CachedEvent)) { /* * Make sure that the event object from the trace is passed * to all columns but the TmfMarginColumn */ passedEvent = ((CachedEvent) event).event; } if (passedEvent == null) { itemStrings.add(EMPTY_STRING); } else { itemStrings.add(column.getItemString(passedEvent)); } } return itemStrings.toArray(new String[0]); } } /** * Get the contents of the row in the events table corresponding to an * event. The order of the elements corresponds to the current order of the * columns. * * @param event * The event printed in this row * @return The event row entries */ public String[] getItemStrings(ITmfEvent event) { List<TmfEventTableColumn> columns = new ArrayList<>(); for (int i : fTable.getColumnOrder()) { columns.add(fColumns.get(i)); } return getItemStrings(columns, event); } /** * Returns an array of zero-relative integers that map the creation order of * the receiver's columns to the order in which they are currently being * displayed. * <p> * Specifically, the indices of the returned array represent the current * visual order of the columns, and the contents of the array represent the * creation order of the columns. * * @return the current visual order of the receiver's columns * @since 1.0 */ public int[] getColumnOrder() { return fColumnOrder; } /** * Get column widths * * @return the current visual widths of the receiver's columns * @since 2.1 */ public int[] getColumnWidth() { return fColumnSize; } /** * Get whether the columns are resizable * * @return an array stating if each column is resizable * @since 2.1 */ public boolean[] getColumnResizable() { return fColumnResizable; } /** * Sets the order that the columns in the receiver should be displayed in to * the given argument which is described in terms of the zero-relative * ordering of when the columns were added. * <p> * Specifically, the contents of the array represent the original position * of each column at the time its creation. * * @param order * the new order to display the columns * @since 1.0 */ public void setColumnOrder(int[] order) { if (order == null || order.length != fTable.getColumns().length) { return; } fTable.setColumnOrder(order); fColumnOrder = fTable.getColumnOrder(); } /** * Sets the column width and resizability * * @param width * an array of widths * @param resizable * an array of bools saying if a column is resizable or not * @since 2.1 */ public void setColumnWidth(int[] width, boolean[] resizable) { int length = fTable.getColumns().length; if (width == null || resizable == null || resizable.length != length || width.length != length) { return; } int i = 0; for (TableColumn column : fTable.getColumns()) { if (i != MARGIN_COLUMN_INDEX) { column.setData(Key.WIDTH, width[i]); column.setResizable(resizable[i]); if (column.getResizable()) { column.setWidth((int) column.getData(Key.WIDTH)); } else { column.setWidth(0); } } i++; } fColumnSize = width; fColumnResizable = resizable; /* Don't pack, it would override these settings */ fPackDone = true; } /** * Notify this table that is got the UI focus. */ public void setFocus() { fTable.setFocus(); } /** * Registers context menus with a site for extension. This method can be * called for part sites so that context menu contributions can be added. * * @param site * the site that the context menus will be registered for * * @since 1.2 */ public void registerContextMenus(IWorkbenchPartSite site) { if (site instanceof IEditorSite) { IEditorSite editorSite = (IEditorSite) site; // Don't use the editor input when adding contributions, otherwise // we get too many unwanted things. editorSite.registerContextMenu(fTablePopupMenuManager, this, false); } } /** * Assign a new trace to this event table. * * @param trace * The trace to assign to this event table * @param disposeOnClose * true if the trace should be disposed when the table is * disposed */ public void setTrace(final ITmfTrace trace, final boolean disposeOnClose) { if ((fTrace != null) && fDisposeOnClose) { fTrace.dispose(); } fTrace = trace; fPackDone = false; fDisposeOnClose = disposeOnClose; // Perform the updates on the UI thread PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { @Override public void run() { fSelectedRank = -1; fSelectedBeginRank = -1; fTable.removeAll(); fCache.setTrace(trace); // Clear the cache if (trace != null) { if (!fTable.isDisposed()) { if (fTable.getData(Key.FILTER_OBJ) == null) { // +1 for header row fTable.setItemCount((int) trace.getNbEvents() + 1); } else { stopFilterThread(); fFilterMatchCount = 0; fFilterCheckCount = 0; /* * +1 for header row, +2 for top and bottom filter * status rows */ fTable.setItemCount(3); startFilterThread(); } } } fRawViewer.setTrace(trace); } }); } /** * Assign the status line manager * * @param statusLineManager * The status line manager, or null to disable status line * messages */ public void setStatusLineManager(IStatusLineManager statusLineManager) { if (fStatusLineManager != null && statusLineManager == null) { fStatusLineManager.setMessage(EMPTY_STRING); } fStatusLineManager = statusLineManager; } private void updateStatusLine(ITmfTimestamp delta) { if (fStatusLineManager != null) { if (delta != null) { fStatusLineManager.setMessage("\u0394: " + delta); //$NON-NLS-1$ } else { fStatusLineManager.setMessage(null); } } } // ------------------------------------------------------------------------ // Event cache // ------------------------------------------------------------------------ /** * Notify that the event cache has been updated * * @param completed * Also notify if the populating of the cache is complete, or * not. */ public void cacheUpdated(final boolean completed) { synchronized (fCacheUpdateSyncObj) { if (fCacheUpdateBusy) { fCacheUpdatePending = true; fCacheUpdateCompleted = completed; return; } fCacheUpdateBusy = true; } // Event cache is now updated. Perform update on the UI thread PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!fTable.isDisposed()) { fTable.refresh(); packColumns(); } if (completed) { populateCompleted(); } synchronized (fCacheUpdateSyncObj) { fCacheUpdateBusy = false; if (fCacheUpdatePending) { fCacheUpdatePending = false; cacheUpdated(fCacheUpdateCompleted); } } } }); } /** * Callback for when populating the table is complete. */ protected void populateCompleted() { // Nothing by default; } // ------------------------------------------------------------------------ // ISelectionProvider // ------------------------------------------------------------------------ @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListeners.add(listener); } @Override public ISelection getSelection() { if (fTable == null || fTable.isDisposed()) { return StructuredSelection.EMPTY; } List<Object> list = new ArrayList<>(fTable.getSelection().length); for (TableItem item : fTable.getSelection()) { if (item.getData() != null) { list.add(item.getData()); } } return new StructuredSelection(list); } @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListeners.remove(listener); } @Override public void setSelection(ISelection selection) { // not implemented } /** * Notifies any selection changed listeners that the viewer's selection has * changed. Only listeners registered at the time this method is called are * notified. * * @param event * a selection changed event * * @see ISelectionChangedListener#selectionChanged */ protected void fireSelectionChanged(final SelectionChangedEvent event) { Object[] listeners = selectionChangedListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.selectionChanged(event); } }); } } // ------------------------------------------------------------------------ // Bookmark handling // ------------------------------------------------------------------------ /** * Add a bookmark to this event table. * * @param bookmarksFile * The file to use for the bookmarks */ public void addBookmark(final IFile bookmarksFile) { fBookmarksFile = bookmarksFile; final TableItem[] selection = fTable.getSelection(); if (selection.length > 0) { final TableItem tableItem = selection[0]; if (tableItem.getData(Key.RANK) != null) { final StringBuffer defaultMessage = new StringBuffer(); for (int i : fTable.getColumnOrder()) { TableColumn column = fTable.getColumns()[i]; // Omit the margin column and hidden columns if (isVisibleEventColumn(column)) { if (defaultMessage.length() > 0) { defaultMessage.append(", "); //$NON-NLS-1$ } defaultMessage.append(tableItem.getText(i)); } } final AddBookmarkDialog dialog = new AddBookmarkDialog( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), defaultMessage.toString()); if (dialog.open() == Window.OK) { final String message = dialog.getValue(); try { final Long rank = (Long) tableItem.getData(Key.RANK); final String location = NLS.bind(Messages.TmfMarker_LocationRank, rank.toString()); final ITmfTimestamp timestamp = (ITmfTimestamp) tableItem.getData(Key.TIMESTAMP); final long[] id = new long[1]; ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() { @Override public void run(IProgressMonitor monitor) throws CoreException { final IMarker bookmark = bookmarksFile.createMarker(IMarker.BOOKMARK); bookmark.setAttribute(IMarker.MESSAGE, message.toString()); bookmark.setAttribute(IMarker.LOCATION, location); bookmark.setAttribute(ITmfMarker.MARKER_RANK, rank.toString()); bookmark.setAttribute(ITmfMarker.MARKER_TIME, Long.toString(timestamp.toNanos())); bookmark.setAttribute(ITmfMarker.MARKER_COLOR, dialog.getColorValue().toString()); id[0] = bookmark.getId(); } }, null); fBookmarksMap.put(rank, id[0]); fTable.refresh(); } catch (final CoreException e) { displayException(e); } } } } } /** * Add one or more bookmarks to this event table. * * @param bookmarks * The bookmarks to add * @since 2.0 */ public void addBookmark(final IMarker... bookmarks) { for (IMarker bookmark : bookmarks) { /* try location as an integer for backward compatibility */ long rank = bookmark.getAttribute(IMarker.LOCATION, -1); if (rank == -1) { String rankString = bookmark.getAttribute(ITmfMarker.MARKER_RANK, (String) null); if (rankString != null) { try { rank = Long.parseLong(rankString); } catch (NumberFormatException e) { Activator.getDefault().logError("Invalid marker rank", e); //$NON-NLS-1$ } } } if (rank != -1) { fBookmarksMap.put(rank, bookmark.getId()); } } fTable.refresh(); } /** * Remove one or more bookmarks from this event table. * * @param bookmarks * The bookmarks to remove * @since 2.0 */ public void removeBookmark(final IMarker... bookmarks) { for (IMarker bookmark : bookmarks) { for (final Entry<Long, Long> entry : fBookmarksMap.entries()) { if (entry.getValue().equals(bookmark.getId())) { fBookmarksMap.remove(entry.getKey(), entry.getValue()); break; } } } fTable.refresh(); } private void toggleBookmark(final Long rank) { if (fBookmarksFile == null) { return; } if (fBookmarksMap.containsKey(rank)) { final Collection<Long> markerIds = fBookmarksMap.removeAll(rank); fTable.refresh(); try { for (long markerId : markerIds) { final IMarker bookmark = fBookmarksFile.findMarker(markerId); if (bookmark != null) { bookmark.delete(); } } } catch (final CoreException e) { displayException(e); } } else { addBookmark(fBookmarksFile); } } /** * Refresh the bookmarks assigned to this trace, from the contents of a * bookmark file. * * @param bookmarksFile * The bookmark file to use */ public void refreshBookmarks(final IFile bookmarksFile) { fBookmarksFile = bookmarksFile; if (bookmarksFile == null) { fBookmarksMap.clear(); fTable.refresh(); return; } try { fBookmarksMap.clear(); IMarker[] bookmarks = bookmarksFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO); addBookmark(bookmarks); } catch (final CoreException e) { displayException(e); } } @Override public void gotoMarker(final IMarker marker) { ITmfTimestamp tsBegin = null; ITmfTimestamp tsEnd = null; /* try location as an integer for backward compatibility */ long rank = marker.getAttribute(IMarker.LOCATION, -1); if (rank == -1) { String rankString = marker.getAttribute(ITmfMarker.MARKER_RANK, (String) null); try { rank = Long.parseLong(rankString); } catch (NumberFormatException e) { /* ignored */ } } try { String timeString = marker.getAttribute(ITmfMarker.MARKER_TIME, (String) null); long time = Long.parseLong(timeString); tsBegin = TmfTimestamp.fromNanos(time); String durationString = marker.getAttribute(ITmfMarker.MARKER_DURATION, (String) null); long duration = Long.parseLong(durationString); tsEnd = TmfTimestamp.fromNanos(time + duration); } catch (NumberFormatException e) { /* ignored */ } if (rank == -1 && tsBegin != null) { final ITmfContext context = fTrace.seekEvent(tsBegin); rank = context.getRank(); context.dispose(); } if (rank != -1) { int index = (int) rank; if (fTable.getData(Key.FILTER_OBJ) != null) { // +1 for top filter status row index = fCache.getFilteredEventIndex(rank) + 1; } else if (rank >= fTable.getItemCount()) { fPendingGotoRank = rank; } fSelectedRank = rank; fSelectedBeginRank = fSelectedRank; fTable.setSelection(index + 1); // +1 for header row updateStatusLine(null); if (tsBegin != null) { if (tsEnd != null) { broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, tsBegin, tsEnd)); } else { broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, tsBegin)); } } } } // ------------------------------------------------------------------------ // Listeners // ------------------------------------------------------------------------ @Override public void colorSettingsChanged(final ColorSetting[] colorSettings) { fTable.refresh(); } // ------------------------------------------------------------------------ // Signal handlers // ------------------------------------------------------------------------ /** * Handler for the trace updated signal * * @param signal * The incoming signal */ @TmfSignalHandler public void traceUpdated(final TmfTraceUpdatedSignal signal) { if ((signal.getTrace() != fTrace) || fTable.isDisposed()) { return; } // Perform the refresh on the UI thread Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (!fTable.isDisposed() && (fTrace != null)) { if (fTable.getData(Key.FILTER_OBJ) == null) { /* +1 for header row */ fTable.setItemCount((int) fTrace.getNbEvents() + 1); /* +1 for header row */ if ((fPendingGotoRank != -1) && ((fPendingGotoRank + 1) < fTable.getItemCount())) { /* +1 for header row */ fTable.setSelection((int) fPendingGotoRank + 1); fPendingGotoRank = -1; updateStatusLine(null); } } else { startFilterThread(); } } if (!fRawViewer.isDisposed() && (fTrace != null)) { fRawViewer.refreshEventCount(); } } }); } /** * Handler for the selection range signal. * * @param signal * The incoming signal * @since 1.0 */ @TmfSignalHandler public void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) { if ((signal.getSource() != this) && (fTrace != null) && (!fTable.isDisposed())) { Job timeSelectJob; synchronized (fTimeSelectMutexRule) { timeSelectJob = fTimeSelectJob; if (timeSelectJob != null) { timeSelectJob.cancel(); } /* * Run in separate thread to not block UI thread for too long. */ timeSelectJob = new Job("Events table selection job") { //$NON-NLS-1$ ITmfTimestamp ts = signal.getBeginTime(); ITmfTimestamp tf = signal.getEndTime(); @Override protected IStatus run(IProgressMonitor monitor) { if (fTrace == null) { return Status.OK_STATUS; } final Pair<Long, Long> selection = getSelectedRanks(monitor); if (monitor.isCanceled() || (selection == null)) { return Status.CANCEL_STATUS; } updateDisplayWithSelection(selection.getFirst().longValue(), selection.getSecond().longValue()); return Status.OK_STATUS; } /** * Verify if the event is within the trace range and adjust if * necessary. * @param monitor * a progress monitor * @return A pair of rank representing the selected area **/ @Nullable private Pair<Long, Long> getSelectedRanks(IProgressMonitor monitor) { /* Clamp the timestamp value to fit inside of the trace */ ITmfTimestamp timestampBegin = ts; if (timestampBegin.compareTo(fTrace.getStartTime()) < 0) { timestampBegin = fTrace.getStartTime(); } if (timestampBegin.compareTo(fTrace.getEndTime()) > 0) { timestampBegin = fTrace.getEndTime(); } ITmfTimestamp timestampEnd = tf; if (timestampEnd.compareTo(fTrace.getStartTime()) < 0) { timestampEnd = fTrace.getStartTime(); } if (timestampEnd.compareTo(fTrace.getEndTime()) > 0) { timestampEnd = fTrace.getEndTime(); } ITmfTimestamp tb; ITmfTimestamp te; long rankBegin; long rankEnd; ITmfContext contextBegin; ITmfContext contextEnd; if (monitor.isCanceled()) { return null; } /* Adjust the rank of the selection to the right range */ if (timestampBegin.compareTo(timestampEnd) > 0) { te = timestampEnd; contextEnd = fTrace.seekEvent(te); rankEnd = contextEnd.getRank(); contextEnd.dispose(); if (monitor.isCanceled()) { return null; } /* * To include all events at the begin time, seek at the * next nanosecond and then use the previous rank */ tb = timestampBegin.normalize(1, ITmfTimestamp.NANOSECOND_SCALE); if (tb.compareTo(fTrace.getEndTime()) <= 0) { contextBegin = fTrace.seekEvent(tb); rankBegin = contextBegin.getRank(); contextBegin.dispose(); } else { rankBegin = ITmfContext.UNKNOWN_RANK; } rankBegin = (rankBegin == ITmfContext.UNKNOWN_RANK ? fTrace.getNbEvents() : rankBegin) - 1; /* * If no events in selection range, select only the next * event */ rankBegin = rankBegin >= rankEnd ? rankBegin : rankEnd; } else { tb = timestampBegin; contextBegin = fTrace.seekEvent(tb); rankBegin = contextBegin.getRank(); contextBegin.dispose(); if (monitor.isCanceled()) { return null; } /* * To include all events at the end time, seek at the * next nanosecond and then use the previous rank */ te = timestampEnd.normalize(1, ITmfTimestamp.NANOSECOND_SCALE); if (te.compareTo(fTrace.getEndTime()) <= 0) { contextEnd = fTrace.seekEvent(te); rankEnd = contextEnd.getRank(); contextEnd.dispose(); } else { rankEnd = ITmfContext.UNKNOWN_RANK; } rankEnd = (rankEnd == ITmfContext.UNKNOWN_RANK ? fTrace.getNbEvents() : rankEnd) - 1; /* * If no events in selection range, select only the next * event */ rankEnd = rankEnd >= rankBegin ? rankEnd : rankBegin; } return new Pair<>(Long.valueOf(rankBegin), Long.valueOf(rankEnd)); } private void updateDisplayWithSelection(final long rankBegin, final long rankEnd) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { // Return if table is disposed if (fTable.isDisposed()) { return; } fSelectedRank = rankEnd; long toReveal = fSelectedBeginRank != rankBegin ? rankBegin : rankEnd; fSelectedBeginRank = rankBegin; int indexBegin = (int) rankBegin; int indexEnd = (int) rankEnd; if (fTable.getData(Key.FILTER_OBJ) != null) { /* +1 for top filter status row */ indexBegin = fCache.getFilteredEventIndex(rankBegin) + 1; indexEnd = rankEnd == rankBegin ? indexBegin : fCache.getFilteredEventIndex(rankEnd) + 1; } /* +1 for header row */ fTable.setSelectionRange(indexBegin + 1, indexEnd + 1); fRawViewer.selectAndReveal(toReveal); updateStatusLine(null); } }); } }; timeSelectJob.setSystem(true); /* * Make subsequent jobs not run concurrently so that they are * executed in order. */ timeSelectJob.setRule(fTimeSelectMutexRule); timeSelectJob.schedule(); fTimeSelectJob = timeSelectJob; } } } // ------------------------------------------------------------------------ // Error handling // ------------------------------------------------------------------------ /** * Display an exception in a message box * * @param e * the exception */ private static void displayException(final Exception e) { final MessageBox mb = new MessageBox(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell()); mb.setText(e.getClass().getSimpleName()); mb.setMessage(e.getMessage()); mb.open(); } /** * Refresh the table */ public void refresh() { fCache.clear(); fTable.refresh(); fTable.redraw(); } /** * Margin column for images and special text (e.g. collapse count) */ private static final class TmfMarginColumn extends TmfEventTableColumn { private static final @NonNull ITmfEventAspect<String> MARGIN_ASPECT = new ITmfEventAspect<String>() { @Override public String getName() { return EMPTY_STRING; } @Override public String resolve(ITmfEvent event) { if (!(event instanceof CachedEvent) || ((CachedEvent) event).repeatCount == 0) { return EMPTY_STRING; } return "+" + ((CachedEvent) event).repeatCount; //$NON-NLS-1$ } @Override public String getHelpText() { return EMPTY_STRING; } }; /** * Constructor */ public TmfMarginColumn() { super(MARGIN_ASPECT); } } }