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