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