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