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