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