Merge branch 'master' into lttng-kepler
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / viewers / events / TmfEventsTable.java
1 /*******************************************************************************
2 * Copyright (c) 2010, 2011, 2012 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
11 * Patrick Tasse - Factored out from events view
12 * Francois Chouinard - Replaced Table by TmfVirtualTable
13 * Patrick Tasse - Filter implementation (inspired by www.eclipse.org/mat)
14 *******************************************************************************/
15
16 package org.eclipse.linuxtools.tmf.ui.viewers.events;
17
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.regex.Pattern;
25 import java.util.regex.PatternSyntaxException;
26
27 import org.eclipse.core.resources.IFile;
28 import org.eclipse.core.resources.IMarker;
29 import org.eclipse.core.resources.IResource;
30 import org.eclipse.core.runtime.CoreException;
31 import org.eclipse.core.runtime.IProgressMonitor;
32 import org.eclipse.core.runtime.IStatus;
33 import org.eclipse.core.runtime.Status;
34 import org.eclipse.core.runtime.jobs.Job;
35 import org.eclipse.jface.action.Action;
36 import org.eclipse.jface.action.IAction;
37 import org.eclipse.jface.action.IMenuListener;
38 import org.eclipse.jface.action.IMenuManager;
39 import org.eclipse.jface.action.MenuManager;
40 import org.eclipse.jface.action.Separator;
41 import org.eclipse.jface.dialogs.InputDialog;
42 import org.eclipse.jface.dialogs.MessageDialog;
43 import org.eclipse.jface.resource.FontDescriptor;
44 import org.eclipse.jface.resource.JFaceResources;
45 import org.eclipse.jface.resource.LocalResourceManager;
46 import org.eclipse.jface.window.Window;
47 import org.eclipse.linuxtools.internal.tmf.ui.Activator;
48 import org.eclipse.linuxtools.internal.tmf.ui.Messages;
49 import org.eclipse.linuxtools.tmf.core.component.ITmfDataProvider;
50 import org.eclipse.linuxtools.tmf.core.component.TmfComponent;
51 import org.eclipse.linuxtools.tmf.core.event.ITmfEvent;
52 import org.eclipse.linuxtools.tmf.core.event.ITmfEventField;
53 import org.eclipse.linuxtools.tmf.core.event.ITmfTimestamp;
54 import org.eclipse.linuxtools.tmf.core.event.TmfEventField;
55 import org.eclipse.linuxtools.tmf.core.event.TmfTimestamp;
56 import org.eclipse.linuxtools.tmf.core.filter.ITmfFilter;
57 import org.eclipse.linuxtools.tmf.core.filter.model.ITmfFilterTreeNode;
58 import org.eclipse.linuxtools.tmf.core.filter.model.TmfFilterAndNode;
59 import org.eclipse.linuxtools.tmf.core.filter.model.TmfFilterMatchesNode;
60 import org.eclipse.linuxtools.tmf.core.filter.model.TmfFilterNode;
61 import org.eclipse.linuxtools.tmf.core.request.ITmfDataRequest.ExecutionType;
62 import org.eclipse.linuxtools.tmf.core.request.TmfDataRequest;
63 import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentUpdatedSignal;
64 import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler;
65 import org.eclipse.linuxtools.tmf.core.signal.TmfTimeSynchSignal;
66 import org.eclipse.linuxtools.tmf.core.signal.TmfTraceUpdatedSignal;
67 import org.eclipse.linuxtools.tmf.core.trace.ITmfContext;
68 import org.eclipse.linuxtools.tmf.core.trace.ITmfLocation;
69 import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace;
70 import org.eclipse.linuxtools.tmf.ui.viewers.events.TmfEventsCache.CachedEvent;
71 import org.eclipse.linuxtools.tmf.ui.views.colors.ColorSetting;
72 import org.eclipse.linuxtools.tmf.ui.views.colors.ColorSettingsManager;
73 import org.eclipse.linuxtools.tmf.ui.views.colors.IColorSettingsListener;
74 import org.eclipse.linuxtools.tmf.ui.views.filter.FilterManager;
75 import org.eclipse.linuxtools.tmf.ui.widgets.rawviewer.TmfRawEventViewer;
76 import org.eclipse.linuxtools.tmf.ui.widgets.virtualtable.ColumnData;
77 import org.eclipse.linuxtools.tmf.ui.widgets.virtualtable.TmfVirtualTable;
78 import org.eclipse.swt.SWT;
79 import org.eclipse.swt.custom.SashForm;
80 import org.eclipse.swt.custom.TableEditor;
81 import org.eclipse.swt.events.FocusAdapter;
82 import org.eclipse.swt.events.FocusEvent;
83 import org.eclipse.swt.events.KeyAdapter;
84 import org.eclipse.swt.events.KeyEvent;
85 import org.eclipse.swt.events.MouseAdapter;
86 import org.eclipse.swt.events.MouseEvent;
87 import org.eclipse.swt.events.SelectionAdapter;
88 import org.eclipse.swt.events.SelectionEvent;
89 import org.eclipse.swt.graphics.Color;
90 import org.eclipse.swt.graphics.Font;
91 import org.eclipse.swt.graphics.Image;
92 import org.eclipse.swt.graphics.Point;
93 import org.eclipse.swt.graphics.Rectangle;
94 import org.eclipse.swt.layout.FillLayout;
95 import org.eclipse.swt.layout.GridData;
96 import org.eclipse.swt.layout.GridLayout;
97 import org.eclipse.swt.widgets.Composite;
98 import org.eclipse.swt.widgets.Display;
99 import org.eclipse.swt.widgets.Event;
100 import org.eclipse.swt.widgets.Label;
101 import org.eclipse.swt.widgets.Listener;
102 import org.eclipse.swt.widgets.Menu;
103 import org.eclipse.swt.widgets.MessageBox;
104 import org.eclipse.swt.widgets.Shell;
105 import org.eclipse.swt.widgets.TableColumn;
106 import org.eclipse.swt.widgets.TableItem;
107 import org.eclipse.swt.widgets.Text;
108 import org.eclipse.ui.PlatformUI;
109 import org.eclipse.ui.ide.IGotoMarker;
110 import org.eclipse.ui.themes.ColorUtil;
111
112 /**
113 * The generic TMF Events table
114 *
115 * This is a view that will list events that are read from a trace.
116 *
117 * @version 1.0
118 * @author Francois Chouinard
119 * @author Patrick Tasse
120 */
121 public class TmfEventsTable extends TmfComponent implements IGotoMarker,
122 IColorSettingsListener, ITmfEventsFilterProvider {
123
124 private static final Image BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath(
125 "icons/elcl16/bookmark_obj.gif"); //$NON-NLS-1$
126 private static final Image SEARCH_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/search.gif"); //$NON-NLS-1$
127 private static final Image SEARCH_MATCH_IMAGE = Activator.getDefault().getImageFromPath(
128 "icons/elcl16/search_match.gif"); //$NON-NLS-1$
129 private static final Image SEARCH_MATCH_BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath(
130 "icons/elcl16/search_match_bookmark.gif"); //$NON-NLS-1$
131 private static final Image FILTER_IMAGE = Activator.getDefault()
132 .getImageFromPath("icons/elcl16/filter_items.gif"); //$NON-NLS-1$
133 private static final Image STOP_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/stop.gif"); //$NON-NLS-1$
134 private static final String SEARCH_HINT = Messages.TmfEventsTable_SearchHint;
135 private static final String FILTER_HINT = Messages.TmfEventsTable_FilterHint;
136 private static final int MAX_CACHE_SIZE = 1000;
137
138 /**
139 * The events table search/filter keys
140 *
141 * @version 1.0
142 * @author Patrick Tasse
143 */
144 public interface Key {
145 /** Search text */
146 String SEARCH_TXT = "$srch_txt"; //$NON-NLS-1$
147
148 /** Search object */
149 String SEARCH_OBJ = "$srch_obj"; //$NON-NLS-1$
150
151 /** Filter text */
152 String FILTER_TXT = "$fltr_txt"; //$NON-NLS-1$
153
154 /** Filter object */
155 String FILTER_OBJ = "$fltr_obj"; //$NON-NLS-1$
156
157 /** Timestamp*/
158 String TIMESTAMP = "$time"; //$NON-NLS-1$
159
160 /** Rank */
161 String RANK = "$rank"; //$NON-NLS-1$
162
163 /** Field ID */
164 String FIELD_ID = "$field_id"; //$NON-NLS-1$
165
166 /** Bookmark indicator */
167 String BOOKMARK = "$bookmark"; //$NON-NLS-1$
168 }
169
170 /**
171 * The events table search/filter state
172 *
173 * @version 1.0
174 * @author Patrick Tasse
175 */
176 public static enum HeaderState {
177 /** A search is being run */
178 SEARCH,
179
180 /** A filter is applied */
181 FILTER
182 }
183
184 interface Direction {
185 int FORWARD = +1;
186 int BACKWARD = -1;
187 }
188
189 // ------------------------------------------------------------------------
190 // Table data
191 // ------------------------------------------------------------------------
192
193 protected Composite fComposite;
194 protected SashForm fSashForm;
195 protected TmfVirtualTable fTable;
196 protected TmfRawEventViewer fRawViewer;
197 protected ITmfTrace fTrace;
198 protected boolean fPackDone = false;
199 protected HeaderState fHeaderState = HeaderState.SEARCH;
200 protected long fSelectedRank = 0;
201
202 // Filter data
203 protected long fFilterMatchCount;
204 protected long fFilterCheckCount;
205 protected FilterThread fFilterThread;
206 protected boolean fFilterThreadResume = false;
207 protected final Object fFilterSyncObj = new Object();
208 protected SearchThread fSearchThread;
209 protected final Object fSearchSyncObj = new Object();
210 protected List<ITmfEventsFilterListener> fEventsFilterListeners = new ArrayList<ITmfEventsFilterListener>();
211
212 // Bookmark map <Rank, MarkerId>
213 protected Map<Long, Long> fBookmarksMap = new HashMap<Long, Long>();
214 protected IFile fBookmarksFile;
215 protected long fPendingGotoRank = -1;
216
217 // SWT resources
218 protected LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
219 protected Color fGrayColor;
220 protected Color fGreenColor;
221 protected Font fBoldFont;
222
223 // Table column names
224 static private final String[] COLUMN_NAMES = new String[] { Messages.TmfEventsTable_TimestampColumnHeader,
225 Messages.TmfEventsTable_SourceColumnHeader, Messages.TmfEventsTable_TypeColumnHeader,
226 Messages.TmfEventsTable_ReferenceColumnHeader, Messages.TmfEventsTable_ContentColumnHeader };
227
228 static private final ColumnData[] COLUMN_DATA = new ColumnData[] { new ColumnData(COLUMN_NAMES[0], 100, SWT.LEFT),
229 new ColumnData(COLUMN_NAMES[1], 100, SWT.LEFT), new ColumnData(COLUMN_NAMES[2], 100, SWT.LEFT),
230 new ColumnData(COLUMN_NAMES[3], 100, SWT.LEFT), new ColumnData(COLUMN_NAMES[4], 100, SWT.LEFT) };
231
232 // Event cache
233 private final TmfEventsCache fCache;
234 private boolean fCacheUpdateBusy = false;
235 private boolean fCacheUpdatePending = false;
236 private boolean fCacheUpdateCompleted = false;
237 private final Object fCacheUpdateSyncObj = new Object();
238
239 private boolean fDisposeOnClose;
240
241 // ------------------------------------------------------------------------
242 // Constructor
243 // ------------------------------------------------------------------------
244
245 /**
246 * Basic constructor, will use default column data.
247 *
248 * @param parent
249 * The parent composite UI object
250 * @param cacheSize
251 * The size of the event table cache
252 */
253 public TmfEventsTable(final Composite parent, final int cacheSize) {
254 this(parent, cacheSize, COLUMN_DATA);
255 }
256
257 /**
258 * Advanced constructor, where we also define which column data to use.
259 *
260 * @param parent
261 * The parent composite UI object
262 * @param cacheSize
263 * The size of the event table cache
264 * @param columnData
265 * The column data to use for this table
266 */
267 public TmfEventsTable(final Composite parent, int cacheSize, final ColumnData[] columnData) {
268 super("TmfEventsTable"); //$NON-NLS-1$
269
270 fComposite = new Composite(parent, SWT.NONE);
271 final GridLayout gl = new GridLayout(1, false);
272 gl.marginHeight = 0;
273 gl.marginWidth = 0;
274 gl.verticalSpacing = 0;
275 fComposite.setLayout(gl);
276
277 fSashForm = new SashForm(fComposite, SWT.HORIZONTAL);
278 fSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
279
280 // Create a virtual table
281 final int style = SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION;
282 fTable = new TmfVirtualTable(fSashForm, style);
283
284 // Set the table layout
285 final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
286 fTable.setLayoutData(layoutData);
287
288 // Some cosmetic enhancements
289 fTable.setHeaderVisible(true);
290 fTable.setLinesVisible(true);
291
292 // Set the columns
293 setColumnHeaders(columnData);
294
295 // Set the default column field ids if this is not a subclass
296 if (Arrays.equals(columnData, COLUMN_DATA)) {
297 fTable.getColumns()[0].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_TIMESTAMP);
298 fTable.getColumns()[1].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_SOURCE);
299 fTable.getColumns()[2].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_TYPE);
300 fTable.getColumns()[3].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_REFERENCE);
301 fTable.getColumns()[4].setData(Key.FIELD_ID, ITmfEvent.EVENT_FIELD_CONTENT);
302 }
303
304 // Set the frozen row for header row
305 fTable.setFrozenRowCount(1);
306
307 // Create the header row cell editor
308 createHeaderEditor();
309
310 // Handle the table item selection
311 fTable.addSelectionListener(new SelectionAdapter() {
312 @Override
313 public void widgetSelected(final SelectionEvent e) {
314 final TableItem[] selection = fTable.getSelection();
315 if (selection.length > 0) {
316 final TableItem selectedTableItem = selection[0];
317 if (selectedTableItem != null) {
318 if (selectedTableItem.getData(Key.RANK) instanceof Long) {
319 fSelectedRank = (Long) selectedTableItem.getData(Key.RANK);
320 fRawViewer.selectAndReveal((Long) selectedTableItem.getData(Key.RANK));
321 }
322 if (selectedTableItem.getData(Key.TIMESTAMP) instanceof TmfTimestamp) {
323 final TmfTimestamp ts = (TmfTimestamp) selectedTableItem.getData(Key.TIMESTAMP);
324 broadcast(new TmfTimeSynchSignal(TmfEventsTable.this, ts));
325 }
326 }
327 }
328 }
329 });
330
331 cacheSize = Math.max(cacheSize, Display.getDefault().getBounds().height / fTable.getItemHeight());
332 cacheSize = Math.min(cacheSize, MAX_CACHE_SIZE);
333 fCache = new TmfEventsCache(cacheSize, this);
334
335 // Handle the table item requests
336 fTable.addListener(SWT.SetData, new Listener() {
337
338 @Override
339 public void handleEvent(final Event event) {
340
341 final TableItem item = (TableItem) event.item;
342 int index = event.index - 1; // -1 for the header row
343
344 if (event.index == 0) {
345 setHeaderRowItemData(item);
346 return;
347 }
348
349 if (fTable.getData(Key.FILTER_OBJ) != null) {
350 if ((event.index == 1) || (event.index == (fTable.getItemCount() - 1))) {
351 setFilterStatusRowItemData(item);
352 return;
353 }
354 index = index - 1; // -1 for top filter status row
355 }
356
357 final CachedEvent cachedEvent = fCache.getEvent(index);
358 if (cachedEvent != null) {
359 setItemData(item, cachedEvent.event, cachedEvent.rank);
360 return;
361 }
362
363 // Else, fill the cache asynchronously (and off the UI thread)
364 event.doit = false;
365 }
366 });
367
368 fTable.addMouseListener(new MouseAdapter() {
369 @Override
370 public void mouseDoubleClick(final MouseEvent event) {
371 if (event.button != 1) {
372 return;
373 }
374 // Identify the selected row
375 final Point point = new Point(event.x, event.y);
376 final TableItem item = fTable.getItem(point);
377 if (item != null) {
378 final Rectangle imageBounds = item.getImageBounds(0);
379 imageBounds.width = BOOKMARK_IMAGE.getBounds().width;
380 if (imageBounds.contains(point)) {
381 final Long rank = (Long) item.getData(Key.RANK);
382 if (rank != null) {
383 toggleBookmark(rank);
384 }
385 }
386 }
387 }
388 });
389
390 final Listener tooltipListener = new Listener () {
391 Shell tooltipShell = null;
392 @Override
393 public void handleEvent(final Event event) {
394 switch (event.type) {
395 case SWT.MouseHover:
396 final TableItem item = fTable.getItem(new Point(event.x, event.y));
397 if (item == null) {
398 return;
399 }
400 final Long rank = (Long) item.getData(Key.RANK);
401 if (rank == null) {
402 return;
403 }
404 final String tooltipText = (String) item.getData(Key.BOOKMARK);
405 final Rectangle bounds = item.getImageBounds(0);
406 bounds.width = BOOKMARK_IMAGE.getBounds().width;
407 if (!bounds.contains(event.x,event.y)) {
408 return;
409 }
410 if ((tooltipShell != null) && !tooltipShell.isDisposed()) {
411 tooltipShell.dispose();
412 }
413 tooltipShell = new Shell(fTable.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
414 tooltipShell.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
415 final FillLayout layout = new FillLayout();
416 layout.marginWidth = 2;
417 tooltipShell.setLayout(layout);
418 final Label label = new Label(tooltipShell, SWT.WRAP);
419 String text = rank.toString() + (tooltipText != null ? ": " + tooltipText : ""); //$NON-NLS-1$ //$NON-NLS-2$
420 label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
421 label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
422 label.setText(text);
423 label.addListener(SWT.MouseExit, this);
424 label.addListener(SWT.MouseDown, this);
425 label.addListener(SWT.MouseWheel, this);
426 final Point size = tooltipShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
427 /*
428 * Bug in Linux. The coordinates of the event have an origin that excludes the table header but
429 * the method toDisplay() expects coordinates relative to an origin that includes the table header.
430 */
431 int y = event.y;
432 if (System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
433 y += fTable.getHeaderHeight();
434 }
435 Point pt = fTable.toDisplay(event.x, y);
436 pt.x += BOOKMARK_IMAGE.getBounds().width;
437 pt.y += size.y;
438 tooltipShell.setBounds(pt.x, pt.y, size.x, size.y);
439 tooltipShell.setVisible(true);
440 break;
441 case SWT.Dispose:
442 case SWT.KeyDown:
443 case SWT.MouseMove:
444 case SWT.MouseExit:
445 case SWT.MouseDown:
446 case SWT.MouseWheel:
447 if (tooltipShell != null) {
448 tooltipShell.dispose();
449 tooltipShell = null;
450 }
451 break;
452 default:
453 break;
454 }
455 }
456 };
457
458 fTable.addListener(SWT.MouseHover, tooltipListener);
459 fTable.addListener(SWT.Dispose, tooltipListener);
460 fTable.addListener(SWT.KeyDown, tooltipListener);
461 fTable.addListener(SWT.MouseMove, tooltipListener);
462 fTable.addListener(SWT.MouseExit, tooltipListener);
463 fTable.addListener(SWT.MouseDown, tooltipListener);
464 fTable.addListener(SWT.MouseWheel, tooltipListener);
465
466 // Create resources
467 createResources();
468
469 ColorSettingsManager.addColorSettingsListener(this);
470
471 fTable.setItemCount(1); // +1 for header row
472
473 fRawViewer = new TmfRawEventViewer(fSashForm, SWT.H_SCROLL | SWT.V_SCROLL);
474
475 fRawViewer.addSelectionListener(new Listener() {
476 @Override
477 public void handleEvent(final Event e) {
478 if (e.data instanceof Long) {
479 final long rank = (Long) e.data;
480 int index = (int) rank;
481 if (fTable.getData(Key.FILTER_OBJ) != null) {
482 index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
483 }
484 fTable.setSelection(index + 1); // +1 for header row
485 fSelectedRank = rank;
486 } else if (e.data instanceof ITmfLocation<?>) {
487 // DOES NOT WORK: rank undefined in context from seekLocation()
488 // ITmfLocation<?> location = (ITmfLocation<?>) e.data;
489 // TmfContext context = fTrace.seekLocation(location);
490 // fTable.setSelection((int) context.getRank());
491 return;
492 } else {
493 return;
494 }
495 final TableItem[] selection = fTable.getSelection();
496 if ((selection != null) && (selection.length > 0)) {
497 final TmfTimestamp ts = (TmfTimestamp) fTable.getSelection()[0].getData(Key.TIMESTAMP);
498 if (ts != null) {
499 broadcast(new TmfTimeSynchSignal(TmfEventsTable.this, ts));
500 }
501 }
502 }
503 });
504
505 fSashForm.setWeights(new int[] { 1, 1 });
506 fRawViewer.setVisible(false);
507
508 createPopupMenu();
509 }
510
511 protected void createPopupMenu() {
512 final IAction showTableAction = new Action(Messages.TmfEventsTable_ShowTableActionText) {
513 @Override
514 public void run() {
515 fTable.setVisible(true);
516 fSashForm.layout();
517 }
518 };
519
520 final IAction hideTableAction = new Action(Messages.TmfEventsTable_HideTableActionText) {
521 @Override
522 public void run() {
523 fTable.setVisible(false);
524 fSashForm.layout();
525 }
526 };
527
528 final IAction showRawAction = new Action(Messages.TmfEventsTable_ShowRawActionText) {
529 @Override
530 public void run() {
531 fRawViewer.setVisible(true);
532 fSashForm.layout();
533 final int index = fTable.getSelectionIndex();
534 if (index >= +1) {
535 fRawViewer.selectAndReveal(index - 1);
536 }
537 }
538 };
539
540 final IAction hideRawAction = new Action(Messages.TmfEventsTable_HideRawActionText) {
541 @Override
542 public void run() {
543 fRawViewer.setVisible(false);
544 fSashForm.layout();
545 }
546 };
547
548 final IAction showSearchBarAction = new Action(Messages.TmfEventsTable_ShowSearchBarActionText) {
549 @Override
550 public void run() {
551 fHeaderState = HeaderState.SEARCH;
552 fTable.refresh();
553 }
554 };
555
556 final IAction showFilterBarAction = new Action(Messages.TmfEventsTable_ShowFilterBarActionText) {
557 @Override
558 public void run() {
559 fHeaderState = HeaderState.FILTER;
560 fTable.refresh();
561 }
562 };
563
564 final IAction clearFiltersAction = new Action(Messages.TmfEventsTable_ClearFiltersActionText) {
565 @Override
566 public void run() {
567 clearFilters();
568 }
569 };
570
571 class ToggleBookmarkAction extends Action {
572 long fRank;
573
574 public ToggleBookmarkAction(final String text, final long rank) {
575 super(text);
576 fRank = rank;
577 }
578
579 @Override
580 public void run() {
581 toggleBookmark(fRank);
582 }
583 }
584
585 final MenuManager tablePopupMenu = new MenuManager();
586 tablePopupMenu.setRemoveAllWhenShown(true);
587 tablePopupMenu.addMenuListener(new IMenuListener() {
588 @Override
589 public void menuAboutToShow(final IMenuManager manager) {
590 if (fTable.getSelectionIndex() == 0) {
591 // Right-click on header row
592 if (fHeaderState == HeaderState.FILTER) {
593 tablePopupMenu.add(showSearchBarAction);
594 } else {
595 tablePopupMenu.add(showFilterBarAction);
596 }
597 return;
598 }
599 final Point point = fTable.toControl(Display.getDefault().getCursorLocation());
600 final TableItem item = fTable.getSelection().length > 0 ? fTable.getSelection()[0] : null;
601 if (item != null) {
602 final Rectangle imageBounds = item.getImageBounds(0);
603 imageBounds.width = BOOKMARK_IMAGE.getBounds().width;
604 if (point.x <= (imageBounds.x + imageBounds.width)) {
605 // Right-click on left margin
606 final Long rank = (Long) item.getData(Key.RANK);
607 if ((rank != null) && (fBookmarksFile != null)) {
608 if (fBookmarksMap.containsKey(rank)) {
609 tablePopupMenu.add(new ToggleBookmarkAction(
610 Messages.TmfEventsTable_RemoveBookmarkActionText, rank));
611 } else {
612 tablePopupMenu.add(new ToggleBookmarkAction(
613 Messages.TmfEventsTable_AddBookmarkActionText, rank));
614 }
615 }
616 return;
617 }
618 }
619 // Right-click on table
620 if (fTable.isVisible() && fRawViewer.isVisible()) {
621 tablePopupMenu.add(hideTableAction);
622 tablePopupMenu.add(hideRawAction);
623 } else if (!fTable.isVisible()) {
624 tablePopupMenu.add(showTableAction);
625 } else if (!fRawViewer.isVisible()) {
626 tablePopupMenu.add(showRawAction);
627 }
628 tablePopupMenu.add(new Separator());
629 tablePopupMenu.add(clearFiltersAction);
630 final ITmfFilterTreeNode[] savedFilters = FilterManager.getSavedFilters();
631 if (savedFilters.length > 0) {
632 final MenuManager subMenu = new MenuManager(Messages.TmfEventsTable_ApplyPresetFilterMenuName);
633 for (final ITmfFilterTreeNode node : savedFilters) {
634 if (node instanceof TmfFilterNode) {
635 final TmfFilterNode filter = (TmfFilterNode) node;
636 subMenu.add(new Action(filter.getFilterName()) {
637 @Override
638 public void run() {
639 applyFilter(filter);
640 }
641 });
642 }
643 }
644 tablePopupMenu.add(subMenu);
645 }
646 appendToTablePopupMenu(tablePopupMenu, item);
647 }
648 });
649
650 final MenuManager rawViewerPopupMenu = new MenuManager();
651 rawViewerPopupMenu.setRemoveAllWhenShown(true);
652 rawViewerPopupMenu.addMenuListener(new IMenuListener() {
653 @Override
654 public void menuAboutToShow(final IMenuManager manager) {
655 if (fTable.isVisible() && fRawViewer.isVisible()) {
656 rawViewerPopupMenu.add(hideTableAction);
657 rawViewerPopupMenu.add(hideRawAction);
658 } else if (!fTable.isVisible()) {
659 rawViewerPopupMenu.add(showTableAction);
660 } else if (!fRawViewer.isVisible()) {
661 rawViewerPopupMenu.add(showRawAction);
662 }
663 appendToRawPopupMenu(tablePopupMenu);
664 }
665 });
666
667 Menu menu = tablePopupMenu.createContextMenu(fTable);
668 fTable.setMenu(menu);
669
670 menu = rawViewerPopupMenu.createContextMenu(fRawViewer);
671 fRawViewer.setMenu(menu);
672 }
673
674 protected void appendToTablePopupMenu(final MenuManager tablePopupMenu, final TableItem selectedItem) {
675 // override to append more actions
676 }
677
678 protected void appendToRawPopupMenu(final MenuManager rawViewerPopupMenu) {
679 // override to append more actions
680 }
681
682 @Override
683 public void dispose() {
684 stopSearchThread();
685 stopFilterThread();
686 ColorSettingsManager.removeColorSettingsListener(this);
687 fComposite.dispose();
688 if ((fTrace != null) && fDisposeOnClose) {
689 fTrace.dispose();
690 }
691 fResourceManager.dispose();
692 super.dispose();
693 }
694
695 /**
696 * Assign a layout data object to this view.
697 *
698 * @param layoutData
699 * The layout data to assign
700 */
701 public void setLayoutData(final Object layoutData) {
702 fComposite.setLayoutData(layoutData);
703 }
704
705 /**
706 * Get the virtual table contained in this event table.
707 *
708 * @return The TMF virtual table
709 */
710 public TmfVirtualTable getTable() {
711 return fTable;
712 }
713
714 /**
715 * @param columnData
716 *
717 * FIXME: Add support for column selection
718 */
719 protected void setColumnHeaders(final ColumnData[] columnData) {
720 fTable.setColumnHeaders(columnData);
721 }
722
723 protected void setItemData(final TableItem item, final ITmfEvent event, final long rank) {
724 final ITmfEventField[] fields = extractItemFields(event);
725 final String[] content = new String[fields.length];
726 for (int i = 0; i < fields.length; i++) {
727 content[i] = fields[i].getValue() != null ? fields[i].getValue().toString() : ""; //$NON-NLS-1$
728 }
729 item.setText(content);
730 item.setData(Key.TIMESTAMP, new TmfTimestamp(event.getTimestamp()));
731 item.setData(Key.RANK, rank);
732
733 boolean bookmark = false;
734 final Long markerId = fBookmarksMap.get(rank);
735 if (markerId != null) {
736 bookmark = true;
737 try {
738 final IMarker marker = fBookmarksFile.findMarker(markerId);
739 item.setData(Key.BOOKMARK, marker.getAttribute(IMarker.MESSAGE));
740 } catch (final CoreException e) {
741 displayException(e);
742 }
743 } else {
744 item.setData(Key.BOOKMARK, null);
745 }
746
747 boolean searchMatch = false;
748 boolean searchNoMatch = false;
749 final ITmfFilter searchFilter = (ITmfFilter) fTable.getData(Key.SEARCH_OBJ);
750 if (searchFilter != null) {
751 if (searchFilter.matches(event)) {
752 searchMatch = true;
753 } else {
754 searchNoMatch = true;
755 }
756 }
757
758 final ColorSetting colorSetting = ColorSettingsManager.getColorSetting(event);
759 if (searchNoMatch) {
760 item.setForeground(colorSetting.getDimmedForegroundColor());
761 item.setBackground(colorSetting.getDimmedBackgroundColor());
762 } else {
763 item.setForeground(colorSetting.getForegroundColor());
764 item.setBackground(colorSetting.getBackgroundColor());
765 }
766
767 if (searchMatch) {
768 if (bookmark) {
769 item.setImage(SEARCH_MATCH_BOOKMARK_IMAGE);
770 } else {
771 item.setImage(SEARCH_MATCH_IMAGE);
772 }
773 } else if (bookmark) {
774 item.setImage(BOOKMARK_IMAGE);
775 } else {
776 item.setImage((Image) null);
777 }
778 }
779
780 protected void setHeaderRowItemData(final TableItem item) {
781 String txtKey = null;
782 if (fHeaderState == HeaderState.SEARCH) {
783 item.setImage(SEARCH_IMAGE);
784 txtKey = Key.SEARCH_TXT;
785 } else if (fHeaderState == HeaderState.FILTER) {
786 item.setImage(FILTER_IMAGE);
787 txtKey = Key.FILTER_TXT;
788 }
789 item.setForeground(fGrayColor);
790 for (int i = 0; i < fTable.getColumns().length; i++) {
791 final TableColumn column = fTable.getColumns()[i];
792 final String filter = (String) column.getData(txtKey);
793 if (filter == null) {
794 if (fHeaderState == HeaderState.SEARCH) {
795 item.setText(i, SEARCH_HINT);
796 } else if (fHeaderState == HeaderState.FILTER) {
797 item.setText(i, FILTER_HINT);
798 }
799 item.setForeground(i, fGrayColor);
800 item.setFont(i, fTable.getFont());
801 } else {
802 item.setText(i, filter);
803 item.setForeground(i, fGreenColor);
804 item.setFont(i, fBoldFont);
805 }
806 }
807 }
808
809 protected void setFilterStatusRowItemData(final TableItem item) {
810 for (int i = 0; i < fTable.getColumns().length; i++) {
811 if (i == 0) {
812 if ((fTrace == null) || (fFilterCheckCount == fTrace.getNbEvents())) {
813 item.setImage(FILTER_IMAGE);
814 } else {
815 item.setImage(STOP_IMAGE);
816 }
817 item.setText(0, fFilterMatchCount + "/" + fFilterCheckCount); //$NON-NLS-1$
818 } else {
819 item.setText(i, ""); //$NON-NLS-1$
820 }
821 }
822 item.setData(Key.TIMESTAMP, null);
823 item.setData(Key.RANK, null);
824 item.setForeground(null);
825 item.setBackground(null);
826 }
827
828 protected void createHeaderEditor() {
829 final TableEditor tableEditor = fTable.createTableEditor();
830 tableEditor.horizontalAlignment = SWT.LEFT;
831 tableEditor.verticalAlignment = SWT.CENTER;
832 tableEditor.grabHorizontal = true;
833 tableEditor.minimumWidth = 50;
834
835 // Handle the header row selection
836 fTable.addMouseListener(new MouseAdapter() {
837 int columnIndex;
838 TableColumn column;
839 TableItem item;
840
841 @Override
842 public void mouseDown(final MouseEvent event) {
843 if (event.button != 1) {
844 return;
845 }
846 // Identify the selected row
847 final Point point = new Point(event.x, event.y);
848 item = fTable.getItem(point);
849
850 // Header row selected
851 if ((item != null) && (fTable.indexOf(item) == 0)) {
852
853 // Icon selected
854 if (item.getImageBounds(0).contains(point)) {
855 if (fHeaderState == HeaderState.SEARCH) {
856 fHeaderState = HeaderState.FILTER;
857 } else if (fHeaderState == HeaderState.FILTER) {
858 fHeaderState = HeaderState.SEARCH;
859 }
860 fTable.refresh();
861 return;
862 }
863
864 // Identify the selected column
865 columnIndex = -1;
866 for (int i = 0; i < fTable.getColumns().length; i++) {
867 final Rectangle rect = item.getBounds(i);
868 if (rect.contains(point)) {
869 columnIndex = i;
870 break;
871 }
872 }
873
874 if (columnIndex == -1) {
875 return;
876 }
877
878 column = fTable.getColumns()[columnIndex];
879
880 String txtKey = null;
881 if (fHeaderState == HeaderState.SEARCH) {
882 txtKey = Key.SEARCH_TXT;
883 } else if (fHeaderState == HeaderState.FILTER) {
884 txtKey = Key.FILTER_TXT;
885 }
886
887 // The control that will be the editor must be a child of the Table
888 final Text newEditor = (Text) fTable.createTableEditorControl(Text.class);
889 final String headerString = (String) column.getData(txtKey);
890 if (headerString != null) {
891 newEditor.setText(headerString);
892 }
893 newEditor.addFocusListener(new FocusAdapter() {
894 @Override
895 public void focusLost(final FocusEvent e) {
896 final boolean changed = updateHeader(newEditor.getText());
897 if (changed) {
898 applyHeader();
899 }
900 }
901 });
902 newEditor.addKeyListener(new KeyAdapter() {
903 @Override
904 public void keyPressed(final KeyEvent e) {
905 if (e.character == SWT.CR) {
906 updateHeader(newEditor.getText());
907 applyHeader();
908 } else if (e.character == SWT.ESC) {
909 tableEditor.getEditor().dispose();
910 }
911 }
912 });
913 newEditor.selectAll();
914 newEditor.setFocus();
915 tableEditor.setEditor(newEditor, item, columnIndex);
916 }
917 }
918
919 /*
920 * returns true is value was changed
921 */
922 private boolean updateHeader(final String text) {
923 String objKey = null;
924 String txtKey = null;
925 if (fHeaderState == HeaderState.SEARCH) {
926 objKey = Key.SEARCH_OBJ;
927 txtKey = Key.SEARCH_TXT;
928 } else if (fHeaderState == HeaderState.FILTER) {
929 objKey = Key.FILTER_OBJ;
930 txtKey = Key.FILTER_TXT;
931 }
932 if (text.trim().length() > 0) {
933 try {
934 final String regex = TmfFilterMatchesNode.regexFix(text);
935 Pattern.compile(regex);
936 if (regex.equals(column.getData(txtKey))) {
937 tableEditor.getEditor().dispose();
938 return false;
939 }
940 final TmfFilterMatchesNode filter = new TmfFilterMatchesNode(null);
941 String fieldId = (String) column.getData(Key.FIELD_ID);
942 if (fieldId == null) {
943 fieldId = column.getText();
944 }
945 filter.setField(fieldId);
946 filter.setRegex(regex);
947 column.setData(objKey, filter);
948 column.setData(txtKey, regex);
949 } catch (final PatternSyntaxException ex) {
950 tableEditor.getEditor().dispose();
951 MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
952 ex.getDescription(), ex.getMessage());
953 return false;
954 }
955 } else {
956 if (column.getData(txtKey) == null) {
957 tableEditor.getEditor().dispose();
958 return false;
959 }
960 column.setData(objKey, null);
961 column.setData(txtKey, null);
962 }
963 return true;
964 }
965
966 private void applyHeader() {
967 if (fHeaderState == HeaderState.SEARCH) {
968 stopSearchThread();
969 final TmfFilterAndNode filter = new TmfFilterAndNode(null);
970 for (final TableColumn column : fTable.getColumns()) {
971 final Object filterObj = column.getData(Key.SEARCH_OBJ);
972 if (filterObj instanceof ITmfFilterTreeNode) {
973 filter.addChild((ITmfFilterTreeNode) filterObj);
974 }
975 }
976 if (filter.getChildrenCount() > 0) {
977 fTable.setData(Key.SEARCH_OBJ, filter);
978 fTable.refresh();
979 searchNext();
980 fireSearchApplied(filter);
981 } else {
982 fTable.setData(Key.SEARCH_OBJ, null);
983 fTable.refresh();
984 fireSearchApplied(null);
985 }
986 } else if (fHeaderState == HeaderState.FILTER) {
987 final TmfFilterAndNode filter = new TmfFilterAndNode(null);
988 for (final TableColumn column : fTable.getColumns()) {
989 final Object filterObj = column.getData(Key.FILTER_OBJ);
990 if (filterObj instanceof ITmfFilterTreeNode) {
991 filter.addChild((ITmfFilterTreeNode) filterObj);
992 }
993 }
994 if (filter.getChildrenCount() > 0) {
995 applyFilter(filter);
996 } else {
997 clearFilters();
998 }
999 }
1000
1001 tableEditor.getEditor().dispose();
1002 }
1003 });
1004
1005 fTable.addKeyListener(new KeyAdapter() {
1006 @Override
1007 public void keyPressed(final KeyEvent e) {
1008 e.doit = false;
1009 if (e.character == SWT.ESC) {
1010 stopFilterThread();
1011 stopSearchThread();
1012 fTable.refresh();
1013 } else if (e.character == SWT.DEL) {
1014 if (fHeaderState == HeaderState.SEARCH) {
1015 stopSearchThread();
1016 for (final TableColumn column : fTable.getColumns()) {
1017 column.setData(Key.SEARCH_OBJ, null);
1018 column.setData(Key.SEARCH_TXT, null);
1019 }
1020 fTable.setData(Key.SEARCH_OBJ, null);
1021 fTable.refresh();
1022 fireSearchApplied(null);
1023 } else if (fHeaderState == HeaderState.FILTER) {
1024 clearFilters();
1025 }
1026 } else if (e.character == SWT.CR) {
1027 if ((e.stateMask & SWT.SHIFT) == 0) {
1028 searchNext();
1029 } else {
1030 searchPrevious();
1031 }
1032 }
1033 }
1034 });
1035 }
1036
1037 protected void fireFilterApplied(final ITmfFilter filter) {
1038 for (final ITmfEventsFilterListener listener : fEventsFilterListeners) {
1039 listener.filterApplied(filter, fTrace);
1040 }
1041 }
1042
1043 protected void fireSearchApplied(final ITmfFilter filter) {
1044 for (final ITmfEventsFilterListener listener : fEventsFilterListeners) {
1045 listener.searchApplied(filter, fTrace);
1046 }
1047 }
1048
1049 protected void startFilterThread() {
1050 synchronized (fFilterSyncObj) {
1051 final ITmfFilterTreeNode filter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1052 if (fFilterThread == null || fFilterThread.filter != filter) {
1053 if (fFilterThread != null) {
1054 fFilterThread.cancel();
1055 fFilterThreadResume = false;
1056 }
1057 fFilterThread = new FilterThread(filter);
1058 fFilterThread.start();
1059 } else {
1060 fFilterThreadResume = true;
1061 }
1062 }
1063 }
1064
1065 protected void stopFilterThread() {
1066 synchronized (fFilterSyncObj) {
1067 if (fFilterThread != null) {
1068 fFilterThread.cancel();
1069 fFilterThread = null;
1070 fFilterThreadResume = false;
1071 }
1072 }
1073 }
1074
1075 /**
1076 * @since 1.1
1077 */
1078 protected void applyFilter(ITmfFilter filter) {
1079 stopFilterThread();
1080 stopSearchThread();
1081 fFilterMatchCount = 0;
1082 fFilterCheckCount = 0;
1083 fCache.applyFilter(filter);
1084 fTable.clearAll();
1085 fTable.setData(Key.FILTER_OBJ, filter);
1086 fTable.setItemCount(3); // +1 for header row, +2 for top and bottom filter status rows
1087 startFilterThread();
1088 fireFilterApplied(filter);
1089 }
1090
1091 protected void clearFilters() {
1092 if (fTable.getData(Key.FILTER_OBJ) == null) {
1093 return;
1094 }
1095 stopFilterThread();
1096 stopSearchThread();
1097 fCache.clearFilter();
1098 fTable.clearAll();
1099 for (final TableColumn column : fTable.getColumns()) {
1100 column.setData(Key.FILTER_OBJ, null);
1101 column.setData(Key.FILTER_TXT, null);
1102 }
1103 fTable.setData(Key.FILTER_OBJ, null);
1104 if (fTrace != null) {
1105 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1106 } else {
1107 fTable.setItemCount(1); // +1 for header row
1108 }
1109 fFilterMatchCount = 0;
1110 fFilterCheckCount = 0;
1111 if (fSelectedRank >= 0) {
1112 fTable.setSelection((int) fSelectedRank + 1); // +1 for header row
1113 } else {
1114 fTable.setSelection(0);
1115 }
1116 fireFilterApplied(null);
1117 }
1118
1119 protected class FilterThread extends Thread {
1120 private final ITmfFilterTreeNode filter;
1121 private TmfDataRequest request;
1122 private boolean refreshBusy = false;
1123 private boolean refreshPending = false;
1124 private final Object syncObj = new Object();
1125
1126 public FilterThread(final ITmfFilterTreeNode filter) {
1127 super("Filter Thread"); //$NON-NLS-1$
1128 this.filter = filter;
1129 }
1130
1131 @Override
1132 public void run() {
1133 if (fTrace == null) {
1134 return;
1135 }
1136 final int nbRequested = (int) (fTrace.getNbEvents() - fFilterCheckCount);
1137 if (nbRequested <= 0) {
1138 return;
1139 }
1140 request = new TmfDataRequest(ITmfEvent.class, (int) fFilterCheckCount,
1141 nbRequested, fTrace.getCacheSize(), ExecutionType.BACKGROUND) {
1142 @Override
1143 public void handleData(final ITmfEvent event) {
1144 super.handleData(event);
1145 if (request.isCancelled()) {
1146 return;
1147 }
1148 if (filter.matches(event)) {
1149 final long rank = fFilterCheckCount;
1150 final int index = (int) fFilterMatchCount;
1151 fFilterMatchCount++;
1152 fCache.storeEvent(event.clone(), rank, index);
1153 refreshTable();
1154 } else if ((fFilterCheckCount % 100) == 0) {
1155 refreshTable();
1156 }
1157 fFilterCheckCount++;
1158 }
1159 };
1160 ((ITmfDataProvider) fTrace).sendRequest(request);
1161 try {
1162 request.waitForCompletion();
1163 } catch (final InterruptedException e) {
1164 }
1165 refreshTable();
1166 synchronized (fFilterSyncObj) {
1167 fFilterThread = null;
1168 if (fFilterThreadResume) {
1169 fFilterThreadResume = false;
1170 fFilterThread = new FilterThread(filter);
1171 fFilterThread.start();
1172 }
1173 }
1174 }
1175
1176 public void refreshTable() {
1177 synchronized (syncObj) {
1178 if (refreshBusy) {
1179 refreshPending = true;
1180 return;
1181 }
1182 refreshBusy = true;
1183 }
1184 Display.getDefault().asyncExec(new Runnable() {
1185 @Override
1186 public void run() {
1187 if (request.isCancelled()) {
1188 return;
1189 }
1190 if (fTable.isDisposed()) {
1191 return;
1192 }
1193 fTable.setItemCount((int) fFilterMatchCount + 3); // +1 for header row, +2 for top and bottom filter status rows
1194 fTable.refresh();
1195 synchronized (syncObj) {
1196 refreshBusy = false;
1197 if (refreshPending) {
1198 refreshPending = false;
1199 refreshTable();
1200 }
1201 }
1202 }
1203 });
1204 }
1205
1206 public void cancel() {
1207 if (request != null) {
1208 request.cancel();
1209 }
1210 }
1211 }
1212
1213 protected void searchNext() {
1214 synchronized (fSearchSyncObj) {
1215 if (fSearchThread != null) {
1216 return;
1217 }
1218 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
1219 if (searchFilter == null) {
1220 return;
1221 }
1222 final int selectionIndex = fTable.getSelectionIndex();
1223 int startIndex;
1224 if (selectionIndex > 0) {
1225 startIndex = selectionIndex; // -1 for header row, +1 for next event
1226 } else {
1227 // header row is selected, start at top event
1228 startIndex = Math.max(0, fTable.getTopIndex() - 1); // -1 for header row
1229 }
1230 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1231 if (eventFilter != null)
1232 {
1233 startIndex = Math.max(0, startIndex - 1); // -1 for top filter status row
1234 }
1235 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.FORWARD);
1236 fSearchThread.schedule();
1237 }
1238 }
1239
1240 protected void searchPrevious() {
1241 synchronized (fSearchSyncObj) {
1242 if (fSearchThread != null) {
1243 return;
1244 }
1245 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
1246 if (searchFilter == null) {
1247 return;
1248 }
1249 final int selectionIndex = fTable.getSelectionIndex();
1250 int startIndex;
1251 if (selectionIndex > 0) {
1252 startIndex = selectionIndex - 2; // -1 for header row, -1 for previous event
1253 } else {
1254 // header row is selected, start at precedent of top event
1255 startIndex = fTable.getTopIndex() - 2; // -1 for header row, -1 for previous event
1256 }
1257 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1258 if (eventFilter != null)
1259 {
1260 startIndex = startIndex - 1; // -1 for top filter status row
1261 }
1262 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.BACKWARD);
1263 fSearchThread.schedule();
1264 }
1265 }
1266
1267 protected void stopSearchThread() {
1268 fPendingGotoRank = -1;
1269 synchronized (fSearchSyncObj) {
1270 if (fSearchThread != null) {
1271 fSearchThread.cancel();
1272 fSearchThread = null;
1273 }
1274 }
1275 }
1276
1277 protected class SearchThread extends Job {
1278 protected ITmfFilterTreeNode searchFilter;
1279 protected ITmfFilterTreeNode eventFilter;
1280 protected int startIndex;
1281 protected int direction;
1282 protected long rank;
1283 protected long foundRank = -1;
1284 protected TmfDataRequest request;
1285 private ITmfTimestamp foundTimestamp = null;
1286
1287 public SearchThread(final ITmfFilterTreeNode searchFilter, final ITmfFilterTreeNode eventFilter, final int startIndex,
1288 final long currentRank, final int direction) {
1289 super(Messages.TmfEventsTable_SearchingJobName);
1290 this.searchFilter = searchFilter;
1291 this.eventFilter = eventFilter;
1292 this.startIndex = startIndex;
1293 this.rank = currentRank;
1294 this.direction = direction;
1295 }
1296
1297 @Override
1298 protected IStatus run(final IProgressMonitor monitor) {
1299 if (fTrace == null) {
1300 return Status.OK_STATUS;
1301 }
1302 final Display display = Display.getDefault();
1303 if (startIndex < 0) {
1304 rank = (int) fTrace.getNbEvents() - 1;
1305 } else if (startIndex >= (fTable.getItemCount() - (eventFilter == null ? 1 : 3))) { // -1 for header row, -2 for top and bottom filter status rows
1306 rank = 0;
1307 } else {
1308 int idx = startIndex;
1309 while (foundRank == -1) {
1310 final CachedEvent event = fCache.peekEvent(idx);
1311 if (event == null) {
1312 break;
1313 }
1314 rank = event.rank;
1315 if (searchFilter.matches(event.event) && ((eventFilter == null) || eventFilter.matches(event.event))) {
1316 foundRank = event.rank;
1317 foundTimestamp = event.event.getTimestamp();
1318 break;
1319 }
1320 if (direction == Direction.FORWARD) {
1321 idx++;
1322 } else {
1323 idx--;
1324 }
1325 }
1326 if (foundRank == -1) {
1327 if (direction == Direction.FORWARD) {
1328 rank++;
1329 if (rank > (fTrace.getNbEvents() - 1)) {
1330 rank = 0;
1331 }
1332 } else {
1333 rank--;
1334 if (rank < 0) {
1335 rank = (int) fTrace.getNbEvents() - 1;
1336 }
1337 }
1338 }
1339 }
1340 final int startRank = (int) rank;
1341 boolean wrapped = false;
1342 while (!monitor.isCanceled() && (foundRank == -1) && (fTrace != null)) {
1343 int nbRequested = (direction == Direction.FORWARD ? Integer.MAX_VALUE : Math.min((int) rank + 1, fTrace.getCacheSize()));
1344 if (direction == Direction.BACKWARD) {
1345 rank = Math.max(0, rank - fTrace.getCacheSize() + 1);
1346 }
1347 request = new TmfDataRequest(ITmfEvent.class, (int) rank, nbRequested, fTrace.getCacheSize(), ExecutionType.BACKGROUND) {
1348 long currentRank = rank;
1349
1350 @Override
1351 public void handleData(final ITmfEvent event) {
1352 super.handleData(event);
1353 if (searchFilter.matches(event) && ((eventFilter == null) || eventFilter.matches(event))) {
1354 foundRank = currentRank;
1355 foundTimestamp = event.getTimestamp();
1356 if (direction == Direction.FORWARD) {
1357 done();
1358 return;
1359 }
1360 }
1361 currentRank++;
1362 }
1363 };
1364 ((ITmfDataProvider) fTrace).sendRequest(request);
1365 try {
1366 request.waitForCompletion();
1367 if (request.isCancelled()) {
1368 return Status.OK_STATUS;
1369 }
1370 } catch (final InterruptedException e) {
1371 synchronized (fSearchSyncObj) {
1372 fSearchThread = null;
1373 }
1374 return Status.OK_STATUS;
1375 }
1376 if (foundRank == -1) {
1377 if (direction == Direction.FORWARD) {
1378 if (rank == 0) {
1379 synchronized (fSearchSyncObj) {
1380 fSearchThread = null;
1381 }
1382 return Status.OK_STATUS;
1383 }
1384 nbRequested = (int) rank;
1385 rank = 0;
1386 wrapped = true;
1387 } else {
1388 rank--;
1389 if (rank < 0) {
1390 rank = (int) fTrace.getNbEvents() - 1;
1391 wrapped = true;
1392 }
1393 if ((rank <= startRank) && wrapped) {
1394 synchronized (fSearchSyncObj) {
1395 fSearchThread = null;
1396 }
1397 return Status.OK_STATUS;
1398 }
1399 }
1400 }
1401 }
1402 int index = (int) foundRank;
1403 if (eventFilter != null) {
1404 index = fCache.getFilteredEventIndex(foundRank);
1405 }
1406 final int selection = index + 1 + (eventFilter != null ? +1 : 0); // +1 for header row, +1 for top filter status row
1407
1408 display.asyncExec(new Runnable() {
1409 @Override
1410 public void run() {
1411 if (monitor.isCanceled()) {
1412 return;
1413 }
1414 if (fTable.isDisposed()) {
1415 return;
1416 }
1417 fTable.setSelection(selection);
1418 fSelectedRank = foundRank;
1419 fRawViewer.selectAndReveal(fSelectedRank);
1420 if (foundTimestamp != null) {
1421 broadcast(new TmfTimeSynchSignal(TmfEventsTable.this, foundTimestamp));
1422 }
1423 synchronized (fSearchSyncObj) {
1424 fSearchThread = null;
1425 }
1426 }
1427 });
1428 return Status.OK_STATUS;
1429 }
1430
1431 @Override
1432 protected void canceling() {
1433 request.cancel();
1434 synchronized (fSearchSyncObj) {
1435 fSearchThread = null;
1436 }
1437 }
1438 }
1439
1440 protected void createResources() {
1441 fGrayColor = fResourceManager.createColor(ColorUtil.blend(fTable.getBackground().getRGB(), fTable
1442 .getForeground().getRGB()));
1443 fGreenColor = fTable.getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN);
1444 fBoldFont = fResourceManager.createFont(FontDescriptor.createFrom(fTable.getFont()).setStyle(SWT.BOLD));
1445 }
1446
1447 protected void packColumns() {
1448 if (fPackDone) {
1449 return;
1450 }
1451 for (final TableColumn column : fTable.getColumns()) {
1452 final int headerWidth = column.getWidth();
1453 column.pack();
1454 if (column.getWidth() < headerWidth) {
1455 column.setWidth(headerWidth);
1456 }
1457 }
1458 fPackDone = true;
1459 }
1460
1461 /**
1462 * @param event
1463 * @return
1464 *
1465 * FIXME: Add support for column selection
1466 */
1467 protected ITmfEventField[] extractItemFields(final ITmfEvent event) {
1468 ITmfEventField[] fields = new TmfEventField[0];
1469 if (event != null) {
1470 final String timestamp = event.getTimestamp().toString();
1471 final String source = event.getSource();
1472 final String type = event.getType().getName();
1473 final String reference = event.getReference();
1474 final ITmfEventField content = event.getContent();
1475 final String value = (content.getValue() != null) ? content.getValue().toString() : content.toString();
1476 fields = new TmfEventField[] {
1477 new TmfEventField(ITmfEvent.EVENT_FIELD_TIMESTAMP, timestamp),
1478 new TmfEventField(ITmfEvent.EVENT_FIELD_SOURCE, source),
1479 new TmfEventField(ITmfEvent.EVENT_FIELD_TYPE, type),
1480 new TmfEventField(ITmfEvent.EVENT_FIELD_REFERENCE, reference),
1481 new TmfEventField(ITmfEvent.EVENT_FIELD_CONTENT, value)
1482 };
1483 }
1484 return fields;
1485 }
1486
1487 /**
1488 * Notify this table that is got the UI focus.
1489 */
1490 public void setFocus() {
1491 fTable.setFocus();
1492 }
1493
1494 /**
1495 * Assign a new trace to this event table.
1496 *
1497 * @param trace
1498 * The trace to assign to this event table
1499 * @param disposeOnClose
1500 * true if the trace should be disposed when the table is
1501 * disposed
1502 */
1503 public void setTrace(final ITmfTrace trace, final boolean disposeOnClose) {
1504 if ((fTrace != null) && fDisposeOnClose) {
1505 fTrace.dispose();
1506 }
1507 fTrace = trace;
1508 fPackDone = false;
1509 fSelectedRank = 0;
1510 fDisposeOnClose = disposeOnClose;
1511
1512 // Perform the updates on the UI thread
1513 fTable.getDisplay().syncExec(new Runnable() {
1514 @Override
1515 public void run() {
1516 fTable.removeAll();
1517 fCache.setTrace(fTrace); // Clear the cache
1518 if (fTrace != null) {
1519 if (!fTable.isDisposed() && (fTrace != null)) {
1520 if (fTable.getData(Key.FILTER_OBJ) == null) {
1521 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1522 } else {
1523 stopFilterThread();
1524 fFilterMatchCount = 0;
1525 fFilterCheckCount = 0;
1526 fTable.setItemCount(3); // +1 for header row, +2 for top and bottom filter status rows
1527 startFilterThread();
1528 }
1529 }
1530 fRawViewer.setTrace(fTrace);
1531 }
1532 }
1533 });
1534 }
1535
1536 // ------------------------------------------------------------------------
1537 // Event cache
1538 // ------------------------------------------------------------------------
1539
1540 /**
1541 * Notify that the event cache has been updated
1542 *
1543 * @param completed
1544 * Also notify if the populating of the cache is complete, or
1545 * not.
1546 */
1547 public void cacheUpdated(final boolean completed) {
1548 synchronized (fCacheUpdateSyncObj) {
1549 if (fCacheUpdateBusy) {
1550 fCacheUpdatePending = true;
1551 fCacheUpdateCompleted = completed;
1552 return;
1553 }
1554 fCacheUpdateBusy = true;
1555 }
1556 // Event cache is now updated. Perform update on the UI thread
1557 if (!fTable.isDisposed()) {
1558 fTable.getDisplay().asyncExec(new Runnable() {
1559 @Override
1560 public void run() {
1561 if (!fTable.isDisposed()) {
1562 fTable.refresh();
1563 packColumns();
1564 }
1565 if (completed) {
1566 populateCompleted();
1567 }
1568 synchronized (fCacheUpdateSyncObj) {
1569 fCacheUpdateBusy = false;
1570 if (fCacheUpdatePending) {
1571 fCacheUpdatePending = false;
1572 cacheUpdated(fCacheUpdateCompleted);
1573 }
1574 }
1575 }
1576 });
1577 }
1578 }
1579
1580 protected void populateCompleted() {
1581 // Nothing by default;
1582 }
1583
1584 // ------------------------------------------------------------------------
1585 // Bookmark handling
1586 // ------------------------------------------------------------------------
1587
1588 /**
1589 * Add a bookmark to this event table.
1590 *
1591 * @param bookmarksFile
1592 * The file to use for the bookmarks
1593 */
1594 public void addBookmark(final IFile bookmarksFile) {
1595 fBookmarksFile = bookmarksFile;
1596 final TableItem[] selection = fTable.getSelection();
1597 if (selection.length > 0) {
1598 final TableItem tableItem = selection[0];
1599 if (tableItem.getData(Key.RANK) != null) {
1600 final StringBuffer defaultMessage = new StringBuffer();
1601 for (int i = 0; i < fTable.getColumns().length; i++) {
1602 if (i > 0)
1603 {
1604 defaultMessage.append(", "); //$NON-NLS-1$
1605 }
1606 defaultMessage.append(tableItem.getText(i));
1607 }
1608 final InputDialog dialog = new InputDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
1609 Messages.TmfEventsTable_AddBookmarkDialogTitle, Messages.TmfEventsTable_AddBookmarkDialogText,
1610 defaultMessage.toString(), null);
1611 if (dialog.open() == Window.OK) {
1612 final String message = dialog.getValue();
1613 try {
1614 final IMarker bookmark = bookmarksFile.createMarker(IMarker.BOOKMARK);
1615 if (bookmark.exists()) {
1616 bookmark.setAttribute(IMarker.MESSAGE, message.toString());
1617 final long rank = (Long) tableItem.getData(Key.RANK);
1618 final int location = (int) rank;
1619 bookmark.setAttribute(IMarker.LOCATION, (Integer) location);
1620 fBookmarksMap.put(rank, bookmark.getId());
1621 fTable.refresh();
1622 }
1623 } catch (final CoreException e) {
1624 displayException(e);
1625 }
1626 }
1627 }
1628 }
1629
1630 }
1631
1632 /**
1633 * Remove a bookmark from this event table.
1634 *
1635 * @param bookmark
1636 * The bookmark to remove
1637 */
1638 public void removeBookmark(final IMarker bookmark) {
1639 for (final Entry<Long, Long> entry : fBookmarksMap.entrySet()) {
1640 if (entry.getValue().equals(bookmark.getId())) {
1641 fBookmarksMap.remove(entry.getKey());
1642 fTable.refresh();
1643 return;
1644 }
1645 }
1646 }
1647
1648 private void toggleBookmark(final long rank) {
1649 if (fBookmarksFile == null) {
1650 return;
1651 }
1652 if (fBookmarksMap.containsKey(rank)) {
1653 final Long markerId = fBookmarksMap.remove(rank);
1654 fTable.refresh();
1655 try {
1656 final IMarker bookmark = fBookmarksFile.findMarker(markerId);
1657 if (bookmark != null) {
1658 bookmark.delete();
1659 }
1660 } catch (final CoreException e) {
1661 displayException(e);
1662 }
1663 } else {
1664 addBookmark(fBookmarksFile);
1665 }
1666 }
1667
1668 /**
1669 * Refresh the bookmarks assigned to this trace, from the contents of a
1670 * bookmark file.
1671 *
1672 * @param bookmarksFile
1673 * The bookmark file to use
1674 */
1675 public void refreshBookmarks(final IFile bookmarksFile) {
1676 fBookmarksFile = bookmarksFile;
1677 if (bookmarksFile == null) {
1678 fBookmarksMap.clear();
1679 fTable.refresh();
1680 return;
1681 }
1682 try {
1683 fBookmarksMap.clear();
1684 for (final IMarker bookmark : bookmarksFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO)) {
1685 final int location = bookmark.getAttribute(IMarker.LOCATION, -1);
1686 if (location != -1) {
1687 final long rank = location;
1688 fBookmarksMap.put(rank, bookmark.getId());
1689 }
1690 }
1691 fTable.refresh();
1692 } catch (final CoreException e) {
1693 displayException(e);
1694 }
1695 }
1696
1697 @Override
1698 public void gotoMarker(final IMarker marker) {
1699 final int rank = marker.getAttribute(IMarker.LOCATION, -1);
1700 if (rank != -1) {
1701 int index = rank;
1702 if (fTable.getData(Key.FILTER_OBJ) != null) {
1703 index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
1704 } else if (rank >= fTable.getItemCount()) {
1705 fPendingGotoRank = rank;
1706 }
1707 fTable.setSelection(index + 1); // +1 for header row
1708 }
1709 }
1710
1711 // ------------------------------------------------------------------------
1712 // Listeners
1713 // ------------------------------------------------------------------------
1714
1715 /*
1716 * (non-Javadoc)
1717 *
1718 * @see org.eclipse.linuxtools.tmf.ui.views.colors.IColorSettingsListener#colorSettingsChanged(org.eclipse.linuxtools.tmf.ui.views.colors.ColorSetting[])
1719 */
1720 @Override
1721 public void colorSettingsChanged(final ColorSetting[] colorSettings) {
1722 fTable.refresh();
1723 }
1724
1725 @Override
1726 public void addEventsFilterListener(final ITmfEventsFilterListener listener) {
1727 if (!fEventsFilterListeners.contains(listener)) {
1728 fEventsFilterListeners.add(listener);
1729 }
1730 }
1731
1732 @Override
1733 public void removeEventsFilterListener(final ITmfEventsFilterListener listener) {
1734 fEventsFilterListeners.remove(listener);
1735 }
1736
1737 // ------------------------------------------------------------------------
1738 // Signal handlers
1739 // ------------------------------------------------------------------------
1740
1741 /**
1742 * Handler for the experiment updated signal.
1743 *
1744 * @param signal
1745 * The incoming signal
1746 */
1747 @TmfSignalHandler
1748 public void experimentUpdated(final TmfExperimentUpdatedSignal signal) {
1749 if ((signal.getExperiment() != fTrace) || fTable.isDisposed()) {
1750 return;
1751 }
1752 // Perform the refresh on the UI thread
1753 Display.getDefault().asyncExec(new Runnable() {
1754 @Override
1755 public void run() {
1756 if (!fTable.isDisposed() && (fTrace != null)) {
1757 if (fTable.getData(Key.FILTER_OBJ) == null) {
1758 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1759 if ((fPendingGotoRank != -1) && ((fPendingGotoRank + 1) < fTable.getItemCount())) { // +1 for header row
1760 fTable.setSelection((int) fPendingGotoRank + 1); // +1 for header row
1761 fPendingGotoRank = -1;
1762 }
1763 } else {
1764 startFilterThread();
1765 }
1766 }
1767 if (!fRawViewer.isDisposed() && (fTrace != null)) {
1768 fRawViewer.refreshEventCount();
1769 }
1770 }
1771 });
1772 }
1773
1774 /**
1775 * Handler for the trace updated signal
1776 *
1777 * @param signal
1778 * The incoming signal
1779 */
1780 @TmfSignalHandler
1781 public void traceUpdated(final TmfTraceUpdatedSignal signal) {
1782 if ((signal.getTrace() != fTrace) || fTable.isDisposed()) {
1783 return;
1784 }
1785 // Perform the refresh on the UI thread
1786 Display.getDefault().asyncExec(new Runnable() {
1787 @Override
1788 public void run() {
1789 if (!fTable.isDisposed() && (fTrace != null)) {
1790 if (fTable.getData(Key.FILTER_OBJ) == null) {
1791 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1792 if ((fPendingGotoRank != -1) && ((fPendingGotoRank + 1) < fTable.getItemCount())) { // +1 for header row
1793 fTable.setSelection((int) fPendingGotoRank + 1); // +1 for header row
1794 fPendingGotoRank = -1;
1795 }
1796 } else {
1797 startFilterThread();
1798 }
1799 }
1800 if (!fRawViewer.isDisposed() && (fTrace != null)) {
1801 fRawViewer.refreshEventCount();
1802 }
1803 }
1804 });
1805 }
1806
1807 /**
1808 * Handler for the time synch signal.
1809 *
1810 * @param signal
1811 * The incoming signal
1812 */
1813 @TmfSignalHandler
1814 public void currentTimeUpdated(final TmfTimeSynchSignal signal) {
1815 if ((signal.getSource() != this) && (fTrace != null) && (!fTable.isDisposed())) {
1816
1817 // Create a request for one event that will be queued after other ongoing requests. When this request is completed
1818 // do the work to select the actual event with the timestamp specified in the signal. This procedure prevents
1819 // the method fTrace.getRank() from interfering and delaying ongoing requests.
1820 final TmfDataRequest subRequest = new TmfDataRequest(ITmfEvent.class, 0, 1, ExecutionType.FOREGROUND) {
1821
1822 TmfTimestamp ts = new TmfTimestamp(signal.getCurrentTime());
1823
1824 @Override
1825 public void handleData(final ITmfEvent event) {
1826 super.handleData(event);
1827 }
1828
1829 @Override
1830 public void handleCompleted() {
1831 super.handleCompleted();
1832 if (fTrace == null) {
1833 return;
1834 }
1835
1836 // Verify if the event is within the trace range and adjust if necessary
1837 ITmfTimestamp timestamp = ts;
1838 if (timestamp.compareTo(fTrace.getStartTime(), true) == -1) {
1839 timestamp = fTrace.getStartTime();
1840 }
1841 if (timestamp.compareTo(fTrace.getEndTime(), true) == 1) {
1842 timestamp = fTrace.getEndTime();
1843 }
1844
1845 // Get the rank of the selected event in the table
1846 final ITmfContext context = fTrace.seekEvent(timestamp);
1847 final long rank = context.getRank();
1848 fSelectedRank = rank;
1849
1850 fTable.getDisplay().asyncExec(new Runnable() {
1851 @Override
1852 public void run() {
1853 // Return if table is disposed
1854 if (fTable.isDisposed()) {
1855 return;
1856 }
1857
1858 int index = (int) rank;
1859 if (fTable.isDisposed()) {
1860 return;
1861 }
1862 if (fTable.getData(Key.FILTER_OBJ) != null)
1863 {
1864 index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
1865 }
1866 fTable.setSelection(index + 1); // +1 for header row
1867 fRawViewer.selectAndReveal(rank);
1868 }
1869 });
1870 }
1871 };
1872
1873 ((ITmfDataProvider) fTrace).sendRequest(subRequest);
1874 }
1875 }
1876
1877 // ------------------------------------------------------------------------
1878 // Error handling
1879 // ------------------------------------------------------------------------
1880
1881 /**
1882 * Display an exception in a message box
1883 *
1884 * @param e the exception
1885 */
1886 private static void displayException(final Exception e) {
1887 final MessageBox mb = new MessageBox(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell());
1888 mb.setText(e.getClass().getName());
1889 mb.setMessage(e.getMessage());
1890 mb.open();
1891 }
1892
1893 }
This page took 0.075506 seconds and 5 git commands to generate.