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