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