aa72bc3330755b29ce9fc30361e52459b4bc3bbb
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / widgets / TmfVirtualTable.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 * Matthew Khouzam - Initial API and implementation
11 * Francois Chouinard - Refactoring, slider support, bug fixing
12 * Patrick Tasse - Improvements and bug fixing
13 ******************************************************************************/
14
15 package org.eclipse.linuxtools.tmf.ui.widgets;
16
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.custom.TableEditor;
19 import org.eclipse.swt.events.ControlAdapter;
20 import org.eclipse.swt.events.ControlEvent;
21 import org.eclipse.swt.events.KeyEvent;
22 import org.eclipse.swt.events.KeyListener;
23 import org.eclipse.swt.events.MouseEvent;
24 import org.eclipse.swt.events.MouseListener;
25 import org.eclipse.swt.events.MouseWheelListener;
26 import org.eclipse.swt.events.SelectionAdapter;
27 import org.eclipse.swt.events.SelectionEvent;
28 import org.eclipse.swt.events.SelectionListener;
29 import org.eclipse.swt.graphics.Point;
30 import org.eclipse.swt.layout.GridData;
31 import org.eclipse.swt.layout.GridLayout;
32 import org.eclipse.swt.widgets.Composite;
33 import org.eclipse.swt.widgets.Control;
34 import org.eclipse.swt.widgets.Display;
35 import org.eclipse.swt.widgets.Event;
36 import org.eclipse.swt.widgets.Listener;
37 import org.eclipse.swt.widgets.Menu;
38 import org.eclipse.swt.widgets.Slider;
39 import org.eclipse.swt.widgets.Table;
40 import org.eclipse.swt.widgets.TableColumn;
41 import org.eclipse.swt.widgets.TableItem;
42
43 /**
44 * <b><u>TmfVirtualTable</u></b>
45 * <p>
46 * TmfVirtualTable allows for the tabular display of arbitrarily large data sets
47 * (well, up to Integer.MAX_VALUE or ~2G rows).
48 *
49 * It is essentially a Composite of Table and Slider, where the number of rows
50 * in the table is set to fill the table display area. The slider is rank-based.
51 *
52 * It differs from Table with the VIRTUAL style flag where an empty entry is
53 * created for each virtual row. This does not scale well for very large data sets.
54 *
55 * Styles:
56 * H_SCROLL, V_SCROLL, SINGLE, CHECK, FULL_SELECTION, HIDE_SELECTION, NO_SCROLL
57 */
58 public class TmfVirtualTable extends Composite {
59
60 // The table
61 private Table fTable;
62 private int fTableRows = 0; // Number of table rows
63 private int fFullyVisibleRows = 0; // Number of fully visible table rows
64 private int fFrozenRowCount = 0; // Number of frozen table rows at top of table
65
66 private int fTableTopEventRank = 0; // Global rank of the first entry displayed
67 private int fSelectedEventRank = 0; // Global rank of the selected event
68 private boolean fPendingSelection = false; // Pending selection update
69
70 private int fTableItemCount = 0;
71
72 // The slider
73 private Slider fSlider;
74
75 private int fLinuxItemHeight = 0; // Calculated item height for Linux workaround
76
77 // ------------------------------------------------------------------------
78 // Constructor
79 // ------------------------------------------------------------------------
80
81 /**
82 * @param parent
83 * @param style
84 */
85 public TmfVirtualTable(Composite parent, int style) {
86 super(parent, style & (~SWT.H_SCROLL) & (~SWT.V_SCROLL) & (~SWT.SINGLE) & (~SWT.FULL_SELECTION) & (~SWT.HIDE_SELECTION) & (~SWT.CHECK));
87
88 // Create the controls
89 createTable(style & (SWT.H_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION | SWT.HIDE_SELECTION | SWT.CHECK));
90 createSlider(style & SWT.V_SCROLL);
91
92 // Prevent the slider from being traversed
93 setTabList(new Control[] { fTable });
94
95 // Set the layout
96 GridLayout gridLayout = new GridLayout();
97 gridLayout.numColumns = 2;
98 gridLayout.horizontalSpacing = 0;
99 gridLayout.verticalSpacing = 0;
100 gridLayout.marginWidth = 0;
101 gridLayout.marginHeight = 0;
102 setLayout(gridLayout);
103
104 GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
105 fTable.setLayoutData(tableGridData);
106
107 GridData sliderGridData = new GridData(SWT.FILL, SWT.FILL, false, true);
108 fSlider.setLayoutData(sliderGridData);
109
110 // Add the listeners
111 fTable.addMouseWheelListener(new MouseWheelListener() {
112 @Override
113 public void mouseScrolled(MouseEvent event) {
114 if (fTableItemCount <= fFullyVisibleRows) {
115 return;
116 }
117 fTableTopEventRank -= event.count;
118 if (fTableTopEventRank < 0) {
119 fTableTopEventRank = 0;
120 }
121 int latestFirstRowOffset = fTableItemCount - fFullyVisibleRows;
122 if (fTableTopEventRank > latestFirstRowOffset) {
123 fTableTopEventRank = latestFirstRowOffset;
124 }
125
126 fSlider.setSelection(fTableTopEventRank);
127 refreshTable();
128 }
129 });
130
131 fTable.addListener(SWT.MouseWheel, new Listener() {
132 // disable mouse scroll of horizontal scroll bar
133 @Override
134 public void handleEvent(Event event) {
135 event.doit = false;
136 }
137 });
138
139 fTable.addControlListener(new ControlAdapter() {
140 @Override
141 public void controlResized(ControlEvent event) {
142 int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight());
143 fFullyVisibleRows = tableHeight / getItemHeight();
144 if (fTableItemCount > 0) {
145 fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows)));
146 }
147 }
148 });
149
150 addControlListener(new ControlAdapter() {
151 @Override
152 public void controlResized(ControlEvent event) {
153 resize();
154 }
155 });
156
157 // And display
158 refresh();
159 }
160
161 // ------------------------------------------------------------------------
162 // Table handling
163 // ------------------------------------------------------------------------
164
165 /**
166 * Create the table and add listeners
167 */
168 private void createTable(int style) {
169 fTable = new Table(this, style | SWT.NO_SCROLL);
170
171 fTable.addSelectionListener(new SelectionAdapter() {
172 @Override
173 public void widgetSelected(SelectionEvent event) {
174 if (fTable.getSelectionIndices().length > 0) {
175 handleTableSelection();
176 }
177 }
178 });
179
180 fTable.addKeyListener(new KeyListener() {
181 @Override
182 public void keyPressed(KeyEvent event) {
183 handleTableKeyEvent(event);
184 }
185 @Override
186 public void keyReleased(KeyEvent event) {
187 }
188 });
189 }
190
191 /**
192 * Update the rows and selected item
193 */
194 private void handleTableSelection() {
195 int selectedRow = fTable.getSelectionIndices()[0];
196 if (selectedRow < fFrozenRowCount) {
197 fSelectedEventRank = selectedRow;
198 } else {
199 fSelectedEventRank = fTableTopEventRank + selectedRow;
200 }
201
202 /*
203 * Feature in Windows. When a partially visible table item is selected,
204 * after ~500 ms the top index is changed to ensure the selected item is
205 * fully visible. This leaves a blank space at the bottom of the virtual
206 * table. The workaround is to update the top event rank, refresh the
207 * table and reset the top index to 0 after a sufficient delay.
208 */
209 if (selectedRow >= fFullyVisibleRows) {
210 final Display display = fTable.getDisplay();
211 Thread thread = new Thread("Top index check") { //$NON-NLS-1$
212 @Override
213 public void run() {
214 try {
215 Thread.sleep(600);
216 } catch (InterruptedException e) {
217 e.printStackTrace();
218 }
219 display.asyncExec(new Runnable() {
220 @Override
221 public void run() {
222 if (fTable.isDisposed()) return;
223 int topIndex = fTable.getTopIndex();
224 if (topIndex != 0) {
225 fTableTopEventRank += topIndex;
226 refreshTable();
227 fSlider.setSelection(fTableTopEventRank);
228 fTable.setTopIndex(0);
229 }
230 }
231 });
232 }
233 };
234 thread.start();
235 }
236 }
237
238 /**
239 * Handle key-based navigation in table.
240 *
241 * @param event
242 */
243 private void handleTableKeyEvent(KeyEvent event) {
244
245 int lastEventRank = fTableItemCount - 1;
246 int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows);
247
248 int previousSelectedEventRank = fSelectedEventRank;
249 int selectedRow = fSelectedEventRank - fTableTopEventRank;
250 boolean needsRefresh = false;
251
252 // In all case, perform the following steps:
253 // - Update the selected entry rank (within valid range)
254 // - Update the selected row
255 // - Update the page's top entry if necessary (which also adjusts the selected row)
256 // - If the top displayed entry was changed, table refresh is needed
257 switch (event.keyCode) {
258
259 case SWT.ARROW_DOWN: {
260 event.doit = false;
261 if (fSelectedEventRank < lastEventRank) {
262 fSelectedEventRank++;
263 selectedRow = fSelectedEventRank - fTableTopEventRank;
264 if (selectedRow >= fFullyVisibleRows) {
265 fTableTopEventRank++;
266 needsRefresh = true;
267 }
268 }
269 break;
270 }
271
272 case SWT.ARROW_UP: {
273 event.doit = false;
274 if (fSelectedEventRank > 0) {
275 fSelectedEventRank--;
276 selectedRow = fSelectedEventRank - fTableTopEventRank;
277 if (selectedRow < fFrozenRowCount && fTableTopEventRank > 0) {
278 fTableTopEventRank--;
279 needsRefresh = true;
280 }
281 }
282 break;
283 }
284
285 case SWT.END: {
286 event.doit = false;
287 fTableTopEventRank = lastPageTopEntryRank;
288 fSelectedEventRank = lastEventRank;
289 needsRefresh = true;
290 break;
291 }
292
293 case SWT.HOME: {
294 event.doit = false;
295 fSelectedEventRank = fFrozenRowCount;
296 fTableTopEventRank = 0;
297 needsRefresh = true;
298 break;
299 }
300
301 case SWT.PAGE_DOWN: {
302 event.doit = false;
303 if (fSelectedEventRank < lastEventRank) {
304 fSelectedEventRank += fFullyVisibleRows;
305 if (fSelectedEventRank > lastEventRank) {
306 fSelectedEventRank = lastEventRank;
307 }
308 selectedRow = fSelectedEventRank - fTableTopEventRank;
309 if (selectedRow > fFullyVisibleRows - 1) {
310 fTableTopEventRank += fFullyVisibleRows;
311 if (fTableTopEventRank > lastPageTopEntryRank) {
312 fTableTopEventRank = lastPageTopEntryRank;
313 }
314 needsRefresh = true;
315 }
316 }
317 break;
318 }
319
320 case SWT.PAGE_UP: {
321 event.doit = false;
322 if (fSelectedEventRank > 0) {
323 fSelectedEventRank -= fFullyVisibleRows;
324 if (fSelectedEventRank < fFrozenRowCount) {
325 fSelectedEventRank = fFrozenRowCount;
326 }
327 selectedRow = fSelectedEventRank - fTableTopEventRank;
328 if (selectedRow < 0) {
329 fTableTopEventRank -= fFullyVisibleRows;
330 if (fTableTopEventRank < 0) {
331 fTableTopEventRank = 0;
332 }
333 needsRefresh = true;
334 }
335 }
336 break;
337 }
338 default: {
339 return;
340 }
341 }
342
343 boolean done = true;
344 if (needsRefresh) {
345 done = refreshTable(); // false if table items not updated yet in this thread
346 } else {
347 fTable.select(selectedRow);
348 }
349
350 if (fFullyVisibleRows < fTableItemCount) {
351 fSlider.setSelection(fTableTopEventRank);
352 }
353
354 if (fSelectedEventRank != previousSelectedEventRank && fTable.getSelection().length > 0) {
355 if (done) {
356 Event e = new Event();
357 e.item = fTable.getSelection()[0];
358 fTable.notifyListeners(SWT.Selection, e);
359 } else {
360 fPendingSelection = true;
361 }
362 }
363 }
364
365 private boolean setDataItem(int index, TableItem item) {
366 if (index != -1) {
367 Event event = new Event();
368 event.item = item;
369 if (index < fFrozenRowCount) {
370 event.index = index;
371 } else {
372 event.index = index + fTableTopEventRank;
373 }
374 event.doit = true;
375 notifyListeners(SWT.SetData, event);
376 return event.doit; // false if table item not updated yet in this thread
377 }
378 return true;
379 }
380
381 // ------------------------------------------------------------------------
382 // Slider handling
383 // ------------------------------------------------------------------------
384
385 private void createSlider(int style) {
386 fSlider = new Slider(this, SWT.VERTICAL | SWT.NO_FOCUS);
387 fSlider.setMinimum(0);
388 fSlider.setMaximum(0);
389 if ((style & SWT.V_SCROLL) == 0) {
390 fSlider.setVisible(false);
391 }
392
393 fSlider.addListener(SWT.Selection, new Listener() {
394 @Override
395 public void handleEvent(Event event) {
396 switch (event.detail) {
397 case SWT.ARROW_DOWN:
398 case SWT.ARROW_UP:
399 case SWT.NONE:
400 case SWT.END:
401 case SWT.HOME:
402 case SWT.PAGE_DOWN:
403 case SWT.PAGE_UP: {
404 fTableTopEventRank = fSlider.getSelection();
405 refreshTable();
406 break;
407 }
408 }
409 }
410 });
411 }
412
413 // ------------------------------------------------------------------------
414 // Simulated Table API
415 // ------------------------------------------------------------------------
416
417 public void setHeaderVisible(boolean b) {
418 fTable.setHeaderVisible(b);
419 }
420
421 public void setLinesVisible(boolean b) {
422 fTable.setLinesVisible(b);
423 }
424
425 public TableItem[] getSelection() {
426 return fTable.getSelection();
427 }
428
429 @Override
430 public void addKeyListener(KeyListener listener) {
431 fTable.addKeyListener(listener);
432 }
433
434 @Override
435 public void addMouseListener(MouseListener listener) {
436 fTable.addMouseListener(listener);
437 }
438
439 public void addSelectionListener(SelectionListener listener) {
440 fTable.addSelectionListener(listener);
441 }
442
443 @Override
444 public void setMenu(Menu menu) {
445 fTable.setMenu(menu);
446 }
447
448 public void clearAll() {
449 setItemCount(0);
450 }
451
452 public void setItemCount(int nbItems) {
453 nbItems = Math.max(0, nbItems);
454
455 if (nbItems != fTableItemCount) {
456 fTableItemCount = nbItems;
457 fTable.remove(fTableItemCount, fTable.getItemCount() - 1);
458 fSlider.setMaximum(nbItems);
459 resize();
460 int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight());
461 fFullyVisibleRows = tableHeight / getItemHeight();
462 if (fTableItemCount > 0) {
463 fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows)));
464 }
465 }
466 }
467
468 public int getItemCount() {
469 return fTableItemCount;
470 }
471
472 public int getItemHeight() {
473 /*
474 * Bug in Linux. The method getItemHeight doesn't always return the correct value.
475 */
476 if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
477 if (fLinuxItemHeight != 0) {
478 return fLinuxItemHeight;
479 }
480 if (fTable.getItemCount() > 1) {
481 int itemHeight = fTable.getItem(1).getBounds().y - fTable.getItem(0).getBounds().y;
482 if (itemHeight > 0) {
483 fLinuxItemHeight = itemHeight;
484 return fLinuxItemHeight;
485 }
486 }
487 } else {
488 fLinuxItemHeight = -1; // Not Linux, don't perform os.name check anymore
489 }
490 return fTable.getItemHeight();
491 }
492
493 public int getTopIndex() {
494 return fTableTopEventRank + fFrozenRowCount;
495 }
496
497 public void setTopIndex(int i) {
498 if (fTableItemCount > 0) {
499 i = Math.min(i, fTableItemCount - 1);
500 i = Math.max(i, fFrozenRowCount);
501
502 fTableTopEventRank = i - fFrozenRowCount;
503 if (fFullyVisibleRows < fTableItemCount) {
504 fSlider.setSelection(fTableTopEventRank);
505 }
506
507 refreshTable();
508 }
509 }
510
511 public int indexOf(TableItem ti) {
512 int index = fTable.indexOf(ti);
513 if (index < fFrozenRowCount) {
514 return index;
515 } else {
516 return (index - fFrozenRowCount) + getTopIndex();
517 }
518 }
519
520 public TableColumn[] getColumns() {
521 return fTable.getColumns();
522 }
523
524 public TableItem getItem(Point point) {
525 return fTable.getItem(point);
526 }
527
528 private void resize() {
529 // Compute the numbers of rows that fit the new area
530 int tableHeight = Math.max(0, getSize().y - fTable.getHeaderHeight());
531 int itemHeight = getItemHeight();
532 fTableRows = Math.min((tableHeight + itemHeight - 1) / itemHeight, fTableItemCount);
533
534 if (fTableTopEventRank + fFullyVisibleRows > fTableItemCount) {
535 // If we are at the end, get elements before to populate
536 fTableTopEventRank = Math.max(0, fTableItemCount - fFullyVisibleRows);
537 refreshTable();
538 } else if (fTableRows > fTable.getItemCount() || fTableItemCount < fTable.getItemCount()) {
539 // Only refresh if new table items are needed or if table items need to be deleted
540 refreshTable();
541 }
542
543 }
544
545 // ------------------------------------------------------------------------
546 // Controls interactions
547 // ------------------------------------------------------------------------
548
549 @Override
550 public boolean setFocus() {
551 boolean isVisible = isVisible();
552 if (isVisible) {
553 fTable.setFocus();
554 }
555 return isVisible;
556 }
557
558 public void refresh() {
559 boolean done = refreshTable();
560 if (fPendingSelection && done) {
561 fPendingSelection = false;
562 if (fTable.getSelection().length > 0) {
563 Event e = new Event();
564 e.item = fTable.getSelection()[0];
565 fTable.notifyListeners(SWT.Selection, e);
566 }
567 }
568 }
569
570 public void setColumnHeaders(ColumnData columnData[]) {
571 for (int i = 0; i < columnData.length; i++) {
572 TableColumn column = new TableColumn(fTable, columnData[i].alignment, i);
573 column.setText(columnData[i].header);
574 if (columnData[i].width > 0) {
575 column.setWidth(columnData[i].width);
576 } else {
577 column.pack();
578 }
579 }
580 }
581
582 public int removeAll() {
583 setItemCount(0);
584 fSlider.setMaximum(0);
585 fTable.removeAll();
586 fSelectedEventRank = fFrozenRowCount;
587 return 0;
588 }
589
590 private boolean refreshTable() {
591 boolean done = true;
592 for (int i = 0; i < fTableRows; i++) {
593 if (i + fTableTopEventRank < fTableItemCount) {
594 TableItem tableItem;
595 if (i < fTable.getItemCount()) {
596 tableItem = fTable.getItem(i);
597 } else {
598 tableItem = new TableItem(fTable, SWT.NONE);
599 }
600 done &= setDataItem(i, tableItem); // false if table item not updated yet in this thread
601 } else {
602 if (fTable.getItemCount() > fTableItemCount - fTableTopEventRank) {
603 fTable.remove(fTableItemCount - fTableTopEventRank);
604 }
605 }
606 }
607
608 int lastRowOffset = fTableTopEventRank + fTableRows - 1;
609 if ((fSelectedEventRank >= fTableTopEventRank + fFrozenRowCount) && (fSelectedEventRank <= lastRowOffset)) {
610 int selectedRow = fSelectedEventRank - fTableTopEventRank;
611 fTable.select(selectedRow);
612 } else if (fSelectedEventRank < fFrozenRowCount) {
613 fTable.select(fSelectedEventRank);
614 } else {
615 fTable.deselectAll();
616 }
617 return done;
618 }
619
620 public void setSelection(int i) {
621 if (fTableItemCount > 0) {
622 i = Math.min(i, fTableItemCount - 1);
623 i = Math.max(i, 0);
624
625 fSelectedEventRank = i;
626 if ((i < fTableTopEventRank + fFrozenRowCount && i >= fFrozenRowCount) ||
627 (i >= fTableTopEventRank + fFullyVisibleRows)) {
628 fTableTopEventRank = Math.max(0, i - fFrozenRowCount - fFullyVisibleRows / 2);
629 }
630 if (fFullyVisibleRows < fTableItemCount) {
631 fSlider.setSelection(fTableTopEventRank);
632 }
633
634 refreshTable();
635
636 }
637 }
638
639 public int getSelectionIndex() {
640 int index = fTable.getSelectionIndex();
641 if (index == -1) {
642 return fSelectedEventRank;
643 }
644 if (index < fFrozenRowCount) {
645 return index;
646 } else {
647 return (index - fFrozenRowCount) + getTopIndex();
648 }
649 }
650
651 public void setFrozenRowCount(int count) {
652 fFrozenRowCount = count;
653 refreshTable();
654 }
655
656 public TableEditor createTableEditor() {
657 return new TableEditor(fTable);
658 }
659
660 public Control createTableEditorControl(Class<? extends Control> control) {
661 try {
662 return (Control) control.getConstructor(Composite.class, int.class).newInstance(new Object[] {fTable, SWT.NONE});
663 } catch (Exception e) {
664 e.printStackTrace();
665 }
666 return null;
667 }
668 }
This page took 0.051368 seconds and 4 git commands to generate.