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