tmf: Add collapsible event table header bar with applied filter labels
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / viewers / events / TmfEventsTable.java
CommitLineData
db4721fb 1/*******************************************************************************
0a08e17d 2 * Copyright (c) 2010, 2016 Ericsson and others.
db4721fb
PT
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
baafe54c
AM
10 * Francois Chouinard - Initial API and implementation, replaced Table by TmfVirtualTable
11 * Patrick Tasse - Factored out from events view,
12 * Filter implementation (inspired by www.eclipse.org/mat)
ac44ef71 13 * Ansgar Radermacher - Support navigation to model URIs (Bug 396956)
f47ed727 14 * Bernd Hufmann - Updated call site and model URI implementation
baafe54c 15 * Alexandre Montplaisir - Update to new column API
346ffed7 16 * Matthew Khouzam - Add hide columns
db4721fb
PT
17 *******************************************************************************/
18
2bdf0193 19package org.eclipse.tracecompass.tmf.ui.viewers.events;
db4721fb 20
61bbd27d 21import java.io.File;
60fb38b8 22import java.io.FileNotFoundException;
9ba0a108 23import java.lang.reflect.InvocationTargetException;
db4721fb 24import java.util.ArrayList;
2db176a0 25import java.util.Collection;
db4721fb 26import java.util.HashMap;
baafe54c 27import java.util.LinkedList;
db4721fb 28import java.util.List;
db4721fb 29import java.util.Map.Entry;
110d44a1 30import java.util.regex.Matcher;
db4721fb
PT
31import java.util.regex.Pattern;
32import java.util.regex.PatternSyntaxException;
33
d3de0920
XR
34import org.eclipse.core.commands.Command;
35import org.eclipse.core.commands.ExecutionException;
36import org.eclipse.core.commands.NotEnabledException;
37import org.eclipse.core.commands.NotHandledException;
38import org.eclipse.core.commands.ParameterizedCommand;
39import org.eclipse.core.commands.common.NotDefinedException;
40import org.eclipse.core.expressions.IEvaluationContext;
61bbd27d
AM
41import org.eclipse.core.filesystem.EFS;
42import org.eclipse.core.filesystem.IFileStore;
db4721fb
PT
43import org.eclipse.core.resources.IFile;
44import org.eclipse.core.resources.IMarker;
45import org.eclipse.core.resources.IResource;
60fb38b8 46import org.eclipse.core.resources.IResourceVisitor;
7697e148 47import org.eclipse.core.resources.IWorkspaceRunnable;
ac44ef71 48import org.eclipse.core.resources.ResourcesPlugin;
db4721fb 49import org.eclipse.core.runtime.CoreException;
ac44ef71 50import org.eclipse.core.runtime.IPath;
db4721fb
PT
51import org.eclipse.core.runtime.IProgressMonitor;
52import org.eclipse.core.runtime.IStatus;
93bfd50a 53import org.eclipse.core.runtime.ListenerList;
ac44ef71 54import org.eclipse.core.runtime.Path;
db4721fb
PT
55import org.eclipse.core.runtime.Status;
56import org.eclipse.core.runtime.jobs.Job;
ac44ef71
AR
57import org.eclipse.emf.common.util.URI;
58import org.eclipse.emf.ecore.EValidator;
46671228 59import org.eclipse.jdt.annotation.NonNull;
db4721fb
PT
60import org.eclipse.jface.action.Action;
61import org.eclipse.jface.action.IAction;
62import org.eclipse.jface.action.IMenuListener;
63import org.eclipse.jface.action.IMenuManager;
3f43dc48 64import org.eclipse.jface.action.IStatusLineManager;
db4721fb
PT
65import org.eclipse.jface.action.MenuManager;
66import org.eclipse.jface.action.Separator;
db4721fb 67import org.eclipse.jface.dialogs.MessageDialog;
9ba0a108 68import org.eclipse.jface.operation.IRunnableWithProgress;
bb7c657f 69import org.eclipse.jface.resource.ColorRegistry;
812e7197 70import org.eclipse.jface.resource.FontRegistry;
db4721fb
PT
71import org.eclipse.jface.resource.JFaceResources;
72import org.eclipse.jface.resource.LocalResourceManager;
61bbd27d
AM
73import org.eclipse.jface.text.BadLocationException;
74import org.eclipse.jface.text.IDocument;
75import org.eclipse.jface.text.IRegion;
812e7197 76import org.eclipse.jface.util.IPropertyChangeListener;
ac44ef71 77import org.eclipse.jface.util.OpenStrategy;
812e7197 78import org.eclipse.jface.util.PropertyChangeEvent;
93bfd50a 79import org.eclipse.jface.util.SafeRunnable;
60fb38b8 80import org.eclipse.jface.viewers.ArrayContentProvider;
93bfd50a
PT
81import org.eclipse.jface.viewers.ISelection;
82import org.eclipse.jface.viewers.ISelectionChangedListener;
83import org.eclipse.jface.viewers.ISelectionProvider;
60fb38b8 84import org.eclipse.jface.viewers.LabelProvider;
93bfd50a
PT
85import org.eclipse.jface.viewers.SelectionChangedEvent;
86import org.eclipse.jface.viewers.StructuredSelection;
db4721fb 87import org.eclipse.jface.window.Window;
7697e148 88import org.eclipse.osgi.util.NLS;
db4721fb
PT
89import org.eclipse.swt.SWT;
90import org.eclipse.swt.custom.SashForm;
110d44a1 91import org.eclipse.swt.custom.StyleRange;
db4721fb 92import org.eclipse.swt.custom.TableEditor;
6817308e
PT
93import org.eclipse.swt.events.ControlAdapter;
94import org.eclipse.swt.events.ControlEvent;
db4721fb
PT
95import org.eclipse.swt.events.FocusAdapter;
96import org.eclipse.swt.events.FocusEvent;
97import org.eclipse.swt.events.KeyAdapter;
98import org.eclipse.swt.events.KeyEvent;
99import org.eclipse.swt.events.MouseAdapter;
100import org.eclipse.swt.events.MouseEvent;
101import org.eclipse.swt.events.SelectionAdapter;
102import org.eclipse.swt.events.SelectionEvent;
103import org.eclipse.swt.graphics.Color;
104import org.eclipse.swt.graphics.Font;
110d44a1 105import org.eclipse.swt.graphics.GC;
db4721fb
PT
106import org.eclipse.swt.graphics.Image;
107import org.eclipse.swt.graphics.Point;
108import org.eclipse.swt.graphics.Rectangle;
109import org.eclipse.swt.layout.FillLayout;
110import org.eclipse.swt.layout.GridData;
111import org.eclipse.swt.layout.GridLayout;
112import org.eclipse.swt.widgets.Composite;
113import org.eclipse.swt.widgets.Display;
114import org.eclipse.swt.widgets.Event;
115import org.eclipse.swt.widgets.Label;
116import org.eclipse.swt.widgets.Listener;
117import org.eclipse.swt.widgets.Menu;
118import org.eclipse.swt.widgets.MessageBox;
119import org.eclipse.swt.widgets.Shell;
120import org.eclipse.swt.widgets.TableColumn;
121import org.eclipse.swt.widgets.TableItem;
122import org.eclipse.swt.widgets.Text;
6cfc180e 123import org.eclipse.tracecompass.common.core.NonNullUtils;
2bdf0193
AM
124import org.eclipse.tracecompass.internal.tmf.core.filter.TmfCollapseFilter;
125import org.eclipse.tracecompass.internal.tmf.ui.Activator;
126import org.eclipse.tracecompass.internal.tmf.ui.Messages;
9ba0a108 127import org.eclipse.tracecompass.internal.tmf.ui.commands.CopyToClipboardOperation;
2bdf0193 128import org.eclipse.tracecompass.internal.tmf.ui.commands.ExportToTextCommandHandler;
7697e148 129import org.eclipse.tracecompass.internal.tmf.ui.dialogs.AddBookmarkDialog;
2bdf0193
AM
130import org.eclipse.tracecompass.tmf.core.component.ITmfEventProvider;
131import org.eclipse.tracecompass.tmf.core.component.TmfComponent;
132import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
9447c7ee 133import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
40dfafb3 134import org.eclipse.tracecompass.tmf.core.event.aspect.TmfContentFieldAspect;
2bdf0193
AM
135import org.eclipse.tracecompass.tmf.core.event.collapse.ITmfCollapsibleEvent;
136import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfCallsite;
137import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfModelLookup;
138import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfSourceLookup;
139import org.eclipse.tracecompass.tmf.core.filter.ITmfFilter;
140import org.eclipse.tracecompass.tmf.core.filter.model.ITmfFilterTreeNode;
2bdf0193
AM
141import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterMatchesNode;
142import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterNode;
0a08e17d
PT
143import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterObjectNode;
144import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterRootNode;
2bdf0193
AM
145import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType;
146import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
7697e148 147import org.eclipse.tracecompass.tmf.core.resources.ITmfMarker;
2bdf0193
AM
148import org.eclipse.tracecompass.tmf.core.signal.TmfEventFilterAppliedSignal;
149import org.eclipse.tracecompass.tmf.core.signal.TmfEventSearchAppliedSignal;
150import org.eclipse.tracecompass.tmf.core.signal.TmfEventSelectedSignal;
97c71024 151import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
bb7c657f 152import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
2bdf0193
AM
153import org.eclipse.tracecompass.tmf.core.signal.TmfTraceUpdatedSignal;
154import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
7697e148 155import org.eclipse.tracecompass.tmf.core.timestamp.TmfNanoTimestamp;
2bdf0193
AM
156import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
157import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
158import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
159import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
b04903a2 160import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
2bdf0193
AM
161import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
162import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
8b6aedef 163import org.eclipse.tracecompass.tmf.core.util.Pair;
2bdf0193 164import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsCache.CachedEvent;
0a08e17d 165import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsTableHeader.IEventsTableHeaderListener;
2bdf0193 166import org.eclipse.tracecompass.tmf.ui.viewers.events.columns.TmfEventTableColumn;
2bdf0193
AM
167import org.eclipse.tracecompass.tmf.ui.views.colors.ColorSetting;
168import org.eclipse.tracecompass.tmf.ui.views.colors.ColorSettingsManager;
169import org.eclipse.tracecompass.tmf.ui.views.colors.IColorSettingsListener;
170import org.eclipse.tracecompass.tmf.ui.views.filter.FilterManager;
171import org.eclipse.tracecompass.tmf.ui.widgets.rawviewer.TmfRawEventViewer;
172import org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.TmfVirtualTable;
61bbd27d 173import org.eclipse.ui.IEditorPart;
8b6aedef 174import org.eclipse.ui.IEditorSite;
ac44ef71 175import org.eclipse.ui.IWorkbenchPage;
0bc16991 176import org.eclipse.ui.IWorkbenchPartSite;
db4721fb 177import org.eclipse.ui.PlatformUI;
d3de0920 178import org.eclipse.ui.commands.ICommandService;
60fb38b8 179import org.eclipse.ui.dialogs.ListDialog;
d3de0920 180import org.eclipse.ui.handlers.IHandlerService;
ac44ef71 181import org.eclipse.ui.ide.IDE;
db4721fb 182import org.eclipse.ui.ide.IGotoMarker;
61bbd27d 183import org.eclipse.ui.texteditor.ITextEditor;
db4721fb 184import org.eclipse.ui.themes.ColorUtil;
812e7197 185import org.eclipse.ui.themes.IThemeManager;
db4721fb 186
2db176a0
PT
187import com.google.common.base.Joiner;
188import com.google.common.collect.HashMultimap;
baafe54c 189import com.google.common.collect.ImmutableList;
2db176a0
PT
190import com.google.common.collect.Multimap;
191
db4721fb
PT
192/**
193 * The generic TMF Events table
194 *
195 * This is a view that will list events that are read from a trace.
196 *
db4721fb
PT
197 * @author Francois Chouinard
198 * @author Patrick Tasse
199 */
812e7197 200public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorSettingsListener, ISelectionProvider, IPropertyChangeListener {
db4721fb 201
cf37ad9f
AM
202 /**
203 * Empty string array, used by {@link #getItemStrings}.
cf37ad9f 204 */
4c4e2816 205 protected static final String @NonNull [] EMPTY_STRING_ARRAY = new String[0];
cf37ad9f 206
baafe54c
AM
207 /**
208 * Empty string
baafe54c
AM
209 */
210 protected static final @NonNull String EMPTY_STRING = ""; //$NON-NLS-1$
211
03142470
BH
212 private static final boolean IS_LINUX = System.getProperty("os.name").contains("Linux") ? true : false; //$NON-NLS-1$ //$NON-NLS-2$
213
812e7197 214 private static final String FONT_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.font.eventtable"; //$NON-NLS-1$
bb7c657f 215 private static final String HIGHLIGHT_COLOR_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.color.eventtable.highlight"; //$NON-NLS-1$
812e7197 216
db4721fb
PT
217 private static final Image BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath(
218 "icons/elcl16/bookmark_obj.gif"); //$NON-NLS-1$
219 private static final Image SEARCH_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/search.gif"); //$NON-NLS-1$
220 private static final Image SEARCH_MATCH_IMAGE = Activator.getDefault().getImageFromPath(
221 "icons/elcl16/search_match.gif"); //$NON-NLS-1$
222 private static final Image SEARCH_MATCH_BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath(
223 "icons/elcl16/search_match_bookmark.gif"); //$NON-NLS-1$
8b7eb3cd 224 private static final Image FILTER_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/filter_items.gif"); //$NON-NLS-1$
0a08e17d 225 private static final Image FILTER_ADD_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/filter_add.gif"); //$NON-NLS-1$
db4721fb
PT
226 private static final Image STOP_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/stop.gif"); //$NON-NLS-1$
227 private static final String SEARCH_HINT = Messages.TmfEventsTable_SearchHint;
db4721fb
PT
228 private static final int MAX_CACHE_SIZE = 1000;
229
03142470
BH
230 private static final int MARGIN_COLUMN_INDEX = 0;
231 private static final int FILTER_SUMMARY_INDEX = 1;
232 private static final int EVENT_COLUMNS_START_INDEX = MARGIN_COLUMN_INDEX + 1;
233
6a0e6f00
MK
234 private final class ColumnMovedListener extends ControlAdapter {
235 /*
236 * Make sure that the margin column is always first and keep the
237 * column order variable up to date.
238 */
239 @Override
240 public void controlMoved(ControlEvent e) {
241 int[] order = fTable.getColumnOrder();
242 if (order[0] == MARGIN_COLUMN_INDEX) {
243 fColumnOrder = order;
244 return;
245 }
246 for (int i = order.length - 1; i > 0; i--) {
247 if (order[i] == MARGIN_COLUMN_INDEX) {
248 order[i] = order[i - 1];
249 order[i - 1] = MARGIN_COLUMN_INDEX;
250 }
251 }
252 fTable.setColumnOrder(order);
253 fColumnOrder = fTable.getColumnOrder();
254 }
255 }
256
257 private final class TableSelectionListener extends SelectionAdapter {
258 @Override
259 public void widgetSelected(final SelectionEvent e) {
260 if (e.item == null) {
261 return;
262 }
263 updateStatusLine(null);
264 if (fTable.getSelectionIndices().length > 0) {
265 if (e.item.getData(Key.RANK) instanceof Long) {
266 fSelectedRank = (Long) e.item.getData(Key.RANK);
267 fRawViewer.selectAndReveal((Long) e.item.getData(Key.RANK));
268 } else {
269 fSelectedRank = -1;
270 }
271 if (fTable.getSelectionIndices().length == 1) {
272 fSelectedBeginRank = fSelectedRank;
273 }
274 if (e.item.getData(Key.TIMESTAMP) instanceof ITmfTimestamp) {
275 final ITmfTimestamp ts = NonNullUtils.checkNotNull((ITmfTimestamp) e.item.getData(Key.TIMESTAMP));
276 if (fTable.getSelectionIndices().length == 1) {
277 fSelectedBeginTimestamp = ts;
278 }
279 ITmfTimestamp selectedBeginTimestamp = fSelectedBeginTimestamp;
280 if (selectedBeginTimestamp != null) {
281 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, selectedBeginTimestamp, ts));
282 if (fTable.getSelectionIndices().length == 2) {
283 updateStatusLine(ts.getDelta(selectedBeginTimestamp));
284 }
285 }
286 } else {
287 if (fTable.getSelectionIndices().length == 1) {
288 fSelectedBeginTimestamp = null;
289 }
290 }
291 }
292 if (e.item.getData() instanceof ITmfEvent) {
293 broadcast(new TmfEventSelectedSignal(TmfEventsTable.this, (ITmfEvent) e.item.getData()));
294 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, new StructuredSelection(e.item.getData())));
295 } else {
296 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, StructuredSelection.EMPTY));
297 }
298 }
299 }
300
301 private final class MouseDoubleClickListener extends MouseAdapter {
302 @Override
303 public void mouseDoubleClick(final MouseEvent event) {
304 if (event.button != 1) {
305 return;
306 }
307 // Identify the selected row
308 final Point point = new Point(event.x, event.y);
309 final TableItem item = fTable.getItem(point);
310 if (item != null) {
311 final Rectangle imageBounds = item.getImageBounds(0);
312 imageBounds.width = BOOKMARK_IMAGE.getBounds().width;
313 if (imageBounds.contains(point)) {
314 final Long rank = (Long) item.getData(Key.RANK);
315 if (rank != null) {
316 toggleBookmark(rank);
317 }
318 }
319 }
320 }
321 }
322
323 private final class RawSelectionListener implements Listener {
324 @Override
325 public void handleEvent(final Event e) {
326 if (fTrace == null) {
327 return;
328 }
329 long rank;
330 if (e.data instanceof Long) {
331 rank = (Long) e.data;
332 } else if (e.data instanceof ITmfLocation) {
333 rank = findRank((ITmfLocation) e.data);
334 } else {
335 return;
336 }
337 int index = (int) rank;
338 if (fTable.getData(Key.FILTER_OBJ) != null) {
339 // +1 for top filter status row
340 index = fCache.getFilteredEventIndex(rank) + 1;
341 }
342 // +1 for header row
343 fTable.setSelection(index + 1);
344 fSelectedRank = rank;
345 fSelectedBeginRank = fSelectedRank;
346 updateStatusLine(null);
347 final TableItem[] selection = fTable.getSelection();
348 if ((selection != null) && (selection.length > 0)) {
349 TableItem item = fTable.getSelection()[0];
350 final TmfTimestamp ts = (TmfTimestamp) item.getData(Key.TIMESTAMP);
351 if (ts != null) {
352 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, ts));
353 }
354 if (item.getData() instanceof ITmfEvent) {
355 broadcast(new TmfEventSelectedSignal(TmfEventsTable.this, (ITmfEvent) item.getData()));
356 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, new StructuredSelection(item.getData())));
357 } else {
358 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, StructuredSelection.EMPTY));
359 }
360 }
361 }
362
363 private long findRank(final ITmfLocation selectedLocation) {
364 final double selectedRatio = fTrace.getLocationRatio(selectedLocation);
365 long low = 0;
366 long high = fTrace.getNbEvents();
367 long rank = high / 2;
368 double ratio = -1;
369 while (ratio != selectedRatio) {
370 ITmfContext context = fTrace.seekEvent(rank);
371 ratio = fTrace.getLocationRatio(context.getLocation());
372 context.dispose();
373 if (ratio < selectedRatio) {
374 low = rank;
375 rank = (rank + high) / 2;
376 } else if (ratio > selectedRatio) {
377 high = rank;
378 rank = (rank + low) / 2;
379 }
380 if ((high - low) < 2) {
381 break;
382 }
383 }
384 return rank;
385 }
386 }
387
388 private final class SetDataListener implements Listener {
389 @Override
390 public void handleEvent(final Event event) {
391
392 final TableItem item = (TableItem) event.item;
393 int index = event.index - 1; // -1 for the header row
394
395 if (event.index == 0) {
396 setHeaderRowItemData(item);
397 return;
398 }
399
400 if (fTable.getData(Key.FILTER_OBJ) != null) {
401 if ((event.index == 1) || (event.index == (fTable.getItemCount() - 1))) {
402 setFilterStatusRowItemData(item);
403 return;
404 }
405 /* -1 for top filter status row */
406 index = index - 1;
407 }
408
409 final CachedEvent cachedEvent = fCache.getEvent(index);
410 if (cachedEvent != null) {
411 setItemData(item, cachedEvent, cachedEvent.rank);
412 return;
413 }
414
415 // Else, fill the cache asynchronously (and off the UI thread)
416 event.doit = false;
417 }
418 }
419
658e0268 420 private static final class PainItemListener implements Listener {
6a0e6f00
MK
421 @Override
422 public void handleEvent(Event event) {
423 TableItem item = (TableItem) event.item;
424
425 // we promised to paint the table item's foreground
426 GC gc = event.gc;
427 Image image = item.getImage(event.index);
428 if (image != null) {
429 Rectangle imageBounds = item.getImageBounds(event.index);
430 /*
431 * The image bounds don't match the default image position.
432 */
433 if (IS_LINUX) {
434 gc.drawImage(image, imageBounds.x + 1, imageBounds.y + 3);
435 } else {
436 gc.drawImage(image, imageBounds.x, imageBounds.y + 1);
437 }
438 }
439 gc.setForeground(item.getForeground(event.index));
440 gc.setFont(item.getFont(event.index));
441 String text = item.getText(event.index);
442 Rectangle textBounds = item.getTextBounds(event.index);
443 /*
444 * The text bounds don't match the default text position.
445 */
446 if (IS_LINUX) {
447 gc.drawText(text, textBounds.x + 1, textBounds.y + 3, true);
448 } else {
449 gc.drawText(text, textBounds.x - 1, textBounds.y + 2, true);
450 }
451 }
452 }
453
658e0268 454 private static final class EraseItemListener implements Listener {
6a0e6f00
MK
455 @Override
456 public void handleEvent(Event event) {
457 TableItem item = (TableItem) event.item;
458 List<?> styleRanges = (List<?>) item.getData(Key.STYLE_RANGES);
459
460 GC gc = event.gc;
461 Color background = item.getBackground(event.index);
462 /*
463 * Paint the background if it is not the default system color.
464 * In Windows, if you let the widget draw the background, it
465 * will not show the item's background color if the item is
466 * selected or hot. If there are no style ranges and the item
467 * background is the default system color, we do not want to
468 * paint it or otherwise we would override the platform theme
469 * (e.g. alternating colors).
470 */
471 if (styleRanges != null || !background.equals(item.getParent().getBackground())) {
472 // we will paint the table item's background
473 event.detail &= ~SWT.BACKGROUND;
474
475 // paint the item's default background
476 gc.setBackground(background);
477 gc.fillRectangle(event.x, event.y, event.width, event.height);
478 }
479
480 /*
481 * We will paint the table item's foreground. In Windows, if you
482 * paint the background but let the widget draw the foreground,
483 * it will override your background, unless the item is selected
484 * or hot.
485 */
486 event.detail &= ~SWT.FOREGROUND;
487
488 // paint the highlighted background for all style ranges
489 if (styleRanges != null) {
490 Rectangle textBounds = item.getTextBounds(event.index);
491 String text = item.getText(event.index);
492 for (Object o : styleRanges) {
493 if (o instanceof StyleRange) {
494 StyleRange styleRange = (StyleRange) o;
495 if (styleRange.data.equals(event.index)) {
496 int startIndex = styleRange.start;
497 int endIndex = startIndex + styleRange.length;
498 int startX = gc.textExtent(text.substring(0, startIndex)).x;
499 int endX = gc.textExtent(text.substring(0, endIndex)).x;
500 gc.setBackground(styleRange.background);
501 gc.fillRectangle(textBounds.x + startX, textBounds.y, (endX - startX), textBounds.height);
502 }
503 }
504 }
505 }
506 }
507 }
508
509 private final class TooltipListener implements Listener {
510 Shell tooltipShell = null;
511
512 @Override
513 public void handleEvent(final Event event) {
514 switch (event.type) {
515 case SWT.MouseHover:
516 final TableItem item = fTable.getItem(new Point(event.x, event.y));
517 if (item == null) {
518 return;
519 }
0a08e17d
PT
520 String text;
521 if (fTable.indexOf(item) == 0) {
522 if (fHeaderState == HeaderState.SEARCH && item.getBounds(0).contains(event.x, event.y)) {
523 text = Messages.TmfEventsTable_AddAsFilterText;
524 } else {
525 return;
526 }
527 } else {
528 final Long rank = (Long) item.getData(Key.RANK);
529 if (rank == null) {
530 return;
531 }
532 final String tooltipText = (String) item.getData(Key.BOOKMARK);
533 final Rectangle bounds = item.getImageBounds(0);
534 bounds.width = BOOKMARK_IMAGE.getBounds().width;
535 if (!bounds.contains(event.x, event.y)) {
536 return;
537 }
538 text = rank.toString() + (tooltipText != null ? ": " + tooltipText : EMPTY_STRING); //$NON-NLS-1$
6a0e6f00
MK
539 }
540 if ((tooltipShell != null) && !tooltipShell.isDisposed()) {
541 tooltipShell.dispose();
542 }
543 tooltipShell = new Shell(fTable.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
544 tooltipShell.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
545 final FillLayout layout = new FillLayout();
546 layout.marginWidth = 2;
547 tooltipShell.setLayout(layout);
548 final Label label = new Label(tooltipShell, SWT.WRAP);
6a0e6f00
MK
549 label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
550 label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
551 label.setText(text);
552 label.addListener(SWT.MouseExit, this);
553 label.addListener(SWT.MouseDown, this);
554 label.addListener(SWT.MouseWheel, this);
555 final Point size = tooltipShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
556 /*
557 * Bug in Linux. The coordinates of the event have an origin
558 * that excludes the table header but the method toDisplay()
559 * expects coordinates relative to an origin that includes
560 * the table header.
561 */
562 int y = event.y;
563 if (IS_LINUX) {
564 y += fTable.getHeaderHeight();
565 }
566 Point pt = fTable.toDisplay(event.x, y);
567 pt.x += BOOKMARK_IMAGE.getBounds().width;
568 pt.y += item.getBounds().height;
569 tooltipShell.setBounds(pt.x, pt.y, size.x, size.y);
570 tooltipShell.setVisible(true);
571 break;
572 case SWT.Dispose:
573 case SWT.KeyDown:
574 case SWT.MouseMove:
575 case SWT.MouseExit:
576 case SWT.MouseDown:
577 case SWT.MouseWheel:
578 if (tooltipShell != null) {
579 tooltipShell.dispose();
580 tooltipShell = null;
581 }
582 break;
583 default:
584 break;
585 }
586 }
587 }
588
db4721fb 589 /**
346ffed7 590 * The events table search/filter/data keys
db4721fb 591 *
db4721fb 592 * @author Patrick Tasse
34c87ae8
AM
593 * @noimplement This interface only contains Event Table specific
594 * static definitions.
db4721fb
PT
595 */
596 public interface Key {
34c87ae8 597
db4721fb
PT
598 /** Search text */
599 String SEARCH_TXT = "$srch_txt"; //$NON-NLS-1$
600
601 /** Search object */
602 String SEARCH_OBJ = "$srch_obj"; //$NON-NLS-1$
603
604 /** Filter text */
605 String FILTER_TXT = "$fltr_txt"; //$NON-NLS-1$
606
607 /** Filter object */
608 String FILTER_OBJ = "$fltr_obj"; //$NON-NLS-1$
609
dadf6e1a 610 /** Timestamp */
db4721fb
PT
611 String TIMESTAMP = "$time"; //$NON-NLS-1$
612
613 /** Rank */
614 String RANK = "$rank"; //$NON-NLS-1$
615
db4721fb
PT
616 /** Bookmark indicator */
617 String BOOKMARK = "$bookmark"; //$NON-NLS-1$
c409f16b
AM
618
619 /** Event aspect represented by this column */
620 String ASPECT = "$aspect"; //$NON-NLS-1$
110d44a1 621
8b7eb3cd
MK
622 /**
623 * Table item list of style ranges
624 *
625 * @since 1.0
626 */
110d44a1 627 String STYLE_RANGES = "$style_ranges"; //$NON-NLS-1$
346ffed7
MK
628
629 /**
630 * The width of a table item
631 *
0336f981 632 * @since 1.1
346ffed7
MK
633 */
634 String WIDTH = "$width"; //$NON-NLS-1$
db4721fb
PT
635 }
636
637 /**
638 * The events table search/filter state
639 *
640 * @version 1.0
641 * @author Patrick Tasse
642 */
643 public static enum HeaderState {
0a08e17d
PT
644 /** No search filter is applied
645 * @since 2.0*/
646 NO_SEARCH,
db4721fb 647
0a08e17d
PT
648 /** A search filter is applied */
649 SEARCH
db4721fb
PT
650 }
651
652 interface Direction {
653 int FORWARD = +1;
654 int BACKWARD = -1;
655 }
656
657 // ------------------------------------------------------------------------
658 // Table data
659 // ------------------------------------------------------------------------
660
0a08e17d
PT
661 /** The header bar */
662 private TmfEventsTableHeader fHeaderBar;
663
a0a88f65 664 /** The virtual event table */
db4721fb 665 protected TmfVirtualTable fTable;
a0a88f65
AM
666
667 private Composite fComposite;
668 private SashForm fSashForm;
669 private TmfRawEventViewer fRawViewer;
670 private ITmfTrace fTrace;
574f43ad 671 private volatile boolean fPackDone = false;
0a08e17d 672 private HeaderState fHeaderState = HeaderState.NO_SEARCH;
9ba0a108
PT
673 private long fSelectedRank = -1;
674 private long fSelectedBeginRank = -1;
3f43dc48
PT
675 private ITmfTimestamp fSelectedBeginTimestamp = null;
676 private IStatusLineManager fStatusLineManager = null;
db4721fb
PT
677
678 // Filter data
a0a88f65
AM
679 private long fFilterMatchCount;
680 private long fFilterCheckCount;
681 private FilterThread fFilterThread;
682 private boolean fFilterThreadResume = false;
683 private final Object fFilterSyncObj = new Object();
684 private SearchThread fSearchThread;
685 private final Object fSearchSyncObj = new Object();
0a08e17d 686 private boolean fCollapseFilterEnabled = false;
db4721fb 687
93bfd50a 688 /**
8b7eb3cd
MK
689 * List of selection change listeners (element type:
690 * <code>ISelectionChangedListener</code>).
93bfd50a
PT
691 *
692 * @see #fireSelectionChanged
693 */
694 private ListenerList selectionChangedListeners = new ListenerList();
695
db4721fb 696 // Bookmark map <Rank, MarkerId>
2db176a0 697 private Multimap<Long, Long> fBookmarksMap = HashMultimap.create();
a0a88f65
AM
698 private IFile fBookmarksFile;
699 private long fPendingGotoRank = -1;
db4721fb
PT
700
701 // SWT resources
a0a88f65
AM
702 private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
703 private Color fGrayColor;
704 private Color fGreenColor;
bb7c657f 705 private Color fHighlightColor;
812e7197 706 private Font fFont;
a0a88f65 707 private Font fBoldFont;
db4721fb 708
baafe54c 709 private final List<TmfEventTableColumn> fColumns = new LinkedList<>();
db4721fb
PT
710
711 // Event cache
712 private final TmfEventsCache fCache;
713 private boolean fCacheUpdateBusy = false;
714 private boolean fCacheUpdatePending = false;
715 private boolean fCacheUpdateCompleted = false;
716 private final Object fCacheUpdateSyncObj = new Object();
717
14665360
PT
718 // Keep track of column order, it is needed after table is disposed
719 private int[] fColumnOrder;
720
db4721fb
PT
721 private boolean fDisposeOnClose;
722
346ffed7
MK
723 private Menu fHeaderMenu;
724
725 private Menu fTablePopup;
726
727 private Menu fRawTablePopup;
728
665990bb 729 private Point fLastMenuCursorLocation;
3bccf3b1
MAL
730 private MenuManager fRawViewerPopupMenuManager;
731 private MenuManager fTablePopupMenuManager;
732 private MenuManager fHeaderPopupMenuManager;
733
db4721fb 734 // ------------------------------------------------------------------------
a0a88f65 735 // Constructors
db4721fb
PT
736 // ------------------------------------------------------------------------
737
738 /**
baafe54c 739 * Basic constructor, using the default set of columns
db4721fb
PT
740 *
741 * @param parent
742 * The parent composite UI object
743 * @param cacheSize
744 * The size of the event table cache
745 */
746 public TmfEventsTable(final Composite parent, final int cacheSize) {
b04903a2 747 this(parent, cacheSize, TmfTrace.BASE_ASPECTS);
db4721fb
PT
748 }
749
750 /**
baafe54c 751 * Legacy constructor, using ColumnData to define columns
db4721fb
PT
752 *
753 * @param parent
754 * The parent composite UI object
755 * @param cacheSize
756 * The size of the event table cache
757 * @param columnData
40dfafb3 758 * The column data array
baafe54c
AM
759 * @deprecated Deprecated constructor, use
760 * {@link #TmfEventsTable(Composite, int, Collection)}
761 */
762 @Deprecated
763 public TmfEventsTable(final Composite parent, int cacheSize,
2bdf0193 764 final org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) {
baafe54c
AM
765 /*
766 * We'll do a "best-effort" to keep trace types still using this API to
8b7eb3cd
MK
767 * keep working, by defining a TmfEventTableColumn for each ColumnData
768 * they passed.
baafe54c
AM
769 */
770 this(parent, cacheSize, convertFromColumnData(columnData));
771 }
772
773 @Deprecated
8192209b 774 private static @NonNull Iterable<ITmfEventAspect> convertFromColumnData(
2bdf0193 775 org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) {
baafe54c 776
b04903a2 777 ImmutableList.Builder<ITmfEventAspect> builder = new ImmutableList.Builder<>();
2bdf0193 778 for (org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData col : columnData) {
9447c7ee
AM
779 String fieldName = col.header;
780 if (fieldName != null) {
40dfafb3 781 builder.add(new TmfContentFieldAspect(fieldName, fieldName));
baafe54c
AM
782 }
783 }
0e4f957e 784 return builder.build();
baafe54c
AM
785 }
786
787 /**
788 * Standard constructor, where we define which columns to use.
789 *
790 * @param parent
791 * The parent composite UI object
792 * @param cacheSize
793 * The size of the event table cache
b04903a2
AM
794 * @param aspects
795 * The event aspects to display in this table. One column per
796 * aspect will be created.
baafe54c
AM
797 * <p>
798 * The iteration order of this collection will correspond to the
b04903a2 799 * initial ordering of the columns in the table.
baafe54c 800 * </p>
db4721fb 801 */
baafe54c 802 public TmfEventsTable(final Composite parent, int cacheSize,
8192209b 803 @NonNull Iterable<ITmfEventAspect> aspects) {
db4721fb
PT
804 super("TmfEventsTable"); //$NON-NLS-1$
805
806 fComposite = new Composite(parent, SWT.NONE);
0a08e17d 807 GridLayout gl = new GridLayout(1, false);
db4721fb
PT
808 gl.marginHeight = 0;
809 gl.marginWidth = 0;
810 gl.verticalSpacing = 0;
811 fComposite.setLayout(gl);
812
813 fSashForm = new SashForm(fComposite, SWT.HORIZONTAL);
814 fSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
815
0a08e17d
PT
816 // Create a composite for the table and its header bar
817 Composite tableComposite = new Composite(fSashForm, SWT.NONE);
818 tableComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
819 gl = new GridLayout(1, false);
820 gl.marginHeight = 0;
821 gl.marginWidth = 0;
822 gl.verticalSpacing = 0;
823 tableComposite.setLayout(gl);
824
825 // Create an events table header bar
826 fHeaderBar = new TmfEventsTableHeader(tableComposite, SWT.NONE, new IEventsTableHeaderListener() {
827 @Override
828 public void filterSelected(ITmfFilter filter) {
829 if (filter instanceof TmfFilterMatchesNode) {
830 TmfFilterMatchesNode matchFilter = (TmfFilterMatchesNode) filter;
831 for (TableColumn col : fTable.getColumns()) {
832 if (col.getData(Key.ASPECT) == matchFilter.getEventAspect()) {
833 col.setData(Key.FILTER_TXT, matchFilter.getRegex());
834 } else {
835 col.setData(Key.FILTER_TXT, null);
836 }
837 }
838 fTable.refresh();
839 fTable.redraw();
840 }
841 }
842
843 @Override
844 public void filterRemoved(ITmfFilter filter) {
845 for (TableColumn col : fTable.getColumns()) {
846 col.setData(Key.FILTER_TXT, null);
847 }
848 removeFilter(filter);
849 }
850 });
851
db4721fb 852 // Create a virtual table
3f43dc48 853 final int style = SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION;
0a08e17d 854 fTable = new TmfVirtualTable(tableComposite, style);
db4721fb
PT
855
856 // Set the table layout
857 final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
858 fTable.setLayoutData(layoutData);
859
860 // Some cosmetic enhancements
861 fTable.setHeaderVisible(true);
862 fTable.setLinesVisible(true);
863
baafe54c 864 // Setup the columns
8192209b
AM
865 for (ITmfEventAspect aspect : aspects) {
866 if (aspect != null) {
867 fColumns.add(new TmfEventTableColumn(aspect));
b04903a2 868 }
db4721fb
PT
869 }
870
03142470
BH
871 TmfMarginColumn collapseCol = new TmfMarginColumn();
872 fColumns.add(MARGIN_COLUMN_INDEX, collapseCol);
873
346ffed7 874 fHeaderMenu = new Menu(fTable);
baafe54c 875 // Create the UI columns in the table
2fcd5eea
AM
876 for (TmfEventTableColumn col : fColumns) {
877 TableColumn column = fTable.newTableColumn(SWT.LEFT);
878 column.setText(col.getHeaderName());
879 column.setToolTipText(col.getHeaderTooltip());
c409f16b 880 column.setData(Key.ASPECT, col.getEventAspect());
2fcd5eea 881 column.pack();
03142470
BH
882 if (col instanceof TmfMarginColumn) {
883 column.setResizable(false);
6817308e
PT
884 } else {
885 column.setMoveable(true);
be42703d 886 column.setData(Key.WIDTH, -1);
03142470 887 }
6a0e6f00 888 column.addControlListener(new ColumnMovedListener());
2fcd5eea 889 }
14665360 890 fColumnOrder = fTable.getColumnOrder();
baafe54c 891
db4721fb
PT
892 // Set the frozen row for header row
893 fTable.setFrozenRowCount(1);
894
895 // Create the header row cell editor
896 createHeaderEditor();
897
898 // Handle the table item selection
6a0e6f00 899 fTable.addSelectionListener(new TableSelectionListener());
db4721fb 900
41b5c37f
AM
901 int realCacheSize = Math.max(cacheSize, Display.getDefault().getBounds().height / fTable.getItemHeight());
902 realCacheSize = Math.min(realCacheSize, MAX_CACHE_SIZE);
903 fCache = new TmfEventsCache(realCacheSize, this);
db4721fb
PT
904
905 // Handle the table item requests
6a0e6f00 906 fTable.addListener(SWT.SetData, new SetDataListener());
db4721fb 907
346ffed7
MK
908 fTable.addListener(SWT.MenuDetect, new Listener() {
909 @Override
910 public void handleEvent(Event event) {
665990bb
MAL
911 fLastMenuCursorLocation = new Point(event.x, event.y);
912 Point pt = fTable.getDisplay().map(null, fTable, fLastMenuCursorLocation);
346ffed7
MK
913 Rectangle clientArea = fTable.getClientArea();
914 boolean header = clientArea.y <= pt.y && pt.y < (clientArea.y + fTable.getHeaderHeight());
915 fTable.setMenu(header ? fHeaderMenu : fTablePopup);
916 }
917 });
918
6a0e6f00 919 fTable.addMouseListener(new MouseDoubleClickListener());
db4721fb 920
6a0e6f00 921 final Listener tooltipListener = new TooltipListener();
db4721fb
PT
922
923 fTable.addListener(SWT.MouseHover, tooltipListener);
924 fTable.addListener(SWT.Dispose, tooltipListener);
925 fTable.addListener(SWT.KeyDown, tooltipListener);
926 fTable.addListener(SWT.MouseMove, tooltipListener);
927 fTable.addListener(SWT.MouseExit, tooltipListener);
928 fTable.addListener(SWT.MouseDown, tooltipListener);
929 fTable.addListener(SWT.MouseWheel, tooltipListener);
930
6a0e6f00 931 fTable.addListener(SWT.EraseItem, new EraseItemListener());
110d44a1 932
6a0e6f00 933 fTable.addListener(SWT.PaintItem, new PainItemListener());
110d44a1 934
db4721fb
PT
935 // Create resources
936 createResources();
937
812e7197 938 initializeFonts();
bb7c657f 939 initializeColors();
812e7197
PT
940 PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(this);
941
db4721fb
PT
942 ColorSettingsManager.addColorSettingsListener(this);
943
944 fTable.setItemCount(1); // +1 for header row
945
946 fRawViewer = new TmfRawEventViewer(fSashForm, SWT.H_SCROLL | SWT.V_SCROLL);
947
6a0e6f00 948 fRawViewer.addSelectionListener(new RawSelectionListener());
db4721fb
PT
949
950 fSashForm.setWeights(new int[] { 1, 1 });
951 fRawViewer.setVisible(false);
952
953 createPopupMenu();
954 }
955
346ffed7
MK
956 /**
957 * Checked menu creator to make columns visible or not.
958 *
959 * @param parent
960 * the parent menu
961 * @param column
962 * the column
963 */
be42703d
MK
964 private static IAction createHeaderAction(final TableColumn column) {
965 final IAction columnMenuAction = new Action(column.getText(), IAction.AS_CHECK_BOX) {
346ffed7 966 @Override
be42703d
MK
967 public void run() {
968 if (isChecked()) {
346ffed7
MK
969 column.setWidth((int) column.getData(Key.WIDTH));
970 column.setResizable(true);
971 } else {
972 column.setData(Key.WIDTH, column.getWidth());
973 column.setWidth(0);
974 column.setResizable(false);
975 }
976 }
be42703d
MK
977 };
978 columnMenuAction.setChecked(column.getResizable());
979 return columnMenuAction;
346ffed7
MK
980 }
981
be42703d
MK
982 private IAction createResetHeaderAction() {
983 return new Action(Messages.TmfEventsTable_ShowAll) {
346ffed7 984 @Override
be42703d 985 public void run() {
346ffed7
MK
986 for (TableColumn column : fTable.getColumns()) {
987 final Object widthVal = column.getData(Key.WIDTH);
988 if (widthVal instanceof Integer) {
989 Integer width = (Integer) widthVal;
990 if (!column.getResizable()) {
991 column.setWidth(width);
992 column.setResizable(true);
993 /*
994 * This is because Linux always resizes the last
995 * column to fill in the void, this means that
996 * hiding a column resizes others and we can have 10
997 * columns that are 1000 pixels wide by hiding the
998 * last one progressively.
999 */
1000 if (IS_LINUX) {
1001 column.pack();
1002 }
1003 }
1004 }
1005 }
346ffed7 1006 }
be42703d 1007 };
346ffed7
MK
1008 }
1009
baafe54c
AM
1010 // ------------------------------------------------------------------------
1011 // Operations
1012 // ------------------------------------------------------------------------
1013
a0a88f65
AM
1014 /**
1015 * Create a pop-up menu.
1016 */
a890f499 1017 private void createPopupMenu() {
9ba0a108
PT
1018 final IAction copyAction = new Action(Messages.TmfEventsTable_CopyToClipboardActionText) {
1019 @Override
1020 public void run() {
1021 ITmfTrace trace = fTrace;
1022 if (trace == null || (fSelectedRank == -1 && fSelectedBeginRank == -1)) {
1023 return;
1024 }
1025
1026 List<TmfEventTableColumn> columns = new ArrayList<>();
1027 for (int i : fTable.getColumnOrder()) {
1028 TableColumn column = fTable.getColumns()[i];
1029 // Omit the margin column and hidden columns
7697e148 1030 if (isVisibleEventColumn(column)) {
9ba0a108
PT
1031 columns.add(fColumns.get(i));
1032 }
1033 }
1034
1035 long start = Math.min(fSelectedBeginRank, fSelectedRank);
1036 long end = Math.max(fSelectedBeginRank, fSelectedRank);
1037 final ITmfFilter filter = (ITmfFilter) fTable.getData(Key.FILTER_OBJ);
1038 IRunnableWithProgress operation = new CopyToClipboardOperation(trace, filter, columns, start, end);
1039 try {
1040 PlatformUI.getWorkbench().getProgressService().busyCursorWhile(operation);
1041 } catch (InvocationTargetException e) {
1042 Activator.getDefault().logError("Invocation target exception copying to clipboard ", e); //$NON-NLS-1$
1043 } catch (InterruptedException e) {
1044 /* ignored */
1045 }
1046 }
1047 };
1048
db4721fb
PT
1049 final IAction showTableAction = new Action(Messages.TmfEventsTable_ShowTableActionText) {
1050 @Override
1051 public void run() {
1052 fTable.setVisible(true);
1053 fSashForm.layout();
1054 }
1055 };
1056
1057 final IAction hideTableAction = new Action(Messages.TmfEventsTable_HideTableActionText) {
1058 @Override
1059 public void run() {
1060 fTable.setVisible(false);
1061 fSashForm.layout();
1062 }
1063 };
1064
1065 final IAction showRawAction = new Action(Messages.TmfEventsTable_ShowRawActionText) {
1066 @Override
1067 public void run() {
1068 fRawViewer.setVisible(true);
1069 fSashForm.layout();
1070 final int index = fTable.getSelectionIndex();
dadf6e1a 1071 if (index >= 1) {
db4721fb
PT
1072 fRawViewer.selectAndReveal(index - 1);
1073 }
1074 }
1075 };
1076
1077 final IAction hideRawAction = new Action(Messages.TmfEventsTable_HideRawActionText) {
1078 @Override
1079 public void run() {
1080 fRawViewer.setVisible(false);
1081 fSashForm.layout();
1082 }
1083 };
1084
029df6e3 1085 final IAction openCallsiteAction = new Action(Messages.TmfEventsTable_OpenSourceCodeActionText) {
60fb38b8
PT
1086 @Override
1087 public void run() {
1088 final TableItem items[] = fTable.getSelection();
1089 if (items.length != 1) {
1090 return;
1091 }
1092 final TableItem item = items[0];
1093
1094 final Object data = item.getData();
61bbd27d
AM
1095 if (!(data instanceof ITmfSourceLookup)) {
1096 return;
1097 }
1098 ITmfSourceLookup event = (ITmfSourceLookup) data;
1099 ITmfCallsite cs = event.getCallsite();
1100 if (cs == null) {
1101 return;
1102 }
1103
1104 String fileName = cs.getFileName();
1105 final String trimmedPath = fileName.replaceAll("\\.\\./", EMPTY_STRING); //$NON-NLS-1$
1106 File fileToOpen = new File(trimmedPath);
1107
1108 try {
1109 if (fileToOpen.exists() && fileToOpen.isFile()) {
1110 /*
1111 * The path points to a "real" file, attempt to open
1112 * that
1113 */
1114 IFileStore fileStore = EFS.getLocalFileSystem().getStore(fileToOpen.toURI());
1115 IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
1116
1117 IEditorPart editor = IDE.openEditorOnFileStore(page, fileStore);
1118 if (editor instanceof ITextEditor) {
1119 /*
1120 * Calculate the "document offset" corresponding to
1121 * the line number, then seek there.
1122 */
1123 ITextEditor textEditor = (ITextEditor) editor;
1124 int lineNumber = Long.valueOf(cs.getLineNumber()).intValue();
1125 IDocument document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
1126
1127 IRegion region = document.getLineInformation(lineNumber - 1);
1128 if (region != null) {
1129 textEditor.selectAndReveal(region.getOffset(), region.getLength());
1130 }
8936d4f2 1131 }
61bbd27d
AM
1132
1133 } else {
1134 /*
1135 * The file was not found on disk, attempt to find it in
1136 * the workspace instead.
1137 */
1138 IMarker marker = null;
507b1336 1139 final ArrayList<IFile> files = new ArrayList<>();
60fb38b8
PT
1140 ResourcesPlugin.getWorkspace().getRoot().accept(new IResourceVisitor() {
1141 @Override
1142 public boolean visit(IResource resource) throws CoreException {
1143 if (resource instanceof IFile && resource.getFullPath().toString().endsWith(trimmedPath)) {
1144 files.add((IFile) resource);
1145 }
1146 return true;
1147 }
1148 });
1149 IFile file = null;
1150 if (files.size() > 1) {
1151 ListDialog dialog = new ListDialog(getTable().getShell());
1152 dialog.setContentProvider(ArrayContentProvider.getInstance());
1153 dialog.setLabelProvider(new LabelProvider() {
1154 @Override
1155 public String getText(Object element) {
1156 return ((IFile) element).getFullPath().toString();
1157 }
1158 });
1159 dialog.setInput(files);
029df6e3
JCK
1160 dialog.setTitle(Messages.TmfEventsTable_OpenSourceCodeSelectFileDialogTitle);
1161 dialog.setMessage(Messages.TmfEventsTable_OpenSourceCodeSelectFileDialogTitle + '\n' + cs.toString());
60fb38b8
PT
1162 dialog.open();
1163 Object[] result = dialog.getResult();
1164 if (result != null && result.length > 0) {
1165 file = (IFile) result[0];
1166 }
1167 } else if (files.size() == 1) {
1168 file = files.get(0);
1169 }
1170 if (file != null) {
1171 marker = file.createMarker(IMarker.MARKER);
1172 marker.setAttribute(IMarker.LINE_NUMBER, Long.valueOf(cs.getLineNumber()).intValue());
1173 IDE.openEditor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), marker);
1174 marker.delete();
61bbd27d 1175 } else if (files.isEmpty()) {
029df6e3 1176 displayException(new FileNotFoundException('\'' + cs.toString() + '\'' + '\n' + Messages.TmfEventsTable_OpenSourceCodeNotFound));
60fb38b8 1177 }
60fb38b8 1178 }
61bbd27d
AM
1179 } catch (BadLocationException | CoreException e) {
1180 displayException(e);
60fb38b8
PT
1181 }
1182 }
1183 };
1184
1185 final IAction openModelAction = new Action(Messages.TmfEventsTable_OpenModelActionText) {
ac44ef71
AR
1186 @Override
1187 public void run() {
1188
1189 final TableItem items[] = fTable.getSelection();
1190 if (items.length != 1) {
1191 return;
1192 }
1193 final TableItem item = items[0];
1194
1195 final Object eventData = item.getData();
f47ed727
BH
1196 if (eventData instanceof ITmfModelLookup) {
1197 String modelURI = ((ITmfModelLookup) eventData).getModelUri();
ac44ef71
AR
1198
1199 if (modelURI != null) {
1200 IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
1201
1202 IFile file = null;
1203 final URI uri = URI.createURI(modelURI);
1204 if (uri.isPlatformResource()) {
1205 IPath path = new Path(uri.toPlatformString(true));
1206 file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
1207 } else if (uri.isFile() && !uri.isRelative()) {
1208 file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(
1209 new Path(uri.toFileString()));
1210 }
1211
1212 if (file != null) {
1213 try {
1214 /*
1215 * create a temporary validation marker on the
1216 * model file, remove it afterwards thus,
1217 * navigation works with all model editors
1218 * supporting the navigation to a marker
1219 */
1220 IMarker marker = file.createMarker(EValidator.MARKER);
1221 marker.setAttribute(EValidator.URI_ATTRIBUTE, modelURI);
1222 marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
1223
1224 IDE.openEditor(activePage, marker, OpenStrategy.activateOnOpen());
1225 marker.delete();
8b7eb3cd 1226 } catch (CoreException e) {
ac44ef71
AR
1227 displayException(e);
1228 }
60fb38b8
PT
1229 } else {
1230 displayException(new FileNotFoundException('\'' + modelURI + '\'' + '\n' + Messages.TmfEventsTable_OpenModelUnsupportedURI));
ac44ef71
AR
1231 }
1232 }
1233 }
1234 }
1235 };
60fb38b8 1236
d3de0920
XR
1237 final IAction exportToTextAction = new Action(Messages.TmfEventsTable_Export_to_text) {
1238 @Override
1239 public void run() {
1240 IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
346fa221
MAL
1241 Object handlerServiceObject = activePage.getActiveEditor().getSite().getService(IHandlerService.class);
1242 IHandlerService handlerService = (IHandlerService) handlerServiceObject;
1243 Object cmdServiceObject = activePage.getActiveEditor().getSite().getService(ICommandService.class);
1244 ICommandService cmdService = (ICommandService) cmdServiceObject;
d3de0920 1245 try {
507b1336 1246 HashMap<String, Object> parameters = new HashMap<>();
d3de0920 1247 Command command = cmdService.getCommand(ExportToTextCommandHandler.COMMAND_ID);
03142470
BH
1248 ParameterizedCommand cmd = ParameterizedCommand.generateCommand(command, parameters);
1249
d3de0920 1250 IEvaluationContext context = handlerService.getCurrentState();
6817308e
PT
1251 List<TmfEventTableColumn> exportColumns = new ArrayList<>();
1252 for (int i : fTable.getColumnOrder()) {
9ba0a108
PT
1253 TableColumn column = fTable.getColumns()[i];
1254 // Omit the margin column and hidden columns
7697e148 1255 if (isVisibleEventColumn(column)) {
6817308e
PT
1256 exportColumns.add(fColumns.get(i));
1257 }
1258 }
03142470
BH
1259 context.addVariable(ExportToTextCommandHandler.TMF_EVENT_TABLE_COLUMNS_ID, exportColumns);
1260
d3de0920 1261 handlerService.executeCommandInContext(cmd, null, context);
23c625f1 1262 } catch (ExecutionException | NotDefinedException | NotEnabledException | NotHandledException e) {
d3de0920
XR
1263 displayException(e);
1264 }
1265 }
1266 };
1267
0a08e17d 1268 final IAction addAsFilterAction = new Action(Messages.TmfEventsTable_AddAsFilterText) {
db4721fb
PT
1269 @Override
1270 public void run() {
0a08e17d 1271 applySearchAsFilter();
db4721fb
PT
1272 }
1273 };
1274
1275 final IAction clearFiltersAction = new Action(Messages.TmfEventsTable_ClearFiltersActionText) {
1276 @Override
1277 public void run() {
1278 clearFilters();
1279 }
1280 };
1281
03142470
BH
1282 final IAction collapseAction = new Action(Messages.TmfEventsTable_CollapseFilterMenuName) {
1283 @Override
1284 public void run() {
1285 applyFilter(new TmfCollapseFilter());
1286 }
1287 };
1288
db4721fb 1289 class ToggleBookmarkAction extends Action {
0126a8ca 1290 Long fRank;
db4721fb 1291
0126a8ca 1292 public ToggleBookmarkAction(final String text, final Long rank) {
db4721fb
PT
1293 super(text);
1294 fRank = rank;
1295 }
1296
1297 @Override
1298 public void run() {
1299 toggleBookmark(fRank);
1300 }
1301 }
1302
3bccf3b1
MAL
1303 fHeaderPopupMenuManager = new MenuManager();
1304 fHeaderPopupMenuManager.setRemoveAllWhenShown(true);
1305 fHeaderPopupMenuManager.addMenuListener(new IMenuListener() {
be42703d
MK
1306 @Override
1307 public void menuAboutToShow(IMenuManager manager) {
1308 for (int index : fTable.getColumnOrder()) {
1309 if (fTable.getColumns()[index].getData(Key.WIDTH) != null) {
3bccf3b1 1310 fHeaderPopupMenuManager.add(createHeaderAction(fTable.getColumns()[index]));
be42703d
MK
1311 }
1312 }
3bccf3b1
MAL
1313 fHeaderPopupMenuManager.add(new Separator());
1314 fHeaderPopupMenuManager.add(createResetHeaderAction());
be42703d
MK
1315 }
1316 });
1317
3bccf3b1
MAL
1318 fTablePopupMenuManager = new MenuManager();
1319 fTablePopupMenuManager.setRemoveAllWhenShown(true);
1320 fTablePopupMenuManager.addMenuListener(new IMenuListener() {
db4721fb
PT
1321 @Override
1322 public void menuAboutToShow(final IMenuManager manager) {
9ba0a108 1323 if (fTable.getSelectionIndices().length == 1 && fTable.getSelectionIndices()[0] == 0) {
db4721fb 1324 // Right-click on header row
0a08e17d
PT
1325 if (fHeaderState == HeaderState.SEARCH) {
1326 fTablePopupMenuManager.add(addAsFilterAction);
db4721fb
PT
1327 }
1328 return;
1329 }
665990bb 1330 final Point point = fTable.toControl(fLastMenuCursorLocation);
db4721fb
PT
1331 final TableItem item = fTable.getSelection().length > 0 ? fTable.getSelection()[0] : null;
1332 if (item != null) {
1333 final Rectangle imageBounds = item.getImageBounds(0);
1334 imageBounds.width = BOOKMARK_IMAGE.getBounds().width;
1335 if (point.x <= (imageBounds.x + imageBounds.width)) {
1336 // Right-click on left margin
1337 final Long rank = (Long) item.getData(Key.RANK);
1338 if ((rank != null) && (fBookmarksFile != null)) {
1339 if (fBookmarksMap.containsKey(rank)) {
3bccf3b1 1340 fTablePopupMenuManager.add(new ToggleBookmarkAction(
db4721fb
PT
1341 Messages.TmfEventsTable_RemoveBookmarkActionText, rank));
1342 } else {
3bccf3b1 1343 fTablePopupMenuManager.add(new ToggleBookmarkAction(
db4721fb
PT
1344 Messages.TmfEventsTable_AddBookmarkActionText, rank));
1345 }
1346 }
1347 return;
1348 }
1349 }
60fb38b8 1350
db4721fb 1351 // Right-click on table
9ba0a108 1352 if (fSelectedRank != -1 && fSelectedBeginRank != -1) {
3bccf3b1
MAL
1353 fTablePopupMenuManager.add(copyAction);
1354 fTablePopupMenuManager.add(new Separator());
9ba0a108 1355 }
db4721fb 1356 if (fTable.isVisible() && fRawViewer.isVisible()) {
3bccf3b1
MAL
1357 fTablePopupMenuManager.add(hideTableAction);
1358 fTablePopupMenuManager.add(hideRawAction);
db4721fb 1359 } else if (!fTable.isVisible()) {
3bccf3b1 1360 fTablePopupMenuManager.add(showTableAction);
db4721fb 1361 } else if (!fRawViewer.isVisible()) {
3bccf3b1 1362 fTablePopupMenuManager.add(showRawAction);
db4721fb 1363 }
3bccf3b1
MAL
1364 fTablePopupMenuManager.add(exportToTextAction);
1365 fTablePopupMenuManager.add(new Separator());
60fb38b8 1366
ac44ef71 1367 if (item != null) {
60fb38b8 1368 final Object data = item.getData();
f47ed727
BH
1369 Separator separator = null;
1370 if (data instanceof ITmfSourceLookup) {
1371 ITmfSourceLookup event = (ITmfSourceLookup) data;
60fb38b8 1372 if (event.getCallsite() != null) {
3bccf3b1 1373 fTablePopupMenuManager.add(openCallsiteAction);
60fb38b8
PT
1374 separator = new Separator();
1375 }
f47ed727
BH
1376 }
1377
1378 if (data instanceof ITmfModelLookup) {
1379 ITmfModelLookup event = (ITmfModelLookup) data;
1380 if (event.getModelUri() != null) {
3bccf3b1 1381 fTablePopupMenuManager.add(openModelAction);
60fb38b8
PT
1382 separator = new Separator();
1383 }
f47ed727 1384
60fb38b8 1385 if (separator != null) {
3bccf3b1 1386 fTablePopupMenuManager.add(separator);
ac44ef71
AR
1387 }
1388 }
1389 }
60fb38b8 1390
8b7eb3cd
MK
1391 /*
1392 * Only show collapse filter if at least one trace can be
1393 * collapsed.
1394 */
03142470
BH
1395 boolean isCollapsible = false;
1396 if (fTrace != null) {
c14c0757 1397 for (ITmfTrace trace : TmfTraceManager.getTraceSet(fTrace)) {
8b7eb3cd 1398 Class<? extends ITmfEvent> eventClass = trace.getEventType();
03142470
BH
1399 isCollapsible = ITmfCollapsibleEvent.class.isAssignableFrom(eventClass);
1400 if (isCollapsible) {
1401 break;
1402 }
1403 }
1404 }
1405
0a08e17d 1406 if (isCollapsible && !fCollapseFilterEnabled) {
3bccf3b1
MAL
1407 fTablePopupMenuManager.add(collapseAction);
1408 fTablePopupMenuManager.add(new Separator());
03142470
BH
1409 }
1410
3bccf3b1 1411 fTablePopupMenuManager.add(clearFiltersAction);
db4721fb
PT
1412 final ITmfFilterTreeNode[] savedFilters = FilterManager.getSavedFilters();
1413 if (savedFilters.length > 0) {
1414 final MenuManager subMenu = new MenuManager(Messages.TmfEventsTable_ApplyPresetFilterMenuName);
1415 for (final ITmfFilterTreeNode node : savedFilters) {
1416 if (node instanceof TmfFilterNode) {
1417 final TmfFilterNode filter = (TmfFilterNode) node;
1418 subMenu.add(new Action(filter.getFilterName()) {
1419 @Override
1420 public void run() {
1421 applyFilter(filter);
1422 }
1423 });
1424 }
1425 }
3bccf3b1 1426 fTablePopupMenuManager.add(subMenu);
db4721fb 1427 }
3bccf3b1 1428 appendToTablePopupMenu(fTablePopupMenuManager, item);
db4721fb
PT
1429 }
1430 });
1431
3bccf3b1
MAL
1432 fRawViewerPopupMenuManager = new MenuManager();
1433 fRawViewerPopupMenuManager.setRemoveAllWhenShown(true);
1434 fRawViewerPopupMenuManager.addMenuListener(new IMenuListener() {
db4721fb
PT
1435 @Override
1436 public void menuAboutToShow(final IMenuManager manager) {
1437 if (fTable.isVisible() && fRawViewer.isVisible()) {
3bccf3b1
MAL
1438 fRawViewerPopupMenuManager.add(hideTableAction);
1439 fRawViewerPopupMenuManager.add(hideRawAction);
db4721fb 1440 } else if (!fTable.isVisible()) {
3bccf3b1 1441 fRawViewerPopupMenuManager.add(showTableAction);
db4721fb 1442 } else if (!fRawViewer.isVisible()) {
3bccf3b1 1443 fRawViewerPopupMenuManager.add(showRawAction);
db4721fb 1444 }
18d4ca27 1445 appendToRawPopupMenu(fRawViewerPopupMenuManager);
db4721fb
PT
1446 }
1447 });
1448
3bccf3b1 1449 fHeaderMenu = fHeaderPopupMenuManager.createContextMenu(fTable);
be42703d 1450
3bccf3b1 1451 fTablePopup = fTablePopupMenuManager.createContextMenu(fTable);
346ffed7 1452 fTable.setMenu(fTablePopup);
db4721fb 1453
3bccf3b1 1454 fRawTablePopup = fRawViewerPopupMenuManager.createContextMenu(fRawViewer);
346ffed7 1455 fRawViewer.setMenu(fRawTablePopup);
db4721fb
PT
1456 }
1457
a0a88f65
AM
1458 /**
1459 * Append an item to the event table's pop-up menu.
1460 *
1461 * @param tablePopupMenu
1462 * The menu manager
1463 * @param selectedItem
1464 * The item to append
1465 */
db4721fb
PT
1466 protected void appendToTablePopupMenu(final MenuManager tablePopupMenu, final TableItem selectedItem) {
1467 // override to append more actions
1468 }
1469
a0a88f65
AM
1470 /**
1471 * Append an item to the raw viewer's pop-up menu.
1472 *
1473 * @param rawViewerPopupMenu
1474 * The menu manager
1475 */
db4721fb
PT
1476 protected void appendToRawPopupMenu(final MenuManager rawViewerPopupMenu) {
1477 // override to append more actions
1478 }
1479
1480 @Override
1481 public void dispose() {
1482 stopSearchThread();
1483 stopFilterThread();
812e7197 1484 PlatformUI.getWorkbench().getThemeManager().removePropertyChangeListener(this);
db4721fb
PT
1485 ColorSettingsManager.removeColorSettingsListener(this);
1486 fComposite.dispose();
1487 if ((fTrace != null) && fDisposeOnClose) {
1488 fTrace.dispose();
1489 }
1490 fResourceManager.dispose();
3a97628a 1491 fRawViewer.dispose();
3bccf3b1
MAL
1492 if (fRawViewerPopupMenuManager != null) {
1493 fRawViewerPopupMenuManager.dispose();
1494 }
1495 if (fHeaderPopupMenuManager != null) {
1496 fHeaderPopupMenuManager.dispose();
1497 }
1498 if (fTablePopupMenuManager != null) {
1499 fTablePopupMenuManager.dispose();
1500 }
1501
db4721fb
PT
1502 super.dispose();
1503 }
1504
1505 /**
1506 * Assign a layout data object to this view.
1507 *
1508 * @param layoutData
1509 * The layout data to assign
1510 */
1511 public void setLayoutData(final Object layoutData) {
1512 fComposite.setLayoutData(layoutData);
1513 }
1514
1515 /**
1516 * Get the virtual table contained in this event table.
1517 *
1518 * @return The TMF virtual table
1519 */
1520 public TmfVirtualTable getTable() {
1521 return fTable;
1522 }
1523
1524 /**
1525 * @param columnData
baafe54c
AM
1526 * columnData
1527 * @deprecated The column headers are now set at the constructor, this
1528 * shouldn't be called anymore.
db4721fb 1529 */
baafe54c 1530 @Deprecated
8b7eb3cd 1531 protected void setColumnHeaders(final org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) {
baafe54c 1532 /* No-op */
db4721fb
PT
1533 }
1534
a0a88f65
AM
1535 /**
1536 * Set a table item's data.
1537 *
1538 * @param item
1539 * The item to set
1540 * @param event
1541 * Which trace event to link with this entry
1542 * @param rank
1543 * Which rank this event has in the trace/experiment
1544 */
db4721fb 1545 protected void setItemData(final TableItem item, final ITmfEvent event, final long rank) {
03142470 1546 String[] itemStrings = getItemStrings(fColumns, event);
32528869
BH
1547
1548 // Get the actual ITmfEvent from the CachedEvent
1549 ITmfEvent tmfEvent = event;
1550 if (event instanceof CachedEvent) {
1551 tmfEvent = ((CachedEvent) event).event;
1552 }
03142470 1553 item.setText(itemStrings);
32528869
BH
1554 item.setData(tmfEvent);
1555 item.setData(Key.TIMESTAMP, new TmfTimestamp(tmfEvent.getTimestamp()));
db4721fb
PT
1556 item.setData(Key.RANK, rank);
1557
2db176a0
PT
1558 final Collection<Long> markerIds = fBookmarksMap.get(rank);
1559 if (!markerIds.isEmpty()) {
1560 Joiner joiner = Joiner.on("\n -").skipNulls(); //$NON-NLS-1$
1561 List<Object> parts = new ArrayList<>();
1562 if (markerIds.size() > 1) {
1563 parts.add(Messages.TmfEventsTable_MultipleBookmarksToolTip);
1564 }
db4721fb 1565 try {
2db176a0
PT
1566 for (long markerId : markerIds) {
1567 final IMarker marker = fBookmarksFile.findMarker(markerId);
7697e148
PT
1568 if (marker != null) {
1569 parts.add(marker.getAttribute(IMarker.MESSAGE));
1570 }
2db176a0
PT
1571 }
1572 } catch (CoreException e) {
db4721fb
PT
1573 displayException(e);
1574 }
2db176a0 1575 item.setData(Key.BOOKMARK, joiner.join(parts));
db4721fb
PT
1576 } else {
1577 item.setData(Key.BOOKMARK, null);
1578 }
1579
1580 boolean searchMatch = false;
1581 boolean searchNoMatch = false;
1582 final ITmfFilter searchFilter = (ITmfFilter) fTable.getData(Key.SEARCH_OBJ);
1583 if (searchFilter != null) {
32528869 1584 if (searchFilter.matches(tmfEvent)) {
db4721fb
PT
1585 searchMatch = true;
1586 } else {
1587 searchNoMatch = true;
1588 }
1589 }
1590
32528869 1591 final ColorSetting colorSetting = ColorSettingsManager.getColorSetting(tmfEvent);
db4721fb
PT
1592 if (searchNoMatch) {
1593 item.setForeground(colorSetting.getDimmedForegroundColor());
1594 item.setBackground(colorSetting.getDimmedBackgroundColor());
1595 } else {
1596 item.setForeground(colorSetting.getForegroundColor());
1597 item.setBackground(colorSetting.getBackgroundColor());
1598 }
812e7197 1599 item.setFont(fFont);
db4721fb
PT
1600
1601 if (searchMatch) {
2db176a0 1602 if (!markerIds.isEmpty()) {
db4721fb
PT
1603 item.setImage(SEARCH_MATCH_BOOKMARK_IMAGE);
1604 } else {
1605 item.setImage(SEARCH_MATCH_IMAGE);
1606 }
2db176a0 1607 } else if (!markerIds.isEmpty()) {
db4721fb
PT
1608 item.setImage(BOOKMARK_IMAGE);
1609 } else {
1610 item.setImage((Image) null);
1611 }
03142470 1612
110d44a1
PT
1613 List<StyleRange> styleRanges = new ArrayList<>();
1614 for (int index = 0; index < fTable.getColumns().length; index++) {
1615 TableColumn column = fTable.getColumns()[index];
1616 String regex = null;
0a08e17d
PT
1617 if (fHeaderState == HeaderState.SEARCH) {
1618 if (searchMatch) {
1619 regex = (String) column.getData(Key.SEARCH_TXT);
1620 }
1621 } else {
110d44a1 1622 regex = (String) column.getData(Key.FILTER_TXT);
110d44a1
PT
1623 }
1624 if (regex != null) {
110d44a1
PT
1625 String text = item.getText(index);
1626 try {
1627 Pattern pattern = Pattern.compile(regex);
1628 Matcher matcher = pattern.matcher(text);
1629 while (matcher.find()) {
1630 int start = matcher.start();
1631 int length = matcher.end() - start;
1632 Color foreground = colorSetting.getForegroundColor();
bb7c657f 1633 Color background = fHighlightColor;
110d44a1
PT
1634 StyleRange styleRange = new StyleRange(start, length, foreground, background);
1635 styleRange.data = index;
1636 styleRanges.add(styleRange);
1637 }
1638 } catch (PatternSyntaxException e) {
1639 /* ignored */
1640 }
1641 }
1642 }
1643 if (styleRanges.isEmpty()) {
1644 item.setData(Key.STYLE_RANGES, null);
1645 } else {
1646 item.setData(Key.STYLE_RANGES, styleRanges);
1647 }
1648 item.getParent().redraw();
1649
03142470
BH
1650 if ((itemStrings[MARGIN_COLUMN_INDEX] != null) && !itemStrings[MARGIN_COLUMN_INDEX].isEmpty()) {
1651 packMarginColumn();
1652 }
db4721fb
PT
1653 }
1654
a0a88f65
AM
1655 /**
1656 * Set the item data of the header row.
1657 *
1658 * @param item
1659 * The item to use as table header
1660 */
db4721fb 1661 protected void setHeaderRowItemData(final TableItem item) {
0a08e17d 1662 if (fHeaderState == HeaderState.NO_SEARCH) {
db4721fb 1663 item.setImage(SEARCH_IMAGE);
0a08e17d
PT
1664 } else if (fHeaderState == HeaderState.SEARCH) {
1665 item.setImage(FILTER_ADD_IMAGE);
db4721fb
PT
1666 }
1667 item.setForeground(fGrayColor);
03142470
BH
1668 // Ignore collapse and image column
1669 for (int i = EVENT_COLUMNS_START_INDEX; i < fTable.getColumns().length; i++) {
db4721fb 1670 final TableColumn column = fTable.getColumns()[i];
0a08e17d 1671 final String filter = (String) column.getData(Key.SEARCH_TXT);
db4721fb 1672 if (filter == null) {
0a08e17d 1673 item.setText(i, SEARCH_HINT);
db4721fb 1674 item.setForeground(i, fGrayColor);
812e7197 1675 item.setFont(i, fFont);
db4721fb
PT
1676 } else {
1677 item.setText(i, filter);
1678 item.setForeground(i, fGreenColor);
1679 item.setFont(i, fBoldFont);
1680 }
1681 }
1682 }
1683
a0a88f65
AM
1684 /**
1685 * Set the item data of the "filter status" row.
1686 *
1687 * @param item
1688 * The item to use as filter status row
1689 */
db4721fb
PT
1690 protected void setFilterStatusRowItemData(final TableItem item) {
1691 for (int i = 0; i < fTable.getColumns().length; i++) {
03142470 1692 if (i == MARGIN_COLUMN_INDEX) {
db4721fb
PT
1693 if ((fTrace == null) || (fFilterCheckCount == fTrace.getNbEvents())) {
1694 item.setImage(FILTER_IMAGE);
1695 } else {
1696 item.setImage(STOP_IMAGE);
1697 }
03142470
BH
1698 }
1699
1700 if (i == FILTER_SUMMARY_INDEX) {
1701 item.setText(FILTER_SUMMARY_INDEX, fFilterMatchCount + "/" + fFilterCheckCount); //$NON-NLS-1$
db4721fb 1702 } else {
03142470 1703 item.setText(i, EMPTY_STRING);
db4721fb
PT
1704 }
1705 }
93bfd50a 1706 item.setData(null);
db4721fb
PT
1707 item.setData(Key.TIMESTAMP, null);
1708 item.setData(Key.RANK, null);
110d44a1 1709 item.setData(Key.STYLE_RANGES, null);
db4721fb
PT
1710 item.setForeground(null);
1711 item.setBackground(null);
812e7197 1712 item.setFont(fFont);
db4721fb
PT
1713 }
1714
a0a88f65
AM
1715 /**
1716 * Create an editor for the header.
1717 */
a890f499 1718 private void createHeaderEditor() {
db4721fb
PT
1719 final TableEditor tableEditor = fTable.createTableEditor();
1720 tableEditor.horizontalAlignment = SWT.LEFT;
1721 tableEditor.verticalAlignment = SWT.CENTER;
1722 tableEditor.grabHorizontal = true;
1723 tableEditor.minimumWidth = 50;
1724
1725 // Handle the header row selection
1726 fTable.addMouseListener(new MouseAdapter() {
1727 int columnIndex;
1728 TableColumn column;
1729 TableItem item;
1730
1731 @Override
1732 public void mouseDown(final MouseEvent event) {
1733 if (event.button != 1) {
1734 return;
1735 }
1736 // Identify the selected row
1737 final Point point = new Point(event.x, event.y);
1738 item = fTable.getItem(point);
1739
1740 // Header row selected
1741 if ((item != null) && (fTable.indexOf(item) == 0)) {
1742
8ec1247f
BH
1743 // Margin column selected
1744 if (item.getBounds(0).contains(point)) {
db4721fb 1745 if (fHeaderState == HeaderState.SEARCH) {
0a08e17d 1746 applySearchAsFilter();
db4721fb 1747 }
db4721fb
PT
1748 return;
1749 }
1750
1751 // Identify the selected column
1752 columnIndex = -1;
1753 for (int i = 0; i < fTable.getColumns().length; i++) {
1754 final Rectangle rect = item.getBounds(i);
1755 if (rect.contains(point)) {
1756 columnIndex = i;
1757 break;
1758 }
1759 }
1760
1761 if (columnIndex == -1) {
1762 return;
1763 }
1764
1765 column = fTable.getColumns()[columnIndex];
1766
8b7eb3cd
MK
1767 /*
1768 * The control that will be the editor must be a child of
1769 * the Table
1770 */
db4721fb 1771 final Text newEditor = (Text) fTable.createTableEditorControl(Text.class);
0a08e17d 1772 final String headerString = (String) column.getData(Key.SEARCH_TXT);
db4721fb
PT
1773 if (headerString != null) {
1774 newEditor.setText(headerString);
1775 }
1776 newEditor.addFocusListener(new FocusAdapter() {
1777 @Override
1778 public void focusLost(final FocusEvent e) {
1779 final boolean changed = updateHeader(newEditor.getText());
1780 if (changed) {
1781 applyHeader();
1782 }
1783 }
1784 });
1785 newEditor.addKeyListener(new KeyAdapter() {
1786 @Override
1787 public void keyPressed(final KeyEvent e) {
1788 if (e.character == SWT.CR) {
1789 updateHeader(newEditor.getText());
1790 applyHeader();
0a08e17d
PT
1791 if ((e.stateMask & SWT.CTRL) != 0) {
1792 applySearchAsFilter();
1793 } else {
1794 /*
1795 * Set focus on the table so that the next
1796 * carriage return goes to the next result
1797 */
1798 TmfEventsTable.this.getTable().setFocus();
1799 }
db4721fb
PT
1800 } else if (e.character == SWT.ESC) {
1801 tableEditor.getEditor().dispose();
1802 }
1803 }
1804 });
1805 newEditor.selectAll();
1806 newEditor.setFocus();
1807 tableEditor.setEditor(newEditor, item, columnIndex);
1808 }
1809 }
1810
1811 /*
1812 * returns true is value was changed
1813 */
17dd85d7 1814 private boolean updateHeader(final String regex) {
17dd85d7 1815 if (regex.length() > 0) {
db4721fb 1816 try {
db4721fb 1817 Pattern.compile(regex);
0a08e17d 1818 if (regex.equals(column.getData(Key.SEARCH_TXT))) {
db4721fb
PT
1819 tableEditor.getEditor().dispose();
1820 return false;
1821 }
ec34bf48 1822 final TmfFilterMatchesNode filter = new TmfFilterMatchesNode(null);
c409f16b
AM
1823 ITmfEventAspect aspect = (ITmfEventAspect) column.getData(Key.ASPECT);
1824 filter.setEventAspect(aspect);
db4721fb 1825 filter.setRegex(regex);
0a08e17d
PT
1826 column.setData(Key.SEARCH_OBJ, filter);
1827 column.setData(Key.SEARCH_TXT, regex);
db4721fb
PT
1828 } catch (final PatternSyntaxException ex) {
1829 tableEditor.getEditor().dispose();
1830 MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
1831 ex.getDescription(), ex.getMessage());
1832 return false;
1833 }
1834 } else {
0a08e17d 1835 if (column.getData(Key.SEARCH_TXT) == null) {
db4721fb
PT
1836 tableEditor.getEditor().dispose();
1837 return false;
1838 }
0a08e17d
PT
1839 column.setData(Key.SEARCH_OBJ, null);
1840 column.setData(Key.SEARCH_TXT, null);
db4721fb
PT
1841 }
1842 return true;
1843 }
1844
1845 private void applyHeader() {
0a08e17d
PT
1846 stopSearchThread();
1847 final TmfFilterRootNode filter = new TmfFilterRootNode();
1848 for (final TableColumn col : fTable.getColumns()) {
1849 final Object filterObj = col.getData(Key.SEARCH_OBJ);
1850 if (filterObj instanceof ITmfFilterTreeNode) {
1851 filter.addChild((ITmfFilterTreeNode) filterObj);
db4721fb
PT
1852 }
1853 }
0a08e17d
PT
1854 if (filter.getChildrenCount() > 0) {
1855 fHeaderState = HeaderState.SEARCH;
1856 fTable.setData(Key.SEARCH_OBJ, filter);
1857 fTable.refresh();
1858 searchNext();
1859 fireSearchApplied(filter);
1860 } else {
1861 fHeaderState = HeaderState.NO_SEARCH;
1862 fTable.setData(Key.SEARCH_OBJ, null);
1863 fTable.refresh();
1864 fireSearchApplied(null);
1865 }
db4721fb
PT
1866
1867 tableEditor.getEditor().dispose();
1868 }
1869 });
1870
1871 fTable.addKeyListener(new KeyAdapter() {
1872 @Override
1873 public void keyPressed(final KeyEvent e) {
1874 e.doit = false;
1875 if (e.character == SWT.ESC) {
1876 stopFilterThread();
1877 stopSearchThread();
1878 fTable.refresh();
1879 } else if (e.character == SWT.DEL) {
1880 if (fHeaderState == HeaderState.SEARCH) {
0a08e17d 1881 fHeaderState = HeaderState.NO_SEARCH;
db4721fb
PT
1882 stopSearchThread();
1883 for (final TableColumn column : fTable.getColumns()) {
1884 column.setData(Key.SEARCH_OBJ, null);
1885 column.setData(Key.SEARCH_TXT, null);
0a08e17d 1886 column.setData(Key.FILTER_TXT, null);
db4721fb
PT
1887 }
1888 fTable.setData(Key.SEARCH_OBJ, null);
1889 fTable.refresh();
1890 fireSearchApplied(null);
0a08e17d
PT
1891 } else {
1892 for (final TableColumn column : fTable.getColumns()) {
1893 column.setData(Key.FILTER_TXT, null);
1894 }
1895 fTable.refresh();
db4721fb
PT
1896 }
1897 } else if (e.character == SWT.CR) {
0a08e17d
PT
1898 if ((e.stateMask & SWT.CTRL) != 0) {
1899 if (fHeaderState == HeaderState.SEARCH) {
1900 applySearchAsFilter();
1901 }
1902 } else if ((e.stateMask & SWT.SHIFT) == 0) {
db4721fb
PT
1903 searchNext();
1904 } else {
1905 searchPrevious();
1906 }
1907 }
1908 }
1909 });
1910 }
1911
0a08e17d
PT
1912 /**
1913 * Apply the current search condition as a new filter.
1914 *
1915 * @since 2.0
1916 */
1917 protected void applySearchAsFilter() {
1918 Object searchObj = fTable.getData(Key.SEARCH_OBJ);
1919 if (searchObj instanceof ITmfFilter) {
1920 ITmfFilter filter = (ITmfFilter) searchObj;
1921 fTable.setData(Key.SEARCH_OBJ, null);
1922 fireSearchApplied(null);
1923 fHeaderState = HeaderState.NO_SEARCH;
1924 for (final TableColumn col : fTable.getColumns()) {
1925 col.setData(Key.FILTER_TXT, col.getData(Key.SEARCH_TXT));
1926 col.setData(Key.SEARCH_TXT, null);
1927 col.setData(Key.SEARCH_OBJ, null);
1928 }
1929 applyFilter(filter);
1930 }
1931 }
1932
a0a88f65
AM
1933 /**
1934 * Send an event indicating a filter has been applied.
1935 *
1936 * @param filter
1937 * The filter that was just applied
1938 */
db4721fb 1939 protected void fireFilterApplied(final ITmfFilter filter) {
faa38350 1940 broadcast(new TmfEventFilterAppliedSignal(this, fTrace, filter));
db4721fb
PT
1941 }
1942
a0a88f65
AM
1943 /**
1944 * Send an event indicating that a search has been applied.
1945 *
1946 * @param filter
1947 * The search filter that was just applied
1948 */
db4721fb 1949 protected void fireSearchApplied(final ITmfFilter filter) {
faa38350 1950 broadcast(new TmfEventSearchAppliedSignal(this, fTrace, filter));
db4721fb
PT
1951 }
1952
a0a88f65
AM
1953 /**
1954 * Start the filtering thread.
1955 */
db4721fb
PT
1956 protected void startFilterThread() {
1957 synchronized (fFilterSyncObj) {
1958 final ITmfFilterTreeNode filter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1959 if (fFilterThread == null || fFilterThread.filter != filter) {
1960 if (fFilterThread != null) {
1961 fFilterThread.cancel();
1962 fFilterThreadResume = false;
1963 }
1964 fFilterThread = new FilterThread(filter);
1965 fFilterThread.start();
1966 } else {
1967 fFilterThreadResume = true;
1968 }
1969 }
1970 }
1971
a0a88f65
AM
1972 /**
1973 * Stop the filtering thread.
1974 */
db4721fb
PT
1975 protected void stopFilterThread() {
1976 synchronized (fFilterSyncObj) {
1977 if (fFilterThread != null) {
1978 fFilterThread.cancel();
1979 fFilterThread = null;
1980 fFilterThreadResume = false;
1981 }
1982 }
1983 }
1984
1985 /**
0a08e17d 1986 * Apply a filter. It is added to the existing filters.
a0a88f65
AM
1987 *
1988 * @param filter
1989 * The filter to apply
db4721fb
PT
1990 */
1991 protected void applyFilter(ITmfFilter filter) {
f29f8868
BH
1992 stopFilterThread();
1993 stopSearchThread();
db4721fb
PT
1994 fFilterMatchCount = 0;
1995 fFilterCheckCount = 0;
0a08e17d
PT
1996 ITmfFilterTreeNode rootFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1997 if (rootFilter == null) {
1998 rootFilter = new TmfFilterRootNode();
1999 }
2000 if (filter instanceof TmfFilterRootNode) {
2001 TmfFilterRootNode parentFilter = (TmfFilterRootNode) filter;
2002 for (ITmfFilterTreeNode child : parentFilter.getChildren()) {
2003 rootFilter.addChild(child);
2004 }
2005 } else if (filter instanceof TmfCollapseFilter) {
2006 fCollapseFilterEnabled = true;
2007 } else if (filter instanceof ITmfFilterTreeNode) {
2008 rootFilter.addChild((ITmfFilterTreeNode) filter);
2009 } else {
2010 rootFilter.addChild(new TmfFilterObjectNode(filter));
2011 }
2012 fCache.applyFilter(rootFilter, fCollapseFilterEnabled);
2013 fHeaderBar.addFilter(filter);
2014 fTable.clearAll();
2015 fTable.setData(Key.FILTER_OBJ, rootFilter);
2016 /* +1 for header row, +2 for top and bottom filter status rows */
2017 fTable.setItemCount(3);
2018 startFilterThread();
2019 fireFilterApplied(rootFilter);
2020 }
2021
2022 /**
2023 * Remove a filter. Any other existing filters remain applied.
2024 *
2025 * @param filter
2026 * The filter to remove
2027 * @since 2.0
2028 */
2029 protected void removeFilter(ITmfFilter filter) {
2030 ITmfFilterTreeNode rootFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
2031 if (rootFilter == null) {
2032 return;
2033 }
2034 stopFilterThread();
2035 stopSearchThread();
2036 fFilterMatchCount = 0;
2037 fFilterCheckCount = 0;
2038 if (filter instanceof TmfCollapseFilter) {
2039 fCollapseFilterEnabled = false;
2040 } else if (filter instanceof ITmfFilterTreeNode) {
2041 rootFilter.removeChild((ITmfFilterTreeNode) filter);
2042 } else {
2043 for (ITmfFilterTreeNode child : rootFilter.getChildren()) {
2044 if (child instanceof TmfFilterObjectNode) {
2045 if (((TmfFilterObjectNode) child).getFilter().equals(filter)) {
2046 rootFilter.removeChild(child);
2047 break;
2048 }
2049 }
2050 }
2051 }
2052 if (!rootFilter.hasChildren() && !fCollapseFilterEnabled) {
2053 clearFilters();
2054 return;
2055 }
2056 fCache.applyFilter(rootFilter, fCollapseFilterEnabled);
2057 fHeaderBar.removeFilter(filter);
db4721fb 2058 fTable.clearAll();
0a08e17d 2059 fTable.setData(Key.FILTER_OBJ, rootFilter);
8b7eb3cd
MK
2060 /* +1 for header row, +2 for top and bottom filter status rows */
2061 fTable.setItemCount(3);
db4721fb 2062 startFilterThread();
0a08e17d
PT
2063 fireFilterApplied(rootFilter);
2064
2065 // Set original width
2066 fTable.getColumns()[MARGIN_COLUMN_INDEX].setWidth(0);
2067 packMarginColumn();
db4721fb
PT
2068 }
2069
a0a88f65
AM
2070 /**
2071 * Clear all currently active filters.
2072 */
db4721fb
PT
2073 protected void clearFilters() {
2074 if (fTable.getData(Key.FILTER_OBJ) == null) {
2075 return;
2076 }
2077 stopFilterThread();
2078 stopSearchThread();
2079 fCache.clearFilter();
0a08e17d
PT
2080 fHeaderBar.clearFilters();
2081 fCollapseFilterEnabled = false;
db4721fb
PT
2082 fTable.clearAll();
2083 for (final TableColumn column : fTable.getColumns()) {
2084 column.setData(Key.FILTER_OBJ, null);
2085 column.setData(Key.FILTER_TXT, null);
2086 }
2087 fTable.setData(Key.FILTER_OBJ, null);
2088 if (fTrace != null) {
8b7eb3cd
MK
2089 /* +1 for header row */
2090 fTable.setItemCount((int) fTrace.getNbEvents() + 1);
db4721fb 2091 } else {
8b7eb3cd
MK
2092 /* +1 for header row */
2093 fTable.setItemCount(1);
db4721fb
PT
2094 }
2095 fFilterMatchCount = 0;
2096 fFilterCheckCount = 0;
2097 if (fSelectedRank >= 0) {
8b7eb3cd
MK
2098 /* +1 for header row */
2099 fTable.setSelection((int) fSelectedRank + 1);
db4721fb
PT
2100 } else {
2101 fTable.setSelection(0);
2102 }
2103 fireFilterApplied(null);
3f43dc48 2104 updateStatusLine(null);
03142470
BH
2105
2106 // Set original width
2107 fTable.getColumns()[MARGIN_COLUMN_INDEX].setWidth(0);
2108 packMarginColumn();
db4721fb
PT
2109 }
2110
a0a88f65
AM
2111 /**
2112 * Wrapper Thread object for the filtering thread.
2113 */
db4721fb
PT
2114 protected class FilterThread extends Thread {
2115 private final ITmfFilterTreeNode filter;
0a08e17d 2116 private TmfCollapseFilter collapseFilter = null;
fd3f1eff 2117 private TmfEventRequest request;
db4721fb
PT
2118 private boolean refreshBusy = false;
2119 private boolean refreshPending = false;
2120 private final Object syncObj = new Object();
2121
a0a88f65
AM
2122 /**
2123 * Constructor.
2124 *
2125 * @param filter
2126 * The filter this thread will be processing
2127 */
db4721fb
PT
2128 public FilterThread(final ITmfFilterTreeNode filter) {
2129 super("Filter Thread"); //$NON-NLS-1$
2130 this.filter = filter;
2131 }
2132
2133 @Override
2134 public void run() {
2135 if (fTrace == null) {
2136 return;
2137 }
0a08e17d
PT
2138 if (fCollapseFilterEnabled) {
2139 collapseFilter = new TmfCollapseFilter();
2140 }
db4721fb
PT
2141 final int nbRequested = (int) (fTrace.getNbEvents() - fFilterCheckCount);
2142 if (nbRequested <= 0) {
2143 return;
2144 }
fd3f1eff
AM
2145 request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY,
2146 (int) fFilterCheckCount, nbRequested, ExecutionType.BACKGROUND) {
db4721fb
PT
2147 @Override
2148 public void handleData(final ITmfEvent event) {
2149 super.handleData(event);
2150 if (request.isCancelled()) {
2151 return;
2152 }
03142470 2153 boolean refresh = false;
db4721fb 2154 if (filter.matches(event)) {
0a08e17d
PT
2155 if (collapseFilter == null || collapseFilter.matches(event)) {
2156 final long rank = fFilterCheckCount;
2157 final int index = (int) fFilterMatchCount;
2158 fFilterMatchCount++;
2159 fCache.storeEvent(event, rank, index);
2160 } else if (collapseFilter != null) {
03142470
BH
2161 fCache.updateCollapsedEvent((int) fFilterMatchCount - 1);
2162 }
0a08e17d 2163 refresh = true;
03142470
BH
2164 }
2165
2166 if (refresh || (fFilterCheckCount % 100) == 0) {
db4721fb
PT
2167 refreshTable();
2168 }
2169 fFilterCheckCount++;
2170 }
2171 };
fd3f1eff 2172 ((ITmfEventProvider) fTrace).sendRequest(request);
db4721fb
PT
2173 try {
2174 request.waitForCompletion();
2175 } catch (final InterruptedException e) {
2176 }
2177 refreshTable();
2178 synchronized (fFilterSyncObj) {
2179 fFilterThread = null;
2180 if (fFilterThreadResume) {
2181 fFilterThreadResume = false;
2182 fFilterThread = new FilterThread(filter);
2183 fFilterThread.start();
2184 }
2185 }
2186 }
2187
a0a88f65
AM
2188 /**
2189 * Refresh the filter.
2190 */
db4721fb
PT
2191 public void refreshTable() {
2192 synchronized (syncObj) {
2193 if (refreshBusy) {
2194 refreshPending = true;
2195 return;
2196 }
2197 refreshBusy = true;
2198 }
2199 Display.getDefault().asyncExec(new Runnable() {
2200 @Override
2201 public void run() {
2202 if (request.isCancelled()) {
2203 return;
2204 }
2205 if (fTable.isDisposed()) {
2206 return;
2207 }
8b7eb3cd
MK
2208 /*
2209 * +1 for header row, +2 for top and bottom filter status
2210 * rows
2211 */
2212 fTable.setItemCount((int) fFilterMatchCount + 3);
db4721fb
PT
2213 fTable.refresh();
2214 synchronized (syncObj) {
2215 refreshBusy = false;
2216 if (refreshPending) {
2217 refreshPending = false;
2218 refreshTable();
2219 }
2220 }
2221 }
2222 });
2223 }
2224
a0a88f65
AM
2225 /**
2226 * Cancel this filtering thread.
2227 */
db4721fb
PT
2228 public void cancel() {
2229 if (request != null) {
2230 request.cancel();
2231 }
2232 }
2233 }
2234
a0a88f65
AM
2235 /**
2236 * Go to the next item of a search.
2237 */
db4721fb
PT
2238 protected void searchNext() {
2239 synchronized (fSearchSyncObj) {
2240 if (fSearchThread != null) {
2241 return;
2242 }
2243 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
2244 if (searchFilter == null) {
2245 return;
2246 }
2247 final int selectionIndex = fTable.getSelectionIndex();
2248 int startIndex;
2249 if (selectionIndex > 0) {
8b7eb3cd
MK
2250 /* -1 for header row, +1 for next event */
2251 startIndex = selectionIndex;
db4721fb 2252 } else {
8b7eb3cd
MK
2253 /*
2254 * header row is selected, start at top event
2255 */
2256 /* -1 for header row */
2257 startIndex = Math.max(0, fTable.getTopIndex() - 1);
db4721fb
PT
2258 }
2259 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
60fb38b8 2260 if (eventFilter != null) {
8b7eb3cd
MK
2261 // -1 for top filter status row
2262 startIndex = Math.max(0, startIndex - 1);
db4721fb
PT
2263 }
2264 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.FORWARD);
2265 fSearchThread.schedule();
2266 }
2267 }
2268
a0a88f65
AM
2269 /**
2270 * Go to the previous item of a search.
2271 */
db4721fb
PT
2272 protected void searchPrevious() {
2273 synchronized (fSearchSyncObj) {
2274 if (fSearchThread != null) {
2275 return;
2276 }
2277 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
2278 if (searchFilter == null) {
2279 return;
2280 }
2281 final int selectionIndex = fTable.getSelectionIndex();
2282 int startIndex;
2283 if (selectionIndex > 0) {
8b7eb3cd
MK
2284 /* -1 for header row, -1 for previous event */
2285 startIndex = selectionIndex - 2;
db4721fb 2286 } else {
8b7eb3cd
MK
2287 /*
2288 * Header row is selected, start at precedent of top event
2289 */
2290 /* -1 for header row, -1 for previous event */
2291 startIndex = fTable.getTopIndex() - 2;
db4721fb
PT
2292 }
2293 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
60fb38b8 2294 if (eventFilter != null) {
8b7eb3cd
MK
2295 /* -1 for top filter status row */
2296 startIndex = startIndex - 1;
db4721fb
PT
2297 }
2298 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.BACKWARD);
2299 fSearchThread.schedule();
2300 }
2301 }
2302
a0a88f65
AM
2303 /**
2304 * Stop the search thread.
2305 */
db4721fb
PT
2306 protected void stopSearchThread() {
2307 fPendingGotoRank = -1;
2308 synchronized (fSearchSyncObj) {
2309 if (fSearchThread != null) {
2310 fSearchThread.cancel();
2311 fSearchThread = null;
2312 }
2313 }
2314 }
2315
a0a88f65
AM
2316 /**
2317 * Wrapper for the search thread.
2318 */
db4721fb 2319 protected class SearchThread extends Job {
a0a88f65
AM
2320
2321 private ITmfFilterTreeNode searchFilter;
2322 private ITmfFilterTreeNode eventFilter;
2323 private int startIndex;
2324 private int direction;
2325 private long rank;
2326 private long foundRank = -1;
fd3f1eff 2327 private TmfEventRequest request;
ae3ffd37 2328 private ITmfTimestamp foundTimestamp = null;
db4721fb 2329
a0a88f65
AM
2330 /**
2331 * Constructor.
2332 *
2333 * @param searchFilter
2334 * The search filter
2335 * @param eventFilter
2336 * The event filter
2337 * @param startIndex
2338 * The index at which we should start searching
2339 * @param currentRank
2340 * The current rank
2341 * @param direction
2342 * In which direction should we search, forward or backwards
2343 */
2344 public SearchThread(final ITmfFilterTreeNode searchFilter,
2345 final ITmfFilterTreeNode eventFilter, final int startIndex,
db4721fb
PT
2346 final long currentRank, final int direction) {
2347 super(Messages.TmfEventsTable_SearchingJobName);
2348 this.searchFilter = searchFilter;
2349 this.eventFilter = eventFilter;
2350 this.startIndex = startIndex;
2351 this.rank = currentRank;
2352 this.direction = direction;
2353 }
2354
2355 @Override
2356 protected IStatus run(final IProgressMonitor monitor) {
f4e06774
FLN
2357 final ITmfTrace trace = fTrace;
2358 if (trace == null) {
db4721fb
PT
2359 return Status.OK_STATUS;
2360 }
2361 final Display display = Display.getDefault();
2362 if (startIndex < 0) {
f4e06774 2363 rank = (int) trace.getNbEvents() - 1;
8b7eb3cd
MK
2364 /*
2365 * -1 for header row, -3 for header and top and bottom filter
2366 * status rows
2367 */
2368 } else if (startIndex >= (fTable.getItemCount() - (eventFilter == null ? 1 : 3))) {
db4721fb
PT
2369 rank = 0;
2370 } else {
2371 int idx = startIndex;
2372 while (foundRank == -1) {
2373 final CachedEvent event = fCache.peekEvent(idx);
2374 if (event == null) {
2375 break;
2376 }
2377 rank = event.rank;
2378 if (searchFilter.matches(event.event) && ((eventFilter == null) || eventFilter.matches(event.event))) {
2379 foundRank = event.rank;
ae3ffd37 2380 foundTimestamp = event.event.getTimestamp();
db4721fb
PT
2381 break;
2382 }
2383 if (direction == Direction.FORWARD) {
2384 idx++;
2385 } else {
2386 idx--;
2387 }
2388 }
2389 if (foundRank == -1) {
2390 if (direction == Direction.FORWARD) {
2391 rank++;
f4e06774 2392 if (rank > (trace.getNbEvents() - 1)) {
db4721fb
PT
2393 rank = 0;
2394 }
2395 } else {
2396 rank--;
2397 if (rank < 0) {
f4e06774 2398 rank = (int) trace.getNbEvents() - 1;
db4721fb
PT
2399 }
2400 }
2401 }
2402 }
2403 final int startRank = (int) rank;
2404 boolean wrapped = false;
f4e06774
FLN
2405 while (!monitor.isCanceled() && (foundRank == -1)) {
2406 int nbRequested = (direction == Direction.FORWARD ? Integer.MAX_VALUE : Math.min((int) rank + 1, trace.getCacheSize()));
db4721fb 2407 if (direction == Direction.BACKWARD) {
f4e06774 2408 rank = Math.max(0, rank - trace.getCacheSize() + 1);
db4721fb 2409 }
fd3f1eff
AM
2410 request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY,
2411 (int) rank, nbRequested, ExecutionType.BACKGROUND) {
db4721fb
PT
2412 long currentRank = rank;
2413
2414 @Override
2415 public void handleData(final ITmfEvent event) {
2416 super.handleData(event);
2417 if (searchFilter.matches(event) && ((eventFilter == null) || eventFilter.matches(event))) {
2418 foundRank = currentRank;
ae3ffd37 2419 foundTimestamp = event.getTimestamp();
db4721fb
PT
2420 if (direction == Direction.FORWARD) {
2421 done();
2422 return;
2423 }
2424 }
2425 currentRank++;
2426 }
2427 };
f4e06774 2428 ((ITmfEventProvider) trace).sendRequest(request);
db4721fb
PT
2429 try {
2430 request.waitForCompletion();
2431 if (request.isCancelled()) {
2432 return Status.OK_STATUS;
2433 }
2434 } catch (final InterruptedException e) {
2435 synchronized (fSearchSyncObj) {
2436 fSearchThread = null;
2437 }
2438 return Status.OK_STATUS;
2439 }
2440 if (foundRank == -1) {
2441 if (direction == Direction.FORWARD) {
2442 if (rank == 0) {
2443 synchronized (fSearchSyncObj) {
2444 fSearchThread = null;
2445 }
2446 return Status.OK_STATUS;
2447 }
2448 nbRequested = (int) rank;
2449 rank = 0;
2450 wrapped = true;
2451 } else {
2452 rank--;
2453 if (rank < 0) {
f4e06774 2454 rank = (int) trace.getNbEvents() - 1;
db4721fb
PT
2455 wrapped = true;
2456 }
2457 if ((rank <= startRank) && wrapped) {
2458 synchronized (fSearchSyncObj) {
2459 fSearchThread = null;
2460 }
2461 return Status.OK_STATUS;
2462 }
2463 }
2464 }
2465 }
2466 int index = (int) foundRank;
2467 if (eventFilter != null) {
2468 index = fCache.getFilteredEventIndex(foundRank);
2469 }
8b7eb3cd
MK
2470 /* +1 for header row, +1 for top filter status row */
2471 final int selection = index + 1 + (eventFilter != null ? +1 : 0);
db4721fb
PT
2472
2473 display.asyncExec(new Runnable() {
2474 @Override
2475 public void run() {
2476 if (monitor.isCanceled()) {
2477 return;
2478 }
2479 if (fTable.isDisposed()) {
2480 return;
2481 }
2482 fTable.setSelection(selection);
2483 fSelectedRank = foundRank;
9ba0a108 2484 fSelectedBeginRank = fSelectedRank;
ae3ffd37
PT
2485 fRawViewer.selectAndReveal(fSelectedRank);
2486 if (foundTimestamp != null) {
97c71024 2487 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, foundTimestamp));
ae3ffd37 2488 }
a56ec2b8 2489 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, getSelection()));
db4721fb
PT
2490 synchronized (fSearchSyncObj) {
2491 fSearchThread = null;
2492 }
3f43dc48 2493 updateStatusLine(null);
db4721fb
PT
2494 }
2495 });
2496 return Status.OK_STATUS;
2497 }
2498
2499 @Override
2500 protected void canceling() {
0a08e17d
PT
2501 if (request != null) {
2502 request.cancel();
2503 }
db4721fb
PT
2504 synchronized (fSearchSyncObj) {
2505 fSearchThread = null;
2506 }
2507 }
2508 }
2509
a0a88f65
AM
2510 /**
2511 * Create the resources.
2512 */
a890f499 2513 private void createResources() {
8b7eb3cd 2514 fGrayColor = fResourceManager.createColor(ColorUtil.blend(fTable.getBackground().getRGB(), fTable.getForeground().getRGB()));
78688b59 2515 fGreenColor = PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN);
812e7197
PT
2516 }
2517
2518 /**
2519 * Initialize the fonts.
812e7197 2520 */
a890f499 2521 private void initializeFonts() {
812e7197
PT
2522 FontRegistry fontRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry();
2523 fFont = fontRegistry.get(FONT_DEFINITION_ID);
2524 fBoldFont = fontRegistry.getBold(FONT_DEFINITION_ID);
2525 fTable.setFont(fFont);
2526 /* Column header font cannot be set. See Bug 63038 */
2527 }
2528
bb7c657f
PT
2529 /**
2530 * Initialize the colors.
bb7c657f 2531 */
a890f499 2532 private void initializeColors() {
bb7c657f
PT
2533 ColorRegistry colorRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry();
2534 fHighlightColor = colorRegistry.get(HIGHLIGHT_COLOR_DEFINITION_ID);
2535 }
2536
812e7197
PT
2537 /**
2538 * @since 1.0
2539 */
2540 @Override
2541 public void propertyChange(PropertyChangeEvent event) {
2542 if ((IThemeManager.CHANGE_CURRENT_THEME.equals(event.getProperty())) ||
2543 (FONT_DEFINITION_ID.equals(event.getProperty()))) {
2544 initializeFonts();
2545 fTable.refresh();
2546 }
bb7c657f
PT
2547 if ((IThemeManager.CHANGE_CURRENT_THEME.equals(event.getProperty())) ||
2548 (HIGHLIGHT_COLOR_DEFINITION_ID.equals(event.getProperty()))) {
2549 initializeColors();
2550 fTable.refresh();
2551 }
db4721fb
PT
2552 }
2553
a0a88f65
AM
2554 /**
2555 * Pack the columns.
2556 */
db4721fb
PT
2557 protected void packColumns() {
2558 if (fPackDone) {
2559 return;
2560 }
d3bc98ee 2561 fTable.setRedraw(false);
03142470
BH
2562 try {
2563 TableColumn tableColumns[] = fTable.getColumns();
2564 for (int i = 0; i < tableColumns.length; i++) {
2565 final TableColumn column = tableColumns[i];
2566 packSingleColumn(i, column);
2567 }
346ffed7 2568
03142470
BH
2569 } finally {
2570 // Make sure that redraw is always enabled.
2571 fTable.setRedraw(true);
2572 }
2573 fPackDone = true;
2574 }
d3bc98ee 2575
03142470
BH
2576 private void packMarginColumn() {
2577 TableColumn[] columns = fTable.getColumns();
2578 if (columns.length > 0) {
2579 packSingleColumn(0, columns[0]);
2580 }
2581 }
f29f8868 2582
03142470
BH
2583 private void packSingleColumn(int i, final TableColumn column) {
2584 final int headerWidth = column.getWidth();
2585 column.pack();
8b7eb3cd
MK
2586 /*
2587 * Workaround for Linux which doesn't consider the image width of
2588 * search/filter row in TableColumn.pack() after having executed
2589 * TableItem.setImage(null) for other rows than search/filter row.
2590 */
0a08e17d 2591 if (IS_LINUX && (i == 0) && fCollapseFilterEnabled) {
03142470 2592 column.setWidth(column.getWidth() + SEARCH_IMAGE.getBounds().width);
db4721fb 2593 }
d3bc98ee 2594
03142470
BH
2595 if (column.getWidth() < headerWidth) {
2596 column.setWidth(headerWidth);
2597 }
db4721fb
PT
2598 }
2599
7697e148
PT
2600 /**
2601 * Returns true if the column is a visible event column.
2602 *
2603 * @param column the column
2604 * @return false if the column is the margin column or hidden, true otherwise
2605 */
2606 private static boolean isVisibleEventColumn(TableColumn column) {
2607 if (column.getData(Key.ASPECT) == TmfMarginColumn.MARGIN_ASPECT) {
2608 return false;
2609 }
2610 if (!column.getResizable() && column.getWidth() == 0) {
2611 return false;
2612 }
2613 return true;
2614 }
2615
d3de0920 2616 /**
baafe54c
AM
2617 * Get the array of item strings (e.g., what to display in each cell of the
2618 * table row) corresponding to the columns and trace event passed in
2619 * parameter. The order of the Strings in the returned array will correspond
2620 * to the iteration order of 'columns'.
d3de0920 2621 *
baafe54c
AM
2622 * <p>
2623 * To ensure consistent results, make sure only call this within a scope
2624 * synchronized on 'columns'! If the order of 'columns' changes right after
2625 * this method is called, the returned value won't be ordered correctly
2626 * anymore.
2627 */
2628 private static String[] getItemStrings(List<TmfEventTableColumn> columns, ITmfEvent event) {
2629 if (event == null) {
2630 return EMPTY_STRING_ARRAY;
2631 }
2632 synchronized (columns) {
2633 List<String> itemStrings = new ArrayList<>(columns.size());
2634 for (TmfEventTableColumn column : columns) {
03142470
BH
2635 ITmfEvent passedEvent = event;
2636 if (!(column instanceof TmfMarginColumn) && (event instanceof CachedEvent)) {
8b7eb3cd
MK
2637 /*
2638 * Make sure that the event object from the trace is passed
2639 * to all columns but the TmfMarginColumn
2640 */
03142470
BH
2641 passedEvent = ((CachedEvent) event).event;
2642 }
2643 if (passedEvent == null) {
2644 itemStrings.add(EMPTY_STRING);
2645 } else {
2646 itemStrings.add(column.getItemString(passedEvent));
2647 }
2648
baafe54c
AM
2649 }
2650 return itemStrings.toArray(new String[0]);
2651 }
2652 }
2653
2654 /**
2655 * Get the contents of the row in the events table corresponding to an
2656 * event. The order of the elements corresponds to the current order of the
2657 * columns.
a0a88f65 2658 *
db4721fb 2659 * @param event
cf37ad9f
AM
2660 * The event printed in this row
2661 * @return The event row entries
db4721fb 2662 */
cf37ad9f 2663 public String[] getItemStrings(ITmfEvent event) {
6817308e
PT
2664 List<TmfEventTableColumn> columns = new ArrayList<>();
2665 for (int i : fTable.getColumnOrder()) {
2666 columns.add(fColumns.get(i));
2667 }
2668 return getItemStrings(columns, event);
db4721fb
PT
2669 }
2670
14665360 2671 /**
8b7eb3cd
MK
2672 * Returns an array of zero-relative integers that map the creation order of
2673 * the receiver's columns to the order in which they are currently being
2674 * displayed.
14665360 2675 * <p>
8b7eb3cd
MK
2676 * Specifically, the indices of the returned array represent the current
2677 * visual order of the columns, and the contents of the array represent the
2678 * creation order of the columns.
14665360
PT
2679 *
2680 * @return the current visual order of the receiver's columns
2681 * @since 1.0
2682 */
2683 public int[] getColumnOrder() {
2684 return fColumnOrder;
2685 }
2686
2687 /**
8b7eb3cd
MK
2688 * Sets the order that the columns in the receiver should be displayed in to
2689 * the given argument which is described in terms of the zero-relative
2690 * ordering of when the columns were added.
14665360 2691 * <p>
8b7eb3cd
MK
2692 * Specifically, the contents of the array represent the original position
2693 * of each column at the time its creation.
14665360 2694 *
8b7eb3cd
MK
2695 * @param order
2696 * the new order to display the columns
14665360
PT
2697 * @since 1.0
2698 */
2699 public void setColumnOrder(int[] order) {
2700 if (order == null || order.length != fTable.getColumns().length) {
2701 return;
2702 }
2703 fTable.setColumnOrder(order);
2704 fColumnOrder = fTable.getColumnOrder();
2705 }
2706
db4721fb
PT
2707 /**
2708 * Notify this table that is got the UI focus.
2709 */
2710 public void setFocus() {
2711 fTable.setFocus();
2712 }
2713
0bc16991
MAL
2714 /**
2715 * Registers context menus with a site for extension. This method can be
2716 * called for part sites so that context menu contributions can be added.
2717 *
2718 * @param site
2719 * the site that the context menus will be registered for
2720 *
2721 * @since 2.0
2722 */
2723 public void registerContextMenus(IWorkbenchPartSite site) {
2724 if (site instanceof IEditorSite) {
2725 IEditorSite editorSite = (IEditorSite) site;
2726 // Don't use the editor input when adding contributions, otherwise
2727 // we get too many unwanted things.
2728 editorSite.registerContextMenu(fTablePopupMenuManager, this, false);
2729 }
2730 }
2731
db4721fb
PT
2732 /**
2733 * Assign a new trace to this event table.
2734 *
2735 * @param trace
2736 * The trace to assign to this event table
2737 * @param disposeOnClose
2738 * true if the trace should be disposed when the table is
2739 * disposed
2740 */
2741 public void setTrace(final ITmfTrace trace, final boolean disposeOnClose) {
2742 if ((fTrace != null) && fDisposeOnClose) {
2743 fTrace.dispose();
2744 }
2745 fTrace = trace;
2746 fPackDone = false;
db4721fb
PT
2747 fDisposeOnClose = disposeOnClose;
2748
2749 // Perform the updates on the UI thread
78688b59 2750 PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
db4721fb
PT
2751 @Override
2752 public void run() {
9ba0a108
PT
2753 fSelectedRank = -1;
2754 fSelectedBeginRank = -1;
db4721fb 2755 fTable.removeAll();
f4e06774
FLN
2756 fCache.setTrace(trace); // Clear the cache
2757 if (trace != null) {
2758 if (!fTable.isDisposed()) {
db4721fb 2759 if (fTable.getData(Key.FILTER_OBJ) == null) {
8b7eb3cd 2760 // +1 for header row
f4e06774 2761 fTable.setItemCount((int) trace.getNbEvents() + 1);
db4721fb
PT
2762 } else {
2763 stopFilterThread();
2764 fFilterMatchCount = 0;
2765 fFilterCheckCount = 0;
8b7eb3cd
MK
2766 /*
2767 * +1 for header row, +2 for top and bottom filter
2768 * status rows
2769 */
2770 fTable.setItemCount(3);
db4721fb
PT
2771 startFilterThread();
2772 }
2773 }
db4721fb 2774 }
f4e06774 2775 fRawViewer.setTrace(trace);
db4721fb
PT
2776 }
2777 });
2778 }
2779
3f43dc48
PT
2780 /**
2781 * Assign the status line manager
2782 *
2783 * @param statusLineManager
8b7eb3cd
MK
2784 * The status line manager, or null to disable status line
2785 * messages
3f43dc48
PT
2786 */
2787 public void setStatusLineManager(IStatusLineManager statusLineManager) {
2788 if (fStatusLineManager != null && statusLineManager == null) {
03142470 2789 fStatusLineManager.setMessage(EMPTY_STRING);
3f43dc48
PT
2790 }
2791 fStatusLineManager = statusLineManager;
2792 }
2793
2794 private void updateStatusLine(ITmfTimestamp delta) {
2795 if (fStatusLineManager != null) {
2796 if (delta != null) {
2797 fStatusLineManager.setMessage("\u0394: " + delta); //$NON-NLS-1$
2798 } else {
2799 fStatusLineManager.setMessage(null);
2800 }
2801 }
2802 }
2803
db4721fb
PT
2804 // ------------------------------------------------------------------------
2805 // Event cache
2806 // ------------------------------------------------------------------------
2807
2808 /**
2809 * Notify that the event cache has been updated
2810 *
2811 * @param completed
2812 * Also notify if the populating of the cache is complete, or
2813 * not.
2814 */
2815 public void cacheUpdated(final boolean completed) {
2816 synchronized (fCacheUpdateSyncObj) {
2817 if (fCacheUpdateBusy) {
2818 fCacheUpdatePending = true;
2819 fCacheUpdateCompleted = completed;
2820 return;
2821 }
2822 fCacheUpdateBusy = true;
2823 }
2824 // Event cache is now updated. Perform update on the UI thread
78688b59
PT
2825 PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
2826 @Override
2827 public void run() {
2828 if (!fTable.isDisposed()) {
2829 fTable.refresh();
2830 packColumns();
2831 }
2832 if (completed) {
2833 populateCompleted();
2834 }
2835 synchronized (fCacheUpdateSyncObj) {
2836 fCacheUpdateBusy = false;
2837 if (fCacheUpdatePending) {
2838 fCacheUpdatePending = false;
2839 cacheUpdated(fCacheUpdateCompleted);
db4721fb
PT
2840 }
2841 }
78688b59
PT
2842 }
2843 });
db4721fb
PT
2844 }
2845
a0a88f65
AM
2846 /**
2847 * Callback for when populating the table is complete.
2848 */
db4721fb
PT
2849 protected void populateCompleted() {
2850 // Nothing by default;
2851 }
2852
93bfd50a
PT
2853 // ------------------------------------------------------------------------
2854 // ISelectionProvider
2855 // ------------------------------------------------------------------------
2856
93bfd50a
PT
2857 @Override
2858 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2859 selectionChangedListeners.add(listener);
2860 }
2861
93bfd50a
PT
2862 @Override
2863 public ISelection getSelection() {
2864 if (fTable == null || fTable.isDisposed()) {
2865 return StructuredSelection.EMPTY;
2866 }
507b1336 2867 List<Object> list = new ArrayList<>(fTable.getSelection().length);
93bfd50a
PT
2868 for (TableItem item : fTable.getSelection()) {
2869 if (item.getData() != null) {
2870 list.add(item.getData());
2871 }
2872 }
2873 return new StructuredSelection(list);
2874 }
2875
93bfd50a
PT
2876 @Override
2877 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2878 selectionChangedListeners.remove(listener);
2879 }
2880
93bfd50a
PT
2881 @Override
2882 public void setSelection(ISelection selection) {
2883 // not implemented
2884 }
2885
2886 /**
8b7eb3cd
MK
2887 * Notifies any selection changed listeners that the viewer's selection has
2888 * changed. Only listeners registered at the time this method is called are
2889 * notified.
93bfd50a 2890 *
8b7eb3cd
MK
2891 * @param event
2892 * a selection changed event
93bfd50a
PT
2893 *
2894 * @see ISelectionChangedListener#selectionChanged
93bfd50a
PT
2895 */
2896 protected void fireSelectionChanged(final SelectionChangedEvent event) {
2897 Object[] listeners = selectionChangedListeners.getListeners();
2898 for (int i = 0; i < listeners.length; ++i) {
2899 final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
2900 SafeRunnable.run(new SafeRunnable() {
faa38350 2901 @Override
93bfd50a
PT
2902 public void run() {
2903 l.selectionChanged(event);
2904 }
2905 });
2906 }
2907 }
2908
db4721fb
PT
2909 // ------------------------------------------------------------------------
2910 // Bookmark handling
2911 // ------------------------------------------------------------------------
2912
2913 /**
2914 * Add a bookmark to this event table.
2915 *
2916 * @param bookmarksFile
2917 * The file to use for the bookmarks
2918 */
2919 public void addBookmark(final IFile bookmarksFile) {
2920 fBookmarksFile = bookmarksFile;
2921 final TableItem[] selection = fTable.getSelection();
2922 if (selection.length > 0) {
2923 final TableItem tableItem = selection[0];
2924 if (tableItem.getData(Key.RANK) != null) {
2925 final StringBuffer defaultMessage = new StringBuffer();
7697e148
PT
2926 for (int i : fTable.getColumnOrder()) {
2927 TableColumn column = fTable.getColumns()[i];
2928 // Omit the margin column and hidden columns
2929 if (isVisibleEventColumn(column)) {
2930 if (defaultMessage.length() > 0) {
2931 defaultMessage.append(", "); //$NON-NLS-1$
2932 }
2933 defaultMessage.append(tableItem.getText(i));
db4721fb 2934 }
db4721fb 2935 }
7697e148
PT
2936 final AddBookmarkDialog dialog = new AddBookmarkDialog(
2937 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), defaultMessage.toString());
db4721fb
PT
2938 if (dialog.open() == Window.OK) {
2939 final String message = dialog.getValue();
2940 try {
7697e148
PT
2941 final Long rank = (Long) tableItem.getData(Key.RANK);
2942 final String location = NLS.bind(Messages.TmfMarker_LocationRank, rank.toString());
2943 final ITmfTimestamp timestamp = (ITmfTimestamp) tableItem.getData(Key.TIMESTAMP);
2944 final long[] id = new long[1];
2945 ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
2946 @Override
2947 public void run(IProgressMonitor monitor) throws CoreException {
2948 final IMarker bookmark = bookmarksFile.createMarker(IMarker.BOOKMARK);
2949 bookmark.setAttribute(IMarker.MESSAGE, message.toString());
2950 bookmark.setAttribute(IMarker.LOCATION, location);
2951 bookmark.setAttribute(ITmfMarker.MARKER_RANK, rank.toString());
2952 bookmark.setAttribute(ITmfMarker.MARKER_TIME, Long.toString(new TmfNanoTimestamp(timestamp).getValue()));
2953 bookmark.setAttribute(ITmfMarker.MARKER_COLOR, dialog.getColorValue().toString());
2954 id[0] = bookmark.getId();
2955 }
2956 }, null);
2957 fBookmarksMap.put(rank, id[0]);
2958 fTable.refresh();
db4721fb
PT
2959 } catch (final CoreException e) {
2960 displayException(e);
2961 }
2962 }
2963 }
2964 }
2965
2966 }
2967
2968 /**
7697e148 2969 * Add one or more bookmarks to this event table.
db4721fb 2970 *
7697e148
PT
2971 * @param bookmarks
2972 * The bookmarks to add
2973 * @since 2.0
db4721fb 2974 */
7697e148
PT
2975 public void addBookmark(final IMarker... bookmarks) {
2976 for (IMarker bookmark : bookmarks) {
2977 /* try location as an integer for backward compatibility */
2978 long rank = bookmark.getAttribute(IMarker.LOCATION, -1);
2979 if (rank == -1) {
2980 String rankString = bookmark.getAttribute(ITmfMarker.MARKER_RANK, (String) null);
2981 if (rankString != null) {
2982 try {
2983 rank = Long.parseLong(rankString);
2984 } catch (NumberFormatException e) {
2985 Activator.getDefault().logError("Invalid marker rank", e); //$NON-NLS-1$
2986 }
2987 }
2988 }
2989 if (rank != -1) {
2990 fBookmarksMap.put(rank, bookmark.getId());
db4721fb
PT
2991 }
2992 }
7697e148
PT
2993 fTable.refresh();
2994 }
2995
2996 /**
2997 * Remove one or more bookmarks from this event table.
2998 *
2999 * @param bookmarks
3000 * The bookmarks to remove
3001 * @since 2.0
3002 */
3003 public void removeBookmark(final IMarker... bookmarks) {
3004 for (IMarker bookmark : bookmarks) {
3005 for (final Entry<Long, Long> entry : fBookmarksMap.entries()) {
3006 if (entry.getValue().equals(bookmark.getId())) {
3007 fBookmarksMap.remove(entry.getKey(), entry.getValue());
3008 break;
3009 }
3010 }
3011 }
3012 fTable.refresh();
db4721fb
PT
3013 }
3014
0126a8ca 3015 private void toggleBookmark(final Long rank) {
db4721fb
PT
3016 if (fBookmarksFile == null) {
3017 return;
3018 }
3019 if (fBookmarksMap.containsKey(rank)) {
2db176a0 3020 final Collection<Long> markerIds = fBookmarksMap.removeAll(rank);
db4721fb
PT
3021 fTable.refresh();
3022 try {
2db176a0
PT
3023 for (long markerId : markerIds) {
3024 final IMarker bookmark = fBookmarksFile.findMarker(markerId);
3025 if (bookmark != null) {
3026 bookmark.delete();
3027 }
db4721fb
PT
3028 }
3029 } catch (final CoreException e) {
3030 displayException(e);
3031 }
3032 } else {
3033 addBookmark(fBookmarksFile);
3034 }
3035 }
3036
3037 /**
3038 * Refresh the bookmarks assigned to this trace, from the contents of a
3039 * bookmark file.
3040 *
3041 * @param bookmarksFile
3042 * The bookmark file to use
3043 */
3044 public void refreshBookmarks(final IFile bookmarksFile) {
3045 fBookmarksFile = bookmarksFile;
3046 if (bookmarksFile == null) {
3047 fBookmarksMap.clear();
3048 fTable.refresh();
3049 return;
3050 }
3051 try {
3052 fBookmarksMap.clear();
7697e148
PT
3053 IMarker[] bookmarks = bookmarksFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO);
3054 addBookmark(bookmarks);
db4721fb
PT
3055 } catch (final CoreException e) {
3056 displayException(e);
3057 }
3058 }
3059
3060 @Override
3061 public void gotoMarker(final IMarker marker) {
7697e148
PT
3062 ITmfTimestamp tsBegin = null;
3063 ITmfTimestamp tsEnd = null;
3064 /* try location as an integer for backward compatibility */
3065 long rank = marker.getAttribute(IMarker.LOCATION, -1);
3066 if (rank == -1) {
3067 String rankString = marker.getAttribute(ITmfMarker.MARKER_RANK, (String) null);
3068 try {
3069 rank = Long.parseLong(rankString);
3070 } catch (NumberFormatException e) {
3071 /* ignored */
3072 }
3073 }
3074 try {
3075 String timeString = marker.getAttribute(ITmfMarker.MARKER_TIME, (String) null);
3076 long time = Long.parseLong(timeString);
3077 tsBegin = new TmfNanoTimestamp(time);
3078 String durationString = marker.getAttribute(ITmfMarker.MARKER_DURATION, (String) null);
3079 long duration = Long.parseLong(durationString);
3080 tsEnd = new TmfNanoTimestamp(time + duration);
3081 } catch (NumberFormatException e) {
3082 /* ignored */
3083 }
3084 if (rank == -1 && tsBegin != null) {
3085 final ITmfContext context = fTrace.seekEvent(tsBegin);
3086 rank = context.getRank();
3087 context.dispose();
3088 }
db4721fb 3089 if (rank != -1) {
7697e148 3090 int index = (int) rank;
db4721fb 3091 if (fTable.getData(Key.FILTER_OBJ) != null) {
8b7eb3cd
MK
3092 // +1 for top filter status row
3093 index = fCache.getFilteredEventIndex(rank) + 1;
db4721fb
PT
3094 } else if (rank >= fTable.getItemCount()) {
3095 fPendingGotoRank = rank;
3096 }
a56ec2b8 3097 fSelectedRank = rank;
9ba0a108 3098 fSelectedBeginRank = fSelectedRank;
db4721fb 3099 fTable.setSelection(index + 1); // +1 for header row
3f43dc48 3100 updateStatusLine(null);
7697e148
PT
3101 if (tsBegin != null) {
3102 if (tsEnd != null) {
3103 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, tsBegin, tsEnd));
3104 } else {
3105 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, tsBegin));
3106 }
3107 }
db4721fb
PT
3108 }
3109 }
3110
3111 // ------------------------------------------------------------------------
3112 // Listeners
3113 // ------------------------------------------------------------------------
3114
db4721fb
PT
3115 @Override
3116 public void colorSettingsChanged(final ColorSetting[] colorSettings) {
3117 fTable.refresh();
3118 }
3119
db4721fb
PT
3120 // ------------------------------------------------------------------------
3121 // Signal handlers
3122 // ------------------------------------------------------------------------
3123
db4721fb
PT
3124 /**
3125 * Handler for the trace updated signal
3126 *
3127 * @param signal
3128 * The incoming signal
3129 */
3130 @TmfSignalHandler
3131 public void traceUpdated(final TmfTraceUpdatedSignal signal) {
3132 if ((signal.getTrace() != fTrace) || fTable.isDisposed()) {
3133 return;
3134 }
3135 // Perform the refresh on the UI thread
3136 Display.getDefault().asyncExec(new Runnable() {
3137 @Override
3138 public void run() {
3139 if (!fTable.isDisposed() && (fTrace != null)) {
3140 if (fTable.getData(Key.FILTER_OBJ) == null) {
8b7eb3cd
MK
3141 /* +1 for header row */
3142 fTable.setItemCount((int) fTrace.getNbEvents() + 1);
3143 /* +1 for header row */
3144 if ((fPendingGotoRank != -1) && ((fPendingGotoRank + 1) < fTable.getItemCount())) {
3145 /* +1 for header row */
3146 fTable.setSelection((int) fPendingGotoRank + 1);
db4721fb 3147 fPendingGotoRank = -1;
3f43dc48 3148 updateStatusLine(null);
db4721fb
PT
3149 }
3150 } else {
3151 startFilterThread();
3152 }
3153 }
3154 if (!fRawViewer.isDisposed() && (fTrace != null)) {
3155 fRawViewer.refreshEventCount();
3156 }
3157 }
3158 });
3159 }
3160
3161 /**
97c71024 3162 * Handler for the selection range signal.
db4721fb
PT
3163 *
3164 * @param signal
3165 * The incoming signal
97c71024 3166 * @since 1.0
db4721fb
PT
3167 */
3168 @TmfSignalHandler
97c71024 3169 public void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) {
db4721fb
PT
3170 if ((signal.getSource() != this) && (fTrace != null) && (!fTable.isDisposed())) {
3171
8b7eb3cd
MK
3172 /*
3173 * Create a request for one event that will be queued after other
3174 * ongoing requests. When this request is completed do the work to
3175 * select the actual event with the timestamp specified in the
3176 * signal. This procedure prevents the method fTrace.getRank() from
3177 * interfering and delaying ongoing requests.
3178 */
fd3f1eff
AM
3179 final TmfEventRequest subRequest = new TmfEventRequest(ITmfEvent.class,
3180 TmfTimeRange.ETERNITY, 0, 1, ExecutionType.FOREGROUND) {
db4721fb 3181
0fcf3b09 3182 TmfTimestamp ts = new TmfTimestamp(signal.getBeginTime());
8b6aedef 3183 TmfTimestamp tf = new TmfTimestamp(signal.getEndTime());
db4721fb
PT
3184
3185 @Override
73fce654
AM
3186 public void handleSuccess() {
3187 super.handleSuccess();
db4721fb
PT
3188 if (fTrace == null) {
3189 return;
3190 }
3191
8b6aedef
JCK
3192 final Pair<Long, Long> selection = getSelectedRanks();
3193 updateDisplayWithSelection(selection.getFirst().longValue(), selection.getSecond().longValue());
3194 }
3195
3196 /**
3197 * Verify if the event is within the trace range and adjust if
3198 * necessary.
3199 *
3200 * @return A pair of rank representing the selected area
3201 **/
3202 private Pair<Long, Long> getSelectedRanks() {
3203
3204 /* Clamp the timestamp value to fit inside of the trace */
3205 ITmfTimestamp timestampBegin = ts;
3206 if (timestampBegin.compareTo(fTrace.getStartTime()) < 0) {
3207 timestampBegin = fTrace.getStartTime();
db4721fb 3208 }
8b6aedef
JCK
3209 if (timestampBegin.compareTo(fTrace.getEndTime()) > 0) {
3210 timestampBegin = fTrace.getEndTime();
db4721fb
PT
3211 }
3212
8b6aedef
JCK
3213 ITmfTimestamp timestampEnd = tf;
3214 if (timestampEnd.compareTo(fTrace.getStartTime()) < 0) {
3215 timestampEnd = fTrace.getStartTime();
3216 }
3217 if (timestampEnd.compareTo(fTrace.getEndTime()) > 0) {
3218 timestampEnd = fTrace.getEndTime();
3219 }
db4721fb 3220
8b6aedef
JCK
3221 ITmfTimestamp tb;
3222 ITmfTimestamp te;
3223 long rankBegin;
3224 long rankEnd;
3225 ITmfContext contextBegin;
3226 ITmfContext contextEnd;
3227
3228 /* Adjust the rank of the selection to the right range */
3229 if (timestampBegin.compareTo(timestampEnd) > 0) {
3230 te = timestampEnd;
3231 contextEnd = fTrace.seekEvent(te);
3232 rankEnd = contextEnd.getRank();
3233 contextEnd.dispose();
3234 /* To include all events at the begin time, seek at the next nanosecond and then use the previous rank */
3235 tb = timestampBegin.normalize(1, ITmfTimestamp.NANOSECOND_SCALE);
3236 if (tb.compareTo(fTrace.getEndTime()) <= 0) {
3237 contextBegin = fTrace.seekEvent(tb);
3238 rankBegin = contextBegin.getRank();
3239 contextBegin.dispose();
3240 } else {
3241 rankBegin = ITmfContext.UNKNOWN_RANK;
3242 }
3243 rankBegin = (rankBegin == ITmfContext.UNKNOWN_RANK ? fTrace.getNbEvents() : rankBegin) - 1;
3244 /* If no events in selection range, select only the next event */
3245 rankBegin = rankBegin >= rankEnd ? rankBegin : rankEnd;
3246 } else {
3247 tb = timestampBegin;
3248 contextBegin = fTrace.seekEvent(tb);
3249 rankBegin = contextBegin.getRank();
3250 contextBegin.dispose();
3251 /* To include all events at the end time, seek at the next nanosecond and then use the previous rank */
3252 te = timestampEnd.normalize(1, ITmfTimestamp.NANOSECOND_SCALE);
3253 if (te.compareTo(fTrace.getEndTime()) <= 0) {
3254 contextEnd = fTrace.seekEvent(te);
3255 rankEnd = contextEnd.getRank();
3256 contextEnd.dispose();
3257 } else {
3258 rankEnd = ITmfContext.UNKNOWN_RANK;
3259 }
3260 rankEnd = (rankEnd == ITmfContext.UNKNOWN_RANK ? fTrace.getNbEvents() : rankEnd) - 1;
3261 /* If no events in selection range, select only the next event */
3262 rankEnd = rankEnd >= rankBegin ? rankEnd : rankBegin;
3263 }
3264 return new Pair<>(Long.valueOf(rankBegin), Long.valueOf(rankEnd));
3265 }
3266
3267 private void updateDisplayWithSelection(final long rankBegin, final long rankEnd) {
78688b59 3268 PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
db4721fb
PT
3269 @Override
3270 public void run() {
3271 // Return if table is disposed
3272 if (fTable.isDisposed()) {
3273 return;
3274 }
3275
8b6aedef
JCK
3276 fSelectedRank = rankEnd;
3277 long toReveal = fSelectedBeginRank != rankBegin ? rankBegin : rankEnd;
3278 fSelectedBeginRank = rankBegin;
3279 int indexBegin = (int) rankBegin;
3280 int indexEnd = (int) rankEnd;
3281
60fb38b8 3282 if (fTable.getData(Key.FILTER_OBJ) != null) {
8b7eb3cd 3283 /* +1 for top filter status row */
8b6aedef
JCK
3284 indexBegin = fCache.getFilteredEventIndex(rankBegin) + 1;
3285 indexEnd = rankEnd == rankBegin ? indexBegin : fCache.getFilteredEventIndex(rankEnd) + 1;
db4721fb 3286 }
8b7eb3cd 3287 /* +1 for header row */
8b6aedef
JCK
3288 fTable.setSelectionRange(indexBegin + 1, indexEnd + 1);
3289 fRawViewer.selectAndReveal(toReveal);
3f43dc48 3290 updateStatusLine(null);
db4721fb
PT
3291 }
3292 });
3293 }
3294 };
3295
fd3f1eff 3296 ((ITmfEventProvider) fTrace).sendRequest(subRequest);
db4721fb
PT
3297 }
3298 }
3299
3300 // ------------------------------------------------------------------------
3301 // Error handling
3302 // ------------------------------------------------------------------------
3303
3304 /**
3305 * Display an exception in a message box
3306 *
8b7eb3cd
MK
3307 * @param e
3308 * the exception
db4721fb
PT
3309 */
3310 private static void displayException(final Exception e) {
3311 final MessageBox mb = new MessageBox(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell());
60fb38b8 3312 mb.setText(e.getClass().getSimpleName());
db4721fb
PT
3313 mb.setMessage(e.getMessage());
3314 mb.open();
3315 }
3316
f8177ba2 3317 /**
ae09c4ad 3318 * Refresh the table
f8177ba2
FC
3319 */
3320 public void refresh() {
3321 fCache.clear();
3322 fTable.refresh();
3323 fTable.redraw();
3324 }
03142470
BH
3325
3326 /**
9447c7ee
AM
3327 * Margin column for images and special text (e.g. collapse count)
3328 */
3329 private static final class TmfMarginColumn extends TmfEventTableColumn {
3330
3331 private static final @NonNull ITmfEventAspect MARGIN_ASPECT = new ITmfEventAspect() {
3332
3333 @Override
3334 public String getName() {
3335 return EMPTY_STRING;
3336 }
3337
3338 @Override
3339 public String resolve(ITmfEvent event) {
3340 if (!(event instanceof CachedEvent) || ((CachedEvent) event).repeatCount == 0) {
3341 return EMPTY_STRING;
3342 }
3343 return "+" + ((CachedEvent) event).repeatCount; //$NON-NLS-1$
3344 }
3345
3346 @Override
3347 public String getHelpText() {
3348 return EMPTY_STRING;
3349 }
9447c7ee
AM
3350 };
3351
3352 /**
3353 * Constructor
3354 */
3355 public TmfMarginColumn() {
3356 super(MARGIN_ASPECT);
3357 }
3358 }
03142470 3359
db4721fb 3360}
This page took 0.435908 seconds and 5 git commands to generate.