tmf: Introduce ITmfEventTableColumns and its extension point
[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 StringBuilder header = new StringBuilder();
822 boolean needTab = false;
823 for (TableColumn tc: fTable.getColumns()) {
824 if (needTab) {
825 header.append('\t');
826 }
827 header.append(tc.getText());
828 needTab = true;
829 }
830 Command command = cmdService.getCommand(ExportToTextCommandHandler.COMMAND_ID);
831 ParameterizedCommand cmd = ParameterizedCommand.generateCommand(command,parameters);
832 IEvaluationContext context = handlerService.getCurrentState();
833 context.addVariable(ExportToTextCommandHandler.TMF_EVENT_TABLE_HEADER_ID, header.toString());
834 context.addVariable(ExportToTextCommandHandler.TMF_EVENT_TABLE_PARAMETER_ID, TmfEventsTable.this);
835 handlerService.executeCommandInContext(cmd, null, context);
836 } catch (ExecutionException e) {
837 displayException(e);
838 } catch (NotDefinedException e) {
839 displayException(e);
840 } catch (NotEnabledException e) {
841 displayException(e);
842 } catch (NotHandledException e) {
843 displayException(e);
844 }
845 }
846 };
847
848 final IAction showSearchBarAction = new Action(Messages.TmfEventsTable_ShowSearchBarActionText) {
849 @Override
850 public void run() {
851 fHeaderState = HeaderState.SEARCH;
852 fTable.refresh();
853 }
854 };
855
856 final IAction showFilterBarAction = new Action(Messages.TmfEventsTable_ShowFilterBarActionText) {
857 @Override
858 public void run() {
859 fHeaderState = HeaderState.FILTER;
860 fTable.refresh();
861 }
862 };
863
864 final IAction clearFiltersAction = new Action(Messages.TmfEventsTable_ClearFiltersActionText) {
865 @Override
866 public void run() {
867 clearFilters();
868 }
869 };
870
871 class ToggleBookmarkAction extends Action {
872 Long fRank;
873
874 public ToggleBookmarkAction(final String text, final Long rank) {
875 super(text);
876 fRank = rank;
877 }
878
879 @Override
880 public void run() {
881 toggleBookmark(fRank);
882 }
883 }
884
885 final MenuManager tablePopupMenu = new MenuManager();
886 tablePopupMenu.setRemoveAllWhenShown(true);
887 tablePopupMenu.addMenuListener(new IMenuListener() {
888 @Override
889 public void menuAboutToShow(final IMenuManager manager) {
890 if (fTable.getSelectionIndex() == 0) {
891 // Right-click on header row
892 if (fHeaderState == HeaderState.FILTER) {
893 tablePopupMenu.add(showSearchBarAction);
894 } else {
895 tablePopupMenu.add(showFilterBarAction);
896 }
897 return;
898 }
899 final Point point = fTable.toControl(Display.getDefault().getCursorLocation());
900 final TableItem item = fTable.getSelection().length > 0 ? fTable.getSelection()[0] : null;
901 if (item != null) {
902 final Rectangle imageBounds = item.getImageBounds(0);
903 imageBounds.width = BOOKMARK_IMAGE.getBounds().width;
904 if (point.x <= (imageBounds.x + imageBounds.width)) {
905 // Right-click on left margin
906 final Long rank = (Long) item.getData(Key.RANK);
907 if ((rank != null) && (fBookmarksFile != null)) {
908 if (fBookmarksMap.containsKey(rank)) {
909 tablePopupMenu.add(new ToggleBookmarkAction(
910 Messages.TmfEventsTable_RemoveBookmarkActionText, rank));
911 } else {
912 tablePopupMenu.add(new ToggleBookmarkAction(
913 Messages.TmfEventsTable_AddBookmarkActionText, rank));
914 }
915 }
916 return;
917 }
918 }
919
920 // Right-click on table
921 if (fTable.isVisible() && fRawViewer.isVisible()) {
922 tablePopupMenu.add(hideTableAction);
923 tablePopupMenu.add(hideRawAction);
924 } else if (!fTable.isVisible()) {
925 tablePopupMenu.add(showTableAction);
926 } else if (!fRawViewer.isVisible()) {
927 tablePopupMenu.add(showRawAction);
928 }
929 tablePopupMenu.add(exportToTextAction);
930 tablePopupMenu.add(new Separator());
931
932 if (item != null) {
933 final Object data = item.getData();
934 Separator separator = null;
935 if (data instanceof ITmfSourceLookup) {
936 ITmfSourceLookup event = (ITmfSourceLookup) data;
937 if (event.getCallsite() != null) {
938 tablePopupMenu.add(openCallsiteAction);
939 separator = new Separator();
940 }
941 }
942
943 if (data instanceof ITmfModelLookup) {
944 ITmfModelLookup event = (ITmfModelLookup) data;
945 if (event.getModelUri() != null) {
946 tablePopupMenu.add(openModelAction);
947 separator = new Separator();
948 }
949
950 if (separator != null) {
951 tablePopupMenu.add(separator);
952 }
953 }
954 }
955
956 tablePopupMenu.add(clearFiltersAction);
957 final ITmfFilterTreeNode[] savedFilters = FilterManager.getSavedFilters();
958 if (savedFilters.length > 0) {
959 final MenuManager subMenu = new MenuManager(Messages.TmfEventsTable_ApplyPresetFilterMenuName);
960 for (final ITmfFilterTreeNode node : savedFilters) {
961 if (node instanceof TmfFilterNode) {
962 final TmfFilterNode filter = (TmfFilterNode) node;
963 subMenu.add(new Action(filter.getFilterName()) {
964 @Override
965 public void run() {
966 applyFilter(filter);
967 }
968 });
969 }
970 }
971 tablePopupMenu.add(subMenu);
972 }
973 appendToTablePopupMenu(tablePopupMenu, item);
974 }
975 });
976
977 final MenuManager rawViewerPopupMenu = new MenuManager();
978 rawViewerPopupMenu.setRemoveAllWhenShown(true);
979 rawViewerPopupMenu.addMenuListener(new IMenuListener() {
980 @Override
981 public void menuAboutToShow(final IMenuManager manager) {
982 if (fTable.isVisible() && fRawViewer.isVisible()) {
983 rawViewerPopupMenu.add(hideTableAction);
984 rawViewerPopupMenu.add(hideRawAction);
985 } else if (!fTable.isVisible()) {
986 rawViewerPopupMenu.add(showTableAction);
987 } else if (!fRawViewer.isVisible()) {
988 rawViewerPopupMenu.add(showRawAction);
989 }
990 appendToRawPopupMenu(tablePopupMenu);
991 }
992 });
993
994 Menu menu = tablePopupMenu.createContextMenu(fTable);
995 fTable.setMenu(menu);
996
997 menu = rawViewerPopupMenu.createContextMenu(fRawViewer);
998 fRawViewer.setMenu(menu);
999 }
1000
1001
1002 /**
1003 * Append an item to the event table's pop-up menu.
1004 *
1005 * @param tablePopupMenu
1006 * The menu manager
1007 * @param selectedItem
1008 * The item to append
1009 */
1010 protected void appendToTablePopupMenu(final MenuManager tablePopupMenu, final TableItem selectedItem) {
1011 // override to append more actions
1012 }
1013
1014 /**
1015 * Append an item to the raw viewer's pop-up menu.
1016 *
1017 * @param rawViewerPopupMenu
1018 * The menu manager
1019 */
1020 protected void appendToRawPopupMenu(final MenuManager rawViewerPopupMenu) {
1021 // override to append more actions
1022 }
1023
1024 @Override
1025 public void dispose() {
1026 stopSearchThread();
1027 stopFilterThread();
1028 ColorSettingsManager.removeColorSettingsListener(this);
1029 fComposite.dispose();
1030 if ((fTrace != null) && fDisposeOnClose) {
1031 fTrace.dispose();
1032 }
1033 fResourceManager.dispose();
1034 fRawViewer.dispose();
1035 super.dispose();
1036 }
1037
1038 /**
1039 * Assign a layout data object to this view.
1040 *
1041 * @param layoutData
1042 * The layout data to assign
1043 */
1044 public void setLayoutData(final Object layoutData) {
1045 fComposite.setLayoutData(layoutData);
1046 }
1047
1048 /**
1049 * Get the virtual table contained in this event table.
1050 *
1051 * @return The TMF virtual table
1052 */
1053 public TmfVirtualTable getTable() {
1054 return fTable;
1055 }
1056
1057 /**
1058 * @param columnData
1059 * columnData
1060 * @deprecated The column headers are now set at the constructor, this
1061 * shouldn't be called anymore.
1062 */
1063 @Deprecated
1064 protected void setColumnHeaders(final org.eclipse.linuxtools.tmf.ui.widgets.virtualtable.ColumnData [] columnData) {
1065 /* No-op */
1066 }
1067
1068 /**
1069 * Set a table item's data.
1070 *
1071 * @param item
1072 * The item to set
1073 * @param event
1074 * Which trace event to link with this entry
1075 * @param rank
1076 * Which rank this event has in the trace/experiment
1077 */
1078 protected void setItemData(final TableItem item, final ITmfEvent event, final long rank) {
1079 item.setText(getItemStrings(fColumns, event));
1080 item.setData(event);
1081 item.setData(Key.TIMESTAMP, new TmfTimestamp(event.getTimestamp()));
1082 item.setData(Key.RANK, rank);
1083
1084 final Collection<Long> markerIds = fBookmarksMap.get(rank);
1085 if (!markerIds.isEmpty()) {
1086 Joiner joiner = Joiner.on("\n -").skipNulls(); //$NON-NLS-1$
1087 List<Object> parts = new ArrayList<>();
1088 if (markerIds.size() > 1) {
1089 parts.add(Messages.TmfEventsTable_MultipleBookmarksToolTip);
1090 }
1091 try {
1092 for (long markerId : markerIds) {
1093 final IMarker marker = fBookmarksFile.findMarker(markerId);
1094 parts.add(marker.getAttribute(IMarker.MESSAGE));
1095 }
1096 } catch (CoreException e) {
1097 displayException(e);
1098 }
1099 item.setData(Key.BOOKMARK, joiner.join(parts));
1100 } else {
1101 item.setData(Key.BOOKMARK, null);
1102 }
1103
1104 boolean searchMatch = false;
1105 boolean searchNoMatch = false;
1106 final ITmfFilter searchFilter = (ITmfFilter) fTable.getData(Key.SEARCH_OBJ);
1107 if (searchFilter != null) {
1108 if (searchFilter.matches(event)) {
1109 searchMatch = true;
1110 } else {
1111 searchNoMatch = true;
1112 }
1113 }
1114
1115 final ColorSetting colorSetting = ColorSettingsManager.getColorSetting(event);
1116 if (searchNoMatch) {
1117 item.setForeground(colorSetting.getDimmedForegroundColor());
1118 item.setBackground(colorSetting.getDimmedBackgroundColor());
1119 } else {
1120 item.setForeground(colorSetting.getForegroundColor());
1121 item.setBackground(colorSetting.getBackgroundColor());
1122 }
1123
1124 if (searchMatch) {
1125 if (!markerIds.isEmpty()) {
1126 item.setImage(SEARCH_MATCH_BOOKMARK_IMAGE);
1127 } else {
1128 item.setImage(SEARCH_MATCH_IMAGE);
1129 }
1130 } else if (!markerIds.isEmpty()) {
1131 item.setImage(BOOKMARK_IMAGE);
1132 } else {
1133 item.setImage((Image) null);
1134 }
1135 }
1136
1137 /**
1138 * Set the item data of the header row.
1139 *
1140 * @param item
1141 * The item to use as table header
1142 */
1143 protected void setHeaderRowItemData(final TableItem item) {
1144 String txtKey = null;
1145 if (fHeaderState == HeaderState.SEARCH) {
1146 item.setImage(SEARCH_IMAGE);
1147 txtKey = Key.SEARCH_TXT;
1148 } else if (fHeaderState == HeaderState.FILTER) {
1149 item.setImage(FILTER_IMAGE);
1150 txtKey = Key.FILTER_TXT;
1151 }
1152 item.setForeground(fGrayColor);
1153 for (int i = 0; i < fTable.getColumns().length; i++) {
1154 final TableColumn column = fTable.getColumns()[i];
1155 final String filter = (String) column.getData(txtKey);
1156 if (filter == null) {
1157 if (fHeaderState == HeaderState.SEARCH) {
1158 item.setText(i, SEARCH_HINT);
1159 } else if (fHeaderState == HeaderState.FILTER) {
1160 item.setText(i, FILTER_HINT);
1161 }
1162 item.setForeground(i, fGrayColor);
1163 item.setFont(i, fTable.getFont());
1164 } else {
1165 item.setText(i, filter);
1166 item.setForeground(i, fGreenColor);
1167 item.setFont(i, fBoldFont);
1168 }
1169 }
1170 }
1171
1172 /**
1173 * Set the item data of the "filter status" row.
1174 *
1175 * @param item
1176 * The item to use as filter status row
1177 */
1178 protected void setFilterStatusRowItemData(final TableItem item) {
1179 for (int i = 0; i < fTable.getColumns().length; i++) {
1180 if (i == 0) {
1181 if ((fTrace == null) || (fFilterCheckCount == fTrace.getNbEvents())) {
1182 item.setImage(FILTER_IMAGE);
1183 } else {
1184 item.setImage(STOP_IMAGE);
1185 }
1186 item.setText(0, fFilterMatchCount + "/" + fFilterCheckCount); //$NON-NLS-1$
1187 } else {
1188 item.setText(i, ""); //$NON-NLS-1$
1189 }
1190 }
1191 item.setData(null);
1192 item.setData(Key.TIMESTAMP, null);
1193 item.setData(Key.RANK, null);
1194 item.setForeground(null);
1195 item.setBackground(null);
1196 }
1197
1198 /**
1199 * Create an editor for the header.
1200 */
1201 protected void createHeaderEditor() {
1202 final TableEditor tableEditor = fTable.createTableEditor();
1203 tableEditor.horizontalAlignment = SWT.LEFT;
1204 tableEditor.verticalAlignment = SWT.CENTER;
1205 tableEditor.grabHorizontal = true;
1206 tableEditor.minimumWidth = 50;
1207
1208 // Handle the header row selection
1209 fTable.addMouseListener(new MouseAdapter() {
1210 int columnIndex;
1211 TableColumn column;
1212 TableItem item;
1213
1214 @Override
1215 public void mouseDown(final MouseEvent event) {
1216 if (event.button != 1) {
1217 return;
1218 }
1219 // Identify the selected row
1220 final Point point = new Point(event.x, event.y);
1221 item = fTable.getItem(point);
1222
1223 // Header row selected
1224 if ((item != null) && (fTable.indexOf(item) == 0)) {
1225
1226 // Icon selected
1227 if (item.getImageBounds(0).contains(point)) {
1228 if (fHeaderState == HeaderState.SEARCH) {
1229 fHeaderState = HeaderState.FILTER;
1230 } else if (fHeaderState == HeaderState.FILTER) {
1231 fHeaderState = HeaderState.SEARCH;
1232 }
1233 fTable.setSelection(0);
1234 fTable.refresh();
1235 return;
1236 }
1237
1238 // Identify the selected column
1239 columnIndex = -1;
1240 for (int i = 0; i < fTable.getColumns().length; i++) {
1241 final Rectangle rect = item.getBounds(i);
1242 if (rect.contains(point)) {
1243 columnIndex = i;
1244 break;
1245 }
1246 }
1247
1248 if (columnIndex == -1) {
1249 return;
1250 }
1251
1252 column = fTable.getColumns()[columnIndex];
1253
1254 String txtKey = null;
1255 if (fHeaderState == HeaderState.SEARCH) {
1256 txtKey = Key.SEARCH_TXT;
1257 } else if (fHeaderState == HeaderState.FILTER) {
1258 txtKey = Key.FILTER_TXT;
1259 }
1260
1261 // The control that will be the editor must be a child of the Table
1262 final Text newEditor = (Text) fTable.createTableEditorControl(Text.class);
1263 final String headerString = (String) column.getData(txtKey);
1264 if (headerString != null) {
1265 newEditor.setText(headerString);
1266 }
1267 newEditor.addFocusListener(new FocusAdapter() {
1268 @Override
1269 public void focusLost(final FocusEvent e) {
1270 final boolean changed = updateHeader(newEditor.getText());
1271 if (changed) {
1272 applyHeader();
1273 }
1274 }
1275 });
1276 newEditor.addKeyListener(new KeyAdapter() {
1277 @Override
1278 public void keyPressed(final KeyEvent e) {
1279 if (e.character == SWT.CR) {
1280 updateHeader(newEditor.getText());
1281 applyHeader();
1282
1283 // Set focus on the table so that the next carriage return goes to the next result
1284 TmfEventsTable.this.getTable().setFocus();
1285 } else if (e.character == SWT.ESC) {
1286 tableEditor.getEditor().dispose();
1287 }
1288 }
1289 });
1290 newEditor.selectAll();
1291 newEditor.setFocus();
1292 tableEditor.setEditor(newEditor, item, columnIndex);
1293 }
1294 }
1295
1296 /*
1297 * returns true is value was changed
1298 */
1299 private boolean updateHeader(final String text) {
1300 String objKey = null;
1301 String txtKey = null;
1302 if (fHeaderState == HeaderState.SEARCH) {
1303 objKey = Key.SEARCH_OBJ;
1304 txtKey = Key.SEARCH_TXT;
1305 } else if (fHeaderState == HeaderState.FILTER) {
1306 objKey = Key.FILTER_OBJ;
1307 txtKey = Key.FILTER_TXT;
1308 }
1309 if (text.trim().length() > 0) {
1310 try {
1311 final String regex = TmfFilterMatchesNode.regexFix(text);
1312 Pattern.compile(regex);
1313 if (regex.equals(column.getData(txtKey))) {
1314 tableEditor.getEditor().dispose();
1315 return false;
1316 }
1317 final TmfFilterMatchesNode filter = new TmfFilterMatchesNode(null);
1318 String fieldId = (String) column.getData(Key.FIELD_ID);
1319 if (fieldId == null) {
1320 fieldId = column.getText();
1321 }
1322 filter.setField(fieldId);
1323 filter.setRegex(regex);
1324 column.setData(objKey, filter);
1325 column.setData(txtKey, regex);
1326 } catch (final PatternSyntaxException ex) {
1327 tableEditor.getEditor().dispose();
1328 MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
1329 ex.getDescription(), ex.getMessage());
1330 return false;
1331 }
1332 } else {
1333 if (column.getData(txtKey) == null) {
1334 tableEditor.getEditor().dispose();
1335 return false;
1336 }
1337 column.setData(objKey, null);
1338 column.setData(txtKey, null);
1339 }
1340 return true;
1341 }
1342
1343 private void applyHeader() {
1344 if (fHeaderState == HeaderState.SEARCH) {
1345 stopSearchThread();
1346 final TmfFilterAndNode filter = new TmfFilterAndNode(null);
1347 for (final TableColumn col : fTable.getColumns()) {
1348 final Object filterObj = col.getData(Key.SEARCH_OBJ);
1349 if (filterObj instanceof ITmfFilterTreeNode) {
1350 filter.addChild((ITmfFilterTreeNode) filterObj);
1351 }
1352 }
1353 if (filter.getChildrenCount() > 0) {
1354 fTable.setData(Key.SEARCH_OBJ, filter);
1355 fTable.refresh();
1356 searchNext();
1357 fireSearchApplied(filter);
1358 } else {
1359 fTable.setData(Key.SEARCH_OBJ, null);
1360 fTable.refresh();
1361 fireSearchApplied(null);
1362 }
1363 } else if (fHeaderState == HeaderState.FILTER) {
1364 final TmfFilterAndNode filter = new TmfFilterAndNode(null);
1365 for (final TableColumn col : fTable.getColumns()) {
1366 final Object filterObj = col.getData(Key.FILTER_OBJ);
1367 if (filterObj instanceof ITmfFilterTreeNode) {
1368 filter.addChild((ITmfFilterTreeNode) filterObj);
1369 }
1370 }
1371 if (filter.getChildrenCount() > 0) {
1372 applyFilter(filter);
1373 } else {
1374 clearFilters();
1375 }
1376 }
1377
1378 tableEditor.getEditor().dispose();
1379 }
1380 });
1381
1382 fTable.addKeyListener(new KeyAdapter() {
1383 @Override
1384 public void keyPressed(final KeyEvent e) {
1385 e.doit = false;
1386 if (e.character == SWT.ESC) {
1387 stopFilterThread();
1388 stopSearchThread();
1389 fTable.refresh();
1390 } else if (e.character == SWT.DEL) {
1391 if (fHeaderState == HeaderState.SEARCH) {
1392 stopSearchThread();
1393 for (final TableColumn column : fTable.getColumns()) {
1394 column.setData(Key.SEARCH_OBJ, null);
1395 column.setData(Key.SEARCH_TXT, null);
1396 }
1397 fTable.setData(Key.SEARCH_OBJ, null);
1398 fTable.refresh();
1399 fireSearchApplied(null);
1400 } else if (fHeaderState == HeaderState.FILTER) {
1401 clearFilters();
1402 }
1403 } else if (e.character == SWT.CR) {
1404 if ((e.stateMask & SWT.SHIFT) == 0) {
1405 searchNext();
1406 } else {
1407 searchPrevious();
1408 }
1409 }
1410 }
1411 });
1412 }
1413
1414 /**
1415 * Send an event indicating a filter has been applied.
1416 *
1417 * @param filter
1418 * The filter that was just applied
1419 */
1420 protected void fireFilterApplied(final ITmfFilter filter) {
1421 broadcast(new TmfEventFilterAppliedSignal(this, fTrace, filter));
1422 }
1423
1424 /**
1425 * Send an event indicating that a search has been applied.
1426 *
1427 * @param filter
1428 * The search filter that was just applied
1429 */
1430 protected void fireSearchApplied(final ITmfFilter filter) {
1431 broadcast(new TmfEventSearchAppliedSignal(this, fTrace, filter));
1432 }
1433
1434 /**
1435 * Start the filtering thread.
1436 */
1437 protected void startFilterThread() {
1438 synchronized (fFilterSyncObj) {
1439 final ITmfFilterTreeNode filter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1440 if (fFilterThread == null || fFilterThread.filter != filter) {
1441 if (fFilterThread != null) {
1442 fFilterThread.cancel();
1443 fFilterThreadResume = false;
1444 }
1445 fFilterThread = new FilterThread(filter);
1446 fFilterThread.start();
1447 } else {
1448 fFilterThreadResume = true;
1449 }
1450 }
1451 }
1452
1453 /**
1454 * Stop the filtering thread.
1455 */
1456 protected void stopFilterThread() {
1457 synchronized (fFilterSyncObj) {
1458 if (fFilterThread != null) {
1459 fFilterThread.cancel();
1460 fFilterThread = null;
1461 fFilterThreadResume = false;
1462 }
1463 }
1464 }
1465
1466 /**
1467 * Apply a filter.
1468 *
1469 * @param filter
1470 * The filter to apply
1471 * @since 1.1
1472 */
1473 protected void applyFilter(ITmfFilter filter) {
1474 stopFilterThread();
1475 stopSearchThread();
1476 fFilterMatchCount = 0;
1477 fFilterCheckCount = 0;
1478 fCache.applyFilter(filter);
1479 fTable.clearAll();
1480 fTable.setData(Key.FILTER_OBJ, filter);
1481 fTable.setItemCount(3); // +1 for header row, +2 for top and bottom filter status rows
1482 startFilterThread();
1483 fireFilterApplied(filter);
1484 }
1485
1486 /**
1487 * Clear all currently active filters.
1488 */
1489 protected void clearFilters() {
1490 if (fTable.getData(Key.FILTER_OBJ) == null) {
1491 return;
1492 }
1493 stopFilterThread();
1494 stopSearchThread();
1495 fCache.clearFilter();
1496 fTable.clearAll();
1497 for (final TableColumn column : fTable.getColumns()) {
1498 column.setData(Key.FILTER_OBJ, null);
1499 column.setData(Key.FILTER_TXT, null);
1500 }
1501 fTable.setData(Key.FILTER_OBJ, null);
1502 if (fTrace != null) {
1503 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1504 } else {
1505 fTable.setItemCount(1); // +1 for header row
1506 }
1507 fFilterMatchCount = 0;
1508 fFilterCheckCount = 0;
1509 if (fSelectedRank >= 0) {
1510 fTable.setSelection((int) fSelectedRank + 1); // +1 for header row
1511 } else {
1512 fTable.setSelection(0);
1513 }
1514 fireFilterApplied(null);
1515 updateStatusLine(null);
1516 }
1517
1518 /**
1519 * Wrapper Thread object for the filtering thread.
1520 */
1521 protected class FilterThread extends Thread {
1522 private final ITmfFilterTreeNode filter;
1523 private TmfEventRequest request;
1524 private boolean refreshBusy = false;
1525 private boolean refreshPending = false;
1526 private final Object syncObj = new Object();
1527
1528 /**
1529 * Constructor.
1530 *
1531 * @param filter
1532 * The filter this thread will be processing
1533 */
1534 public FilterThread(final ITmfFilterTreeNode filter) {
1535 super("Filter Thread"); //$NON-NLS-1$
1536 this.filter = filter;
1537 }
1538
1539 @Override
1540 public void run() {
1541 if (fTrace == null) {
1542 return;
1543 }
1544 final int nbRequested = (int) (fTrace.getNbEvents() - fFilterCheckCount);
1545 if (nbRequested <= 0) {
1546 return;
1547 }
1548 request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY,
1549 (int) fFilterCheckCount, nbRequested, ExecutionType.BACKGROUND) {
1550 @Override
1551 public void handleData(final ITmfEvent event) {
1552 super.handleData(event);
1553 if (request.isCancelled()) {
1554 return;
1555 }
1556 if (filter.matches(event)) {
1557 final long rank = fFilterCheckCount;
1558 final int index = (int) fFilterMatchCount;
1559 fFilterMatchCount++;
1560 fCache.storeEvent(event, rank, index);
1561 refreshTable();
1562 } else if ((fFilterCheckCount % 100) == 0) {
1563 refreshTable();
1564 }
1565 fFilterCheckCount++;
1566 }
1567 };
1568 ((ITmfEventProvider) fTrace).sendRequest(request);
1569 try {
1570 request.waitForCompletion();
1571 } catch (final InterruptedException e) {
1572 }
1573 refreshTable();
1574 synchronized (fFilterSyncObj) {
1575 fFilterThread = null;
1576 if (fFilterThreadResume) {
1577 fFilterThreadResume = false;
1578 fFilterThread = new FilterThread(filter);
1579 fFilterThread.start();
1580 }
1581 }
1582 }
1583
1584 /**
1585 * Refresh the filter.
1586 */
1587 public void refreshTable() {
1588 synchronized (syncObj) {
1589 if (refreshBusy) {
1590 refreshPending = true;
1591 return;
1592 }
1593 refreshBusy = true;
1594 }
1595 Display.getDefault().asyncExec(new Runnable() {
1596 @Override
1597 public void run() {
1598 if (request.isCancelled()) {
1599 return;
1600 }
1601 if (fTable.isDisposed()) {
1602 return;
1603 }
1604 fTable.setItemCount((int) fFilterMatchCount + 3); // +1 for header row, +2 for top and bottom filter status rows
1605 fTable.refresh();
1606 synchronized (syncObj) {
1607 refreshBusy = false;
1608 if (refreshPending) {
1609 refreshPending = false;
1610 refreshTable();
1611 }
1612 }
1613 }
1614 });
1615 }
1616
1617 /**
1618 * Cancel this filtering thread.
1619 */
1620 public void cancel() {
1621 if (request != null) {
1622 request.cancel();
1623 }
1624 }
1625 }
1626
1627 /**
1628 * Go to the next item of a search.
1629 */
1630 protected void searchNext() {
1631 synchronized (fSearchSyncObj) {
1632 if (fSearchThread != null) {
1633 return;
1634 }
1635 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
1636 if (searchFilter == null) {
1637 return;
1638 }
1639 final int selectionIndex = fTable.getSelectionIndex();
1640 int startIndex;
1641 if (selectionIndex > 0) {
1642 startIndex = selectionIndex; // -1 for header row, +1 for next event
1643 } else {
1644 // header row is selected, start at top event
1645 startIndex = Math.max(0, fTable.getTopIndex() - 1); // -1 for header row
1646 }
1647 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1648 if (eventFilter != null) {
1649 startIndex = Math.max(0, startIndex - 1); // -1 for top filter status row
1650 }
1651 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.FORWARD);
1652 fSearchThread.schedule();
1653 }
1654 }
1655
1656 /**
1657 * Go to the previous item of a search.
1658 */
1659 protected void searchPrevious() {
1660 synchronized (fSearchSyncObj) {
1661 if (fSearchThread != null) {
1662 return;
1663 }
1664 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
1665 if (searchFilter == null) {
1666 return;
1667 }
1668 final int selectionIndex = fTable.getSelectionIndex();
1669 int startIndex;
1670 if (selectionIndex > 0) {
1671 startIndex = selectionIndex - 2; // -1 for header row, -1 for previous event
1672 } else {
1673 // header row is selected, start at precedent of top event
1674 startIndex = fTable.getTopIndex() - 2; // -1 for header row, -1 for previous event
1675 }
1676 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1677 if (eventFilter != null) {
1678 startIndex = startIndex - 1; // -1 for top filter status row
1679 }
1680 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.BACKWARD);
1681 fSearchThread.schedule();
1682 }
1683 }
1684
1685 /**
1686 * Stop the search thread.
1687 */
1688 protected void stopSearchThread() {
1689 fPendingGotoRank = -1;
1690 synchronized (fSearchSyncObj) {
1691 if (fSearchThread != null) {
1692 fSearchThread.cancel();
1693 fSearchThread = null;
1694 }
1695 }
1696 }
1697
1698 /**
1699 * Wrapper for the search thread.
1700 */
1701 protected class SearchThread extends Job {
1702
1703 private ITmfFilterTreeNode searchFilter;
1704 private ITmfFilterTreeNode eventFilter;
1705 private int startIndex;
1706 private int direction;
1707 private long rank;
1708 private long foundRank = -1;
1709 private TmfEventRequest request;
1710 private ITmfTimestamp foundTimestamp = null;
1711
1712 /**
1713 * Constructor.
1714 *
1715 * @param searchFilter
1716 * The search filter
1717 * @param eventFilter
1718 * The event filter
1719 * @param startIndex
1720 * The index at which we should start searching
1721 * @param currentRank
1722 * The current rank
1723 * @param direction
1724 * In which direction should we search, forward or backwards
1725 */
1726 public SearchThread(final ITmfFilterTreeNode searchFilter,
1727 final ITmfFilterTreeNode eventFilter, final int startIndex,
1728 final long currentRank, final int direction) {
1729 super(Messages.TmfEventsTable_SearchingJobName);
1730 this.searchFilter = searchFilter;
1731 this.eventFilter = eventFilter;
1732 this.startIndex = startIndex;
1733 this.rank = currentRank;
1734 this.direction = direction;
1735 }
1736
1737 @Override
1738 protected IStatus run(final IProgressMonitor monitor) {
1739 if (fTrace == null) {
1740 return Status.OK_STATUS;
1741 }
1742 final Display display = Display.getDefault();
1743 if (startIndex < 0) {
1744 rank = (int) fTrace.getNbEvents() - 1;
1745 } else if (startIndex >= (fTable.getItemCount() - (eventFilter == null ? 1 : 3))) { // -1 for header row, -2 for top and bottom filter status rows
1746 rank = 0;
1747 } else {
1748 int idx = startIndex;
1749 while (foundRank == -1) {
1750 final CachedEvent event = fCache.peekEvent(idx);
1751 if (event == null) {
1752 break;
1753 }
1754 rank = event.rank;
1755 if (searchFilter.matches(event.event) && ((eventFilter == null) || eventFilter.matches(event.event))) {
1756 foundRank = event.rank;
1757 foundTimestamp = event.event.getTimestamp();
1758 break;
1759 }
1760 if (direction == Direction.FORWARD) {
1761 idx++;
1762 } else {
1763 idx--;
1764 }
1765 }
1766 if (foundRank == -1) {
1767 if (direction == Direction.FORWARD) {
1768 rank++;
1769 if (rank > (fTrace.getNbEvents() - 1)) {
1770 rank = 0;
1771 }
1772 } else {
1773 rank--;
1774 if (rank < 0) {
1775 rank = (int) fTrace.getNbEvents() - 1;
1776 }
1777 }
1778 }
1779 }
1780 final int startRank = (int) rank;
1781 boolean wrapped = false;
1782 while (!monitor.isCanceled() && (foundRank == -1) && (fTrace != null)) {
1783 int nbRequested = (direction == Direction.FORWARD ? Integer.MAX_VALUE : Math.min((int) rank + 1, fTrace.getCacheSize()));
1784 if (direction == Direction.BACKWARD) {
1785 rank = Math.max(0, rank - fTrace.getCacheSize() + 1);
1786 }
1787 request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY,
1788 (int) rank, nbRequested, ExecutionType.BACKGROUND) {
1789 long currentRank = rank;
1790
1791 @Override
1792 public void handleData(final ITmfEvent event) {
1793 super.handleData(event);
1794 if (searchFilter.matches(event) && ((eventFilter == null) || eventFilter.matches(event))) {
1795 foundRank = currentRank;
1796 foundTimestamp = event.getTimestamp();
1797 if (direction == Direction.FORWARD) {
1798 done();
1799 return;
1800 }
1801 }
1802 currentRank++;
1803 }
1804 };
1805 ((ITmfEventProvider) fTrace).sendRequest(request);
1806 try {
1807 request.waitForCompletion();
1808 if (request.isCancelled()) {
1809 return Status.OK_STATUS;
1810 }
1811 } catch (final InterruptedException e) {
1812 synchronized (fSearchSyncObj) {
1813 fSearchThread = null;
1814 }
1815 return Status.OK_STATUS;
1816 }
1817 if (foundRank == -1) {
1818 if (direction == Direction.FORWARD) {
1819 if (rank == 0) {
1820 synchronized (fSearchSyncObj) {
1821 fSearchThread = null;
1822 }
1823 return Status.OK_STATUS;
1824 }
1825 nbRequested = (int) rank;
1826 rank = 0;
1827 wrapped = true;
1828 } else {
1829 rank--;
1830 if (rank < 0) {
1831 rank = (int) fTrace.getNbEvents() - 1;
1832 wrapped = true;
1833 }
1834 if ((rank <= startRank) && wrapped) {
1835 synchronized (fSearchSyncObj) {
1836 fSearchThread = null;
1837 }
1838 return Status.OK_STATUS;
1839 }
1840 }
1841 }
1842 }
1843 int index = (int) foundRank;
1844 if (eventFilter != null) {
1845 index = fCache.getFilteredEventIndex(foundRank);
1846 }
1847 final int selection = index + 1 + (eventFilter != null ? +1 : 0); // +1 for header row, +1 for top filter status row
1848
1849 display.asyncExec(new Runnable() {
1850 @Override
1851 public void run() {
1852 if (monitor.isCanceled()) {
1853 return;
1854 }
1855 if (fTable.isDisposed()) {
1856 return;
1857 }
1858 fTable.setSelection(selection);
1859 fSelectedRank = foundRank;
1860 fRawViewer.selectAndReveal(fSelectedRank);
1861 if (foundTimestamp != null) {
1862 broadcast(new TmfTimeSynchSignal(TmfEventsTable.this, foundTimestamp));
1863 }
1864 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, getSelection()));
1865 synchronized (fSearchSyncObj) {
1866 fSearchThread = null;
1867 }
1868 updateStatusLine(null);
1869 }
1870 });
1871 return Status.OK_STATUS;
1872 }
1873
1874 @Override
1875 protected void canceling() {
1876 request.cancel();
1877 synchronized (fSearchSyncObj) {
1878 fSearchThread = null;
1879 }
1880 }
1881 }
1882
1883 /**
1884 * Create the resources.
1885 */
1886 protected void createResources() {
1887 fGrayColor = fResourceManager.createColor(ColorUtil.blend(fTable.getBackground().getRGB(), fTable
1888 .getForeground().getRGB()));
1889 fGreenColor = fTable.getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN);
1890 fBoldFont = fResourceManager.createFont(FontDescriptor.createFrom(fTable.getFont()).setStyle(SWT.BOLD));
1891 }
1892
1893 /**
1894 * Pack the columns.
1895 */
1896 protected void packColumns() {
1897 if (fPackDone) {
1898 return;
1899 }
1900 fTable.setRedraw(false);
1901
1902 boolean isLinux = System.getProperty("os.name").contains("Linux") ? true : false; //$NON-NLS-1$ //$NON-NLS-2$
1903
1904 TableColumn tableColumns[] = fTable.getColumns();
1905 for (int i = 0; i < tableColumns.length; i++) {
1906 final TableColumn column = tableColumns[i];
1907 final int headerWidth = column.getWidth();
1908 column.pack();
1909 // Workaround for Linux which doesn't consider the image width of
1910 // search/filter row in TableColumn.pack() after having executed
1911 // TableItem.setImage((Image)null) for other rows than search/filter row.
1912 if (isLinux && (i == 0)) {
1913 column.setWidth(column.getWidth() + SEARCH_IMAGE.getBounds().width);
1914 }
1915
1916 if (column.getWidth() < headerWidth) {
1917 column.setWidth(headerWidth);
1918 }
1919 }
1920
1921 fTable.setRedraw(true);
1922 fPackDone = true;
1923 }
1924
1925 /**
1926 * Get the array of item strings (e.g., what to display in each cell of the
1927 * table row) corresponding to the columns and trace event passed in
1928 * parameter. The order of the Strings in the returned array will correspond
1929 * to the iteration order of 'columns'.
1930 *
1931 * <p>
1932 * To ensure consistent results, make sure only call this within a scope
1933 * synchronized on 'columns'! If the order of 'columns' changes right after
1934 * this method is called, the returned value won't be ordered correctly
1935 * anymore.
1936 */
1937 private static String[] getItemStrings(List<TmfEventTableColumn> columns, ITmfEvent event) {
1938 if (event == null) {
1939 return EMPTY_STRING_ARRAY;
1940 }
1941 synchronized (columns) {
1942 List<String> itemStrings = new ArrayList<>(columns.size());
1943 for (TmfEventTableColumn column : columns) {
1944 itemStrings.add(column.getItemString(event));
1945 }
1946 return itemStrings.toArray(new String[0]);
1947 }
1948 }
1949
1950 /**
1951 * Get the contents of the row in the events table corresponding to an
1952 * event. The order of the elements corresponds to the current order of the
1953 * columns.
1954 *
1955 * @param event
1956 * The event printed in this row
1957 * @return The event row entries
1958 * @since 3.0
1959 */
1960 public String[] getItemStrings(ITmfEvent event) {
1961 return getItemStrings(fColumns, event);
1962 }
1963
1964 /**
1965 * Notify this table that is got the UI focus.
1966 */
1967 public void setFocus() {
1968 fTable.setFocus();
1969 }
1970
1971 /**
1972 * Assign a new trace to this event table.
1973 *
1974 * @param trace
1975 * The trace to assign to this event table
1976 * @param disposeOnClose
1977 * true if the trace should be disposed when the table is
1978 * disposed
1979 */
1980 public void setTrace(final ITmfTrace trace, final boolean disposeOnClose) {
1981 if ((fTrace != null) && fDisposeOnClose) {
1982 fTrace.dispose();
1983 }
1984 fTrace = trace;
1985 fPackDone = false;
1986 fSelectedRank = 0;
1987 fDisposeOnClose = disposeOnClose;
1988
1989 // Perform the updates on the UI thread
1990 fTable.getDisplay().syncExec(new Runnable() {
1991 @Override
1992 public void run() {
1993 fTable.removeAll();
1994 fCache.setTrace(fTrace); // Clear the cache
1995 if (fTrace != null) {
1996 if (!fTable.isDisposed() && (fTrace != null)) {
1997 if (fTable.getData(Key.FILTER_OBJ) == null) {
1998 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1999 } else {
2000 stopFilterThread();
2001 fFilterMatchCount = 0;
2002 fFilterCheckCount = 0;
2003 fTable.setItemCount(3); // +1 for header row, +2 for top and bottom filter status rows
2004 startFilterThread();
2005 }
2006 }
2007 }
2008 fRawViewer.setTrace(fTrace);
2009 }
2010 });
2011 }
2012
2013 /**
2014 * Assign the status line manager
2015 *
2016 * @param statusLineManager
2017 * The status line manager, or null to disable status line messages
2018 * @since 2.1
2019 */
2020 public void setStatusLineManager(IStatusLineManager statusLineManager) {
2021 if (fStatusLineManager != null && statusLineManager == null) {
2022 fStatusLineManager.setMessage(""); //$NON-NLS-1$
2023 }
2024 fStatusLineManager = statusLineManager;
2025 }
2026
2027 private void updateStatusLine(ITmfTimestamp delta) {
2028 if (fStatusLineManager != null) {
2029 if (delta != null) {
2030 fStatusLineManager.setMessage("\u0394: " + delta); //$NON-NLS-1$
2031 } else {
2032 fStatusLineManager.setMessage(null);
2033 }
2034 }
2035 }
2036
2037 // ------------------------------------------------------------------------
2038 // Event cache
2039 // ------------------------------------------------------------------------
2040
2041 /**
2042 * Notify that the event cache has been updated
2043 *
2044 * @param completed
2045 * Also notify if the populating of the cache is complete, or
2046 * not.
2047 */
2048 public void cacheUpdated(final boolean completed) {
2049 synchronized (fCacheUpdateSyncObj) {
2050 if (fCacheUpdateBusy) {
2051 fCacheUpdatePending = true;
2052 fCacheUpdateCompleted = completed;
2053 return;
2054 }
2055 fCacheUpdateBusy = true;
2056 }
2057 // Event cache is now updated. Perform update on the UI thread
2058 if (!fTable.isDisposed()) {
2059 fTable.getDisplay().asyncExec(new Runnable() {
2060 @Override
2061 public void run() {
2062 if (!fTable.isDisposed()) {
2063 fTable.refresh();
2064 packColumns();
2065 }
2066 if (completed) {
2067 populateCompleted();
2068 }
2069 synchronized (fCacheUpdateSyncObj) {
2070 fCacheUpdateBusy = false;
2071 if (fCacheUpdatePending) {
2072 fCacheUpdatePending = false;
2073 cacheUpdated(fCacheUpdateCompleted);
2074 }
2075 }
2076 }
2077 });
2078 }
2079 }
2080
2081 /**
2082 * Callback for when populating the table is complete.
2083 */
2084 protected void populateCompleted() {
2085 // Nothing by default;
2086 }
2087
2088 // ------------------------------------------------------------------------
2089 // ISelectionProvider
2090 // ------------------------------------------------------------------------
2091
2092 /**
2093 * @since 2.0
2094 */
2095 @Override
2096 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2097 selectionChangedListeners.add(listener);
2098 }
2099
2100 /**
2101 * @since 2.0
2102 */
2103 @Override
2104 public ISelection getSelection() {
2105 if (fTable == null || fTable.isDisposed()) {
2106 return StructuredSelection.EMPTY;
2107 }
2108 List<Object> list = new ArrayList<>(fTable.getSelection().length);
2109 for (TableItem item : fTable.getSelection()) {
2110 if (item.getData() != null) {
2111 list.add(item.getData());
2112 }
2113 }
2114 return new StructuredSelection(list);
2115 }
2116
2117 /**
2118 * @since 2.0
2119 */
2120 @Override
2121 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2122 selectionChangedListeners.remove(listener);
2123 }
2124
2125 /**
2126 * @since 2.0
2127 */
2128 @Override
2129 public void setSelection(ISelection selection) {
2130 // not implemented
2131 }
2132
2133 /**
2134 * Notifies any selection changed listeners that the viewer's selection has changed.
2135 * Only listeners registered at the time this method is called are notified.
2136 *
2137 * @param event a selection changed event
2138 *
2139 * @see ISelectionChangedListener#selectionChanged
2140 * @since 2.0
2141 */
2142 protected void fireSelectionChanged(final SelectionChangedEvent event) {
2143 Object[] listeners = selectionChangedListeners.getListeners();
2144 for (int i = 0; i < listeners.length; ++i) {
2145 final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
2146 SafeRunnable.run(new SafeRunnable() {
2147 @Override
2148 public void run() {
2149 l.selectionChanged(event);
2150 }
2151 });
2152 }
2153 }
2154
2155 // ------------------------------------------------------------------------
2156 // Bookmark handling
2157 // ------------------------------------------------------------------------
2158
2159 /**
2160 * Add a bookmark to this event table.
2161 *
2162 * @param bookmarksFile
2163 * The file to use for the bookmarks
2164 */
2165 public void addBookmark(final IFile bookmarksFile) {
2166 fBookmarksFile = bookmarksFile;
2167 final TableItem[] selection = fTable.getSelection();
2168 if (selection.length > 0) {
2169 final TableItem tableItem = selection[0];
2170 if (tableItem.getData(Key.RANK) != null) {
2171 final StringBuffer defaultMessage = new StringBuffer();
2172 for (int i = 0; i < fTable.getColumns().length; i++) {
2173 if (i > 0) {
2174 defaultMessage.append(", "); //$NON-NLS-1$
2175 }
2176 defaultMessage.append(tableItem.getText(i));
2177 }
2178 final InputDialog dialog = new MultiLineInputDialog(
2179 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
2180 Messages.TmfEventsTable_AddBookmarkDialogTitle,
2181 Messages.TmfEventsTable_AddBookmarkDialogMessage,
2182 defaultMessage.toString());
2183 if (dialog.open() == Window.OK) {
2184 final String message = dialog.getValue();
2185 try {
2186 final IMarker bookmark = bookmarksFile.createMarker(IMarker.BOOKMARK);
2187 if (bookmark.exists()) {
2188 bookmark.setAttribute(IMarker.MESSAGE, message.toString());
2189 final Long rank = (Long) tableItem.getData(Key.RANK);
2190 final int location = rank.intValue();
2191 bookmark.setAttribute(IMarker.LOCATION, Integer.valueOf(location));
2192 fBookmarksMap.put(rank, bookmark.getId());
2193 fTable.refresh();
2194 }
2195 } catch (final CoreException e) {
2196 displayException(e);
2197 }
2198 }
2199 }
2200 }
2201
2202 }
2203
2204 /**
2205 * Remove a bookmark from this event table.
2206 *
2207 * @param bookmark
2208 * The bookmark to remove
2209 */
2210 public void removeBookmark(final IMarker bookmark) {
2211 for (final Entry<Long, Long> entry : fBookmarksMap.entries()) {
2212 if (entry.getValue().equals(bookmark.getId())) {
2213 fBookmarksMap.remove(entry.getKey(), entry.getValue());
2214 fTable.refresh();
2215 return;
2216 }
2217 }
2218 }
2219
2220 private void toggleBookmark(final Long rank) {
2221 if (fBookmarksFile == null) {
2222 return;
2223 }
2224 if (fBookmarksMap.containsKey(rank)) {
2225 final Collection<Long> markerIds = fBookmarksMap.removeAll(rank);
2226 fTable.refresh();
2227 try {
2228 for (long markerId : markerIds) {
2229 final IMarker bookmark = fBookmarksFile.findMarker(markerId);
2230 if (bookmark != null) {
2231 bookmark.delete();
2232 }
2233 }
2234 } catch (final CoreException e) {
2235 displayException(e);
2236 }
2237 } else {
2238 addBookmark(fBookmarksFile);
2239 }
2240 }
2241
2242 /**
2243 * Refresh the bookmarks assigned to this trace, from the contents of a
2244 * bookmark file.
2245 *
2246 * @param bookmarksFile
2247 * The bookmark file to use
2248 */
2249 public void refreshBookmarks(final IFile bookmarksFile) {
2250 fBookmarksFile = bookmarksFile;
2251 if (bookmarksFile == null) {
2252 fBookmarksMap.clear();
2253 fTable.refresh();
2254 return;
2255 }
2256 try {
2257 fBookmarksMap.clear();
2258 for (final IMarker bookmark : bookmarksFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO)) {
2259 final int location = bookmark.getAttribute(IMarker.LOCATION, -1);
2260 if (location != -1) {
2261 final long rank = location;
2262 fBookmarksMap.put(rank, bookmark.getId());
2263 }
2264 }
2265 fTable.refresh();
2266 } catch (final CoreException e) {
2267 displayException(e);
2268 }
2269 }
2270
2271 @Override
2272 public void gotoMarker(final IMarker marker) {
2273 final int rank = marker.getAttribute(IMarker.LOCATION, -1);
2274 if (rank != -1) {
2275 int index = rank;
2276 if (fTable.getData(Key.FILTER_OBJ) != null) {
2277 index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
2278 } else if (rank >= fTable.getItemCount()) {
2279 fPendingGotoRank = rank;
2280 }
2281 fSelectedRank = rank;
2282 fTable.setSelection(index + 1); // +1 for header row
2283 updateStatusLine(null);
2284 }
2285 }
2286
2287 // ------------------------------------------------------------------------
2288 // Listeners
2289 // ------------------------------------------------------------------------
2290
2291 @Override
2292 public void colorSettingsChanged(final ColorSetting[] colorSettings) {
2293 fTable.refresh();
2294 }
2295
2296 // ------------------------------------------------------------------------
2297 // Signal handlers
2298 // ------------------------------------------------------------------------
2299
2300 /**
2301 * Handler for the trace updated signal
2302 *
2303 * @param signal
2304 * The incoming signal
2305 */
2306 @TmfSignalHandler
2307 public void traceUpdated(final TmfTraceUpdatedSignal signal) {
2308 if ((signal.getTrace() != fTrace) || fTable.isDisposed()) {
2309 return;
2310 }
2311 // Perform the refresh on the UI thread
2312 Display.getDefault().asyncExec(new Runnable() {
2313 @Override
2314 public void run() {
2315 if (!fTable.isDisposed() && (fTrace != null)) {
2316 if (fTable.getData(Key.FILTER_OBJ) == null) {
2317 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
2318 if ((fPendingGotoRank != -1) && ((fPendingGotoRank + 1) < fTable.getItemCount())) { // +1 for header row
2319 fTable.setSelection((int) fPendingGotoRank + 1); // +1 for header row
2320 fPendingGotoRank = -1;
2321 updateStatusLine(null);
2322 }
2323 } else {
2324 startFilterThread();
2325 }
2326 }
2327 if (!fRawViewer.isDisposed() && (fTrace != null)) {
2328 fRawViewer.refreshEventCount();
2329 }
2330 }
2331 });
2332 }
2333
2334 /**
2335 * Handler for the time synch signal.
2336 *
2337 * @param signal
2338 * The incoming signal
2339 */
2340 @TmfSignalHandler
2341 public void currentTimeUpdated(final TmfTimeSynchSignal signal) {
2342 if ((signal.getSource() != this) && (fTrace != null) && (!fTable.isDisposed())) {
2343
2344 // Create a request for one event that will be queued after other ongoing requests. When this request is completed
2345 // do the work to select the actual event with the timestamp specified in the signal. This procedure prevents
2346 // the method fTrace.getRank() from interfering and delaying ongoing requests.
2347 final TmfEventRequest subRequest = new TmfEventRequest(ITmfEvent.class,
2348 TmfTimeRange.ETERNITY, 0, 1, ExecutionType.FOREGROUND) {
2349
2350 TmfTimestamp ts = new TmfTimestamp(signal.getBeginTime());
2351
2352 @Override
2353 public void handleData(final ITmfEvent event) {
2354 super.handleData(event);
2355 }
2356
2357 @Override
2358 public void handleCompleted() {
2359 super.handleCompleted();
2360 if (fTrace == null) {
2361 return;
2362 }
2363
2364 // Verify if the event is within the trace range and adjust if necessary
2365 ITmfTimestamp timestamp = ts;
2366 if (timestamp.compareTo(fTrace.getStartTime(), true) == -1) {
2367 timestamp = fTrace.getStartTime();
2368 }
2369 if (timestamp.compareTo(fTrace.getEndTime(), true) == 1) {
2370 timestamp = fTrace.getEndTime();
2371 }
2372
2373 // Get the rank of the selected event in the table
2374 final ITmfContext context = fTrace.seekEvent(timestamp);
2375 final long rank = context.getRank();
2376 context.dispose();
2377 fSelectedRank = rank;
2378
2379 fTable.getDisplay().asyncExec(new Runnable() {
2380 @Override
2381 public void run() {
2382 // Return if table is disposed
2383 if (fTable.isDisposed()) {
2384 return;
2385 }
2386
2387 int index = (int) rank;
2388 if (fTable.isDisposed()) {
2389 return;
2390 }
2391 if (fTable.getData(Key.FILTER_OBJ) != null) {
2392 index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
2393 }
2394 fTable.setSelection(index + 1); // +1 for header row
2395 fRawViewer.selectAndReveal(rank);
2396 updateStatusLine(null);
2397 }
2398 });
2399 }
2400 };
2401
2402 ((ITmfEventProvider) fTrace).sendRequest(subRequest);
2403 }
2404 }
2405
2406 // ------------------------------------------------------------------------
2407 // Error handling
2408 // ------------------------------------------------------------------------
2409
2410 /**
2411 * Display an exception in a message box
2412 *
2413 * @param e the exception
2414 */
2415 private static void displayException(final Exception e) {
2416 final MessageBox mb = new MessageBox(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell());
2417 mb.setText(e.getClass().getSimpleName());
2418 mb.setMessage(e.getMessage());
2419 mb.open();
2420 }
2421
2422 /**
2423 * @since 2.0
2424 */
2425 public void refresh() {
2426 fCache.clear();
2427 fTable.refresh();
2428 fTable.redraw();
2429 }
2430 }
This page took 0.086097 seconds and 6 git commands to generate.