2010-12-15 Bernd Hufmann <bhufmann@gmail.com> Contribution to Bug 332590
[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 ******************************************************************************/
13
14 package org.eclipse.linuxtools.tmf.ui.widgets;
15
16 import org.eclipse.linuxtools.tmf.event.TmfTimestamp;
17 import org.eclipse.linuxtools.tmf.signal.TmfSignalManager;
18 import org.eclipse.linuxtools.tmf.signal.TmfTimeSynchSignal;
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;
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
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
57
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];
62 private int fTableItemCount = 0;
63 private TableItem fTableItems[];
64
65 // The slider
66 private Slider fSlider;
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
80 createTable(style);
81 createSlider();
82
83 // Set the layout
84 GridLayout gridLayout = new GridLayout();
85 gridLayout.numColumns = 2;
86 gridLayout.horizontalSpacing = 0;
87 gridLayout.verticalSpacing = 0;
88 gridLayout.marginWidth = 0;
89 gridLayout.marginHeight = 0;
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
99 fTable.addMouseWheelListener(new MouseWheelListener() {
100 @Override
101 public void mouseScrolled(MouseEvent event) {
102 fTableTopEventRank -= event.count;
103 if (fTableTopEventRank < 0) {
104 fTableTopEventRank = 0;
105 }
106 int latestFirstRowOffset = fTableItemCount - fTableRows;
107 if (fTableTopEventRank > latestFirstRowOffset) {
108 fTableTopEventRank = latestFirstRowOffset;
109 }
110
111 fSlider.setSelection(fTableTopEventRank);
112 refreshTable();
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 */
134 private void createTable(int style) {
135
136 // int tableStyle = SWT.NO_SCROLL | SWT.H_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION;
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
147 fTable.addKeyListener(new KeyListener() {
148 @Override
149 public void keyPressed(KeyEvent event) {
150 handleTableKeyEvent(event);
151 }
152 @Override
153 public void keyReleased(KeyEvent event) {
154 }
155 });
156 }
157
158 /**
159 * Update the rows and selected item
160 */
161 private void handleTableSelection() {
162 fSelectedRow = fTable.getSelectionIndices()[0];
163 fSelectedEventRank = fTableTopEventRank + fSelectedRow;
164 fSelectedItems[0] = fTable.getSelection()[0];
165 }
166
167 /**
168 * Handle key-based navigation in table.
169 *
170 * @param event
171 */
172 private void handleTableKeyEvent(KeyEvent event) {
173
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);
179
180 boolean needsRefresh = false;
181
182 // We are handling things...
183 event.doit = false;
184
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
190 switch (event.keyCode) {
191
192 case SWT.ARROW_DOWN: {
193 if (fSelectedEventRank < lastEventRank) {
194 fSelectedEventRank++;
195 fSelectedRow = fSelectedEventRank - fTableTopEventRank;
196 if (fSelectedRow > lastRowIndex) {
197 fTableTopEventRank++;
198 fSelectedRow = lastRowIndex;
199 needsRefresh = true;
200 }
201 }
202 break;
203 }
204
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;
213 }
214 }
215 break;
216 }
217
218 case SWT.END: {
219 fTableTopEventRank = lastPageTopEntryRank;
220 fSelectedEventRank = lastEventRank;
221 fSelectedRow = lastRowIndex;
222 needsRefresh = true;
223 break;
224 }
225
226 case SWT.HOME: {
227 fSelectedEventRank = 0;
228 fSelectedRow = 0;
229 fTableTopEventRank = 0;
230 needsRefresh = true;
231 break;
232 }
233
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;
248 }
249 }
250 break;
251 }
252
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 }
270 break;
271 }
272 }
273
274 if (needsRefresh) {
275 for (int i = 0; i < fTableItems.length; i++) {
276 setDataItem(i, fTableItems[i]);
277 }
278 }
279 else {
280 notifyUpdatedSelection();
281 // fTable.showSelection();
282 }
283 }
284
285 private void setDataItem(int index, TableItem item) {
286 if( index != -1) {
287 Event event = new Event();
288 event.item = item;
289 event.index = index + fTableTopEventRank;
290 event.doit = true;
291 notifyListeners(SWT.SetData, event);
292 }
293 }
294
295 public void notifyUpdatedSelection() {
296 fSlider.setSelection(fTableTopEventRank + fSelectedRow);
297 setSelectedRowVisibility();
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
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
315 fSlider.addListener(SWT.Selection, new Listener() {
316 @Override
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: {
326 fTableTopEventRank = fSlider.getSelection();
327 refreshTable();
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() {
369 return fTableTopEventRank;
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
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;
399 }
400
401 // If we are at the end, get elements before to populate
402 if (fTableTopEventRank + fTableRows >= fTableItemCount) {
403 fTableTopEventRank = fTableItemCount - fTableRows;
404 }
405
406 // Set the slider thumb size
407 if (fTableItemCount > 0) {
408 fSlider.setThumb(fTableRows);
409 }
410
411 // Re-size and re-create the virtual table if needed
412 int delta = fTable.getItemCount() - fTableRows;
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 }
423 fTableItems = new TableItem[fTableRows];
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() {
446 refreshTable();
447 }
448
449 public void setColumnHeaders(ColumnData columnData[]) {
450 for (int i = 0; i < columnData.length; i++) {
451 TableColumn column = new TableColumn(fTable, columnData[i].alignment, i);
452 column.setText(columnData[i].header);
453 if (columnData[i].width > 0) {
454 column.setWidth(columnData[i].width);
455 } else {
456 column.pack();
457 }
458 }
459 }
460
461 public int removeAll() {
462 fSlider.setMaximum(0);
463 fTable.removeAll();
464 return 0;
465 }
466
467 private void refreshTable() {
468 setSelectedRowVisibility();
469
470 for (int i = 0; i < fTableRows; i++) {
471 setDataItem(i, fTableItems[i]);
472 }
473 }
474
475 private void setSelectedRowVisibility() {
476 int lastRowOffset = fTableTopEventRank + fTableRows - 1;
477 if ((fSelectedEventRank >= fTableTopEventRank) && (fSelectedEventRank <= lastRowOffset)) {
478 fSelectedRow = fSelectedEventRank - fTableTopEventRank;
479 fTable.setSelection(fSelectedRow);
480 } else {
481 fTable.deselect(fSelectedRow);
482 }
483 }
484
485 public void setSelection(int i) {
486 if (fTableItems != null) {
487 i = Math.min(i, fTableItemCount);
488 i = Math.max(i, 0);
489 fSlider.setSelection(i);
490
491 fSelectedEventRank = i;
492 fTableTopEventRank = i - (fTableRows / 2);
493 if (fTableTopEventRank < 0) {
494 fTableTopEventRank = 0;
495 }
496 fSelectedRow = fSelectedEventRank - fTableTopEventRank;
497
498 refreshTable();
499 }
500 }
501
502 }
This page took 0.042107 seconds and 6 git commands to generate.