Commit | Line | Data |
---|---|---|
9ccc6d01 | 1 | /******************************************************************************* |
c8422608 | 2 | * Copyright (c) 2010, 2013 Ericsson |
3934297e | 3 | * |
9ccc6d01 FC |
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 | |
3934297e | 8 | * |
9ccc6d01 FC |
9 | * Contributors: |
10 | * Matthew Khouzam - Initial API and implementation | |
3934297e | 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 | |
8fd82db5 | 18 | import org.eclipse.linuxtools.internal.tmf.ui.Activator; |
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). | |
3934297e | 55 | * |
9ccc6d01 FC |
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. | |
3934297e | 58 | * |
9ccc6d01 FC |
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. | |
3934297e | 61 | * |
b21305c2 FC |
62 | * Styles: |
63 | * H_SCROLL, V_SCROLL, SINGLE, CHECK, FULL_SELECTION, HIDE_SELECTION, NO_SCROLL | |
02023181 | 64 | * @author Matthew Khouzam, Francois Chouinard, Patrick Tasse, Xavier Raynaud |
3934297e | 65 | * @version $Revision: 1.0 |
9ccc6d01 FC |
66 | */ |
67 | public class TmfVirtualTable extends Composite { | |
68 | ||
631d853f | 69 | // The table |
02023181 MK |
70 | /** |
71 | * Field fTable. | |
72 | */ | |
631d853f | 73 | private Table fTable; |
02023181 MK |
74 | /** |
75 | * Field fTableRows. | |
76 | */ | |
631d853f | 77 | private int fTableRows = 0; // Number of table rows |
02023181 MK |
78 | /** |
79 | * Field fFullyVisibleRows. | |
80 | */ | |
631d853f | 81 | private int fFullyVisibleRows = 0; // Number of fully visible table rows |
02023181 MK |
82 | /** |
83 | * Field fFrozenRowCount. | |
84 | */ | |
631d853f PT |
85 | private int fFrozenRowCount = 0; // Number of frozen table rows at top of table |
86 | ||
02023181 MK |
87 | /** |
88 | * Field fTableTopEventRank. | |
89 | */ | |
631d853f | 90 | private int fTableTopEventRank = 0; // Global rank of the first entry displayed |
02023181 MK |
91 | /** |
92 | * Field fSelectedEventRank. | |
93 | */ | |
631d853f | 94 | private int fSelectedEventRank = 0; // Global rank of the selected event |
02023181 MK |
95 | /** |
96 | * Field fPendingSelection. | |
97 | */ | |
631d853f PT |
98 | private boolean fPendingSelection = false; // Pending selection update |
99 | ||
02023181 MK |
100 | /** |
101 | * Field fTableItemCount. | |
102 | */ | |
631d853f PT |
103 | private int fTableItemCount = 0; |
104 | ||
105 | // The slider | |
02023181 MK |
106 | /** |
107 | * Field fSlider. | |
108 | */ | |
631d853f PT |
109 | private Slider fSlider; |
110 | ||
02023181 MK |
111 | /** |
112 | * Field fLinuxItemHeight. | |
113 | */ | |
631d853f | 114 | private int fLinuxItemHeight = 0; // Calculated item height for Linux workaround |
02023181 MK |
115 | /** |
116 | * Field tooltipProvider. | |
117 | */ | |
631d853f | 118 | private TooltipProvider tooltipProvider = null; |
02023181 MK |
119 | /** |
120 | * Field doubleClickListener. | |
121 | */ | |
631d853f PT |
122 | private IDoubleClickListener doubleClickListener = null; |
123 | ||
124 | // ------------------------------------------------------------------------ | |
125 | // Constructor | |
126 | // ------------------------------------------------------------------------ | |
127 | ||
128 | /** | |
3934297e AM |
129 | * Standard constructor |
130 | * | |
631d853f | 131 | * @param parent |
3934297e | 132 | * The parent composite object |
631d853f | 133 | * @param style |
3934297e | 134 | * The style to use |
631d853f PT |
135 | */ |
136 | public TmfVirtualTable(Composite parent, int style) { | |
137 | super(parent, style & (~SWT.H_SCROLL) & (~SWT.V_SCROLL) & (~SWT.SINGLE) & (~SWT.FULL_SELECTION) & (~SWT.HIDE_SELECTION) & (~SWT.CHECK)); | |
138 | ||
139 | // Create the controls | |
140 | createTable(style & (SWT.H_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION | SWT.HIDE_SELECTION | SWT.CHECK)); | |
141 | createSlider(style & SWT.V_SCROLL); | |
142 | ||
143 | // Prevent the slider from being traversed | |
144 | setTabList(new Control[] { fTable }); | |
145 | ||
146 | // Set the layout | |
147 | GridLayout gridLayout = new GridLayout(); | |
148 | gridLayout.numColumns = 2; | |
149 | gridLayout.horizontalSpacing = 0; | |
150 | gridLayout.verticalSpacing = 0; | |
151 | gridLayout.marginWidth = 0; | |
152 | gridLayout.marginHeight = 0; | |
153 | setLayout(gridLayout); | |
154 | ||
155 | GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true); | |
156 | fTable.setLayoutData(tableGridData); | |
157 | ||
158 | GridData sliderGridData = new GridData(SWT.FILL, SWT.FILL, false, true); | |
159 | fSlider.setLayoutData(sliderGridData); | |
160 | ||
161 | // Add the listeners | |
162 | fTable.addMouseWheelListener(new MouseWheelListener() { | |
163 | @Override | |
164 | public void mouseScrolled(MouseEvent event) { | |
165 | if (fTableItemCount <= fFullyVisibleRows) { | |
166 | return; | |
167 | } | |
168 | fTableTopEventRank -= event.count; | |
169 | if (fTableTopEventRank < 0) { | |
170 | fTableTopEventRank = 0; | |
171 | } | |
172 | int latestFirstRowOffset = fTableItemCount - fFullyVisibleRows; | |
173 | if (fTableTopEventRank > latestFirstRowOffset) { | |
174 | fTableTopEventRank = latestFirstRowOffset; | |
175 | } | |
176 | ||
177 | fSlider.setSelection(fTableTopEventRank); | |
178 | refreshTable(); | |
179 | } | |
180 | }); | |
181 | ||
182 | fTable.addListener(SWT.MouseWheel, new Listener() { | |
183 | // disable mouse scroll of horizontal scroll bar | |
184 | @Override | |
b21305c2 | 185 | public void handleEvent(Event event) { |
631d853f PT |
186 | event.doit = false; |
187 | } | |
188 | }); | |
189 | ||
190 | fTable.addControlListener(new ControlAdapter() { | |
191 | @Override | |
192 | public void controlResized(ControlEvent event) { | |
193 | int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight()); | |
194 | fFullyVisibleRows = tableHeight / getItemHeight(); | |
195 | if (fTableItemCount > 0) { | |
196 | fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows))); | |
197 | } | |
198 | } | |
199 | }); | |
200 | // Implement a "fake" tooltip | |
201 | final String TOOLTIP_DATA_KEY = "_TABLEITEM"; //$NON-NLS-1$ | |
202 | final Listener labelListener = new Listener () { | |
203 | @Override | |
f4c52cea | 204 | public void handleEvent (Event event) { |
631d853f PT |
205 | Label label = (Label)event.widget; |
206 | Shell shell = label.getShell (); | |
207 | switch (event.type) { | |
208 | case SWT.MouseDown: | |
209 | Event e = new Event (); | |
210 | e.item = (TableItem) label.getData (TOOLTIP_DATA_KEY); | |
211 | // Assuming table is single select, set the selection as if | |
212 | // the mouse down event went through to the table | |
213 | fTable.setSelection (new TableItem [] {(TableItem) e.item}); | |
214 | fTable.notifyListeners (SWT.Selection, e); | |
215 | shell.dispose (); | |
216 | fTable.setFocus(); | |
217 | break; | |
218 | case SWT.MouseExit: | |
219 | case SWT.MouseWheel: | |
220 | shell.dispose (); | |
221 | break; | |
abbdd66a AM |
222 | default: |
223 | break; | |
631d853f PT |
224 | } |
225 | } | |
226 | }; | |
227 | ||
228 | Listener tableListener = new Listener () { | |
229 | Shell tip = null; | |
230 | Label label = null; | |
231 | @Override | |
f4c52cea | 232 | public void handleEvent (Event event) { |
631d853f PT |
233 | switch (event.type) { |
234 | case SWT.Dispose: | |
235 | case SWT.KeyDown: | |
236 | case SWT.MouseMove: { | |
420bceb2 PT |
237 | if (tip == null) { |
238 | break; | |
239 | } | |
631d853f PT |
240 | tip.dispose (); |
241 | tip = null; | |
242 | label = null; | |
243 | break; | |
244 | } | |
245 | case SWT.MouseHover: { | |
246 | TableItem item = fTable.getItem (new Point(event.x, event.y)); | |
247 | if (item != null) { | |
420bceb2 | 248 | for (int i=0; i < fTable.getColumnCount(); i++) { |
631d853f PT |
249 | Rectangle bounds = item.getBounds(i); |
250 | if (bounds.contains(event.x,event.y)){ | |
420bceb2 PT |
251 | if (tip != null && !tip.isDisposed()) { |
252 | tip.dispose(); | |
253 | } | |
631d853f PT |
254 | if (tooltipProvider == null) { |
255 | return; | |
631d853f | 256 | } |
abbdd66a AM |
257 | String tooltipText = tooltipProvider.getTooltip(i, item.getData()); |
258 | if (tooltipText == null) { | |
259 | return; | |
260 | } | |
261 | tip = new Shell(fTable.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); | |
262 | tip.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); | |
263 | FillLayout layout = new FillLayout(); | |
264 | layout.marginWidth = 2; | |
265 | tip.setLayout(layout); | |
266 | label = new Label(tip, SWT.WRAP); | |
267 | label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); | |
268 | label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); | |
269 | label.setData(TOOLTIP_DATA_KEY, item); | |
270 | label.setText(tooltipText); | |
271 | ||
272 | label.addListener(SWT.MouseExit, labelListener); | |
273 | label.addListener(SWT.MouseDown, labelListener); | |
274 | label.addListener(SWT.MouseWheel, labelListener); | |
275 | Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT); | |
276 | Point pt = fTable.toDisplay(bounds.x, bounds.y); | |
277 | tip.setBounds(pt.x, pt.y, size.x, size.y); | |
278 | tip.setVisible(true); | |
631d853f PT |
279 | } |
280 | } | |
281 | } | |
abbdd66a | 282 | break; |
631d853f | 283 | } |
abbdd66a AM |
284 | default: |
285 | break; | |
631d853f PT |
286 | } |
287 | } | |
288 | }; | |
289 | fTable.addListener(SWT.Dispose, tableListener); | |
290 | fTable.addListener(SWT.KeyDown, tableListener); | |
291 | fTable.addListener(SWT.MouseMove, tableListener); | |
292 | fTable.addListener(SWT.MouseHover, tableListener); | |
293 | addControlListener(new ControlAdapter() { | |
294 | @Override | |
295 | public void controlResized(ControlEvent event) { | |
296 | resize(); | |
420bceb2 PT |
297 | if (fTableItemCount > 0) { |
298 | fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows))); | |
299 | } | |
631d853f PT |
300 | } |
301 | }); | |
302 | ||
303 | // And display | |
304 | refresh(); | |
305 | } | |
306 | ||
307 | // ------------------------------------------------------------------------ | |
308 | // Table handling | |
309 | // ------------------------------------------------------------------------ | |
310 | ||
311 | /** | |
312 | * Create the table and add listeners | |
02023181 | 313 | * @param style int can be H_SCROLL, V_SCROLL, SINGLE, CHECK, FULL_SELECTION, HIDE_SELECTION, NO_SCROLL |
631d853f PT |
314 | */ |
315 | private void createTable(int style) { | |
316 | fTable = new Table(this, style | SWT.NO_SCROLL); | |
317 | ||
318 | fTable.addSelectionListener(new SelectionAdapter() { | |
319 | @Override | |
320 | public void widgetSelected(SelectionEvent event) { | |
321 | if (fTable.getSelectionIndices().length > 0) { | |
322 | handleTableSelection(); | |
323 | } | |
324 | } | |
325 | }); | |
9ccc6d01 | 326 | |
2cc874d6 | 327 | fTable.addKeyListener(new KeyListener() { |
631d853f PT |
328 | @Override |
329 | public void keyPressed(KeyEvent event) { | |
330 | handleTableKeyEvent(event); | |
331 | } | |
332 | @Override | |
333 | public void keyReleased(KeyEvent event) { | |
334 | } | |
335 | }); | |
7800a42f XR |
336 | |
337 | fTable.addListener( | |
631d853f PT |
338 | SWT.MouseDoubleClick, new Listener() { |
339 | @Override | |
340 | public void handleEvent(Event event) { | |
341 | if (doubleClickListener != null) { | |
342 | TableItem item = fTable.getItem (new Point (event.x, event.y)); | |
343 | if (item != null) { | |
344 | for(int i=0;i<fTable.getColumnCount();i++){ | |
345 | Rectangle bounds = item.getBounds(i); | |
346 | if (bounds.contains(event.x,event.y)){ | |
347 | doubleClickListener.handleDoubleClick(TmfVirtualTable.this, item, i); | |
348 | break; | |
349 | } | |
350 | } | |
351 | } | |
352 | } | |
353 | } | |
354 | } | |
355 | ); | |
356 | } | |
357 | ||
358 | /** | |
359 | * Update the rows and selected item | |
360 | */ | |
361 | private void handleTableSelection() { | |
362 | int selectedRow = fTable.getSelectionIndices()[0]; | |
363 | if (selectedRow < fFrozenRowCount) { | |
364 | fSelectedEventRank = selectedRow; | |
365 | } else { | |
366 | fSelectedEventRank = fTableTopEventRank + selectedRow; | |
367 | } | |
368 | ||
369 | /* | |
370 | * Feature in Windows. When a partially visible table item is selected, | |
371 | * after ~500 ms the top index is changed to ensure the selected item is | |
372 | * fully visible. This leaves a blank space at the bottom of the virtual | |
373 | * table. The workaround is to update the top event rank, refresh the | |
374 | * table and reset the top index to 0 after a sufficient delay. | |
375 | */ | |
376 | if (selectedRow >= fFullyVisibleRows) { | |
377 | final Display display = fTable.getDisplay(); | |
378 | Thread thread = new Thread("Top index check") { //$NON-NLS-1$ | |
379 | @Override | |
380 | public void run() { | |
381 | try { | |
382 | Thread.sleep(600); | |
383 | } catch (InterruptedException e) { | |
384 | } | |
385 | display.asyncExec(new Runnable() { | |
386 | @Override | |
b21305c2 | 387 | public void run() { |
420bceb2 PT |
388 | if (fTable.isDisposed()) { |
389 | return; | |
390 | } | |
631d853f PT |
391 | int topIndex = fTable.getTopIndex(); |
392 | if (topIndex != 0) { | |
393 | fTableTopEventRank += topIndex; | |
394 | refreshTable(); | |
395 | fSlider.setSelection(fTableTopEventRank); | |
396 | fTable.setTopIndex(0); | |
397 | } | |
398 | } | |
399 | }); | |
400 | } | |
401 | }; | |
402 | thread.start(); | |
403 | } | |
404 | } | |
405 | ||
406 | /** | |
407 | * Handle key-based navigation in table. | |
3934297e | 408 | * |
631d853f PT |
409 | * @param event |
410 | */ | |
411 | private void handleTableKeyEvent(KeyEvent event) { | |
412 | ||
413 | int lastEventRank = fTableItemCount - 1; | |
414 | int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows); | |
415 | ||
416 | int previousSelectedEventRank = fSelectedEventRank; | |
417 | int selectedRow = fSelectedEventRank - fTableTopEventRank; | |
418 | boolean needsRefresh = false; | |
419 | ||
420 | // In all case, perform the following steps: | |
421 | // - Update the selected entry rank (within valid range) | |
422 | // - Update the selected row | |
423 | // - Update the page's top entry if necessary (which also adjusts the selected row) | |
424 | // - If the top displayed entry was changed, table refresh is needed | |
425 | switch (event.keyCode) { | |
426 | ||
427 | case SWT.ARROW_DOWN: { | |
428 | event.doit = false; | |
429 | if (fSelectedEventRank < lastEventRank) { | |
430 | fSelectedEventRank++; | |
431 | selectedRow = fSelectedEventRank - fTableTopEventRank; | |
75c271cf | 432 | if (selectedRow == fFullyVisibleRows) { |
631d853f PT |
433 | fTableTopEventRank++; |
434 | needsRefresh = true; | |
75c271cf PT |
435 | } else if (selectedRow < fFrozenRowCount || selectedRow > fFullyVisibleRows) { |
436 | fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank)); | |
437 | needsRefresh = true; | |
631d853f PT |
438 | } |
439 | } | |
440 | break; | |
441 | } | |
442 | ||
443 | case SWT.ARROW_UP: { | |
444 | event.doit = false; | |
445 | if (fSelectedEventRank > 0) { | |
446 | fSelectedEventRank--; | |
447 | selectedRow = fSelectedEventRank - fTableTopEventRank; | |
75c271cf | 448 | if (selectedRow == fFrozenRowCount - 1 && fTableTopEventRank > 0) { |
631d853f PT |
449 | fTableTopEventRank--; |
450 | needsRefresh = true; | |
75c271cf PT |
451 | } else if (selectedRow < fFrozenRowCount || selectedRow > fFullyVisibleRows) { |
452 | fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank)); | |
453 | needsRefresh = true; | |
631d853f PT |
454 | } |
455 | } | |
456 | break; | |
457 | } | |
458 | ||
459 | case SWT.END: { | |
460 | event.doit = false; | |
461 | fTableTopEventRank = lastPageTopEntryRank; | |
462 | fSelectedEventRank = lastEventRank; | |
463 | needsRefresh = true; | |
464 | break; | |
465 | } | |
466 | ||
467 | case SWT.HOME: { | |
468 | event.doit = false; | |
469 | fSelectedEventRank = fFrozenRowCount; | |
470 | fTableTopEventRank = 0; | |
471 | needsRefresh = true; | |
472 | break; | |
473 | } | |
474 | ||
475 | case SWT.PAGE_DOWN: { | |
476 | event.doit = false; | |
477 | if (fSelectedEventRank < lastEventRank) { | |
478 | fSelectedEventRank += fFullyVisibleRows; | |
479 | if (fSelectedEventRank > lastEventRank) { | |
480 | fSelectedEventRank = lastEventRank; | |
481 | } | |
482 | selectedRow = fSelectedEventRank - fTableTopEventRank; | |
75c271cf | 483 | if (selectedRow > fFullyVisibleRows + fFrozenRowCount - 1 && selectedRow < 2 * fFullyVisibleRows) { |
631d853f PT |
484 | fTableTopEventRank += fFullyVisibleRows; |
485 | if (fTableTopEventRank > lastPageTopEntryRank) { | |
486 | fTableTopEventRank = lastPageTopEntryRank; | |
487 | } | |
488 | needsRefresh = true; | |
75c271cf PT |
489 | } else if (selectedRow < fFrozenRowCount || selectedRow >= 2 * fFullyVisibleRows) { |
490 | fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank)); | |
491 | needsRefresh = true; | |
631d853f PT |
492 | } |
493 | } | |
494 | break; | |
495 | } | |
496 | ||
497 | case SWT.PAGE_UP: { | |
498 | event.doit = false; | |
499 | if (fSelectedEventRank > 0) { | |
500 | fSelectedEventRank -= fFullyVisibleRows; | |
501 | if (fSelectedEventRank < fFrozenRowCount) { | |
502 | fSelectedEventRank = fFrozenRowCount; | |
503 | } | |
504 | selectedRow = fSelectedEventRank - fTableTopEventRank; | |
75c271cf | 505 | if (selectedRow < fFrozenRowCount && selectedRow > -fFullyVisibleRows) { |
631d853f PT |
506 | fTableTopEventRank -= fFullyVisibleRows; |
507 | if (fTableTopEventRank < 0) { | |
508 | fTableTopEventRank = 0; | |
509 | } | |
510 | needsRefresh = true; | |
75c271cf PT |
511 | } else if (selectedRow <= -fFullyVisibleRows || selectedRow >= fFullyVisibleRows) { |
512 | fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank)); | |
513 | needsRefresh = true; | |
631d853f PT |
514 | } |
515 | } | |
516 | break; | |
517 | } | |
518 | default: { | |
519 | return; | |
520 | } | |
521 | } | |
522 | ||
523 | boolean done = true; | |
524 | if (needsRefresh) { | |
525 | done = refreshTable(); // false if table items not updated yet in this thread | |
526 | } else { | |
527 | fTable.select(selectedRow); | |
528 | } | |
529 | ||
530 | if (fFullyVisibleRows < fTableItemCount) { | |
531 | fSlider.setSelection(fTableTopEventRank); | |
532 | } | |
533 | ||
420bceb2 | 534 | if (fSelectedEventRank != previousSelectedEventRank && fSelectedEventRank < fTableItemCount) { |
631d853f PT |
535 | if (done) { |
536 | Event e = new Event(); | |
537 | e.item = fTable.getSelection()[0]; | |
538 | fTable.notifyListeners(SWT.Selection, e); | |
539 | } else { | |
540 | fPendingSelection = true; | |
541 | } | |
542 | } | |
543 | } | |
544 | ||
02023181 MK |
545 | /** |
546 | * Method setDataItem. | |
547 | * @param index int | |
548 | * @param item TableItem | |
549 | * @return boolean | |
550 | */ | |
631d853f PT |
551 | private boolean setDataItem(int index, TableItem item) { |
552 | if (index != -1) { | |
553 | Event event = new Event(); | |
554 | event.item = item; | |
555 | if (index < fFrozenRowCount) { | |
556 | event.index = index; | |
557 | } else { | |
558 | event.index = index + fTableTopEventRank; | |
559 | } | |
560 | event.doit = true; | |
561 | fTable.notifyListeners(SWT.SetData, event); | |
562 | return event.doit; // false if table item not updated yet in this thread | |
563 | } | |
564 | return true; | |
565 | } | |
566 | ||
567 | // ------------------------------------------------------------------------ | |
568 | // Slider handling | |
569 | // ------------------------------------------------------------------------ | |
570 | ||
02023181 MK |
571 | /** |
572 | * Method createSlider. | |
573 | * @param style int | |
574 | */ | |
631d853f PT |
575 | private void createSlider(int style) { |
576 | fSlider = new Slider(this, SWT.VERTICAL | SWT.NO_FOCUS); | |
577 | fSlider.setMinimum(0); | |
578 | fSlider.setMaximum(0); | |
579 | if ((style & SWT.V_SCROLL) == 0) { | |
580 | fSlider.setVisible(false); | |
581 | } | |
582 | ||
583 | fSlider.addListener(SWT.Selection, new Listener() { | |
584 | @Override | |
585 | public void handleEvent(Event event) { | |
586 | switch (event.detail) { | |
587 | case SWT.ARROW_DOWN: | |
588 | case SWT.ARROW_UP: | |
589 | case SWT.NONE: | |
590 | case SWT.END: | |
591 | case SWT.HOME: | |
592 | case SWT.PAGE_DOWN: | |
593 | case SWT.PAGE_UP: { | |
594 | fTableTopEventRank = fSlider.getSelection(); | |
595 | refreshTable(); | |
596 | break; | |
597 | } | |
abbdd66a AM |
598 | default: |
599 | break; | |
631d853f PT |
600 | } |
601 | } | |
602 | }); | |
603 | } | |
604 | ||
605 | // ------------------------------------------------------------------------ | |
606 | // Simulated Table API | |
607 | // ------------------------------------------------------------------------ | |
608 | ||
02023181 MK |
609 | /** |
610 | * Method setHeaderVisible. | |
611 | * @param b boolean | |
612 | */ | |
631d853f PT |
613 | public void setHeaderVisible(boolean b) { |
614 | fTable.setHeaderVisible(b); | |
615 | } | |
616 | ||
02023181 MK |
617 | /** |
618 | * Method setLinesVisible. | |
619 | * @param b boolean | |
620 | */ | |
631d853f PT |
621 | public void setLinesVisible(boolean b) { |
622 | fTable.setLinesVisible(b); | |
623 | } | |
624 | ||
02023181 | 625 | /** |
3934297e | 626 | * Method getSelection. |
02023181 MK |
627 | * @return TableItem[] the items that are selected. |
628 | */ | |
631d853f PT |
629 | public TableItem[] getSelection() { |
630 | return fTable.getSelection(); | |
631 | } | |
632 | ||
02023181 MK |
633 | /** |
634 | * Method addListener. | |
635 | * @param eventType int | |
636 | * @param listener Listener | |
637 | */ | |
631d853f | 638 | @Override |
828e5592 PT |
639 | public void addListener(int eventType, Listener listener) { |
640 | fTable.addListener(eventType, listener); | |
641 | } | |
631d853f | 642 | |
02023181 MK |
643 | /** |
644 | * Method addKeyListener. | |
645 | * @param listener KeyListener | |
646 | */ | |
631d853f PT |
647 | @Override |
648 | public void addKeyListener(KeyListener listener) { | |
649 | fTable.addKeyListener(listener); | |
650 | } | |
b21305c2 | 651 | |
828e5592 | 652 | |
02023181 MK |
653 | /** |
654 | * Method addMouseListener. | |
655 | * @param listener MouseListener | |
656 | */ | |
828e5592 | 657 | @Override |
631d853f PT |
658 | public void addMouseListener(MouseListener listener) { |
659 | fTable.addMouseListener(listener); | |
660 | } | |
661 | ||
02023181 MK |
662 | /** |
663 | * Method addSelectionListener. | |
664 | * @param listener SelectionListener | |
665 | */ | |
631d853f PT |
666 | public void addSelectionListener(SelectionListener listener) { |
667 | fTable.addSelectionListener(listener); | |
668 | } | |
669 | ||
02023181 MK |
670 | /** |
671 | * Method setMenu sets the menu | |
672 | * @param menu Menu the menu | |
673 | */ | |
631d853f PT |
674 | @Override |
675 | public void setMenu(Menu menu) { | |
676 | fTable.setMenu(menu); | |
677 | } | |
678 | ||
48da630d XR |
679 | /** |
680 | * Gets the menu of this table | |
681 | * @return a Menu | |
682 | */ | |
683 | @Override | |
684 | public Menu getMenu() { | |
685 | return fTable.getMenu(); | |
686 | } | |
687 | ||
02023181 | 688 | /** |
3934297e | 689 | * Method clearAll empties a table. |
02023181 | 690 | */ |
631d853f PT |
691 | public void clearAll() { |
692 | setItemCount(0); | |
693 | } | |
694 | ||
02023181 MK |
695 | /** |
696 | * Method setItemCount | |
697 | * @param nbItems int the number of items in the table | |
3934297e | 698 | * |
02023181 | 699 | */ |
631d853f | 700 | public void setItemCount(int nbItems) { |
41b5c37f | 701 | final int nb = Math.max(0, nbItems); |
631d853f | 702 | |
41b5c37f AM |
703 | if (nb != fTableItemCount) { |
704 | fTableItemCount = nb; | |
631d853f | 705 | fTable.remove(fTableItemCount, fTable.getItemCount() - 1); |
41b5c37f | 706 | fSlider.setMaximum(nb); |
631d853f PT |
707 | resize(); |
708 | int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight()); | |
709 | fFullyVisibleRows = tableHeight / getItemHeight(); | |
710 | if (fTableItemCount > 0) { | |
711 | fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows))); | |
712 | } | |
713 | } | |
714 | } | |
715 | ||
02023181 MK |
716 | /** |
717 | * Method getItemCount. | |
718 | * @return int the number of items in the table | |
719 | */ | |
631d853f PT |
720 | public int getItemCount() { |
721 | return fTableItemCount; | |
722 | } | |
723 | ||
02023181 MK |
724 | /** |
725 | * Method getItemHeight. | |
726 | * @return int the height of a table item in pixels. (may vary from one os to another) | |
727 | */ | |
631d853f PT |
728 | public int getItemHeight() { |
729 | /* | |
730 | * Bug in Linux. The method getItemHeight doesn't always return the correct value. | |
731 | */ | |
732 | if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$ | |
733 | if (fLinuxItemHeight != 0) { | |
734 | return fLinuxItemHeight; | |
735 | } | |
736 | if (fTable.getItemCount() > 1) { | |
737 | int itemHeight = fTable.getItem(1).getBounds().y - fTable.getItem(0).getBounds().y; | |
738 | if (itemHeight > 0) { | |
739 | fLinuxItemHeight = itemHeight; | |
740 | return fLinuxItemHeight; | |
741 | } | |
742 | } | |
743 | } else { | |
744 | fLinuxItemHeight = -1; // Not Linux, don't perform os.name check anymore | |
745 | } | |
746 | return fTable.getItemHeight(); | |
747 | } | |
748 | ||
02023181 MK |
749 | /** |
750 | * Method getHeaderHeight. | |
751 | * @return int get the height of the header in pixels. | |
752 | */ | |
ea08e8ed PT |
753 | public int getHeaderHeight() { |
754 | return fTable.getHeaderHeight(); | |
755 | } | |
756 | ||
02023181 MK |
757 | /** |
758 | * Method getTopIndex. | |
759 | * @return int get the first data item index, if you have a header it is offset. | |
760 | */ | |
631d853f PT |
761 | public int getTopIndex() { |
762 | return fTableTopEventRank + fFrozenRowCount; | |
763 | } | |
764 | ||
02023181 MK |
765 | /** |
766 | * Method setTopIndex. | |
41b5c37f | 767 | * @param index int suggested top index for the table. |
02023181 | 768 | */ |
41b5c37f | 769 | public void setTopIndex(int index) { |
631d853f | 770 | if (fTableItemCount > 0) { |
41b5c37f | 771 | int i = Math.min(index, fTableItemCount - 1); |
631d853f PT |
772 | i = Math.max(i, fFrozenRowCount); |
773 | ||
774 | fTableTopEventRank = i - fFrozenRowCount; | |
775 | if (fFullyVisibleRows < fTableItemCount) { | |
776 | fSlider.setSelection(fTableTopEventRank); | |
777 | } | |
778 | ||
779 | refreshTable(); | |
780 | } | |
781 | } | |
782 | ||
02023181 MK |
783 | /** |
784 | * Method indexOf. Return the index of a table item | |
785 | * @param ti TableItem the table item to search for in the table | |
3934297e | 786 | * @return int the index of the first match. (there should only be one match) |
02023181 | 787 | */ |
631d853f PT |
788 | public int indexOf(TableItem ti) { |
789 | int index = fTable.indexOf(ti); | |
790 | if (index < fFrozenRowCount) { | |
791 | return index; | |
631d853f | 792 | } |
abbdd66a | 793 | return (index - fFrozenRowCount) + getTopIndex(); |
631d853f PT |
794 | } |
795 | ||
02023181 MK |
796 | /** |
797 | * Method getColumns. | |
3934297e | 798 | * @return TableColumn[] the table columns |
02023181 | 799 | */ |
631d853f PT |
800 | public TableColumn[] getColumns() { |
801 | return fTable.getColumns(); | |
802 | } | |
803 | ||
02023181 MK |
804 | /** |
805 | * Method getItem. | |
806 | * @param point Point the coordinates in the table | |
3934297e | 807 | * @return TableItem the corresponding table item |
02023181 | 808 | */ |
631d853f PT |
809 | public TableItem getItem(Point point) { |
810 | return fTable.getItem(point); | |
811 | } | |
812 | ||
02023181 MK |
813 | /** |
814 | * Method resize. | |
815 | */ | |
631d853f PT |
816 | private void resize() { |
817 | // Compute the numbers of rows that fit the new area | |
818 | int tableHeight = Math.max(0, getSize().y - fTable.getHeaderHeight()); | |
819 | int itemHeight = getItemHeight(); | |
820 | fTableRows = Math.min((tableHeight + itemHeight - 1) / itemHeight, fTableItemCount); | |
821 | ||
822 | if (fTableTopEventRank + fFullyVisibleRows > fTableItemCount) { | |
823 | // If we are at the end, get elements before to populate | |
824 | fTableTopEventRank = Math.max(0, fTableItemCount - fFullyVisibleRows); | |
825 | refreshTable(); | |
826 | } else if (fTableRows > fTable.getItemCount() || fTableItemCount < fTable.getItemCount()) { | |
827 | // Only refresh if new table items are needed or if table items need to be deleted | |
828 | refreshTable(); | |
829 | } | |
830 | ||
831 | } | |
832 | ||
833 | // ------------------------------------------------------------------------ | |
834 | // Controls interactions | |
835 | // ------------------------------------------------------------------------ | |
836 | ||
02023181 MK |
837 | /** |
838 | * Method setFocus. | |
839 | * @return boolean is this visible? | |
840 | */ | |
631d853f PT |
841 | @Override |
842 | public boolean setFocus() { | |
843 | boolean isVisible = isVisible(); | |
844 | if (isVisible) { | |
845 | fTable.setFocus(); | |
846 | } | |
847 | return isVisible; | |
848 | } | |
849 | ||
02023181 MK |
850 | /** |
851 | * Method refresh. | |
852 | */ | |
631d853f PT |
853 | public void refresh() { |
854 | boolean done = refreshTable(); | |
855 | if (fPendingSelection && done) { | |
856 | fPendingSelection = false; | |
857 | if (fTable.getSelection().length > 0) { | |
858 | Event e = new Event(); | |
859 | e.item = fTable.getSelection()[0]; | |
860 | fTable.notifyListeners(SWT.Selection, e); | |
861 | } | |
862 | } | |
863 | } | |
864 | ||
02023181 MK |
865 | /** |
866 | * Method setColumnHeaders. | |
3934297e | 867 | * @param columnData ColumnData[] the columndata array. |
02023181 | 868 | */ |
631d853f PT |
869 | public void setColumnHeaders(ColumnData columnData[]) { |
870 | for (int i = 0; i < columnData.length; i++) { | |
871 | TableColumn column = new TableColumn(fTable, columnData[i].alignment, i); | |
872 | column.setText(columnData[i].header); | |
873 | if (columnData[i].width > 0) { | |
874 | column.setWidth(columnData[i].width); | |
875 | } else { | |
876 | column.pack(); | |
877 | } | |
878 | } | |
879 | } | |
880 | ||
02023181 MK |
881 | /** |
882 | * Method removeAll. | |
883 | * @return int 0 the number of elements in the table | |
884 | */ | |
631d853f PT |
885 | public int removeAll() { |
886 | setItemCount(0); | |
887 | fSlider.setMaximum(0); | |
888 | fTable.removeAll(); | |
889 | fSelectedEventRank = fFrozenRowCount; | |
890 | return 0; | |
891 | } | |
892 | ||
02023181 MK |
893 | /** |
894 | * Method refreshTable. | |
3934297e | 895 | * @return boolean did all the items regresh properly? |
02023181 | 896 | */ |
631d853f PT |
897 | private boolean refreshTable() { |
898 | boolean done = true; | |
899 | for (int i = 0; i < fTableRows; i++) { | |
900 | if (i + fTableTopEventRank < fTableItemCount) { | |
901 | TableItem tableItem; | |
902 | if (i < fTable.getItemCount()) { | |
903 | tableItem = fTable.getItem(i); | |
904 | } else { | |
905 | tableItem = new TableItem(fTable, SWT.NONE); | |
906 | } | |
907 | done &= setDataItem(i, tableItem); // false if table item not updated yet in this thread | |
908 | } else { | |
909 | if (fTable.getItemCount() > fTableItemCount - fTableTopEventRank) { | |
910 | fTable.remove(fTableItemCount - fTableTopEventRank); | |
911 | } | |
912 | } | |
913 | } | |
914 | ||
915 | int lastRowOffset = fTableTopEventRank + fTableRows - 1; | |
916 | if (fSelectedEventRank < fFrozenRowCount) { | |
917 | fTable.select(fSelectedEventRank); | |
918 | } else if (!done) { | |
919 | fTable.deselectAll(); | |
920 | } else if ((fSelectedEventRank >= fTableTopEventRank + fFrozenRowCount) && (fSelectedEventRank <= lastRowOffset)) { | |
921 | int selectedRow = fSelectedEventRank - fTableTopEventRank; | |
922 | fTable.select(selectedRow); | |
923 | } else { | |
924 | fTable.deselectAll(); | |
925 | } | |
926 | return done; | |
927 | } | |
928 | ||
02023181 MK |
929 | /** |
930 | * Method setSelection. | |
41b5c37f | 931 | * @param index int the item number to select in the table. |
02023181 | 932 | */ |
41b5c37f | 933 | public void setSelection(int index) { |
631d853f | 934 | if (fTableItemCount > 0) { |
41b5c37f | 935 | int i = Math.min(index, fTableItemCount - 1); |
631d853f PT |
936 | i = Math.max(i, 0); |
937 | ||
938 | fSelectedEventRank = i; | |
939 | if ((i < fTableTopEventRank + fFrozenRowCount && i >= fFrozenRowCount) || | |
940 | (i >= fTableTopEventRank + fFullyVisibleRows)) { | |
faa38350 PT |
941 | int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows); |
942 | fTableTopEventRank = Math.max(0, Math.min(lastPageTopEntryRank, i - fFrozenRowCount - fFullyVisibleRows / 2)); | |
631d853f PT |
943 | } |
944 | if (fFullyVisibleRows < fTableItemCount) { | |
945 | fSlider.setSelection(fTableTopEventRank); | |
946 | } | |
947 | ||
948 | refreshTable(); | |
949 | ||
950 | } | |
951 | } | |
952 | ||
02023181 MK |
953 | /** |
954 | * Method getSelectionIndex. | |
955 | * @return int the table index of the selected event. not necessarrily the index of the event due to filtering. | |
956 | */ | |
631d853f PT |
957 | public int getSelectionIndex() { |
958 | int index = fTable.getSelectionIndex(); | |
959 | if (index == -1) { | |
960 | return fSelectedEventRank; | |
961 | } | |
962 | if (index < fFrozenRowCount) { | |
963 | return index; | |
631d853f | 964 | } |
abbdd66a | 965 | return (index - fFrozenRowCount) + getTopIndex(); |
631d853f PT |
966 | } |
967 | ||
02023181 MK |
968 | /** |
969 | * Method setFrozenRowCount. | |
970 | * @param count int the number of rows to freeze from the top row | |
971 | */ | |
631d853f PT |
972 | public void setFrozenRowCount(int count) { |
973 | fFrozenRowCount = count; | |
974 | refreshTable(); | |
975 | } | |
976 | ||
02023181 MK |
977 | /** |
978 | * Method createTableEditor. | |
979 | * @return a TableEditor of the table | |
980 | */ | |
631d853f PT |
981 | public TableEditor createTableEditor() { |
982 | return new TableEditor(fTable); | |
983 | } | |
984 | ||
02023181 MK |
985 | /** |
986 | * Method createTableEditorControl. | |
987 | * @param control Class<? extends Control> | |
988 | * @return Control | |
989 | */ | |
631d853f PT |
990 | public Control createTableEditorControl(Class<? extends Control> control) { |
991 | try { | |
420bceb2 | 992 | return control.getConstructor(Composite.class, int.class).newInstance(new Object[] {fTable, SWT.NONE}); |
631d853f PT |
993 | } catch (Exception e) { |
994 | Activator.getDefault().logError("Error creating table editor control", e); //$NON-NLS-1$ | |
995 | } | |
996 | return null; | |
997 | } | |
998 | ||
999 | /** | |
1000 | * @return the tooltipProvider | |
02023181 | 1001 | */ |
631d853f PT |
1002 | public TooltipProvider getTooltipProvider() { |
1003 | return tooltipProvider; | |
1004 | } | |
1005 | ||
1006 | /** | |
1007 | * @param tooltipProvider the tooltipProvider to set | |
1008 | */ | |
1009 | public void setTooltipProvider(TooltipProvider tooltipProvider) { | |
1010 | this.tooltipProvider = tooltipProvider; | |
1011 | } | |
1012 | ||
1013 | /** | |
1014 | * @return the doubleClickListener | |
02023181 | 1015 | */ |
631d853f PT |
1016 | public IDoubleClickListener getDoubleClickListener() { |
1017 | return doubleClickListener; | |
1018 | } | |
1019 | ||
1020 | /** | |
1021 | * @param doubleClickListener the doubleClickListener to set | |
1022 | */ | |
1023 | public void setDoubleClickListener(IDoubleClickListener doubleClickListener) { | |
1024 | this.doubleClickListener = doubleClickListener; | |
1025 | } | |
1026 | ||
9ccc6d01 | 1027 | } |