2011-01-12 Bernd Hufmann <bhufmann@gmail.com> Fix for Bug 333606
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / viewers / events / TmfEventsTable.java
1 /*******************************************************************************
2 * Copyright (c) 2010 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 *******************************************************************************/
14
15 package org.eclipse.linuxtools.tmf.ui.viewers.events;
16
17 import org.eclipse.core.runtime.IProgressMonitor;
18 import org.eclipse.core.runtime.IStatus;
19 import org.eclipse.core.runtime.Status;
20 import org.eclipse.core.runtime.jobs.Job;
21 import org.eclipse.linuxtools.tmf.component.ITmfDataProvider;
22 import org.eclipse.linuxtools.tmf.component.TmfComponent;
23 import org.eclipse.linuxtools.tmf.event.TmfEvent;
24 import org.eclipse.linuxtools.tmf.event.TmfTimestamp;
25 import org.eclipse.linuxtools.tmf.experiment.TmfExperiment;
26 import org.eclipse.linuxtools.tmf.request.ITmfDataRequest.ExecutionType;
27 import org.eclipse.linuxtools.tmf.request.TmfDataRequest;
28 import org.eclipse.linuxtools.tmf.signal.TmfExperimentUpdatedSignal;
29 import org.eclipse.linuxtools.tmf.signal.TmfRangeSynchSignal;
30 import org.eclipse.linuxtools.tmf.signal.TmfSignalHandler;
31 import org.eclipse.linuxtools.tmf.signal.TmfTimeSynchSignal;
32 import org.eclipse.linuxtools.tmf.signal.TmfTraceUpdatedSignal;
33 import org.eclipse.linuxtools.tmf.trace.ITmfTrace;
34 import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin;
35 import org.eclipse.linuxtools.tmf.ui.widgets.ColumnData;
36 import org.eclipse.linuxtools.tmf.ui.widgets.TmfVirtualTable;
37 import org.eclipse.swt.SWT;
38 import org.eclipse.swt.events.SelectionAdapter;
39 import org.eclipse.swt.events.SelectionEvent;
40 import org.eclipse.swt.layout.GridData;
41 import org.eclipse.swt.widgets.Composite;
42 import org.eclipse.swt.widgets.Event;
43 import org.eclipse.swt.widgets.Listener;
44 import org.eclipse.swt.widgets.TableColumn;
45 import org.eclipse.swt.widgets.TableItem;
46
47 /**
48 * <b><u>TmfEventsTable</u></b>
49 */
50 public class TmfEventsTable extends TmfComponent {
51
52 // ------------------------------------------------------------------------
53 // Table data
54 // ------------------------------------------------------------------------
55
56 protected TmfVirtualTable fTable;
57 protected ITmfTrace fTrace;
58 protected boolean fPackDone = false;
59
60 // Table column names
61 static private final String[] COLUMN_NAMES = new String[] {
62 Messages.TmfEventsTable_TimestampColumnHeader,
63 Messages.TmfEventsTable_SourceColumnHeader,
64 Messages.TmfEventsTable_TypeColumnHeader,
65 Messages.TmfEventsTable_ReferenceColumnHeader,
66 Messages.TmfEventsTable_ContentColumnHeader
67 };
68
69 static private ColumnData[] COLUMN_DATA = new ColumnData[] {
70 new ColumnData(COLUMN_NAMES[0], 100, SWT.LEFT),
71 new ColumnData(COLUMN_NAMES[1], 100, SWT.LEFT),
72 new ColumnData(COLUMN_NAMES[2], 100, SWT.LEFT),
73 new ColumnData(COLUMN_NAMES[3], 100, SWT.LEFT),
74 new ColumnData(COLUMN_NAMES[4], 100, SWT.LEFT)
75 };
76
77 // ------------------------------------------------------------------------
78 // Event cache
79 // ------------------------------------------------------------------------
80
81 private final int fCacheSize;
82 private TmfEvent[] fCache;
83 private int fCacheStartIndex = 0;
84 private int fCacheEndIndex = 0;
85
86 private boolean fDisposeOnClose;
87
88 // ------------------------------------------------------------------------
89 // Constructor
90 // ------------------------------------------------------------------------
91
92 public TmfEventsTable(Composite parent, int cacheSize) {
93 this(parent, cacheSize, COLUMN_DATA);
94 }
95
96 public TmfEventsTable(Composite parent, int cacheSize, ColumnData[] columnData) {
97 super("TmfEventsTable"); //$NON-NLS-1$
98
99 fCacheSize = cacheSize;
100 fCache = new TmfEvent[fCacheSize];
101
102 // Create a virtual table
103 final int style = SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER;
104 fTable = new TmfVirtualTable(parent, style);
105
106 // Set the table layout
107 GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
108 fTable.setLayoutData(layoutData);
109
110 // Some cosmetic enhancements
111 fTable.setHeaderVisible(true);
112 fTable.setLinesVisible(true);
113
114 // Set the columns
115 setColumnHeaders(columnData);
116
117 // Handle the table item requests
118 fTable.addSelectionListener(new SelectionAdapter() {
119 @Override
120 public void widgetSelected(SelectionEvent e) {
121 TmfTimestamp ts = (TmfTimestamp) fTable.getSelection()[0].getData();
122 broadcast(new TmfTimeSynchSignal(fTable, ts));
123 }
124 });
125
126 // Handle the table item requests
127 fTable.addListener(SWT.SetData, new Listener() {
128
129 @Override
130 public void handleEvent(Event event) {
131
132 final TableItem item = (TableItem) event.item;
133 final int index = fTable.indexOf(item);
134
135 // If available, return the cached data
136 if ((index >= fCacheStartIndex) && (index < fCacheEndIndex)) {
137 int i = index - fCacheStartIndex;
138 item.setText(extractItemFields(fCache[i]));
139 item.setData(new TmfTimestamp(fCache[i].getTimestamp()));
140 return;
141 }
142
143 // Else, fill the cache asynchronously (and off the UI thread)
144 populateCache(index);
145 }
146 });
147
148 fTable.setItemCount(0);
149 }
150
151 @Override
152 public void dispose() {
153 fTable.dispose();
154 if (fTrace != null && fDisposeOnClose) {
155 fTrace.dispose();
156 }
157 super.dispose();
158 }
159
160 public TmfVirtualTable getTable() {
161 return fTable;
162 }
163
164 /**
165 * @param table
166 *
167 * FIXME: Add support for column selection
168 */
169 protected void setColumnHeaders(ColumnData[] columnData) {
170 fTable.setColumnHeaders(columnData);
171 }
172
173 protected void packColumns() {
174 if (fPackDone) return;
175 for (TableColumn column : fTable.getColumns()) {
176 int headerWidth = column.getWidth();
177 column.pack();
178 if (column.getWidth() < headerWidth) {
179 column.setWidth(headerWidth);
180 }
181 }
182 fPackDone = true;
183 }
184
185 /**
186 * @param event
187 * @return
188 *
189 * FIXME: Add support for column selection
190 */
191 protected String[] extractItemFields(TmfEvent event) {
192 String[] fields = new String[0];
193 if (event != null) {
194 fields = new String[] {
195 new Long(event.getTimestamp().getValue()).toString(),
196 event.getSource().getSourceId().toString(),
197 event.getType().getTypeId().toString(),
198 event.getReference().getReference().toString(),
199 event.getContent().toString()
200 };
201 }
202 return fields;
203 }
204
205 public void setFocus() {
206 fTable.setFocus();
207 }
208
209 /**
210 * @param trace
211 * @param disposeOnClose true if the trace should be disposed when the table is disposed
212 */
213 public void setTrace(ITmfTrace trace, boolean disposeOnClose) {
214 if (fTrace != null && fDisposeOnClose) {
215 fTrace.dispose();
216 }
217 fTrace = trace;
218 fDisposeOnClose = disposeOnClose;
219
220 // Perform the updates on the UI thread
221 fTable.getDisplay().syncExec(new Runnable() {
222 @Override
223 public void run() {
224 //fTable.setSelection(0);
225 fTable.removeAll();
226 fCacheStartIndex = fCacheEndIndex = 0; // Clear the cache
227
228 if (!fTable.isDisposed() && fTrace != null) {
229 //int nbEvents = (int) fTrace.getNbEvents();
230 //fTable.setItemCount((nbEvents > 100) ? nbEvents : 100);
231 fTable.setItemCount((int) fTrace.getNbEvents());
232 }
233 }
234 });
235 }
236
237 // ------------------------------------------------------------------------
238 // Signal handlers
239 // ------------------------------------------------------------------------
240
241 @TmfSignalHandler
242 public void experimentUpdated(TmfExperimentUpdatedSignal signal) {
243 if ((signal.getExperiment() != fTrace) || fTable.isDisposed()) return;
244 // Perform the refresh on the UI thread
245 fTable.getDisplay().asyncExec(new Runnable() {
246 @Override
247 public void run() {
248 if (!fTable.isDisposed() && fTrace != null) {
249 fTable.setItemCount((int) fTrace.getNbEvents());
250 fTable.refresh();
251 }
252 }
253 });
254 }
255
256 @TmfSignalHandler
257 public void traceUpdated(TmfTraceUpdatedSignal signal) {
258 if ((signal.getTrace() != fTrace ) || fTable.isDisposed()) return;
259 // Perform the refresh on the UI thread
260 fTable.getDisplay().asyncExec(new Runnable() {
261 @Override
262 public void run() {
263 if (!fTable.isDisposed() && fTrace != null) {
264 //int nbEvents = (int) fTrace.getNbEvents();
265 //fTable.setItemCount((nbEvents > 100) ? nbEvents : 100);
266 fTable.setItemCount((int) fTrace.getNbEvents());
267 }
268 }
269 });
270 }
271
272 private boolean fRefreshPending = false;
273 @TmfSignalHandler
274 public synchronized void rangeSynched(TmfRangeSynchSignal signal) {
275 if (!fRefreshPending && !fTable.isDisposed()) {
276 // Perform the refresh on the UI thread
277 fRefreshPending = true;
278 fTable.getDisplay().asyncExec(new Runnable() {
279 @Override
280 public void run() {
281 fRefreshPending = false;
282 if (!fTable.isDisposed() && fTrace != null) {
283 fTable.setItemCount((int) fTrace.getNbEvents());
284 }
285 }
286 });
287 }
288 }
289
290 @TmfSignalHandler
291 public void currentTimeUpdated(final TmfTimeSynchSignal signal) {
292 if ((signal.getSource() != fTable) && (fTrace != null) && (!fTable.isDisposed())) {
293
294 // Create a request for one event that will be queued after other ongoing requests. When this request is completed
295 // do the work to select the actual event with the timestamp specified in the signal. This procedure prevents
296 // the method fTrace.getRank() from interfering and delaying ongoing requests.
297 final TmfDataRequest<TmfEvent> subRequest = new TmfDataRequest<TmfEvent>(TmfEvent.class, 0, 1, ExecutionType.FOREGROUND) {
298
299 @Override
300 public void handleData(TmfEvent event) {
301 super.handleData(event);
302 }
303
304 @Override
305 public void handleCompleted() {
306
307 // Verify if event is within the trace range
308 final TmfTimestamp timestamp[] = new TmfTimestamp[1];
309 timestamp[0] = signal.getCurrentTime();
310 if (timestamp[0].compareTo(fTrace.getStartTime(), true) == -1) {
311 timestamp[0] = fTrace.getStartTime();
312 }
313 if (timestamp[0].compareTo(fTrace.getEndTime(), true) == 1) {
314 timestamp[0] = fTrace.getEndTime();
315 }
316
317 // Get the rank for the event selection in the table
318 final int index = (int) fTrace.getRank(timestamp[0]);
319
320 fTable.getDisplay().asyncExec(new Runnable() {
321 @Override
322 public void run() {
323 // Return if table is disposed
324 if (fTable.isDisposed()) return;
325
326 fTable.setSelection(index);
327
328 // If index is in cache, then notify about updated selection.
329 // Otherwise it's done after fetching the relevant events from the trace
330 if ((index >= fCacheStartIndex) && (index < fCacheEndIndex)) {
331 // Use the timestamp in signal to broadcast to avoid moving the selection
332 // at the source of the signal
333 fTable.notifyUpdatedSelection(timestamp[0]);
334 }
335
336 // The timestamp might not correspond to an actual event
337 // and the selection will point to the next experiment event.
338 // But we would like to display both the event before and
339 // after the selected timestamp.
340 // This works fine by default except when the selected event
341 // is the top displayed event. The following ensures that we
342 // always see both events.
343 if ((index > 0) && (index == fTable.getTopIndex())) {
344 fTable.setTopIndex(index - 1);
345 }
346 }
347 });
348 super.handleCompleted();
349 }
350 };
351
352 @SuppressWarnings("unchecked")
353 TmfExperiment<TmfEvent> experiment = (TmfExperiment<TmfEvent>)TmfExperiment.getCurrentExperiment();
354 if (experiment != null) {
355 experiment.sendRequest(subRequest);
356 }
357 }
358 }
359
360 // ------------------------------------------------------------------------
361 // Event cache population
362 // ------------------------------------------------------------------------
363
364 // The event fetching job
365 private Job job;
366 private synchronized void populateCache(final int index) {
367
368 /* Check if the current job will fetch the requested event:
369 * 1. The job must exist
370 * 2. It must be running (i.e. not completed)
371 * 3. The requested index must be within the cache range
372 *
373 * If the job meets these conditions, we simply exit.
374 * Otherwise, we create a new job but we might have to cancel
375 * an existing job for an obsolete range.
376 */
377 if (job != null) {
378 if (job.getState() != Job.NONE) {
379 if (index >= fCacheStartIndex && index < (fCacheStartIndex + fCacheSize)) {
380 return;
381 }
382 // The new index is out of the requested range
383 // Kill the job and start a new one
384 job.cancel();
385 }
386 }
387
388 fCacheStartIndex = index;
389 fCacheEndIndex = index;
390
391 job = new Job("Fetching Events") { //$NON-NLS-1$
392 @Override
393 @SuppressWarnings("unchecked")
394 protected IStatus run(final IProgressMonitor monitor) {
395
396 TmfDataRequest<TmfEvent> request = new TmfDataRequest<TmfEvent>(TmfEvent.class, index, fCacheSize) {
397 private int count = 0;
398 @Override
399 public void handleData(TmfEvent event) {
400 // If the job is canceled, cancel the request so waitForCompletion() will unlock
401 if (monitor.isCanceled()) {
402 cancel();
403 return;
404 }
405 super.handleData(event);
406 if (event != null) {
407 fCache[count++] = event.clone();
408 fCacheEndIndex++; // TODO: Need to protect this??
409 }
410 }
411 };
412
413 ((ITmfDataProvider<TmfEvent>) fTrace).sendRequest(request);
414 try {
415 request.waitForCompletion();
416 } catch (InterruptedException e) {
417 e.printStackTrace();
418 }
419
420 // Event cache is now updated. Perform update on the UI thread
421 if (!fTable.isDisposed() && !monitor.isCanceled()) {
422 fTable.getDisplay().asyncExec(new Runnable() {
423 @Override
424 public void run() {
425 if (!fTable.isDisposed()) {
426 fTable.refresh();
427 fTable.notifyUpdatedSelection();
428 }
429 }
430 });
431 }
432
433 // Flag the UI thread that the cache is ready
434 if (monitor.isCanceled()) {
435 return new Status(IStatus.CANCEL, TmfUiPlugin.PLUGIN_ID, "Canceled"); //$NON-NLS-1$
436 }
437 else {
438 return new Status(IStatus.OK, TmfUiPlugin.PLUGIN_ID, "Completed"); //$NON-NLS-1$
439 }
440 }
441 };
442 job.setPriority(Job.SHORT);
443 job.schedule();
444 }
445 }
This page took 0.040336 seconds and 6 git commands to generate.