1 /*******************************************************************************
2 * Copyright (c) 2010 Ericsson
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
10 * Matthew Khouzam - Initial API and implementation
11 * Francois Chouinard - Refactoring, slider support, bug fixing
12 * Patrick Tasse - Improvements and bug fixing
13 ******************************************************************************/
15 package org
.eclipse
.linuxtools
.tmf
.ui
.widgets
;
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
;
44 * <b><u>TmfVirtualTable</u></b>
46 * TmfVirtualTable allows for the tabular display of arbitrarily large data sets
47 * (well, up to Integer.MAX_VALUE or ~2G rows).
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.
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.
56 * H_SCROLL, V_SCROLL, SINGLE, CHECK, FULL_SELECTION, HIDE_SELECTION, NO_SCROLL
58 public class TmfVirtualTable
extends Composite
{
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
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
70 private int fTableItemCount
= 0;
73 private Slider fSlider
;
75 private int fLinuxItemHeight
= 0; // Calculated item height for Linux workaround
77 // ------------------------------------------------------------------------
79 // ------------------------------------------------------------------------
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
));
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
);
92 // Prevent the slider from being traversed
93 setTabList(new Control
[] { fTable
});
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
);
104 GridData tableGridData
= new GridData(SWT
.FILL
, SWT
.FILL
, true, true);
105 fTable
.setLayoutData(tableGridData
);
107 GridData sliderGridData
= new GridData(SWT
.FILL
, SWT
.FILL
, false, true);
108 fSlider
.setLayoutData(sliderGridData
);
111 fTable
.addMouseWheelListener(new MouseWheelListener() {
113 public void mouseScrolled(MouseEvent event
) {
114 if (fTableItemCount
<= fFullyVisibleRows
) {
117 fTableTopEventRank
-= event
.count
;
118 if (fTableTopEventRank
< 0) {
119 fTableTopEventRank
= 0;
121 int latestFirstRowOffset
= fTableItemCount
- fFullyVisibleRows
;
122 if (fTableTopEventRank
> latestFirstRowOffset
) {
123 fTableTopEventRank
= latestFirstRowOffset
;
126 fSlider
.setSelection(fTableTopEventRank
);
131 fTable
.addListener(SWT
.MouseWheel
, new Listener() {
132 // disable mouse scroll of horizontal scroll bar
134 public void handleEvent(Event event
) {
139 fTable
.addControlListener(new ControlAdapter() {
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
)));
150 addControlListener(new ControlAdapter() {
152 public void controlResized(ControlEvent event
) {
161 // ------------------------------------------------------------------------
163 // ------------------------------------------------------------------------
166 * Create the table and add listeners
168 private void createTable(int style
) {
169 fTable
= new Table(this, style
| SWT
.NO_SCROLL
);
171 fTable
.addSelectionListener(new SelectionAdapter() {
173 public void widgetSelected(SelectionEvent event
) {
174 if (fTable
.getSelectionIndices().length
> 0) {
175 handleTableSelection();
180 fTable
.addKeyListener(new KeyListener() {
182 public void keyPressed(KeyEvent event
) {
183 handleTableKeyEvent(event
);
186 public void keyReleased(KeyEvent event
) {
192 * Update the rows and selected item
194 private void handleTableSelection() {
195 int selectedRow
= fTable
.getSelectionIndices()[0];
196 if (selectedRow
< fFrozenRowCount
) {
197 fSelectedEventRank
= selectedRow
;
199 fSelectedEventRank
= fTableTopEventRank
+ selectedRow
;
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.
209 if (selectedRow
>= fFullyVisibleRows
) {
210 final Display display
= fTable
.getDisplay();
211 Thread thread
= new Thread("Top index check") { //$NON-NLS-1$
216 } catch (InterruptedException e
) {
219 display
.asyncExec(new Runnable() {
222 if (fTable
.isDisposed()) return;
223 int topIndex
= fTable
.getTopIndex();
225 fTableTopEventRank
+= topIndex
;
227 fSlider
.setSelection(fTableTopEventRank
);
228 fTable
.setTopIndex(0);
239 * Handle key-based navigation in table.
243 private void handleTableKeyEvent(KeyEvent event
) {
245 int lastEventRank
= fTableItemCount
- 1;
246 int lastPageTopEntryRank
= Math
.max(0, fTableItemCount
- fFullyVisibleRows
);
248 int previousSelectedEventRank
= fSelectedEventRank
;
249 int selectedRow
= fSelectedEventRank
- fTableTopEventRank
;
250 boolean needsRefresh
= false;
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
) {
259 case SWT
.ARROW_DOWN
: {
261 if (fSelectedEventRank
< lastEventRank
) {
262 fSelectedEventRank
++;
263 selectedRow
= fSelectedEventRank
- fTableTopEventRank
;
264 if (selectedRow
>= fFullyVisibleRows
) {
265 fTableTopEventRank
++;
274 if (fSelectedEventRank
> 0) {
275 fSelectedEventRank
--;
276 selectedRow
= fSelectedEventRank
- fTableTopEventRank
;
277 if (selectedRow
< fFrozenRowCount
&& fTableTopEventRank
> 0) {
278 fTableTopEventRank
--;
287 fTableTopEventRank
= lastPageTopEntryRank
;
288 fSelectedEventRank
= lastEventRank
;
295 fSelectedEventRank
= fFrozenRowCount
;
296 fTableTopEventRank
= 0;
301 case SWT
.PAGE_DOWN
: {
303 if (fSelectedEventRank
< lastEventRank
) {
304 fSelectedEventRank
+= fFullyVisibleRows
;
305 if (fSelectedEventRank
> lastEventRank
) {
306 fSelectedEventRank
= lastEventRank
;
308 selectedRow
= fSelectedEventRank
- fTableTopEventRank
;
309 if (selectedRow
> fFullyVisibleRows
- 1) {
310 fTableTopEventRank
+= fFullyVisibleRows
;
311 if (fTableTopEventRank
> lastPageTopEntryRank
) {
312 fTableTopEventRank
= lastPageTopEntryRank
;
322 if (fSelectedEventRank
> 0) {
323 fSelectedEventRank
-= fFullyVisibleRows
;
324 if (fSelectedEventRank
< fFrozenRowCount
) {
325 fSelectedEventRank
= fFrozenRowCount
;
327 selectedRow
= fSelectedEventRank
- fTableTopEventRank
;
328 if (selectedRow
< 0) {
329 fTableTopEventRank
-= fFullyVisibleRows
;
330 if (fTableTopEventRank
< 0) {
331 fTableTopEventRank
= 0;
345 done
= refreshTable(); // false if table items not updated yet in this thread
347 fTable
.select(selectedRow
);
350 if (fFullyVisibleRows
< fTableItemCount
) {
351 fSlider
.setSelection(fTableTopEventRank
);
354 if (fSelectedEventRank
!= previousSelectedEventRank
&& fTable
.getSelection().length
> 0) {
356 Event e
= new Event();
357 e
.item
= fTable
.getSelection()[0];
358 fTable
.notifyListeners(SWT
.Selection
, e
);
360 fPendingSelection
= true;
365 private boolean setDataItem(int index
, TableItem item
) {
367 Event event
= new Event();
369 if (index
< fFrozenRowCount
) {
372 event
.index
= index
+ fTableTopEventRank
;
375 notifyListeners(SWT
.SetData
, event
);
376 return event
.doit
; // false if table item not updated yet in this thread
381 // ------------------------------------------------------------------------
383 // ------------------------------------------------------------------------
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);
393 fSlider
.addListener(SWT
.Selection
, new Listener() {
395 public void handleEvent(Event event
) {
396 switch (event
.detail
) {
404 fTableTopEventRank
= fSlider
.getSelection();
413 // ------------------------------------------------------------------------
414 // Simulated Table API
415 // ------------------------------------------------------------------------
417 public void setHeaderVisible(boolean b
) {
418 fTable
.setHeaderVisible(b
);
421 public void setLinesVisible(boolean b
) {
422 fTable
.setLinesVisible(b
);
425 public TableItem
[] getSelection() {
426 return fTable
.getSelection();
430 public void addKeyListener(KeyListener listener
) {
431 fTable
.addKeyListener(listener
);
435 public void addMouseListener(MouseListener listener
) {
436 fTable
.addMouseListener(listener
);
439 public void addSelectionListener(SelectionListener listener
) {
440 fTable
.addSelectionListener(listener
);
444 public void setMenu(Menu menu
) {
445 fTable
.setMenu(menu
);
448 public void clearAll() {
452 public void setItemCount(int nbItems
) {
453 nbItems
= Math
.max(0, nbItems
);
455 if (nbItems
!= fTableItemCount
) {
456 fTableItemCount
= nbItems
;
457 fTable
.remove(fTableItemCount
, fTable
.getItemCount() - 1);
458 fSlider
.setMaximum(nbItems
);
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
)));
468 public int getItemCount() {
469 return fTableItemCount
;
472 public int getItemHeight() {
474 * Bug in Linux. The method getItemHeight doesn't always return the correct value.
476 if (fLinuxItemHeight
>= 0 && System
.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
477 if (fLinuxItemHeight
!= 0) {
478 return fLinuxItemHeight
;
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
;
488 fLinuxItemHeight
= -1; // Not Linux, don't perform os.name check anymore
490 return fTable
.getItemHeight();
493 public int getTopIndex() {
494 return fTableTopEventRank
+ fFrozenRowCount
;
497 public void setTopIndex(int i
) {
498 if (fTableItemCount
> 0) {
499 i
= Math
.min(i
, fTableItemCount
- 1);
500 i
= Math
.max(i
, fFrozenRowCount
);
502 fTableTopEventRank
= i
- fFrozenRowCount
;
503 if (fFullyVisibleRows
< fTableItemCount
) {
504 fSlider
.setSelection(fTableTopEventRank
);
511 public int indexOf(TableItem ti
) {
512 int index
= fTable
.indexOf(ti
);
513 if (index
< fFrozenRowCount
) {
516 return (index
- fFrozenRowCount
) + getTopIndex();
520 public TableColumn
[] getColumns() {
521 return fTable
.getColumns();
524 public TableItem
getItem(Point point
) {
525 return fTable
.getItem(point
);
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
);
534 if (fTableTopEventRank
+ fFullyVisibleRows
> fTableItemCount
) {
535 // If we are at the end, get elements before to populate
536 fTableTopEventRank
= Math
.max(0, fTableItemCount
- fFullyVisibleRows
);
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
545 // ------------------------------------------------------------------------
546 // Controls interactions
547 // ------------------------------------------------------------------------
550 public boolean setFocus() {
551 boolean isVisible
= isVisible();
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
);
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
);
582 public int removeAll() {
584 fSlider
.setMaximum(0);
586 fSelectedEventRank
= fFrozenRowCount
;
590 private boolean refreshTable() {
592 for (int i
= 0; i
< fTableRows
; i
++) {
593 if (i
+ fTableTopEventRank
< fTableItemCount
) {
595 if (i
< fTable
.getItemCount()) {
596 tableItem
= fTable
.getItem(i
);
598 tableItem
= new TableItem(fTable
, SWT
.NONE
);
600 done
&= setDataItem(i
, tableItem
); // false if table item not updated yet in this thread
602 if (fTable
.getItemCount() > fTableItemCount
- fTableTopEventRank
) {
603 fTable
.remove(fTableItemCount
- fTableTopEventRank
);
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
);
615 fTable
.deselectAll();
620 public void setSelection(int i
) {
621 if (fTableItemCount
> 0) {
622 i
= Math
.min(i
, fTableItemCount
- 1);
625 fSelectedEventRank
= i
;
626 if ((i
< fTableTopEventRank
+ fFrozenRowCount
&& i
>= fFrozenRowCount
) ||
627 (i
>= fTableTopEventRank
+ fFullyVisibleRows
)) {
628 fTableTopEventRank
= Math
.max(0, i
- fFrozenRowCount
- fFullyVisibleRows
/ 2);
630 if (fFullyVisibleRows
< fTableItemCount
) {
631 fSlider
.setSelection(fTableTopEventRank
);
639 public int getSelectionIndex() {
640 int index
= fTable
.getSelectionIndex();
642 return fSelectedEventRank
;
644 if (index
< fFrozenRowCount
) {
647 return (index
- fFrozenRowCount
) + getTopIndex();
651 public void setFrozenRowCount(int count
) {
652 fFrozenRowCount
= count
;
656 public TableEditor
createTableEditor() {
657 return new TableEditor(fTable
);
660 public Control
createTableEditorControl(Class
<?
extends Control
> control
) {
662 return (Control
) control
.getConstructor(Composite
.class, int.class).newInstance(new Object
[] {fTable
, SWT
.NONE
});
663 } catch (Exception e
) {