Store event in TableItem data.
[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(event);
731 item.setData(Key.TIMESTAMP, new TmfTimestamp(event.getTimestamp()));
732 item.setData(Key.RANK, rank);
733
734 boolean bookmark = false;
735 final Long markerId = fBookmarksMap.get(rank);
736 if (markerId != null) {
737 bookmark = true;
738 try {
739 final IMarker marker = fBookmarksFile.findMarker(markerId);
740 item.setData(Key.BOOKMARK, marker.getAttribute(IMarker.MESSAGE));
741 } catch (final CoreException e) {
742 displayException(e);
743 }
744 } else {
745 item.setData(Key.BOOKMARK, null);
746 }
747
748 boolean searchMatch = false;
749 boolean searchNoMatch = false;
750 final ITmfFilter searchFilter = (ITmfFilter) fTable.getData(Key.SEARCH_OBJ);
751 if (searchFilter != null) {
752 if (searchFilter.matches(event)) {
753 searchMatch = true;
754 } else {
755 searchNoMatch = true;
756 }
757 }
758
759 final ColorSetting colorSetting = ColorSettingsManager.getColorSetting(event);
760 if (searchNoMatch) {
761 item.setForeground(colorSetting.getDimmedForegroundColor());
762 item.setBackground(colorSetting.getDimmedBackgroundColor());
763 } else {
764 item.setForeground(colorSetting.getForegroundColor());
765 item.setBackground(colorSetting.getBackgroundColor());
766 }
767
768 if (searchMatch) {
769 if (bookmark) {
770 item.setImage(SEARCH_MATCH_BOOKMARK_IMAGE);
771 } else {
772 item.setImage(SEARCH_MATCH_IMAGE);
773 }
774 } else if (bookmark) {
775 item.setImage(BOOKMARK_IMAGE);
776 } else {
777 item.setImage((Image) null);
778 }
779 }
780
781 protected void setHeaderRowItemData(final TableItem item) {
782 String txtKey = null;
783 if (fHeaderState == HeaderState.SEARCH) {
784 item.setImage(SEARCH_IMAGE);
785 txtKey = Key.SEARCH_TXT;
786 } else if (fHeaderState == HeaderState.FILTER) {
787 item.setImage(FILTER_IMAGE);
788 txtKey = Key.FILTER_TXT;
789 }
790 item.setForeground(fGrayColor);
791 for (int i = 0; i < fTable.getColumns().length; i++) {
792 final TableColumn column = fTable.getColumns()[i];
793 final String filter = (String) column.getData(txtKey);
794 if (filter == null) {
795 if (fHeaderState == HeaderState.SEARCH) {
796 item.setText(i, SEARCH_HINT);
797 } else if (fHeaderState == HeaderState.FILTER) {
798 item.setText(i, FILTER_HINT);
799 }
800 item.setForeground(i, fGrayColor);
801 item.setFont(i, fTable.getFont());
802 } else {
803 item.setText(i, filter);
804 item.setForeground(i, fGreenColor);
805 item.setFont(i, fBoldFont);
806 }
807 }
808 }
809
810 protected void setFilterStatusRowItemData(final TableItem item) {
811 for (int i = 0; i < fTable.getColumns().length; i++) {
812 if (i == 0) {
813 if ((fTrace == null) || (fFilterCheckCount == fTrace.getNbEvents())) {
814 item.setImage(FILTER_IMAGE);
815 } else {
816 item.setImage(STOP_IMAGE);
817 }
818 item.setText(0, fFilterMatchCount + "/" + fFilterCheckCount); //$NON-NLS-1$
819 } else {
820 item.setText(i, ""); //$NON-NLS-1$
821 }
822 }
823 item.setData(null);
824 item.setData(Key.TIMESTAMP, null);
825 item.setData(Key.RANK, null);
826 item.setForeground(null);
827 item.setBackground(null);
828 }
829
830 protected void createHeaderEditor() {
831 final TableEditor tableEditor = fTable.createTableEditor();
832 tableEditor.horizontalAlignment = SWT.LEFT;
833 tableEditor.verticalAlignment = SWT.CENTER;
834 tableEditor.grabHorizontal = true;
835 tableEditor.minimumWidth = 50;
836
837 // Handle the header row selection
838 fTable.addMouseListener(new MouseAdapter() {
839 int columnIndex;
840 TableColumn column;
841 TableItem item;
842
843 @Override
844 public void mouseDown(final MouseEvent event) {
845 if (event.button != 1) {
846 return;
847 }
848 // Identify the selected row
849 final Point point = new Point(event.x, event.y);
850 item = fTable.getItem(point);
851
852 // Header row selected
853 if ((item != null) && (fTable.indexOf(item) == 0)) {
854
855 // Icon selected
856 if (item.getImageBounds(0).contains(point)) {
857 if (fHeaderState == HeaderState.SEARCH) {
858 fHeaderState = HeaderState.FILTER;
859 } else if (fHeaderState == HeaderState.FILTER) {
860 fHeaderState = HeaderState.SEARCH;
861 }
862 fTable.refresh();
863 return;
864 }
865
866 // Identify the selected column
867 columnIndex = -1;
868 for (int i = 0; i < fTable.getColumns().length; i++) {
869 final Rectangle rect = item.getBounds(i);
870 if (rect.contains(point)) {
871 columnIndex = i;
872 break;
873 }
874 }
875
876 if (columnIndex == -1) {
877 return;
878 }
879
880 column = fTable.getColumns()[columnIndex];
881
882 String txtKey = null;
883 if (fHeaderState == HeaderState.SEARCH) {
884 txtKey = Key.SEARCH_TXT;
885 } else if (fHeaderState == HeaderState.FILTER) {
886 txtKey = Key.FILTER_TXT;
887 }
888
889 // The control that will be the editor must be a child of the Table
890 final Text newEditor = (Text) fTable.createTableEditorControl(Text.class);
891 final String headerString = (String) column.getData(txtKey);
892 if (headerString != null) {
893 newEditor.setText(headerString);
894 }
895 newEditor.addFocusListener(new FocusAdapter() {
896 @Override
897 public void focusLost(final FocusEvent e) {
898 final boolean changed = updateHeader(newEditor.getText());
899 if (changed) {
900 applyHeader();
901 }
902 }
903 });
904 newEditor.addKeyListener(new KeyAdapter() {
905 @Override
906 public void keyPressed(final KeyEvent e) {
907 if (e.character == SWT.CR) {
908 updateHeader(newEditor.getText());
909 applyHeader();
910 } else if (e.character == SWT.ESC) {
911 tableEditor.getEditor().dispose();
912 }
913 }
914 });
915 newEditor.selectAll();
916 newEditor.setFocus();
917 tableEditor.setEditor(newEditor, item, columnIndex);
918 }
919 }
920
921 /*
922 * returns true is value was changed
923 */
924 private boolean updateHeader(final String text) {
925 String objKey = null;
926 String txtKey = null;
927 if (fHeaderState == HeaderState.SEARCH) {
928 objKey = Key.SEARCH_OBJ;
929 txtKey = Key.SEARCH_TXT;
930 } else if (fHeaderState == HeaderState.FILTER) {
931 objKey = Key.FILTER_OBJ;
932 txtKey = Key.FILTER_TXT;
933 }
934 if (text.trim().length() > 0) {
935 try {
936 final String regex = TmfFilterMatchesNode.regexFix(text);
937 Pattern.compile(regex);
938 if (regex.equals(column.getData(txtKey))) {
939 tableEditor.getEditor().dispose();
940 return false;
941 }
942 final TmfFilterMatchesNode filter = new TmfFilterMatchesNode(null);
943 String fieldId = (String) column.getData(Key.FIELD_ID);
944 if (fieldId == null) {
945 fieldId = column.getText();
946 }
947 filter.setField(fieldId);
948 filter.setRegex(regex);
949 column.setData(objKey, filter);
950 column.setData(txtKey, regex);
951 } catch (final PatternSyntaxException ex) {
952 tableEditor.getEditor().dispose();
953 MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
954 ex.getDescription(), ex.getMessage());
955 return false;
956 }
957 } else {
958 if (column.getData(txtKey) == null) {
959 tableEditor.getEditor().dispose();
960 return false;
961 }
962 column.setData(objKey, null);
963 column.setData(txtKey, null);
964 }
965 return true;
966 }
967
968 private void applyHeader() {
969 if (fHeaderState == HeaderState.SEARCH) {
970 stopSearchThread();
971 final TmfFilterAndNode filter = new TmfFilterAndNode(null);
972 for (final TableColumn column : fTable.getColumns()) {
973 final Object filterObj = column.getData(Key.SEARCH_OBJ);
974 if (filterObj instanceof ITmfFilterTreeNode) {
975 filter.addChild((ITmfFilterTreeNode) filterObj);
976 }
977 }
978 if (filter.getChildrenCount() > 0) {
979 fTable.setData(Key.SEARCH_OBJ, filter);
980 fTable.refresh();
981 searchNext();
982 fireSearchApplied(filter);
983 } else {
984 fTable.setData(Key.SEARCH_OBJ, null);
985 fTable.refresh();
986 fireSearchApplied(null);
987 }
988 } else if (fHeaderState == HeaderState.FILTER) {
989 final TmfFilterAndNode filter = new TmfFilterAndNode(null);
990 for (final TableColumn column : fTable.getColumns()) {
991 final Object filterObj = column.getData(Key.FILTER_OBJ);
992 if (filterObj instanceof ITmfFilterTreeNode) {
993 filter.addChild((ITmfFilterTreeNode) filterObj);
994 }
995 }
996 if (filter.getChildrenCount() > 0) {
997 applyFilter(filter);
998 } else {
999 clearFilters();
1000 }
1001 }
1002
1003 tableEditor.getEditor().dispose();
1004 }
1005 });
1006
1007 fTable.addKeyListener(new KeyAdapter() {
1008 @Override
1009 public void keyPressed(final KeyEvent e) {
1010 e.doit = false;
1011 if (e.character == SWT.ESC) {
1012 stopFilterThread();
1013 stopSearchThread();
1014 fTable.refresh();
1015 } else if (e.character == SWT.DEL) {
1016 if (fHeaderState == HeaderState.SEARCH) {
1017 stopSearchThread();
1018 for (final TableColumn column : fTable.getColumns()) {
1019 column.setData(Key.SEARCH_OBJ, null);
1020 column.setData(Key.SEARCH_TXT, null);
1021 }
1022 fTable.setData(Key.SEARCH_OBJ, null);
1023 fTable.refresh();
1024 fireSearchApplied(null);
1025 } else if (fHeaderState == HeaderState.FILTER) {
1026 clearFilters();
1027 }
1028 } else if (e.character == SWT.CR) {
1029 if ((e.stateMask & SWT.SHIFT) == 0) {
1030 searchNext();
1031 } else {
1032 searchPrevious();
1033 }
1034 }
1035 }
1036 });
1037 }
1038
1039 protected void fireFilterApplied(final ITmfFilter filter) {
1040 for (final ITmfEventsFilterListener listener : fEventsFilterListeners) {
1041 listener.filterApplied(filter, fTrace);
1042 }
1043 }
1044
1045 protected void fireSearchApplied(final ITmfFilter filter) {
1046 for (final ITmfEventsFilterListener listener : fEventsFilterListeners) {
1047 listener.searchApplied(filter, fTrace);
1048 }
1049 }
1050
1051 protected void startFilterThread() {
1052 synchronized (fFilterSyncObj) {
1053 final ITmfFilterTreeNode filter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1054 if (fFilterThread == null || fFilterThread.filter != filter) {
1055 if (fFilterThread != null) {
1056 fFilterThread.cancel();
1057 fFilterThreadResume = false;
1058 }
1059 fFilterThread = new FilterThread(filter);
1060 fFilterThread.start();
1061 } else {
1062 fFilterThreadResume = true;
1063 }
1064 }
1065 }
1066
1067 protected void stopFilterThread() {
1068 synchronized (fFilterSyncObj) {
1069 if (fFilterThread != null) {
1070 fFilterThread.cancel();
1071 fFilterThread = null;
1072 fFilterThreadResume = false;
1073 }
1074 }
1075 }
1076
1077 /**
1078 * @since 1.1
1079 */
1080 protected void applyFilter(ITmfFilter filter) {
1081 stopFilterThread();
1082 stopSearchThread();
1083 fFilterMatchCount = 0;
1084 fFilterCheckCount = 0;
1085 fCache.applyFilter(filter);
1086 fTable.clearAll();
1087 fTable.setData(Key.FILTER_OBJ, filter);
1088 fTable.setItemCount(3); // +1 for header row, +2 for top and bottom filter status rows
1089 startFilterThread();
1090 fireFilterApplied(filter);
1091 }
1092
1093 protected void clearFilters() {
1094 if (fTable.getData(Key.FILTER_OBJ) == null) {
1095 return;
1096 }
1097 stopFilterThread();
1098 stopSearchThread();
1099 fCache.clearFilter();
1100 fTable.clearAll();
1101 for (final TableColumn column : fTable.getColumns()) {
1102 column.setData(Key.FILTER_OBJ, null);
1103 column.setData(Key.FILTER_TXT, null);
1104 }
1105 fTable.setData(Key.FILTER_OBJ, null);
1106 if (fTrace != null) {
1107 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1108 } else {
1109 fTable.setItemCount(1); // +1 for header row
1110 }
1111 fFilterMatchCount = 0;
1112 fFilterCheckCount = 0;
1113 if (fSelectedRank >= 0) {
1114 fTable.setSelection((int) fSelectedRank + 1); // +1 for header row
1115 } else {
1116 fTable.setSelection(0);
1117 }
1118 fireFilterApplied(null);
1119 }
1120
1121 protected class FilterThread extends Thread {
1122 private final ITmfFilterTreeNode filter;
1123 private TmfDataRequest request;
1124 private boolean refreshBusy = false;
1125 private boolean refreshPending = false;
1126 private final Object syncObj = new Object();
1127
1128 public FilterThread(final ITmfFilterTreeNode filter) {
1129 super("Filter Thread"); //$NON-NLS-1$
1130 this.filter = filter;
1131 }
1132
1133 @Override
1134 public void run() {
1135 if (fTrace == null) {
1136 return;
1137 }
1138 final int nbRequested = (int) (fTrace.getNbEvents() - fFilterCheckCount);
1139 if (nbRequested <= 0) {
1140 return;
1141 }
1142 request = new TmfDataRequest(ITmfEvent.class, (int) fFilterCheckCount,
1143 nbRequested, fTrace.getCacheSize(), ExecutionType.BACKGROUND) {
1144 @Override
1145 public void handleData(final ITmfEvent event) {
1146 super.handleData(event);
1147 if (request.isCancelled()) {
1148 return;
1149 }
1150 if (filter.matches(event)) {
1151 final long rank = fFilterCheckCount;
1152 final int index = (int) fFilterMatchCount;
1153 fFilterMatchCount++;
1154 fCache.storeEvent(event.clone(), rank, index);
1155 refreshTable();
1156 } else if ((fFilterCheckCount % 100) == 0) {
1157 refreshTable();
1158 }
1159 fFilterCheckCount++;
1160 }
1161 };
1162 ((ITmfDataProvider) fTrace).sendRequest(request);
1163 try {
1164 request.waitForCompletion();
1165 } catch (final InterruptedException e) {
1166 }
1167 refreshTable();
1168 synchronized (fFilterSyncObj) {
1169 fFilterThread = null;
1170 if (fFilterThreadResume) {
1171 fFilterThreadResume = false;
1172 fFilterThread = new FilterThread(filter);
1173 fFilterThread.start();
1174 }
1175 }
1176 }
1177
1178 public void refreshTable() {
1179 synchronized (syncObj) {
1180 if (refreshBusy) {
1181 refreshPending = true;
1182 return;
1183 }
1184 refreshBusy = true;
1185 }
1186 Display.getDefault().asyncExec(new Runnable() {
1187 @Override
1188 public void run() {
1189 if (request.isCancelled()) {
1190 return;
1191 }
1192 if (fTable.isDisposed()) {
1193 return;
1194 }
1195 fTable.setItemCount((int) fFilterMatchCount + 3); // +1 for header row, +2 for top and bottom filter status rows
1196 fTable.refresh();
1197 synchronized (syncObj) {
1198 refreshBusy = false;
1199 if (refreshPending) {
1200 refreshPending = false;
1201 refreshTable();
1202 }
1203 }
1204 }
1205 });
1206 }
1207
1208 public void cancel() {
1209 if (request != null) {
1210 request.cancel();
1211 }
1212 }
1213 }
1214
1215 protected void searchNext() {
1216 synchronized (fSearchSyncObj) {
1217 if (fSearchThread != null) {
1218 return;
1219 }
1220 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
1221 if (searchFilter == null) {
1222 return;
1223 }
1224 final int selectionIndex = fTable.getSelectionIndex();
1225 int startIndex;
1226 if (selectionIndex > 0) {
1227 startIndex = selectionIndex; // -1 for header row, +1 for next event
1228 } else {
1229 // header row is selected, start at top event
1230 startIndex = Math.max(0, fTable.getTopIndex() - 1); // -1 for header row
1231 }
1232 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1233 if (eventFilter != null)
1234 {
1235 startIndex = Math.max(0, startIndex - 1); // -1 for top filter status row
1236 }
1237 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.FORWARD);
1238 fSearchThread.schedule();
1239 }
1240 }
1241
1242 protected void searchPrevious() {
1243 synchronized (fSearchSyncObj) {
1244 if (fSearchThread != null) {
1245 return;
1246 }
1247 final ITmfFilterTreeNode searchFilter = (ITmfFilterTreeNode) fTable.getData(Key.SEARCH_OBJ);
1248 if (searchFilter == null) {
1249 return;
1250 }
1251 final int selectionIndex = fTable.getSelectionIndex();
1252 int startIndex;
1253 if (selectionIndex > 0) {
1254 startIndex = selectionIndex - 2; // -1 for header row, -1 for previous event
1255 } else {
1256 // header row is selected, start at precedent of top event
1257 startIndex = fTable.getTopIndex() - 2; // -1 for header row, -1 for previous event
1258 }
1259 final ITmfFilterTreeNode eventFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ);
1260 if (eventFilter != null)
1261 {
1262 startIndex = startIndex - 1; // -1 for top filter status row
1263 }
1264 fSearchThread = new SearchThread(searchFilter, eventFilter, startIndex, fSelectedRank, Direction.BACKWARD);
1265 fSearchThread.schedule();
1266 }
1267 }
1268
1269 protected void stopSearchThread() {
1270 fPendingGotoRank = -1;
1271 synchronized (fSearchSyncObj) {
1272 if (fSearchThread != null) {
1273 fSearchThread.cancel();
1274 fSearchThread = null;
1275 }
1276 }
1277 }
1278
1279 protected class SearchThread extends Job {
1280 protected ITmfFilterTreeNode searchFilter;
1281 protected ITmfFilterTreeNode eventFilter;
1282 protected int startIndex;
1283 protected int direction;
1284 protected long rank;
1285 protected long foundRank = -1;
1286 protected TmfDataRequest request;
1287
1288 public SearchThread(final ITmfFilterTreeNode searchFilter, final ITmfFilterTreeNode eventFilter, final int startIndex,
1289 final long currentRank, final int direction) {
1290 super(Messages.TmfEventsTable_SearchingJobName);
1291 this.searchFilter = searchFilter;
1292 this.eventFilter = eventFilter;
1293 this.startIndex = startIndex;
1294 this.rank = currentRank;
1295 this.direction = direction;
1296 }
1297
1298 @Override
1299 protected IStatus run(final IProgressMonitor monitor) {
1300 if (fTrace == null) {
1301 return Status.OK_STATUS;
1302 }
1303 final Display display = Display.getDefault();
1304 if (startIndex < 0) {
1305 rank = (int) fTrace.getNbEvents() - 1;
1306 } else if (startIndex >= (fTable.getItemCount() - (eventFilter == null ? 1 : 3))) { // -1 for header row, -2 for top and bottom filter status rows
1307 rank = 0;
1308 } else {
1309 int idx = startIndex;
1310 while (foundRank == -1) {
1311 final CachedEvent event = fCache.peekEvent(idx);
1312 if (event == null) {
1313 break;
1314 }
1315 rank = event.rank;
1316 if (searchFilter.matches(event.event) && ((eventFilter == null) || eventFilter.matches(event.event))) {
1317 foundRank = event.rank;
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 if (direction == Direction.FORWARD) {
1356 done();
1357 return;
1358 }
1359 }
1360 currentRank++;
1361 }
1362 };
1363 ((ITmfDataProvider) fTrace).sendRequest(request);
1364 try {
1365 request.waitForCompletion();
1366 if (request.isCancelled()) {
1367 return Status.OK_STATUS;
1368 }
1369 } catch (final InterruptedException e) {
1370 synchronized (fSearchSyncObj) {
1371 fSearchThread = null;
1372 }
1373 return Status.OK_STATUS;
1374 }
1375 if (foundRank == -1) {
1376 if (direction == Direction.FORWARD) {
1377 if (rank == 0) {
1378 synchronized (fSearchSyncObj) {
1379 fSearchThread = null;
1380 }
1381 return Status.OK_STATUS;
1382 }
1383 nbRequested = (int) rank;
1384 rank = 0;
1385 wrapped = true;
1386 } else {
1387 rank--;
1388 if (rank < 0) {
1389 rank = (int) fTrace.getNbEvents() - 1;
1390 wrapped = true;
1391 }
1392 if ((rank <= startRank) && wrapped) {
1393 synchronized (fSearchSyncObj) {
1394 fSearchThread = null;
1395 }
1396 return Status.OK_STATUS;
1397 }
1398 }
1399 }
1400 }
1401 int index = (int) foundRank;
1402 if (eventFilter != null) {
1403 index = fCache.getFilteredEventIndex(foundRank);
1404 }
1405 final int selection = index + 1 + (eventFilter != null ? +1 : 0); // +1 for header row, +1 for top filter status row
1406
1407 display.asyncExec(new Runnable() {
1408 @Override
1409 public void run() {
1410 if (monitor.isCanceled()) {
1411 return;
1412 }
1413 if (fTable.isDisposed()) {
1414 return;
1415 }
1416 fTable.setSelection(selection);
1417 fSelectedRank = foundRank;
1418 synchronized (fSearchSyncObj) {
1419 fSearchThread = null;
1420 }
1421 }
1422 });
1423 return Status.OK_STATUS;
1424 }
1425
1426 @Override
1427 protected void canceling() {
1428 request.cancel();
1429 synchronized (fSearchSyncObj) {
1430 fSearchThread = null;
1431 }
1432 }
1433 }
1434
1435 protected void createResources() {
1436 fGrayColor = fResourceManager.createColor(ColorUtil.blend(fTable.getBackground().getRGB(), fTable
1437 .getForeground().getRGB()));
1438 fGreenColor = fTable.getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN);
1439 fBoldFont = fResourceManager.createFont(FontDescriptor.createFrom(fTable.getFont()).setStyle(SWT.BOLD));
1440 }
1441
1442 protected void packColumns() {
1443 if (fPackDone) {
1444 return;
1445 }
1446 for (final TableColumn column : fTable.getColumns()) {
1447 final int headerWidth = column.getWidth();
1448 column.pack();
1449 if (column.getWidth() < headerWidth) {
1450 column.setWidth(headerWidth);
1451 }
1452 }
1453 fPackDone = true;
1454 }
1455
1456 /**
1457 * @param event
1458 * @return
1459 *
1460 * FIXME: Add support for column selection
1461 */
1462 protected ITmfEventField[] extractItemFields(final ITmfEvent event) {
1463 ITmfEventField[] fields = new TmfEventField[0];
1464 if (event != null) {
1465 final String timestamp = event.getTimestamp().toString();
1466 final String source = event.getSource();
1467 final String type = event.getType().getName();
1468 final String reference = event.getReference();
1469 final ITmfEventField content = event.getContent();
1470 final String value = (content.getValue() != null) ? content.getValue().toString() : content.toString();
1471 fields = new TmfEventField[] {
1472 new TmfEventField(ITmfEvent.EVENT_FIELD_TIMESTAMP, timestamp),
1473 new TmfEventField(ITmfEvent.EVENT_FIELD_SOURCE, source),
1474 new TmfEventField(ITmfEvent.EVENT_FIELD_TYPE, type),
1475 new TmfEventField(ITmfEvent.EVENT_FIELD_REFERENCE, reference),
1476 new TmfEventField(ITmfEvent.EVENT_FIELD_CONTENT, value)
1477 };
1478 }
1479 return fields;
1480 }
1481
1482 /**
1483 * Notify this table that is got the UI focus.
1484 */
1485 public void setFocus() {
1486 fTable.setFocus();
1487 }
1488
1489 /**
1490 * Assign a new trace to this event table.
1491 *
1492 * @param trace
1493 * The trace to assign to this event table
1494 * @param disposeOnClose
1495 * true if the trace should be disposed when the table is
1496 * disposed
1497 */
1498 public void setTrace(final ITmfTrace trace, final boolean disposeOnClose) {
1499 if ((fTrace != null) && fDisposeOnClose) {
1500 fTrace.dispose();
1501 }
1502 fTrace = trace;
1503 fPackDone = false;
1504 fSelectedRank = 0;
1505 fDisposeOnClose = disposeOnClose;
1506
1507 // Perform the updates on the UI thread
1508 fTable.getDisplay().syncExec(new Runnable() {
1509 @Override
1510 public void run() {
1511 fTable.removeAll();
1512 fCache.setTrace(fTrace); // Clear the cache
1513 if (fTrace != null) {
1514 if (!fTable.isDisposed() && (fTrace != null)) {
1515 if (fTable.getData(Key.FILTER_OBJ) == null) {
1516 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1517 } else {
1518 stopFilterThread();
1519 fFilterMatchCount = 0;
1520 fFilterCheckCount = 0;
1521 fTable.setItemCount(3); // +1 for header row, +2 for top and bottom filter status rows
1522 startFilterThread();
1523 }
1524 }
1525 fRawViewer.setTrace(fTrace);
1526 }
1527 }
1528 });
1529 }
1530
1531 // ------------------------------------------------------------------------
1532 // Event cache
1533 // ------------------------------------------------------------------------
1534
1535 /**
1536 * Notify that the event cache has been updated
1537 *
1538 * @param completed
1539 * Also notify if the populating of the cache is complete, or
1540 * not.
1541 */
1542 public void cacheUpdated(final boolean completed) {
1543 synchronized (fCacheUpdateSyncObj) {
1544 if (fCacheUpdateBusy) {
1545 fCacheUpdatePending = true;
1546 fCacheUpdateCompleted = completed;
1547 return;
1548 }
1549 fCacheUpdateBusy = true;
1550 }
1551 // Event cache is now updated. Perform update on the UI thread
1552 if (!fTable.isDisposed()) {
1553 fTable.getDisplay().asyncExec(new Runnable() {
1554 @Override
1555 public void run() {
1556 if (!fTable.isDisposed()) {
1557 fTable.refresh();
1558 packColumns();
1559 }
1560 if (completed) {
1561 populateCompleted();
1562 }
1563 synchronized (fCacheUpdateSyncObj) {
1564 fCacheUpdateBusy = false;
1565 if (fCacheUpdatePending) {
1566 fCacheUpdatePending = false;
1567 cacheUpdated(fCacheUpdateCompleted);
1568 }
1569 }
1570 }
1571 });
1572 }
1573 }
1574
1575 protected void populateCompleted() {
1576 // Nothing by default;
1577 }
1578
1579 // ------------------------------------------------------------------------
1580 // Bookmark handling
1581 // ------------------------------------------------------------------------
1582
1583 /**
1584 * Add a bookmark to this event table.
1585 *
1586 * @param bookmarksFile
1587 * The file to use for the bookmarks
1588 */
1589 public void addBookmark(final IFile bookmarksFile) {
1590 fBookmarksFile = bookmarksFile;
1591 final TableItem[] selection = fTable.getSelection();
1592 if (selection.length > 0) {
1593 final TableItem tableItem = selection[0];
1594 if (tableItem.getData(Key.RANK) != null) {
1595 final StringBuffer defaultMessage = new StringBuffer();
1596 for (int i = 0; i < fTable.getColumns().length; i++) {
1597 if (i > 0)
1598 {
1599 defaultMessage.append(", "); //$NON-NLS-1$
1600 }
1601 defaultMessage.append(tableItem.getText(i));
1602 }
1603 final InputDialog dialog = new InputDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
1604 Messages.TmfEventsTable_AddBookmarkDialogTitle, Messages.TmfEventsTable_AddBookmarkDialogText,
1605 defaultMessage.toString(), null);
1606 if (dialog.open() == Window.OK) {
1607 final String message = dialog.getValue();
1608 try {
1609 final IMarker bookmark = bookmarksFile.createMarker(IMarker.BOOKMARK);
1610 if (bookmark.exists()) {
1611 bookmark.setAttribute(IMarker.MESSAGE, message.toString());
1612 final long rank = (Long) tableItem.getData(Key.RANK);
1613 final int location = (int) rank;
1614 bookmark.setAttribute(IMarker.LOCATION, (Integer) location);
1615 fBookmarksMap.put(rank, bookmark.getId());
1616 fTable.refresh();
1617 }
1618 } catch (final CoreException e) {
1619 displayException(e);
1620 }
1621 }
1622 }
1623 }
1624
1625 }
1626
1627 /**
1628 * Remove a bookmark from this event table.
1629 *
1630 * @param bookmark
1631 * The bookmark to remove
1632 */
1633 public void removeBookmark(final IMarker bookmark) {
1634 for (final Entry<Long, Long> entry : fBookmarksMap.entrySet()) {
1635 if (entry.getValue().equals(bookmark.getId())) {
1636 fBookmarksMap.remove(entry.getKey());
1637 fTable.refresh();
1638 return;
1639 }
1640 }
1641 }
1642
1643 private void toggleBookmark(final long rank) {
1644 if (fBookmarksFile == null) {
1645 return;
1646 }
1647 if (fBookmarksMap.containsKey(rank)) {
1648 final Long markerId = fBookmarksMap.remove(rank);
1649 fTable.refresh();
1650 try {
1651 final IMarker bookmark = fBookmarksFile.findMarker(markerId);
1652 if (bookmark != null) {
1653 bookmark.delete();
1654 }
1655 } catch (final CoreException e) {
1656 displayException(e);
1657 }
1658 } else {
1659 addBookmark(fBookmarksFile);
1660 }
1661 }
1662
1663 /**
1664 * Refresh the bookmarks assigned to this trace, from the contents of a
1665 * bookmark file.
1666 *
1667 * @param bookmarksFile
1668 * The bookmark file to use
1669 */
1670 public void refreshBookmarks(final IFile bookmarksFile) {
1671 fBookmarksFile = bookmarksFile;
1672 if (bookmarksFile == null) {
1673 fBookmarksMap.clear();
1674 fTable.refresh();
1675 return;
1676 }
1677 try {
1678 fBookmarksMap.clear();
1679 for (final IMarker bookmark : bookmarksFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO)) {
1680 final int location = bookmark.getAttribute(IMarker.LOCATION, -1);
1681 if (location != -1) {
1682 final long rank = location;
1683 fBookmarksMap.put(rank, bookmark.getId());
1684 }
1685 }
1686 fTable.refresh();
1687 } catch (final CoreException e) {
1688 displayException(e);
1689 }
1690 }
1691
1692 @Override
1693 public void gotoMarker(final IMarker marker) {
1694 final int rank = marker.getAttribute(IMarker.LOCATION, -1);
1695 if (rank != -1) {
1696 int index = rank;
1697 if (fTable.getData(Key.FILTER_OBJ) != null) {
1698 index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
1699 } else if (rank >= fTable.getItemCount()) {
1700 fPendingGotoRank = rank;
1701 }
1702 fTable.setSelection(index + 1); // +1 for header row
1703 }
1704 }
1705
1706 // ------------------------------------------------------------------------
1707 // Listeners
1708 // ------------------------------------------------------------------------
1709
1710 /*
1711 * (non-Javadoc)
1712 *
1713 * @see org.eclipse.linuxtools.tmf.ui.views.colors.IColorSettingsListener#colorSettingsChanged(org.eclipse.linuxtools.tmf.ui.views.colors.ColorSetting[])
1714 */
1715 @Override
1716 public void colorSettingsChanged(final ColorSetting[] colorSettings) {
1717 fTable.refresh();
1718 }
1719
1720 @Override
1721 public void addEventsFilterListener(final ITmfEventsFilterListener listener) {
1722 if (!fEventsFilterListeners.contains(listener)) {
1723 fEventsFilterListeners.add(listener);
1724 }
1725 }
1726
1727 @Override
1728 public void removeEventsFilterListener(final ITmfEventsFilterListener listener) {
1729 fEventsFilterListeners.remove(listener);
1730 }
1731
1732 // ------------------------------------------------------------------------
1733 // Signal handlers
1734 // ------------------------------------------------------------------------
1735
1736 /**
1737 * Handler for the experiment updated signal.
1738 *
1739 * @param signal
1740 * The incoming signal
1741 */
1742 @TmfSignalHandler
1743 public void experimentUpdated(final TmfExperimentUpdatedSignal signal) {
1744 if ((signal.getExperiment() != fTrace) || fTable.isDisposed()) {
1745 return;
1746 }
1747 // Perform the refresh on the UI thread
1748 Display.getDefault().asyncExec(new Runnable() {
1749 @Override
1750 public void run() {
1751 if (!fTable.isDisposed() && (fTrace != null)) {
1752 if (fTable.getData(Key.FILTER_OBJ) == null) {
1753 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1754 if ((fPendingGotoRank != -1) && ((fPendingGotoRank + 1) < fTable.getItemCount())) { // +1 for header row
1755 fTable.setSelection((int) fPendingGotoRank + 1); // +1 for header row
1756 fPendingGotoRank = -1;
1757 }
1758 } else {
1759 startFilterThread();
1760 }
1761 }
1762 if (!fRawViewer.isDisposed() && (fTrace != null)) {
1763 fRawViewer.refreshEventCount();
1764 }
1765 }
1766 });
1767 }
1768
1769 /**
1770 * Handler for the trace updated signal
1771 *
1772 * @param signal
1773 * The incoming signal
1774 */
1775 @TmfSignalHandler
1776 public void traceUpdated(final TmfTraceUpdatedSignal signal) {
1777 if ((signal.getTrace() != fTrace) || fTable.isDisposed()) {
1778 return;
1779 }
1780 // Perform the refresh on the UI thread
1781 Display.getDefault().asyncExec(new Runnable() {
1782 @Override
1783 public void run() {
1784 if (!fTable.isDisposed() && (fTrace != null)) {
1785 if (fTable.getData(Key.FILTER_OBJ) == null) {
1786 fTable.setItemCount((int) fTrace.getNbEvents() + 1); // +1 for header row
1787 if ((fPendingGotoRank != -1) && ((fPendingGotoRank + 1) < fTable.getItemCount())) { // +1 for header row
1788 fTable.setSelection((int) fPendingGotoRank + 1); // +1 for header row
1789 fPendingGotoRank = -1;
1790 }
1791 } else {
1792 startFilterThread();
1793 }
1794 }
1795 if (!fRawViewer.isDisposed() && (fTrace != null)) {
1796 fRawViewer.refreshEventCount();
1797 }
1798 }
1799 });
1800 }
1801
1802 /**
1803 * Handler for the time synch signal.
1804 *
1805 * @param signal
1806 * The incoming signal
1807 */
1808 @TmfSignalHandler
1809 public void currentTimeUpdated(final TmfTimeSynchSignal signal) {
1810 if ((signal.getSource() != this) && (fTrace != null) && (!fTable.isDisposed())) {
1811
1812 // Create a request for one event that will be queued after other ongoing requests. When this request is completed
1813 // do the work to select the actual event with the timestamp specified in the signal. This procedure prevents
1814 // the method fTrace.getRank() from interfering and delaying ongoing requests.
1815 final TmfDataRequest subRequest = new TmfDataRequest(ITmfEvent.class, 0, 1, ExecutionType.FOREGROUND) {
1816
1817 TmfTimestamp ts = new TmfTimestamp(signal.getCurrentTime());
1818
1819 @Override
1820 public void handleData(final ITmfEvent event) {
1821 super.handleData(event);
1822 }
1823
1824 @Override
1825 public void handleCompleted() {
1826 super.handleCompleted();
1827 if (fTrace == null) {
1828 return;
1829 }
1830
1831 // Verify if the event is within the trace range and adjust if necessary
1832 ITmfTimestamp timestamp = ts;
1833 if (timestamp.compareTo(fTrace.getStartTime(), true) == -1) {
1834 timestamp = fTrace.getStartTime();
1835 }
1836 if (timestamp.compareTo(fTrace.getEndTime(), true) == 1) {
1837 timestamp = fTrace.getEndTime();
1838 }
1839
1840 // Get the rank of the selected event in the table
1841 final ITmfContext context = fTrace.seekEvent(timestamp);
1842 final long rank = context.getRank();
1843 fSelectedRank = rank;
1844
1845 fTable.getDisplay().asyncExec(new Runnable() {
1846 @Override
1847 public void run() {
1848 // Return if table is disposed
1849 if (fTable.isDisposed()) {
1850 return;
1851 }
1852
1853 int index = (int) rank;
1854 if (fTable.isDisposed()) {
1855 return;
1856 }
1857 if (fTable.getData(Key.FILTER_OBJ) != null)
1858 {
1859 index = fCache.getFilteredEventIndex(rank) + 1; // +1 for top filter status row
1860 }
1861 fTable.setSelection(index + 1); // +1 for header row
1862 fRawViewer.selectAndReveal(rank);
1863 }
1864 });
1865 }
1866 };
1867
1868 ((ITmfDataProvider) fTrace).sendRequest(subRequest);
1869 }
1870 }
1871
1872 // ------------------------------------------------------------------------
1873 // Error handling
1874 // ------------------------------------------------------------------------
1875
1876 /**
1877 * Display an exception in a message box
1878 *
1879 * @param e the exception
1880 */
1881 private static void displayException(final Exception e) {
1882 final MessageBox mb = new MessageBox(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell());
1883 mb.setText(e.getClass().getName());
1884 mb.setMessage(e.getMessage());
1885 mb.open();
1886 }
1887
1888 }
This page took 0.074202 seconds and 5 git commands to generate.