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