tmf: Bug 494810: SelectionEvent not sent after using vertical slider
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / virtualtable / TmfVirtualTable.java
1 /*******************************************************************************
2 * Copyright (c) 2010, 2016 Ericsson and others.
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Matthew Khouzam - Initial API and implementation
11 * Francois Chouinard - Refactoring, slider support, bug fixing
12 * Patrick Tasse - Improvements and bug fixing
13 * Xavier Raynaud - Improvements
14 ******************************************************************************/
15
16 package org.eclipse.tracecompass.tmf.ui.widgets.virtualtable;
17
18 import org.eclipse.swt.SWT;
19 import org.eclipse.swt.SWTException;
20 import org.eclipse.swt.custom.TableEditor;
21 import org.eclipse.swt.events.ControlAdapter;
22 import org.eclipse.swt.events.ControlEvent;
23 import org.eclipse.swt.events.FocusAdapter;
24 import org.eclipse.swt.events.FocusEvent;
25 import org.eclipse.swt.events.KeyAdapter;
26 import org.eclipse.swt.events.KeyEvent;
27 import org.eclipse.swt.events.KeyListener;
28 import org.eclipse.swt.events.MouseAdapter;
29 import org.eclipse.swt.events.MouseEvent;
30 import org.eclipse.swt.events.MouseListener;
31 import org.eclipse.swt.events.MouseWheelListener;
32 import org.eclipse.swt.events.PaintEvent;
33 import org.eclipse.swt.events.PaintListener;
34 import org.eclipse.swt.events.SelectionAdapter;
35 import org.eclipse.swt.events.SelectionEvent;
36 import org.eclipse.swt.events.SelectionListener;
37 import org.eclipse.swt.graphics.Point;
38 import org.eclipse.swt.graphics.Rectangle;
39 import org.eclipse.swt.layout.FillLayout;
40 import org.eclipse.swt.layout.GridData;
41 import org.eclipse.swt.layout.GridLayout;
42 import org.eclipse.swt.widgets.Composite;
43 import org.eclipse.swt.widgets.Control;
44 import org.eclipse.swt.widgets.Display;
45 import org.eclipse.swt.widgets.Event;
46 import org.eclipse.swt.widgets.Label;
47 import org.eclipse.swt.widgets.Listener;
48 import org.eclipse.swt.widgets.Menu;
49 import org.eclipse.swt.widgets.Shell;
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;
54 import org.eclipse.tracecompass.internal.tmf.ui.Activator;
55 import org.eclipse.ui.PlatformUI;
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).
62 *
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.
65 *
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.
68 *
69 * Styles:
70 * H_SCROLL, V_SCROLL, SINGLE, MULTI, CHECK, FULL_SELECTION, HIDE_SELECTION, NO_SCROLL
71 * @author Matthew Khouzam, Francois Chouinard, Patrick Tasse, Xavier Raynaud
72 * @version $Revision: 1.0
73 */
74 public class TmfVirtualTable extends Composite {
75
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
83 private int fSelectedEventRank = -1; // Global rank of the selected event
84 private int fSelectedBeginRank = -1; // Global rank of the selected begin event
85 private boolean fPendingSelection = false; // Pending selection update
86
87 private int fTableItemCount = 0;
88
89 // The slider
90 private Slider fSlider;
91 private SliderThrottler fSliderThrottler;
92
93 private int fLinuxItemHeight = 0; // Calculated item height for Linux workaround
94 private TooltipProvider tooltipProvider = null;
95 private IDoubleClickListener doubleClickListener = null;
96
97 private boolean fResetTopIndex = false; // Flag to trigger reset of top index
98 private ControlAdapter fResizeListener; // Resize listener to update visible rows
99
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
133 // ------------------------------------------------------------------------
134 // Constructor
135 // ------------------------------------------------------------------------
136
137 /**
138 * Standard constructor
139 *
140 * @param parent
141 * The parent composite object
142 * @param style
143 * The style to use
144 */
145 public TmfVirtualTable(Composite parent, int style) {
146 super(parent, style & (~SWT.H_SCROLL) & (~SWT.V_SCROLL) & (~SWT.SINGLE) & (~SWT.MULTI) & (~SWT.FULL_SELECTION) & (~SWT.HIDE_SELECTION) & (~SWT.CHECK));
147
148 // Create the controls
149 createTable(style & (SWT.H_SCROLL | SWT.SINGLE | SWT.MULTI | SWT.FULL_SELECTION | SWT.HIDE_SELECTION | SWT.CHECK));
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) {
174 if (fTableItemCount <= fFullyVisibleRows || event.count == 0) {
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
194 public void handleEvent(Event event) {
195 event.doit = false;
196 }
197 });
198
199 fResizeListener = new ControlAdapter() {
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 }
208 };
209 fTable.addControlListener(fResizeListener);
210
211 // Implement a "fake" tooltip
212 final String TOOLTIP_DATA_KEY = "_TABLEITEM"; //$NON-NLS-1$
213 final Listener labelListener = new Listener() {
214 @Override
215 public void handleEvent (Event event) {
216 Label label = (Label) event.widget;
217 Shell shell = label.getShell();
218 switch (event.type) {
219 case SWT.MouseDown:
220 Event e = new Event();
221 e.item = (TableItem) label.getData(TOOLTIP_DATA_KEY);
222 // Assuming table is single select, set the selection as if
223 // the mouse down event went through to the table
224 fTable.setSelection(new TableItem [] {(TableItem) e.item});
225 fTable.notifyListeners(SWT.Selection, e);
226 shell.dispose();
227 fTable.setFocus();
228 break;
229 case SWT.MouseExit:
230 case SWT.MouseWheel:
231 shell.dispose();
232 break;
233 default:
234 break;
235 }
236 }
237 };
238
239 Listener tableListener = new Listener() {
240 Shell tip = null;
241 Label label = null;
242 @Override
243 public void handleEvent(Event event) {
244 switch (event.type) {
245 case SWT.Dispose:
246 case SWT.KeyDown:
247 case SWT.MouseMove: {
248 if (tip == null) {
249 break;
250 }
251 tip.dispose();
252 tip = null;
253 label = null;
254 break;
255 }
256 case SWT.MouseHover: {
257 TableItem item = fTable.getItem(new Point(event.x, event.y));
258 if (item != null) {
259 for (int i = 0; i < fTable.getColumnCount(); i++) {
260 Rectangle bounds = item.getBounds(i);
261 if (bounds.contains(event.x, event.y)) {
262 if (tip != null && !tip.isDisposed()) {
263 tip.dispose();
264 }
265 if (tooltipProvider == null) {
266 return;
267 }
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);
290
291 // Item found, leave loop.
292 break;
293 }
294 }
295 }
296 break;
297 }
298 default:
299 break;
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();
311 if (fTableItemCount > 0) {
312 fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows)));
313 }
314 }
315 });
316
317 // And display
318 refresh();
319 }
320
321 // ------------------------------------------------------------------------
322 // Table handling
323 // ------------------------------------------------------------------------
324
325 /**
326 * Create the table and add listeners
327 * @param style int can be H_SCROLL, SINGLE, MULTI, FULL_SELECTION, HIDE_SELECTION, CHECK
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) {
335 if (event.item == null) {
336 // Override table selection from Select All action
337 refreshSelection();
338 }
339 }
340 });
341
342 fTable.addMouseListener(new MouseAdapter() {
343 @Override
344 public void mouseDown(MouseEvent e) {
345 handleTableMouseEvent(e);
346 }
347 });
348
349 fTable.addKeyListener(new KeyAdapter() {
350 @Override
351 public void keyPressed(KeyEvent event) {
352 handleTableKeyEvent(event);
353 }
354 });
355
356 fTable.addListener(
357 SWT.MouseDoubleClick, new Listener() {
358 @Override
359 public void handleEvent(Event event) {
360 if (doubleClickListener != null) {
361 TableItem item = fTable.getItem(new Point (event.x, event.y));
362 if (item != null) {
363 for (int i = 0; i < fTable.getColumnCount(); i++){
364 Rectangle bounds = item.getBounds(i);
365 if (bounds.contains(event.x, event.y)){
366 doubleClickListener.handleDoubleClick(TmfVirtualTable.this, item, i);
367 break;
368 }
369 }
370 }
371 }
372 }
373 }
374 );
375
376 /*
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.
383 */
384 fTable.addPaintListener(new PaintListener() {
385 @Override
386 public void paintControl(PaintEvent e) {
387 if (fTable.getTopIndex() != 0 || fResetTopIndex) {
388 fTable.setTopIndex(0);
389 }
390 fResetTopIndex = false;
391 }
392 });
393 }
394
395 /**
396 * Handle mouse-based selection in table.
397 *
398 * @param event the mouse event
399 */
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);
406 if (event.button == 1 || (event.button == 3 &&
407 (selectedRow < Math.min(fSelectedBeginRank, fSelectedEventRank) ||
408 selectedRow > Math.max(fSelectedBeginRank, fSelectedEventRank)))) {
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 }
417 }
418 refreshSelection();
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 }
430 }
431
432 /**
433 * Handle key-based navigation in table.
434 *
435 * @param event the key event
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;
443 int previousSelectedBeginRank = fSelectedBeginRank;
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++;
457 int selectedRow = fSelectedEventRank - fTableTopEventRank;
458 if (selectedRow == fFullyVisibleRows) {
459 fTableTopEventRank++;
460 needsRefresh = true;
461 } else if (selectedRow < fFrozenRowCount || selectedRow > fFullyVisibleRows) {
462 fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank));
463 needsRefresh = true;
464 }
465 }
466 break;
467 }
468
469 case SWT.ARROW_UP: {
470 event.doit = false;
471 if (fSelectedEventRank > 0) {
472 fSelectedEventRank--;
473 int selectedRow = fSelectedEventRank - fTableTopEventRank;
474 if (selectedRow == fFrozenRowCount - 1 && fTableTopEventRank > 0) {
475 fTableTopEventRank--;
476 needsRefresh = true;
477 } else if (selectedRow < fFrozenRowCount || selectedRow > fFullyVisibleRows) {
478 fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank));
479 needsRefresh = true;
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 }
508 int selectedRow = fSelectedEventRank - fTableTopEventRank;
509 if (selectedRow > fFullyVisibleRows + fFrozenRowCount - 1 && selectedRow < 2 * fFullyVisibleRows) {
510 fTableTopEventRank += fFullyVisibleRows;
511 if (fTableTopEventRank > lastPageTopEntryRank) {
512 fTableTopEventRank = lastPageTopEntryRank;
513 }
514 needsRefresh = true;
515 } else if (selectedRow < fFrozenRowCount || selectedRow >= 2 * fFullyVisibleRows) {
516 fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank));
517 needsRefresh = true;
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 }
530 int selectedRow = fSelectedEventRank - fTableTopEventRank;
531 if (selectedRow < fFrozenRowCount && selectedRow > -fFullyVisibleRows) {
532 fTableTopEventRank -= fFullyVisibleRows;
533 if (fTableTopEventRank < 0) {
534 fTableTopEventRank = 0;
535 }
536 needsRefresh = true;
537 } else if (selectedRow <= -fFullyVisibleRows || selectedRow >= fFullyVisibleRows) {
538 fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank));
539 needsRefresh = true;
540 }
541 }
542 break;
543 }
544 default: {
545 return;
546 }
547 }
548
549 if ((event.stateMask & SWT.SHIFT) == 0 || (fTable.getStyle() & SWT.MULTI) == 0 || fSelectedBeginRank == -1) {
550 fSelectedBeginRank = fSelectedEventRank;
551 }
552
553 boolean done = true;
554 if (needsRefresh) {
555 done = refreshTable(); // false if table items not updated yet in this thread
556 } else {
557 refreshSelection();
558 }
559
560 if (fFullyVisibleRows < fTableItemCount) {
561 fSlider.setSelection(fTableTopEventRank);
562 }
563
564 if (fSelectedEventRank != previousSelectedEventRank || fSelectedBeginRank != previousSelectedBeginRank) {
565 if (done) {
566 Event e = new Event();
567 e.item = fTable.getItem(fSelectedEventRank - fTableTopEventRank);
568 fTable.notifyListeners(SWT.Selection, e);
569 } else {
570 fPendingSelection = true;
571 }
572 }
573 }
574
575 /**
576 * Method setDataItem.
577 * @param index int
578 * @param item TableItem
579 * @return boolean
580 */
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
601 /**
602 * Method createSlider.
603 * @param style int
604 */
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:
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 }
627 case SWT.DRAG:
628 case SWT.NONE:
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;
640 default:
641 break;
642 }
643 }
644 });
645
646 /*
647 * When the mouse button is released, perform the refresh immediately
648 * and interrupt and discard the slider throttler.
649 */
650 fSlider.addMouseListener(new MouseAdapter() {
651 @Override
652 public void mouseUp(MouseEvent e) {
653 if (fSliderThrottler != null) {
654 fSliderThrottler.interrupt();
655 fSliderThrottler = null;
656 }
657 fTableTopEventRank = fSlider.getSelection();
658 refreshTable();
659 }
660 });
661
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 });
672 }
673
674 // ------------------------------------------------------------------------
675 // Simulated Table API
676 // ------------------------------------------------------------------------
677
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
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
705 /**
706 * Method setHeaderVisible.
707 * @param b boolean
708 */
709 public void setHeaderVisible(boolean b) {
710 fTable.setHeaderVisible(b);
711 }
712
713 /**
714 * Method setLinesVisible.
715 * @param b boolean
716 */
717 public void setLinesVisible(boolean b) {
718 fTable.setLinesVisible(b);
719 }
720
721 /**
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>
737 */
738 public TableItem[] getSelection() {
739 return fTable.getSelection();
740 }
741
742 /**
743 * Method addListener.
744 * @param eventType int
745 * @param listener Listener
746 */
747 @Override
748 public void addListener(int eventType, Listener listener) {
749 fTable.addListener(eventType, listener);
750 }
751
752 /**
753 * Method addKeyListener.
754 * @param listener KeyListener
755 */
756 @Override
757 public void addKeyListener(KeyListener listener) {
758 fTable.addKeyListener(listener);
759 }
760
761 /**
762 * Method addMouseListener.
763 * @param listener MouseListener
764 */
765 @Override
766 public void addMouseListener(MouseListener listener) {
767 fTable.addMouseListener(listener);
768 }
769
770 /**
771 * Method addSelectionListener.
772 * @param listener SelectionListener
773 */
774 public void addSelectionListener(SelectionListener listener) {
775 fTable.addSelectionListener(listener);
776 }
777
778 /**
779 * Method setMenu sets the menu
780 * @param menu Menu the menu
781 */
782 @Override
783 public void setMenu(Menu menu) {
784 fTable.setMenu(menu);
785 }
786
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
796 /**
797 * Method clearAll empties a table.
798 */
799 public void clearAll() {
800 setItemCount(0);
801 }
802
803 /**
804 * Method setItemCount
805 * @param nbItems int the number of items in the table
806 *
807 */
808 public void setItemCount(int nbItems) {
809 final int nb = Math.max(0, nbItems);
810
811 if (nb != fTableItemCount) {
812 fTableItemCount = nb;
813 fTable.remove(fTableItemCount, fTable.getItemCount() - 1);
814 fSlider.setMaximum(nb);
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
824 /**
825 * Method getItemCount.
826 * @return int the number of items in the table
827 */
828 public int getItemCount() {
829 return fTableItemCount;
830 }
831
832 /**
833 * Method getItemHeight.
834 * @return int the height of a table item in pixels. (may vary from one os to another)
835 */
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
857 /**
858 * Method getHeaderHeight.
859 * @return int get the height of the header in pixels.
860 */
861 public int getHeaderHeight() {
862 return fTable.getHeaderHeight();
863 }
864
865 /**
866 * Method getTopIndex.
867 * @return int get the first data item index, if you have a header it is offset.
868 */
869 public int getTopIndex() {
870 return fTableTopEventRank + fFrozenRowCount;
871 }
872
873 /**
874 * Method setTopIndex.
875 * @param index int suggested top index for the table.
876 */
877 public void setTopIndex(int index) {
878 if (fTableItemCount > 0) {
879 int i = Math.min(index, fTableItemCount - 1);
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
891 /**
892 * Method indexOf. Return the index of a table item
893 * @param ti TableItem the table item to search for in the table
894 * @return int the index of the first match. (there should only be one match)
895 */
896 public int indexOf(TableItem ti) {
897 int index = fTable.indexOf(ti);
898 if (index < fFrozenRowCount) {
899 return index;
900 }
901 return (index - fFrozenRowCount) + getTopIndex();
902 }
903
904 /**
905 * Method getColumns.
906 * @return TableColumn[] the table columns
907 */
908 public TableColumn[] getColumns() {
909 return fTable.getColumns();
910 }
911
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
942 /**
943 * Method getItem.
944 * @param point Point the coordinates in the table
945 * @return TableItem the corresponding table item
946 */
947 public TableItem getItem(Point point) {
948 return fTable.getItem(point);
949 }
950
951 /**
952 * Method resize.
953 */
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
975 /**
976 * Method setFocus.
977 * @return boolean is this visible?
978 */
979 @Override
980 public boolean setFocus() {
981 boolean isVisible = isVisible();
982 if (isVisible) {
983 fTable.setFocus();
984 }
985 return isVisible;
986 }
987
988 /**
989 * Method refresh.
990 */
991 public void refresh() {
992 boolean done = refreshTable();
993 if (!done) {
994 return;
995 }
996 if (fPendingSelection) {
997 fPendingSelection = false;
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) {
1005 Event e = new Event();
1006 e.item = item;
1007 fTable.notifyListeners(SWT.Selection, e);
1008 }
1009 }
1010 }
1011
1012 /**
1013 * Method setColumnHeaders.
1014 *
1015 * @param columnData
1016 * ColumnData[] the columndata array.
1017 */
1018 @Deprecated
1019 public void setColumnHeaders(ColumnData columnData[]) {
1020 /* No-op */
1021 }
1022
1023 /**
1024 * Method removeAll.
1025 * @return int 0 the number of elements in the table
1026 */
1027 public int removeAll() {
1028 setItemCount(0);
1029 fSlider.setMaximum(0);
1030 fTable.removeAll();
1031 fSelectedEventRank = -1;
1032 fSelectedBeginRank = fSelectedEventRank;
1033 return 0;
1034 }
1035
1036 /**
1037 * Method refreshTable.
1038 * @return true if all table items have been refreshed, false otherwise
1039 */
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 }
1057 if (done) {
1058 refreshSelection();
1059 } else {
1060 fTable.deselectAll();
1061 }
1062 return done;
1063 }
1064
1065 private void refreshSelection() {
1066 int lastRowOffset = fTableTopEventRank + fTableRows - 1;
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);
1089 /* Reset origin in case partially visible item selected */
1090 fTable.setTopIndex(0);
1091 if (startRank == fSelectedEventRank) {
1092 fTable.select(start);
1093 } else {
1094 fTable.select(end);
1095 }
1096 } else {
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 }
1114 fTable.deselectAll();
1115 }
1116 }
1117
1118 /**
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>
1129 */
1130 public void setSelection(int index) {
1131 setSelectionRange(index, index);
1132 }
1133
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)) {
1164 int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows);
1165 fTableTopEventRank = Math.max(0, Math.min(lastPageTopEntryRank, selection - fFrozenRowCount - fFullyVisibleRows / 2));
1166 }
1167 if (fFullyVisibleRows < fTableItemCount) {
1168 fSlider.setSelection(fTableTopEventRank);
1169 }
1170 refreshTable();
1171 }
1172 }
1173
1174 /**
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
1179 */
1180 public int getSelectionIndex() {
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
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 };
1198 }
1199 return new int[] { fSelectedBeginRank, fSelectedEventRank };
1200 }
1201
1202 /**
1203 * Method setFrozenRowCount.
1204 * @param count int the number of rows to freeze from the top row
1205 */
1206 public void setFrozenRowCount(int count) {
1207 fFrozenRowCount = count;
1208 refreshTable();
1209 }
1210
1211 /**
1212 * Method createTableEditor.
1213 * @return a TableEditor of the table
1214 */
1215 public TableEditor createTableEditor() {
1216 return new TableEditor(fTable);
1217 }
1218
1219 /**
1220 * Method createTableEditorControl.
1221 * @param control Class<? extends Control>
1222 * @return Control
1223 */
1224 public Control createTableEditorControl(Class<? extends Control> control) {
1225 try {
1226 return control.getConstructor(Composite.class, int.class).newInstance(new Object[] {fTable, SWT.NONE});
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
1235 */
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
1249 */
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
1261 }
This page took 0.064801 seconds and 5 git commands to generate.