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