2011-01-12 Bernd Hufmann <bhufmann@gmail.com> Fix for Bug 333606
[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 // Notify about changed selection
280 notifyUpdatedSelection();
281 }
282
283 private void setDataItem(int index, TableItem item) {
284 if( index != -1) {
285 Event event = new Event();
286 event.item = item;
287 event.index = index + fTableTopEventRank;
288 event.doit = true;
289 notifyListeners(SWT.SetData, event);
290 }
291 }
292
293 /**
294 * Updates the selection in the table and broadcasts a signal to notify all signal
295 * handlers about the updated selection.
296 */
297 public void notifyUpdatedSelection() {
298 notifyUpdatedSelection(null);
299 }
300
301 /**
302 * Updates the selection in the table and broadcasts a signal to notify all signal
303 * handlers about the updated selection.
304 *
305 * @param broadcastTimestamp - timestamp to broadcast if other than from the table selection.
306 * use null to broadcast selected time
307 */
308 public void notifyUpdatedSelection(TmfTimestamp broadcastTimestamp) {
309 fSlider.setSelection(fTableTopEventRank + fSelectedRow);
310 setSelectedRowVisibility();
311 TableItem[] tableSelection = fTable.getSelection();
312 if (tableSelection.length > 0 && tableSelection[0] != null) {
313 fSelectedItems[0] = tableSelection[0];
314 TmfTimestamp ts = (broadcastTimestamp != null) ? broadcastTimestamp : (TmfTimestamp) fSelectedItems[0].getData();
315 TmfSignalManager.dispatchSignal(new TmfTimeSynchSignal(this, ts));
316 }
317 }
318
319 // ------------------------------------------------------------------------
320 // Slider handling
321 // ------------------------------------------------------------------------
322
323 private void createSlider() {
324 fSlider = new Slider(this, SWT.VERTICAL);
325 fSlider.setMinimum(0);
326 fSlider.setMaximum(0);
327
328 fSlider.addListener(SWT.Selection, new Listener() {
329 @Override
330 public void handleEvent(Event event) {
331 switch (event.detail) {
332 case SWT.ARROW_DOWN:
333 case SWT.ARROW_UP:
334 case SWT.NONE:
335 case SWT.END:
336 case SWT.HOME:
337 case SWT.PAGE_DOWN:
338 case SWT.PAGE_UP: {
339 fTableTopEventRank = fSlider.getSelection();
340 refreshTable();
341 break;
342 }
343 }
344 }
345 });
346 }
347
348 // ------------------------------------------------------------------------
349 // Simulated Table API
350 // ------------------------------------------------------------------------
351
352 public void setHeaderVisible(boolean b) {
353 fTable.setHeaderVisible(b);
354 }
355
356 public void setLinesVisible(boolean b) {
357 fTable.setLinesVisible(b);
358 }
359
360 public TableItem[] getSelection() {
361 return fSelectedItems;
362 }
363
364 public void addSelectionListener(SelectionAdapter sa) {
365 fTable.addSelectionListener(sa);
366 }
367
368 public void setItemCount(int nbItems) {
369 nbItems = Math.max(0, nbItems);
370 if (nbItems != fTableItemCount) {
371 fTableItemCount = nbItems;
372 fSlider.setMaximum(nbItems);
373 resize();
374 }
375 }
376
377 public int getItemHeight() {
378 return fTable.getItemHeight();
379 }
380
381 public int getTopIndex() {
382 return fTableTopEventRank;
383 }
384
385 public void setTopIndex(int i) {
386 fSlider.setSelection(i);
387 }
388
389 public int indexOf(TableItem ti) {
390 return fTable.indexOf(ti) + getTopIndex();
391 }
392
393 public TableColumn[] getColumns() {
394 return fTable.getColumns();
395 }
396
397 private void resize() {
398
399 // Compute the numbers of rows that fit the new area
400 int tableHeight = fTable.getClientArea().height - fTable.getHeaderHeight();
401
402 if (tableHeight < 0) tableHeight = 0;
403 int itemHeight = fTable.getItemHeight();
404 fTableRows = tableHeight / itemHeight;
405 fPartialRowVisible = false;
406 if (fTableRows * itemHeight < tableHeight) {
407 fTableRows++; // For partial rows
408 fPartialRowVisible = true;
409 }
410 if (fTableRows > fTableItemCount) {
411 fTableRows = fTableItemCount;
412 }
413
414 // If we are at the end, get elements before to populate
415 if (fTableTopEventRank + fTableRows >= fTableItemCount) {
416 fTableTopEventRank = fTableItemCount - fTableRows;
417 }
418
419 // Set the slider thumb size
420 if (fTableItemCount > 0) {
421 fSlider.setThumb(fTableRows);
422 }
423
424 // Re-size and re-create the virtual table if needed
425 int delta = fTable.getItemCount() - fTableRows;
426 if (delta != 0) {
427 fTable.removeAll();
428 if (fTableItems != null) {
429 for (int i = 0; i < fTableItems.length; i++) {
430 if (fTableItems[i] != null) {
431 fTableItems[i].dispose();
432 }
433 fTableItems[i] = null;
434 }
435 }
436 fTableItems = new TableItem[fTableRows];
437 for (int i = 0; i < fTableItems.length; i++) {
438 fTableItems[i] = new TableItem(fTable, i);
439 }
440 }
441
442 refresh();
443 }
444
445 // ------------------------------------------------------------------------
446 // Controls interactions
447 // ------------------------------------------------------------------------
448
449 @Override
450 public boolean setFocus() {
451 boolean isVisible = isVisible();
452 if (isVisible) {
453 fTable.setFocus();
454 }
455 return isVisible;
456 }
457
458 public void refresh() {
459 refreshTable();
460 }
461
462 public void setColumnHeaders(ColumnData columnData[]) {
463 for (int i = 0; i < columnData.length; i++) {
464 TableColumn column = new TableColumn(fTable, columnData[i].alignment, i);
465 column.setText(columnData[i].header);
466 if (columnData[i].width > 0) {
467 column.setWidth(columnData[i].width);
468 } else {
469 column.pack();
470 }
471 }
472 }
473
474 public int removeAll() {
475 fSlider.setMaximum(0);
476 fTable.removeAll();
477 return 0;
478 }
479
480 private void refreshTable() {
481 setSelectedRowVisibility();
482
483 for (int i = 0; i < fTableRows; i++) {
484 setDataItem(i, fTableItems[i]);
485 }
486 }
487
488 private void setSelectedRowVisibility() {
489 int lastRowOffset = fTableTopEventRank + fTableRows - 1;
490 if ((fSelectedEventRank >= fTableTopEventRank) && (fSelectedEventRank <= lastRowOffset)) {
491 fSelectedRow = fSelectedEventRank - fTableTopEventRank;
492 fTable.setSelection(fSelectedRow);
493 } else {
494 fTable.deselect(fSelectedRow);
495 }
496 }
497
498 public void setSelection(int i) {
499 if (fTableItems != null) {
500 i = Math.min(i, fTableItemCount);
501 i = Math.max(i, 0);
502 fSlider.setSelection(i);
503
504 fSelectedEventRank = i;
505
506 // Check if enough events are available to display selected event
507 // in the middle of table
508 if (i < (fTableItemCount - fTableRows/2)) {
509 fTableTopEventRank = i - (fTableRows / 2);
510 } else {
511 fTableTopEventRank = fTableItemCount - fTableRows;
512 }
513
514 // Sanity check
515 if (fTableTopEventRank < 0) {
516 fTableTopEventRank = 0;
517 }
518 fSelectedRow = fSelectedEventRank - fTableTopEventRank;
519
520 refreshTable();
521 }
522 }
523
524 }
This page took 0.042283 seconds and 6 git commands to generate.