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