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() { |
9ccc6d01 | 100 | public void mouseScrolled(MouseEvent event) { |
2cc874d6 FC |
101 | fTableTopEventRank -= event.count; |
102 | if (fTableTopEventRank < 0) { | |
103 | fTableTopEventRank = 0; | |
104 | } | |
105 | int latestFirstRowOffset = fTableItemCount - fTableRows; | |
106 | if (fTableTopEventRank > latestFirstRowOffset) { | |
107 | fTableTopEventRank = latestFirstRowOffset; | |
9ccc6d01 | 108 | } |
2cc874d6 FC |
109 | |
110 | fSlider.setSelection(fTableTopEventRank); | |
111 | refreshTable(); | |
9ccc6d01 FC |
112 | } |
113 | }); | |
114 | ||
115 | addControlListener(new ControlAdapter() { | |
116 | @Override | |
117 | public void controlResized(ControlEvent event) { | |
118 | resize(); | |
119 | } | |
120 | }); | |
121 | ||
122 | // And display | |
123 | refresh(); | |
124 | } | |
125 | ||
126 | // ------------------------------------------------------------------------ | |
127 | // Table handling | |
128 | // ------------------------------------------------------------------------ | |
129 | ||
130 | /** | |
131 | * Create the table and add listeners | |
132 | */ | |
2cc874d6 | 133 | private void createTable(int style) { |
9ccc6d01 | 134 | |
2cc874d6 | 135 | // int tableStyle = SWT.NO_SCROLL | SWT.H_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION; |
9ccc6d01 FC |
136 | int tableStyle = SWT.NO_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION; |
137 | fTable = new Table(this, tableStyle); | |
138 | ||
139 | fTable.addSelectionListener(new SelectionAdapter() { | |
140 | @Override | |
141 | public void widgetSelected(SelectionEvent event) { | |
142 | handleTableSelection(); | |
143 | } | |
144 | }); | |
145 | ||
2cc874d6 | 146 | fTable.addKeyListener(new KeyListener() { |
9ccc6d01 FC |
147 | public void keyPressed(KeyEvent event) { |
148 | handleTableKeyEvent(event); | |
149 | } | |
150 | public void keyReleased(KeyEvent event) { | |
151 | } | |
152 | }); | |
153 | } | |
154 | ||
155 | /** | |
156 | * Update the rows and selected item | |
157 | */ | |
158 | private void handleTableSelection() { | |
2cc874d6 FC |
159 | fSelectedRow = fTable.getSelectionIndices()[0]; |
160 | fSelectedEventRank = fTableTopEventRank + fSelectedRow; | |
161 | fSelectedItems[0] = fTable.getSelection()[0]; | |
9ccc6d01 FC |
162 | } |
163 | ||
164 | /** | |
165 | * Handle key-based navigation in table. | |
166 | * | |
9ccc6d01 FC |
167 | * @param event |
168 | */ | |
169 | private void handleTableKeyEvent(KeyEvent event) { | |
170 | ||
2cc874d6 FC |
171 | int lastEventRank = fTableItemCount - 1; |
172 | int lastPageTopEntryRank = fTableItemCount - fTableRows; | |
173 | ||
174 | int lastRowIndex = ((fTableItemCount < fTableRows) ? fTableItemCount : fTableRows) - 1; | |
175 | int numberOfFullyVisibleRows = fTableRows - ((fPartialRowVisible) ? 1 : 0); | |
9ccc6d01 | 176 | |
2cc874d6 FC |
177 | boolean needsRefresh = false; |
178 | ||
179 | // We are handling things... | |
9ccc6d01 FC |
180 | event.doit = false; |
181 | ||
2cc874d6 FC |
182 | // In all case, perform the following steps: |
183 | // - Update the selected entry rank (within valid range) | |
184 | // - Update the selected row | |
185 | // - Update the page's top entry if necessary (which also adjusts the selected row) | |
186 | // - If the top displayed entry was changed, table refresh is needed | |
9ccc6d01 FC |
187 | switch (event.keyCode) { |
188 | ||
189 | case SWT.ARROW_DOWN: { | |
2cc874d6 FC |
190 | if (fSelectedEventRank < lastEventRank) { |
191 | fSelectedEventRank++; | |
192 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
193 | if (fSelectedRow > lastRowIndex) { | |
194 | fTableTopEventRank++; | |
195 | fSelectedRow = lastRowIndex; | |
196 | needsRefresh = true; | |
9ccc6d01 FC |
197 | } |
198 | } | |
199 | break; | |
200 | } | |
201 | ||
2cc874d6 FC |
202 | case SWT.ARROW_UP: { |
203 | if (fSelectedEventRank > 0) { | |
204 | fSelectedEventRank--; | |
205 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
206 | if (fSelectedRow < 0) { | |
207 | fTableTopEventRank--; | |
208 | fSelectedRow = 0; | |
209 | needsRefresh = true; | |
9ccc6d01 | 210 | } |
9ccc6d01 FC |
211 | } |
212 | break; | |
213 | } | |
214 | ||
215 | case SWT.END: { | |
2cc874d6 FC |
216 | fTableTopEventRank = lastPageTopEntryRank; |
217 | fSelectedEventRank = lastEventRank; | |
218 | fSelectedRow = lastRowIndex; | |
219 | needsRefresh = true; | |
9ccc6d01 FC |
220 | break; |
221 | } | |
222 | ||
2cc874d6 FC |
223 | case SWT.HOME: { |
224 | fSelectedEventRank = 0; | |
225 | fSelectedRow = 0; | |
226 | fTableTopEventRank = 0; | |
227 | needsRefresh = true; | |
9ccc6d01 FC |
228 | break; |
229 | } | |
230 | ||
2cc874d6 FC |
231 | case SWT.PAGE_DOWN: { |
232 | if (fSelectedEventRank < lastEventRank) { | |
233 | fSelectedEventRank += numberOfFullyVisibleRows; | |
234 | if (fSelectedEventRank > lastEventRank) { | |
235 | fSelectedEventRank = lastEventRank; | |
236 | } | |
237 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
238 | if (fSelectedRow > numberOfFullyVisibleRows - 1) { | |
239 | fTableTopEventRank += numberOfFullyVisibleRows; | |
240 | if (fTableTopEventRank > lastPageTopEntryRank) { | |
241 | fTableTopEventRank = lastPageTopEntryRank; | |
242 | } | |
243 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
244 | needsRefresh = true; | |
9ccc6d01 | 245 | } |
9ccc6d01 FC |
246 | } |
247 | break; | |
248 | } | |
249 | ||
2cc874d6 FC |
250 | case SWT.PAGE_UP: { |
251 | if (fSelectedEventRank > 0) { | |
252 | fSelectedEventRank -= numberOfFullyVisibleRows; | |
253 | if (fSelectedEventRank < 0) { | |
254 | fSelectedEventRank = 0; | |
255 | } | |
256 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
257 | if (fSelectedRow < 0) { | |
258 | fSelectedRow = 0; | |
259 | fTableTopEventRank -= numberOfFullyVisibleRows; | |
260 | if (fTableTopEventRank < 0) { | |
261 | fTableTopEventRank = 0; | |
262 | } | |
263 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
264 | needsRefresh = true; | |
265 | } | |
266 | } | |
9ccc6d01 FC |
267 | break; |
268 | } | |
269 | } | |
270 | ||
2cc874d6 | 271 | if (needsRefresh) { |
9ccc6d01 | 272 | for (int i = 0; i < fTableItems.length; i++) { |
2cc874d6 | 273 | setDataItem(i, fTableItems[i]); |
9ccc6d01 FC |
274 | } |
275 | } | |
276 | ||
2cc874d6 FC |
277 | fSlider.setSelection(fSelectedEventRank); |
278 | fTable.setSelection(fSelectedRow); | |
279 | fTable.showSelection(); | |
280 | fSelectedItems[0] = fTable.getSelection()[0]; | |
9ccc6d01 | 281 | |
2cc874d6 FC |
282 | TmfTimestamp ts = (TmfTimestamp) fSelectedItems[0].getData(); |
283 | TmfSignalManager.dispatchSignal(new TmfTimeSynchSignal(this, ts)); | |
9ccc6d01 FC |
284 | } |
285 | ||
2cc874d6 | 286 | private void setDataItem(int index, TableItem item) { |
9ccc6d01 FC |
287 | if( index != -1) { |
288 | Event event = new Event(); | |
289 | event.item = item; | |
2cc874d6 | 290 | event.index = index + fTableTopEventRank; |
9ccc6d01 FC |
291 | event.doit = true; |
292 | notifyListeners(SWT.SetData, event); | |
293 | } | |
294 | } | |
295 | ||
296 | // ------------------------------------------------------------------------ | |
297 | // Slider handling | |
298 | // ------------------------------------------------------------------------ | |
299 | ||
300 | private void createSlider() { | |
301 | fSlider = new Slider(this, SWT.VERTICAL); | |
302 | fSlider.setMinimum(0); | |
303 | fSlider.setMaximum(0); | |
304 | ||
9ccc6d01 FC |
305 | fSlider.addListener(SWT.Selection, new Listener() { |
306 | public void handleEvent(Event event) { | |
307 | switch (event.detail) { | |
308 | case SWT.ARROW_DOWN: | |
309 | case SWT.ARROW_UP: | |
310 | case SWT.NONE: | |
311 | case SWT.END: | |
312 | case SWT.HOME: | |
313 | case SWT.PAGE_DOWN: | |
314 | case SWT.PAGE_UP: { | |
2cc874d6 FC |
315 | fTableTopEventRank = fSlider.getSelection(); |
316 | refreshTable(); | |
9ccc6d01 FC |
317 | break; |
318 | } | |
319 | } | |
320 | } | |
321 | }); | |
322 | } | |
323 | ||
324 | // ------------------------------------------------------------------------ | |
325 | // Simulated Table API | |
326 | // ------------------------------------------------------------------------ | |
327 | ||
328 | public void setHeaderVisible(boolean b) { | |
329 | fTable.setHeaderVisible(b); | |
330 | } | |
331 | ||
332 | public void setLinesVisible(boolean b) { | |
333 | fTable.setLinesVisible(b); | |
334 | } | |
335 | ||
336 | public TableItem[] getSelection() { | |
337 | return fSelectedItems; | |
338 | } | |
339 | ||
340 | public void addSelectionListener(SelectionAdapter sa) { | |
341 | fTable.addSelectionListener(sa); | |
342 | } | |
343 | ||
344 | public void setItemCount(int nbItems) { | |
345 | nbItems = Math.max(0, nbItems); | |
346 | if (nbItems != fTableItemCount) { | |
347 | fTableItemCount = nbItems; | |
348 | fSlider.setMaximum(nbItems); | |
349 | resize(); | |
350 | } | |
351 | } | |
352 | ||
353 | public int getItemHeight() { | |
354 | return fTable.getItemHeight(); | |
355 | } | |
356 | ||
357 | public int getTopIndex() { | |
2cc874d6 | 358 | return fTableTopEventRank; |
9ccc6d01 FC |
359 | } |
360 | ||
361 | public void setTopIndex(int i) { | |
362 | fSlider.setSelection(i); | |
363 | } | |
364 | ||
365 | public int indexOf(TableItem ti) { | |
366 | return fTable.indexOf(ti) + getTopIndex(); | |
367 | } | |
368 | ||
369 | public TableColumn[] getColumns() { | |
370 | return fTable.getColumns(); | |
371 | } | |
372 | ||
373 | private void resize() { | |
374 | ||
375 | // Compute the numbers of rows that fit the new area | |
2cc874d6 FC |
376 | int tableHeight = fTable.getClientArea().height - fTable.getHeaderHeight(); |
377 | ||
378 | if (tableHeight < 0) tableHeight = 0; | |
379 | int itemHeight = fTable.getItemHeight(); | |
380 | fTableRows = tableHeight / itemHeight; | |
381 | fPartialRowVisible = false; | |
382 | if (fTableRows * itemHeight < tableHeight) { | |
383 | fTableRows++; // For partial rows | |
384 | fPartialRowVisible = true; | |
385 | } | |
386 | if (fTableRows > fTableItemCount) { | |
387 | fTableRows = fTableItemCount; | |
9ccc6d01 | 388 | } |
86148c85 FC |
389 | |
390 | // If we are at the end, get elements before to populate | |
2cc874d6 FC |
391 | if (fTableTopEventRank + fTableRows >= fTableItemCount) { |
392 | fTableTopEventRank = fTableItemCount - fTableRows; | |
86148c85 FC |
393 | } |
394 | ||
395 | // Set the slider thumb size | |
2cc874d6 FC |
396 | if (fTableItemCount > 0) { |
397 | fSlider.setThumb(fTableRows); | |
398 | } | |
86148c85 | 399 | |
9ccc6d01 | 400 | // Re-size and re-create the virtual table if needed |
2cc874d6 | 401 | int delta = fTable.getItemCount() - fTableRows; |
9ccc6d01 FC |
402 | if (delta != 0) { |
403 | fTable.removeAll(); | |
404 | if (fTableItems != null) { | |
405 | for (int i = 0; i < fTableItems.length; i++) { | |
406 | if (fTableItems[i] != null) { | |
407 | fTableItems[i].dispose(); | |
408 | } | |
409 | fTableItems[i] = null; | |
410 | } | |
411 | } | |
2cc874d6 | 412 | fTableItems = new TableItem[fTableRows]; |
9ccc6d01 FC |
413 | for (int i = 0; i < fTableItems.length; i++) { |
414 | fTableItems[i] = new TableItem(fTable, i); | |
415 | } | |
416 | } | |
417 | ||
418 | refresh(); | |
419 | } | |
420 | ||
421 | // ------------------------------------------------------------------------ | |
422 | // Controls interactions | |
423 | // ------------------------------------------------------------------------ | |
424 | ||
425 | @Override | |
426 | public boolean setFocus() { | |
427 | boolean isVisible = isVisible(); | |
428 | if (isVisible) { | |
429 | fTable.setFocus(); | |
430 | } | |
431 | return isVisible; | |
432 | } | |
433 | ||
434 | public void refresh() { | |
2cc874d6 | 435 | refreshTable(); |
9ccc6d01 FC |
436 | } |
437 | ||
438 | public void setColumnHeaders(ColumnData columnData[]) { | |
439 | for (int i = 0; i < columnData.length; i++) { | |
440 | TableColumn column = new TableColumn(fTable, columnData[i].alignment, i); | |
441 | column.setText(columnData[i].header); | |
442 | if (columnData[i].width > 0) { | |
443 | column.setWidth(columnData[i].width); | |
444 | } else { | |
445 | column.pack(); | |
446 | } | |
447 | } | |
448 | } | |
449 | ||
450 | public int removeAll() { | |
451 | fSlider.setMaximum(0); | |
452 | fTable.removeAll(); | |
453 | return 0; | |
454 | } | |
455 | ||
2cc874d6 FC |
456 | private void refreshTable() { |
457 | int lastRowOffset = fTableTopEventRank + fTableRows - 1; | |
458 | if ((fSelectedEventRank >= fTableTopEventRank) && (fSelectedEventRank <= lastRowOffset)) { | |
459 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
460 | fTable.setSelection(fSelectedRow); | |
9ccc6d01 | 461 | } else { |
2cc874d6 | 462 | fTable.deselect(fSelectedRow); |
9ccc6d01 FC |
463 | } |
464 | ||
2cc874d6 FC |
465 | for (int i = 0; i < fTableRows; i++) { |
466 | setDataItem(i, fTableItems[i]); | |
9ccc6d01 FC |
467 | } |
468 | } | |
469 | ||
470 | public void setSelection(int i) { | |
471 | if (fTableItems != null) { | |
472 | i = Math.min(i, fTableItemCount); | |
473 | i = Math.max(i, 0); | |
474 | fSlider.setSelection(i); | |
2cc874d6 FC |
475 | |
476 | fSelectedEventRank = i; | |
477 | fTableTopEventRank = i - (fTableRows / 2); | |
478 | if (fTableTopEventRank < 0) { | |
479 | fTableTopEventRank = 0; | |
480 | } | |
481 | fSelectedRow = fSelectedEventRank - fTableTopEventRank; | |
482 | ||
483 | refreshTable(); | |
9ccc6d01 FC |
484 | } |
485 | } | |
486 | ||
487 | } |