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