/*******************************************************************************
* Copyright (c) 2010, 2014 Ericsson
*
* 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
* Patrick Tasse - Factored out from events view
* Francois Chouinard - Replaced Table by TmfVirtualTable
* Patrick Tasse - 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
*******************************************************************************/
package fr.inria.linuxtools.tmf.ui.viewers.events;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
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.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.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.Job;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EValidator;
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.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.util.OpenStrategy;
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.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.TableEditor;
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.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.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPage;
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.themes.ColorUtil;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import fr.inria.linuxtools.internal.tmf.ui.Activator;
import fr.inria.linuxtools.internal.tmf.ui.Messages;
import fr.inria.linuxtools.internal.tmf.ui.commands.ExportToTextCommandHandler;
import fr.inria.linuxtools.internal.tmf.ui.dialogs.MultiLineInputDialog;
import fr.inria.linuxtools.tmf.core.component.ITmfEventProvider;
import fr.inria.linuxtools.tmf.core.component.TmfComponent;
import fr.inria.linuxtools.tmf.core.event.ITmfEvent;
import fr.inria.linuxtools.tmf.core.event.lookup.ITmfCallsite;
import fr.inria.linuxtools.tmf.core.event.lookup.ITmfModelLookup;
import fr.inria.linuxtools.tmf.core.event.lookup.ITmfSourceLookup;
import fr.inria.linuxtools.tmf.core.filter.ITmfFilter;
import fr.inria.linuxtools.tmf.core.filter.model.ITmfFilterTreeNode;
import fr.inria.linuxtools.tmf.core.filter.model.TmfFilterAndNode;
import fr.inria.linuxtools.tmf.core.filter.model.TmfFilterMatchesNode;
import fr.inria.linuxtools.tmf.core.filter.model.TmfFilterNode;
import fr.inria.linuxtools.tmf.core.request.TmfEventRequest;
import fr.inria.linuxtools.tmf.core.request.ITmfEventRequest.ExecutionType;
import fr.inria.linuxtools.tmf.core.signal.TmfEventFilterAppliedSignal;
import fr.inria.linuxtools.tmf.core.signal.TmfEventSearchAppliedSignal;
import fr.inria.linuxtools.tmf.core.signal.TmfSignalHandler;
import fr.inria.linuxtools.tmf.core.signal.TmfTimeSynchSignal;
import fr.inria.linuxtools.tmf.core.signal.TmfTraceUpdatedSignal;
import fr.inria.linuxtools.tmf.core.timestamp.ITmfTimestamp;
import fr.inria.linuxtools.tmf.core.timestamp.TmfTimeRange;
import fr.inria.linuxtools.tmf.core.timestamp.TmfTimestamp;
import fr.inria.linuxtools.tmf.core.trace.ITmfContext;
import fr.inria.linuxtools.tmf.core.trace.ITmfTrace;
import fr.inria.linuxtools.tmf.core.trace.location.ITmfLocation;
import fr.inria.linuxtools.tmf.ui.viewers.events.TmfEventsCache.CachedEvent;
import fr.inria.linuxtools.tmf.ui.views.colors.ColorSetting;
import fr.inria.linuxtools.tmf.ui.views.colors.ColorSettingsManager;
import fr.inria.linuxtools.tmf.ui.views.colors.IColorSettingsListener;
import fr.inria.linuxtools.tmf.ui.views.filter.FilterManager;
import fr.inria.linuxtools.tmf.ui.widgets.rawviewer.TmfRawEventViewer;
import fr.inria.linuxtools.tmf.ui.widgets.virtualtable.ColumnData;
import fr.inria.linuxtools.tmf.ui.widgets.virtualtable.TmfVirtualTable;
/**
* The generic TMF Events table
*
* This is a view that will list events that are read from a trace.
*
* @version 1.0
* @author Francois Chouinard
* @author Patrick Tasse
* @since 2.0
*/
public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorSettingsListener, ISelectionProvider {
/**
* Empty string array, used by {@link #getItemStrings}.
* @since 3.0
*/
protected static final String[] EMPTY_STRING_ARRAY = new String[0];
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 STOP_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/stop.gif"); //$NON-NLS-1$
private static final String SEARCH_HINT = Messages.TmfEventsTable_SearchHint;
private static final String FILTER_HINT = Messages.TmfEventsTable_FilterHint;
private static final int MAX_CACHE_SIZE = 1000;
/**
* The events table search/filter keys
*
* @version 1.0
* @author Patrick Tasse
*/
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$
/** Field ID */
String FIELD_ID = "$field_id"; //$NON-NLS-1$
/** Bookmark indicator */
String BOOKMARK = "$bookmark"; //$NON-NLS-1$
}
/**
* The events table search/filter state
*
* @version 1.0
* @author Patrick Tasse
*/
public static enum HeaderState {
/** A search is being run */
SEARCH,
/** A filter is applied */
FILTER
}
interface Direction {
int FORWARD = +1;
int BACKWARD = -1;
}
// ------------------------------------------------------------------------
// Table data
// ------------------------------------------------------------------------
/** The virtual event table */
protected TmfVirtualTable fTable;
private Composite fComposite;
private SashForm fSashForm;
private TmfRawEventViewer fRawViewer;
private ITmfTrace fTrace;
private boolean fPackDone = false;
private HeaderState fHeaderState = HeaderState.SEARCH;
private long fSelectedRank = 0;
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();
/**
* 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 Font fBoldFont;
// Table column names
private static final String[] COLUMN_NAMES = new String[] { Messages.TmfEventsTable_TimestampColumnHeader,
Messages.TmfEventsTable_SourceColumnHeader, Messages.TmfEventsTable_TypeColumnHeader,
Messages.TmfEventsTable_ReferenceColumnHeader, Messages.TmfEventsTable_ContentColumnHeader };
private static final ColumnData[] COLUMN_DATA = new ColumnData[] { new ColumnData(COLUMN_NAMES[0], 100, SWT.LEFT),
new ColumnData(COLUMN_NAMES[1], 100, SWT.LEFT), new ColumnData(COLUMN_NAMES[2], 100, SWT.LEFT),
new ColumnData(COLUMN_NAMES[3], 100, SWT.LEFT), new ColumnData(COLUMN_NAMES[4], 100, SWT.LEFT) };
// Event cache
private final TmfEventsCache fCache;
private boolean fCacheUpdateBusy = false;
private boolean fCacheUpdatePending = false;
private boolean fCacheUpdateCompleted = false;
private final Object fCacheUpdateSyncObj = new Object();
private boolean fDisposeOnClose;
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
/**
* Basic constructor, will use default column data.
*
* @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, COLUMN_DATA);
}
/**
* Advanced constructor, where we also define which column data to use.
*
* @param parent
* The parent composite UI object
* @param cacheSize
* The size of the event table cache
* @param columnData
* The column data to use for this table
*/
public TmfEventsTable(final Composite parent, int cacheSize, final ColumnData[] columnData) {
super("TmfEventsTable"); //$NON-NLS-1$
fComposite = new Composite(parent, SWT.NONE);
final 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 virtual table
final int style = SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION;
fTable = new TmfVirtualTable(fSashForm, 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);
// Set the columns
setColumnHeaders(columnData);
// Set the default column field ids if this is not a subclass
if (Arrays.equals(columnData, COLUMN_DATA)) {
fTable.getColumns()[0].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_TIMESTAMP);
fTable.getColumns()[1].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_SOURCE);
fTable.getColumns()[2].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_TYPE);
fTable.getColumns()[3].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_REFERENCE);
fTable.getColumns()[4].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_CONTENT);
}
// 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 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));
}
if (e.item.getData(Key.TIMESTAMP) instanceof ITmfTimestamp) {
final ITmfTimestamp ts = (ITmfTimestamp) e.item.getData(Key.TIMESTAMP);
if (fTable.getSelectionIndices().length == 1) {
fSelectedBeginTimestamp = ts;
}
if (fSelectedBeginTimestamp != null) {
if (fSelectedBeginTimestamp.compareTo(ts) <= 0) {
broadcast(new TmfTimeSynchSignal(TmfEventsTable.this, fSelectedBeginTimestamp, ts));
if (fTable.getSelectionIndices().length == 2) {
updateStatusLine(ts.getDelta(fSelectedBeginTimestamp));
}
} else {
broadcast(new TmfTimeSynchSignal(TmfEventsTable.this, ts, fSelectedBeginTimestamp));
updateStatusLine(fSelectedBeginTimestamp.getDelta(ts));
}
}
} else {
if (fTable.getSelectionIndices().length == 1) {
fSelectedBeginTimestamp = null;
}
}
}
if (e.item.getData() != null) {
fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, new StructuredSelection(e.item.getData())));
} else {
fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, StructuredSelection.EMPTY));
}
}
});
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 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;
}
index = index - 1; // -1 for top filter status row
}
final CachedEvent cachedEvent = fCache.getEvent(index);
if (cachedEvent != null) {
setItemData(item, cachedEvent.event, cachedEvent.rank);
return;
}
// Else, fill the cache asynchronously (and off the UI thread)
event.doit = false;
}
});
fTable.addMouseListener(new 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);
}
}
}
}
});
final Listener tooltipListener = new 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;
}
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;
}
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);
String text = rank.toString() + (tooltipText != null ? ": " + tooltipText : ""); //$NON-NLS-1$ //$NON-NLS-2$
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 (System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
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;
}
}
};
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);
// Create resources
createResources();
ColorSettingsManager.addColorSettingsListener(this);
fTable.setItemCount(1); // +1 for header row
fRawViewer = new TmfRawEventViewer(fSashForm, SWT.H_SCROLL | SWT.V_SCROLL);
fRawViewer.addSelectionListener(new Listener() {
@Override
public void handleEvent(final Event e) {
if (e.data instanceof Long) {
final long rank = (Long) e.data;
int index = (int) rank;
if (fTable.getData(Key.FILTER_OBJ) != null) {
index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
}
fTable.setSelection(index + 1); // +1 for header row
fSelectedRank = rank;
updateStatusLine(null);
} else if (e.data instanceof ITmfLocation) {
// DOES NOT WORK: rank undefined in context from seekLocation()
// ITmfLocation<?> location = (ITmfLocation<?>) e.data;
// TmfContext context = fTrace.seekLocation(location);
// fTable.setSelection((int) context.getRank());
return;
} else {
return;
}
final TableItem[] selection = fTable.getSelection();
if ((selection != null) && (selection.length > 0)) {
final TmfTimestamp ts = (TmfTimestamp) fTable.getSelection()[0].getData(Key.TIMESTAMP);
if (ts != null) {
broadcast(new TmfTimeSynchSignal(TmfEventsTable.this, ts));
}
}
}
});
fSashForm.setWeights(new int[] { 1, 1 });
fRawViewer.setVisible(false);
createPopupMenu();
}
/**
* Create a pop-up menu.
*/
protected void createPopupMenu() {
final IAction showTableAction = new Action(Messages.TmfEventsTable_ShowTableActionText) {
@Override
public void run() {
fTable.setVisible(true);
fSashForm.layout();
}
};
final IAction hideTableAction = new Action(Messages.TmfEventsTable_HideTableActionText) {
@Override
public void run() {
fTable.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) {
ITmfSourceLookup event = (ITmfSourceLookup) data;
ITmfCallsite cs = event.getCallsite();
if (cs == null || cs.getFileName() == null) {
return;
}
IMarker marker = null;
try {
String fileName = cs.getFileName();
final String trimmedPath = fileName.replaceAll("\\.\\./", ""); //$NON-NLS-1$ //$NON-NLS-2$
final ArrayList<IFile> files = new ArrayList<>();
ResourcesPlugin.getWorkspace().getRoot().accept(new IResourceVisitor() {
@Override
public boolean visit(IResource resource) throws CoreException {
if (resource instanceof IFile && resource.getFullPath().toString().endsWith(trimmedPath)) {
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, Long.valueOf(cs.getLineNumber()).intValue());
IDE.openEditor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), marker);
marker.delete();
} else if (files.size() == 0){
displayException(new FileNotFoundException('\'' + cs.toString() + '\'' + '\n' + Messages.TmfEventsTable_OpenSourceCodeNotFound));
}
} catch (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();
IHandlerService handlerService = (IHandlerService) activePage.getActiveEditor().getSite().getService(IHandlerService.class);
ICommandService cmdService = (ICommandService) activePage.getActiveEditor().getSite().getService(ICommandService.class);
try {
HashMap<String, Object> parameters = new HashMap<>();
StringBuilder header = new StringBuilder();
boolean needTab = false;
for (TableColumn tc: fTable.getColumns()) {
if (needTab) {
header.append('\t');
}
header.append(tc.getText());
needTab = true;
}
Command command = cmdService.getCommand(ExportToTextCommandHandler.COMMAND_ID);
ParameterizedCommand cmd = ParameterizedCommand.generateCommand(command,parameters);
IEvaluationContext context = handlerService.getCurrentState();
context.addVariable(ExportToTextCommandHandler.TMF_EVENT_TABLE_HEADER_ID, header.toString());
context.addVariable(ExportToTextCommandHandler.TMF_EVENT_TABLE_PARAMETER_ID, TmfEventsTable.this);
handlerService.executeCommandInContext(cmd, null, context);
} catch (ExecutionException e) {
displayException(e);
} catch (NotDefinedException e) {
displayException(e);
} catch (NotEnabledException e) {
displayException(e);
} catch (NotHandledException e) {
displayException(e);
}
}
};
final IAction showSearchBarAction = new Action(Messages.TmfEventsTable_ShowSearchBarActionText) {
@Override
public void run() {
fHeaderState = HeaderState.SEARCH;
fTable.refresh();
}
};
final IAction showFilterBarAction = new Action(Messages.TmfEventsTable_ShowFilterBarActionText) {
@Override
public void run() {
fHeaderState = HeaderState.FILTER;
fTable.refresh();
}
};
final IAction clearFiltersAction = new Action(Messages.TmfEventsTable_ClearFiltersActionText) {
@Override
public void run() {
clearFilters();
}
};
class ToggleBookmarkAction extends Action {
Long fRank;
public ToggleBookmarkAction(final String text, final Long rank) {
super(text);
fRank = rank;
}
@Override
public void run() {
toggleBookmark(fRank);
}
}
final MenuManager tablePopupMenu = new MenuManager();
tablePopupMenu.setRemoveAllWhenShown(true);
tablePopupMenu.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(final IMenuManager manager) {
if (fTable.getSelectionIndex() == 0) {
// Right-click on header row
if (fHeaderState == HeaderState.FILTER) {
tablePopupMenu.add(showSearchBarAction);
} else {
tablePopupMenu.add(showFilterBarAction);
}
return;
}
final Point point = fTable.toControl(Display.getDefault().getCursorLocation());
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)) {
tablePopupMenu.add(new ToggleBookmarkAction(
Messages.TmfEventsTable_RemoveBookmarkActionText, rank));
} else {
tablePopupMenu.add(new ToggleBookmarkAction(
Messages.TmfEventsTable_AddBookmarkActionText, rank));
}
}
return;
}
}
// Right-click on table
if (fTable.isVisible() && fRawViewer.isVisible()) {
tablePopupMenu.add(hideTableAction);
tablePopupMenu.add(hideRawAction);
} else if (!fTable.isVisible()) {
tablePopupMenu.add(showTableAction);
} else if (!fRawViewer.isVisible()) {
tablePopupMenu.add(showRawAction);
}
tablePopupMenu.add(exportToTextAction);
tablePopupMenu.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) {
tablePopupMenu.add(openCallsiteAction);
separator = new Separator();
}
}
if (data instanceof ITmfModelLookup) {
ITmfModelLookup event = (ITmfModelLookup) data;
if (event.getModelUri() != null) {
tablePopupMenu.add(openModelAction);
separator = new Separator();
}
if (separator != null) {
tablePopupMenu.add(separator);
}
}
}
tablePopupMenu.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);
}
});
}
}
tablePopupMenu.add(subMenu);
}
appendToTablePopupMenu(tablePopupMenu, item);
}
});
final MenuManager rawViewerPopupMenu = new MenuManager();
rawViewerPopupMenu.setRemoveAllWhenShown(true);
rawViewerPopupMenu.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(final IMenuManager manager) {
if (fTable.isVisible() && fRawViewer.isVisible()) {
rawViewerPopupMenu.add(hideTableAction);
rawViewerPopupMenu.add(hideRawAction);
} else if (!fTable.isVisible()) {
rawViewerPopupMenu.add(showTableAction);
} else if (!fRawViewer.isVisible()) {
rawViewerPopupMenu.add(showRawAction);
}
appendToRawPopupMenu(tablePopupMenu);
}
});
Menu menu = tablePopupMenu.createContextMenu(fTable);
fTable.setMenu(menu);
menu = rawViewerPopupMenu.createContextMenu(fRawViewer);
fRawViewer.setMenu(menu);
}
/**
* 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() {
stopSearchThread();
stopFilterThread();
ColorSettingsManager.removeColorSettingsListener(this);
fComposite.dispose();
if ((fTrace != null) && fDisposeOnClose) {
fTrace.dispose();
}
fResourceManager.dispose();
fRawViewer.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
*
* FIXME: Add support for column selection
*/
protected void setColumnHeaders(final ColumnData[] columnData) {
fTable.setColumnHeaders(columnData);
}
/**
* 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) {
item.setText(getItemStrings(event));
item.setData(event);
item.setData(Key.TIMESTAMP, new TmfTimestamp(event.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);
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(event)) {
searchMatch = true;
} else {
searchNoMatch = true;
}
}
final ColorSetting colorSetting = ColorSettingsManager.getColorSetting(event);
if (searchNoMatch) {
item.setForeground(colorSetting.getDimmedForegroundColor());
item.setBackground(colorSetting.getDimmedBackgroundColor());
} else {
item.setForeground(colorSetting.getForegroundColor());
item.setBackground(colorSetting.getBackgroundColor());
}
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);
}
}
/**
* Set the item data of the header row.
*
* @param item
* The item to use as table header
*/
protected void setHeaderRowItemData(final TableItem item) {
String txtKey = null;
if (fHeaderState == HeaderState.SEARCH) {
item.setImage(SEARCH_IMAGE);
txtKey = Key.SEARCH_TXT;
} else if (fHeaderState == HeaderState.FILTER) {
item.setImage(FILTER_IMAGE);
txtKey = Key.FILTER_TXT;
}
item.setForeground(fGrayColor);
for (int i = 0; i < fTable.getColumns().length; i++) {
final TableColumn column = fTable.getColumns()[i];
final String filter = (String) column.getData(txtKey);
if (filter == null) {
if (fHeaderState == HeaderState.SEARCH) {
item.setText(i, SEARCH_HINT);
} else if (fHeaderState == HeaderState.FILTER) {
item.setText(i, FILTER_HINT);
}
item.setForeground(i, fGrayColor);
item.setFont(i, fTable.getFont());
} else {
item.setText(i, filter);
item.setForeground(i, fGreenColor);
item.setFont(i, fBoldFont);
}
}
}
/**
* 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 == 0) {
if ((fTrace == null) || (fFilterCheckCount == fTrace.getNbEvents())) {
item.setImage(FILTER_IMAGE);
} else {
item.setImage(STOP_IMAGE);
}
item.setText(0, fFilterMatchCount + "/" + fFilterCheckCount); //$NON-NLS-1$
} else {
item.setText(i, ""); //$NON-NLS-1$
}
}
item.setData(null);
item.setData(Key.TIMESTAMP, null);
item.setData(Key.RANK, null);
item.setForeground(null);
item.setBackground(null);
}
/**
* Create an editor for the header.
*/
protected 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)) {
// Icon selected
if (item.getImageBounds(0).contains(point)) {
if (fHeaderState == HeaderState.SEARCH) {
fHeaderState = HeaderState.FILTER;
} else if (fHeaderState == HeaderState.FILTER) {
fHeaderState = HeaderState.SEARCH;
}
fTable.setSelection(0);
fTable.refresh();
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];
String txtKey = null;
if (fHeaderState == HeaderState.SEARCH) {
txtKey = Key.SEARCH_TXT;
} else if (fHeaderState == HeaderState.FILTER) {
txtKey = Key.FILTER_TXT;
}
// 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(txtKey);
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();
// 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();
}
}
});
newEditor.selectAll();
newEditor.setFocus();
tableEditor.setEditor(newEditor, item, columnIndex);
}
}
/*
* returns true is value was changed
*/
private boolean updateHeader(final String text) {
String objKey = null;
String txtKey = null;
if (fHeaderState == HeaderState.SEARCH) {
objKey = Key.SEARCH_OBJ;
txtKey = Key.SEARCH_TXT;
} else if (fHeaderState == HeaderState.FILTER) {
objKey = Key.FILTER_OBJ;
txtKey = Key.FILTER_TXT;
}
if (text.trim().length() > 0) {
try {
final String regex = TmfFilterMatchesNode.regexFix(text);
Pattern.compile(regex);
if (regex.equals(column.getData(txtKey))) {
tableEditor.getEditor().dispose();
return false;
}
final TmfFilterMatchesNode filter = new TmfFilterMatchesNode(null);
String fieldId = (String) column.getData(Key.FIELD_ID);
if (fieldId == null) {
fieldId = column.getText();
}
filter.setField(fieldId);
filter.setRegex(regex);
column.setData(objKey, filter);
column.setData(txtKey, regex);
} catch (final PatternSyntaxException ex) {
tableEditor.getEditor().dispose();
MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
ex.getDescription(), ex.getMessage());
return false;
}
} else {
if (column.getData(txtKey) == null) {
tableEditor.getEditor().dispose();
return false;
}
column.setData(objKey, null);
column.setData(txtKey, null);
}
return true;
}
private void applyHeader() {
if (fHeaderState == HeaderState.SEARCH) {
stopSearchThread();
final TmfFilterAndNode filter = new TmfFilterAndNode(null);
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) {
fTable.setData(Key.SEARCH_OBJ, filter);
fTable.refresh();
searchNext();
fireSearchApplied(filter);
} else {
fTable.setData(Key.SEARCH_OBJ, null);
fTable.refresh();
fireSearchApplied(null);
}
} else if (fHeaderState == HeaderState.FILTER) {
final TmfFilterAndNode filter = new TmfFilterAndNode(null);
for (final TableColumn col : fTable.getColumns()) {
final Object filterObj = col.getData(Key.FILTER_OBJ);
if (filterObj instanceof ITmfFilterTreeNode) {
filter.addChild((ITmfFilterTreeNode) filterObj);
}
}
if (filter.getChildrenCount() > 0) {
applyFilter(filter);
} else {
clearFilters();
}
}
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) {
stopSearchThread();
for (final TableColumn column : fTable.getColumns()) {
column.setData(Key.SEARCH_OBJ, null);
column.setData(Key.SEARCH_TXT, null);
}
fTable.setData(Key.SEARCH_OBJ, null);
fTable.refresh();
fireSearchApplied(null);
} else if (fHeaderState == HeaderState.FILTER) {
clearFilters();
}
} else if (e.character == SWT.CR) {
if ((e.stateMask & SWT.SHIFT) == 0) {
searchNext();
} else {
searchPrevious();
}
}
}
});
}
/**
* 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.
*
* @param filter
* The filter to apply
* @since 1.1
*/
protected void applyFilter(ITmfFilter filter) {
stopFilterThread();
stopSearchThread();
fFilterMatchCount = 0;
fFilterCheckCount = 0;
fCache.applyFilter(filter);
fTable.clearAll();
fTable.setData(Key.FILTER_OBJ, filter);
fTable.setItemCount(3); // +1 for header row, +2 for top and bottom filter status rows
startFilterThread();
fireFilterApplied(filter);
}
/**
* Clear all currently active filters.
*/
protected void clearFilters() {
if (fTable.getData(Key.FILTER_OBJ) == null) {
return;
}
stopFilterThread();
stopSearchThread();
fCache.clearFilter();
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) {
fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
} else {
fTable.setItemCount(1); // +1 for header row
}
fFilterMatchCount = 0;
fFilterCheckCount = 0;
if (fSelectedRank >= 0) {
fTable.setSelection((int) fSelectedRank + 1); // +1 for header row
} else {
fTable.setSelection(0);
}
fireFilterApplied(null);
updateStatusLine(null);
}
/**
* Wrapper Thread object for the filtering thread.
*/
protected class FilterThread extends Thread {
private final ITmfFilterTreeNode filter;
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;
}
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;
}
if (filter.matches(event)) {
final long rank = fFilterCheckCount;
final int index = (int) fFilterMatchCount;
fFilterMatchCount++;
fCache.storeEvent(event, rank, index);
refreshTable();
} else if ((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;
}
fTable.setItemCount((int) fFilterMatchCount + 3); // +1 for header row, +2 for top and bottom filter status rows
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) {
startIndex = selectionIndex; // -1 for header row, +1 for next event
} else {
// header row is selected, start at top event
startIndex = Math.max(0, fTable.getTopIndex() - 1); // -1 for header row
}
final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
if (eventFilter != null) {
startIndex = Math.max(0, startIndex - 1); // -1 for top filter status row
}
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) {
startIndex = selectionIndex - 2; // -1 for header row, -1 for previous event
} else {
// header row is selected, start at precedent of top event
startIndex = fTable.getTopIndex() - 2; // -1 for header row, -1 for previous event
}
final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
if (eventFilter != null) {
startIndex = startIndex - 1; // -1 for top filter status row
}
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) {
if (fTrace == null) {
return Status.OK_STATUS;
}
final Display display = Display.getDefault();
if (startIndex < 0) {
rank = (int) fTrace.getNbEvents() - 1;
} else if (startIndex >= (fTable.getItemCount() - (eventFilter == null ? 1 : 3))) { // -1 for header row, -2 for top and bottom filter status rows
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 > (fTrace.getNbEvents() - 1)) {
rank = 0;
}
} else {
rank--;
if (rank < 0) {
rank = (int) fTrace.getNbEvents() - 1;
}
}
}
}
final int startRank = (int) rank;
boolean wrapped = false;
while (!monitor.isCanceled() && (foundRank == -1) && (fTrace != null)) {
int nbRequested = (direction == Direction.FORWARD ? Integer.MAX_VALUE : Math.min((int) rank + 1, fTrace.getCacheSize()));
if (direction == Direction.BACKWARD) {
rank = Math.max(0, rank - fTrace.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) fTrace).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) fTrace.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);
}
final int selection = index + 1 + (eventFilter != null ? +1 : 0); // +1 for header row, +1 for top filter status row
display.asyncExec(new Runnable() {
@Override
public void run() {
if (monitor.isCanceled()) {
return;
}
if (fTable.isDisposed()) {
return;
}
fTable.setSelection(selection);
fSelectedRank = foundRank;
fRawViewer.selectAndReveal(fSelectedRank);
if (foundTimestamp != null) {
broadcast(new TmfTimeSynchSignal(TmfEventsTable.this, foundTimestamp));
}
fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, getSelection()));
synchronized (fSearchSyncObj) {
fSearchThread = null;
}
updateStatusLine(null);
}
});
return Status.OK_STATUS;
}
@Override
protected void canceling() {
request.cancel();
synchronized (fSearchSyncObj) {
fSearchThread = null;
}
}
}
/**
* Create the resources.
*/
protected void createResources() {
fGrayColor = fResourceManager.createColor(ColorUtil.blend(fTable.getBackground().getRGB(), fTable
.getForeground().getRGB()));
fGreenColor = fTable.getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN);
fBoldFont = fResourceManager.createFont(FontDescriptor.createFrom(fTable.getFont()).setStyle(SWT.BOLD));
}
/**
* Pack the columns.
*/
protected void packColumns() {
if (fPackDone) {
return;
}
fTable.setRedraw(false);
boolean isLinux = System.getProperty("os.name").contains("Linux") ? true : false; //$NON-NLS-1$ //$NON-NLS-2$
TableColumn tableColumns[] = fTable.getColumns();
for (int i = 0; i < tableColumns.length; i++) {
final TableColumn column = tableColumns[i];
final int headerWidth = column.getWidth();
column.pack();
// Workaround for Linux which doesn't consider the image width of
// search/filter row in TableColumn.pack() after having executed
// TableItem.setImage((Image)null) for other rows than search/filter row.
if (isLinux && (i == 0)) {
column.setWidth(column.getWidth() + SEARCH_IMAGE.getBounds().width);
}
if (column.getWidth() < headerWidth) {
column.setWidth(headerWidth);
}
}
fTable.setRedraw(true);
fPackDone = true;
}
/**
* Get the contents of the row in the events table corresponding to an
* event. The order of the elements corresponds to the order of the columns.
*
* TODO Use column IDs, not indexes, so that the column order can be
* re-arranged.
*
* @param event
* The event printed in this row
* @return The event row entries
* @since 3.0
*/
public String[] getItemStrings(ITmfEvent event) {
if (event == null) {
return EMPTY_STRING_ARRAY;
}
return new String[] {
event.getTimestamp().toString(),
event.getSource(),
event.getType().getName(),
event.getReference(),
event.getContent().toString()
};
}
/**
* Notify this table that is got the UI focus.
*/
public void setFocus() {
fTable.setFocus();
}
/**
* 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;
fSelectedRank = 0;
fDisposeOnClose = disposeOnClose;
// Perform the updates on the UI thread
fTable.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
fTable.removeAll();
fCache.setTrace(fTrace); // Clear the cache
if (fTrace != null) {
if (!fTable.isDisposed() && (fTrace != null)) {
if (fTable.getData(Key.FILTER_OBJ) == null) {
fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
} else {
stopFilterThread();
fFilterMatchCount = 0;
fFilterCheckCount = 0;
fTable.setItemCount(3); // +1 for header row, +2 for top and bottom filter status rows
startFilterThread();
}
}
}
fRawViewer.setTrace(fTrace);
}
});
}
/**
* Assign the status line manager
*
* @param statusLineManager
* The status line manager, or null to disable status line messages
* @since 2.1
*/
public void setStatusLineManager(IStatusLineManager statusLineManager) {
if (fStatusLineManager != null && statusLineManager == null) {
fStatusLineManager.setMessage(""); //$NON-NLS-1$
}
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
if (!fTable.isDisposed()) {
fTable.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
// ------------------------------------------------------------------------
/**
* @since 2.0
*/
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.add(listener);
}
/**
* @since 2.0
*/
@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);
}
/**
* @since 2.0
*/
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.remove(listener);
}
/**
* @since 2.0
*/
@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
* @since 2.0
*/
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 = 0; i < fTable.getColumns().length; i++) {
if (i > 0) {
defaultMessage.append(", "); //$NON-NLS-1$
}
defaultMessage.append(tableItem.getText(i));
}
final InputDialog dialog = new MultiLineInputDialog(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
Messages.TmfEventsTable_AddBookmarkDialogTitle,
Messages.TmfEventsTable_AddBookmarkDialogMessage,
defaultMessage.toString());
if (dialog.open() == Window.OK) {
final String message = dialog.getValue();
try {
final IMarker bookmark = bookmarksFile.createMarker(IMarker.BOOKMARK);
if (bookmark.exists()) {
bookmark.setAttribute(IMarker.MESSAGE, message.toString());
final Long rank = (Long) tableItem.getData(Key.RANK);
final int location = rank.intValue();
bookmark.setAttribute(IMarker.LOCATION, Integer.valueOf(location));
fBookmarksMap.put(rank, bookmark.getId());
fTable.refresh();
}
} catch (final CoreException e) {
displayException(e);
}
}
}
}
}
/**
* Remove a bookmark from this event table.
*
* @param bookmark
* The bookmark to remove
*/
public void removeBookmark(final IMarker bookmark) {
for (final Entry<Long, Long> entry : fBookmarksMap.entries()) {
if (entry.getValue().equals(bookmark.getId())) {
fBookmarksMap.remove(entry.getKey(), entry.getValue());
fTable.refresh();
return;
}
}
}
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();
for (final IMarker bookmark : bookmarksFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO)) {
final int location = bookmark.getAttribute(IMarker.LOCATION, -1);
if (location != -1) {
final long rank = location;
fBookmarksMap.put(rank, bookmark.getId());
}
}
fTable.refresh();
} catch (final CoreException e) {
displayException(e);
}
}
@Override
public void gotoMarker(final IMarker marker) {
final int rank = marker.getAttribute(IMarker.LOCATION, -1);
if (rank != -1) {
int index = rank;
if (fTable.getData(Key.FILTER_OBJ) != null) {
index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
} else if (rank >= fTable.getItemCount()) {
fPendingGotoRank = rank;
}
fSelectedRank = rank;
fTable.setSelection(index + 1); // +1 for header row
updateStatusLine(null);
}
}
// ------------------------------------------------------------------------
// 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) {
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); // +1 for header row
fPendingGotoRank = -1;
updateStatusLine(null);
}
} else {
startFilterThread();
}
}
if (!fRawViewer.isDisposed() && (fTrace != null)) {
fRawViewer.refreshEventCount();
}
}
});
}
/**
* Handler for the time synch signal.
*
* @param signal
* The incoming signal
*/
@TmfSignalHandler
public void currentTimeUpdated(final TmfTimeSynchSignal signal) {
if ((signal.getSource() != this) && (fTrace != null) && (!fTable.isDisposed())) {
// Create a request for one event that will be queued after other ongoing requests. When this request is completed
// do the work to select the actual event with the timestamp specified in the signal. This procedure prevents
// the method fTrace.getRank() from interfering and delaying ongoing requests.
final TmfEventRequest subRequest = new TmfEventRequest(ITmfEvent.class,
TmfTimeRange.ETERNITY, 0, 1, ExecutionType.FOREGROUND) {
TmfTimestamp ts = new TmfTimestamp(signal.getBeginTime());
@Override
public void handleData(final ITmfEvent event) {
super.handleData(event);
}
@Override
public void handleCompleted() {
super.handleCompleted();
if (fTrace == null) {
return;
}
// Verify if the event is within the trace range and adjust if necessary
ITmfTimestamp timestamp = ts;
if (timestamp.compareTo(fTrace.getStartTime(), true) == -1) {
timestamp = fTrace.getStartTime();
}
if (timestamp.compareTo(fTrace.getEndTime(), true) == 1) {
timestamp = fTrace.getEndTime();
}
// Get the rank of the selected event in the table
final ITmfContext context = fTrace.seekEvent(timestamp);
final long rank = context.getRank();
context.dispose();
fSelectedRank = rank;
fTable.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
// Return if table is disposed
if (fTable.isDisposed()) {
return;
}
int index = (int) rank;
if (fTable.isDisposed()) {
return;
}
if (fTable.getData(Key.FILTER_OBJ) != null) {
index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
}
fTable.setSelection(index + 1); // +1 for header row
fRawViewer.selectAndReveal(rank);
updateStatusLine(null);
}
});
}
};
((ITmfEventProvider) fTrace).sendRequest(subRequest);
}
}
// ------------------------------------------------------------------------
// 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();
}
/**
* @since 2.0
*/
public void refresh() {
fCache.clear();
fTable.refresh();
fTable.redraw();
}
}