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 | |
b21305c2 | 12 | * Patrick Tasse - Improvements and bug fixing |
7cc11afa | 13 | * Xavier Raynaud - Improvements |
9ccc6d01 FC |
14 | ******************************************************************************/ |
15 | ||
fb5cad3d | 16 | package org.eclipse.linuxtools.tmf.ui.widgets.virtualtable; |
9ccc6d01 | 17 | |
9fa32496 | 18 | import org.eclipse.linuxtools.internal.tmf.ui.TmfUiPlugin; |
9ccc6d01 | 19 | import org.eclipse.swt.SWT; |
b21305c2 | 20 | import org.eclipse.swt.custom.TableEditor; |
9ccc6d01 FC |
21 | import org.eclipse.swt.events.ControlAdapter; |
22 | import org.eclipse.swt.events.ControlEvent; | |
23 | import org.eclipse.swt.events.KeyEvent; | |
24 | import org.eclipse.swt.events.KeyListener; | |
25 | import org.eclipse.swt.events.MouseEvent; | |
b21305c2 | 26 | import org.eclipse.swt.events.MouseListener; |
9ccc6d01 FC |
27 | import org.eclipse.swt.events.MouseWheelListener; |
28 | import org.eclipse.swt.events.SelectionAdapter; | |
29 | import org.eclipse.swt.events.SelectionEvent; | |
b21305c2 FC |
30 | import org.eclipse.swt.events.SelectionListener; |
31 | import org.eclipse.swt.graphics.Point; | |
7800a42f XR |
32 | import org.eclipse.swt.graphics.Rectangle; |
33 | import org.eclipse.swt.layout.FillLayout; | |
9ccc6d01 FC |
34 | import org.eclipse.swt.layout.GridData; |
35 | import org.eclipse.swt.layout.GridLayout; | |
36 | import org.eclipse.swt.widgets.Composite; | |
b21305c2 FC |
37 | import org.eclipse.swt.widgets.Control; |
38 | import org.eclipse.swt.widgets.Display; | |
9ccc6d01 | 39 | import org.eclipse.swt.widgets.Event; |
7800a42f | 40 | import org.eclipse.swt.widgets.Label; |
9ccc6d01 | 41 | import org.eclipse.swt.widgets.Listener; |
b21305c2 | 42 | import org.eclipse.swt.widgets.Menu; |
7800a42f | 43 | import org.eclipse.swt.widgets.Shell; |
9ccc6d01 FC |
44 | import org.eclipse.swt.widgets.Slider; |
45 | import org.eclipse.swt.widgets.Table; | |
46 | import org.eclipse.swt.widgets.TableColumn; | |
47 | import org.eclipse.swt.widgets.TableItem; | |
7800a42f | 48 | import org.eclipse.ui.PlatformUI; |
9ccc6d01 FC |
49 | |
50 | /** | |
51 | * <b><u>TmfVirtualTable</u></b> | |
52 | * <p> | |
53 | * TmfVirtualTable allows for the tabular display of arbitrarily large data sets | |
54 | * (well, up to Integer.MAX_VALUE or ~2G rows). | |
55 | * | |
56 | * It is essentially a Composite of Table and Slider, where the number of rows | |
57 | * in the table is set to fill the table display area. The slider is rank-based. | |
58 | * | |
59 | * It differs from Table with the VIRTUAL style flag where an empty entry is | |
60 | * created for each virtual row. This does not scale well for very large data sets. | |
b21305c2 FC |
61 | * |
62 | * Styles: | |
63 | * H_SCROLL, V_SCROLL, SINGLE, CHECK, FULL_SELECTION, HIDE_SELECTION, NO_SCROLL | |
9ccc6d01 FC |
64 | */ |
65 | public class TmfVirtualTable extends Composite { | |
66 | ||
67 | // The table | |
2cc874d6 FC |
68 | private Table fTable; |
69 | private int fTableRows = 0; // Number of table rows | |
b21305c2 FC |
70 | private int fFullyVisibleRows = 0; // Number of fully visible table rows |
71 | private int fFrozenRowCount = 0; // Number of frozen table rows at top of table | |
9ccc6d01 | 72 | |
2cc874d6 FC |
73 | private int fTableTopEventRank = 0; // Global rank of the first entry displayed |
74 | private int fSelectedEventRank = 0; // Global rank of the selected event | |
b21305c2 | 75 | private boolean fPendingSelection = false; // Pending selection update |
2cc874d6 | 76 | |
9ccc6d01 | 77 | private int fTableItemCount = 0; |
9ccc6d01 FC |
78 | |
79 | // The slider | |
2cc874d6 | 80 | private Slider fSlider; |
9ccc6d01 | 81 | |
b21305c2 | 82 | private int fLinuxItemHeight = 0; // Calculated item height for Linux workaround |
7800a42f XR |
83 | private TooltipProvider tooltipProvider = null; |
84 | private IDoubleClickListener doubleClickListener = null; | |
b21305c2 | 85 | |
9ccc6d01 FC |
86 | // ------------------------------------------------------------------------ |
87 | // Constructor | |
88 | // ------------------------------------------------------------------------ | |
89 | ||
90 | /** | |
91 | * @param parent | |
92 | * @param style | |
93 | */ | |
94 | public TmfVirtualTable(Composite parent, int style) { | |
b21305c2 | 95 | super(parent, style & (~SWT.H_SCROLL) & (~SWT.V_SCROLL) & (~SWT.SINGLE) & (~SWT.FULL_SELECTION) & (~SWT.HIDE_SELECTION) & (~SWT.CHECK)); |
9ccc6d01 FC |
96 | |
97 | // Create the controls | |
b21305c2 FC |
98 | createTable(style & (SWT.H_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION | SWT.HIDE_SELECTION | SWT.CHECK)); |
99 | createSlider(style & SWT.V_SCROLL); | |
100 | ||
101 | // Prevent the slider from being traversed | |
102 | setTabList(new Control[] { fTable }); | |
9ccc6d01 FC |
103 | |
104 | // Set the layout | |
105 | GridLayout gridLayout = new GridLayout(); | |
106 | gridLayout.numColumns = 2; | |
107 | gridLayout.horizontalSpacing = 0; | |
2cc874d6 FC |
108 | gridLayout.verticalSpacing = 0; |
109 | gridLayout.marginWidth = 0; | |
110 | gridLayout.marginHeight = 0; | |
9ccc6d01 FC |
111 | setLayout(gridLayout); |
112 | ||
113 | GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true); | |
114 | fTable.setLayoutData(tableGridData); | |
115 | ||
116 | GridData sliderGridData = new GridData(SWT.FILL, SWT.FILL, false, true); | |
117 | fSlider.setLayoutData(sliderGridData); | |
118 | ||
119 | // Add the listeners | |
2cc874d6 | 120 | fTable.addMouseWheelListener(new MouseWheelListener() { |
d4011df2 | 121 | @Override |
9ccc6d01 | 122 | public void mouseScrolled(MouseEvent event) { |
b21305c2 FC |
123 | if (fTableItemCount <= fFullyVisibleRows) { |
124 | return; | |
125 | } | |
2cc874d6 FC |
126 | fTableTopEventRank -= event.count; |
127 | if (fTableTopEventRank < 0) { | |
128 | fTableTopEventRank = 0; | |
129 | } | |
b21305c2 | 130 | int latestFirstRowOffset = fTableItemCount - fFullyVisibleRows; |
2cc874d6 FC |
131 | if (fTableTopEventRank > latestFirstRowOffset) { |
132 | fTableTopEventRank = latestFirstRowOffset; | |
9ccc6d01 | 133 | } |
2cc874d6 FC |
134 | |
135 | fSlider.setSelection(fTableTopEventRank); | |
136 | refreshTable(); | |
9ccc6d01 FC |
137 | } |
138 | }); | |
139 | ||
b21305c2 FC |
140 | fTable.addListener(SWT.MouseWheel, new Listener() { |
141 | // disable mouse scroll of horizontal scroll bar | |
142 | @Override | |
143 | public void handleEvent(Event event) { | |
144 | event.doit = false; | |
145 | } | |
146 | }); | |
147 | ||
148 | fTable.addControlListener(new ControlAdapter() { | |
149 | @Override | |
150 | public void controlResized(ControlEvent event) { | |
151 | int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight()); | |
152 | fFullyVisibleRows = tableHeight / getItemHeight(); | |
153 | if (fTableItemCount > 0) { | |
154 | fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows))); | |
155 | } | |
156 | } | |
157 | }); | |
7800a42f | 158 | // Implement a "fake" tooltip |
828e5592 | 159 | final String TOOLTIP_DATA_KEY = "_TABLEITEM"; //$NON-NLS-1$ |
7800a42f | 160 | final Listener labelListener = new Listener () { |
f4c52cea | 161 | @Override |
162 | public void handleEvent (Event event) { | |
7800a42f XR |
163 | Label label = (Label)event.widget; |
164 | Shell shell = label.getShell (); | |
165 | switch (event.type) { | |
166 | case SWT.MouseDown: | |
167 | Event e = new Event (); | |
168 | e.item = (TableItem) label.getData (TOOLTIP_DATA_KEY); | |
169 | // Assuming table is single select, set the selection as if | |
170 | // the mouse down event went through to the table | |
171 | fTable.setSelection (new TableItem [] {(TableItem) e.item}); | |
172 | fTable.notifyListeners (SWT.Selection, e); | |
173 | shell.dispose (); | |
174 | fTable.setFocus(); | |
175 | break; | |
176 | case SWT.MouseExit: | |
177 | case SWT.MouseWheel: | |
178 | shell.dispose (); | |
179 | break; | |
180 | } | |
181 | } | |
182 | }; | |
183 | ||
184 | Listener tableListener = new Listener () { | |
185 | Shell tip = null; | |
186 | Label label = null; | |
f4c52cea | 187 | @Override |
188 | public void handleEvent (Event event) { | |
7800a42f XR |
189 | switch (event.type) { |
190 | case SWT.Dispose: | |
191 | case SWT.KeyDown: | |
192 | case SWT.MouseMove: { | |
193 | if (tip == null) break; | |
194 | tip.dispose (); | |
195 | tip = null; | |
196 | label = null; | |
197 | break; | |
198 | } | |
199 | case SWT.MouseHover: { | |
200 | TableItem item = fTable.getItem (new Point(event.x, event.y)); | |
201 | if (item != null) { | |
202 | for(int i=0;i<fTable.getColumnCount();i++){ | |
203 | Rectangle bounds = item.getBounds(i); | |
204 | if (bounds.contains(event.x,event.y)){ | |
205 | if (tip != null && !tip.isDisposed()) tip.dispose(); | |
206 | if (tooltipProvider == null) { | |
207 | return; | |
208 | } else { | |
209 | String tooltipText = tooltipProvider.getTooltip(i, item.getData()); | |
210 | if (tooltipText == null) return; | |
211 | tip = new Shell(fTable.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); | |
212 | tip.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); | |
213 | FillLayout layout = new FillLayout(); | |
214 | layout.marginWidth = 2; | |
215 | tip.setLayout(layout); | |
216 | label = new Label(tip, SWT.WRAP); | |
217 | label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); | |
218 | label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); | |
219 | label.setData(TOOLTIP_DATA_KEY, item); | |
220 | label.setText(tooltipText); | |
221 | ||
222 | label.addListener(SWT.MouseExit, labelListener); | |
223 | label.addListener(SWT.MouseDown, labelListener); | |
224 | label.addListener(SWT.MouseWheel, labelListener); | |
225 | Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT); | |
226 | Point pt = fTable.toDisplay(bounds.x, bounds.y); | |
227 | tip.setBounds(pt.x, pt.y, size.x, size.y); | |
228 | tip.setVisible(true); | |
229 | } | |
230 | break; | |
231 | } | |
232 | } | |
233 | } | |
234 | } | |
235 | } | |
236 | } | |
237 | }; | |
238 | fTable.addListener(SWT.Dispose, tableListener); | |
239 | fTable.addListener(SWT.KeyDown, tableListener); | |
240 | fTable.addListener(SWT.MouseMove, tableListener); | |
241 | fTable.addListener(SWT.MouseHover, tableListener); | |
9ccc6d01 FC |
242 | addControlListener(new ControlAdapter() { |
243 | @Override | |
244 | public void controlResized(ControlEvent event) { | |
245 | resize(); | |
246 | } | |
b21305c2 | 247 | }); |
9ccc6d01 FC |
248 | |
249 | // And display | |
250 | refresh(); | |
251 | } | |
252 | ||
253 | // ------------------------------------------------------------------------ | |
254 | // Table handling | |
255 | // ------------------------------------------------------------------------ | |
256 | ||
257 | /** | |
258 | * Create the table and add listeners | |
259 | */ | |
2cc874d6 | 260 | private void createTable(int style) { |
b21305c2 | 261 | fTable = new Table(this, style | SWT.NO_SCROLL); |
9ccc6d01 FC |
262 | |
263 | fTable.addSelectionListener(new SelectionAdapter() { | |
264 | @Override | |
265 | public void widgetSelected(SelectionEvent event) { | |
b21305c2 FC |
266 | if (fTable.getSelectionIndices().length > 0) { |
267 | handleTableSelection(); | |
268 | } | |
9ccc6d01 FC |
269 | } |
270 | }); | |
271 | ||
2cc874d6 | 272 | fTable.addKeyListener(new KeyListener() { |
d4011df2 | 273 | @Override |
9ccc6d01 FC |
274 | public void keyPressed(KeyEvent event) { |
275 | handleTableKeyEvent(event); | |
276 | } | |
d4011df2 | 277 | @Override |
9ccc6d01 FC |
278 | public void keyReleased(KeyEvent event) { |
279 | } | |
280 | }); | |
7800a42f XR |
281 | |
282 | fTable.addListener( | |
283 | SWT.MouseDoubleClick, new Listener() { | |
284 | @Override | |
285 | public void handleEvent(Event event) { | |
286 | if (doubleClickListener != null) { | |
287 | TableItem item = fTable.getItem (new Point (event.x, event.y)); | |
288 | if (item != null) { | |
289 | for(int i=0;i<fTable.getColumnCount();i++){ | |
290 | Rectangle bounds = item.getBounds(i); | |
291 | if (bounds.contains(event.x,event.y)){ | |
292 | doubleClickListener.handleDoubleClick(TmfVirtualTable.this, item, i); | |
293 | break; | |
294 | } | |
295 | } | |
296 | } | |
297 | } | |
298 | } | |
299 | } | |
300 | ); | |
b21305c2 | 301 | } |
9ccc6d01 FC |
302 | |
303 | /** | |
304 | * Update the rows and selected item | |
305 | */ | |
306 | private void handleTableSelection() { | |
b21305c2 FC |
307 | int selectedRow = fTable.getSelectionIndices()[0]; |
308 | if (selectedRow < fFrozenRowCount) { | |
309 | fSelectedEventRank = selectedRow; | |
310 | } else { | |
311 | fSelectedEventRank = fTableTopEventRank + selectedRow; | |
312 | } | |
313 | ||
314 | /* | |
315 | * Feature in Windows. When a partially visible table item is selected, | |
316 | * after ~500 ms the top index is changed to ensure the selected item is | |
317 | * fully visible. This leaves a blank space at the bottom of the virtual | |
318 | * table. The workaround is to update the top event rank, refresh the | |
319 | * table and reset the top index to 0 after a sufficient delay. | |
320 | */ | |
321 | if (selectedRow >= fFullyVisibleRows) { | |
322 | final Display display = fTable.getDisplay(); | |
323 | Thread thread = new Thread("Top index check") { //$NON-NLS-1$ | |
324 | @Override | |
325 | public void run() { | |
326 | try { | |
327 | Thread.sleep(600); | |
328 | } catch (InterruptedException e) { | |
b21305c2 FC |
329 | } |
330 | display.asyncExec(new Runnable() { | |
331 | @Override | |
332 | public void run() { | |
333 | if (fTable.isDisposed()) return; | |
334 | int topIndex = fTable.getTopIndex(); | |
335 | if (topIndex != 0) { | |
336 | fTableTopEventRank += topIndex; | |
337 | refreshTable(); | |
338 | fSlider.setSelection(fTableTopEventRank); | |
339 | fTable.setTopIndex(0); | |
340 | } | |
341 | } | |
342 | }); | |
343 | } | |
344 | }; | |
345 | thread.start(); | |
346 | } | |
9ccc6d01 FC |
347 | } |
348 | ||
349 | /** | |
350 | * Handle key-based navigation in table. | |
351 | * | |
9ccc6d01 FC |
352 | * @param event |
353 | */ | |
354 | private void handleTableKeyEvent(KeyEvent event) { | |
355 | ||
2cc874d6 | 356 | int lastEventRank = fTableItemCount - 1; |
b21305c2 | 357 | int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows); |
9ccc6d01 | 358 | |
b21305c2 FC |
359 | int previousSelectedEventRank = fSelectedEventRank; |
360 | int selectedRow = fSelectedEventRank - fTableTopEventRank; | |
2cc874d6 FC |
361 | boolean needsRefresh = false; |
362 | ||
2cc874d6 FC |
363 | // In all case, perform the following steps: |
364 | // - Update the selected entry rank (within valid range) | |
365 | // - Update the selected row | |
366 | // - Update the page's top entry if necessary (which also adjusts the selected row) | |
367 | // - If the top displayed entry was changed, table refresh is needed | |
9ccc6d01 FC |
368 | switch (event.keyCode) { |
369 | ||
370 | case SWT.ARROW_DOWN: { | |
b21305c2 | 371 | event.doit = false; |
2cc874d6 FC |
372 | if (fSelectedEventRank < lastEventRank) { |
373 | fSelectedEventRank++; | |
b21305c2 FC |
374 | selectedRow = fSelectedEventRank - fTableTopEventRank; |
375 | if (selectedRow >= fFullyVisibleRows) { | |
2cc874d6 | 376 | fTableTopEventRank++; |
2cc874d6 | 377 | needsRefresh = true; |
9ccc6d01 FC |
378 | } |
379 | } | |
380 | break; | |
381 | } | |
382 | ||
2cc874d6 | 383 | case SWT.ARROW_UP: { |
b21305c2 | 384 | event.doit = false; |
2cc874d6 FC |
385 | if (fSelectedEventRank > 0) { |
386 | fSelectedEventRank--; | |
b21305c2 FC |
387 | selectedRow = fSelectedEventRank - fTableTopEventRank; |
388 | if (selectedRow < fFrozenRowCount && fTableTopEventRank > 0) { | |
2cc874d6 | 389 | fTableTopEventRank--; |
2cc874d6 | 390 | needsRefresh = true; |
9ccc6d01 | 391 | } |
9ccc6d01 FC |
392 | } |
393 | break; | |
394 | } | |
395 | ||
396 | case SWT.END: { | |
b21305c2 | 397 | event.doit = false; |
2cc874d6 FC |
398 | fTableTopEventRank = lastPageTopEntryRank; |
399 | fSelectedEventRank = lastEventRank; | |
2cc874d6 | 400 | needsRefresh = true; |
9ccc6d01 FC |
401 | break; |
402 | } | |
403 | ||
2cc874d6 | 404 | case SWT.HOME: { |
b21305c2 FC |
405 | event.doit = false; |
406 | fSelectedEventRank = fFrozenRowCount; | |
2cc874d6 | 407 | fTableTopEventRank = 0; |
b21305c2 | 408 | needsRefresh = true; |
9ccc6d01 FC |
409 | break; |
410 | } | |
411 | ||
2cc874d6 | 412 | case SWT.PAGE_DOWN: { |
b21305c2 | 413 | event.doit = false; |
2cc874d6 | 414 | if (fSelectedEventRank < lastEventRank) { |
b21305c2 | 415 | fSelectedEventRank += fFullyVisibleRows; |
2cc874d6 FC |
416 | if (fSelectedEventRank > lastEventRank) { |
417 | fSelectedEventRank = lastEventRank; | |
418 | } | |
b21305c2 FC |
419 | selectedRow = fSelectedEventRank - fTableTopEventRank; |
420 | if (selectedRow > fFullyVisibleRows - 1) { | |
421 | fTableTopEventRank += fFullyVisibleRows; | |
2cc874d6 FC |
422 | if (fTableTopEventRank > lastPageTopEntryRank) { |
423 | fTableTopEventRank = lastPageTopEntryRank; | |
424 | } | |
2cc874d6 | 425 | needsRefresh = true; |
9ccc6d01 | 426 | } |
9ccc6d01 FC |
427 | } |
428 | break; | |
429 | } | |
430 | ||
2cc874d6 | 431 | case SWT.PAGE_UP: { |
b21305c2 | 432 | event.doit = false; |
2cc874d6 | 433 | if (fSelectedEventRank > 0) { |
b21305c2 FC |
434 | fSelectedEventRank -= fFullyVisibleRows; |
435 | if (fSelectedEventRank < fFrozenRowCount) { | |
436 | fSelectedEventRank = fFrozenRowCount; | |
2cc874d6 | 437 | } |
b21305c2 FC |
438 | selectedRow = fSelectedEventRank - fTableTopEventRank; |
439 | if (selectedRow < 0) { | |
440 | fTableTopEventRank -= fFullyVisibleRows; | |
2cc874d6 FC |
441 | if (fTableTopEventRank < 0) { |
442 | fTableTopEventRank = 0; | |
443 | } | |
2cc874d6 FC |
444 | needsRefresh = true; |
445 | } | |
446 | } | |
9ccc6d01 FC |
447 | break; |
448 | } | |
b21305c2 FC |
449 | default: { |
450 | return; | |
451 | } | |
9ccc6d01 | 452 | } |
b21305c2 FC |
453 | |
454 | boolean done = true; | |
2cc874d6 | 455 | if (needsRefresh) { |
b21305c2 FC |
456 | done = refreshTable(); // false if table items not updated yet in this thread |
457 | } else { | |
458 | fTable.select(selectedRow); | |
459 | } | |
460 | ||
461 | if (fFullyVisibleRows < fTableItemCount) { | |
462 | fSlider.setSelection(fTableTopEventRank); | |
463 | } | |
464 | ||
465 | if (fSelectedEventRank != previousSelectedEventRank && fTable.getSelection().length > 0) { | |
466 | if (done) { | |
467 | Event e = new Event(); | |
468 | e.item = fTable.getSelection()[0]; | |
469 | fTable.notifyListeners(SWT.Selection, e); | |
470 | } else { | |
471 | fPendingSelection = true; | |
472 | } | |
473 | } | |
474 | } | |
475 | ||
476 | private boolean setDataItem(int index, TableItem item) { | |
477 | if (index != -1) { | |
478 | Event event = new Event(); | |
479 | event.item = item; | |
480 | if (index < fFrozenRowCount) { | |
481 | event.index = index; | |
482 | } else { | |
483 | event.index = index + fTableTopEventRank; | |
9ccc6d01 | 484 | } |
b21305c2 | 485 | event.doit = true; |
828e5592 | 486 | fTable.notifyListeners(SWT.SetData, event); |
b21305c2 | 487 | return event.doit; // false if table item not updated yet in this thread |
9ccc6d01 | 488 | } |
b21305c2 FC |
489 | return true; |
490 | } | |
bbb3457d | 491 | |
9ccc6d01 FC |
492 | // ------------------------------------------------------------------------ |
493 | // Slider handling | |
494 | // ------------------------------------------------------------------------ | |
495 | ||
b21305c2 FC |
496 | private void createSlider(int style) { |
497 | fSlider = new Slider(this, SWT.VERTICAL | SWT.NO_FOCUS); | |
9ccc6d01 FC |
498 | fSlider.setMinimum(0); |
499 | fSlider.setMaximum(0); | |
b21305c2 FC |
500 | if ((style & SWT.V_SCROLL) == 0) { |
501 | fSlider.setVisible(false); | |
502 | } | |
9ccc6d01 | 503 | |
9ccc6d01 | 504 | fSlider.addListener(SWT.Selection, new Listener() { |
d4011df2 | 505 | @Override |
9ccc6d01 FC |
506 | public void handleEvent(Event event) { |
507 | switch (event.detail) { | |
508 | case SWT.ARROW_DOWN: | |
509 | case SWT.ARROW_UP: | |
510 | case SWT.NONE: | |
511 | case SWT.END: | |
512 | case SWT.HOME: | |
513 | case SWT.PAGE_DOWN: | |
514 | case SWT.PAGE_UP: { | |
2cc874d6 FC |
515 | fTableTopEventRank = fSlider.getSelection(); |
516 | refreshTable(); | |
9ccc6d01 FC |
517 | break; |
518 | } | |
519 | } | |
520 | } | |
521 | }); | |
522 | } | |
523 | ||
524 | // ------------------------------------------------------------------------ | |
525 | // Simulated Table API | |
526 | // ------------------------------------------------------------------------ | |
527 | ||
528 | public void setHeaderVisible(boolean b) { | |
529 | fTable.setHeaderVisible(b); | |
530 | } | |
531 | ||
532 | public void setLinesVisible(boolean b) { | |
533 | fTable.setLinesVisible(b); | |
534 | } | |
535 | ||
536 | public TableItem[] getSelection() { | |
b21305c2 FC |
537 | return fTable.getSelection(); |
538 | } | |
539 | ||
828e5592 PT |
540 | @Override |
541 | public void addListener(int eventType, Listener listener) { | |
542 | fTable.addListener(eventType, listener); | |
543 | } | |
544 | ||
b21305c2 FC |
545 | @Override |
546 | public void addKeyListener(KeyListener listener) { | |
547 | fTable.addKeyListener(listener); | |
548 | } | |
549 | ||
828e5592 PT |
550 | |
551 | @Override | |
b21305c2 FC |
552 | public void addMouseListener(MouseListener listener) { |
553 | fTable.addMouseListener(listener); | |
554 | } | |
555 | ||
556 | public void addSelectionListener(SelectionListener listener) { | |
557 | fTable.addSelectionListener(listener); | |
558 | } | |
559 | ||
560 | @Override | |
561 | public void setMenu(Menu menu) { | |
562 | fTable.setMenu(menu); | |
563 | } | |
564 | ||
565 | public void clearAll() { | |
566 | setItemCount(0); | |
9ccc6d01 | 567 | } |
9ccc6d01 FC |
568 | |
569 | public void setItemCount(int nbItems) { | |
570 | nbItems = Math.max(0, nbItems); | |
b21305c2 | 571 | |
9ccc6d01 FC |
572 | if (nbItems != fTableItemCount) { |
573 | fTableItemCount = nbItems; | |
b21305c2 | 574 | fTable.remove(fTableItemCount, fTable.getItemCount() - 1); |
9ccc6d01 FC |
575 | fSlider.setMaximum(nbItems); |
576 | resize(); | |
b21305c2 FC |
577 | int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight()); |
578 | fFullyVisibleRows = tableHeight / getItemHeight(); | |
579 | if (fTableItemCount > 0) { | |
580 | fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows))); | |
581 | } | |
9ccc6d01 FC |
582 | } |
583 | } | |
584 | ||
b21305c2 FC |
585 | public int getItemCount() { |
586 | return fTableItemCount; | |
587 | } | |
588 | ||
9ccc6d01 | 589 | public int getItemHeight() { |
b21305c2 FC |
590 | /* |
591 | * Bug in Linux. The method getItemHeight doesn't always return the correct value. | |
592 | */ | |
593 | if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$ | |
594 | if (fLinuxItemHeight != 0) { | |
595 | return fLinuxItemHeight; | |
596 | } | |
597 | if (fTable.getItemCount() > 1) { | |
598 | int itemHeight = fTable.getItem(1).getBounds().y - fTable.getItem(0).getBounds().y; | |
599 | if (itemHeight > 0) { | |
600 | fLinuxItemHeight = itemHeight; | |
601 | return fLinuxItemHeight; | |
602 | } | |
603 | } | |
604 | } else { | |
605 | fLinuxItemHeight = -1; // Not Linux, don't perform os.name check anymore | |
606 | } | |
9ccc6d01 FC |
607 | return fTable.getItemHeight(); |
608 | } | |
609 | ||
610 | public int getTopIndex() { | |
b21305c2 | 611 | return fTableTopEventRank + fFrozenRowCount; |
9ccc6d01 FC |
612 | } |
613 | ||
614 | public void setTopIndex(int i) { | |
b21305c2 FC |
615 | if (fTableItemCount > 0) { |
616 | i = Math.min(i, fTableItemCount - 1); | |
617 | i = Math.max(i, fFrozenRowCount); | |
618 | ||
619 | fTableTopEventRank = i - fFrozenRowCount; | |
620 | if (fFullyVisibleRows < fTableItemCount) { | |
621 | fSlider.setSelection(fTableTopEventRank); | |
622 | } | |
623 | ||
624 | refreshTable(); | |
625 | } | |
9ccc6d01 | 626 | } |
b21305c2 | 627 | |
9ccc6d01 | 628 | public int indexOf(TableItem ti) { |
b21305c2 FC |
629 | int index = fTable.indexOf(ti); |
630 | if (index < fFrozenRowCount) { | |
631 | return index; | |
632 | } else { | |
633 | return (index - fFrozenRowCount) + getTopIndex(); | |
634 | } | |
9ccc6d01 | 635 | } |
b21305c2 | 636 | |
9ccc6d01 FC |
637 | public TableColumn[] getColumns() { |
638 | return fTable.getColumns(); | |
639 | } | |
640 | ||
b21305c2 FC |
641 | public TableItem getItem(Point point) { |
642 | return fTable.getItem(point); | |
643 | } | |
644 | ||
9ccc6d01 | 645 | private void resize() { |
9ccc6d01 | 646 | // Compute the numbers of rows that fit the new area |
b21305c2 FC |
647 | int tableHeight = Math.max(0, getSize().y - fTable.getHeaderHeight()); |
648 | int itemHeight = getItemHeight(); | |
649 | fTableRows = Math.min((tableHeight + itemHeight - 1) / itemHeight, fTableItemCount); | |
650 | ||
651 | if (fTableTopEventRank + fFullyVisibleRows > fTableItemCount) { | |
652 | // If we are at the end, get elements before to populate | |
653 | fTableTopEventRank = Math.max(0, fTableItemCount - fFullyVisibleRows); | |
654 | refreshTable(); | |
655 | } else if (fTableRows > fTable.getItemCount() || fTableItemCount < fTable.getItemCount()) { | |
656 | // Only refresh if new table items are needed or if table items need to be deleted | |
657 | refreshTable(); | |
9ccc6d01 FC |
658 | } |
659 | ||
9ccc6d01 FC |
660 | } |
661 | ||
662 | // ------------------------------------------------------------------------ | |
663 | // Controls interactions | |
664 | // ------------------------------------------------------------------------ | |
665 | ||
666 | @Override | |
667 | public boolean setFocus() { | |
668 | boolean isVisible = isVisible(); | |
669 | if (isVisible) { | |
670 | fTable.setFocus(); | |
671 | } | |
672 | return isVisible; | |
673 | } | |
674 | ||
675 | public void refresh() { | |
b21305c2 FC |
676 | boolean done = refreshTable(); |
677 | if (fPendingSelection && done) { | |
678 | fPendingSelection = false; | |
679 | if (fTable.getSelection().length > 0) { | |
680 | Event e = new Event(); | |
681 | e.item = fTable.getSelection()[0]; | |
682 | fTable.notifyListeners(SWT.Selection, e); | |
683 | } | |
684 | } | |
9ccc6d01 FC |
685 | } |
686 | ||
687 | public void setColumnHeaders(ColumnData columnData[]) { | |
688 | for (int i = 0; i < columnData.length; i++) { | |
689 | TableColumn column = new TableColumn(fTable, columnData[i].alignment, i); | |
690 | column.setText(columnData[i].header); | |
691 | if (columnData[i].width > 0) { | |
692 | column.setWidth(columnData[i].width); | |
693 | } else { | |
694 | column.pack(); | |
695 | } | |
696 | } | |
b21305c2 | 697 | } |
9ccc6d01 FC |
698 | |
699 | public int removeAll() { | |
b21305c2 | 700 | setItemCount(0); |
9ccc6d01 FC |
701 | fSlider.setMaximum(0); |
702 | fTable.removeAll(); | |
b21305c2 | 703 | fSelectedEventRank = fFrozenRowCount; |
9ccc6d01 FC |
704 | return 0; |
705 | } | |
706 | ||
b21305c2 FC |
707 | private boolean refreshTable() { |
708 | boolean done = true; | |
886b12b1 | 709 | for (int i = 0; i < fTableRows; i++) { |
b21305c2 FC |
710 | if (i + fTableTopEventRank < fTableItemCount) { |
711 | TableItem tableItem; | |
712 | if (i < fTable.getItemCount()) { | |
713 | tableItem = fTable.getItem(i); | |
714 | } else { | |
715 | tableItem = new TableItem(fTable, SWT.NONE); | |
716 | } | |
717 | done &= setDataItem(i, tableItem); // false if table item not updated yet in this thread | |
718 | } else { | |
719 | if (fTable.getItemCount() > fTableItemCount - fTableTopEventRank) { | |
720 | fTable.remove(fTableItemCount - fTableTopEventRank); | |
721 | } | |
722 | } | |
886b12b1 | 723 | } |
886b12b1 | 724 | |
b21305c2 FC |
725 | int lastRowOffset = fTableTopEventRank + fTableRows - 1; |
726 | if ((fSelectedEventRank >= fTableTopEventRank + fFrozenRowCount) && (fSelectedEventRank <= lastRowOffset)) { | |
727 | int selectedRow = fSelectedEventRank - fTableTopEventRank; | |
728 | fTable.select(selectedRow); | |
729 | } else if (fSelectedEventRank < fFrozenRowCount) { | |
730 | fTable.select(fSelectedEventRank); | |
9ccc6d01 | 731 | } else { |
b21305c2 | 732 | fTable.deselectAll(); |
9ccc6d01 | 733 | } |
b21305c2 FC |
734 | return done; |
735 | } | |
9ccc6d01 FC |
736 | |
737 | public void setSelection(int i) { | |
b21305c2 FC |
738 | if (fTableItemCount > 0) { |
739 | i = Math.min(i, fTableItemCount - 1); | |
9ccc6d01 | 740 | i = Math.max(i, 0); |
2cc874d6 FC |
741 | |
742 | fSelectedEventRank = i; | |
b21305c2 FC |
743 | if ((i < fTableTopEventRank + fFrozenRowCount && i >= fFrozenRowCount) || |
744 | (i >= fTableTopEventRank + fFullyVisibleRows)) { | |
745 | fTableTopEventRank = Math.max(0, i - fFrozenRowCount - fFullyVisibleRows / 2); | |
89dcf304 | 746 | } |
b21305c2 FC |
747 | if (fFullyVisibleRows < fTableItemCount) { |
748 | fSlider.setSelection(fTableTopEventRank); | |
2cc874d6 | 749 | } |
b21305c2 | 750 | |
2cc874d6 | 751 | refreshTable(); |
b21305c2 FC |
752 | |
753 | } | |
754 | } | |
755 | ||
756 | public int getSelectionIndex() { | |
757 | int index = fTable.getSelectionIndex(); | |
2d55fd20 FC |
758 | if (index == -1) { |
759 | return fSelectedEventRank; | |
760 | } | |
b21305c2 FC |
761 | if (index < fFrozenRowCount) { |
762 | return index; | |
763 | } else { | |
764 | return (index - fFrozenRowCount) + getTopIndex(); | |
9ccc6d01 FC |
765 | } |
766 | } | |
b21305c2 FC |
767 | |
768 | public void setFrozenRowCount(int count) { | |
769 | fFrozenRowCount = count; | |
770 | refreshTable(); | |
771 | } | |
9ccc6d01 | 772 | |
b21305c2 FC |
773 | public TableEditor createTableEditor() { |
774 | return new TableEditor(fTable); | |
775 | } | |
776 | ||
777 | public Control createTableEditorControl(Class<? extends Control> control) { | |
778 | try { | |
779 | return (Control) control.getConstructor(Composite.class, int.class).newInstance(new Object[] {fTable, SWT.NONE}); | |
780 | } catch (Exception e) { | |
9fa32496 | 781 | TmfUiPlugin.getDefault().logError("Error creating table editor control", e); //$NON-NLS-1$ |
b21305c2 FC |
782 | } |
783 | return null; | |
784 | } | |
7800a42f XR |
785 | |
786 | /** | |
787 | * @return the tooltipProvider | |
788 | */ | |
789 | public TooltipProvider getTooltipProvider() { | |
790 | return tooltipProvider; | |
791 | } | |
792 | ||
793 | /** | |
794 | * @param tooltipProvider the tooltipProvider to set | |
795 | */ | |
796 | public void setTooltipProvider(TooltipProvider tooltipProvider) { | |
797 | this.tooltipProvider = tooltipProvider; | |
798 | } | |
799 | ||
800 | /** | |
801 | * @return the doubleClickListener | |
802 | */ | |
803 | public IDoubleClickListener getDoubleClickListener() { | |
804 | return doubleClickListener; | |
805 | } | |
806 | ||
807 | /** | |
808 | * @param doubleClickListener the doubleClickListener to set | |
809 | */ | |
810 | public void setDoubleClickListener(IDoubleClickListener doubleClickListener) { | |
811 | this.doubleClickListener = doubleClickListener; | |
812 | } | |
813 | ||
9ccc6d01 | 814 | } |