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