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 | ||
16 | import org.eclipse.swt.SWT; | |
17 | import org.eclipse.swt.events.ControlAdapter; | |
18 | import org.eclipse.swt.events.ControlEvent; | |
19 | import org.eclipse.swt.events.KeyEvent; | |
20 | import org.eclipse.swt.events.KeyListener; | |
21 | import org.eclipse.swt.events.MouseEvent; | |
22 | import org.eclipse.swt.events.MouseWheelListener; | |
23 | import org.eclipse.swt.events.SelectionAdapter; | |
24 | import org.eclipse.swt.events.SelectionEvent; | |
25 | import org.eclipse.swt.graphics.Rectangle; | |
26 | import org.eclipse.swt.layout.GridData; | |
27 | import org.eclipse.swt.layout.GridLayout; | |
28 | import org.eclipse.swt.widgets.Composite; | |
29 | import org.eclipse.swt.widgets.Event; | |
30 | import org.eclipse.swt.widgets.Listener; | |
31 | import org.eclipse.swt.widgets.Slider; | |
32 | import org.eclipse.swt.widgets.Table; | |
33 | import org.eclipse.swt.widgets.TableColumn; | |
34 | import org.eclipse.swt.widgets.TableItem; | |
35 | ||
36 | /** | |
37 | * <b><u>TmfVirtualTable</u></b> | |
38 | * <p> | |
39 | * TmfVirtualTable allows for the tabular display of arbitrarily large data sets | |
40 | * (well, up to Integer.MAX_VALUE or ~2G rows). | |
41 | * | |
42 | * It is essentially a Composite of Table and Slider, where the number of rows | |
43 | * in the table is set to fill the table display area. The slider is rank-based. | |
44 | * | |
45 | * It differs from Table with the VIRTUAL style flag where an empty entry is | |
46 | * created for each virtual row. This does not scale well for very large data sets. | |
47 | */ | |
48 | public class TmfVirtualTable extends Composite { | |
49 | ||
50 | // The table | |
51 | private Table fTable; | |
52 | private int fFirstRowOffset = 0; | |
53 | private int fTableRow = 0; | |
54 | private int fEffectiveRow = 0; | |
55 | ||
56 | private TableItem fSelectedItems[] = null; | |
57 | private int fTableItemCount = 0; | |
58 | private int fRowsDisplayed; | |
59 | private TableItem fTableItems[]; | |
60 | ||
61 | // The slider | |
62 | private Slider fSlider; | |
63 | ||
64 | // ------------------------------------------------------------------------ | |
65 | // Constructor | |
66 | // ------------------------------------------------------------------------ | |
67 | ||
68 | /** | |
69 | * @param parent | |
70 | * @param style | |
71 | */ | |
72 | public TmfVirtualTable(Composite parent, int style) { | |
73 | super(parent, style | SWT.BORDER & (~SWT.H_SCROLL) & (~SWT.V_SCROLL)); | |
74 | ||
75 | // Create the controls | |
76 | createTable(); | |
77 | createSlider(); | |
78 | ||
79 | // Set the layout | |
80 | GridLayout gridLayout = new GridLayout(); | |
81 | gridLayout.numColumns = 2; | |
82 | gridLayout.horizontalSpacing = 0; | |
83 | gridLayout.verticalSpacing = 0; | |
84 | gridLayout.marginWidth = 0; | |
85 | setLayout(gridLayout); | |
86 | ||
87 | GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true); | |
88 | fTable.setLayoutData(tableGridData); | |
89 | ||
90 | GridData sliderGridData = new GridData(SWT.FILL, SWT.FILL, false, true); | |
91 | fSlider.setLayoutData(sliderGridData); | |
92 | ||
93 | // Add the listeners | |
94 | addMouseWheelListener(new MouseWheelListener() { | |
95 | public void mouseScrolled(MouseEvent event) { | |
96 | fFirstRowOffset -= event.count; | |
97 | int lastFirstRowOffset = fTableItemCount - fRowsDisplayed - 1; | |
98 | if (fFirstRowOffset > lastFirstRowOffset) { | |
99 | fFirstRowOffset = lastFirstRowOffset; | |
100 | } else if (fFirstRowOffset < 0) { | |
101 | fFirstRowOffset = 0; | |
102 | } | |
103 | fSlider.setSelection(fFirstRowOffset); | |
104 | setSelection(); | |
105 | } | |
106 | }); | |
107 | ||
108 | addControlListener(new ControlAdapter() { | |
109 | @Override | |
110 | public void controlResized(ControlEvent event) { | |
111 | resize(); | |
112 | } | |
113 | }); | |
114 | ||
115 | // And display | |
116 | refresh(); | |
117 | } | |
118 | ||
119 | // ------------------------------------------------------------------------ | |
120 | // Table handling | |
121 | // ------------------------------------------------------------------------ | |
122 | ||
123 | /** | |
124 | * Create the table and add listeners | |
125 | */ | |
126 | private void createTable() { | |
127 | ||
128 | int tableStyle = SWT.NO_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION; | |
129 | fTable = new Table(this, tableStyle); | |
130 | ||
131 | fTable.addSelectionListener(new SelectionAdapter() { | |
132 | @Override | |
133 | public void widgetSelected(SelectionEvent event) { | |
134 | handleTableSelection(); | |
135 | } | |
136 | }); | |
137 | ||
138 | fTable.addKeyListener(new KeyListener() { | |
139 | public void keyPressed(KeyEvent event) { | |
140 | handleTableKeyEvent(event); | |
141 | } | |
142 | public void keyReleased(KeyEvent event) { | |
143 | } | |
144 | }); | |
145 | } | |
146 | ||
147 | /** | |
148 | * Update the rows and selected item | |
149 | */ | |
150 | private void handleTableSelection() { | |
151 | fTableRow = fTable.getSelectionIndices()[0]; | |
152 | fEffectiveRow = fFirstRowOffset + fTableRow; | |
153 | fSelectedItems = new TableItem[1]; | |
154 | fSelectedItems[0] = fTable.getSelection()[0]; | |
155 | } | |
156 | ||
157 | /** | |
158 | * Handle key-based navigation in table. | |
159 | * | |
160 | * The key variables are: | |
161 | * - fFirstRowOffset: the absolute index (in the data set) of the first row displayed | |
162 | * - fTableRow: the index of the selected event in the table window | |
163 | * - fEffectiveRow: the absolute index of the selected event (in the data set) | |
164 | * | |
165 | * At all times, the following relation should hold true: | |
166 | * fEffectiveRow = fFirstRowOffset + fTableRow | |
167 | * | |
168 | * @param event | |
169 | */ | |
170 | private void handleTableKeyEvent(KeyEvent event) { | |
171 | ||
172 | boolean needsUpdate = false; | |
173 | final int lastTableRow = fTableItemCount - 1; | |
174 | int lastRowDisplayed = ((fTableItemCount < fRowsDisplayed) ? fTableItemCount : fRowsDisplayed) - 1; | |
175 | ||
176 | // We are handling things | |
177 | event.doit = false; | |
178 | ||
179 | switch (event.keyCode) { | |
180 | ||
181 | case SWT.ARROW_DOWN: { | |
182 | if (fEffectiveRow < lastTableRow) { | |
183 | fEffectiveRow++; | |
184 | if (fTableRow < lastRowDisplayed) { | |
185 | fTableRow++; | |
186 | } else if (fTableRow < fEffectiveRow) { | |
187 | fFirstRowOffset++; | |
188 | needsUpdate = true; | |
189 | } | |
190 | } | |
191 | break; | |
192 | } | |
193 | ||
194 | case SWT.PAGE_DOWN: { | |
195 | if (fEffectiveRow < lastTableRow) { | |
196 | if ((lastTableRow - fEffectiveRow) >= fRowsDisplayed) { | |
197 | fEffectiveRow += fRowsDisplayed; | |
198 | fFirstRowOffset += fRowsDisplayed; | |
199 | } else { | |
200 | fEffectiveRow = lastTableRow; | |
201 | fTableRow = lastRowDisplayed; | |
202 | } | |
203 | needsUpdate = true; | |
204 | } | |
205 | break; | |
206 | } | |
207 | ||
208 | case SWT.END: { | |
209 | fEffectiveRow = lastTableRow; | |
210 | fTableRow = lastRowDisplayed; | |
211 | if (lastTableRow > lastRowDisplayed) { | |
212 | fFirstRowOffset = fTableItemCount - fRowsDisplayed; | |
213 | } | |
214 | needsUpdate = true; | |
215 | break; | |
216 | } | |
217 | ||
218 | case SWT.ARROW_UP: { | |
219 | if (fEffectiveRow > 0) { | |
220 | fEffectiveRow--; | |
221 | if (fTableRow > 0) { | |
222 | fTableRow--; | |
223 | } else { | |
224 | fFirstRowOffset--; | |
225 | needsUpdate = true; | |
226 | } | |
227 | } | |
228 | break; | |
229 | } | |
230 | ||
231 | case SWT.PAGE_UP: { | |
232 | if (fEffectiveRow > 0) { | |
233 | if (fEffectiveRow > fRowsDisplayed - 1) { | |
234 | fEffectiveRow -= fRowsDisplayed; | |
235 | fFirstRowOffset -= fRowsDisplayed; | |
236 | } else { | |
237 | fEffectiveRow = 0; | |
238 | fTableRow = 0; | |
239 | } | |
240 | needsUpdate = true; | |
241 | } | |
242 | break; | |
243 | } | |
244 | ||
245 | case SWT.HOME: { | |
246 | fEffectiveRow = 0; | |
247 | fTableRow = 0; | |
248 | fFirstRowOffset = 0; | |
249 | needsUpdate = true; | |
250 | break; | |
251 | } | |
252 | } | |
253 | ||
254 | if (needsUpdate) { | |
255 | for (int i = 0; i < fTableItems.length; i++) { | |
256 | setDataItem(fTableItems[i]); | |
257 | } | |
258 | } | |
259 | ||
260 | fTable.setSelection(fTableRow); | |
261 | fSlider.setSelection(fEffectiveRow); | |
262 | ||
263 | // System.out.println("1st: " + fFirstRowOffset + ", TR: " + fTableRow + ", ER: " + fEffectiveRow + | |
264 | // ", Valid: " + ((fFirstRowOffset >= 0) && (fEffectiveRow == (fFirstRowOffset + fTableRow)))); | |
265 | } | |
266 | ||
267 | private void setDataItem(TableItem item) { | |
268 | int index = fTable.indexOf(item); | |
269 | if( index != -1) { | |
270 | Event event = new Event(); | |
271 | event.item = item; | |
272 | event.index = index + fFirstRowOffset; | |
273 | event.doit = true; | |
274 | notifyListeners(SWT.SetData, event); | |
275 | } | |
276 | } | |
277 | ||
278 | // ------------------------------------------------------------------------ | |
279 | // Slider handling | |
280 | // ------------------------------------------------------------------------ | |
281 | ||
282 | private void createSlider() { | |
283 | fSlider = new Slider(this, SWT.VERTICAL); | |
284 | fSlider.setMinimum(0); | |
285 | fSlider.setMaximum(0); | |
286 | ||
287 | fSlider.addSelectionListener(new SelectionAdapter() { | |
288 | @Override | |
289 | public void widgetSelected(SelectionEvent event) { | |
290 | setSelection(); | |
291 | } | |
292 | }); | |
293 | ||
294 | fSlider.addListener(SWT.Selection, new Listener() { | |
295 | public void handleEvent(Event event) { | |
296 | switch (event.detail) { | |
297 | case SWT.ARROW_DOWN: | |
298 | case SWT.ARROW_UP: | |
299 | case SWT.NONE: | |
300 | case SWT.END: | |
301 | case SWT.HOME: | |
302 | case SWT.PAGE_DOWN: | |
303 | case SWT.PAGE_UP: { | |
304 | fFirstRowOffset = fSlider.getSelection(); | |
305 | setSelection(); | |
306 | break; | |
307 | } | |
308 | } | |
309 | } | |
310 | }); | |
311 | } | |
312 | ||
313 | // ------------------------------------------------------------------------ | |
314 | // Simulated Table API | |
315 | // ------------------------------------------------------------------------ | |
316 | ||
317 | public void setHeaderVisible(boolean b) { | |
318 | fTable.setHeaderVisible(b); | |
319 | } | |
320 | ||
321 | public void setLinesVisible(boolean b) { | |
322 | fTable.setLinesVisible(b); | |
323 | } | |
324 | ||
325 | public TableItem[] getSelection() { | |
326 | return fSelectedItems; | |
327 | } | |
328 | ||
329 | public void addSelectionListener(SelectionAdapter sa) { | |
330 | fTable.addSelectionListener(sa); | |
331 | } | |
332 | ||
333 | public void setItemCount(int nbItems) { | |
334 | nbItems = Math.max(0, nbItems); | |
335 | if (nbItems != fTableItemCount) { | |
336 | fTableItemCount = nbItems; | |
337 | fSlider.setMaximum(nbItems); | |
338 | resize(); | |
339 | } | |
340 | } | |
341 | ||
342 | public int getItemHeight() { | |
343 | return fTable.getItemHeight(); | |
344 | } | |
345 | ||
346 | public int getTopIndex() { | |
347 | return fFirstRowOffset; | |
348 | } | |
349 | ||
350 | public void setTopIndex(int i) { | |
351 | fSlider.setSelection(i); | |
352 | } | |
353 | ||
354 | public int indexOf(TableItem ti) { | |
355 | return fTable.indexOf(ti) + getTopIndex(); | |
356 | } | |
357 | ||
358 | public TableColumn[] getColumns() { | |
359 | return fTable.getColumns(); | |
360 | } | |
361 | ||
362 | private void resize() { | |
363 | ||
364 | // Compute the numbers of rows that fit the new area | |
365 | Rectangle clientArea = getClientArea(); | |
366 | int tableHeight = clientArea.height - fTable.getHeaderHeight(); | |
367 | int itemHeight = fTable.getItemHeight(); | |
368 | fRowsDisplayed = tableHeight / itemHeight + 1; // For partial rows | |
369 | if (fTableItemCount == 0) { | |
370 | fRowsDisplayed = 0; | |
371 | } | |
372 | ||
373 | // Re-size and re-create the virtual table if needed | |
374 | int delta = fTable.getItemCount() - fRowsDisplayed; | |
375 | if (delta != 0) { | |
376 | fTable.removeAll(); | |
377 | if (fTableItems != null) { | |
378 | for (int i = 0; i < fTableItems.length; i++) { | |
379 | if (fTableItems[i] != null) { | |
380 | fTableItems[i].dispose(); | |
381 | } | |
382 | fTableItems[i] = null; | |
383 | } | |
384 | } | |
385 | fTableItems = new TableItem[fRowsDisplayed]; | |
386 | for (int i = 0; i < fTableItems.length; i++) { | |
387 | fTableItems[i] = new TableItem(fTable, i); | |
388 | } | |
389 | } | |
390 | ||
391 | refresh(); | |
392 | } | |
393 | ||
394 | // ------------------------------------------------------------------------ | |
395 | // Controls interactions | |
396 | // ------------------------------------------------------------------------ | |
397 | ||
398 | @Override | |
399 | public boolean setFocus() { | |
400 | boolean isVisible = isVisible(); | |
401 | if (isVisible) { | |
402 | fTable.setFocus(); | |
403 | } | |
404 | return isVisible; | |
405 | } | |
406 | ||
407 | public void refresh() { | |
408 | setSelection(); | |
409 | } | |
410 | ||
411 | public void setColumnHeaders(ColumnData columnData[]) { | |
412 | for (int i = 0; i < columnData.length; i++) { | |
413 | TableColumn column = new TableColumn(fTable, columnData[i].alignment, i); | |
414 | column.setText(columnData[i].header); | |
415 | if (columnData[i].width > 0) { | |
416 | column.setWidth(columnData[i].width); | |
417 | } else { | |
418 | column.pack(); | |
419 | } | |
420 | } | |
421 | } | |
422 | ||
423 | public int removeAll() { | |
424 | fSlider.setMaximum(0); | |
425 | fTable.removeAll(); | |
426 | return 0; | |
427 | } | |
428 | ||
429 | private void setSelection() { | |
430 | if ((fEffectiveRow >= fFirstRowOffset) && (fEffectiveRow < (fFirstRowOffset + fRowsDisplayed))) { | |
431 | fTableRow = fEffectiveRow - fFirstRowOffset; | |
432 | fTable.setSelection(fTableRow); | |
433 | } else { | |
434 | fTable.deselect(fTableRow); | |
435 | } | |
436 | ||
437 | for (int i = 0; i < fRowsDisplayed; i++) { | |
438 | setDataItem(fTableItems[i]); | |
439 | } | |
440 | } | |
441 | ||
442 | public void setSelection(int i) { | |
443 | if (fTableItems != null) { | |
444 | i = Math.min(i, fTableItemCount); | |
445 | i = Math.max(i, 0); | |
446 | fSlider.setSelection(i); | |
447 | setSelection(); | |
448 | } | |
449 | } | |
450 | ||
451 | } |