/******************************************************************************* * Copyright (c) 2010, 2016 Ericsson and others. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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.MouseWheelListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; 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.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) { } } Display.getDefault().asyncExec(new Runnable() { @Override public void run() { 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(new MouseWheelListener() { @Override public void mouseScrolled(MouseEvent 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, new Listener() { // disable mouse scroll of horizontal scroll bar @Override public void handleEvent(Event 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 = new Listener() { @Override public void handleEvent (Event 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, new Listener() { @Override public void handleEvent(Event 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(new PaintListener() { @Override public void paintControl(PaintEvent 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, new Listener() { @Override public void handleEvent(Event 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(); } } /** * 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); } /** * Method getItem. * @param point Point the coordinates in the table * @return TableItem the corresponding table item */ public TableItem getItem(Point point) { return fTable.getItem(point); } /** * 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 setColumnHeaders. * * @param columnData * ColumnData[] the columndata array. */ @Deprecated public void setColumnHeaders(ColumnData columnData[]) { /* No-op */ } /** * 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; } }