Commit | Line | Data |
---|---|---|
9ccc6d01 | 1 | /******************************************************************************* |
2e200fc7 | 2 | * Copyright (c) 2010, 2016 Ericsson and others. |
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 | ||
2bdf0193 | 16 | package org.eclipse.tracecompass.tmf.ui.widgets.virtualtable; |
9ccc6d01 FC |
17 | |
18 | import org.eclipse.swt.SWT; | |
3f43dc48 | 19 | import org.eclipse.swt.SWTException; |
b21305c2 | 20 | import org.eclipse.swt.custom.TableEditor; |
9ccc6d01 FC |
21 | import org.eclipse.swt.events.ControlAdapter; |
22 | import org.eclipse.swt.events.ControlEvent; | |
2e200fc7 PT |
23 | import org.eclipse.swt.events.FocusAdapter; |
24 | import org.eclipse.swt.events.FocusEvent; | |
3f43dc48 | 25 | import org.eclipse.swt.events.KeyAdapter; |
9ccc6d01 FC |
26 | import org.eclipse.swt.events.KeyEvent; |
27 | import org.eclipse.swt.events.KeyListener; | |
3f43dc48 | 28 | import org.eclipse.swt.events.MouseAdapter; |
9ccc6d01 | 29 | import org.eclipse.swt.events.MouseEvent; |
b21305c2 | 30 | import org.eclipse.swt.events.MouseListener; |
9ccc6d01 | 31 | import org.eclipse.swt.events.MouseWheelListener; |
dd03e2f1 PT |
32 | import org.eclipse.swt.events.PaintEvent; |
33 | import org.eclipse.swt.events.PaintListener; | |
9ccc6d01 FC |
34 | import org.eclipse.swt.events.SelectionAdapter; |
35 | import org.eclipse.swt.events.SelectionEvent; | |
b21305c2 FC |
36 | import org.eclipse.swt.events.SelectionListener; |
37 | import org.eclipse.swt.graphics.Point; | |
7800a42f XR |
38 | import org.eclipse.swt.graphics.Rectangle; |
39 | import org.eclipse.swt.layout.FillLayout; | |
9ccc6d01 FC |
40 | import org.eclipse.swt.layout.GridData; |
41 | import org.eclipse.swt.layout.GridLayout; | |
42 | import org.eclipse.swt.widgets.Composite; | |
b21305c2 | 43 | import org.eclipse.swt.widgets.Control; |
22ba2f65 | 44 | import org.eclipse.swt.widgets.Display; |
9ccc6d01 | 45 | import org.eclipse.swt.widgets.Event; |
7800a42f | 46 | import org.eclipse.swt.widgets.Label; |
9ccc6d01 | 47 | import org.eclipse.swt.widgets.Listener; |
b21305c2 | 48 | import org.eclipse.swt.widgets.Menu; |
7800a42f | 49 | import org.eclipse.swt.widgets.Shell; |
9ccc6d01 FC |
50 | import org.eclipse.swt.widgets.Slider; |
51 | import org.eclipse.swt.widgets.Table; | |
52 | import org.eclipse.swt.widgets.TableColumn; | |
53 | import org.eclipse.swt.widgets.TableItem; | |
2bdf0193 | 54 | import org.eclipse.tracecompass.internal.tmf.ui.Activator; |
7800a42f | 55 | import org.eclipse.ui.PlatformUI; |
9ccc6d01 FC |
56 | |
57 | /** | |
58 | * <b><u>TmfVirtualTable</u></b> | |
59 | * <p> | |
60 | * TmfVirtualTable allows for the tabular display of arbitrarily large data sets | |
61 | * (well, up to Integer.MAX_VALUE or ~2G rows). | |
3934297e | 62 | * |
9ccc6d01 FC |
63 | * It is essentially a Composite of Table and Slider, where the number of rows |
64 | * in the table is set to fill the table display area. The slider is rank-based. | |
3934297e | 65 | * |
9ccc6d01 FC |
66 | * It differs from Table with the VIRTUAL style flag where an empty entry is |
67 | * created for each virtual row. This does not scale well for very large data sets. | |
3934297e | 68 | * |
b21305c2 | 69 | * Styles: |
3f43dc48 | 70 | * H_SCROLL, V_SCROLL, SINGLE, MULTI, CHECK, FULL_SELECTION, HIDE_SELECTION, NO_SCROLL |
02023181 | 71 | * @author Matthew Khouzam, Francois Chouinard, Patrick Tasse, Xavier Raynaud |
3934297e | 72 | * @version $Revision: 1.0 |
9ccc6d01 FC |
73 | */ |
74 | public class TmfVirtualTable extends Composite { | |
75 | ||
631d853f PT |
76 | // The table |
77 | private Table fTable; | |
78 | private int fTableRows = 0; // Number of table rows | |
79 | private int fFullyVisibleRows = 0; // Number of fully visible table rows | |
80 | private int fFrozenRowCount = 0; // Number of frozen table rows at top of table | |
81 | ||
82 | private int fTableTopEventRank = 0; // Global rank of the first entry displayed | |
3f43dc48 PT |
83 | private int fSelectedEventRank = -1; // Global rank of the selected event |
84 | private int fSelectedBeginRank = -1; // Global rank of the selected begin event | |
631d853f PT |
85 | private boolean fPendingSelection = false; // Pending selection update |
86 | ||
dadf6e1a | 87 | private int fTableItemCount = 0; |
631d853f PT |
88 | |
89 | // The slider | |
90 | private Slider fSlider; | |
22ba2f65 | 91 | private SliderThrottler fSliderThrottler; |
631d853f PT |
92 | |
93 | private int fLinuxItemHeight = 0; // Calculated item height for Linux workaround | |
94 | private TooltipProvider tooltipProvider = null; | |
95 | private IDoubleClickListener doubleClickListener = null; | |
96 | ||
b06f64b7 PT |
97 | private boolean fResetTopIndex = false; // Flag to trigger reset of top index |
98 | private ControlAdapter fResizeListener; // Resize listener to update visible rows | |
c9787691 | 99 | |
22ba2f65 PT |
100 | // ------------------------------------------------------------------------ |
101 | // Classes | |
102 | // ------------------------------------------------------------------------ | |
103 | ||
104 | private class SliderThrottler extends Thread { | |
105 | private static final long DELAY = 400L; | |
106 | private static final long POLLING_INTERVAL = 10L; | |
107 | ||
108 | @Override | |
109 | public void run() { | |
110 | final long startTime = System.currentTimeMillis(); | |
111 | while ((System.currentTimeMillis() - startTime) < DELAY) { | |
112 | try { | |
113 | Thread.sleep(POLLING_INTERVAL); | |
114 | } catch (InterruptedException e) { | |
115 | } | |
116 | } | |
117 | Display.getDefault().asyncExec(new Runnable() { | |
118 | @Override | |
119 | public void run() { | |
120 | if (fSliderThrottler != SliderThrottler.this) { | |
121 | return; | |
122 | } | |
123 | fSliderThrottler = null; | |
124 | if (SliderThrottler.this.isInterrupted() || fTable.isDisposed()) { | |
125 | return; | |
126 | } | |
127 | refreshTable(); | |
128 | } | |
129 | }); | |
130 | } | |
131 | } | |
132 | ||
631d853f PT |
133 | // ------------------------------------------------------------------------ |
134 | // Constructor | |
135 | // ------------------------------------------------------------------------ | |
136 | ||
137 | /** | |
3934297e AM |
138 | * Standard constructor |
139 | * | |
631d853f | 140 | * @param parent |
3934297e | 141 | * The parent composite object |
631d853f | 142 | * @param style |
3934297e | 143 | * The style to use |
631d853f PT |
144 | */ |
145 | public TmfVirtualTable(Composite parent, int style) { | |
3f43dc48 | 146 | super(parent, style & (~SWT.H_SCROLL) & (~SWT.V_SCROLL) & (~SWT.SINGLE) & (~SWT.MULTI) & (~SWT.FULL_SELECTION) & (~SWT.HIDE_SELECTION) & (~SWT.CHECK)); |
631d853f PT |
147 | |
148 | // Create the controls | |
3f43dc48 | 149 | createTable(style & (SWT.H_SCROLL | SWT.SINGLE | SWT.MULTI | SWT.FULL_SELECTION | SWT.HIDE_SELECTION | SWT.CHECK)); |
631d853f PT |
150 | createSlider(style & SWT.V_SCROLL); |
151 | ||
152 | // Prevent the slider from being traversed | |
153 | setTabList(new Control[] { fTable }); | |
154 | ||
155 | // Set the layout | |
156 | GridLayout gridLayout = new GridLayout(); | |
157 | gridLayout.numColumns = 2; | |
158 | gridLayout.horizontalSpacing = 0; | |
159 | gridLayout.verticalSpacing = 0; | |
160 | gridLayout.marginWidth = 0; | |
161 | gridLayout.marginHeight = 0; | |
162 | setLayout(gridLayout); | |
163 | ||
164 | GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true); | |
165 | fTable.setLayoutData(tableGridData); | |
166 | ||
167 | GridData sliderGridData = new GridData(SWT.FILL, SWT.FILL, false, true); | |
168 | fSlider.setLayoutData(sliderGridData); | |
169 | ||
170 | // Add the listeners | |
171 | fTable.addMouseWheelListener(new MouseWheelListener() { | |
172 | @Override | |
173 | public void mouseScrolled(MouseEvent event) { | |
93cfc823 | 174 | if (fTableItemCount <= fFullyVisibleRows || event.count == 0) { |
631d853f PT |
175 | return; |
176 | } | |
177 | fTableTopEventRank -= event.count; | |
178 | if (fTableTopEventRank < 0) { | |
179 | fTableTopEventRank = 0; | |
180 | } | |
181 | int latestFirstRowOffset = fTableItemCount - fFullyVisibleRows; | |
182 | if (fTableTopEventRank > latestFirstRowOffset) { | |
183 | fTableTopEventRank = latestFirstRowOffset; | |
184 | } | |
185 | ||
186 | fSlider.setSelection(fTableTopEventRank); | |
187 | refreshTable(); | |
188 | } | |
189 | }); | |
190 | ||
191 | fTable.addListener(SWT.MouseWheel, new Listener() { | |
192 | // disable mouse scroll of horizontal scroll bar | |
193 | @Override | |
b21305c2 | 194 | public void handleEvent(Event event) { |
631d853f PT |
195 | event.doit = false; |
196 | } | |
197 | }); | |
198 | ||
b06f64b7 | 199 | fResizeListener = new ControlAdapter() { |
631d853f PT |
200 | @Override |
201 | public void controlResized(ControlEvent event) { | |
202 | int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight()); | |
203 | fFullyVisibleRows = tableHeight / getItemHeight(); | |
204 | if (fTableItemCount > 0) { | |
205 | fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows))); | |
206 | } | |
207 | } | |
b06f64b7 PT |
208 | }; |
209 | fTable.addControlListener(fResizeListener); | |
210 | ||
631d853f PT |
211 | // Implement a "fake" tooltip |
212 | final String TOOLTIP_DATA_KEY = "_TABLEITEM"; //$NON-NLS-1$ | |
dadf6e1a | 213 | final Listener labelListener = new Listener() { |
631d853f | 214 | @Override |
f4c52cea | 215 | public void handleEvent (Event event) { |
dadf6e1a EB |
216 | Label label = (Label) event.widget; |
217 | Shell shell = label.getShell(); | |
631d853f PT |
218 | switch (event.type) { |
219 | case SWT.MouseDown: | |
dadf6e1a EB |
220 | Event e = new Event(); |
221 | e.item = (TableItem) label.getData(TOOLTIP_DATA_KEY); | |
631d853f PT |
222 | // Assuming table is single select, set the selection as if |
223 | // the mouse down event went through to the table | |
dadf6e1a EB |
224 | fTable.setSelection(new TableItem [] {(TableItem) e.item}); |
225 | fTable.notifyListeners(SWT.Selection, e); | |
226 | shell.dispose(); | |
631d853f PT |
227 | fTable.setFocus(); |
228 | break; | |
229 | case SWT.MouseExit: | |
230 | case SWT.MouseWheel: | |
dadf6e1a | 231 | shell.dispose(); |
631d853f | 232 | break; |
abbdd66a AM |
233 | default: |
234 | break; | |
631d853f PT |
235 | } |
236 | } | |
237 | }; | |
238 | ||
dadf6e1a | 239 | Listener tableListener = new Listener() { |
631d853f PT |
240 | Shell tip = null; |
241 | Label label = null; | |
242 | @Override | |
dadf6e1a | 243 | public void handleEvent(Event event) { |
631d853f PT |
244 | switch (event.type) { |
245 | case SWT.Dispose: | |
246 | case SWT.KeyDown: | |
247 | case SWT.MouseMove: { | |
420bceb2 PT |
248 | if (tip == null) { |
249 | break; | |
250 | } | |
dadf6e1a | 251 | tip.dispose(); |
631d853f PT |
252 | tip = null; |
253 | label = null; | |
254 | break; | |
255 | } | |
256 | case SWT.MouseHover: { | |
dadf6e1a | 257 | TableItem item = fTable.getItem(new Point(event.x, event.y)); |
631d853f | 258 | if (item != null) { |
dadf6e1a | 259 | for (int i = 0; i < fTable.getColumnCount(); i++) { |
631d853f | 260 | Rectangle bounds = item.getBounds(i); |
dadf6e1a | 261 | if (bounds.contains(event.x, event.y)) { |
420bceb2 PT |
262 | if (tip != null && !tip.isDisposed()) { |
263 | tip.dispose(); | |
264 | } | |
631d853f PT |
265 | if (tooltipProvider == null) { |
266 | return; | |
631d853f | 267 | } |
abbdd66a AM |
268 | String tooltipText = tooltipProvider.getTooltip(i, item.getData()); |
269 | if (tooltipText == null) { | |
270 | return; | |
271 | } | |
272 | tip = new Shell(fTable.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); | |
273 | tip.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); | |
274 | FillLayout layout = new FillLayout(); | |
275 | layout.marginWidth = 2; | |
276 | tip.setLayout(layout); | |
277 | label = new Label(tip, SWT.WRAP); | |
278 | label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); | |
279 | label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); | |
280 | label.setData(TOOLTIP_DATA_KEY, item); | |
281 | label.setText(tooltipText); | |
282 | ||
283 | label.addListener(SWT.MouseExit, labelListener); | |
284 | label.addListener(SWT.MouseDown, labelListener); | |
285 | label.addListener(SWT.MouseWheel, labelListener); | |
286 | Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT); | |
287 | Point pt = fTable.toDisplay(bounds.x, bounds.y); | |
288 | tip.setBounds(pt.x, pt.y, size.x, size.y); | |
289 | tip.setVisible(true); | |
dadf6e1a EB |
290 | |
291 | // Item found, leave loop. | |
292 | break; | |
631d853f PT |
293 | } |
294 | } | |
295 | } | |
abbdd66a | 296 | break; |
631d853f | 297 | } |
abbdd66a AM |
298 | default: |
299 | break; | |
631d853f PT |
300 | } |
301 | } | |
302 | }; | |
303 | fTable.addListener(SWT.Dispose, tableListener); | |
304 | fTable.addListener(SWT.KeyDown, tableListener); | |
305 | fTable.addListener(SWT.MouseMove, tableListener); | |
306 | fTable.addListener(SWT.MouseHover, tableListener); | |
307 | addControlListener(new ControlAdapter() { | |
308 | @Override | |
309 | public void controlResized(ControlEvent event) { | |
310 | resize(); | |
420bceb2 PT |
311 | if (fTableItemCount > 0) { |
312 | fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows))); | |
313 | } | |
631d853f PT |
314 | } |
315 | }); | |
316 | ||
317 | // And display | |
318 | refresh(); | |
319 | } | |
320 | ||
321 | // ------------------------------------------------------------------------ | |
322 | // Table handling | |
323 | // ------------------------------------------------------------------------ | |
324 | ||
325 | /** | |
326 | * Create the table and add listeners | |
3f43dc48 | 327 | * @param style int can be H_SCROLL, SINGLE, MULTI, FULL_SELECTION, HIDE_SELECTION, CHECK |
631d853f PT |
328 | */ |
329 | private void createTable(int style) { | |
330 | fTable = new Table(this, style | SWT.NO_SCROLL); | |
331 | ||
332 | fTable.addSelectionListener(new SelectionAdapter() { | |
333 | @Override | |
334 | public void widgetSelected(SelectionEvent event) { | |
3f43dc48 PT |
335 | if (event.item == null) { |
336 | // Override table selection from Select All action | |
337 | refreshSelection(); | |
631d853f PT |
338 | } |
339 | } | |
340 | }); | |
9ccc6d01 | 341 | |
3f43dc48 | 342 | fTable.addMouseListener(new MouseAdapter() { |
631d853f | 343 | @Override |
3f43dc48 PT |
344 | public void mouseDown(MouseEvent e) { |
345 | handleTableMouseEvent(e); | |
631d853f | 346 | } |
3f43dc48 PT |
347 | }); |
348 | ||
349 | fTable.addKeyListener(new KeyAdapter() { | |
631d853f | 350 | @Override |
3f43dc48 PT |
351 | public void keyPressed(KeyEvent event) { |
352 | handleTableKeyEvent(event); | |
631d853f PT |
353 | } |
354 | }); | |
7800a42f XR |
355 | |
356 | fTable.addListener( | |
631d853f PT |
357 | SWT.MouseDoubleClick, new Listener() { |
358 | @Override | |
359 | public void handleEvent(Event event) { | |
360 | if (doubleClickListener != null) { | |
dadf6e1a | 361 | TableItem item = fTable.getItem(new Point (event.x, event.y)); |
631d853f | 362 | if (item != null) { |
dadf6e1a | 363 | for (int i = 0; i < fTable.getColumnCount(); i++){ |
631d853f | 364 | Rectangle bounds = item.getBounds(i); |
dadf6e1a | 365 | if (bounds.contains(event.x, event.y)){ |
631d853f PT |
366 | doubleClickListener.handleDoubleClick(TmfVirtualTable.this, item, i); |
367 | break; | |
368 | } | |
369 | } | |
370 | } | |
371 | } | |
372 | } | |
373 | } | |
374 | ); | |
dd03e2f1 PT |
375 | |
376 | /* | |
c9787691 PT |
377 | * Feature in Windows. When a partially visible table item is selected, |
378 | * after ~500 ms the top index is changed to ensure the selected item is | |
379 | * fully visible. This leaves a blank space at the bottom of the virtual | |
380 | * table. The workaround is to reset the top index to 0 if it is not 0. | |
381 | * Also reset the top index to 0 if indicated by the flag that was set | |
382 | * at table selection of a partially visible table item. | |
dd03e2f1 PT |
383 | */ |
384 | fTable.addPaintListener(new PaintListener() { | |
385 | @Override | |
386 | public void paintControl(PaintEvent e) { | |
c9787691 PT |
387 | if (fTable.getTopIndex() != 0 || fResetTopIndex) { |
388 | fTable.setTopIndex(0); | |
389 | } | |
390 | fResetTopIndex = false; | |
dd03e2f1 PT |
391 | } |
392 | }); | |
631d853f PT |
393 | } |
394 | ||
395 | /** | |
3f43dc48 PT |
396 | * Handle mouse-based selection in table. |
397 | * | |
398 | * @param event the mouse event | |
631d853f | 399 | */ |
3f43dc48 PT |
400 | private void handleTableMouseEvent(MouseEvent event) { |
401 | TableItem item = fTable.getItem(new Point(event.x, event.y)); | |
402 | if (item == null) { | |
403 | return; | |
404 | } | |
405 | int selectedRow = indexOf(item); | |
90328647 PT |
406 | if (event.button == 1 || (event.button == 3 && |
407 | (selectedRow < Math.min(fSelectedBeginRank, fSelectedEventRank) || | |
408 | selectedRow > Math.max(fSelectedBeginRank, fSelectedEventRank)))) { | |
3f43dc48 PT |
409 | if (selectedRow >= 0) { |
410 | fSelectedEventRank = selectedRow; | |
411 | } else { | |
412 | fSelectedEventRank = -1; | |
413 | } | |
414 | if ((event.stateMask & SWT.SHIFT) == 0 || (fTable.getStyle() & SWT.MULTI) == 0 || fSelectedBeginRank == -1) { | |
415 | fSelectedBeginRank = fSelectedEventRank; | |
416 | } | |
631d853f | 417 | } |
3f43dc48 | 418 | refreshSelection(); |
c9787691 PT |
419 | |
420 | /* | |
421 | * Feature in Linux. When a partially visible table item is selected, | |
422 | * the origin is changed to ensure the selected item is fully visible. | |
423 | * This makes the first row partially visible. The solution is to force | |
424 | * reset the origin by setting the top index to 0. This should happen | |
425 | * only once at the next redraw by the paint listener. | |
426 | */ | |
427 | if (selectedRow >= fFullyVisibleRows) { | |
428 | fResetTopIndex = true; | |
429 | } | |
631d853f PT |
430 | } |
431 | ||
432 | /** | |
433 | * Handle key-based navigation in table. | |
3934297e | 434 | * |
3f43dc48 | 435 | * @param event the key event |
631d853f PT |
436 | */ |
437 | private void handleTableKeyEvent(KeyEvent event) { | |
438 | ||
439 | int lastEventRank = fTableItemCount - 1; | |
440 | int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows); | |
441 | ||
442 | int previousSelectedEventRank = fSelectedEventRank; | |
3f43dc48 | 443 | int previousSelectedBeginRank = fSelectedBeginRank; |
631d853f PT |
444 | boolean needsRefresh = false; |
445 | ||
446 | // In all case, perform the following steps: | |
447 | // - Update the selected entry rank (within valid range) | |
448 | // - Update the selected row | |
449 | // - Update the page's top entry if necessary (which also adjusts the selected row) | |
450 | // - If the top displayed entry was changed, table refresh is needed | |
451 | switch (event.keyCode) { | |
452 | ||
453 | case SWT.ARROW_DOWN: { | |
454 | event.doit = false; | |
455 | if (fSelectedEventRank < lastEventRank) { | |
456 | fSelectedEventRank++; | |
3f43dc48 | 457 | int selectedRow = fSelectedEventRank - fTableTopEventRank; |
75c271cf | 458 | if (selectedRow == fFullyVisibleRows) { |
631d853f PT |
459 | fTableTopEventRank++; |
460 | needsRefresh = true; | |
75c271cf PT |
461 | } else if (selectedRow < fFrozenRowCount || selectedRow > fFullyVisibleRows) { |
462 | fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank)); | |
463 | needsRefresh = true; | |
631d853f PT |
464 | } |
465 | } | |
466 | break; | |
467 | } | |
468 | ||
469 | case SWT.ARROW_UP: { | |
470 | event.doit = false; | |
471 | if (fSelectedEventRank > 0) { | |
472 | fSelectedEventRank--; | |
3f43dc48 | 473 | int selectedRow = fSelectedEventRank - fTableTopEventRank; |
75c271cf | 474 | if (selectedRow == fFrozenRowCount - 1 && fTableTopEventRank > 0) { |
631d853f PT |
475 | fTableTopEventRank--; |
476 | needsRefresh = true; | |
75c271cf PT |
477 | } else if (selectedRow < fFrozenRowCount || selectedRow > fFullyVisibleRows) { |
478 | fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank)); | |
479 | needsRefresh = true; | |
631d853f PT |
480 | } |
481 | } | |
482 | break; | |
483 | } | |
484 | ||
485 | case SWT.END: { | |
486 | event.doit = false; | |
487 | fTableTopEventRank = lastPageTopEntryRank; | |
488 | fSelectedEventRank = lastEventRank; | |
489 | needsRefresh = true; | |
490 | break; | |
491 | } | |
492 | ||
493 | case SWT.HOME: { | |
494 | event.doit = false; | |
495 | fSelectedEventRank = fFrozenRowCount; | |
496 | fTableTopEventRank = 0; | |
497 | needsRefresh = true; | |
498 | break; | |
499 | } | |
500 | ||
501 | case SWT.PAGE_DOWN: { | |
502 | event.doit = false; | |
503 | if (fSelectedEventRank < lastEventRank) { | |
504 | fSelectedEventRank += fFullyVisibleRows; | |
505 | if (fSelectedEventRank > lastEventRank) { | |
506 | fSelectedEventRank = lastEventRank; | |
507 | } | |
3f43dc48 | 508 | int selectedRow = fSelectedEventRank - fTableTopEventRank; |
75c271cf | 509 | if (selectedRow > fFullyVisibleRows + fFrozenRowCount - 1 && selectedRow < 2 * fFullyVisibleRows) { |
631d853f PT |
510 | fTableTopEventRank += fFullyVisibleRows; |
511 | if (fTableTopEventRank > lastPageTopEntryRank) { | |
512 | fTableTopEventRank = lastPageTopEntryRank; | |
513 | } | |
514 | needsRefresh = true; | |
75c271cf PT |
515 | } else if (selectedRow < fFrozenRowCount || selectedRow >= 2 * fFullyVisibleRows) { |
516 | fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank)); | |
517 | needsRefresh = true; | |
631d853f PT |
518 | } |
519 | } | |
520 | break; | |
521 | } | |
522 | ||
523 | case SWT.PAGE_UP: { | |
524 | event.doit = false; | |
525 | if (fSelectedEventRank > 0) { | |
526 | fSelectedEventRank -= fFullyVisibleRows; | |
527 | if (fSelectedEventRank < fFrozenRowCount) { | |
528 | fSelectedEventRank = fFrozenRowCount; | |
529 | } | |
3f43dc48 | 530 | int selectedRow = fSelectedEventRank - fTableTopEventRank; |
75c271cf | 531 | if (selectedRow < fFrozenRowCount && selectedRow > -fFullyVisibleRows) { |
631d853f PT |
532 | fTableTopEventRank -= fFullyVisibleRows; |
533 | if (fTableTopEventRank < 0) { | |
534 | fTableTopEventRank = 0; | |
535 | } | |
536 | needsRefresh = true; | |
75c271cf PT |
537 | } else if (selectedRow <= -fFullyVisibleRows || selectedRow >= fFullyVisibleRows) { |
538 | fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank)); | |
539 | needsRefresh = true; | |
631d853f PT |
540 | } |
541 | } | |
542 | break; | |
543 | } | |
544 | default: { | |
545 | return; | |
546 | } | |
547 | } | |
548 | ||
3f43dc48 PT |
549 | if ((event.stateMask & SWT.SHIFT) == 0 || (fTable.getStyle() & SWT.MULTI) == 0 || fSelectedBeginRank == -1) { |
550 | fSelectedBeginRank = fSelectedEventRank; | |
551 | } | |
552 | ||
631d853f PT |
553 | boolean done = true; |
554 | if (needsRefresh) { | |
555 | done = refreshTable(); // false if table items not updated yet in this thread | |
556 | } else { | |
3f43dc48 | 557 | refreshSelection(); |
631d853f PT |
558 | } |
559 | ||
560 | if (fFullyVisibleRows < fTableItemCount) { | |
561 | fSlider.setSelection(fTableTopEventRank); | |
562 | } | |
563 | ||
3f43dc48 | 564 | if (fSelectedEventRank != previousSelectedEventRank || fSelectedBeginRank != previousSelectedBeginRank) { |
631d853f PT |
565 | if (done) { |
566 | Event e = new Event(); | |
3f43dc48 | 567 | e.item = fTable.getItem(fSelectedEventRank - fTableTopEventRank); |
631d853f PT |
568 | fTable.notifyListeners(SWT.Selection, e); |
569 | } else { | |
570 | fPendingSelection = true; | |
571 | } | |
572 | } | |
573 | } | |
574 | ||
02023181 MK |
575 | /** |
576 | * Method setDataItem. | |
577 | * @param index int | |
578 | * @param item TableItem | |
579 | * @return boolean | |
580 | */ | |
631d853f PT |
581 | private boolean setDataItem(int index, TableItem item) { |
582 | if (index != -1) { | |
583 | Event event = new Event(); | |
584 | event.item = item; | |
585 | if (index < fFrozenRowCount) { | |
586 | event.index = index; | |
587 | } else { | |
588 | event.index = index + fTableTopEventRank; | |
589 | } | |
590 | event.doit = true; | |
591 | fTable.notifyListeners(SWT.SetData, event); | |
592 | return event.doit; // false if table item not updated yet in this thread | |
593 | } | |
594 | return true; | |
595 | } | |
596 | ||
597 | // ------------------------------------------------------------------------ | |
598 | // Slider handling | |
599 | // ------------------------------------------------------------------------ | |
600 | ||
02023181 MK |
601 | /** |
602 | * Method createSlider. | |
603 | * @param style int | |
604 | */ | |
631d853f PT |
605 | private void createSlider(int style) { |
606 | fSlider = new Slider(this, SWT.VERTICAL | SWT.NO_FOCUS); | |
607 | fSlider.setMinimum(0); | |
608 | fSlider.setMaximum(0); | |
609 | if ((style & SWT.V_SCROLL) == 0) { | |
610 | fSlider.setVisible(false); | |
611 | } | |
612 | ||
613 | fSlider.addListener(SWT.Selection, new Listener() { | |
614 | @Override | |
615 | public void handleEvent(Event event) { | |
616 | switch (event.detail) { | |
617 | case SWT.ARROW_DOWN: | |
618 | case SWT.ARROW_UP: | |
631d853f PT |
619 | case SWT.END: |
620 | case SWT.HOME: | |
621 | case SWT.PAGE_DOWN: | |
622 | case SWT.PAGE_UP: { | |
623 | fTableTopEventRank = fSlider.getSelection(); | |
624 | refreshTable(); | |
625 | break; | |
626 | } | |
22ba2f65 | 627 | case SWT.DRAG: |
874af45a | 628 | case SWT.NONE: |
22ba2f65 PT |
629 | /* |
630 | * While the slider thumb is being dragged, only perform the | |
631 | * refresh periodically. The event detail during the drag is | |
632 | * SWT.DRAG on Windows and SWT.NONE on Linux. | |
633 | */ | |
634 | fTableTopEventRank = fSlider.getSelection(); | |
635 | if (fSliderThrottler == null) { | |
636 | fSliderThrottler = new SliderThrottler(); | |
637 | fSliderThrottler.start(); | |
638 | } | |
639 | break; | |
abbdd66a AM |
640 | default: |
641 | break; | |
631d853f PT |
642 | } |
643 | } | |
644 | }); | |
874af45a PT |
645 | |
646 | /* | |
22ba2f65 PT |
647 | * When the mouse button is released, perform the refresh immediately |
648 | * and interrupt and discard the slider throttler. | |
874af45a PT |
649 | */ |
650 | fSlider.addMouseListener(new MouseAdapter() { | |
651 | @Override | |
652 | public void mouseUp(MouseEvent e) { | |
22ba2f65 PT |
653 | if (fSliderThrottler != null) { |
654 | fSliderThrottler.interrupt(); | |
655 | fSliderThrottler = null; | |
656 | } | |
874af45a PT |
657 | fTableTopEventRank = fSlider.getSelection(); |
658 | refreshTable(); | |
659 | } | |
660 | }); | |
661 | ||
2e200fc7 PT |
662 | /* |
663 | * The SWT.NO_FOCUS style is only a hint and is not always respected. If | |
664 | * the slider gains focus, give it back to the table. | |
665 | */ | |
666 | fSlider.addFocusListener(new FocusAdapter() { | |
667 | @Override | |
668 | public void focusGained(FocusEvent e) { | |
669 | fTable.setFocus(); | |
670 | } | |
671 | }); | |
631d853f PT |
672 | } |
673 | ||
674 | // ------------------------------------------------------------------------ | |
675 | // Simulated Table API | |
676 | // ------------------------------------------------------------------------ | |
677 | ||
2fcd5eea AM |
678 | /** |
679 | * Constructs a new TableColumn instance given a style value describing its | |
680 | * alignment behavior. The column is added to the end of the columns | |
681 | * maintained by the table. | |
682 | * | |
683 | * @param style | |
684 | * the alignment style | |
685 | * @return the new TableColumn | |
686 | * | |
687 | * @see SWT#LEFT | |
688 | * @see SWT#RIGHT | |
689 | * @see SWT#CENTER | |
2fcd5eea AM |
690 | */ |
691 | public TableColumn newTableColumn(int style) { | |
692 | TableColumn column = new TableColumn(fTable, style); | |
693 | ||
694 | /* | |
695 | * In Linux the table does not receive a control resized event when | |
696 | * a table column resize causes the horizontal scroll bar to become | |
697 | * visible or invisible, so a resize listener must be added to every | |
698 | * table column to properly update the number of fully visible rows. | |
699 | */ | |
700 | column.addControlListener(fResizeListener); | |
701 | ||
702 | return column; | |
703 | } | |
704 | ||
02023181 MK |
705 | /** |
706 | * Method setHeaderVisible. | |
707 | * @param b boolean | |
708 | */ | |
631d853f PT |
709 | public void setHeaderVisible(boolean b) { |
710 | fTable.setHeaderVisible(b); | |
711 | } | |
712 | ||
02023181 MK |
713 | /** |
714 | * Method setLinesVisible. | |
715 | * @param b boolean | |
716 | */ | |
631d853f PT |
717 | public void setLinesVisible(boolean b) { |
718 | fTable.setLinesVisible(b); | |
719 | } | |
720 | ||
02023181 | 721 | /** |
3f43dc48 PT |
722 | * Returns an array of <code>TableItem</code>s that are currently selected |
723 | * in the receiver. The order of the items is unspecified. An empty array | |
724 | * indicates that no items are selected. | |
725 | * <p> | |
726 | * Note: This array only contains the visible selected items in the virtual | |
727 | * table. To get information about the full selection range, use | |
728 | * {@link #getSelectionIndices()}. | |
729 | * </p> | |
730 | * | |
731 | * @return an array representing the selection | |
732 | * | |
733 | * @exception SWTException <ul> | |
734 | * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> | |
735 | * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> | |
736 | * </ul> | |
02023181 | 737 | */ |
631d853f PT |
738 | public TableItem[] getSelection() { |
739 | return fTable.getSelection(); | |
740 | } | |
741 | ||
02023181 MK |
742 | /** |
743 | * Method addListener. | |
744 | * @param eventType int | |
745 | * @param listener Listener | |
746 | */ | |
631d853f | 747 | @Override |
828e5592 PT |
748 | public void addListener(int eventType, Listener listener) { |
749 | fTable.addListener(eventType, listener); | |
750 | } | |
631d853f | 751 | |
02023181 MK |
752 | /** |
753 | * Method addKeyListener. | |
754 | * @param listener KeyListener | |
755 | */ | |
631d853f PT |
756 | @Override |
757 | public void addKeyListener(KeyListener listener) { | |
758 | fTable.addKeyListener(listener); | |
759 | } | |
b21305c2 | 760 | |
02023181 MK |
761 | /** |
762 | * Method addMouseListener. | |
763 | * @param listener MouseListener | |
764 | */ | |
828e5592 | 765 | @Override |
631d853f PT |
766 | public void addMouseListener(MouseListener listener) { |
767 | fTable.addMouseListener(listener); | |
768 | } | |
769 | ||
02023181 MK |
770 | /** |
771 | * Method addSelectionListener. | |
772 | * @param listener SelectionListener | |
773 | */ | |
631d853f PT |
774 | public void addSelectionListener(SelectionListener listener) { |
775 | fTable.addSelectionListener(listener); | |
776 | } | |
777 | ||
02023181 MK |
778 | /** |
779 | * Method setMenu sets the menu | |
780 | * @param menu Menu the menu | |
781 | */ | |
631d853f PT |
782 | @Override |
783 | public void setMenu(Menu menu) { | |
784 | fTable.setMenu(menu); | |
785 | } | |
786 | ||
48da630d XR |
787 | /** |
788 | * Gets the menu of this table | |
789 | * @return a Menu | |
790 | */ | |
791 | @Override | |
792 | public Menu getMenu() { | |
793 | return fTable.getMenu(); | |
794 | } | |
795 | ||
02023181 | 796 | /** |
3934297e | 797 | * Method clearAll empties a table. |
02023181 | 798 | */ |
631d853f PT |
799 | public void clearAll() { |
800 | setItemCount(0); | |
801 | } | |
802 | ||
02023181 MK |
803 | /** |
804 | * Method setItemCount | |
805 | * @param nbItems int the number of items in the table | |
d5efe032 | 806 | * |
02023181 | 807 | */ |
631d853f | 808 | public void setItemCount(int nbItems) { |
41b5c37f | 809 | final int nb = Math.max(0, nbItems); |
631d853f | 810 | |
41b5c37f AM |
811 | if (nb != fTableItemCount) { |
812 | fTableItemCount = nb; | |
631d853f | 813 | fTable.remove(fTableItemCount, fTable.getItemCount() - 1); |
41b5c37f | 814 | fSlider.setMaximum(nb); |
631d853f PT |
815 | resize(); |
816 | int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight()); | |
817 | fFullyVisibleRows = tableHeight / getItemHeight(); | |
818 | if (fTableItemCount > 0) { | |
819 | fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows))); | |
820 | } | |
821 | } | |
822 | } | |
823 | ||
02023181 MK |
824 | /** |
825 | * Method getItemCount. | |
826 | * @return int the number of items in the table | |
827 | */ | |
631d853f PT |
828 | public int getItemCount() { |
829 | return fTableItemCount; | |
830 | } | |
831 | ||
02023181 MK |
832 | /** |
833 | * Method getItemHeight. | |
834 | * @return int the height of a table item in pixels. (may vary from one os to another) | |
835 | */ | |
631d853f PT |
836 | public int getItemHeight() { |
837 | /* | |
838 | * Bug in Linux. The method getItemHeight doesn't always return the correct value. | |
839 | */ | |
840 | if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$ | |
841 | if (fLinuxItemHeight != 0) { | |
842 | return fLinuxItemHeight; | |
843 | } | |
844 | if (fTable.getItemCount() > 1) { | |
845 | int itemHeight = fTable.getItem(1).getBounds().y - fTable.getItem(0).getBounds().y; | |
846 | if (itemHeight > 0) { | |
847 | fLinuxItemHeight = itemHeight; | |
848 | return fLinuxItemHeight; | |
849 | } | |
850 | } | |
851 | } else { | |
852 | fLinuxItemHeight = -1; // Not Linux, don't perform os.name check anymore | |
853 | } | |
854 | return fTable.getItemHeight(); | |
855 | } | |
856 | ||
02023181 MK |
857 | /** |
858 | * Method getHeaderHeight. | |
859 | * @return int get the height of the header in pixels. | |
860 | */ | |
ea08e8ed PT |
861 | public int getHeaderHeight() { |
862 | return fTable.getHeaderHeight(); | |
863 | } | |
864 | ||
02023181 MK |
865 | /** |
866 | * Method getTopIndex. | |
867 | * @return int get the first data item index, if you have a header it is offset. | |
868 | */ | |
631d853f PT |
869 | public int getTopIndex() { |
870 | return fTableTopEventRank + fFrozenRowCount; | |
871 | } | |
872 | ||
02023181 MK |
873 | /** |
874 | * Method setTopIndex. | |
41b5c37f | 875 | * @param index int suggested top index for the table. |
02023181 | 876 | */ |
41b5c37f | 877 | public void setTopIndex(int index) { |
631d853f | 878 | if (fTableItemCount > 0) { |
41b5c37f | 879 | int i = Math.min(index, fTableItemCount - 1); |
631d853f PT |
880 | i = Math.max(i, fFrozenRowCount); |
881 | ||
882 | fTableTopEventRank = i - fFrozenRowCount; | |
883 | if (fFullyVisibleRows < fTableItemCount) { | |
884 | fSlider.setSelection(fTableTopEventRank); | |
885 | } | |
886 | ||
887 | refreshTable(); | |
888 | } | |
889 | } | |
890 | ||
02023181 MK |
891 | /** |
892 | * Method indexOf. Return the index of a table item | |
893 | * @param ti TableItem the table item to search for in the table | |
3934297e | 894 | * @return int the index of the first match. (there should only be one match) |
02023181 | 895 | */ |
631d853f PT |
896 | public int indexOf(TableItem ti) { |
897 | int index = fTable.indexOf(ti); | |
898 | if (index < fFrozenRowCount) { | |
899 | return index; | |
631d853f | 900 | } |
abbdd66a | 901 | return (index - fFrozenRowCount) + getTopIndex(); |
631d853f PT |
902 | } |
903 | ||
02023181 MK |
904 | /** |
905 | * Method getColumns. | |
3934297e | 906 | * @return TableColumn[] the table columns |
02023181 | 907 | */ |
631d853f PT |
908 | public TableColumn[] getColumns() { |
909 | return fTable.getColumns(); | |
910 | } | |
911 | ||
6817308e PT |
912 | /** |
913 | * Returns an array of zero-relative integers that map | |
914 | * the creation order of the receiver's columns to the | |
915 | * order in which they are currently being displayed. | |
916 | * <p> | |
917 | * Specifically, the indices of the returned array represent | |
918 | * the current visual order of the columns, and the contents | |
919 | * of the array represent the creation order of the columns. | |
920 | * | |
921 | * @return the current visual order of the receiver's columns | |
922 | */ | |
923 | public int[] getColumnOrder() { | |
924 | return fTable.getColumnOrder(); | |
925 | } | |
926 | ||
927 | /** | |
928 | * Sets the order that the columns in the receiver should | |
929 | * be displayed in to the given argument which is described | |
930 | * in terms of the zero-relative ordering of when the columns | |
931 | * were added. | |
932 | * <p> | |
933 | * Specifically, the contents of the array represent the | |
934 | * original position of each column at the time its creation. | |
935 | * | |
936 | * @param order the new order to display the columns | |
937 | */ | |
938 | public void setColumnOrder(int[] order) { | |
939 | fTable.setColumnOrder(order); | |
940 | } | |
941 | ||
02023181 MK |
942 | /** |
943 | * Method getItem. | |
944 | * @param point Point the coordinates in the table | |
3934297e | 945 | * @return TableItem the corresponding table item |
02023181 | 946 | */ |
631d853f PT |
947 | public TableItem getItem(Point point) { |
948 | return fTable.getItem(point); | |
949 | } | |
950 | ||
02023181 MK |
951 | /** |
952 | * Method resize. | |
953 | */ | |
631d853f PT |
954 | private void resize() { |
955 | // Compute the numbers of rows that fit the new area | |
956 | int tableHeight = Math.max(0, getSize().y - fTable.getHeaderHeight()); | |
957 | int itemHeight = getItemHeight(); | |
958 | fTableRows = Math.min((tableHeight + itemHeight - 1) / itemHeight, fTableItemCount); | |
959 | ||
960 | if (fTableTopEventRank + fFullyVisibleRows > fTableItemCount) { | |
961 | // If we are at the end, get elements before to populate | |
962 | fTableTopEventRank = Math.max(0, fTableItemCount - fFullyVisibleRows); | |
963 | refreshTable(); | |
964 | } else if (fTableRows > fTable.getItemCount() || fTableItemCount < fTable.getItemCount()) { | |
965 | // Only refresh if new table items are needed or if table items need to be deleted | |
966 | refreshTable(); | |
967 | } | |
968 | ||
969 | } | |
970 | ||
971 | // ------------------------------------------------------------------------ | |
972 | // Controls interactions | |
973 | // ------------------------------------------------------------------------ | |
974 | ||
02023181 MK |
975 | /** |
976 | * Method setFocus. | |
977 | * @return boolean is this visible? | |
978 | */ | |
631d853f PT |
979 | @Override |
980 | public boolean setFocus() { | |
981 | boolean isVisible = isVisible(); | |
982 | if (isVisible) { | |
983 | fTable.setFocus(); | |
984 | } | |
985 | return isVisible; | |
986 | } | |
987 | ||
02023181 MK |
988 | /** |
989 | * Method refresh. | |
990 | */ | |
631d853f PT |
991 | public void refresh() { |
992 | boolean done = refreshTable(); | |
3f43dc48 PT |
993 | if (!done) { |
994 | return; | |
995 | } | |
dadf6e1a | 996 | if (fPendingSelection) { |
631d853f | 997 | fPendingSelection = false; |
3f43dc48 PT |
998 | TableItem item = null; |
999 | if (fSelectedEventRank >= 0 && fSelectedEventRank < fFrozenRowCount) { | |
1000 | item = fTable.getItem(fSelectedEventRank); | |
1001 | } else if (fSelectedEventRank >= fTableTopEventRank + fFrozenRowCount && fSelectedEventRank - fTableTopEventRank < fTable.getItemCount()) { | |
1002 | item = fTable.getItem(fSelectedEventRank - fTableTopEventRank); | |
1003 | } | |
1004 | if (item != null) { | |
631d853f | 1005 | Event e = new Event(); |
3f43dc48 | 1006 | e.item = item; |
631d853f PT |
1007 | fTable.notifyListeners(SWT.Selection, e); |
1008 | } | |
1009 | } | |
1010 | } | |
1011 | ||
02023181 MK |
1012 | /** |
1013 | * Method setColumnHeaders. | |
baafe54c AM |
1014 | * |
1015 | * @param columnData | |
1016 | * ColumnData[] the columndata array. | |
02023181 | 1017 | */ |
baafe54c | 1018 | @Deprecated |
631d853f | 1019 | public void setColumnHeaders(ColumnData columnData[]) { |
baafe54c AM |
1020 | /* No-op */ |
1021 | } | |
1022 | ||
02023181 MK |
1023 | /** |
1024 | * Method removeAll. | |
1025 | * @return int 0 the number of elements in the table | |
1026 | */ | |
631d853f PT |
1027 | public int removeAll() { |
1028 | setItemCount(0); | |
1029 | fSlider.setMaximum(0); | |
1030 | fTable.removeAll(); | |
3f43dc48 PT |
1031 | fSelectedEventRank = -1; |
1032 | fSelectedBeginRank = fSelectedEventRank; | |
631d853f PT |
1033 | return 0; |
1034 | } | |
1035 | ||
02023181 MK |
1036 | /** |
1037 | * Method refreshTable. | |
3f43dc48 | 1038 | * @return true if all table items have been refreshed, false otherwise |
02023181 | 1039 | */ |
631d853f PT |
1040 | private boolean refreshTable() { |
1041 | boolean done = true; | |
1042 | for (int i = 0; i < fTableRows; i++) { | |
1043 | if (i + fTableTopEventRank < fTableItemCount) { | |
1044 | TableItem tableItem; | |
1045 | if (i < fTable.getItemCount()) { | |
1046 | tableItem = fTable.getItem(i); | |
1047 | } else { | |
1048 | tableItem = new TableItem(fTable, SWT.NONE); | |
1049 | } | |
1050 | done &= setDataItem(i, tableItem); // false if table item not updated yet in this thread | |
1051 | } else { | |
1052 | if (fTable.getItemCount() > fTableItemCount - fTableTopEventRank) { | |
1053 | fTable.remove(fTableItemCount - fTableTopEventRank); | |
1054 | } | |
1055 | } | |
1056 | } | |
3f43dc48 PT |
1057 | if (done) { |
1058 | refreshSelection(); | |
1059 | } else { | |
1060 | fTable.deselectAll(); | |
1061 | } | |
1062 | return done; | |
1063 | } | |
631d853f | 1064 | |
3f43dc48 | 1065 | private void refreshSelection() { |
631d853f | 1066 | int lastRowOffset = fTableTopEventRank + fTableRows - 1; |
3f43dc48 PT |
1067 | int startRank = Math.min(fSelectedBeginRank, fSelectedEventRank); |
1068 | int endRank = Math.max(fSelectedBeginRank, fSelectedEventRank); | |
1069 | int start = Integer.MAX_VALUE; | |
1070 | int end = Integer.MIN_VALUE; | |
1071 | if (startRank < fFrozenRowCount) { | |
1072 | start = startRank; | |
1073 | } else if (startRank < fTableTopEventRank + fFrozenRowCount) { | |
1074 | start = fFrozenRowCount; | |
1075 | } else if (startRank <= lastRowOffset) { | |
1076 | start = startRank - fTableTopEventRank; | |
1077 | } | |
1078 | if (endRank < fFrozenRowCount) { | |
1079 | end = endRank; | |
1080 | } else if (endRank < fTableTopEventRank + fFrozenRowCount) { | |
1081 | end = fFrozenRowCount - 1; | |
1082 | } else if (endRank <= lastRowOffset) { | |
1083 | end = endRank - fTableTopEventRank; | |
1084 | } else { | |
1085 | end = fTableRows - 1; | |
1086 | } | |
1087 | if (start <= end) { | |
1088 | fTable.setSelection(start, end); | |
2e200fc7 PT |
1089 | /* Reset origin in case partially visible item selected */ |
1090 | fTable.setTopIndex(0); | |
3f43dc48 PT |
1091 | if (startRank == fSelectedEventRank) { |
1092 | fTable.select(start); | |
1093 | } else { | |
1094 | fTable.select(end); | |
1095 | } | |
631d853f | 1096 | } else { |
2e200fc7 PT |
1097 | /* |
1098 | * In GTK2, when the table is given focus, one table item is | |
1099 | * highlighted even if there is no selection. In that case the | |
1100 | * highlighted item is the last selected item. Make that last | |
1101 | * selected item the top or bottom item depending on if the | |
1102 | * out-of-range selection is above or below the visible items. | |
1103 | */ | |
1104 | if (SWT.getPlatform().equals("gtk") && fTableRows > 0) { //$NON-NLS-1$ | |
1105 | fTable.setRedraw(false); | |
1106 | if (start < Integer.MAX_VALUE) { | |
1107 | fTable.setSelection(0); | |
1108 | } else { | |
1109 | fTable.setSelection(fTableRows - 1); | |
1110 | fTable.setTopIndex(0); | |
1111 | } | |
1112 | fTable.setRedraw(true); | |
1113 | } | |
631d853f PT |
1114 | fTable.deselectAll(); |
1115 | } | |
631d853f PT |
1116 | } |
1117 | ||
02023181 | 1118 | /** |
3f43dc48 PT |
1119 | * Selects the item at the given zero-relative index in the receiver. |
1120 | * The current selection is first cleared, then the new item is selected, | |
1121 | * and if necessary the receiver is scrolled to make the new selection visible. | |
1122 | * | |
1123 | * @param index the index of the item to select | |
1124 | * | |
1125 | * @exception SWTException <ul> | |
1126 | * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> | |
1127 | * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> | |
1128 | * </ul> | |
02023181 | 1129 | */ |
41b5c37f | 1130 | public void setSelection(int index) { |
8b6aedef JCK |
1131 | setSelectionRange(index, index); |
1132 | } | |
631d853f | 1133 | |
8b6aedef JCK |
1134 | /** |
1135 | * Selects a range of items starting at the given zero-relative index in the | |
1136 | * receiver. The current selection is first cleared, then the new items are | |
1137 | * selected, and if necessary the receiver is scrolled to make the new | |
1138 | * selection visible. | |
1139 | * | |
1140 | * @param beginIndex | |
1141 | * The index of the first item in the selection range | |
1142 | * @param endIndex | |
1143 | * The index of the last item in the selection range | |
1144 | * | |
1145 | * @exception SWTException | |
1146 | * <ul> | |
1147 | * <li>ERROR_WIDGET_DISPOSED - if the receiver has been | |
1148 | * disposed</li> | |
1149 | * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the | |
1150 | * thread that created the receiver</li> | |
1151 | * </ul> | |
1152 | * @since 2.0 | |
1153 | */ | |
1154 | public void setSelectionRange(int beginIndex, int endIndex) { | |
1155 | if (fTableItemCount > 0) { | |
1156 | int begin = Math.max(Math.min(beginIndex, fTableItemCount - 1), 0); | |
1157 | int end = Math.max(Math.min(endIndex, fTableItemCount - 1), 0); | |
1158 | int selection = fSelectedBeginRank != begin ? begin : end; | |
1159 | ||
1160 | fSelectedBeginRank = begin; | |
1161 | fSelectedEventRank = end; | |
1162 | if ((selection < fTableTopEventRank + fFrozenRowCount && selection >= fFrozenRowCount) || | |
1163 | (selection >= fTableTopEventRank + fFullyVisibleRows)) { | |
faa38350 | 1164 | int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows); |
8b6aedef | 1165 | fTableTopEventRank = Math.max(0, Math.min(lastPageTopEntryRank, selection - fFrozenRowCount - fFullyVisibleRows / 2)); |
631d853f PT |
1166 | } |
1167 | if (fFullyVisibleRows < fTableItemCount) { | |
1168 | fSlider.setSelection(fTableTopEventRank); | |
1169 | } | |
631d853f | 1170 | refreshTable(); |
631d853f PT |
1171 | } |
1172 | } | |
1173 | ||
02023181 | 1174 | /** |
3f43dc48 PT |
1175 | * Returns the zero-relative index of the item which is currently |
1176 | * selected in the receiver, or -1 if no item is selected. | |
1177 | * | |
1178 | * @return the index of the selected item | |
02023181 | 1179 | */ |
631d853f | 1180 | public int getSelectionIndex() { |
3f43dc48 PT |
1181 | return fSelectedEventRank; |
1182 | } | |
1183 | ||
1184 | /** | |
1185 | * Returns an index array representing the selection range. If there is a | |
1186 | * single item selected the array holds one index. If there is a selected | |
1187 | * range the first item in the array is the start index of the selection and | |
1188 | * the second item is the end index of the selection, which is the item most | |
1189 | * recently selected. The array is empty if no items are selected. | |
1190 | * <p> | |
1191 | * @return the array of indices of the selected items | |
3f43dc48 PT |
1192 | */ |
1193 | public int[] getSelectionIndices() { | |
1194 | if (fSelectedEventRank < 0 || fSelectedBeginRank < 0) { | |
1195 | return new int[] {}; | |
1196 | } else if (fSelectedEventRank == fSelectedBeginRank) { | |
1197 | return new int[] { fSelectedEventRank }; | |
631d853f | 1198 | } |
3f43dc48 | 1199 | return new int[] { fSelectedBeginRank, fSelectedEventRank }; |
631d853f PT |
1200 | } |
1201 | ||
02023181 MK |
1202 | /** |
1203 | * Method setFrozenRowCount. | |
1204 | * @param count int the number of rows to freeze from the top row | |
1205 | */ | |
631d853f PT |
1206 | public void setFrozenRowCount(int count) { |
1207 | fFrozenRowCount = count; | |
1208 | refreshTable(); | |
1209 | } | |
1210 | ||
02023181 MK |
1211 | /** |
1212 | * Method createTableEditor. | |
1213 | * @return a TableEditor of the table | |
1214 | */ | |
631d853f PT |
1215 | public TableEditor createTableEditor() { |
1216 | return new TableEditor(fTable); | |
1217 | } | |
1218 | ||
02023181 MK |
1219 | /** |
1220 | * Method createTableEditorControl. | |
1221 | * @param control Class<? extends Control> | |
1222 | * @return Control | |
1223 | */ | |
631d853f PT |
1224 | public Control createTableEditorControl(Class<? extends Control> control) { |
1225 | try { | |
420bceb2 | 1226 | return control.getConstructor(Composite.class, int.class).newInstance(new Object[] {fTable, SWT.NONE}); |
631d853f PT |
1227 | } catch (Exception e) { |
1228 | Activator.getDefault().logError("Error creating table editor control", e); //$NON-NLS-1$ | |
1229 | } | |
1230 | return null; | |
1231 | } | |
1232 | ||
1233 | /** | |
1234 | * @return the tooltipProvider | |
d5efe032 | 1235 | */ |
631d853f PT |
1236 | public TooltipProvider getTooltipProvider() { |
1237 | return tooltipProvider; | |
1238 | } | |
1239 | ||
1240 | /** | |
1241 | * @param tooltipProvider the tooltipProvider to set | |
1242 | */ | |
1243 | public void setTooltipProvider(TooltipProvider tooltipProvider) { | |
1244 | this.tooltipProvider = tooltipProvider; | |
1245 | } | |
1246 | ||
1247 | /** | |
1248 | * @return the doubleClickListener | |
d5efe032 | 1249 | */ |
631d853f PT |
1250 | public IDoubleClickListener getDoubleClickListener() { |
1251 | return doubleClickListener; | |
1252 | } | |
1253 | ||
1254 | /** | |
1255 | * @param doubleClickListener the doubleClickListener to set | |
1256 | */ | |
1257 | public void setDoubleClickListener(IDoubleClickListener doubleClickListener) { | |
1258 | this.doubleClickListener = doubleClickListener; | |
1259 | } | |
1260 | ||
9ccc6d01 | 1261 | } |