blob: 373303ab6d43099dfd99a8928e20836b6720b2a9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2018 Ericsson and others.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Matthew Khouzam - Initial API and implementation
* Francois Chouinard - Refactoring, slider support, bug fixing
* Patrick Tasse - Improvements and bug fixing
* Xavier Raynaud - Improvements
******************************************************************************/
package org.eclipse.tracecompass.tmf.ui.widgets.virtualtable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.ui.PlatformUI;
/**
* <b><u>TmfVirtualTable</u></b>
* <p>
* TmfVirtualTable allows for the tabular display of arbitrarily large data sets
* (well, up to Integer.MAX_VALUE or ~2G rows).
*
* It is essentially a Composite of Table and Slider, where the number of rows
* in the table is set to fill the table display area. The slider is rank-based.
*
* It differs from Table with the VIRTUAL style flag where an empty entry is
* created for each virtual row. This does not scale well for very large data sets.
*
* Styles:
* H_SCROLL, V_SCROLL, SINGLE, MULTI, CHECK, FULL_SELECTION, HIDE_SELECTION, NO_SCROLL
* @author Matthew Khouzam, Francois Chouinard, Patrick Tasse, Xavier Raynaud
* @version $Revision: 1.0
*/
public class TmfVirtualTable extends Composite {
// The table
private Table fTable;
private int fTableRows = 0; // Number of table rows
private int fFullyVisibleRows = 0; // Number of fully visible table rows
private int fFrozenRowCount = 0; // Number of frozen table rows at top of table
private int fTableTopEventRank = 0; // Global rank of the first entry displayed
private int fSelectedEventRank = -1; // Global rank of the selected event
private int fSelectedBeginRank = -1; // Global rank of the selected begin event
private boolean fPendingSelection = false; // Pending selection update
private int fTableItemCount = 0;
// The slider
private Slider fSlider;
private SliderThrottler fSliderThrottler;
private int fLinuxItemHeight = 0; // Calculated item height for Linux workaround
private TooltipProvider tooltipProvider = null;
private IDoubleClickListener doubleClickListener = null;
private boolean fResetTopIndex = false; // Flag to trigger reset of top index
private ControlAdapter fResizeListener; // Resize listener to update visible rows
// ------------------------------------------------------------------------
// Classes
// ------------------------------------------------------------------------
private class SliderThrottler extends Thread {
private static final long DELAY = 400L;
private static final long POLLING_INTERVAL = 10L;
@Override
public void run() {
final long startTime = System.currentTimeMillis();
while ((System.currentTimeMillis() - startTime) < DELAY) {
try {
Thread.sleep(POLLING_INTERVAL);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
Display.getDefault().asyncExec(() -> {
if (fSliderThrottler != SliderThrottler.this) {
return;
}
fSliderThrottler = null;
if (SliderThrottler.this.isInterrupted() || fTable.isDisposed()) {
return;
}
refreshTable();
});
}
}
// ------------------------------------------------------------------------
// Constructor
// ------------------------------------------------------------------------
/**
* Standard constructor
*
* @param parent
* The parent composite object
* @param style
* The style to use
*/
public TmfVirtualTable(Composite parent, int style) {
super(parent, style & (~SWT.H_SCROLL) & (~SWT.V_SCROLL) & (~SWT.SINGLE) & (~SWT.MULTI) & (~SWT.FULL_SELECTION) & (~SWT.HIDE_SELECTION) & (~SWT.CHECK));
// Create the controls
createTable(style & (SWT.H_SCROLL | SWT.SINGLE | SWT.MULTI | SWT.FULL_SELECTION | SWT.HIDE_SELECTION | SWT.CHECK));
createSlider(style & SWT.V_SCROLL);
// Prevent the slider from being traversed
setTabList(new Control[] { fTable });
// Set the layout
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 2;
gridLayout.horizontalSpacing = 0;
gridLayout.verticalSpacing = 0;
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
setLayout(gridLayout);
GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
fTable.setLayoutData(tableGridData);
GridData sliderGridData = new GridData(SWT.FILL, SWT.FILL, false, true);
fSlider.setLayoutData(sliderGridData);
// Add the listeners
fTable.addMouseWheelListener(event -> {
if (fTableItemCount <= fFullyVisibleRows || event.count == 0) {
return;
}
fTableTopEventRank -= event.count;
if (fTableTopEventRank < 0) {
fTableTopEventRank = 0;
}
int latestFirstRowOffset = fTableItemCount - fFullyVisibleRows;
if (fTableTopEventRank > latestFirstRowOffset) {
fTableTopEventRank = latestFirstRowOffset;
}
fSlider.setSelection(fTableTopEventRank);
refreshTable();
});
fTable.addListener(SWT.MouseWheel, event -> event.doit = false);
fResizeListener = new ControlAdapter() {
@Override
public void controlResized(ControlEvent event) {
int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight());
fFullyVisibleRows = tableHeight / getItemHeight();
if (fTableItemCount > 0) {
fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows)));
}
}
};
fTable.addControlListener(fResizeListener);
// Implement a "fake" tooltip
final String TOOLTIP_DATA_KEY = "_TABLEITEM"; //$NON-NLS-1$
final Listener labelListener = event -> {
Label label = (Label) event.widget;
Shell shell = label.getShell();
switch (event.type) {
case SWT.MouseDown:
Event e = new Event();
e.item = (TableItem) label.getData(TOOLTIP_DATA_KEY);
// Assuming table is single select, set the selection as if
// the mouse down event went through to the table
fTable.setSelection(new TableItem [] {(TableItem) e.item});
fTable.notifyListeners(SWT.Selection, e);
shell.dispose();
fTable.setFocus();
break;
case SWT.MouseExit:
case SWT.MouseWheel:
shell.dispose();
break;
default:
break;
}
};
Listener tableListener = new Listener() {
Shell tip = null;
Label label = null;
@Override
public void handleEvent(Event event) {
switch (event.type) {
case SWT.Dispose:
case SWT.KeyDown:
case SWT.MouseMove: {
if (tip == null) {
break;
}
tip.dispose();
tip = null;
label = null;
break;
}
case SWT.MouseHover: {
TableItem item = fTable.getItem(new Point(event.x, event.y));
if (item != null) {
for (int i = 0; i < fTable.getColumnCount(); i++) {
Rectangle bounds = item.getBounds(i);
if (bounds.contains(event.x, event.y)) {
if (tip != null && !tip.isDisposed()) {
tip.dispose();
}
if (tooltipProvider == null) {
return;
}
String tooltipText = tooltipProvider.getTooltip(i, item.getData());
if (tooltipText == null) {
return;
}
tip = new Shell(fTable.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
tip.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
FillLayout layout = new FillLayout();
layout.marginWidth = 2;
tip.setLayout(layout);
label = new Label(tip, SWT.WRAP);
label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
label.setData(TOOLTIP_DATA_KEY, item);
label.setText(tooltipText);
label.addListener(SWT.MouseExit, labelListener);
label.addListener(SWT.MouseDown, labelListener);
label.addListener(SWT.MouseWheel, labelListener);
Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
Point pt = fTable.toDisplay(bounds.x, bounds.y);
tip.setBounds(pt.x, pt.y, size.x, size.y);
tip.setVisible(true);
// Item found, leave loop.
break;
}
}
}
break;
}
default:
break;
}
}
};
fTable.addListener(SWT.Dispose, tableListener);
fTable.addListener(SWT.KeyDown, tableListener);
fTable.addListener(SWT.MouseMove, tableListener);
fTable.addListener(SWT.MouseHover, tableListener);
addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent event) {
resize();
if (fTableItemCount > 0) {
fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows)));
}
}
});
// And display
refresh();
}
// ------------------------------------------------------------------------
// Table handling
// ------------------------------------------------------------------------
/**
* Create the table and add listeners
* @param style int can be H_SCROLL, SINGLE, MULTI, FULL_SELECTION, HIDE_SELECTION, CHECK
*/
private void createTable(int style) {
fTable = new Table(this, style | SWT.NO_SCROLL);
fTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
if (event.item == null) {
// Override table selection from Select All action
refreshSelection();
}
}
});
fTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(MouseEvent e) {
handleTableMouseEvent(e);
}
});
fTable.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent event) {
handleTableKeyEvent(event);
}
});
fTable.addListener(
SWT.MouseDoubleClick, event -> {
if (doubleClickListener != null) {
TableItem item = fTable.getItem(new Point (event.x, event.y));
if (item != null) {
for (int i = 0; i < fTable.getColumnCount(); i++){
Rectangle bounds = item.getBounds(i);
if (bounds.contains(event.x, event.y)){
doubleClickListener.handleDoubleClick(TmfVirtualTable.this, item, i);
break;
}
}
}
}
}
);
/*
* Feature in Windows. When a partially visible table item is selected,
* after ~500 ms the top index is changed to ensure the selected item is
* fully visible. This leaves a blank space at the bottom of the virtual
* table. The workaround is to reset the top index to 0 if it is not 0.
* Also reset the top index to 0 if indicated by the flag that was set
* at table selection of a partially visible table item.
*/
fTable.addPaintListener(e -> {
if (fTable.getTopIndex() != 0 || fResetTopIndex) {
fTable.setTopIndex(0);
}
fResetTopIndex = false;
});
}
/**
* Handle mouse-based selection in table.
*
* @param event the mouse event
*/
private void handleTableMouseEvent(MouseEvent event) {
TableItem item = fTable.getItem(new Point(event.x, event.y));
if (item == null) {
return;
}
int selectedRow = indexOf(item);
if (event.button == 1 || (event.button == 3 &&
(selectedRow < Math.min(fSelectedBeginRank, fSelectedEventRank) ||
selectedRow > Math.max(fSelectedBeginRank, fSelectedEventRank)))) {
if (selectedRow >= 0) {
fSelectedEventRank = selectedRow;
} else {
fSelectedEventRank = -1;
}
if ((event.stateMask & SWT.SHIFT) == 0 || (fTable.getStyle() & SWT.MULTI) == 0 || fSelectedBeginRank == -1) {
fSelectedBeginRank = fSelectedEventRank;
}
}
refreshSelection();
/*
* Feature in Linux. When a partially visible table item is selected,
* the origin is changed to ensure the selected item is fully visible.
* This makes the first row partially visible. The solution is to force
* reset the origin by setting the top index to 0. This should happen
* only once at the next redraw by the paint listener.
*/
if (selectedRow >= fFullyVisibleRows) {
fResetTopIndex = true;
}
}
/**
* Handle key-based navigation in table.
*
* @param event the key event
*/
private void handleTableKeyEvent(KeyEvent event) {
int lastEventRank = fTableItemCount - 1;
int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows);
int previousSelectedEventRank = fSelectedEventRank;
int previousSelectedBeginRank = fSelectedBeginRank;
boolean needsRefresh = false;
// In all case, perform the following steps:
// - Update the selected entry rank (within valid range)
// - Update the selected row
// - Update the page's top entry if necessary (which also adjusts the selected row)
// - If the top displayed entry was changed, table refresh is needed
switch (event.keyCode) {
case SWT.ARROW_DOWN: {
event.doit = false;
if (fSelectedEventRank < lastEventRank) {
fSelectedEventRank++;
int selectedRow = fSelectedEventRank - fTableTopEventRank;
if (selectedRow == fFullyVisibleRows) {
fTableTopEventRank++;
needsRefresh = true;
} else if (selectedRow < fFrozenRowCount || selectedRow > fFullyVisibleRows) {
fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank));
needsRefresh = true;
}
}
break;
}
case SWT.ARROW_UP: {
event.doit = false;
if (fSelectedEventRank > 0) {
fSelectedEventRank--;
int selectedRow = fSelectedEventRank - fTableTopEventRank;
if (selectedRow == fFrozenRowCount - 1 && fTableTopEventRank > 0) {
fTableTopEventRank--;
needsRefresh = true;
} else if (selectedRow < fFrozenRowCount || selectedRow > fFullyVisibleRows) {
fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank));
needsRefresh = true;
}
}
break;
}
case SWT.END: {
event.doit = false;
fTableTopEventRank = lastPageTopEntryRank;
fSelectedEventRank = lastEventRank;
needsRefresh = true;
break;
}
case SWT.HOME: {
event.doit = false;
fSelectedEventRank = fFrozenRowCount;
fTableTopEventRank = 0;
needsRefresh = true;
break;
}
case SWT.PAGE_DOWN: {
event.doit = false;
if (fSelectedEventRank < lastEventRank) {
fSelectedEventRank += fFullyVisibleRows;
if (fSelectedEventRank > lastEventRank) {
fSelectedEventRank = lastEventRank;
}
int selectedRow = fSelectedEventRank - fTableTopEventRank;
if (selectedRow > fFullyVisibleRows + fFrozenRowCount - 1 && selectedRow < 2 * fFullyVisibleRows) {
fTableTopEventRank += fFullyVisibleRows;
if (fTableTopEventRank > lastPageTopEntryRank) {
fTableTopEventRank = lastPageTopEntryRank;
}
needsRefresh = true;
} else if (selectedRow < fFrozenRowCount || selectedRow >= 2 * fFullyVisibleRows) {
fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank));
needsRefresh = true;
}
}
break;
}
case SWT.PAGE_UP: {
event.doit = false;
if (fSelectedEventRank > 0) {
fSelectedEventRank -= fFullyVisibleRows;
if (fSelectedEventRank < fFrozenRowCount) {
fSelectedEventRank = fFrozenRowCount;
}
int selectedRow = fSelectedEventRank - fTableTopEventRank;
if (selectedRow < fFrozenRowCount && selectedRow > -fFullyVisibleRows) {
fTableTopEventRank -= fFullyVisibleRows;
if (fTableTopEventRank < 0) {
fTableTopEventRank = 0;
}
needsRefresh = true;
} else if (selectedRow <= -fFullyVisibleRows || selectedRow >= fFullyVisibleRows) {
fTableTopEventRank = Math.max(0, Math.min(fSelectedEventRank - fFrozenRowCount, lastPageTopEntryRank));
needsRefresh = true;
}
}
break;
}
default: {
return;
}
}
if ((event.stateMask & SWT.SHIFT) == 0 || (fTable.getStyle() & SWT.MULTI) == 0 || fSelectedBeginRank == -1) {
fSelectedBeginRank = fSelectedEventRank;
}
boolean done = true;
if (needsRefresh) {
done = refreshTable(); // false if table items not updated yet in this thread
} else {
refreshSelection();
}
if (fFullyVisibleRows < fTableItemCount) {
fSlider.setSelection(fTableTopEventRank);
}
if (fSelectedEventRank != previousSelectedEventRank || fSelectedBeginRank != previousSelectedBeginRank) {
if (done) {
Event e = new Event();
e.item = fTable.getItem(fSelectedEventRank - fTableTopEventRank);
fTable.notifyListeners(SWT.Selection, e);
} else {
fPendingSelection = true;
}
}
}
/**
* Method setDataItem.
* @param index int
* @param item TableItem
* @return boolean
*/
private boolean setDataItem(int index, TableItem item) {
if (index != -1) {
Event event = new Event();
event.item = item;
if (index < fFrozenRowCount) {
event.index = index;
} else {
event.index = index + fTableTopEventRank;
}
event.doit = true;
fTable.notifyListeners(SWT.SetData, event);
return event.doit; // false if table item not updated yet in this thread
}
return true;
}
// ------------------------------------------------------------------------
// Slider handling
// ------------------------------------------------------------------------
/**
* Method createSlider.
* @param style int
*/
private void createSlider(int style) {
fSlider = new Slider(this, SWT.VERTICAL | SWT.NO_FOCUS);
fSlider.setMinimum(0);
fSlider.setMaximum(0);
if ((style & SWT.V_SCROLL) == 0) {
fSlider.setVisible(false);
}
fSlider.addListener(SWT.Selection, event -> {
switch (event.detail) {
case SWT.ARROW_DOWN:
case SWT.ARROW_UP:
case SWT.END:
case SWT.HOME:
case SWT.PAGE_DOWN:
case SWT.PAGE_UP: {
fTableTopEventRank = fSlider.getSelection();
refreshTable();
break;
}
case SWT.DRAG:
case SWT.NONE:
/*
* While the slider thumb is being dragged, only perform the
* refresh periodically. The event detail during the drag is
* SWT.DRAG on Windows and SWT.NONE on Linux.
*/
fTableTopEventRank = fSlider.getSelection();
if (fSliderThrottler == null) {
fSliderThrottler = new SliderThrottler();
fSliderThrottler.start();
}
break;
default:
break;
}
});
/*
* When the mouse button is released, perform the refresh immediately
* and interrupt and discard the slider throttler.
*/
fSlider.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
if (fSliderThrottler != null) {
fSliderThrottler.interrupt();
fSliderThrottler = null;
}
fTableTopEventRank = fSlider.getSelection();
refreshTable();
}
});
/*
* The SWT.NO_FOCUS style is only a hint and is not always respected. If
* the slider gains focus, give it back to the table.
*/
fSlider.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
fTable.setFocus();
}
});
}
// ------------------------------------------------------------------------
// Simulated Table API
// ------------------------------------------------------------------------
/**
* Constructs a new TableColumn instance given a style value describing its
* alignment behavior. The column is added to the end of the columns
* maintained by the table.
*
* @param style
* the alignment style
* @return the new TableColumn
*
* @see SWT#LEFT
* @see SWT#RIGHT
* @see SWT#CENTER
*/
public TableColumn newTableColumn(int style) {
TableColumn column = new TableColumn(fTable, style);
/*
* In Linux the table does not receive a control resized event when
* a table column resize causes the horizontal scroll bar to become
* visible or invisible, so a resize listener must be added to every
* table column to properly update the number of fully visible rows.
*/
column.addControlListener(fResizeListener);
return column;
}
/**
* Method setHeaderVisible.
* @param b boolean
*/
public void setHeaderVisible(boolean b) {
fTable.setHeaderVisible(b);
}
/**
* Method setLinesVisible.
* @param b boolean
*/
public void setLinesVisible(boolean b) {
fTable.setLinesVisible(b);
}
/**
* Returns an array of <code>TableItem</code>s that are currently selected
* in the receiver. The order of the items is unspecified. An empty array
* indicates that no items are selected.
* <p>
* Note: This array only contains the visible selected items in the virtual
* table. To get information about the full selection range, use
* {@link #getSelectionIndices()}.
* </p>
*
* @return an array representing the selection
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public TableItem[] getSelection() {
return fTable.getSelection();
}
/**
* Method addListener.
* @param eventType int
* @param listener Listener
*/
@Override
public void addListener(int eventType, Listener listener) {
fTable.addListener(eventType, listener);
}
/**
* Method addKeyListener.
* @param listener KeyListener
*/
@Override
public void addKeyListener(KeyListener listener) {
fTable.addKeyListener(listener);
}
/**
* Method addMouseListener.
* @param listener MouseListener
*/
@Override
public void addMouseListener(MouseListener listener) {
fTable.addMouseListener(listener);
}
/**
* Method addSelectionListener.
* @param listener SelectionListener
*/
public void addSelectionListener(SelectionListener listener) {
fTable.addSelectionListener(listener);
}
/**
* Method setMenu sets the menu
* @param menu Menu the menu
*/
@Override
public void setMenu(Menu menu) {
fTable.setMenu(menu);
}
/**
* Gets the menu of this table
* @return a Menu
*/
@Override
public Menu getMenu() {
return fTable.getMenu();
}
/**
* Method clearAll empties a table.
*/
public void clearAll() {
setItemCount(0);
}
/**
* Method setItemCount
* @param nbItems int the number of items in the table
*
*/
public void setItemCount(int nbItems) {
final int nb = Math.max(0, nbItems);
if (nb != fTableItemCount) {
fTableItemCount = nb;
fTable.remove(fTableItemCount, fTable.getItemCount() - 1);
fSlider.setMaximum(nb);
resize();
int tableHeight = Math.max(0, fTable.getClientArea().height - fTable.getHeaderHeight());
fFullyVisibleRows = tableHeight / getItemHeight();
if (fTableItemCount > 0) {
fSlider.setThumb(Math.max(1, Math.min(fTableRows, fFullyVisibleRows)));
}
}
}
/**
* Method getItemCount.
* @return int the number of items in the table
*/
public int getItemCount() {
return fTableItemCount;
}
/**
* Method getItemHeight.
* @return int the height of a table item in pixels. (may vary from one os to another)
*/
public int getItemHeight() {
/*
* Bug in Linux. The method getItemHeight doesn't always return the correct value.
*/
if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
if (fLinuxItemHeight != 0) {
return fLinuxItemHeight;
}
if (fTable.getItemCount() > 1) {
int itemHeight = fTable.getItem(1).getBounds().y - fTable.getItem(0).getBounds().y;
if (itemHeight > 0) {
fLinuxItemHeight = itemHeight;
return fLinuxItemHeight;
}
}
} else {
fLinuxItemHeight = -1; // Not Linux, don't perform os.name check anymore
}
return fTable.getItemHeight();
}
/**
* Method getHeaderHeight.
* @return int get the height of the header in pixels.
*/
public int getHeaderHeight() {
return fTable.getHeaderHeight();
}
/**
* Method getTopIndex.
* @return int get the first data item index, if you have a header it is offset.
*/
public int getTopIndex() {
return fTableTopEventRank + fFrozenRowCount;
}
/**
* Method setTopIndex.
* @param index int suggested top index for the table.
*/
public void setTopIndex(int index) {
if (fTableItemCount > 0) {
int i = Math.min(index, fTableItemCount - 1);
i = Math.max(i, fFrozenRowCount);
fTableTopEventRank = i - fFrozenRowCount;
if (fFullyVisibleRows < fTableItemCount) {
fSlider.setSelection(fTableTopEventRank);
}
refreshTable();
}
}
@Override
public ScrollBar getHorizontalBar() {
return fTable.getHorizontalBar();
}
@Override
public ScrollBar getVerticalBar() {
return fTable.getVerticalBar();
}
/**
* Method indexOf. Return the index of a table item
* @param ti TableItem the table item to search for in the table
* @return int the index of the first match. (there should only be one match)
*/
public int indexOf(TableItem ti) {
int index = fTable.indexOf(ti);
if (index < fFrozenRowCount) {
return index;
}
return (index - fFrozenRowCount) + getTopIndex();
}
/**
* Method getColumns.
* @return TableColumn[] the table columns
*/
public TableColumn[] getColumns() {
return fTable.getColumns();
}
/**
* Returns an array of zero-relative integers that map
* the creation order of the receiver's columns to the
* order in which they are currently being displayed.
* <p>
* Specifically, the indices of the returned array represent
* the current visual order of the columns, and the contents
* of the array represent the creation order of the columns.
*
* @return the current visual order of the receiver's columns
*/
public int[] getColumnOrder() {
return fTable.getColumnOrder();
}
/**
* Sets the order that the columns in the receiver should
* be displayed in to the given argument which is described
* in terms of the zero-relative ordering of when the columns
* were added.
* <p>
* Specifically, the contents of the array represent the
* original position of each column at the time its creation.
*
* @param order the new order to display the columns
*/
public void setColumnOrder(int[] order) {
fTable.setColumnOrder(order);
}
/**
* Returns the table item at the given point in the table or null if no such
* item exists. The point is in coordinates relative to the table.
*
* @param point
* the point in coordinates relative to the table
* @return the corresponding table item, or null
*/
public TableItem getItem(Point point) {
return fTable.getItem(point);
}
/**
* Returns the table column at the given point in the table or null if no such
* column exists. The point is in coordinates relative to the table.
*
* @param point
* the point in coordinates relative to the table
* @return the corresponding table column, or null
* @since 4.0
*/
public TableColumn getColumn(Point point) {
Rectangle clientArea = fTable.getClientArea();
Point p = new Point(clientArea.x + point.x, clientArea.y + point.y);
if (!clientArea.contains(p)) {
return null;
}
int x = 0;
for (int i : fTable.getColumnOrder()) {
TableColumn column = fTable.getColumn(i);
int width = column.getWidth();
x += width;
if (p.x < x && width > 0) {
return column;
}
}
return null;
}
/**
* Method resize.
*/
private void resize() {
// Compute the numbers of rows that fit the new area
int tableHeight = Math.max(0, getSize().y - fTable.getHeaderHeight());
int itemHeight = getItemHeight();
fTableRows = Math.min((tableHeight + itemHeight - 1) / itemHeight, fTableItemCount);
if (fTableTopEventRank + fFullyVisibleRows > fTableItemCount) {
// If we are at the end, get elements before to populate
fTableTopEventRank = Math.max(0, fTableItemCount - fFullyVisibleRows);
refreshTable();
} else if (fTableRows > fTable.getItemCount() || fTableItemCount < fTable.getItemCount()) {
// Only refresh if new table items are needed or if table items need to be deleted
refreshTable();
}
}
// ------------------------------------------------------------------------
// Controls interactions
// ------------------------------------------------------------------------
/**
* Method setFocus.
* @return boolean is this visible?
*/
@Override
public boolean setFocus() {
boolean isVisible = isVisible();
if (isVisible) {
fTable.setFocus();
}
return isVisible;
}
/**
* Method refresh.
*/
public void refresh() {
boolean done = refreshTable();
if (!done) {
return;
}
if (fPendingSelection) {
fPendingSelection = false;
TableItem item = null;
if (fSelectedEventRank >= 0 && fSelectedEventRank < fFrozenRowCount) {
item = fTable.getItem(fSelectedEventRank);
} else if (fSelectedEventRank >= fTableTopEventRank + fFrozenRowCount && fSelectedEventRank - fTableTopEventRank < fTable.getItemCount()) {
item = fTable.getItem(fSelectedEventRank - fTableTopEventRank);
}
if (item != null) {
Event e = new Event();
e.item = item;
fTable.notifyListeners(SWT.Selection, e);
}
}
}
/**
* Method removeAll.
* @return int 0 the number of elements in the table
*/
public int removeAll() {
setItemCount(0);
fSlider.setMaximum(0);
fTable.removeAll();
fSelectedEventRank = -1;
fSelectedBeginRank = fSelectedEventRank;
return 0;
}
/**
* Method refreshTable.
* @return true if all table items have been refreshed, false otherwise
*/
private boolean refreshTable() {
boolean done = true;
for (int i = 0; i < fTableRows; i++) {
if (i + fTableTopEventRank < fTableItemCount) {
TableItem tableItem;
if (i < fTable.getItemCount()) {
tableItem = fTable.getItem(i);
} else {
tableItem = new TableItem(fTable, SWT.NONE);
}
done &= setDataItem(i, tableItem); // false if table item not updated yet in this thread
} else {
if (fTable.getItemCount() > fTableItemCount - fTableTopEventRank) {
fTable.remove(fTableItemCount - fTableTopEventRank);
}
}
}
if (done) {
refreshSelection();
} else {
fTable.deselectAll();
}
return done;
}
private void refreshSelection() {
int lastRowOffset = fTableTopEventRank + fTableRows - 1;
int startRank = Math.min(fSelectedBeginRank, fSelectedEventRank);
int endRank = Math.max(fSelectedBeginRank, fSelectedEventRank);
int start = Integer.MAX_VALUE;
int end = Integer.MIN_VALUE;
if (startRank < fFrozenRowCount) {
start = startRank;
} else if (startRank < fTableTopEventRank + fFrozenRowCount) {
start = fFrozenRowCount;
} else if (startRank <= lastRowOffset) {
start = startRank - fTableTopEventRank;
}
if (endRank < fFrozenRowCount) {
end = endRank;
} else if (endRank < fTableTopEventRank + fFrozenRowCount) {
end = fFrozenRowCount - 1;
} else if (endRank <= lastRowOffset) {
end = endRank - fTableTopEventRank;
} else {
end = fTableRows - 1;
}
if (start <= end) {
fTable.setSelection(start, end);
/* Reset origin in case partially visible item selected */
fTable.setTopIndex(0);
if (startRank == fSelectedEventRank) {
fTable.select(start);
} else {
fTable.select(end);
}
} else {
/*
* In GTK2, when the table is given focus, one table item is
* highlighted even if there is no selection. In that case the
* highlighted item is the last selected item. Make that last
* selected item the top or bottom item depending on if the
* out-of-range selection is above or below the visible items.
*/
if (SWT.getPlatform().equals("gtk") && fTableRows > 0) { //$NON-NLS-1$
fTable.setRedraw(false);
if (start < Integer.MAX_VALUE) {
fTable.setSelection(0);
} else {
fTable.setSelection(fTableRows - 1);
fTable.setTopIndex(0);
}
fTable.setRedraw(true);
}
fTable.deselectAll();
}
}
/**
* Selects the item at the given zero-relative index in the receiver.
* The current selection is first cleared, then the new item is selected,
* and if necessary the receiver is scrolled to make the new selection visible.
*
* @param index the index of the item to select
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setSelection(int index) {
setSelectionRange(index, index);
}
/**
* Selects a range of items starting at the given zero-relative index in the
* receiver. The current selection is first cleared, then the new items are
* selected, and if necessary the receiver is scrolled to make the new
* selection visible.
*
* @param beginIndex
* The index of the first item in the selection range
* @param endIndex
* The index of the last item in the selection range
*
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
* @since 2.0
*/
public void setSelectionRange(int beginIndex, int endIndex) {
if (fTableItemCount > 0) {
int begin = Math.max(Math.min(beginIndex, fTableItemCount - 1), 0);
int end = Math.max(Math.min(endIndex, fTableItemCount - 1), 0);
int selection = fSelectedBeginRank != begin ? begin : end;
fSelectedBeginRank = begin;
fSelectedEventRank = end;
if ((selection < fTableTopEventRank + fFrozenRowCount && selection >= fFrozenRowCount) ||
(selection >= fTableTopEventRank + fFullyVisibleRows)) {
int lastPageTopEntryRank = Math.max(0, fTableItemCount - fFullyVisibleRows);
fTableTopEventRank = Math.max(0, Math.min(lastPageTopEntryRank, selection - fFrozenRowCount - fFullyVisibleRows / 2));
}
if (fFullyVisibleRows < fTableItemCount) {
fSlider.setSelection(fTableTopEventRank);
}
refreshTable();
}
}
/**
* Returns the zero-relative index of the item which is currently
* selected in the receiver, or -1 if no item is selected.
*
* @return the index of the selected item
*/
public int getSelectionIndex() {
return fSelectedEventRank;
}
/**
* Returns an index array representing the selection range. If there is a
* single item selected the array holds one index. If there is a selected
* range the first item in the array is the start index of the selection and
* the second item is the end index of the selection, which is the item most
* recently selected. The array is empty if no items are selected.
* <p>
* @return the array of indices of the selected items
*/
public int[] getSelectionIndices() {
if (fSelectedEventRank < 0 || fSelectedBeginRank < 0) {
return new int[] {};
} else if (fSelectedEventRank == fSelectedBeginRank) {
return new int[] { fSelectedEventRank };
}
return new int[] { fSelectedBeginRank, fSelectedEventRank };
}
/**
* Method setFrozenRowCount.
* @param count int the number of rows to freeze from the top row
*/
public void setFrozenRowCount(int count) {
fFrozenRowCount = count;
refreshTable();
}
/**
* Method createTableEditor.
* @return a TableEditor of the table
*/
public TableEditor createTableEditor() {
return new TableEditor(fTable);
}
/**
* Method createTableEditorControl.
* @param control Class<? extends Control>
* @return Control
*/
public Control createTableEditorControl(Class<? extends Control> control) {
try {
return control.getConstructor(Composite.class, int.class).newInstance(new Object[] {fTable, SWT.NONE});
} catch (Exception e) {
Activator.getDefault().logError("Error creating table editor control", e); //$NON-NLS-1$
}
return null;
}
/**
* @return the tooltipProvider
*/
public TooltipProvider getTooltipProvider() {
return tooltipProvider;
}
/**
* @param tooltipProvider the tooltipProvider to set
*/
public void setTooltipProvider(TooltipProvider tooltipProvider) {
this.tooltipProvider = tooltipProvider;
}
/**
* @return the doubleClickListener
*/
public IDoubleClickListener getDoubleClickListener() {
return doubleClickListener;
}
/**
* @param doubleClickListener the doubleClickListener to set
*/
public void setDoubleClickListener(IDoubleClickListener doubleClickListener) {
this.doubleClickListener = doubleClickListener;
}
}