Commit | Line | Data |
---|---|---|
9ccc6d01 FC |
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 | * Matthew Khouzam - Initial API and implementation | |
11 | * Francois Chouinard - Refactoring, slider support, bug fixing | |
12 | ******************************************************************************/ | |
13 | ||
14 | package org.eclipse.linuxtools.tmf.ui.widgets; | |
15 | ||
2cc874d6 FC |
16 | import org.eclipse.linuxtools.tmf.event.TmfTimestamp; |
17 | import org.eclipse.linuxtools.tmf.signal.TmfSignalManager; | |
18 | import org.eclipse.linuxtools.tmf.signal.TmfTimeSynchSignal; | |
9ccc6d01 FC |
19 | import org.eclipse.swt.SWT; |
20 | import org.eclipse.swt.events.ControlAdapter; | |
21 | import org.eclipse.swt.events.ControlEvent; | |
22 | import org.eclipse.swt.events.KeyEvent; | |
23 | import org.eclipse.swt.events.KeyListener; | |
24 | import org.eclipse.swt.events.MouseEvent; | |
25 | import org.eclipse.swt.events.MouseWheelListener; | |
26 | import org.eclipse.swt.events.SelectionAdapter; | |
27 | import org.eclipse.swt.events.SelectionEvent; | |
9ccc6d01 FC |
28 | import org.eclipse.swt.layout.GridData; |
29 | import org.eclipse.swt.layout.GridLayout; | |
30 | import org.eclipse.swt.widgets.Composite; | |
31 | import org.eclipse.swt.widgets.Event; | |
32 | import org.eclipse.swt.widgets.Listener; | |
33 | import org.eclipse.swt.widgets.Slider; | |
34 | import org.eclipse.swt.widgets.Table; | |
35 | import org.eclipse.swt.widgets.TableColumn; | |
36 | import org.eclipse.swt.widgets.TableItem; | |
37 | ||
38 | /** | |
39 | * <b><u>TmfVirtualTable</u></b> | |
40 | * <p> | |
41 | * TmfVirtualTable allows for the tabular display of arbitrarily large data sets | |
42 | * (well, up to Integer.MAX_VALUE or ~2G rows). | |
43 | * | |
44 | * It is essentially a Composite of Table and Slider, where the number of rows | |
45 | * in the table is set to fill the table display area. The slider is rank-based. | |
46 | * | |
47 | * It differs from Table with the VIRTUAL style flag where an empty entry is | |
48 | * created for each virtual row. This does not scale well for very large data sets. | |
49 | */ | |
50 | public class TmfVirtualTable extends Composite { | |
51 | ||
52 | // The table | |
2cc874d6 FC |
53 | private Table fTable; |
54 | private int fTableRows = 0; // Number of table rows | |
55 | private boolean fPartialRowVisible = false; // Indicates that a row is partially displayed | |
56 | private int fSelectedRow = 0; // Currently selected row in the table | |
9ccc6d01 | 57 | |
2cc874d6 FC |
58 | private int fTableTopEventRank = 0; // Global rank of the first entry displayed |
59 | private int fSelectedEventRank = 0; // Global rank of the selected event | |
60 | ||
61 | private TableItem fSelectedItems[] = new TableItem[1]; | |
9ccc6d01 | 62 | private int fTableItemCount = 0; |
9ccc6d01 FC |
63 | private TableItem fTableItems[]; |
64 | ||
65 | // The slider | |
2cc874d6 | 66 | private Slider fSlider; |
9ccc6d01 FC |
67 | |
68 | // ------------------------------------------------------------------------ | |
69 | // Constructor | |
70 | // ------------------------------------------------------------------------ | |
71 | ||
72 | /** | |
73 | * @param parent | |
74 | * @param style | |
75 | */ | |
76 | public TmfVirtualTable(Composite parent, int style) { | |
77 | super(parent, style | SWT.BORDER & (~SWT.H_SCROLL) & (~SWT.V_SCROLL)); | |
78 | ||
79 | // Create the controls | |
2cc874d6 | 80 | createTable(style); |
9ccc6d01 FC |
81 | createSlider(); |
82 | ||
83 | // Set the layout | |
84 | GridLayout gridLayout = new GridLayout(); | |
85 | gridLayout.numColumns = 2; | |
86 | gridLayout.horizontalSpacing = 0; | |
2cc874d6 FC |
87 | gridLayout.verticalSpacing = 0; |
88 | gridLayout.marginWidth = 0; | |
89 | gridLayout.marginHeight = 0; | |
9ccc6d01 FC |
90 | setLayout(gridLayout); |
91 | ||
92 | GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true); | |
93 | fTable.setLayoutData(tableGridData); | |
94 | ||
95 | GridData sliderGridData = new GridData(SWT.FILL, SWT.FILL, false, true); | |
96 | fSlider.setLayoutData(sliderGridData); | |
97 | ||
98 | // Add the listeners | |
2cc874d6 | 99 | fTable.addMouseWheelListener(new MouseWheelListener() { |
d4011df2 | 100 | @Override |
9ccc6d01 | 101 | public void mouseScrolled(MouseEvent event) { |
2cc874d6 FC |
102 | fTableTopEventRank -= event.count; |
103 | if (fTableTopEventRank < 0) { | |
104 | fTableTopEventRank = 0; | |
105 | } | |
106 | int latestFirstRowOffset = fTableItemCount - fTableRows; | |
107 | if (fTableTopEventRank > latestFirstRowOffset) { | |
108 | fTableTopEventRank = latestFirstRowOffset; | |
9ccc6d01 | 109 | } |
2cc874d6 FC |
110 | |
111 | fSlider.setSelection(fTableTopEventRank); | |
112 | refreshTable(); | |
9ccc6d01 FC |
113 | } |
114 | }); | |
115 | ||
116 | addControlListener(new ControlAdapter() { | |
117 | @Override | |
118 | public void controlResized(ControlEvent event) { | |
119 | resize(); | |
120 | } | |
121 | }); | |
122 | ||
123 | // And display | |
124 | refresh(); | |
125 | } | |
126 | ||
127 | // ------------------------------------------------------------------------ | |
128 | // Table handling | |
129 | // ------------------------------------------------------------------------ | |
130 | ||
131 | /** | |
132 | * Create the table and add listeners | |
133 | */ | |
2cc874d6 | 134 | private void createTable(int style) { |
9ccc6d01 | 135 | |
2cc874d6 | 136 | // int tableStyle = SWT.NO_SCROLL | SWT.H_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION; |
9ccc6d01 FC |
137 | int tableStyle = SWT.NO_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION; |
138 | fTable = new Table(this, tableStyle); | |
139 | ||
140 | fTable.addSelectionListener(new SelectionAdapter() { | |
141 | @Override | |
142 | public void widgetSelected(SelectionEvent event) { | |
143 | handleTableSelection(); | |
144 | } | |
145 | }); | |
146 | ||
2cc874d6 | 147 | fTable.addKeyListener(new KeyListener() { |
d4011df2 | 148 | @Override |
9ccc6d01 FC |
149 | public void keyPressed(KeyEvent event) { |
150 | handleTableKeyEvent(event); | |
151 | } | |
d4011df2 | 152 | @Override |
9ccc6d01 FC |
153 | public void keyReleased(KeyEvent event) { |
154 | } | |
155 | }); | |
156 | } | |
157 | ||
158 | /** | |
159 | * Update the rows and selected item | |
160 | */ | |
161 | private void handleTableSelection() { | |
2cc874d6 FC |
162 | fSelectedRow = fTable.getSelectionIndices()[0]; |
163 | fSelectedEventRank = fTableTopEventRank + fSelectedRow; | |
164 | fSelectedItems[0] = fTable.getSelection()[0]; | |
9ccc6d01 FC |
165 | } |
166 | ||
167 | /** | |
168 | * Handle key-based navigation in table. | |
169 | * | |
9ccc6d01 FC |
170 | * @param event |
171 | */ | |
172 | private void handleTableKeyEvent(KeyEvent event) { | |
173 | ||
2cc874d6 FC |
174 | int lastEventRank = fTableItemCount - 1; |
175 | int lastPageTopEntryRank = fTableItemCount - fTableRows; | |
176 | ||
177 | int lastRowIndex = ((fTableItemCount < fTableRows) ? fTableItemCount : fTableRows) - 1; | |
178 | int numberOfFullyVisibleRows = fTableRows - ((fPartialRowVisible) ? 1 : 0); | |
9ccc6d01 | 179 | |
2cc874d6 FC |
180 | boolean needsRefresh = false; |
181 | ||
182 | // We are handling things... | |
9ccc6d01 FC |
183 | event.doit = false; |
184 | ||
2cc874d6 FC |
185 | // In all case, perform the following steps: |
186 | // - Update the selected entry rank (within valid range) | |
187 | // - Update the selected row | |
188 | // - Update the page's top entry if necessary (which also adjusts the selected row) | |
189 | // - If the top displayed entry was changed, table refresh is needed | |
9ccc6d01 FC |
190 | switch (event.keyCode) { |
191 | ||
192 | case SWT.ARROW_DOWN: { | |
2cc874d6 FC |
193 | if (fSelectedEventRank < lastEventRank) { |
194 | fSelectedEventRank++; | |
195 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
196 | if (fSelectedRow > lastRowIndex) { | |
197 | fTableTopEventRank++; | |
198 | fSelectedRow = lastRowIndex; | |
199 | needsRefresh = true; | |
9ccc6d01 FC |
200 | } |
201 | } | |
202 | break; | |
203 | } | |
204 | ||
2cc874d6 FC |
205 | case SWT.ARROW_UP: { |
206 | if (fSelectedEventRank > 0) { | |
207 | fSelectedEventRank--; | |
208 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
209 | if (fSelectedRow < 0) { | |
210 | fTableTopEventRank--; | |
211 | fSelectedRow = 0; | |
212 | needsRefresh = true; | |
9ccc6d01 | 213 | } |
9ccc6d01 FC |
214 | } |
215 | break; | |
216 | } | |
217 | ||
218 | case SWT.END: { | |
2cc874d6 FC |
219 | fTableTopEventRank = lastPageTopEntryRank; |
220 | fSelectedEventRank = lastEventRank; | |
221 | fSelectedRow = lastRowIndex; | |
222 | needsRefresh = true; | |
9ccc6d01 FC |
223 | break; |
224 | } | |
225 | ||
2cc874d6 FC |
226 | case SWT.HOME: { |
227 | fSelectedEventRank = 0; | |
228 | fSelectedRow = 0; | |
229 | fTableTopEventRank = 0; | |
230 | needsRefresh = true; | |
9ccc6d01 FC |
231 | break; |
232 | } | |
233 | ||
2cc874d6 FC |
234 | case SWT.PAGE_DOWN: { |
235 | if (fSelectedEventRank < lastEventRank) { | |
236 | fSelectedEventRank += numberOfFullyVisibleRows; | |
237 | if (fSelectedEventRank > lastEventRank) { | |
238 | fSelectedEventRank = lastEventRank; | |
239 | } | |
240 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
241 | if (fSelectedRow > numberOfFullyVisibleRows - 1) { | |
242 | fTableTopEventRank += numberOfFullyVisibleRows; | |
243 | if (fTableTopEventRank > lastPageTopEntryRank) { | |
244 | fTableTopEventRank = lastPageTopEntryRank; | |
245 | } | |
246 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
247 | needsRefresh = true; | |
9ccc6d01 | 248 | } |
9ccc6d01 FC |
249 | } |
250 | break; | |
251 | } | |
252 | ||
2cc874d6 FC |
253 | case SWT.PAGE_UP: { |
254 | if (fSelectedEventRank > 0) { | |
255 | fSelectedEventRank -= numberOfFullyVisibleRows; | |
256 | if (fSelectedEventRank < 0) { | |
257 | fSelectedEventRank = 0; | |
258 | } | |
259 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
260 | if (fSelectedRow < 0) { | |
261 | fSelectedRow = 0; | |
262 | fTableTopEventRank -= numberOfFullyVisibleRows; | |
263 | if (fTableTopEventRank < 0) { | |
264 | fTableTopEventRank = 0; | |
265 | } | |
266 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
267 | needsRefresh = true; | |
268 | } | |
269 | } | |
9ccc6d01 FC |
270 | break; |
271 | } | |
272 | } | |
273 | ||
2cc874d6 | 274 | if (needsRefresh) { |
9ccc6d01 | 275 | for (int i = 0; i < fTableItems.length; i++) { |
2cc874d6 | 276 | setDataItem(i, fTableItems[i]); |
9ccc6d01 FC |
277 | } |
278 | } | |
bbb3457d FC |
279 | else { |
280 | notifyUpdatedSelection(); | |
9ccc6d01 FC |
281 | } |
282 | } | |
283 | ||
bbb3457d FC |
284 | private void setDataItem(int index, TableItem item) { |
285 | if( index != -1) { | |
286 | Event event = new Event(); | |
287 | event.item = item; | |
288 | event.index = index + fTableTopEventRank; | |
289 | event.doit = true; | |
290 | notifyListeners(SWT.SetData, event); | |
291 | } | |
292 | } | |
293 | ||
294 | public void notifyUpdatedSelection() { | |
295 | fSlider.setSelection(fSelectedEventRank); | |
296 | fTable.setSelection(fSelectedRow); | |
297 | fTable.showSelection(); | |
298 | TableItem[] tableSelection = fTable.getSelection(); | |
299 | if (tableSelection.length > 0 && tableSelection[0] != null) { | |
300 | fSelectedItems[0] = tableSelection[0]; | |
301 | TmfTimestamp ts = (TmfTimestamp) fSelectedItems[0].getData(); | |
302 | TmfSignalManager.dispatchSignal(new TmfTimeSynchSignal(this, ts)); | |
303 | } | |
304 | } | |
305 | ||
9ccc6d01 FC |
306 | // ------------------------------------------------------------------------ |
307 | // Slider handling | |
308 | // ------------------------------------------------------------------------ | |
309 | ||
310 | private void createSlider() { | |
311 | fSlider = new Slider(this, SWT.VERTICAL); | |
312 | fSlider.setMinimum(0); | |
313 | fSlider.setMaximum(0); | |
314 | ||
9ccc6d01 | 315 | fSlider.addListener(SWT.Selection, new Listener() { |
d4011df2 | 316 | @Override |
9ccc6d01 FC |
317 | public void handleEvent(Event event) { |
318 | switch (event.detail) { | |
319 | case SWT.ARROW_DOWN: | |
320 | case SWT.ARROW_UP: | |
321 | case SWT.NONE: | |
322 | case SWT.END: | |
323 | case SWT.HOME: | |
324 | case SWT.PAGE_DOWN: | |
325 | case SWT.PAGE_UP: { | |
2cc874d6 FC |
326 | fTableTopEventRank = fSlider.getSelection(); |
327 | refreshTable(); | |
9ccc6d01 FC |
328 | break; |
329 | } | |
330 | } | |
331 | } | |
332 | }); | |
333 | } | |
334 | ||
335 | // ------------------------------------------------------------------------ | |
336 | // Simulated Table API | |
337 | // ------------------------------------------------------------------------ | |
338 | ||
339 | public void setHeaderVisible(boolean b) { | |
340 | fTable.setHeaderVisible(b); | |
341 | } | |
342 | ||
343 | public void setLinesVisible(boolean b) { | |
344 | fTable.setLinesVisible(b); | |
345 | } | |
346 | ||
347 | public TableItem[] getSelection() { | |
348 | return fSelectedItems; | |
349 | } | |
350 | ||
351 | public void addSelectionListener(SelectionAdapter sa) { | |
352 | fTable.addSelectionListener(sa); | |
353 | } | |
354 | ||
355 | public void setItemCount(int nbItems) { | |
356 | nbItems = Math.max(0, nbItems); | |
357 | if (nbItems != fTableItemCount) { | |
358 | fTableItemCount = nbItems; | |
359 | fSlider.setMaximum(nbItems); | |
360 | resize(); | |
361 | } | |
362 | } | |
363 | ||
364 | public int getItemHeight() { | |
365 | return fTable.getItemHeight(); | |
366 | } | |
367 | ||
368 | public int getTopIndex() { | |
2cc874d6 | 369 | return fTableTopEventRank; |
9ccc6d01 FC |
370 | } |
371 | ||
372 | public void setTopIndex(int i) { | |
373 | fSlider.setSelection(i); | |
374 | } | |
375 | ||
376 | public int indexOf(TableItem ti) { | |
377 | return fTable.indexOf(ti) + getTopIndex(); | |
378 | } | |
379 | ||
380 | public TableColumn[] getColumns() { | |
381 | return fTable.getColumns(); | |
382 | } | |
383 | ||
384 | private void resize() { | |
385 | ||
386 | // Compute the numbers of rows that fit the new area | |
2cc874d6 FC |
387 | int tableHeight = fTable.getClientArea().height - fTable.getHeaderHeight(); |
388 | ||
389 | if (tableHeight < 0) tableHeight = 0; | |
390 | int itemHeight = fTable.getItemHeight(); | |
391 | fTableRows = tableHeight / itemHeight; | |
392 | fPartialRowVisible = false; | |
393 | if (fTableRows * itemHeight < tableHeight) { | |
394 | fTableRows++; // For partial rows | |
395 | fPartialRowVisible = true; | |
396 | } | |
397 | if (fTableRows > fTableItemCount) { | |
398 | fTableRows = fTableItemCount; | |
9ccc6d01 | 399 | } |
86148c85 FC |
400 | |
401 | // If we are at the end, get elements before to populate | |
2cc874d6 FC |
402 | if (fTableTopEventRank + fTableRows >= fTableItemCount) { |
403 | fTableTopEventRank = fTableItemCount - fTableRows; | |
86148c85 FC |
404 | } |
405 | ||
406 | // Set the slider thumb size | |
2cc874d6 FC |
407 | if (fTableItemCount > 0) { |
408 | fSlider.setThumb(fTableRows); | |
409 | } | |
86148c85 | 410 | |
9ccc6d01 | 411 | // Re-size and re-create the virtual table if needed |
2cc874d6 | 412 | int delta = fTable.getItemCount() - fTableRows; |
9ccc6d01 FC |
413 | if (delta != 0) { |
414 | fTable.removeAll(); | |
415 | if (fTableItems != null) { | |
416 | for (int i = 0; i < fTableItems.length; i++) { | |
417 | if (fTableItems[i] != null) { | |
418 | fTableItems[i].dispose(); | |
419 | } | |
420 | fTableItems[i] = null; | |
421 | } | |
422 | } | |
2cc874d6 | 423 | fTableItems = new TableItem[fTableRows]; |
9ccc6d01 FC |
424 | for (int i = 0; i < fTableItems.length; i++) { |
425 | fTableItems[i] = new TableItem(fTable, i); | |
426 | } | |
427 | } | |
428 | ||
429 | refresh(); | |
430 | } | |
431 | ||
432 | // ------------------------------------------------------------------------ | |
433 | // Controls interactions | |
434 | // ------------------------------------------------------------------------ | |
435 | ||
436 | @Override | |
437 | public boolean setFocus() { | |
438 | boolean isVisible = isVisible(); | |
439 | if (isVisible) { | |
440 | fTable.setFocus(); | |
441 | } | |
442 | return isVisible; | |
443 | } | |
444 | ||
445 | public void refresh() { | |
2cc874d6 | 446 | refreshTable(); |
bbb3457d | 447 | notifyUpdatedSelection(); |
9ccc6d01 FC |
448 | } |
449 | ||
450 | public void setColumnHeaders(ColumnData columnData[]) { | |
451 | for (int i = 0; i < columnData.length; i++) { | |
452 | TableColumn column = new TableColumn(fTable, columnData[i].alignment, i); | |
453 | column.setText(columnData[i].header); | |
454 | if (columnData[i].width > 0) { | |
455 | column.setWidth(columnData[i].width); | |
456 | } else { | |
457 | column.pack(); | |
458 | } | |
459 | } | |
460 | } | |
461 | ||
462 | public int removeAll() { | |
463 | fSlider.setMaximum(0); | |
464 | fTable.removeAll(); | |
465 | return 0; | |
466 | } | |
467 | ||
2cc874d6 FC |
468 | private void refreshTable() { |
469 | int lastRowOffset = fTableTopEventRank + fTableRows - 1; | |
470 | if ((fSelectedEventRank >= fTableTopEventRank) && (fSelectedEventRank <= lastRowOffset)) { | |
471 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
472 | fTable.setSelection(fSelectedRow); | |
9ccc6d01 | 473 | } else { |
2cc874d6 | 474 | fTable.deselect(fSelectedRow); |
9ccc6d01 FC |
475 | } |
476 | ||
2cc874d6 FC |
477 | for (int i = 0; i < fTableRows; i++) { |
478 | setDataItem(i, fTableItems[i]); | |
9ccc6d01 FC |
479 | } |
480 | } | |
481 | ||
482 | public void setSelection(int i) { | |
483 | if (fTableItems != null) { | |
484 | i = Math.min(i, fTableItemCount); | |
485 | i = Math.max(i, 0); | |
486 | fSlider.setSelection(i); | |
2cc874d6 FC |
487 | |
488 | fSelectedEventRank = i; | |
489 | fTableTopEventRank = i - (fTableRows / 2); | |
490 | if (fTableTopEventRank < 0) { | |
491 | fTableTopEventRank = 0; | |
492 | } | |
493 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
494 | ||
495 | refreshTable(); | |
9ccc6d01 FC |
496 | } |
497 | } | |
498 | ||
499 | } |