tmf: Bug: 499359: Fix deadlock in table when closing trace selection
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / viewers / events / TmfEventsTable.java
1 /*******************************************************************************
2 * Copyright (c) 2010, 2016 Ericsson and others.
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 * Matthew Khouzam - Add hide columns
17 *******************************************************************************/
18
19 package org.eclipse.tracecompass.tmf.ui.viewers.events;
20
21 import java.io.File;
22 import java.io.FileNotFoundException;
23 import java.lang.reflect.InvocationTargetException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.Map.Entry;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 import java.util.regex.PatternSyntaxException;
33
34 import org.eclipse.core.commands.Command;
35 import org.eclipse.core.commands.ExecutionException;
36 import org.eclipse.core.commands.NotEnabledException;
37 import org.eclipse.core.commands.NotHandledException;
38 import org.eclipse.core.commands.ParameterizedCommand;
39 import org.eclipse.core.commands.common.NotDefinedException;
40 import org.eclipse.core.expressions.IEvaluationContext;
41 import org.eclipse.core.filesystem.EFS;
42 import org.eclipse.core.filesystem.IFileStore;
43 import org.eclipse.core.resources.IFile;
44 import org.eclipse.core.resources.IMarker;
45 import org.eclipse.core.resources.IResource;
46 import org.eclipse.core.resources.IResourceVisitor;
47 import org.eclipse.core.resources.IWorkspaceRunnable;
48 import org.eclipse.core.resources.ResourcesPlugin;
49 import org.eclipse.core.runtime.CoreException;
50 import org.eclipse.core.runtime.IPath;
51 import org.eclipse.core.runtime.IProgressMonitor;
52 import org.eclipse.core.runtime.IStatus;
53 import org.eclipse.core.runtime.ListenerList;
54 import org.eclipse.core.runtime.Path;
55 import org.eclipse.core.runtime.Status;
56 import org.eclipse.core.runtime.jobs.ISchedulingRule;
57 import org.eclipse.core.runtime.jobs.Job;
58 import org.eclipse.emf.common.util.URI;
59 import org.eclipse.emf.ecore.EValidator;
60 import org.eclipse.jdt.annotation.NonNull;
61 import org.eclipse.jdt.annotation.Nullable;
62 import org.eclipse.jface.action.Action;
63 import org.eclipse.jface.action.IAction;
64 import org.eclipse.jface.action.IMenuListener;
65 import org.eclipse.jface.action.IMenuManager;
66 import org.eclipse.jface.action.IStatusLineManager;
67 import org.eclipse.jface.action.MenuManager;
68 import org.eclipse.jface.action.Separator;
69 import org.eclipse.jface.dialogs.MessageDialog;
70 import org.eclipse.jface.operation.IRunnableWithProgress;
71 import org.eclipse.jface.resource.ColorRegistry;
72 import org.eclipse.jface.resource.FontRegistry;
73 import org.eclipse.jface.resource.JFaceResources;
74 import org.eclipse.jface.resource.LocalResourceManager;
75 import org.eclipse.jface.text.BadLocationException;
76 import org.eclipse.jface.text.IDocument;
77 import org.eclipse.jface.text.IRegion;
78 import org.eclipse.jface.util.IPropertyChangeListener;
79 import org.eclipse.jface.util.OpenStrategy;
80 import org.eclipse.jface.util.PropertyChangeEvent;
81 import org.eclipse.jface.util.SafeRunnable;
82 import org.eclipse.jface.viewers.ArrayContentProvider;
83 import org.eclipse.jface.viewers.ISelection;
84 import org.eclipse.jface.viewers.ISelectionChangedListener;
85 import org.eclipse.jface.viewers.ISelectionProvider;
86 import org.eclipse.jface.viewers.LabelProvider;
87 import org.eclipse.jface.viewers.SelectionChangedEvent;
88 import org.eclipse.jface.viewers.StructuredSelection;
89 import org.eclipse.jface.window.Window;
90 import org.eclipse.osgi.util.NLS;
91 import org.eclipse.swt.SWT;
92 import org.eclipse.swt.custom.SashForm;
93 import org.eclipse.swt.custom.StyleRange;
94 import org.eclipse.swt.custom.TableEditor;
95 import org.eclipse.swt.events.ControlAdapter;
96 import org.eclipse.swt.events.ControlEvent;
97 import org.eclipse.swt.events.FocusAdapter;
98 import org.eclipse.swt.events.FocusEvent;
99 import org.eclipse.swt.events.KeyAdapter;
100 import org.eclipse.swt.events.KeyEvent;
101 import org.eclipse.swt.events.MouseAdapter;
102 import org.eclipse.swt.events.MouseEvent;
103 import org.eclipse.swt.events.SelectionAdapter;
104 import org.eclipse.swt.events.SelectionEvent;
105 import org.eclipse.swt.graphics.Color;
106 import org.eclipse.swt.graphics.Font;
107 import org.eclipse.swt.graphics.GC;
108 import org.eclipse.swt.graphics.Image;
109 import org.eclipse.swt.graphics.Point;
110 import org.eclipse.swt.graphics.Rectangle;
111 import org.eclipse.swt.layout.FillLayout;
112 import org.eclipse.swt.layout.GridData;
113 import org.eclipse.swt.layout.GridLayout;
114 import org.eclipse.swt.widgets.Composite;
115 import org.eclipse.swt.widgets.Display;
116 import org.eclipse.swt.widgets.Event;
117 import org.eclipse.swt.widgets.Label;
118 import org.eclipse.swt.widgets.Listener;
119 import org.eclipse.swt.widgets.Menu;
120 import org.eclipse.swt.widgets.MessageBox;
121 import org.eclipse.swt.widgets.Shell;
122 import org.eclipse.swt.widgets.Table;
123 import org.eclipse.swt.widgets.TableColumn;
124 import org.eclipse.swt.widgets.TableItem;
125 import org.eclipse.swt.widgets.Text;
126 import org.eclipse.tracecompass.common.core.NonNullUtils;
127 import org.eclipse.tracecompass.internal.tmf.core.filter.TmfCollapseFilter;
128 import org.eclipse.tracecompass.internal.tmf.ui.Activator;
129 import org.eclipse.tracecompass.internal.tmf.ui.Messages;
130 import org.eclipse.tracecompass.internal.tmf.ui.commands.CopyToClipboardOperation;
131 import org.eclipse.tracecompass.internal.tmf.ui.commands.ExportToTextCommandHandler;
132 import org.eclipse.tracecompass.internal.tmf.ui.dialogs.AddBookmarkDialog;
133 import org.eclipse.tracecompass.tmf.core.component.ITmfEventProvider;
134 import org.eclipse.tracecompass.tmf.core.component.TmfComponent;
135 import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
136 import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
137 import org.eclipse.tracecompass.tmf.core.event.aspect.TmfContentFieldAspect;
138 import org.eclipse.tracecompass.tmf.core.event.collapse.ITmfCollapsibleEvent;
139 import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfCallsite;
140 import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfModelLookup;
141 import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfSourceLookup;
142 import org.eclipse.tracecompass.tmf.core.filter.ITmfFilter;
143 import org.eclipse.tracecompass.tmf.core.filter.model.ITmfFilterTreeNode;
144 import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterMatchesNode;
145 import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterNode;
146 import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterObjectNode;
147 import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterRootNode;
148 import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType;
149 import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
150 import org.eclipse.tracecompass.tmf.core.resources.ITmfMarker;
151 import org.eclipse.tracecompass.tmf.core.signal.TmfEventFilterAppliedSignal;
152 import org.eclipse.tracecompass.tmf.core.signal.TmfEventSearchAppliedSignal;
153 import org.eclipse.tracecompass.tmf.core.signal.TmfEventSelectedSignal;
154 import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
155 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
156 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceUpdatedSignal;
157 import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
158 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
159 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
160 import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
161 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
162 import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
163 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
164 import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
165 import org.eclipse.tracecompass.tmf.core.util.Pair;
166 import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsCache.CachedEvent;
167 import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsTableHeader.IEventsTableHeaderListener;
168 import org.eclipse.tracecompass.tmf.ui.viewers.events.columns.TmfEventTableColumn;
169 import org.eclipse.tracecompass.tmf.ui.views.colors.ColorSetting;
170 import org.eclipse.tracecompass.tmf.ui.views.colors.ColorSettingsManager;
171 import org.eclipse.tracecompass.tmf.ui.views.colors.IColorSettingsListener;
172 import org.eclipse.tracecompass.tmf.ui.views.filter.FilterManager;
173 import org.eclipse.tracecompass.tmf.ui.widgets.rawviewer.TmfRawEventViewer;
174 import org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.TmfVirtualTable;
175 import org.eclipse.ui.IEditorPart;
176 import org.eclipse.ui.IEditorSite;
177 import org.eclipse.ui.IWorkbenchPage;
178 import org.eclipse.ui.IWorkbenchPartSite;
179 import org.eclipse.ui.PlatformUI;
180 import org.eclipse.ui.commands.ICommandService;
181 import org.eclipse.ui.dialogs.ListDialog;
182 import org.eclipse.ui.handlers.IHandlerService;
183 import org.eclipse.ui.ide.IDE;
184 import org.eclipse.ui.ide.IGotoMarker;
185 import org.eclipse.ui.texteditor.ITextEditor;
186 import org.eclipse.ui.themes.ColorUtil;
187 import org.eclipse.ui.themes.IThemeManager;
188
189 import com.google.common.base.Joiner;
190 import com.google.common.collect.HashMultimap;
191 import com.google.common.collect.ImmutableList;
192 import com.google.common.collect.Multimap;
193
194 /**
195 * The generic TMF Events table
196 *
197 * This is a view that will list events that are read from a trace.
198 *
199 * @author Francois Chouinard
200 * @author Patrick Tasse
201 */
202 public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorSettingsListener, ISelectionProvider, IPropertyChangeListener {
203
204 /**
205 * Empty string array, used by {@link #getItemStrings}.
206 */
207 protected static final String @NonNull [] EMPTY_STRING_ARRAY = new String[0];
208
209 /**
210 * Empty string
211 */
212 protected static final @NonNull String EMPTY_STRING = ""; //$NON-NLS-1$
213
214 private static final boolean IS_LINUX = System.getProperty("os.name").contains("Linux") ? true : false; //$NON-NLS-1$ //$NON-NLS-2$
215
216 private static final String FONT_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.font.eventtable"; //$NON-NLS-1$
217 private static final String HIGHLIGHT_COLOR_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.color.eventtable.highlight"; //$NON-NLS-1$
218
219 private static final Image BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath(
220 "icons/elcl16/bookmark_obj.gif"); //$NON-NLS-1$
221 private static final Image SEARCH_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/search.gif"); //$NON-NLS-1$
222 private static final Image SEARCH_MATCH_IMAGE = Activator.getDefault().getImageFromPath(
223 "icons/elcl16/search_match.gif"); //$NON-NLS-1$
224 private static final Image SEARCH_MATCH_BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath(
225 "icons/elcl16/search_match_bookmark.gif"); //$NON-NLS-1$
226 private static final Image FILTER_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/filter_items.gif"); //$NON-NLS-1$
227 private static final Image FILTER_ADD_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/filter_add.gif"); //$NON-NLS-1$
228 private static final Image STOP_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/stop.gif"); //$NON-NLS-1$
229 private static final String SEARCH_HINT = Messages.TmfEventsTable_SearchHint;
230 private static final int MAX_CACHE_SIZE = 1000;
231
232 private static final int MARGIN_COLUMN_INDEX = 0;
233 private static final int FILTER_SUMMARY_INDEX = 1;
234 private static final int EVENT_COLUMNS_START_INDEX = MARGIN_COLUMN_INDEX + 1;
235
236 private final ISchedulingRule fTimeSelectMutexRule = new ISchedulingRule() {
237 @Override
238 public boolean isConflicting(ISchedulingRule rule) {
239 return (rule == this);
240 }
241
242 @Override
243 public boolean contains(ISchedulingRule rule) {
244 return (rule == this);
245 }
246 };
247
248 private Job fTimeSelectJob = null;
249
250 private final class ColumnListener extends ControlAdapter {
251 /*
252 * Make sure that the margin column is always first and keep the column
253 * order variable up to date.
254 */
255 @Override
256 public void controlMoved(ControlEvent e) {
257 int[] order = fTable.getColumnOrder();
258 if (order[0] != MARGIN_COLUMN_INDEX) {
259 for (int i = order.length - 1; i > 0; i--) {
260 if (order[i] == MARGIN_COLUMN_INDEX) {
261 order[i] = order[i - 1];
262 order[i - 1] = MARGIN_COLUMN_INDEX;
263 }
264 }
265 fTable.setColumnOrder(order);
266 }
267 fColumnOrder = order;
268 fTable.layout();
269 }
270
271 @Override
272 public void controlResized(ControlEvent e) {
273 TableColumn column = (TableColumn) e.widget;
274 if (column.getResizable() && !isExpanded(column)) {
275 int i = (int) column.getData(Key.INDEX);
276 fColumnSize[i] = column.getWidth();
277 column.setData(Key.WIDTH, fColumnSize[i]);
278 }
279 }
280 }
281
282 private final class TableSelectionListener extends SelectionAdapter {
283 @Override
284 public void widgetSelected(final SelectionEvent e) {
285 if (e.item == null) {
286 return;
287 }
288 updateStatusLine(null);
289 if (fTable.getSelectionIndices().length > 0) {
290 if (e.item.getData(Key.RANK) instanceof Long) {
291 fSelectedRank = (Long) e.item.getData(Key.RANK);
292 fRawViewer.selectAndReveal((Long) e.item.getData(Key.RANK));
293 } else {
294 fSelectedRank = -1;
295 }
296 if (fTable.getSelectionIndices().length == 1) {
297 fSelectedBeginRank = fSelectedRank;
298 }
299 if (e.item.getData(Key.TIMESTAMP) instanceof ITmfTimestamp) {
300 final ITmfTimestamp ts = NonNullUtils.checkNotNull((ITmfTimestamp) e.item.getData(Key.TIMESTAMP));
301 if (fTable.getSelectionIndices().length == 1) {
302 fSelectedBeginTimestamp = ts;
303 }
304 ITmfTimestamp selectedBeginTimestamp = fSelectedBeginTimestamp;
305 if (selectedBeginTimestamp != null) {
306 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, selectedBeginTimestamp, ts));
307 if (fTable.getSelectionIndices().length == 2) {
308 updateStatusLine(ts.getDelta(selectedBeginTimestamp));
309 }
310 }
311 } else {
312 if (fTable.getSelectionIndices().length == 1) {
313 fSelectedBeginTimestamp = null;
314 }
315 }
316 }
317 if (e.item.getData() instanceof ITmfEvent) {
318 broadcast(new TmfEventSelectedSignal(TmfEventsTable.this, (ITmfEvent) e.item.getData()));
319 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, new StructuredSelection(e.item.getData())));
320 } else {
321 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, StructuredSelection.EMPTY));
322 }
323 }
324 }
325
326 private final class MouseDoubleClickListener extends MouseAdapter {
327 @Override
328 public void mouseDoubleClick(final MouseEvent event) {
329 if (event.button != 1) {
330 return;
331 }
332 // Identify the selected row
333 final Point point = new Point(event.x, event.y);
334 final TableItem item = fTable.getItem(point);
335 if (item != null) {
336 final Rectangle imageBounds = item.getImageBounds(0);
337 imageBounds.width = BOOKMARK_IMAGE.getBounds().width;
338 if (imageBounds.contains(point)) {
339 final Long rank = (Long) item.getData(Key.RANK);
340 if (rank != null) {
341 toggleBookmark(rank);
342 }
343 }
344 }
345 }
346 }
347
348 private final class RawSelectionListener implements Listener {
349 @Override
350 public void handleEvent(final Event e) {
351 if (fTrace == null) {
352 return;
353 }
354 long rank;
355 if (e.data instanceof Long) {
356 rank = (Long) e.data;
357 } else if (e.data instanceof ITmfLocation) {
358 rank = findRank((ITmfLocation) e.data);
359 } else {
360 return;
361 }
362 int index = (int) rank;
363 if (fTable.getData(Key.FILTER_OBJ) != null) {
364 // +1 for top filter status row
365 index = fCache.getFilteredEventIndex(rank) + 1;
366 }
367 // +1 for header row
368 fTable.setSelection(index + 1);
369 fSelectedRank = rank;
370 fSelectedBeginRank = fSelectedRank;
371 updateStatusLine(null);
372 final TableItem[] selection = fTable.getSelection();
373 if ((selection != null) && (selection.length > 0)) {
374 TableItem item = fTable.getSelection()[0];
375 final TmfTimestamp ts = (TmfTimestamp) item.getData(Key.TIMESTAMP);
376 if (ts != null) {
377 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, ts));
378 }
379 if (item.getData() instanceof ITmfEvent) {
380 broadcast(new TmfEventSelectedSignal(TmfEventsTable.this, (ITmfEvent) item.getData()));
381 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, new StructuredSelection(item.getData())));
382 } else {
383 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, StructuredSelection.EMPTY));
384 }
385 }
386 }
387
388 private long findRank(final ITmfLocation selectedLocation) {
389 final double selectedRatio = fTrace.getLocationRatio(selectedLocation);
390 long low = 0;
391 long high = fTrace.getNbEvents();
392 long rank = high / 2;
393 double ratio = -1;
394 while (ratio != selectedRatio) {
395 ITmfContext context = fTrace.seekEvent(rank);
396 ratio = fTrace.getLocationRatio(context.getLocation());
397 context.dispose();
398 if (ratio < selectedRatio) {
399 low = rank;
400 rank = (rank + high) / 2;
401 } else if (ratio > selectedRatio) {
402 high = rank;
403 rank = (rank + low) / 2;
404 }
405 if ((high - low) < 2) {
406 break;
407 }
408 }
409 return rank;
410 }
411 }
412
413 private final class SetDataListener implements Listener {
414 @Override
415 public void handleEvent(final Event event) {
416
417 final TableItem item = (TableItem) event.item;
418 int index = event.index - 1; // -1 for the header row
419
420 if (event.index == 0) {
421 setHeaderRowItemData(item);
422 return;
423 }
424
425 if (fTable.getData(Key.FILTER_OBJ) != null) {
426 if ((event.index == 1) || (event.index == (fTable.getItemCount() - 1))) {
427 setFilterStatusRowItemData(item);
428 return;
429 }
430 /* -1 for top filter status row */
431 index = index - 1;
432 }
433
434 final CachedEvent cachedEvent = fCache.getEvent(index);
435 if (cachedEvent != null) {
436 setItemData(item, cachedEvent, cachedEvent.rank);
437 return;
438 }
439
440 // Else, fill the cache asynchronously (and off the UI thread)
441 event.doit = false;
442 }
443 }
444
445 private static final class PainItemListener implements Listener {
446 @Override
447 public void handleEvent(Event event) {
448 TableItem item = (TableItem) event.item;
449
450 // we promised to paint the table item's foreground
451 GC gc = event.gc;
452 Image image = item.getImage(event.index);
453 if (image != null) {
454 Rectangle imageBounds = item.getImageBounds(event.index);
455 /*
456 * The image bounds don't match the default image position.
457 */
458 if (IS_LINUX) {
459 gc.drawImage(image, imageBounds.x + 1, imageBounds.y + 3);
460 } else {
461 gc.drawImage(image, imageBounds.x, imageBounds.y + 1);
462 }
463 }
464 gc.setForeground(item.getForeground(event.index));
465 gc.setFont(item.getFont(event.index));
466 String text = item.getText(event.index);
467 Rectangle textBounds = item.getTextBounds(event.index);
468 /*
469 * The text bounds don't match the default text position.
470 */
471 if (IS_LINUX) {
472 gc.drawText(text, textBounds.x + 1, textBounds.y + 3, true);
473 } else {
474 gc.drawText(text, textBounds.x - 1, textBounds.y + 2, true);
475 }
476 }
477 }
478
479 private static final class EraseItemListener implements Listener {
480 @Override
481 public void handleEvent(Event event) {
482 TableItem item = (TableItem) event.item;
483 List<?> styleRanges = (List<?>) item.getData(Key.STYLE_RANGES);
484
485 GC gc = event.gc;
486 Color background = item.getBackground(event.index);
487 /*
488 * Paint the background if it is not the default system color. In
489 * Windows, if you let the widget draw the background, it will not
490 * show the item's background color if the item is selected or hot.
491 * If there are no style ranges and the item background is the
492 * default system color, we do not want to paint it or otherwise we
493 * would override the platform theme (e.g. alternating colors).
494 */
495 if (styleRanges != null || !background.equals(item.getParent().getBackground())) {
496 // we will paint the table item's background
497 event.detail &= ~SWT.BACKGROUND;
498
499 // paint the item's default background
500 gc.setBackground(background);
501 gc.fillRectangle(event.x, event.y, event.width, event.height);
502 }
503
504 /*
505 * We will paint the table item's foreground. In Windows, if you
506 * paint the background but let the widget draw the foreground, it
507 * will override your background, unless the item is selected or
508 * hot.
509 */
510 event.detail &= ~SWT.FOREGROUND;
511
512 // paint the highlighted background for all style ranges
513 if (styleRanges != null) {
514 Rectangle textBounds = item.getTextBounds(event.index);
515 String text = item.getText(event.index);
516 for (Object o : styleRanges) {
517 if (o instanceof StyleRange) {
518 StyleRange styleRange = (StyleRange) o;
519 if (styleRange.data.equals(event.index)) {
520 int startIndex = styleRange.start;
521 int endIndex = startIndex + styleRange.length;
522 int startX = gc.textExtent(text.substring(0, startIndex)).x;
523 int endX = gc.textExtent(text.substring(0, endIndex)).x;
524 gc.setBackground(styleRange.background);
525 gc.fillRectangle(textBounds.x + startX, textBounds.y, (endX - startX), textBounds.height);
526 }
527 }
528 }
529 }
530 }
531 }
532
533 private final class TooltipListener implements Listener {
534 Shell tooltipShell = null;
535
536 @Override
537 public void handleEvent(final Event event) {
538 switch (event.type) {
539 case SWT.MouseHover:
540 final TableItem item = fTable.getItem(new Point(event.x, event.y));
541 if (item == null) {
542 return;
543 }
544 String text;
545 if (fTable.indexOf(item) == 0) {
546 if (fHeaderState == HeaderState.SEARCH && item.getBounds(0).contains(event.x, event.y)) {
547 text = Messages.TmfEventsTable_AddAsFilterText;
548 } else {
549 return;
550 }
551 } else {
552 final Long rank = (Long) item.getData(Key.RANK);
553 if (rank == null) {
554 return;
555 }
556 final String tooltipText = (String) item.getData(Key.BOOKMARK);
557 final Rectangle bounds = item.getImageBounds(0);
558 bounds.width = BOOKMARK_IMAGE.getBounds().width;
559 if (!bounds.contains(event.x, event.y)) {
560 return;
561 }
562 text = rank.toString() + (tooltipText != null ? ": " + tooltipText : EMPTY_STRING); //$NON-NLS-1$
563 }
564 if ((tooltipShell != null) && !tooltipShell.isDisposed()) {
565 tooltipShell.dispose();
566 }
567 tooltipShell = new Shell(fTable.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
568 tooltipShell.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
569 final FillLayout layout = new FillLayout();
570 layout.marginWidth = 2;
571 tooltipShell.setLayout(layout);
572 final Label label = new Label(tooltipShell, SWT.WRAP);
573 label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
574 label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
575 label.setText(text);
576 label.addListener(SWT.MouseExit, this);
577 label.addListener(SWT.MouseDown, this);
578 label.addListener(SWT.MouseWheel, this);
579 final Point size = tooltipShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
580 /*
581 * Bug in Linux. The coordinates of the event have an origin
582 * that excludes the table header but the method toDisplay()
583 * expects coordinates relative to an origin that includes the
584 * table header.
585 */
586 int y = event.y;
587 if (IS_LINUX) {
588 y += fTable.getHeaderHeight();
589 }
590 Point pt = fTable.toDisplay(event.x, y);
591 pt.x += BOOKMARK_IMAGE.getBounds().width;
592 pt.y += item.getBounds().height;
593 tooltipShell.setBounds(pt.x, pt.y, size.x, size.y);
594 tooltipShell.setVisible(true);
595 break;
596 case SWT.Dispose:
597 case SWT.KeyDown:
598 case SWT.MouseMove:
599 case SWT.MouseExit:
600 case SWT.MouseDown:
601 case SWT.MouseWheel:
602 if (tooltipShell != null) {
603 tooltipShell.dispose();
604 tooltipShell = null;
605 }
606 break;
607 default:
608 break;
609 }
610 }
611 }
612
613 /**
614 * The events table search/filter/data keys
615 *
616 * @author Patrick Tasse
617 * @noimplement This interface only contains Event Table specific static
618 * definitions.
619 */
620 public interface Key {
621
622 /** Search text */
623 String SEARCH_TXT = "$srch_txt"; //$NON-NLS-1$
624
625 /** Search object */
626 String SEARCH_OBJ = "$srch_obj"; //$NON-NLS-1$
627
628 /** Filter text */
629 String FILTER_TXT = "$fltr_txt"; //$NON-NLS-1$
630
631 /** Filter object */
632 String FILTER_OBJ = "$fltr_obj"; //$NON-NLS-1$
633
634 /** Timestamp */
635 String TIMESTAMP = "$time"; //$NON-NLS-1$
636
637 /** Rank */
638 String RANK = "$rank"; //$NON-NLS-1$
639
640 /** Bookmark indicator */
641 String BOOKMARK = "$bookmark"; //$NON-NLS-1$
642
643 /** Event aspect represented by this column */
644 String ASPECT = "$aspect"; //$NON-NLS-1$
645
646 /**
647 * Table item list of style ranges
648 *
649 * @since 1.0
650 */
651 String STYLE_RANGES = "$style_ranges"; //$NON-NLS-1$
652
653 /**
654 * The width of a table item
655 *
656 * @since 1.1
657 */
658 String WIDTH = "$width"; //$NON-NLS-1$
659
660 /**
661 * The position of the column
662 *
663 * @since 2.1
664 */
665 String INDEX = "$index"; //$NON-NLS-1$
666 }
667
668 /**
669 * The events table search/filter state
670 *
671 * @version 1.0
672 * @author Patrick Tasse
673 */
674 public static enum HeaderState {
675 /**
676 * No search filter is applied
677 *
678 * @since 2.0
679 */
680 NO_SEARCH,
681
682 /** A search filter is applied */
683 SEARCH
684 }
685
686 interface Direction {
687 int FORWARD = +1;
688 int BACKWARD = -1;
689 }
690
691 // ------------------------------------------------------------------------
692 // Table data
693 // ------------------------------------------------------------------------
694
695 /** The header bar */
696 private TmfEventsTableHeader fHeaderBar;
697
698 /** The virtual event table */
699 protected TmfVirtualTable fTable;
700
701 private Composite fComposite;
702 private SashForm fSashForm;
703 private Composite fTableComposite;
704 private TmfRawEventViewer fRawViewer;
705 private ITmfTrace fTrace;
706 private volatile boolean fPackDone = false;
707 private volatile boolean fPackMarginDone = false;
708 private HeaderState fHeaderState = HeaderState.NO_SEARCH;
709 private long fSelectedRank = -1;
710 private long fSelectedBeginRank = -1;
711 private ITmfTimestamp fSelectedBeginTimestamp = null;
712 private IStatusLineManager fStatusLineManager = null;
713
714 // Filter data
715 private long fFilterMatchCount;
716 private long fFilterCheckCount;
717 private FilterThread fFilterThread;
718 private boolean fFilterThreadResume = false;
719 private final Object fFilterSyncObj = new Object();
720 private SearchThread fSearchThread;
721 private final Object fSearchSyncObj = new Object();
722 private boolean fCollapseFilterEnabled = false;
723
724 /**
725 * List of selection change listeners (element type:
726 * <code>ISelectionChangedListener</code>).
727 *
728 * @see #fireSelectionChanged
729 */
730 private ListenerList selectionChangedListeners = new ListenerList();
731
732 // Bookmark map <Rank, MarkerId>
733 private Multimap<Long, Long> fBookmarksMap = HashMultimap.create();
734 private IFile fBookmarksFile;
735 private long fPendingGotoRank = -1;
736
737 // SWT resources
738 private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
739 private Color fGrayColor;
740 private Color fGreenColor;
741 private Color fHighlightColor;
742 private Font fFont;
743 private Font fBoldFont;
744
745 private final List<TmfEventTableColumn> fColumns = new LinkedList<>();
746
747 // Event cache
748 private final TmfEventsCache fCache;
749 private boolean fCacheUpdateBusy = false;
750 private boolean fCacheUpdatePending = false;
751 private boolean fCacheUpdateCompleted = false;
752 private final Object fCacheUpdateSyncObj = new Object();
753
754 // Keep track of column order, it is needed after table is disposed
755 private int[] fColumnOrder;
756
757 private boolean fDisposeOnClose;
758
759 private Menu fHeaderMenu;
760
761 private Menu fTablePopup;
762
763 private Menu fRawTablePopup;
764
765 private Point fLastMenuCursorLocation;
766 private MenuManager fRawViewerPopupMenuManager;
767 private MenuManager fTablePopupMenuManager;
768 private MenuManager fHeaderPopupMenuManager;
769
770 private boolean[] fColumnResizable;
771
772 private int[] fColumnSize;
773
774 // ------------------------------------------------------------------------
775 // Constructors
776 // ------------------------------------------------------------------------
777
778 /**
779 * Basic constructor, using the default set of columns
780 *
781 * @param parent
782 * The parent composite UI object
783 * @param cacheSize
784 * The size of the event table cache
785 */
786 public TmfEventsTable(final Composite parent, final int cacheSize) {
787 this(parent, cacheSize, TmfTrace.BASE_ASPECTS);
788 }
789
790 /**
791 * Legacy constructor, using ColumnData to define columns
792 *
793 * @param parent
794 * The parent composite UI object
795 * @param cacheSize
796 * The size of the event table cache
797 * @param columnData
798 * The column data array
799 * @deprecated Deprecated constructor, use
800 * {@link #TmfEventsTable(Composite, int, Collection)}
801 */
802 @Deprecated
803 public TmfEventsTable(final Composite parent, int cacheSize,
804 final org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) {
805 /*
806 * We'll do a "best-effort" to keep trace types still using this API to
807 * keep working, by defining a TmfEventTableColumn for each ColumnData
808 * they passed.
809 */
810 this(parent, cacheSize, convertFromColumnData(columnData));
811 }
812
813 @Deprecated
814 private static @NonNull Iterable<ITmfEventAspect<?>> convertFromColumnData(
815 org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) {
816
817 ImmutableList.Builder<ITmfEventAspect<?>> builder = new ImmutableList.Builder<>();
818 for (org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData col : columnData) {
819 String fieldName = col.header;
820 if (fieldName != null) {
821 builder.add(new TmfContentFieldAspect(fieldName, fieldName));
822 }
823 }
824 return builder.build();
825 }
826
827 /**
828 * Standard constructor, where we define which columns to use.
829 *
830 * @param parent
831 * The parent composite UI object
832 * @param cacheSize
833 * The size of the event table cache
834 * @param aspects
835 * The event aspects to display in this table. One column per
836 * aspect will be created.
837 * <p>
838 * The iteration order of this collection will correspond to the
839 * initial ordering of the columns in the table.
840 * </p>
841 */
842 public TmfEventsTable(final Composite parent, int cacheSize,
843 @NonNull Iterable<ITmfEventAspect<?>> aspects) {
844 super("TmfEventsTable"); //$NON-NLS-1$
845
846 fComposite = new Composite(parent, SWT.NONE);
847 GridLayout gl = new GridLayout(1, false);
848 gl.marginHeight = 0;
849 gl.marginWidth = 0;
850 gl.verticalSpacing = 0;
851 fComposite.setLayout(gl);
852
853 fSashForm = new SashForm(fComposite, SWT.HORIZONTAL);
854 fSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
855
856 // Create a composite for the table and its header bar
857 fTableComposite = new Composite(fSashForm, SWT.NONE);
858 fTableComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
859 gl = new GridLayout(1, false);
860 gl.marginHeight = 0;
861 gl.marginWidth = 0;
862 gl.verticalSpacing = 0;
863 fTableComposite.setLayout(gl);
864
865 // Create an events table header bar
866 fHeaderBar = new TmfEventsTableHeader(fTableComposite, SWT.NONE, new IEventsTableHeaderListener() {
867 @Override
868 public void filterSelected(ITmfFilter filter) {
869 if (filter instanceof TmfFilterMatchesNode) {
870 TmfFilterMatchesNode matchFilter = (TmfFilterMatchesNode) filter;
871 for (TableColumn col : fTable.getColumns()) {
872 if (col.getData(Key.ASPECT) == matchFilter.getEventAspect()) {
873 col.setData(Key.FILTER_TXT, matchFilter.getRegex());
874 } else {
875 col.setData(Key.FILTER_TXT, null);
876 }
877 }
878 fTable.refresh();
879 fTable.redraw();
880 }
881 }
882
883 @Override
884 public void filterRemoved(ITmfFilter filter) {
885 for (TableColumn col : fTable.getColumns()) {
886 col.setData(Key.FILTER_TXT, null);
887 }
888 removeFilter(filter);
889 }
890 });
891
892 // Create a virtual table
893 final int style = SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION;
894 fTable = new TmfVirtualTable(fTableComposite, style);
895
896 // Set the table layout
897 final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
898 fTable.setLayoutData(layoutData);
899
900 // Some cosmetic enhancements
901 fTable.setHeaderVisible(true);
902 fTable.setLinesVisible(true);
903
904 // Setup the columns
905 for (ITmfEventAspect<?> aspect : aspects) {
906 if (aspect != null) {
907 fColumns.add(new TmfEventTableColumn(aspect));
908
909 }
910 }
911
912 TmfMarginColumn collapseCol = new TmfMarginColumn();
913 fColumns.add(MARGIN_COLUMN_INDEX, collapseCol);
914
915 fHeaderMenu = new Menu(fTable);
916
917 fColumnSize = new int[fColumns.size()];
918 fColumnResizable = new boolean[fColumns.size()];
919 int i = 0;
920 // Create the UI columns in the table
921 for (TmfEventTableColumn col : fColumns) {
922 TableColumn column = fTable.newTableColumn(SWT.LEFT);
923 column.setText(col.getHeaderName());
924 column.setToolTipText(col.getHeaderTooltip());
925 column.setData(Key.ASPECT, col.getEventAspect());
926 column.setData(Key.INDEX, i);
927 if (col instanceof TmfMarginColumn) {
928 column.setResizable(false);
929 } else {
930 column.pack();
931 column.setMoveable(true);
932 column.setData(Key.WIDTH, column.getWidth());
933 fColumnSize[i] = column.getWidth();
934 }
935 column.addControlListener(new ColumnListener());
936 fColumnResizable[i] = column.getResizable();
937 i++;
938 }
939 fColumnOrder = fTable.getColumnOrder();
940
941 // Set the frozen row for header row
942 fTable.setFrozenRowCount(1);
943
944 // Create the header row cell editor
945 createHeaderEditor();
946
947 // Handle the table item selection
948 fTable.addSelectionListener(new TableSelectionListener());
949
950 int realCacheSize = Math.max(cacheSize, Display.getDefault().getBounds().height / fTable.getItemHeight());
951 realCacheSize = Math.min(realCacheSize, MAX_CACHE_SIZE);
952 fCache = new TmfEventsCache(realCacheSize, this);
953
954 // Handle the table item requests
955 fTable.addListener(SWT.SetData, new SetDataListener());
956
957 fTable.addMenuDetectListener(event -> {
958 fLastMenuCursorLocation = new Point(event.x, event.y);
959 Point pt = fTable.getDisplay().map(null, fTable, fLastMenuCursorLocation);
960 Rectangle clientArea = fTable.getClientArea();
961 boolean header = clientArea.y <= pt.y && pt.y < (clientArea.y + fTable.getHeaderHeight());
962 fTable.setMenu(header ? fHeaderMenu : fTablePopup);
963 });
964
965 fTable.addMouseListener(new MouseDoubleClickListener());
966
967 final Listener tooltipListener = new TooltipListener();
968
969 fTable.addListener(SWT.MouseHover, tooltipListener);
970 fTable.addListener(SWT.Dispose, tooltipListener);
971 fTable.addListener(SWT.KeyDown, tooltipListener);
972 fTable.addListener(SWT.MouseMove, tooltipListener);
973 fTable.addListener(SWT.MouseExit, tooltipListener);
974 fTable.addListener(SWT.MouseDown, tooltipListener);
975 fTable.addListener(SWT.MouseWheel, tooltipListener);
976
977 fTable.addListener(SWT.EraseItem, new EraseItemListener());
978
979 fTable.addListener(SWT.PaintItem, new PainItemListener());
980
981 // Create resources
982 createResources();
983
984 initializeFonts();
985 initializeColors();
986 PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(this);
987
988 ColorSettingsManager.addColorSettingsListener(this);
989
990 fTable.setItemCount(1); // +1 for header row
991
992 fRawViewer = new TmfRawEventViewer(fSashForm, SWT.H_SCROLL | SWT.V_SCROLL);
993
994 fRawViewer.addSelectionListener(new RawSelectionListener());
995
996 fSashForm.setWeights(new int[] { 1, 1 });
997 fRawViewer.setVisible(false);
998
999 createPopupMenu();
1000
1001 fComposite.addDisposeListener((e) -> {
1002 internalDispose();
1003 });
1004 }
1005
1006 /**
1007 * Checked menu creator to make columns visible or not.
1008 *
1009 * @param parent
1010 * the parent menu
1011 * @param column
1012 * the column
1013 */
1014 private IAction createHeaderAction(final TableColumn column) {
1015 final IAction columnMenuAction = new Action(column.getText(), IAction.AS_CHECK_BOX) {
1016 @Override
1017 public void run() {
1018 boolean isChecked = isChecked();
1019 if (isChecked) {
1020 int width = (int) column.getData(Key.WIDTH);
1021 column.setResizable(true);
1022 if (width == 0) {
1023 column.pack();
1024 } else {
1025 column.setWidth(width);
1026 }
1027 } else {
1028 column.setResizable(false);
1029 column.setWidth(0);
1030 }
1031 int pos = (int) column.getData(Key.INDEX);
1032 fColumnResizable[pos] = isChecked;
1033 }
1034 };
1035 columnMenuAction.setChecked(column.getResizable());
1036 return columnMenuAction;
1037 }
1038
1039 private IAction createResetHeaderAction() {
1040 return new Action(Messages.TmfEventsTable_ShowAll) {
1041 @Override
1042 public void run() {
1043 for (TableColumn column : fTable.getColumns()) {
1044 int index = (int) column.getData(Key.INDEX);
1045 if (index != MARGIN_COLUMN_INDEX) {
1046 final int width = (int) column.getData(Key.WIDTH);
1047 column.setResizable(true);
1048 if (width == 0) {
1049 column.pack();
1050 } else {
1051 column.setWidth(width);
1052 }
1053 fColumnResizable[index] = true;
1054 }
1055 }
1056 }
1057 };
1058 }
1059
1060 // ------------------------------------------------------------------------
1061 // Operations
1062 // ------------------------------------------------------------------------
1063
1064 /**
1065 * Create a pop-up menu.
1066 */
1067 private void createPopupMenu() {
1068 final IAction copyAction = new Action(Messages.TmfEventsTable_CopyToClipboardActionText) {
1069 @Override
1070 public void run() {
1071 ITmfTrace trace = fTrace;
1072 if (trace == null || (fSelectedRank == -1 && fSelectedBeginRank == -1)) {
1073 return;
1074 }
1075
1076 List<TmfEventTableColumn> columns = new ArrayList<>();
1077 for (int i : fTable.getColumnOrder()) {
1078 TableColumn column = fTable.getColumns()[i];
1079 // Omit the margin column and hidden columns
1080 if (isVisibleEventColumn(column)) {
1081 columns.add(fColumns.get(i));
1082 }
1083 }
1084
1085 long start = Math.min(fSelectedBeginRank, fSelectedRank);
1086 long end = Math.max(fSelectedBeginRank, fSelectedRank);
1087 final ITmfFilter filter = (ITmfFilter) fTable.getData(Key.FILTER_OBJ);
1088 IRunnableWithProgress operation = new CopyToClipboardOperation(trace, filter, columns, start, end);
1089 try {
1090 PlatformUI.getWorkbench().getProgressService().busyCursorWhile(operation);
1091 } catch (InvocationTargetException e) {
1092 Activator.getDefault().logError("Invocation target exception copying to clipboard ", e); //$NON-NLS-1$
1093 } catch (InterruptedException e) {
1094 /* ignored */
1095 }
1096 }
1097 };
1098
1099 final IAction showTableAction = new Action(Messages.TmfEventsTable_ShowTableActionText) {
1100 @Override
1101 public void run() {
1102 fTableComposite.setVisible(true);
1103 fSashForm.layout();
1104 }
1105 };
1106
1107 final IAction hideTableAction = new Action(Messages.TmfEventsTable_HideTableActionText) {
1108 @Override
1109 public void run() {
1110 fTableComposite.setVisible(false);
1111 fSashForm.layout();
1112 }
1113 };
1114
1115 final IAction showRawAction = new Action(Messages.TmfEventsTable_ShowRawActionText) {
1116 @Override
1117 public void run() {
1118 fRawViewer.setVisible(true);
1119 fSashForm.layout();
1120 final int index = fTable.getSelectionIndex();
1121 if (index >= 1) {
1122 fRawViewer.selectAndReveal(index - 1);
1123 }
1124 }
1125 };
1126
1127 final IAction hideRawAction = new Action(Messages.TmfEventsTable_HideRawActionText) {
1128 @Override
1129 public void run() {
1130 fRawViewer.setVisible(false);
1131 fSashForm.layout();
1132 }
1133 };
1134
1135 final IAction openCallsiteAction = new Action(Messages.TmfEventsTable_OpenSourceCodeActionText) {
1136 @Override
1137 public void run() {
1138 final TableItem items[] = fTable.getSelection();
1139 if (items.length != 1) {
1140 return;
1141 }
1142 final TableItem item = items[0];
1143
1144 final Object data = item.getData();
1145 if (!(data instanceof ITmfSourceLookup)) {
1146 return;
1147 }
1148 ITmfSourceLookup event = (ITmfSourceLookup) data;
1149 ITmfCallsite cs = event.getCallsite();
1150 if (cs == null) {
1151 return;
1152 }
1153 Long lineNo = cs.getLineNo();
1154 if (lineNo == null) {
1155 /* Not enough information to provide a full callsite */
1156 return;
1157 }
1158
1159 String fileName = cs.getFileName();
1160 final String trimmedPath = fileName.replaceAll("\\.\\./", EMPTY_STRING); //$NON-NLS-1$
1161 File fileToOpen = new File(trimmedPath);
1162
1163 try {
1164 if (fileToOpen.exists() && fileToOpen.isFile()) {
1165 /*
1166 * The path points to a "real" file, attempt to open
1167 * that
1168 */
1169 IFileStore fileStore = EFS.getLocalFileSystem().getStore(fileToOpen.toURI());
1170 IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
1171
1172 IEditorPart editor = IDE.openEditorOnFileStore(page, fileStore);
1173 if (editor instanceof ITextEditor) {
1174 /*
1175 * Calculate the "document offset" corresponding to
1176 * the line number, then seek there.
1177 */
1178 ITextEditor textEditor = (ITextEditor) editor;
1179 int lineNumber = lineNo.intValue();
1180 IDocument document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
1181
1182 IRegion region = document.getLineInformation(lineNumber - 1);
1183 if (region != null) {
1184 textEditor.selectAndReveal(region.getOffset(), region.getLength());
1185 }
1186 }
1187
1188 } else {
1189 /*
1190 * The file was not found on disk, attempt to find it in
1191 * the workspace instead.
1192 */
1193 IMarker marker = null;
1194 final ArrayList<IFile> files = new ArrayList<>();
1195 IPath p = new Path(trimmedPath);
1196 ResourcesPlugin.getWorkspace().getRoot().accept(new IResourceVisitor() {
1197 @Override
1198 public boolean visit(IResource resource) throws CoreException {
1199 if (resource instanceof IFile && resource.getFullPath().toString().endsWith(p.lastSegment())) {
1200 files.add((IFile) resource);
1201 }
1202 return true;
1203 }
1204 });
1205 IFile file = null;
1206 if (files.size() > 1) {
1207 ListDialog dialog = new ListDialog(getTable().getShell());
1208 dialog.setContentProvider(ArrayContentProvider.getInstance());
1209 dialog.setLabelProvider(new LabelProvider() {
1210 @Override
1211 public String getText(Object element) {
1212 return ((IFile) element).getFullPath().toString();
1213 }
1214 });
1215 dialog.setInput(files);
1216 dialog.setTitle(Messages.TmfEventsTable_OpenSourceCodeSelectFileDialogTitle);
1217 dialog.setMessage(Messages.TmfEventsTable_OpenSourceCodeSelectFileDialogTitle + '\n' + cs.toString());
1218 dialog.open();
1219 Object[] result = dialog.getResult();
1220 if (result != null && result.length > 0) {
1221 file = (IFile) result[0];
1222 }
1223 } else if (files.size() == 1) {
1224 file = files.get(0);
1225 }
1226 if (file != null) {
1227 marker = file.createMarker(IMarker.MARKER);
1228 marker.setAttribute(IMarker.LINE_NUMBER, lineNo.intValue());
1229 IDE.openEditor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), marker);
1230 marker.delete();
1231 } else if (files.isEmpty()) {
1232 displayException(new FileNotFoundException('\'' + cs.toString() + '\'' + '\n' + Messages.TmfEventsTable_OpenSourceCodeNotFound));
1233 }
1234 }
1235 } catch (BadLocationException | CoreException e) {
1236 displayException(e);
1237 }
1238 }
1239 };
1240
1241 final IAction openModelAction = new Action(Messages.TmfEventsTable_OpenModelActionText) {
1242 @Override
1243 public void run() {
1244
1245 final TableItem items[] = fTable.getSelection();
1246 if (items.length != 1) {
1247 return;
1248 }
1249 final TableItem item = items[0];
1250
1251 final Object eventData = item.getData();
1252 if (eventData instanceof ITmfModelLookup) {
1253 String modelURI = ((ITmfModelLookup) eventData).getModelUri();
1254
1255 if (modelURI != null) {
1256 IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
1257
1258 IFile file = null;
1259 final URI uri = URI.createURI(modelURI);
1260 if (uri.isPlatformResource()) {
1261 IPath path = new Path(uri.toPlatformString(true));
1262 file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
1263 } else if (uri.isFile() && !uri.isRelative()) {
1264 file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(
1265 new Path(uri.toFileString()));
1266 }
1267
1268 if (file != null) {
1269 try {
1270 /*
1271 * create a temporary validation marker on the
1272 * model file, remove it afterwards thus,
1273 * navigation works with all model editors
1274 * supporting the navigation to a marker
1275 */
1276 IMarker marker = file.createMarker(EValidator.MARKER);
1277 marker.setAttribute(EValidator.URI_ATTRIBUTE, modelURI);
1278 marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
1279
1280 IDE.openEditor(activePage, marker, OpenStrategy.activateOnOpen());
1281 marker.delete();
1282 } catch (CoreException e) {
1283 displayException(e);
1284 }
1285 } else {
1286 displayException(new FileNotFoundException('\'' + modelURI + '\'' + '\n' + Messages.TmfEventsTable_OpenModelUnsupportedURI));
1287 }
1288 }
1289 }
1290 }
1291 };
1292
1293 final IAction exportToTextAction = new Action(Messages.TmfEventsTable_Export_to_text) {
1294 @Override
1295 public void run() {
1296 IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
1297 Object handlerServiceObject = activePage.getActiveEditor().getSite().getService(IHandlerService.class);
1298 IHandlerService handlerService = (IHandlerService) handlerServiceObject;
1299 Object cmdServiceObject = activePage.getActiveEditor().getSite().getService(ICommandService.class);
1300 ICommandService cmdService = (ICommandService) cmdServiceObject;
1301 try {
1302 HashMap<String, Object> parameters = new HashMap<>();
1303 Command command = cmdService.getCommand(ExportToTextCommandHandler.COMMAND_ID);
1304 ParameterizedCommand cmd = ParameterizedCommand.generateCommand(command, parameters);
1305
1306 IEvaluationContext context = handlerService.getCurrentState();
1307 List<TmfEventTableColumn> exportColumns = new ArrayList<>();
1308 for (int i : fTable.getColumnOrder()) {
1309 TableColumn column = fTable.getColumns()[i];
1310 // Omit the margin column and hidden columns
1311 if (isVisibleEventColumn(column)) {
1312 exportColumns.add(fColumns.get(i));
1313 }
1314 }
1315 context.addVariable(ExportToTextCommandHandler.TMF_EVENT_TABLE_COLUMNS_ID, exportColumns);
1316
1317 handlerService.executeCommandInContext(cmd, null, context);
1318 } catch (ExecutionException | NotDefinedException | NotEnabledException | NotHandledException e) {
1319 displayException(e);
1320 }
1321 }
1322 };
1323
1324 final IAction addAsFilterAction = new Action(Messages.TmfEventsTable_AddAsFilterText) {
1325 @Override
1326 public void run() {
1327 applySearchAsFilter();
1328 }
1329 };
1330
1331 final IAction clearFiltersAction = new Action(Messages.TmfEventsTable_ClearFiltersActionText) {
1332 @Override
1333 public void run() {
1334 clearFilters();
1335 }
1336 };
1337
1338 final IAction collapseAction = new Action(Messages.TmfEventsTable_CollapseFilterMenuName) {
1339 @Override
1340 public void run() {
1341 applyFilter(new TmfCollapseFilter());
1342 }
1343 };
1344
1345 class ToggleBookmarkAction extends Action {
1346 Long fRank;
1347
1348 public ToggleBookmarkAction(final String text, final Long rank) {
1349 super(text);
1350 fRank = rank;
1351 }
1352
1353 @Override
1354 public void run() {
1355 toggleBookmark(fRank);
1356 }
1357 }
1358
1359 fHeaderPopupMenuManager = new MenuManager();
1360 fHeaderPopupMenuManager.setRemoveAllWhenShown(true);
1361 fHeaderPopupMenuManager.addMenuListener(new IMenuListener() {
1362 @Override
1363 public void menuAboutToShow(IMenuManager manager) {
1364 for (int index : fTable.getColumnOrder()) {
1365 if (fTable.getColumns()[index].getData(Key.WIDTH) != null) {
1366 fHeaderPopupMenuManager.add(createHeaderAction(fTable.getColumns()[index]));
1367 }
1368 }
1369 fHeaderPopupMenuManager.add(new Separator());
1370 fHeaderPopupMenuManager.add(createResetHeaderAction());
1371 }
1372 });
1373
1374 fTablePopupMenuManager = new MenuManager();
1375 fTablePopupMenuManager.setRemoveAllWhenShown(true);
1376 fTablePopupMenuManager.addMenuListener(new IMenuListener() {
1377 @Override
1378 public void menuAboutToShow(final IMenuManager manager) {
1379 if (fTable.getSelectionIndices().length == 1 && fTable.getSelectionIndices()[0] == 0) {
1380 // Right-click on header row
1381 if (fHeaderState == HeaderState.SEARCH) {
1382 fTablePopupMenuManager.add(addAsFilterAction);
1383 }
1384 return;
1385 }
1386 final Point point = fTable.toControl(fLastMenuCursorLocation);
1387 final TableItem item = fTable.getSelection().length > 0 ? fTable.getSelection()[0] : null;
1388 if (item != null) {
1389 final Rectangle imageBounds = item.getImageBounds(0);
1390 imageBounds.width = BOOKMARK_IMAGE.getBounds().width;
1391 if (point.x <= (imageBounds.x + imageBounds.width)) {
1392 // Right-click on left margin
1393 final Long rank = (Long) item.getData(Key.RANK);
1394 if ((rank != null) && (fBookmarksFile != null)) {
1395 if (fBookmarksMap.containsKey(rank)) {
1396 fTablePopupMenuManager.add(new ToggleBookmarkAction(
1397 Messages.TmfEventsTable_RemoveBookmarkActionText, rank));
1398 } else {
1399 fTablePopupMenuManager.add(new ToggleBookmarkAction(
1400 Messages.TmfEventsTable_AddBookmarkActionText, rank));
1401 }
1402 }
1403 return;
1404 }
1405 }
1406
1407 // Right-click on table
1408 if (fSelectedRank != -1 && fSelectedBeginRank != -1) {
1409 fTablePopupMenuManager.add(copyAction);
1410 fTablePopupMenuManager.add(new Separator());
1411 }
1412 if (fTable.isVisible() && fRawViewer.isVisible()) {
1413 fTablePopupMenuManager.add(hideTableAction);
1414 fTablePopupMenuManager.add(hideRawAction);
1415 } else if (!fTable.isVisible()) {
1416 fTablePopupMenuManager.add(showTableAction);
1417 } else if (!fRawViewer.isVisible()) {
1418 fTablePopupMenuManager.add(showRawAction);
1419 }
1420 fTablePopupMenuManager.add(exportToTextAction);
1421 fTablePopupMenuManager.add(new Separator());
1422
1423 if (item != null) {
1424 final Object data = item.getData();
1425 Separator separator = null;
1426 if (data instanceof ITmfSourceLookup) {
1427 ITmfSourceLookup event = (ITmfSourceLookup) data;
1428 if (event.getCallsite() != null) {
1429 fTablePopupMenuManager.add(openCallsiteAction);
1430 separator = new Separator();
1431 }
1432 }
1433
1434 if (data instanceof ITmfModelLookup) {
1435 ITmfModelLookup event = (ITmfModelLookup) data;
1436 if (event.getModelUri() != null) {
1437 fTablePopupMenuManager.add(openModelAction);
1438 separator = new Separator();
1439 }
1440
1441 if (separator != null) {
1442 fTablePopupMenuManager.add(separator);
1443 }
1444 }
1445 }
1446
1447 /*
1448 * Only show collapse filter if at least one trace can be
1449 * collapsed.
1450 */
1451 boolean isCollapsible = false;
1452 if (fTrace != null) {
1453 for (ITmfTrace trace : TmfTraceManager.getTraceSet(fTrace)) {
1454 Class<? extends ITmfEvent> eventClass = trace.getEventType();
1455 isCollapsible = ITmfCollapsibleEvent.class.isAssignableFrom(eventClass);
1456 if (isCollapsible) {
1457 break;
1458 }
1459 }
1460 }
1461
1462 if (isCollapsible && !fCollapseFilterEnabled) {
1463 fTablePopupMenuManager.add(collapseAction);
1464 fTablePopupMenuManager.add(new Separator());
1465 }
1466
1467 fTablePopupMenuManager.add(clearFiltersAction);
1468 final ITmfFilterTreeNode[] savedFilters = FilterManager.getSavedFilters();
1469 if (savedFilters.length > 0) {
1470 final MenuManager subMenu = new MenuManager(Messages.TmfEventsTable_ApplyPresetFilterMenuName);
1471 for (final ITmfFilterTreeNode node : savedFilters) {
1472 if (node instanceof TmfFilterNode) {
1473 final TmfFilterNode filter = (TmfFilterNode) node;
1474 subMenu.add(new Action(filter.getFilterName()) {
1475 @Override
1476 public void run() {
1477 applyFilter(filter);
1478 }
1479 });
1480 }
1481 }
1482 fTablePopupMenuManager.add(subMenu);
1483 }
1484 appendToTablePopupMenu(fTablePopupMenuManager, item);
1485 }
1486 });
1487
1488 fRawViewerPopupMenuManager = new MenuManager();
1489 fRawViewerPopupMenuManager.setRemoveAllWhenShown(true);
1490 fRawViewerPopupMenuManager.addMenuListener(new IMenuListener() {
1491 @Override
1492 public void menuAboutToShow(final IMenuManager manager) {
1493 if (fTable.isVisible() && fRawViewer.isVisible()) {
1494 fRawViewerPopupMenuManager.add(hideTableAction);
1495 fRawViewerPopupMenuManager.add(hideRawAction);
1496 } else if (!fTable.isVisible()) {
1497 fRawViewerPopupMenuManager.add(showTableAction);
1498 } else if (!fRawViewer.isVisible()) {
1499 fRawViewerPopupMenuManager.add(showRawAction);
1500 }
1501 appendToRawPopupMenu(fRawViewerPopupMenuManager);
1502 }
1503 });
1504
1505 fHeaderMenu = fHeaderPopupMenuManager.createContextMenu(fTable);
1506
1507 fTablePopup = fTablePopupMenuManager.createContextMenu(fTable);
1508 fTable.setMenu(fTablePopup);
1509
1510 fRawTablePopup = fRawViewerPopupMenuManager.createContextMenu(fRawViewer);
1511 fRawViewer.setMenu(fRawTablePopup);
1512 }
1513
1514 /**
1515 * Append an item to the event table's pop-up menu.
1516 *
1517 * @param tablePopupMenu
1518 * The menu manager
1519 * @param selectedItem
1520 * The item to append
1521 */
1522 protected void appendToTablePopupMenu(final MenuManager tablePopupMenu, final TableItem selectedItem) {
1523 // override to append more actions
1524 }
1525
1526 /**
1527 * Append an item to the raw viewer's pop-up menu.
1528 *
1529 * @param rawViewerPopupMenu
1530 * The menu manager
1531 */
1532 protected void appendToRawPopupMenu(final MenuManager rawViewerPopupMenu) {
1533 // override to append more actions
1534 }
1535
1536 @Override
1537 public void dispose() {
1538 fComposite.dispose();
1539 }
1540
1541 private void internalDispose() {
1542 stopSearchThread();
1543 stopFilterThread();
1544 PlatformUI.getWorkbench().getThemeManager().removePropertyChangeListener(this);
1545 ColorSettingsManager.removeColorSettingsListener(this);
1546 fCache.clear();
1547 if ((fTrace != null) && fDisposeOnClose) {
1548 fTrace.dispose();
1549 }
1550 fResourceManager.dispose();
1551 if (fRawViewerPopupMenuManager != null) {
1552 fRawViewerPopupMenuManager.dispose();
1553 }
1554 if (fHeaderPopupMenuManager != null) {
1555 fHeaderPopupMenuManager.dispose();
1556 }
1557 if (fTablePopupMenuManager != null) {
1558 fTablePopupMenuManager.dispose();
1559 }
1560
1561 super.dispose();
1562 }
1563
1564 /**
1565 * Assign a layout data object to this view.
1566 *
1567 * @param layoutData
1568 * The layout data to assign
1569 */
1570 public void setLayoutData(final Object layoutData) {
1571 fComposite.setLayoutData(layoutData);
1572 }
1573
1574 /**
1575 * Get the virtual table contained in this event table.
1576 *
1577 * @return The TMF virtual table
1578 */
1579 public TmfVirtualTable getTable() {
1580 return fTable;
1581 }
1582
1583 /**
1584 * @param columnData
1585 * columnData
1586 * @deprecated The column headers are now set at the constructor, this
1587 * shouldn't be called anymore.
1588 */
1589 @Deprecated
1590 protected void setColumnHeaders(final org.eclipse.tracecompass.tmf.ui.widgets.virtualtable.ColumnData[] columnData) {
1591 /* No-op */
1592 }
1593
1594 /**
1595 * Set a table item's data.
1596 *
1597 * @param item
1598 * The item to set
1599 * @param event
1600 * Which trace event to link with this entry
1601 * @param rank
1602 * Which rank this event has in the trace/experiment
1603 */
1604 protected void setItemData(final TableItem item, final ITmfEvent event, final long rank) {
1605 String[] itemStrings = getItemStrings(fColumns, event);
1606
1607 // Get the actual ITmfEvent from the CachedEvent
1608 ITmfEvent tmfEvent = event;
1609 if (event instanceof CachedEvent) {
1610 tmfEvent = ((CachedEvent) event).event;
1611 }
1612 item.setText(itemStrings);
1613 item.setData(tmfEvent);
1614 item.setData(Key.TIMESTAMP, tmfEvent.getTimestamp());
1615 item.setData(Key.RANK, rank);
1616
1617 final Collection<Long> markerIds = fBookmarksMap.get(rank);
1618 if (!markerIds.isEmpty()) {
1619 Joiner joiner = Joiner.on("\n -").skipNulls(); //$NON-NLS-1$
1620 List<Object> parts = new ArrayList<>();
1621 if (markerIds.size() > 1) {
1622 parts.add(Messages.TmfEventsTable_MultipleBookmarksToolTip);
1623 }
1624 try {
1625 for (long markerId : markerIds) {
1626 final IMarker marker = fBookmarksFile.findMarker(markerId);
1627 if (marker != null) {
1628 parts.add(marker.getAttribute(IMarker.MESSAGE));
1629 }
1630 }
1631 } catch (CoreException e) {
1632 displayException(e);
1633 }
1634 item.setData(Key.BOOKMARK, joiner.join(parts));
1635 } else {
1636 item.setData(Key.BOOKMARK, null);
1637 }
1638
1639 boolean searchMatch = false;
1640 boolean searchNoMatch = false;
1641 final ITmfFilter searchFilter = (ITmfFilter) fTable.getData(Key.SEARCH_OBJ);
1642 if (searchFilter != null) {
1643 if (searchFilter.matches(tmfEvent)) {
1644 searchMatch = true;
1645 } else {
1646 searchNoMatch = true;
1647 }
1648 }
1649
1650 final ColorSetting colorSetting = ColorSettingsManager.getColorSetting(tmfEvent);
1651 if (searchNoMatch) {
1652 item.setForeground(colorSetting.getDimmedForegroundColor());
1653 item.setBackground(colorSetting.getDimmedBackgroundColor());
1654 } else {
1655 item.setForeground(colorSetting.getForegroundColor());
1656 item.setBackground(colorSetting.getBackgroundColor());
1657 }
1658 item.setFont(fFont);
1659
1660 if (searchMatch) {
1661 if (!markerIds.isEmpty()) {
1662 item.setImage(SEARCH_MATCH_BOOKMARK_IMAGE);
1663 } else {
1664 item.setImage(SEARCH_MATCH_IMAGE);
1665 }
1666 } else if (!markerIds.isEmpty()) {
1667 item.setImage(BOOKMARK_IMAGE);
1668 } else {
1669 item.setImage((Image) null);
1670 }
1671
1672 List<StyleRange> styleRanges = new ArrayList<>();
1673 for (int index = 0; index < fTable.getColumns().length; index++) {
1674 TableColumn column = fTable.getColumns()[index];
1675 String regex = null;
1676 if (fHeaderState == HeaderState.SEARCH) {
1677 if (searchMatch) {
1678 regex = (String) column.getData(Key.SEARCH_TXT);
1679 }
1680 } else {
1681 regex = (String) column.getData(Key.FILTER_TXT);
1682 }
1683 if (regex != null) {
1684 String text = item.getText(index);
1685 try {
1686 Pattern pattern = Pattern.compile(regex);
1687 Matcher matcher = pattern.matcher(text);
1688 while (matcher.find()) {
1689 int start = matcher.start();
1690 int length = matcher.end() - start;
1691 Color foreground = colorSetting.getForegroundColor();
1692 Color background = fHighlightColor;
1693 StyleRange styleRange = new StyleRange(start, length, foreground, background);
1694 styleRange.data = index;
1695 styleRanges.add(styleRange);
1696 }
1697 } catch (PatternSyntaxException e) {
1698 /* ignored */
1699 }
1700 }
1701 }
1702 if (styleRanges.isEmpty()) {
1703 item.setData(Key.STYLE_RANGES, null);
1704 } else {
1705 item.setData(Key.STYLE_RANGES, styleRanges);
1706 }
1707 item.getParent().redraw();
1708
1709 if ((itemStrings[MARGIN_COLUMN_INDEX] != null) && !itemStrings[MARGIN_COLUMN_INDEX].isEmpty()) {
1710 packMarginColumn();
1711 }
1712 }
1713
1714 /**
1715 * Set the item data of the header row.
1716 *
1717 * @param item
1718 * The item to use as table header
1719 */
1720 protected void setHeaderRowItemData(final TableItem item) {
1721 if (fHeaderState == HeaderState.NO_SEARCH) {
1722 item.setImage(SEARCH_IMAGE);
1723 } else if (fHeaderState == HeaderState.SEARCH) {
1724 item.setImage(FILTER_ADD_IMAGE);
1725 }
1726 item.setForeground(fGrayColor);
1727 // Ignore collapse and image column
1728 for (int i = EVENT_COLUMNS_START_INDEX; i < fTable.getColumns().length; i++) {
1729 final TableColumn column = fTable.getColumns()[i];
1730 final String filter = (String) column.getData(Key.SEARCH_TXT);
1731 if (filter == null) {
1732 item.setText(i, SEARCH_HINT);
1733 item.setForeground(i, fGrayColor);
1734 item.setFont(i, fFont);
1735 } else {
1736 item.setText(i, filter);
1737 item.setForeground(i, fGreenColor);
1738 item.setFont(i, fBoldFont);
1739 }
1740 }
1741 if (!fPackMarginDone) {
1742 packMarginColumn();
1743 fPackMarginDone = true;
1744 }
1745 }
1746
1747 /**
1748 * Set the item data of the "filter status" row.
1749 *
1750 * @param item
1751 * The item to use as filter status row
1752 */
1753 protected void setFilterStatusRowItemData(final TableItem item) {
1754 for (int i = 0; i < fTable.getColumns().length; i++) {
1755 if (i == MARGIN_COLUMN_INDEX) {
1756 if ((fTrace == null) || (fFilterCheckCount == fTrace.getNbEvents())) {
1757 item.setImage(FILTER_IMAGE);
1758 } else {
1759 item.setImage(STOP_IMAGE);
1760 }
1761 }
1762
1763 if (i == FILTER_SUMMARY_INDEX) {
1764 item.setText(FILTER_SUMMARY_INDEX, fFilterMatchCount + "/" + fFilterCheckCount); //$NON-NLS-1$
1765 } else {
1766 item.setText(i, EMPTY_STRING);
1767 }
1768 }
1769 item.setData(null);
1770 item.setData(Key.TIMESTAMP, null);
1771 item.setData(Key.RANK, null);
1772 item.setData(Key.STYLE_RANGES, null);
1773 item.setForeground(null);
1774 item.setBackground(null);
1775 item.setFont(fFont);
1776 }
1777
1778 /**
1779 * Create an editor for the header.
1780 */
1781 private void createHeaderEditor() {
1782 final TableEditor tableEditor = fTable.createTableEditor();
1783 tableEditor.horizontalAlignment = SWT.LEFT;
1784 tableEditor.verticalAlignment = SWT.CENTER;
1785 tableEditor.grabHorizontal = true;
1786 tableEditor.minimumWidth = 50;
1787
1788 // Handle the header row selection
1789 fTable.addMouseListener(new MouseAdapter() {
1790 int columnIndex;
1791 TableColumn column;
1792 TableItem item;
1793
1794 @Override
1795 public void mouseDown(final MouseEvent event) {
1796 if (event.button != 1) {
1797 return;
1798 }
1799 // Identify the selected row
1800 final Point point = new Point(event.x, event.y);
1801 item = fTable.getItem(point);
1802
1803 // Header row selected
1804 if ((item != null) && (fTable.indexOf(item) == 0)) {
1805
1806 // Margin column selected
1807 if (item.getBounds(0).contains(point)) {
1808 if (fHeaderState == HeaderState.SEARCH) {
1809 applySearchAsFilter();
1810 }
1811 return;
1812 }
1813
1814 // Identify the selected column
1815 columnIndex = -1;
1816 for (int i = 0; i < fTable.getColumns().length; i++) {
1817 final Rectangle rect = item.getBounds(i);
1818 if (rect.contains(point)) {
1819 columnIndex = i;
1820 break;
1821 }
1822 }
1823
1824 if (columnIndex == -1) {
1825 return;
1826 }
1827
1828 column = fTable.getColumns()[columnIndex];
1829
1830 /*
1831 * The control that will be the editor must be a child of
1832 * the Table
1833 */
1834 final Text newEditor = (Text) fTable.createTableEditorControl(Text.class);
1835 final String headerString = (String) column.getData(Key.SEARCH_TXT);
1836 if (headerString != null) {
1837 newEditor.setText(headerString);
1838 }
1839 newEditor.addFocusListener(new FocusAdapter() {
1840 @Override
1841 public void focusLost(final FocusEvent e) {
1842 final boolean changed = updateHeader(newEditor.getText());
1843 if (changed) {
1844 applyHeader();
1845 }
1846 }
1847 });
1848 newEditor.addKeyListener(new KeyAdapter() {
1849 @Override
1850 public void keyPressed(final KeyEvent e) {
1851 if (e.character == SWT.CR) {
1852 updateHeader(newEditor.getText());
1853 applyHeader();
1854 if ((e.stateMask & SWT.CTRL) != 0) {
1855 applySearchAsFilter();
1856 }
1857 /*
1858 * Set focus on the table so that the next
1859 * carriage return goes to the next result
1860 */
1861 TmfEventsTable.this.getTable().setFocus();
1862 } else if (e.character == SWT.ESC) {
1863 tableEditor.getEditor().dispose();
1864 TmfEventsTable.this.getTable().setFocus();
1865 }
1866 }
1867 });
1868 newEditor.selectAll();
1869 newEditor.setFocus();
1870 tableEditor.setEditor(newEditor, item, columnIndex);
1871 }
1872 }
1873
1874 /*
1875 * returns true is value was changed
1876 */
1877 private boolean updateHeader(final String regex) {
1878 if (regex.length() > 0) {
1879 try {
1880 Pattern.compile(regex);
1881 if (regex.equals(column.getData(Key.SEARCH_TXT))) {
1882 tableEditor.getEditor().dispose();
1883 return false;
1884 }
1885 final TmfFilterMatchesNode filter = new TmfFilterMatchesNode(null);
1886 ITmfEventAspect<?> aspect = (ITmfEventAspect<?>) column.getData(Key.ASPECT);
1887 filter.setEventAspect(aspect);
1888 filter.setRegex(regex);
1889 column.setData(Key.SEARCH_OBJ, filter);
1890 column.setData(Key.SEARCH_TXT, regex);
1891 } catch (final PatternSyntaxException ex) {
1892 tableEditor.getEditor().dispose();
1893 MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
1894 ex.getDescription(), ex.getMessage());
1895 return false;
1896 }
1897 } else {
1898 if (column.getData(Key.SEARCH_TXT) == null) {
1899 tableEditor.getEditor().dispose();
1900 return false;
1901 }
1902 column.setData(Key.SEARCH_OBJ, null);
1903 column.setData(Key.SEARCH_TXT, null);
1904 }
1905 return true;
1906 }
1907
1908 private void applyHeader() {
1909 stopSearchThread();
1910 final TmfFilterRootNode filter = new TmfFilterRootNode();
1911 for (final TableColumn col : fTable.getColumns()) {
1912 final Object filterObj = col.getData(Key.SEARCH_OBJ);
1913 if (filterObj instanceof ITmfFilterTreeNode) {
1914 filter.addChild((ITmfFilterTreeNode) filterObj);
1915 }
1916 }
1917 if (filter.getChildrenCount() > 0) {
1918 fHeaderState = HeaderState.SEARCH;
1919 fTable.setData(Key.SEARCH_OBJ, filter);
1920 fTable.refresh();
1921 searchNext();
1922 fireSearchApplied(filter);
1923 } else {
1924 fHeaderState = HeaderState.NO_SEARCH;
1925 fTable.setData(Key.SEARCH_OBJ, null);
1926 fTable.refresh();
1927 fireSearchApplied(null);
1928 }
1929
1930 tableEditor.getEditor().dispose();
1931 }
1932 });
1933
1934 fTable.addKeyListener(new KeyAdapter() {
1935 @Override
1936 public void keyPressed(final KeyEvent e) {
1937 e.doit = false;
1938 if (e.character == SWT.ESC) {
1939 stopFilterThread();
1940 stopSearchThread();
1941 fTable.refresh();
1942 } else if (e.character == SWT.DEL) {
1943 if (fHeaderState == HeaderState.SEARCH) {
1944 fHeaderState = HeaderState.NO_SEARCH;
1945 stopSearchThread();
1946 for (final TableColumn column : fTable.getColumns()) {
1947 column.setData(Key.SEARCH_OBJ, null);
1948 column.setData(Key.SEARCH_TXT, null);
1949 column.setData(Key.FILTER_TXT, null);
1950 }
1951 fTable.setData(Key.SEARCH_OBJ, null);
1952 fTable.refresh();
1953 fireSearchApplied(null);
1954 } else {
1955 for (final TableColumn column : fTable.getColumns()) {
1956 column.setData(Key.FILTER_TXT, null);
1957 }
1958 fTable.refresh();
1959 }
1960 } else if (e.character == SWT.CR) {
1961 if ((e.stateMask & SWT.CTRL) != 0) {
1962 if (fHeaderState == HeaderState.SEARCH) {
1963 applySearchAsFilter();
1964 }
1965 } else if ((e.stateMask & SWT.SHIFT) == 0) {
1966 searchNext();
1967 } else {
1968 searchPrevious();
1969 }
1970 }
1971 }
1972 });
1973 }
1974
1975 /**
1976 * Apply the current search condition as a new filter.
1977 *
1978 * @since 2.0
1979 */
1980 protected void applySearchAsFilter() {
1981 Object searchObj = fTable.getData(Key.SEARCH_OBJ);
1982 if (searchObj instanceof ITmfFilter) {
1983 ITmfFilter filter = (ITmfFilter) searchObj;
1984 fTable.setData(Key.SEARCH_OBJ, null);
1985 fireSearchApplied(null);
1986 fHeaderState = HeaderState.NO_SEARCH;
1987 for (final TableColumn col : fTable.getColumns()) {
1988 col.setData(Key.FILTER_TXT, col.getData(Key.SEARCH_TXT));
1989 col.setData(Key.SEARCH_TXT, null);
1990 col.setData(Key.SEARCH_OBJ, null);
1991 }
1992 applyFilter(filter);
1993 }
1994 }
1995
1996 /**
1997 * Send an event indicating a filter has been applied.
1998 *
1999 * @param filter
2000 * The filter that was just applied
2001 */
2002 protected void fireFilterApplied(final ITmfFilter filter) {
2003 broadcast(new TmfEventFilterAppliedSignal(this, fTrace, filter));
2004 }
2005
2006 /**
2007 * Send an event indicating that a search has been applied.
2008 *
2009 * @param filter
2010 * The search filter that was just applied
2011 */
2012 protected void fireSearchApplied(final ITmfFilter filter) {
2013 broadcast(new TmfEventSearchAppliedSignal(this, fTrace, filter));
2014 }
2015
2016 /**
2017 * Start the filtering thread.
2018 */
2019 protected void startFilterThread() {
2020 synchronized (fFilterSyncObj) {
2021 final ITmfFilterTreeNode filter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
2022 if (fFilterThread == null || fFilterThread.filter != filter) {
2023 if (fFilterThread != null) {
2024 fFilterThread.cancel();
2025 fFilterThreadResume = false;
2026 }
2027 fFilterThread = new FilterThread(filter);
2028 fFilterThread.start();
2029 } else {
2030 fFilterThreadResume = true;
2031 }
2032 }
2033 }
2034
2035 /**
2036 * Stop the filtering thread.
2037 */
2038 protected void stopFilterThread() {
2039 synchronized (fFilterSyncObj) {
2040 if (fFilterThread != null) {
2041 fFilterThread.cancel();
2042 fFilterThread = null;
2043 fFilterThreadResume = false;
2044 }
2045 }
2046 }
2047
2048 /**
2049 * Apply a filter. It is added to the existing filters.
2050 *
2051 * @param filter
2052 * The filter to apply
2053 */
2054 protected void applyFilter(ITmfFilter filter) {
2055 stopFilterThread();
2056 stopSearchThread();
2057 fFilterMatchCount = 0;
2058 fFilterCheckCount = 0;
2059 ITmfFilterTreeNode rootFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
2060 if (rootFilter == null) {
2061 rootFilter = new TmfFilterRootNode();
2062 }
2063 if (filter instanceof TmfFilterRootNode) {
2064 TmfFilterRootNode parentFilter = (TmfFilterRootNode) filter;
2065 for (ITmfFilterTreeNode child : parentFilter.getChildren()) {
2066 rootFilter.addChild(child);
2067 }
2068 } else if (filter instanceof TmfCollapseFilter) {
2069 fCollapseFilterEnabled = true;
2070 } else if (filter instanceof ITmfFilterTreeNode) {
2071 rootFilter.addChild((ITmfFilterTreeNode) filter);
2072 } else {
2073 rootFilter.addChild(new TmfFilterObjectNode(filter));
2074 }
2075 fCache.applyFilter(rootFilter, fCollapseFilterEnabled);
2076 fHeaderBar.addFilter(filter);
2077 fTable.clearAll();
2078 fTable.setData(Key.FILTER_OBJ, rootFilter);
2079 /* +1 for header row, +2 for top and bottom filter status rows */
2080 fTable.setItemCount(3);
2081 startFilterThread();
2082 fireFilterApplied(rootFilter);
2083 }
2084
2085 /**
2086 * Remove a filter. Any other existing filters remain applied.
2087 *
2088 * @param filter
2089 * The filter to remove
2090 * @since 2.0
2091 */
2092 protected void removeFilter(ITmfFilter filter) {
2093 ITmfFilterTreeNode rootFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
2094 if (rootFilter == null) {
2095 return;
2096 }
2097 stopFilterThread();
2098 stopSearchThread();
2099 fFilterMatchCount = 0;
2100 fFilterCheckCount = 0;
2101 if (filter instanceof TmfCollapseFilter) {
2102 fCollapseFilterEnabled = false;
2103 } else if (filter instanceof ITmfFilterTreeNode) {
2104 rootFilter.removeChild((ITmfFilterTreeNode) filter);
2105 } else {
2106 for (ITmfFilterTreeNode child : rootFilter.getChildren()) {
2107 if (child instanceof TmfFilterObjectNode) {
2108 if (((TmfFilterObjectNode) child).getFilter().equals(filter)) {
2109 rootFilter.removeChild(child);
2110 break;
2111 }
2112 }
2113 }
2114 }
2115 if (!rootFilter.hasChildren() && !fCollapseFilterEnabled) {
2116 clearFilters();
2117 return;
2118 }
2119 fCache.applyFilter(rootFilter, fCollapseFilterEnabled);
2120 fHeaderBar.removeFilter(filter);
2121 fTable.clearAll();
2122 fTable.setData(Key.FILTER_OBJ, rootFilter);
2123 /* +1 for header row, +2 for top and bottom filter status rows */
2124 fTable.setItemCount(3);
2125 startFilterThread();
2126 fireFilterApplied(rootFilter);
2127
2128 // Set original width
2129 fTable.getColumns()[MARGIN_COLUMN_INDEX].setWidth(0);
2130 packMarginColumn();
2131 }
2132
2133 /**
2134 * Clear all currently active filters.
2135 */
2136 protected void clearFilters() {
2137 if (fTable.getData(Key.FILTER_OBJ) == null) {
2138 return;
2139 }
2140 stopFilterThread();
2141 stopSearchThread();
2142 fCache.clearFilter();
2143 fHeaderBar.clearFilters();
2144 fCollapseFilterEnabled = false;
2145 fTable.clearAll();
2146 for (final TableColumn column : fTable.getColumns()) {
2147 column.setData(Key.FILTER_OBJ, null);
2148 column.setData(Key.FILTER_TXT, null);
2149 }
2150 fTable.setData(Key.FILTER_OBJ, null);
2151 if (fTrace != null) {
2152 /* +1 for header row */
2153 fTable.setItemCount((int) fTrace.getNbEvents() + 1);
2154 } else {
2155 /* +1 for header row */
2156 fTable.setItemCount(1);
2157 }
2158 fFilterMatchCount = 0;
2159 fFilterCheckCount = 0;
2160 if (fSelectedRank >= 0) {
2161 /* +1 for header row */
2162 fTable.setSelection((int) fSelectedRank + 1);
2163 } else {
2164 fTable.setSelection(0);
2165 }
2166 fireFilterApplied(null);
2167 updateStatusLine(null);
2168
2169 // Set original width
2170 fTable.getColumns()[MARGIN_COLUMN_INDEX].setWidth(0);
2171 packMarginColumn();
2172 }
2173
2174 /**
2175 * Wrapper Thread object for the filtering thread.
2176 */
2177 protected class FilterThread extends Thread {
2178 private final ITmfFilterTreeNode filter;
2179 private TmfCollapseFilter collapseFilter = null;
2180 private TmfEventRequest request;
2181 private boolean refreshBusy = false;
2182 private boolean refreshPending = false;
2183 private final Object syncObj = new Object();
2184
2185 /**
2186 * Constructor.
2187 *
2188 * @param filter
2189 * The filter this thread will be processing
2190 */
2191 public FilterThread(final ITmfFilterTreeNode filter) {
2192 super("Filter Thread"); //$NON-NLS-1$
2193 this.filter = filter;
2194 }
2195
2196 @Override
2197 public void run() {
2198 if (fTrace == null) {
2199 return;
2200 }
2201 if (fCollapseFilterEnabled) {
2202 collapseFilter = new TmfCollapseFilter();
2203 }
2204 final int nbRequested = (int) (fTrace.getNbEvents() - fFilterCheckCount);
2205 if (nbRequested <= 0) {
2206 return;
2207 }
2208 request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY,
2209 (int) fFilterCheckCount, nbRequested, ExecutionType.BACKGROUND) {
2210 @Override
2211 public void handleData(final ITmfEvent event) {
2212 super.handleData(event);
2213 if (request.isCancelled()) {
2214 return;
2215 }
2216 boolean refresh = false;
2217 if (filter.matches(event)) {
2218 if (collapseFilter == null || collapseFilter.matches(event)) {
2219 final long rank = fFilterCheckCount;
2220 final int index = (int) fFilterMatchCount;
2221 fFilterMatchCount++;
2222 fCache.storeEvent(event, rank, index);
2223 } else if (collapseFilter != null) {
2224 fCache.updateCollapsedEvent((int) fFilterMatchCount - 1);
2225 }
2226 refresh = true;
2227 }
2228
2229 if (refresh || (fFilterCheckCount % 100) == 0) {
2230 refreshTable();
2231 }
2232 fFilterCheckCount++;
2233 }
2234 };
2235 ((ITmfEventProvider) fTrace).sendRequest(request);
2236 try {
2237 request.waitForCompletion();
2238 } catch (final InterruptedException e) {
2239 }
2240 refreshTable();
2241 synchronized (fFilterSyncObj) {
2242 fFilterThread = null;
2243 if (fFilterThreadResume) {
2244 fFilterThreadResume = false;
2245 fFilterThread = new FilterThread(filter);
2246 fFilterThread.start();
2247 }
2248 }
2249 }
2250
2251 /**
2252 * Refresh the filter.
2253 */
2254 public void refreshTable() {
2255 synchronized (syncObj) {
2256 if (refreshBusy) {
2257 refreshPending = true;
2258 return;
2259 }
2260 refreshBusy = true;
2261 }
2262 Display.getDefault().asyncExec(new Runnable() {
2263 @Override
2264 public void run() {
2265 if (request.isCancelled()) {
2266 return;
2267 }
2268 if (fTable.isDisposed()) {
2269 return;
2270 }
2271 /*
2272 * +1 for header row, +2 for top and bottom filter status
2273 * rows
2274 */
2275 fTable.setItemCount((int) fFilterMatchCount + 3);
2276 fTable.refresh();
2277 synchronized (syncObj) {
2278 refreshBusy = false;
2279 if (refreshPending) {
2280 refreshPending = false;
2281 refreshTable();
2282 }
2283 }
2284 }
2285 });
2286 }
2287
2288 /**
2289 * Cancel this filtering thread.
2290 */
2291 public void cancel() {
2292 if (request != null) {
2293 request.cancel();
2294 }
2295 }
2296 }
2297
2298 /**
2299 * Go to the next item of a search.
2300 */
2301 protected void searchNext() {
2302 synchronized (fSearchSyncObj) {
2303 if (fSearchThread != null) {
2304 return;
2305 }
2306 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
2307 if (searchFilter == null) {
2308 return;
2309 }
2310 final int selectionIndex = fTable.getSelectionIndex();
2311 int startIndex;
2312 if (selectionIndex > 0) {
2313 /* -1 for header row, +1 for next event */
2314 startIndex = selectionIndex;
2315 } else {
2316 /*
2317 * header row is selected, start at top event
2318 */
2319 /* -1 for header row */
2320 startIndex = Math.max(0, fTable.getTopIndex() - 1);
2321 }
2322 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
2323 if (eventFilter != null) {
2324 // -1 for top filter status row
2325 startIndex = Math.max(0, startIndex - 1);
2326 }
2327 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.FORWARD);
2328 fSearchThread.schedule();
2329 }
2330 }
2331
2332 /**
2333 * Go to the previous item of a search.
2334 */
2335 protected void searchPrevious() {
2336 synchronized (fSearchSyncObj) {
2337 if (fSearchThread != null) {
2338 return;
2339 }
2340 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
2341 if (searchFilter == null) {
2342 return;
2343 }
2344 final int selectionIndex = fTable.getSelectionIndex();
2345 int startIndex;
2346 if (selectionIndex > 0) {
2347 /* -1 for header row, -1 for previous event */
2348 startIndex = selectionIndex - 2;
2349 } else {
2350 /*
2351 * Header row is selected, start at precedent of top event
2352 */
2353 /* -1 for header row, -1 for previous event */
2354 startIndex = fTable.getTopIndex() - 2;
2355 }
2356 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
2357 if (eventFilter != null) {
2358 /* -1 for top filter status row */
2359 startIndex = startIndex - 1;
2360 }
2361 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.BACKWARD);
2362 fSearchThread.schedule();
2363 }
2364 }
2365
2366 /**
2367 * Stop the search thread.
2368 */
2369 protected void stopSearchThread() {
2370 fPendingGotoRank = -1;
2371 synchronized (fSearchSyncObj) {
2372 if (fSearchThread != null) {
2373 fSearchThread.cancel();
2374 fSearchThread = null;
2375 }
2376 }
2377 }
2378
2379 /**
2380 * Wrapper for the search thread.
2381 */
2382 protected class SearchThread extends Job {
2383
2384 private ITmfFilterTreeNode searchFilter;
2385 private ITmfFilterTreeNode eventFilter;
2386 private int startIndex;
2387 private int direction;
2388 private long rank;
2389 private long foundRank = -1;
2390 private TmfEventRequest request;
2391 private ITmfTimestamp foundTimestamp = null;
2392
2393 /**
2394 * Constructor.
2395 *
2396 * @param searchFilter
2397 * The search filter
2398 * @param eventFilter
2399 * The event filter
2400 * @param startIndex
2401 * The index at which we should start searching
2402 * @param currentRank
2403 * The current rank
2404 * @param direction
2405 * In which direction should we search, forward or backwards
2406 */
2407 public SearchThread(final ITmfFilterTreeNode searchFilter,
2408 final ITmfFilterTreeNode eventFilter, final int startIndex,
2409 final long currentRank, final int direction) {
2410 super(Messages.TmfEventsTable_SearchingJobName);
2411 this.searchFilter = searchFilter;
2412 this.eventFilter = eventFilter;
2413 this.startIndex = startIndex;
2414 this.rank = currentRank;
2415 this.direction = direction;
2416 }
2417
2418 @Override
2419 protected IStatus run(final IProgressMonitor monitor) {
2420 final ITmfTrace trace = fTrace;
2421 if (trace == null) {
2422 return Status.OK_STATUS;
2423 }
2424 final Display display = Display.getDefault();
2425 if (startIndex < 0) {
2426 rank = (int) trace.getNbEvents() - 1;
2427 /*
2428 * -1 for header row, -3 for header and top and bottom filter
2429 * status rows
2430 */
2431 } else if (startIndex >= (fTable.getItemCount() - (eventFilter == null ? 1 : 3))) {
2432 rank = 0;
2433 } else {
2434 int idx = startIndex;
2435 while (foundRank == -1) {
2436 final CachedEvent event = fCache.peekEvent(idx);
2437 if (event == null) {
2438 break;
2439 }
2440 rank = event.rank;
2441 if (searchFilter.matches(event.event) && ((eventFilter == null) || eventFilter.matches(event.event))) {
2442 foundRank = event.rank;
2443 foundTimestamp = event.event.getTimestamp();
2444 break;
2445 }
2446 if (direction == Direction.FORWARD) {
2447 idx++;
2448 } else {
2449 idx--;
2450 }
2451 }
2452 if (foundRank == -1) {
2453 if (direction == Direction.FORWARD) {
2454 rank++;
2455 if (rank > (trace.getNbEvents() - 1)) {
2456 rank = 0;
2457 }
2458 } else {
2459 rank--;
2460 if (rank < 0) {
2461 rank = (int) trace.getNbEvents() - 1;
2462 }
2463 }
2464 }
2465 }
2466 final int startRank = (int) rank;
2467 boolean wrapped = false;
2468 while (!monitor.isCanceled() && (foundRank == -1)) {
2469 int nbRequested = (direction == Direction.FORWARD ? Integer.MAX_VALUE : Math.min((int) rank + 1, trace.getCacheSize()));
2470 if (direction == Direction.BACKWARD) {
2471 rank = Math.max(0, rank - trace.getCacheSize() + 1);
2472 }
2473 request = new TmfEventRequest(ITmfEvent.class, TmfTimeRange.ETERNITY,
2474 (int) rank, nbRequested, ExecutionType.BACKGROUND) {
2475 long currentRank = rank;
2476
2477 @Override
2478 public void handleData(final ITmfEvent event) {
2479 super.handleData(event);
2480 if (searchFilter.matches(event) && ((eventFilter == null) || eventFilter.matches(event))) {
2481 foundRank = currentRank;
2482 foundTimestamp = event.getTimestamp();
2483 if (direction == Direction.FORWARD) {
2484 done();
2485 return;
2486 }
2487 }
2488 currentRank++;
2489 }
2490 };
2491 ((ITmfEventProvider) trace).sendRequest(request);
2492 try {
2493 request.waitForCompletion();
2494 if (request.isCancelled()) {
2495 return Status.OK_STATUS;
2496 }
2497 } catch (final InterruptedException e) {
2498 synchronized (fSearchSyncObj) {
2499 fSearchThread = null;
2500 }
2501 return Status.OK_STATUS;
2502 }
2503 if (foundRank == -1) {
2504 if (direction == Direction.FORWARD) {
2505 if (rank == 0) {
2506 synchronized (fSearchSyncObj) {
2507 fSearchThread = null;
2508 }
2509 return Status.OK_STATUS;
2510 }
2511 nbRequested = (int) rank;
2512 rank = 0;
2513 wrapped = true;
2514 } else {
2515 rank--;
2516 if (rank < 0) {
2517 rank = (int) trace.getNbEvents() - 1;
2518 wrapped = true;
2519 }
2520 if ((rank <= startRank) && wrapped) {
2521 synchronized (fSearchSyncObj) {
2522 fSearchThread = null;
2523 }
2524 return Status.OK_STATUS;
2525 }
2526 }
2527 }
2528 }
2529 int index = (int) foundRank;
2530 if (eventFilter != null) {
2531 index = fCache.getFilteredEventIndex(foundRank);
2532 }
2533 /* +1 for header row, +1 for top filter status row */
2534 final int selection = index + 1 + (eventFilter != null ? +1 : 0);
2535
2536 display.asyncExec(new Runnable() {
2537
2538 @Override
2539 public void run() {
2540 if (monitor.isCanceled()) {
2541 return;
2542 }
2543 if (fTable.isDisposed()) {
2544 return;
2545 }
2546 fTable.setSelection(selection);
2547 fSelectedRank = foundRank;
2548 fSelectedBeginRank = fSelectedRank;
2549 fRawViewer.selectAndReveal(fSelectedRank);
2550 if (foundTimestamp != null) {
2551 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, foundTimestamp));
2552 }
2553 fireSelectionChanged(new SelectionChangedEvent(TmfEventsTable.this, getSelection()));
2554 synchronized (fSearchSyncObj) {
2555 fSearchThread = null;
2556 }
2557 updateStatusLine(null);
2558 }
2559 });
2560 return Status.OK_STATUS;
2561
2562 }
2563
2564 @Override
2565 protected void canceling() {
2566 if (request != null) {
2567 request.cancel();
2568 }
2569 synchronized (fSearchSyncObj) {
2570 fSearchThread = null;
2571 }
2572 }
2573
2574 }
2575
2576 /**
2577 * Create the resources.
2578 */
2579 private void createResources() {
2580 fGrayColor = fResourceManager.createColor(ColorUtil.blend(fTable.getBackground().getRGB(), fTable.getForeground().getRGB()));
2581 fGreenColor = PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN);
2582 }
2583
2584 /**
2585 * Initialize the fonts.
2586 */
2587 private void initializeFonts() {
2588 FontRegistry fontRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry();
2589 fFont = fontRegistry.get(FONT_DEFINITION_ID);
2590 fBoldFont = fontRegistry.getBold(FONT_DEFINITION_ID);
2591 fTable.setFont(fFont);
2592 /* Column header font cannot be set. See Bug 63038 */
2593 }
2594
2595 /**
2596 * Initialize the colors.
2597 */
2598 private void initializeColors() {
2599 ColorRegistry colorRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry();
2600 fHighlightColor = colorRegistry.get(HIGHLIGHT_COLOR_DEFINITION_ID);
2601 }
2602
2603 /**
2604 * @since 1.0
2605 */
2606 @Override
2607 public void propertyChange(PropertyChangeEvent event) {
2608 if ((IThemeManager.CHANGE_CURRENT_THEME.equals(event.getProperty())) ||
2609 (FONT_DEFINITION_ID.equals(event.getProperty()))) {
2610 initializeFonts();
2611 fTable.refresh();
2612 }
2613 if ((IThemeManager.CHANGE_CURRENT_THEME.equals(event.getProperty())) ||
2614 (HIGHLIGHT_COLOR_DEFINITION_ID.equals(event.getProperty()))) {
2615 initializeColors();
2616 fTable.refresh();
2617 }
2618 }
2619
2620 /**
2621 * Pack the columns.
2622 *
2623 * @return Whether or not a pack was done in this call. Otherwise, it was
2624 * already done by a previous call
2625 *
2626 * @since 2.0
2627 */
2628 protected boolean packColumns() {
2629 if (fPackDone) {
2630 return false;
2631 }
2632 fTable.setRedraw(false);
2633 try {
2634 TableColumn tableColumns[] = fTable.getColumns();
2635 for (int i = 0; i < tableColumns.length; i++) {
2636 final TableColumn column = tableColumns[i];
2637 packSingleColumn(i, column);
2638 }
2639
2640 } finally {
2641 // Make sure that redraw is always enabled.
2642 fTable.setRedraw(true);
2643 }
2644 fPackDone = true;
2645 return true;
2646 }
2647
2648 private void packMarginColumn() {
2649 TableColumn[] columns = fTable.getColumns();
2650 if (columns.length > 0) {
2651 packSingleColumn(0, columns[0]);
2652 }
2653 }
2654
2655 private void packSingleColumn(int i, final TableColumn column) {
2656 if (i != MARGIN_COLUMN_INDEX && !column.getResizable()) {
2657 return;
2658 }
2659 Object data = column.getData(Key.WIDTH);
2660 final int headerWidth = data instanceof Integer ? (int) data : -1;
2661 column.pack();
2662 /*
2663 * Workaround for Linux which doesn't consider the image width of
2664 * search/filter row in TableColumn.pack() after having executed
2665 * TableItem.setImage(null) for other rows than search/filter row.
2666 */
2667 if (IS_LINUX && (i == MARGIN_COLUMN_INDEX) && fCollapseFilterEnabled) {
2668 column.setWidth(column.getWidth() + SEARCH_IMAGE.getBounds().width);
2669 }
2670
2671 if (column.getWidth() < headerWidth) {
2672 column.setWidth(headerWidth);
2673 } else if (i != MARGIN_COLUMN_INDEX) {
2674 column.setData(Key.WIDTH, column.getWidth());
2675 }
2676 }
2677
2678 /**
2679 * Returns true if the column is a visible event column.
2680 *
2681 * @param column
2682 * the column
2683 * @return false if the column is the margin column or hidden, true
2684 * otherwise
2685 */
2686 private static boolean isVisibleEventColumn(TableColumn column) {
2687 if (column.getData(Key.ASPECT) == TmfMarginColumn.MARGIN_ASPECT) {
2688 return false;
2689 }
2690 if (!column.getResizable() && column.getWidth() == 0) {
2691 return false;
2692 }
2693 return true;
2694 }
2695
2696 /**
2697 * Returns true if the column is expanded to take extra available space.
2698 * This is the last non-zero-width visible column in the column order on
2699 * Linux. This column's width should not be persisted.
2700 *
2701 * @param column
2702 * the column
2703 * @return true if the column is expanded.
2704 */
2705 private static boolean isExpanded(TableColumn column) {
2706 if (IS_LINUX) {
2707 Table table = column.getParent();
2708 int[] order = table.getColumnOrder();
2709 for (int i = order.length - 1; i >= 0; i--) {
2710 TableColumn col = table.getColumn(order[i]);
2711 if (col == column) {
2712 return true;
2713 }
2714 if (col.getWidth() > 0) {
2715 return false;
2716 }
2717 }
2718 }
2719 return false;
2720 }
2721
2722 /**
2723 * Get the array of item strings (e.g., what to display in each cell of the
2724 * table row) corresponding to the columns and trace event passed in
2725 * parameter. The order of the Strings in the returned array will correspond
2726 * to the iteration order of 'columns'.
2727 *
2728 * <p>
2729 * To ensure consistent results, make sure only call this within a scope
2730 * synchronized on 'columns'! If the order of 'columns' changes right after
2731 * this method is called, the returned value won't be ordered correctly
2732 * anymore.
2733 */
2734 private static String[] getItemStrings(List<TmfEventTableColumn> columns, ITmfEvent event) {
2735 if (event == null) {
2736 return EMPTY_STRING_ARRAY;
2737 }
2738 synchronized (columns) {
2739 List<String> itemStrings = new ArrayList<>(columns.size());
2740 for (TmfEventTableColumn column : columns) {
2741 ITmfEvent passedEvent = event;
2742 if (!(column instanceof TmfMarginColumn) && (event instanceof CachedEvent)) {
2743 /*
2744 * Make sure that the event object from the trace is passed
2745 * to all columns but the TmfMarginColumn
2746 */
2747 passedEvent = ((CachedEvent) event).event;
2748 }
2749 if (passedEvent == null) {
2750 itemStrings.add(EMPTY_STRING);
2751 } else {
2752 itemStrings.add(column.getItemString(passedEvent));
2753 }
2754
2755 }
2756 return itemStrings.toArray(new String[0]);
2757 }
2758 }
2759
2760 /**
2761 * Get the contents of the row in the events table corresponding to an
2762 * event. The order of the elements corresponds to the current order of the
2763 * columns.
2764 *
2765 * @param event
2766 * The event printed in this row
2767 * @return The event row entries
2768 */
2769 public String[] getItemStrings(ITmfEvent event) {
2770 List<TmfEventTableColumn> columns = new ArrayList<>();
2771 for (int i : fTable.getColumnOrder()) {
2772 columns.add(fColumns.get(i));
2773 }
2774 return getItemStrings(columns, event);
2775 }
2776
2777 /**
2778 * Returns an array of zero-relative integers that map the creation order of
2779 * the receiver's columns to the order in which they are currently being
2780 * displayed.
2781 * <p>
2782 * Specifically, the indices of the returned array represent the current
2783 * visual order of the columns, and the contents of the array represent the
2784 * creation order of the columns.
2785 *
2786 * @return the current visual order of the receiver's columns
2787 * @since 1.0
2788 */
2789 public int[] getColumnOrder() {
2790 return fColumnOrder;
2791 }
2792
2793 /**
2794 * Get column widths
2795 *
2796 * @return the current visual widths of the receiver's columns
2797 * @since 2.1
2798 */
2799 public int[] getColumnWidth() {
2800 return fColumnSize;
2801 }
2802
2803 /**
2804 * Get whether the columns are resizable
2805 *
2806 * @return an array stating if each column is resizable
2807 * @since 2.1
2808 */
2809 public boolean[] getColumnResizable() {
2810 return fColumnResizable;
2811 }
2812
2813 /**
2814 * Sets the order that the columns in the receiver should be displayed in to
2815 * the given argument which is described in terms of the zero-relative
2816 * ordering of when the columns were added.
2817 * <p>
2818 * Specifically, the contents of the array represent the original position
2819 * of each column at the time its creation.
2820 *
2821 * @param order
2822 * the new order to display the columns
2823 * @since 1.0
2824 */
2825 public void setColumnOrder(int[] order) {
2826 if (order == null || order.length != fTable.getColumns().length) {
2827 return;
2828 }
2829 fTable.setColumnOrder(order);
2830 fColumnOrder = fTable.getColumnOrder();
2831 }
2832
2833 /**
2834 * Sets the column width and resizability
2835 *
2836 * @param width
2837 * an array of widths
2838 * @param resizable
2839 * an array of bools saying if a column is resizable or not
2840 * @since 2.1
2841 */
2842 public void setColumnWidth(int[] width, boolean[] resizable) {
2843 int length = fTable.getColumns().length;
2844 if (width == null || resizable == null || resizable.length != length || width.length != length) {
2845 return;
2846 }
2847 int i = 0;
2848 for (TableColumn column : fTable.getColumns()) {
2849 if (i != MARGIN_COLUMN_INDEX) {
2850 column.setData(Key.WIDTH, width[i]);
2851 column.setResizable(resizable[i]);
2852 if (column.getResizable()) {
2853 column.setWidth((int) column.getData(Key.WIDTH));
2854 } else {
2855 column.setWidth(0);
2856 }
2857 }
2858 i++;
2859 }
2860 fColumnSize = width;
2861 fColumnResizable = resizable;
2862 /* Don't pack, it would override these settings */
2863 fPackDone = true;
2864 }
2865
2866 /**
2867 * Notify this table that is got the UI focus.
2868 */
2869 public void setFocus() {
2870 fTable.setFocus();
2871 }
2872
2873 /**
2874 * Registers context menus with a site for extension. This method can be
2875 * called for part sites so that context menu contributions can be added.
2876 *
2877 * @param site
2878 * the site that the context menus will be registered for
2879 *
2880 * @since 1.2
2881 */
2882 public void registerContextMenus(IWorkbenchPartSite site) {
2883 if (site instanceof IEditorSite) {
2884 IEditorSite editorSite = (IEditorSite) site;
2885 // Don't use the editor input when adding contributions, otherwise
2886 // we get too many unwanted things.
2887 editorSite.registerContextMenu(fTablePopupMenuManager, this, false);
2888 }
2889 }
2890
2891 /**
2892 * Assign a new trace to this event table.
2893 *
2894 * @param trace
2895 * The trace to assign to this event table
2896 * @param disposeOnClose
2897 * true if the trace should be disposed when the table is
2898 * disposed
2899 */
2900 public void setTrace(final ITmfTrace trace, final boolean disposeOnClose) {
2901 if ((fTrace != null) && fDisposeOnClose) {
2902 fTrace.dispose();
2903 }
2904 fTrace = trace;
2905 fPackDone = false;
2906 fDisposeOnClose = disposeOnClose;
2907
2908 // Perform the updates on the UI thread
2909 PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
2910 @Override
2911 public void run() {
2912 fSelectedRank = -1;
2913 fSelectedBeginRank = -1;
2914 fTable.removeAll();
2915 fCache.setTrace(trace); // Clear the cache
2916 if (trace != null) {
2917 if (!fTable.isDisposed()) {
2918 if (fTable.getData(Key.FILTER_OBJ) == null) {
2919 // +1 for header row
2920 fTable.setItemCount((int) trace.getNbEvents() + 1);
2921 } else {
2922 stopFilterThread();
2923 fFilterMatchCount = 0;
2924 fFilterCheckCount = 0;
2925 /*
2926 * +1 for header row, +2 for top and bottom filter
2927 * status rows
2928 */
2929 fTable.setItemCount(3);
2930 startFilterThread();
2931 }
2932 }
2933 }
2934 fRawViewer.setTrace(trace);
2935 }
2936 });
2937 }
2938
2939 /**
2940 * Assign the status line manager
2941 *
2942 * @param statusLineManager
2943 * The status line manager, or null to disable status line
2944 * messages
2945 */
2946 public void setStatusLineManager(IStatusLineManager statusLineManager) {
2947 if (fStatusLineManager != null && statusLineManager == null) {
2948 fStatusLineManager.setMessage(EMPTY_STRING);
2949 }
2950 fStatusLineManager = statusLineManager;
2951 }
2952
2953 private void updateStatusLine(ITmfTimestamp delta) {
2954 if (fStatusLineManager != null) {
2955 if (delta != null) {
2956 fStatusLineManager.setMessage("\u0394: " + delta); //$NON-NLS-1$
2957 } else {
2958 fStatusLineManager.setMessage(null);
2959 }
2960 }
2961 }
2962
2963 // ------------------------------------------------------------------------
2964 // Event cache
2965 // ------------------------------------------------------------------------
2966
2967 /**
2968 * Notify that the event cache has been updated
2969 *
2970 * @param completed
2971 * Also notify if the populating of the cache is complete, or
2972 * not.
2973 */
2974 public void cacheUpdated(final boolean completed) {
2975 synchronized (fCacheUpdateSyncObj) {
2976 if (fCacheUpdateBusy) {
2977 fCacheUpdatePending = true;
2978 fCacheUpdateCompleted = completed;
2979 return;
2980 }
2981 fCacheUpdateBusy = true;
2982 }
2983 // Event cache is now updated. Perform update on the UI thread
2984 PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
2985 @Override
2986 public void run() {
2987 if (!fTable.isDisposed()) {
2988 fTable.refresh();
2989 packColumns();
2990 }
2991 if (completed) {
2992 populateCompleted();
2993 }
2994 synchronized (fCacheUpdateSyncObj) {
2995 fCacheUpdateBusy = false;
2996 if (fCacheUpdatePending) {
2997 fCacheUpdatePending = false;
2998 cacheUpdated(fCacheUpdateCompleted);
2999 }
3000 }
3001 }
3002 });
3003 }
3004
3005 /**
3006 * Callback for when populating the table is complete.
3007 */
3008 protected void populateCompleted() {
3009 // Nothing by default;
3010 }
3011
3012 // ------------------------------------------------------------------------
3013 // ISelectionProvider
3014 // ------------------------------------------------------------------------
3015
3016 @Override
3017 public void addSelectionChangedListener(ISelectionChangedListener listener) {
3018 selectionChangedListeners.add(listener);
3019 }
3020
3021 @Override
3022 public ISelection getSelection() {
3023 if (fTable == null || fTable.isDisposed()) {
3024 return StructuredSelection.EMPTY;
3025 }
3026 List<Object> list = new ArrayList<>(fTable.getSelection().length);
3027 for (TableItem item : fTable.getSelection()) {
3028 if (item.getData() != null) {
3029 list.add(item.getData());
3030 }
3031 }
3032 return new StructuredSelection(list);
3033 }
3034
3035 @Override
3036 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
3037 selectionChangedListeners.remove(listener);
3038 }
3039
3040 @Override
3041 public void setSelection(ISelection selection) {
3042 // not implemented
3043 }
3044
3045 /**
3046 * Notifies any selection changed listeners that the viewer's selection has
3047 * changed. Only listeners registered at the time this method is called are
3048 * notified.
3049 *
3050 * @param event
3051 * a selection changed event
3052 *
3053 * @see ISelectionChangedListener#selectionChanged
3054 */
3055 protected void fireSelectionChanged(final SelectionChangedEvent event) {
3056 Object[] listeners = selectionChangedListeners.getListeners();
3057 for (int i = 0; i < listeners.length; ++i) {
3058 final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
3059 SafeRunnable.run(new SafeRunnable() {
3060 @Override
3061 public void run() {
3062 l.selectionChanged(event);
3063 }
3064 });
3065 }
3066 }
3067
3068 // ------------------------------------------------------------------------
3069 // Bookmark handling
3070 // ------------------------------------------------------------------------
3071
3072 /**
3073 * Add a bookmark to this event table.
3074 *
3075 * @param bookmarksFile
3076 * The file to use for the bookmarks
3077 */
3078 public void addBookmark(final IFile bookmarksFile) {
3079 fBookmarksFile = bookmarksFile;
3080 final TableItem[] selection = fTable.getSelection();
3081 if (selection.length > 0) {
3082 final TableItem tableItem = selection[0];
3083 if (tableItem.getData(Key.RANK) != null) {
3084 final StringBuffer defaultMessage = new StringBuffer();
3085 for (int i : fTable.getColumnOrder()) {
3086 TableColumn column = fTable.getColumns()[i];
3087 // Omit the margin column and hidden columns
3088 if (isVisibleEventColumn(column)) {
3089 if (defaultMessage.length() > 0) {
3090 defaultMessage.append(", "); //$NON-NLS-1$
3091 }
3092 defaultMessage.append(tableItem.getText(i));
3093 }
3094 }
3095 final AddBookmarkDialog dialog = new AddBookmarkDialog(
3096 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), defaultMessage.toString());
3097 if (dialog.open() == Window.OK) {
3098 final String message = dialog.getValue();
3099 try {
3100 final Long rank = (Long) tableItem.getData(Key.RANK);
3101 final String location = NLS.bind(Messages.TmfMarker_LocationRank, rank.toString());
3102 final ITmfTimestamp timestamp = (ITmfTimestamp) tableItem.getData(Key.TIMESTAMP);
3103 final long[] id = new long[1];
3104 ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
3105 @Override
3106 public void run(IProgressMonitor monitor) throws CoreException {
3107 final IMarker bookmark = bookmarksFile.createMarker(IMarker.BOOKMARK);
3108 bookmark.setAttribute(IMarker.MESSAGE, message.toString());
3109 bookmark.setAttribute(IMarker.LOCATION, location);
3110 bookmark.setAttribute(ITmfMarker.MARKER_RANK, rank.toString());
3111 bookmark.setAttribute(ITmfMarker.MARKER_TIME, Long.toString(timestamp.toNanos()));
3112 bookmark.setAttribute(ITmfMarker.MARKER_COLOR, dialog.getColorValue().toString());
3113 id[0] = bookmark.getId();
3114 }
3115 }, null);
3116 fBookmarksMap.put(rank, id[0]);
3117 fTable.refresh();
3118 } catch (final CoreException e) {
3119 displayException(e);
3120 }
3121 }
3122 }
3123 }
3124
3125 }
3126
3127 /**
3128 * Add one or more bookmarks to this event table.
3129 *
3130 * @param bookmarks
3131 * The bookmarks to add
3132 * @since 2.0
3133 */
3134 public void addBookmark(final IMarker... bookmarks) {
3135 for (IMarker bookmark : bookmarks) {
3136 /* try location as an integer for backward compatibility */
3137 long rank = bookmark.getAttribute(IMarker.LOCATION, -1);
3138 if (rank == -1) {
3139 String rankString = bookmark.getAttribute(ITmfMarker.MARKER_RANK, (String) null);
3140 if (rankString != null) {
3141 try {
3142 rank = Long.parseLong(rankString);
3143 } catch (NumberFormatException e) {
3144 Activator.getDefault().logError("Invalid marker rank", e); //$NON-NLS-1$
3145 }
3146 }
3147 }
3148 if (rank != -1) {
3149 fBookmarksMap.put(rank, bookmark.getId());
3150 }
3151 }
3152 fTable.refresh();
3153 }
3154
3155 /**
3156 * Remove one or more bookmarks from this event table.
3157 *
3158 * @param bookmarks
3159 * The bookmarks to remove
3160 * @since 2.0
3161 */
3162 public void removeBookmark(final IMarker... bookmarks) {
3163 for (IMarker bookmark : bookmarks) {
3164 for (final Entry<Long, Long> entry : fBookmarksMap.entries()) {
3165 if (entry.getValue().equals(bookmark.getId())) {
3166 fBookmarksMap.remove(entry.getKey(), entry.getValue());
3167 break;
3168 }
3169 }
3170 }
3171 fTable.refresh();
3172 }
3173
3174 private void toggleBookmark(final Long rank) {
3175 if (fBookmarksFile == null) {
3176 return;
3177 }
3178 if (fBookmarksMap.containsKey(rank)) {
3179 final Collection<Long> markerIds = fBookmarksMap.removeAll(rank);
3180 fTable.refresh();
3181 try {
3182 for (long markerId : markerIds) {
3183 final IMarker bookmark = fBookmarksFile.findMarker(markerId);
3184 if (bookmark != null) {
3185 bookmark.delete();
3186 }
3187 }
3188 } catch (final CoreException e) {
3189 displayException(e);
3190 }
3191 } else {
3192 addBookmark(fBookmarksFile);
3193 }
3194 }
3195
3196 /**
3197 * Refresh the bookmarks assigned to this trace, from the contents of a
3198 * bookmark file.
3199 *
3200 * @param bookmarksFile
3201 * The bookmark file to use
3202 */
3203 public void refreshBookmarks(final IFile bookmarksFile) {
3204 fBookmarksFile = bookmarksFile;
3205 if (bookmarksFile == null) {
3206 fBookmarksMap.clear();
3207 fTable.refresh();
3208 return;
3209 }
3210 try {
3211 fBookmarksMap.clear();
3212 IMarker[] bookmarks = bookmarksFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO);
3213 addBookmark(bookmarks);
3214 } catch (final CoreException e) {
3215 displayException(e);
3216 }
3217 }
3218
3219 @Override
3220 public void gotoMarker(final IMarker marker) {
3221 ITmfTimestamp tsBegin = null;
3222 ITmfTimestamp tsEnd = null;
3223 /* try location as an integer for backward compatibility */
3224 long rank = marker.getAttribute(IMarker.LOCATION, -1);
3225 if (rank == -1) {
3226 String rankString = marker.getAttribute(ITmfMarker.MARKER_RANK, (String) null);
3227 try {
3228 rank = Long.parseLong(rankString);
3229 } catch (NumberFormatException e) {
3230 /* ignored */
3231 }
3232 }
3233 try {
3234 String timeString = marker.getAttribute(ITmfMarker.MARKER_TIME, (String) null);
3235 long time = Long.parseLong(timeString);
3236 tsBegin = TmfTimestamp.fromNanos(time);
3237 String durationString = marker.getAttribute(ITmfMarker.MARKER_DURATION, (String) null);
3238 long duration = Long.parseLong(durationString);
3239 tsEnd = TmfTimestamp.fromNanos(time + duration);
3240 } catch (NumberFormatException e) {
3241 /* ignored */
3242 }
3243 if (rank == -1 && tsBegin != null) {
3244 final ITmfContext context = fTrace.seekEvent(tsBegin);
3245 rank = context.getRank();
3246 context.dispose();
3247 }
3248 if (rank != -1) {
3249 int index = (int) rank;
3250 if (fTable.getData(Key.FILTER_OBJ) != null) {
3251 // +1 for top filter status row
3252 index = fCache.getFilteredEventIndex(rank) + 1;
3253 } else if (rank >= fTable.getItemCount()) {
3254 fPendingGotoRank = rank;
3255 }
3256 fSelectedRank = rank;
3257 fSelectedBeginRank = fSelectedRank;
3258 fTable.setSelection(index + 1); // +1 for header row
3259 updateStatusLine(null);
3260 if (tsBegin != null) {
3261 if (tsEnd != null) {
3262 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, tsBegin, tsEnd));
3263 } else {
3264 broadcast(new TmfSelectionRangeUpdatedSignal(TmfEventsTable.this, tsBegin));
3265 }
3266 }
3267 }
3268 }
3269
3270 // ------------------------------------------------------------------------
3271 // Listeners
3272 // ------------------------------------------------------------------------
3273
3274 @Override
3275 public void colorSettingsChanged(final ColorSetting[] colorSettings) {
3276 fTable.refresh();
3277 }
3278
3279 // ------------------------------------------------------------------------
3280 // Signal handlers
3281 // ------------------------------------------------------------------------
3282
3283 /**
3284 * Handler for the trace updated signal
3285 *
3286 * @param signal
3287 * The incoming signal
3288 */
3289 @TmfSignalHandler
3290 public void traceUpdated(final TmfTraceUpdatedSignal signal) {
3291 if ((signal.getTrace() != fTrace) || fTable.isDisposed()) {
3292 return;
3293 }
3294 // Perform the refresh on the UI thread
3295 Display.getDefault().asyncExec(new Runnable() {
3296 @Override
3297 public void run() {
3298 if (!fTable.isDisposed() && (fTrace != null)) {
3299 if (fTable.getData(Key.FILTER_OBJ) == null) {
3300 /* +1 for header row */
3301 fTable.setItemCount((int) fTrace.getNbEvents() + 1);
3302 /* +1 for header row */
3303 if ((fPendingGotoRank != -1) && ((fPendingGotoRank + 1) < fTable.getItemCount())) {
3304 /* +1 for header row */
3305 fTable.setSelection((int) fPendingGotoRank + 1);
3306 fPendingGotoRank = -1;
3307 updateStatusLine(null);
3308 }
3309 } else {
3310 startFilterThread();
3311 }
3312 }
3313 if (!fRawViewer.isDisposed() && (fTrace != null)) {
3314 fRawViewer.refreshEventCount();
3315 }
3316 }
3317 });
3318 }
3319
3320 /**
3321 * Handler for the selection range signal.
3322 *
3323 * @param signal
3324 * The incoming signal
3325 * @since 1.0
3326 */
3327 @TmfSignalHandler
3328 public void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) {
3329 if ((signal.getSource() != this) && (fTrace != null) && (!fTable.isDisposed())) {
3330
3331 Job timeSelectJob;
3332 synchronized (fTimeSelectMutexRule) {
3333 timeSelectJob = fTimeSelectJob;
3334 if (timeSelectJob != null) {
3335 timeSelectJob.cancel();
3336 }
3337
3338 /*
3339 * Run in separate thread to not block UI thread for too long.
3340 */
3341 timeSelectJob = new Job("Events table selection job") { //$NON-NLS-1$
3342 ITmfTimestamp ts = signal.getBeginTime();
3343 ITmfTimestamp tf = signal.getEndTime();
3344
3345 @Override
3346 protected IStatus run(IProgressMonitor monitor) {
3347 if (fTrace == null) {
3348 return Status.OK_STATUS;
3349 }
3350
3351 final Pair<Long, Long> selection = getSelectedRanks(monitor);
3352
3353 if (monitor.isCanceled() || (selection == null)) {
3354 return Status.CANCEL_STATUS;
3355 }
3356 updateDisplayWithSelection(selection.getFirst().longValue(), selection.getSecond().longValue());
3357 return Status.OK_STATUS;
3358 }
3359
3360 /**
3361 * Verify if the event is within the trace range and adjust if
3362 * necessary.
3363 * @param monitor
3364 * a progress monitor
3365 * @return A pair of rank representing the selected area
3366 **/
3367 @Nullable
3368 private Pair<Long, Long> getSelectedRanks(IProgressMonitor monitor) {
3369
3370 /* Clamp the timestamp value to fit inside of the trace */
3371 ITmfTimestamp timestampBegin = ts;
3372 if (timestampBegin.compareTo(fTrace.getStartTime()) < 0) {
3373 timestampBegin = fTrace.getStartTime();
3374 }
3375 if (timestampBegin.compareTo(fTrace.getEndTime()) > 0) {
3376 timestampBegin = fTrace.getEndTime();
3377 }
3378
3379 ITmfTimestamp timestampEnd = tf;
3380 if (timestampEnd.compareTo(fTrace.getStartTime()) < 0) {
3381 timestampEnd = fTrace.getStartTime();
3382 }
3383 if (timestampEnd.compareTo(fTrace.getEndTime()) > 0) {
3384 timestampEnd = fTrace.getEndTime();
3385 }
3386
3387 ITmfTimestamp tb;
3388 ITmfTimestamp te;
3389 long rankBegin;
3390 long rankEnd;
3391 ITmfContext contextBegin;
3392 ITmfContext contextEnd;
3393 if (monitor.isCanceled()) {
3394 return null;
3395 }
3396
3397 /* Adjust the rank of the selection to the right range */
3398 if (timestampBegin.compareTo(timestampEnd) > 0) {
3399 te = timestampEnd;
3400 contextEnd = fTrace.seekEvent(te);
3401 rankEnd = contextEnd.getRank();
3402 contextEnd.dispose();
3403
3404 if (monitor.isCanceled()) {
3405 return null;
3406 }
3407 /*
3408 * To include all events at the begin time, seek at the
3409 * next nanosecond and then use the previous rank
3410 */
3411 tb = timestampBegin.normalize(1, ITmfTimestamp.NANOSECOND_SCALE);
3412 if (tb.compareTo(fTrace.getEndTime()) <= 0) {
3413 contextBegin = fTrace.seekEvent(tb);
3414 rankBegin = contextBegin.getRank();
3415 contextBegin.dispose();
3416 } else {
3417 rankBegin = ITmfContext.UNKNOWN_RANK;
3418 }
3419 rankBegin = (rankBegin == ITmfContext.UNKNOWN_RANK ? fTrace.getNbEvents() : rankBegin) - 1;
3420 /*
3421 * If no events in selection range, select only the next
3422 * event
3423 */
3424 rankBegin = rankBegin >= rankEnd ? rankBegin : rankEnd;
3425 } else {
3426 tb = timestampBegin;
3427 contextBegin = fTrace.seekEvent(tb);
3428 rankBegin = contextBegin.getRank();
3429 contextBegin.dispose();
3430 if (monitor.isCanceled()) {
3431 return null;
3432 }
3433 /*
3434 * To include all events at the end time, seek at the
3435 * next nanosecond and then use the previous rank
3436 */
3437 te = timestampEnd.normalize(1, ITmfTimestamp.NANOSECOND_SCALE);
3438 if (te.compareTo(fTrace.getEndTime()) <= 0) {
3439 contextEnd = fTrace.seekEvent(te);
3440 rankEnd = contextEnd.getRank();
3441 contextEnd.dispose();
3442 } else {
3443 rankEnd = ITmfContext.UNKNOWN_RANK;
3444 }
3445 rankEnd = (rankEnd == ITmfContext.UNKNOWN_RANK ? fTrace.getNbEvents() : rankEnd) - 1;
3446 /*
3447 * If no events in selection range, select only the next
3448 * event
3449 */
3450 rankEnd = rankEnd >= rankBegin ? rankEnd : rankBegin;
3451 }
3452 return new Pair<>(Long.valueOf(rankBegin), Long.valueOf(rankEnd));
3453 }
3454
3455 private void updateDisplayWithSelection(final long rankBegin, final long rankEnd) {
3456 PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
3457 @Override
3458 public void run() {
3459 // Return if table is disposed
3460 if (fTable.isDisposed()) {
3461 return;
3462 }
3463
3464 fSelectedRank = rankEnd;
3465 long toReveal = fSelectedBeginRank != rankBegin ? rankBegin : rankEnd;
3466 fSelectedBeginRank = rankBegin;
3467 int indexBegin = (int) rankBegin;
3468 int indexEnd = (int) rankEnd;
3469
3470 if (fTable.getData(Key.FILTER_OBJ) != null) {
3471 /* +1 for top filter status row */
3472 indexBegin = fCache.getFilteredEventIndex(rankBegin) + 1;
3473 indexEnd = rankEnd == rankBegin ? indexBegin : fCache.getFilteredEventIndex(rankEnd) + 1;
3474 }
3475 /* +1 for header row */
3476 fTable.setSelectionRange(indexBegin + 1, indexEnd + 1);
3477 fRawViewer.selectAndReveal(toReveal);
3478 updateStatusLine(null);
3479 }
3480 });
3481 }
3482 };
3483 timeSelectJob.setSystem(true);
3484 /*
3485 * Make subsequent jobs not run concurrently so that they are
3486 * executed in order.
3487 */
3488 timeSelectJob.setRule(fTimeSelectMutexRule);
3489 timeSelectJob.schedule();
3490 fTimeSelectJob = timeSelectJob;
3491 }
3492 }
3493 }
3494
3495 // ------------------------------------------------------------------------
3496 // Error handling
3497 // ------------------------------------------------------------------------
3498
3499 /**
3500 * Display an exception in a message box
3501 *
3502 * @param e
3503 * the exception
3504 */
3505 private static void displayException(final Exception e) {
3506 final MessageBox mb = new MessageBox(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell());
3507 mb.setText(e.getClass().getSimpleName());
3508 mb.setMessage(e.getMessage());
3509 mb.open();
3510 }
3511
3512 /**
3513 * Refresh the table
3514 */
3515 public void refresh() {
3516 fCache.clear();
3517 fTable.refresh();
3518 fTable.redraw();
3519 }
3520
3521 /**
3522 * Margin column for images and special text (e.g. collapse count)
3523 */
3524 private static final class TmfMarginColumn extends TmfEventTableColumn {
3525
3526 private static final @NonNull ITmfEventAspect<String> MARGIN_ASPECT = new ITmfEventAspect<String>() {
3527
3528 @Override
3529 public String getName() {
3530 return EMPTY_STRING;
3531 }
3532
3533 @Override
3534 public String resolve(ITmfEvent event) {
3535 if (!(event instanceof CachedEvent) || ((CachedEvent) event).repeatCount == 0) {
3536 return EMPTY_STRING;
3537 }
3538 return "+" + ((CachedEvent) event).repeatCount; //$NON-NLS-1$
3539 }
3540
3541 @Override
3542 public String getHelpText() {
3543 return EMPTY_STRING;
3544 }
3545 };
3546
3547 /**
3548 * Constructor
3549 */
3550 public TmfMarginColumn() {
3551 super(MARGIN_ASPECT);
3552 }
3553 }
3554 }
This page took 0.130916 seconds and 5 git commands to generate.