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