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