/* * Copyright (C) 2005 David Orme <djo@coconut-palm-software.com> * * 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: * David Orme - Initial API and implementation * Elias Volanakis - 267316 */ package org.eclipse.swt.nebula.widgets.compositetable; import java.lang.reflect.Constructor; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.graphics.Color; 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.nebula.widgets.compositetable.internal.DuckType; import org.eclipse.swt.nebula.widgets.compositetable.internal.ISelectableRegionControl; 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.Layout; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Slider; import org.eclipse.swt.widgets.Widget; /** * (non-API) Class InternalCompositeTable. This is the run-time * CompositeTableControl. It gets its prototype row and (optional) header * objects from its SWT parent, then uses them to implement an SWT virtual table * control. * * @author djo */ class InternalCompositeTable extends Composite implements Listener { // The internal UI controls that make up this control. private Composite controlHolder = null; private Composite vSliderHolder = null; private Slider vSlider = null; private Composite hScroller; private Composite hSliderHolder = null; private Slider hSlider = null; EmptyTablePlaceholder emptyTablePlaceholder = null; // My parent CompositeTable private CompositeTable parent; // Property fields private int maxRowsVisible; private int numRowsInDisplay; private int numRowsInCollection; private int topRow; private int currentRow; private int currentColumn; // The visible/invisible row objects and bookeeping info about them private int currentVisibleTopRow = 0; private int numRowsVisible = 0; private LinkedList rows = new LinkedList(); private LinkedList spareRows = new LinkedList(); int clientAreaHeight; // The prototype header/row objects and Constructors so we can duplicate // them private Constructor headerConstructor; private Constructor rowConstructor; private Control headerControl; private Control myHeader = null; private Control rowControl; /** * Constructor InternalCompositeTable. The usual SWT constructor. The same * style bits are allowed here as are allowed on Composite. * * @param parentControl * The SWT parent. * @param style * Style bits. */ public InternalCompositeTable(Composite parentControl, int style) { super(parentControl, style); initialize(); this.parent = (CompositeTable) parentControl; setBackground(parentControl.getBackground()); controlHolder.addListener(SWT.MouseWheel, this); maxRowsVisible = parent.getMaxRowsVisible(); numRowsInCollection = parent.getNumRowsInCollection(); topRow = parent.getTopRow(); headerConstructor = parent.getHeaderConstructor(); rowConstructor = parent.getRowConstructor(); headerControl = parent.getHeaderControl(); rowControl = parent.getRowControl(); setMenu(parent.getMenu()); currentVisibleTopRow = topRow; showHeader(); updateVisibleRows(); if (numRowsVisible < 1) { createEmptyTablePlaceholer(); } currentRow = -1; // initialize to undefined } public void setBackground(Color color) { super.setBackground(color); controlHolder.setBackground(color); } /** * Initialize the overall table UI. */ private void initialize() { GridLayout gl = new GridLayout(); gl.numColumns = 2; gl.verticalSpacing = 0; gl.marginWidth = 0; gl.marginHeight = 0; // borderWidth times two, since border steals pixels from both sides int borderInPixels = getParent().getBorderWidth() * 2; gl.marginRight = borderInPixels; gl.marginBottom = borderInPixels; gl.horizontalSpacing = 0; this.setLayout(gl); createControlHolder(); createVSliderHolder(); createHSliderHolder(); } /** * Initialize the controlHolder, which is the holder Composite for the * header object (if applicable) and the row objects. */ private void createControlHolder() { hScroller = new Composite(this, SWT.NULL); GridData gridData = new GridData(); gridData.horizontalAlignment = GridData.FILL; gridData.grabExcessHorizontalSpace = true; gridData.grabExcessVerticalSpace = true; gridData.verticalAlignment = GridData.FILL; hScroller.setLayoutData(gridData); controlHolder = new Composite(hScroller, SWT.NONE); controlHolder.setLayout(new Layout() { protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { if (rowControl != null) { int height = 0; int width = 0; if (headerControl != null) { Point headerSize = headerControl.computeSize(wHint, hHint, flushCache); width = headerSize.x; height = headerSize.y; } Point rowSize = rowControl.computeSize(wHint, hHint, flushCache); height += rowSize.y * 2; if (width < rowSize.x) { width = rowSize.x; } return new Point(width, height); } return new Point(50, 50); } protected void layout(Composite composite, boolean flushCache) { layoutControlHolder(); } }); hScroller.addControlListener(scrollerResizeHandler); } ControlAdapter scrollerResizeHandler = new ControlAdapter() { public void controlResized(ControlEvent e) { Point size = hScroller.getSize(); int preferredWidth = controlHolder.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x; if (preferredWidth > size.x && !isHSliderVisible()) { setHSliderVisible(true); } if (preferredWidth <= size.x && isHSliderVisible()) { setHSliderVisible(false); } if (preferredWidth <= size.x) { controlHolder.setBounds(0, 0, size.x, size.y); return; } if (isHSliderVisible()) { hSlider.setMaximum(preferredWidth); hSlider.setPageIncrement(size.x); hSlider.setThumb(preferredWidth - (preferredWidth - size.x)); int currentSelection = hSlider.getSelection(); if (preferredWidth - currentSelection < size.x) { hSlider.setSelection(preferredWidth - size.x); } } hSlider.notifyListeners(SWT.Selection, new Event()); } }; /** * Initialize the sliderHolder and slider. The SliderHolder is a Composite * that is responsible for showing and hiding the vertical slider upon * request. */ private void createVSliderHolder() { GridData gd = getVSliderGridData(); vSliderHolder = new Composite(this, SWT.NULL); vSlider = new Slider(vSliderHolder, SWT.VERTICAL); vSlider.addSelectionListener(sliderSelectionListener); vSliderHolder.setLayout(new FillLayout()); vSliderHolder.setLayoutData(gd); vSliderHolder.setTabList(new Control[] {}); } /** * Initialize the sliderHolder and slider. The SliderHolder is a Composite * that is responsible for showing and hiding the vertical slider upon * request. */ private void createHSliderHolder() { GridData gd = getHSliderGridData(); hSliderHolder = new Composite(this, SWT.NULL); hSlider = new Slider(hSliderHolder, SWT.HORIZONTAL); hSlider.setMinimum(0); hSlider.setIncrement(20); hSlider.addSelectionListener(sliderSelectionListener); hSliderHolder.setLayout(new FillLayout()); hSliderHolder.setLayoutData(gd); hSliderHolder.setTabList(new Control[] {}); hSlider.addSelectionListener(hSliderSelectionListener); } // Slider utility methods // --------------------------------------------------------------------- /** * Returns a GridData for the SliderHolder appropriate for if the slider is * visible or not. * * @return A GridData with a widthHint of 0 if the slider is not visible or * with a widthHint of SWT.DEFAULT otherwise. */ private GridData getVSliderGridData() { GridData gd = new GridData(); gd.grabExcessVerticalSpace = true; gd.verticalAlignment = GridData.FILL; gd.verticalSpan = 1; if (!vSliderVisible) { gd.widthHint = 0; } return gd; } /** * Returns a GridData for the SliderHolder appropriate for if the slider is * visible or not. * * @return A GridData with a heightHint of 0 if the slider is not visible or * with a heightHint of SWT.DEFAULT otherwise. */ private GridData getHSliderGridData() { GridData gd = new GridData(); gd.grabExcessHorizontalSpace = true; gd.horizontalAlignment = GridData.FILL; gd.horizontalSpan = 1; if (!hSliderVisible) { gd.heightHint = 0; } return gd; } private boolean vSliderVisible = false; /** * Sets if the slider is visible or not. * * @param visible * true if the slider should be visible; false otherwise. */ public void setVSliderVisible(boolean visible) { this.vSliderVisible = visible; vSliderHolder.setLayoutData(getVSliderGridData()); if (visible) { Display.getCurrent().addFilter(SWT.KeyDown, displayKeyDownFilter); } else { Display.getCurrent().removeFilter(SWT.KeyDown, displayKeyDownFilter); } Display.getCurrent().asyncExec(new Runnable() { public void run() { if (!InternalCompositeTable.this.isDisposed() && !vSliderHolder.isDisposed() && !vSliderHolder.getParent().isDisposed()) { vSliderHolder.getParent().layout(true); vSliderHolder.layout(true); Point sliderHolderSize = vSliderHolder.getSize(); vSlider.setBounds(0, 0, sliderHolderSize.x, sliderHolderSize.y); } } }); } /** * Returns if the slider is visible. * * @return true if the slider is visible; false otherwise. */ public boolean isVSliderVisible() { return vSliderVisible; } private boolean hSliderVisible = false; /** * Sets if the slider is visible or not. * * @param visible * true if the slider should be visible; false otherwise. */ public void setHSliderVisible(boolean visible) { this.hSliderVisible = visible; hSliderHolder.setLayoutData(getHSliderGridData()); Display.getCurrent().asyncExec(new Runnable() { public void run() { if (!InternalCompositeTable.this.isDisposed() && !hSliderHolder.isDisposed() && !hSliderHolder.getParent().isDisposed()) { hSliderHolder.getParent().layout(true); hSliderHolder.layout(true); Point sliderHolderSize = hSliderHolder.getSize(); hSlider.setBounds(0, 0, sliderHolderSize.x, sliderHolderSize.y); } } }); } /** * Returns if the slider is visible. * * @return true if the slider is visible; false otherwise. */ public boolean isHSliderVisible() { return hSliderVisible; } /* * (non-Javadoc) * * @see org.eclipse.swt.widgets.Widget#dispose() */ public void dispose() { disposeRows(rows); disposeRows(spareRows); super.dispose(); } /** * Disposes all the row objects in the specified LinkedList. * * @param rowsCollection * The collection containing TableRow objects to dispose. */ private void disposeRows(LinkedList rowsCollection) { for (Iterator rowsIter = rowsCollection.iterator(); rowsIter.hasNext();) { TableRow row = (TableRow) rowsIter.next(); if (row instanceof IRowFocusListener) { parent.removeRowFocusListener((IRowFocusListener) row); } if (row instanceof IRowContentProvider) { parent.removeRowContentProvider((IRowContentProvider) row); } row.dispose(); } } // Row object layout // -------------------------------------------------------------------------- /** * Layout the child controls within the controlHolder Composite. */ protected void layoutControlHolder() { if (myHeader != null) { layoutHeaderOrRow(myHeader); } for (Iterator rowsIter = rows.iterator(); rowsIter.hasNext();) { TableRow row = (TableRow) rowsIter.next(); layoutHeaderOrRow(row.getRowControl()); } updateVisibleRows(); } private void layoutHeaderOrRow(Control control) { if (control instanceof Composite) { Composite headerOrRow = (Composite) control; headerOrRow.layout(true); } } // Table control layout -- utility methods // ---------------------------------------------------- /** * Construct a header or row object on demand. Logs an error and returns * null on failure. * * @param parent * The SWT parent. * @param constructor * The header or row object's constructor. * @return The constructed control or null if none could be constructed. */ private Control createInternalControl(Composite parent, Constructor constructor) { Control result = null; try { if (!constructor.isAccessible()) { constructor.setAccessible(true); } result = (Control) constructor.newInstance(new Object[] { parent, new Integer(SWT.NULL) }); } catch (Exception e) { throw new IllegalArgumentException("Unable to construct control"); //$NON-NLS-1$ } if (result instanceof IRowFocusListener) { this.parent.addRowFocusListener((IRowFocusListener) result); } if (result instanceof IRowContentProvider) { this.parent.addRowContentProvider((IRowContentProvider) result); } return result; } /** * If the header control hasn't been created yet, create and show it. */ private void showHeader() { if (myHeader == null && headerConstructor != null) { myHeader = createInternalControl(controlHolder, headerConstructor); fireHeaderConstructionEvent(myHeader); if (myHeader instanceof Composite) { Composite headerComp = (Composite) myHeader; if (headerComp.getLayout() instanceof GridRowLayout) { headerComp.addPaintListener(headerPaintListener); } } layoutHeaderOrRow(myHeader); } } // Table control layout -- main refresh algorithm // --------------------------------------------- /** * Main refresh algorithm entry point. This method refreshes everything in * the table: * * <ul> * <li>Makes sure the correct number of rows are visible * <li>Makes sure each row has been refreshed with data from the underlying * model * </ul> */ void updateVisibleRows() { // If we don't have our prototype row object yet, bail out if (rowControl == null) { return; } clientAreaHeight = controlHolder.getSize().y; if (clientAreaHeight <= 0) { return; } int topPosition = 0; int headerHeight = 0; if (myHeader != null) { headerHeight = headerControl.getSize().y + 3; clientAreaHeight -= headerHeight; topPosition += headerHeight; } numRowsInDisplay = clientAreaHeight / getRowHeight(clientAreaHeight); // Make sure we have something to lay out to begin with int userScrollDirection = 0; if (numRowsInCollection > 0) { numRowsVisible = numRowsInDisplay; disposeEmptyTablePlaceholder(); int displayableRows = numRowsInCollection - topRow; if (numRowsVisible > displayableRows) { numRowsVisible = displayableRows; } if (numRowsVisible > maxRowsVisible) { numRowsVisible = maxRowsVisible; } if (numRowsVisible < 1) { numRowsVisible = 1; } // Keep track of if we're scrolling forwards or backwards if (currentVisibleTopRow - topRow > 0) { userScrollDirection = ScrollEvent.BACKWARD; } else if (topRow - currentVisibleTopRow > 0) { userScrollDirection = ScrollEvent.FORWARD; } // Scroll the view so that the right number of row // objects are showing and they have the right data if (rows.size() - Math.abs(currentVisibleTopRow - topRow) > 0) { // if (currentRow >= numRowsVisible) { // deleteRowAt(0); // ++currentVisibleTopRow; // ++topRow; // --currentRow; // } scrollTop(); fixNumberOfRows(); } else { currentVisibleTopRow = topRow; fixNumberOfRows(); refreshAllRows(); } } else { numRowsVisible = 0; topRow = 0; currentRow = 0; currentColumn = 0; currentVisibleTopRow = 0; numRowsVisible = 0; if (emptyTablePlaceholder == null) { fixNumberOfRows(); createEmptyTablePlaceholer(); } } // Make sure that the currentRow is within the visible range // (after PgDn, it could wind up outside the visible range) if (currentRow >= numRowsVisible && getNumRowsVisible() < numRowsInDisplay) { currentRow = numRowsVisible-1; } // Show, hide, reset the scroll bar if (numRowsVisible < numRowsInCollection) { int extra = numRowsInCollection - numRowsVisible; int pageIncrement = numRowsVisible; if (pageIncrement > extra) pageIncrement = extra; vSlider.setMaximum(numRowsInCollection); vSlider.setMinimum(0); vSlider.setIncrement(1); vSlider.setPageIncrement(pageIncrement); vSlider.setThumb(numRowsInCollection - (numRowsInCollection - numRowsVisible)); vSlider.setSelection(topRow); if (!isVSliderVisible()) { setVSliderVisible(true); } } else { setVSliderVisible(false); } // Lay out the header and rows correctly in the display int width = controlHolder.getSize().x; // First, the header... if (myHeader != null) { myHeader.setBounds(0, 0, width, headerHeight); } // Make sure we have rows to lay out... if (numRowsInCollection < 1) { return; } // Now the rows. int rowHeight = getRowHeight(clientAreaHeight); // We have to move the controls front-to-back if we're scrolling // forwards and back-to-front if we're scrolling backwards to avoid ugly // screen refresh artifacts. if (userScrollDirection == ScrollEvent.FORWARD || userScrollDirection == ScrollEvent.NONE) { for (Iterator rowsIter = rows.iterator(); rowsIter.hasNext();) { TableRow row = (TableRow) rowsIter.next(); Control rowControl = row.getRowControl(); rowControl.setBounds(0, topPosition, width, rowHeight); layoutHeaderOrRow(rowControl); topPosition += rowHeight; } } else { ListIterator rowsIter = rows.listIterator(); while (rowsIter.hasNext()) { rowsIter.next(); } topPosition += rowHeight * (rows.size() - 1); while (rowsIter.hasPrevious()) { TableRow row = (TableRow) rowsIter.previous(); Control rowControl = row.getRowControl(); rowControl.setBounds(0, topPosition, width, rowHeight); layoutHeaderOrRow(rowControl); topPosition -= rowHeight; } } // If we scrolled, tell clients about it if (userScrollDirection != ScrollEvent.NONE) { fireScrollEvent(new ScrollEvent(userScrollDirection, parent)); } } int getRowHeight(int clientAreaHeight) { int rowControlHeight = rowControl.getSize().y; if (maxRowsVisible == Integer.MAX_VALUE) { return rowControlHeight; } return rowControlHeight; } /** * Utility method: Makes sure that the currently visible top row is the same * as the top row specified in the TopRow property. */ private void scrollTop() { while (currentVisibleTopRow < topRow) { deleteRowAt(0); ++currentVisibleTopRow; } while (currentVisibleTopRow > topRow) { --currentVisibleTopRow; insertRowAt(0); } } /** * Utility method: Makes sure that the number of rows that are visible * correspond with what should be visible given the table control's size, * where it is scrolled, and the number of rows in the underlying * collection. */ private void fixNumberOfRows() { int numRows = rows.size(); while (numRows > numRowsVisible) { deleteRowAt(numRows - 1); numRows = rows.size(); } while (numRows < numRowsVisible) { insertRowAt(numRows); numRows = rows.size(); } } /** * Fire the refresh event on all visible rows. */ void refreshAllRows() { int row = 0; for (Iterator rowsIter = rows.iterator(); rowsIter.hasNext();) { TableRow rowControl = (TableRow) rowsIter.next(); fireRefreshEvent(topRow + row, rowControl.getRowControl()); ++row; } resetFocus(); } void refreshRow(int row) { if (topRow > -1) { if (isRowVisible(row)) { fireRefreshEvent(row + topRow, ((TableRow) rows.get(row)).getRowControl()); } } } private boolean isRowVisible(int row) { return row >= 0 && row < numRowsVisible; } /** * Make sure that something sane inside the table has focus. */ private void resetFocus() { /* * FEATURE IN WINDOWS: When we refresh all rows and one already has * focus, Windows gets into a schizophrenic state where some part of * Windows thinks that the current control has focus and another part of * Windows thinks that the current control doesn't have focus. * * The symptom is that the current control stops receiving events from * Windows but Windows still thinks the current control has focus and so * it won't give the control complete focus if you click on it. * * The workaround is to set the focus away from the currently-focused * control and to set it back. */ if (numRowsVisible < 1 || currentRow < 0) { return; } Control control = null; if (currentRow < numRowsVisible) { control = getControl(currentColumn, currentRow); } else if (currentRow > 0) { control = getControl(currentColumn, numRowsVisible - 1); } if (control != null && control.isFocusControl()) { this.setFocus(); deferredSetFocus(control, true); } } /** * Insert a new row object at the specified 0-based position relatve to the * topmost row. * * @param position * The 0-based position relative to the topmost row. */ private void insertRowAt(int position) { TableRow newRow = getNewRow(); if (position > rows.size()) { position = rows.size(); } rows.add(position, newRow); fireRefreshEvent(currentVisibleTopRow + position, newRow .getRowControl()); } /** * Delete the row at the specified 0-based position relative to the topmost * row. * * @param position * The 0-based position relative to the topmost row. */ private void deleteRowAt(int position) { TableRow row = (TableRow) rows.remove(position); row.setVisible(false); spareRows.addLast(row); } /** * Utility method: Creates a new row object or recycles one that had been * previously created but was no longer needed. * * @return The new row object. */ private TableRow getNewRow() { if (spareRows.size() > 0) { TableRow recycledRow = (TableRow) spareRows.removeFirst(); recycledRow.setVisible(true); return recycledRow; } Control newControl = createInternalControl(controlHolder, rowConstructor); if (menu != null) { newControl.setMenu(menu); } fireRowConstructionEvent(newControl); TableRow newRow = new TableRow(this, newControl); if (newRow.getRowControl() instanceof Composite) { Composite rowComp = (Composite) newRow.getRowControl(); if (rowComp.getLayout() instanceof GridRowLayout) { rowComp.setBackground(getBackground()); rowComp.addPaintListener(rowPaintListener); } } return newRow; } // Property getters/setters // -------------------------------------------------------------- /* * These are internal API. <p> Plese refer to the JavaDoc on CompositeTable * for detailed description of these property methods. */ /** * (non-API) Method getHeaderControl. Return the prototype control being * used as a header. * * @return The header control */ public Control getHeaderControl() { return headerControl; } /** * Returns the actual header control (not the prototype). * * @return a control instance or null, if no header is available */ Control getHeader() { return myHeader; } /** * Method setMaxRowsVisible. Sets the maximum number of rows that will be * permitted in the table at once. For example, setting this property to 1 * will have the effect of creating a single editing area with a scroll bar * on the right allowing the user to scroll through all rows using either * the mouse or the PgUp/PgDn keys. The default value is Integer.MAX_VALUE. * * @param maxRowsVisible * the maximum number of rows that are permitted to be visible at * one time, regardless of the control's size. */ public void setMaxRowsVisible(int maxRowsVisible) { this.maxRowsVisible = maxRowsVisible; updateVisibleRows(); } /** * Method getNumRowsVisible. Returns the actual number of rows that are * currently visible. Normally CompositeTable displays as many rows as will * fit vertically given the control's size. This value can be clamped to a * maximum using the MaxRowsVisible property. * * @return the actual number of rows that are currently visible. */ public int getNumRowsVisible() { return rows.size(); } /** * Method setNumRowsInCollection. Sets the number of rows in the data * structure that is being edited. * * @param numRowsInCollection * the number of rows represented by the underlying data * structure. */ public void setNumRowsInCollection(int numRowsInCollection) { this.topRow = 0; if (topRow + currentRow > numRowsInCollection) { if (topRow < numRowsInCollection) { currentRow = numRowsInCollection - topRow; } else { topRow = numRowsInCollection - 1; currentRow = 0; } deferredSetFocus(getCurrentRowControl(), false); } this.numRowsInCollection = numRowsInCollection; updateVisibleRows(); refreshAllRows(); } private void doSetTopRow(int topRow, int currentRow) { // fireRowDepartEvent(); this.topRow = topRow; this.currentRow = currentRow; updateVisibleRows(); // fireRowArriveEvent(); } /** * Method setTopRow. Set the number of the line that is being displayed in * the top row of the CompositeTable editor (0-based). If the new top row is * not equal to the current top row, the table will automatically be * scrolled to the new position. This number must be greater than 0 and less * than NumRowsInCollection. * * @param topRow * the line number of the new top row. */ public void setTopRow(int topRow) { fireRowDepartEvent(); int topRowDelta = this.topRow - topRow; doSetTopRow(topRow, currentRow + topRowDelta); fireRowArriveEvent(); } /** * Method getTopRow. Return the number of the line that is being displayed * in the top row of the CompositeTable editor (0-based). * * @return the number of the top line. */ public int getTopRow() { return topRow; } /** * Method getSelection. Returns the currently-selected (column, row) pair * where the row specifies the offset from the top of the table window. In * order to get the current row in the underlying data structure, use * getSelection().y + getCurrentRow(). * * @return the currently-selected (column, row) pair where the row specifies * the offset from the top of the table window, or null if no * selection is available. */ public Point getSelection() { return currentRow != -1 ? new Point(currentColumn, currentRow) : null; } /** * Method setSelection. Sets the currently-selected (column, row) pair where * the row specifies the offset from the top of the table window. In order * to get the current row in the underlying data structure, use * getSelection().y + getCurrentRow(). * * @param column * the column to select * @param row * the row to select */ public void setSelection(int column, int row) { int topRowDelta = computeTopRowDelta(row); if (topRowDelta != 0) { doSetTopRow(topRow + topRowDelta, currentRow); row += -1 * topRowDelta; internalSetSelection(column, row, true); } else { if (row == currentRow) internalSetSelection(column, row, false); else { if (fireRequestRowChangeEvent()) internalSetSelection(column, row, true); } } } /** * (non-API) See {@link CompositeTable#clearSelection()} instead. */ public void clearSelection() { Point currentSelection = getSelection(); if(currentSelection != null) { fireRowDepartEvent(); currentRow = -1; } } /** * Method setWeights. Indicates that the column weights were just set and we * should re-layout the control holder object. */ public void setWeights() { layoutControlHolder(); } // Refresh Event API // -------------------------------------------------------------------------- /** * Adds the specified listener to the set of listeners that will be notified * when a row refresh event occurs. * * @param listener * the listener to add. */ public void addRefreshContentProvider(IRowContentProvider listener) { parent.contentProviders.add(listener); } /** * Remove the specified listener from the set of listeners that will be * notified when a row refresh event occurs. * * @param listener * the listener to remove. */ public void removeRefreshContentProvider(IRowContentProvider listener) { parent.contentProviders.remove(listener); } private void fireRefreshEvent(int positionInCollection, Control rowControl) { if (numRowsInCollection < 1) { return; } for (Iterator refreshListenersIter = parent.contentProviders.iterator(); refreshListenersIter .hasNext();) { IRowContentProvider listener = (IRowContentProvider) refreshListenersIter .next(); listener.refresh(parent, positionInCollection, rowControl); } } // Empty table placeholder // -------------------------------------------------------------------- private void createEmptyTablePlaceholer() { emptyTablePlaceholder = new EmptyTablePlaceholder(controlHolder, SWT.NULL); if (rowControl != null) emptyTablePlaceholder.setBackground(rowControl.getBackground()); emptyTablePlaceholder.setMessage(parent.getInsertHint()); } private void disposeEmptyTablePlaceholder() { if (emptyTablePlaceholder != null) { emptyTablePlaceholder.dispose(); emptyTablePlaceholder = null; } } // Event Handling // ----------------------------------------------------------------------------- private boolean needToRequestRC = true; private Listener displayKeyDownFilter = new Listener() { public void handleEvent(Event event) { if (rowControl.getClass().isAssignableFrom(event.widget.getClass())) { doMakeFocusedRowVisible(); } } }; /** * Handle a keyPressed event on any row control. * * @param sender * The row that is sending the event * @param e * the actual KeyEvent */ public void keyPressed(TableRow sender, KeyEvent e) { if (doMakeFocusedRowVisible()) return; if ((e.stateMask & SWT.CONTROL) != 0) { switch (e.keyCode) { case SWT.HOME: doFocusInitialRow(); return; case SWT.END: doFocusLastRow(); return; case SWT.INSERT: doInsertRow(); return; case SWT.DEL: doDeleteRow(); return; default: return; } } switch (e.keyCode) { case SWT.ARROW_UP: doRowUp(); return; case SWT.ARROW_DOWN: doRowDown(); return; case SWT.PAGE_UP: doPageUp(); return; case SWT.PAGE_DOWN: doPageDown(); return; } } /** * Handle the keyTraversed event on any child control in the table. * * @param sender * The row sending the event. * @param e * The SWT TraverseEvent */ public void keyTraversed(TableRow sender, TraverseEvent e) { if (doMakeFocusedRowVisible()) return; if (parent.isTraverseOnTabsEnabled()) { if (e.detail == SWT.TRAVERSE_TAB_NEXT) { if (currentColumn >= sender.getNumColumns() - 1) { e.detail = SWT.TRAVERSE_NONE; handleNextRowNavigation(); } } else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS) { if (currentColumn == 0) { e.detail = SWT.TRAVERSE_NONE; handlePreviousRowNavigation(sender); } } else if (e.detail == SWT.TRAVERSE_RETURN) { e.detail = SWT.TRAVERSE_NONE; if (currentColumn >= sender.getNumColumns() - 1) { handleNextRowNavigation(); } else { deferredSetFocus(getControl(currentColumn + 1, currentRow), false); } } } else { if (e.detail == SWT.TRAVERSE_TAB_NEXT) { if (currentColumn >= sender.getNumColumns() - 1) { e.detail = SWT.TRAVERSE_NONE; } } else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS) { if (currentColumn == 0) { e.detail = SWT.TRAVERSE_NONE; } } } } /** * Makes sure that the focused row is visible * * @return true if the display needed to be scrolled; false otherwise */ public boolean doMakeFocusedRowVisible() { if (numRowsVisible < 1) { return false; } int topRowDelta = computeTopRowDelta(currentRow); if (topRowDelta == 0) { return false; } // currentRow += -1 * topRowDelta; doSetTopRow(topRow + topRowDelta, currentRow + (-1 * topRowDelta)); Control control = getControl(currentColumn, currentRow); if (control != null) { control.setFocus(); // ?? Can I get away with avoiding asyncExec here ?? } return true; } private int computeTopRowDelta(int row) { int topRowDelta; if (row < 0) { topRowDelta = row; } else if (row >= getNumRowsVisible()) { topRowDelta = row - getNumRowsVisible() + 1; } else { return 0; } return topRowDelta; } /** * The SelectionListener for the table's vertical slider control. */ private SelectionListener sliderSelectionListener = new SelectionListener() { public void widgetSelected(SelectionEvent e) { if (vSlider.getSelection() == topRow) { return; } if (!fireRequestRowChangeEvent()) { vSlider.setSelection(topRow); return; } deselectCurrentRowIfVisible(); int delta = topRow - vSlider.getSelection(); int oldCurrentRow = currentRow; currentRow = vSlider.getSelection(); // setTopRow(vSlider.getSelection()); // Removed as a result of patch doSetTopRow(vSlider.getSelection(), currentRow + delta); // If the focused row just became visible, show the focus if (oldCurrentRow < 0 || oldCurrentRow >= getNumRowsVisible()) { if (currentRow >= 0 && currentRow < getNumRowsVisible()) { Control newFocusControl = getControl(currentColumn, currentRow); if (newFocusControl == null) { return; } if (newFocusControl.isFocusControl()) { newFocusControl.notifyListeners(SWT.FocusIn, new Event()); } else { deferredSetFocus(newFocusControl, true); } } } else { // If the new viewport doesn't overlap the old one, hide the focus if (currentRow < 0 || currentRow >= getNumRowsVisible()) { // deleteRowAt(oldCurrentRow); // getControl(currentColumn, oldCurrentRow).getParent().setVisible(false); // getControl(currentColumn, oldCurrentRow).getParent().setVisible(true); Control control = getControl(currentColumn, oldCurrentRow); if (control != null) { control.notifyListeners(SWT.FocusOut, new Event()); } } } } public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }; private SelectionListener hSliderSelectionListener = new SelectionListener() { /* (non-Javadoc) * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent) */ public void widgetSelected(SelectionEvent e) { Point scrollerSize = hScroller.getSize(); int preferredWidth = controlHolder.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x; Rectangle controlHolderBounds = new Rectangle(-1 * hSlider.getSelection(), 0, preferredWidth, scrollerSize.y); controlHolder.setBounds(controlHolderBounds); } public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }; /** * Scroll wheel event handling. */ public void handleEvent(Event event) { event.doit = false; if (event.count > 0) { // scroll up if (topRow > 0) { if (!fireRequestRowChangeEvent()) { return; } deselectCurrentRowIfVisible(); doSetTopRow(topRow - 1, currentRow + 1); if (isRowVisible(currentRow)) { deferredSetFocus(getControl(currentColumn, currentRow), true); } } } else { // scroll down if (topRow < numRowsInCollection - numRowsVisible) { if (!fireRequestRowChangeEvent()) { return; } deselectCurrentRowIfVisible(); doSetTopRow(topRow + 1, currentRow - 1); if (isRowVisible(currentRow)) { deferredSetFocus(getControl(currentColumn, currentRow), true); } } } // if (event.count > 0) { // Old code (before patch) // if (topRow > 0) { // if (!fireRequestRowChangeEvent()) { // return; // } // deselectCurrentRowIfVisible(); // setTopRow(topRow - 1); // ++currentRow; // if (currentRow == 0) { // deferredSetFocus(getControl(currentColumn, currentRow), true); // } // } // } else { // if (topRow < numRowsInCollection - numRowsVisible) { // if (!fireRequestRowChangeEvent()) { // return; // } // deselectCurrentRowIfVisible(); // setTopRow(topRow + 1); // --currentRow; // if (currentRow == getNumRowsVisible()-1) { // deferredSetFocus(getControl(currentColumn, currentRow), true); // } // } // } } private void deselectCurrentRowIfVisible() { if (currentRow >= 0 && currentRow < numRowsVisible) { Control control = getControl(currentColumn, currentRow); if (control != null) { deselect(control); } } } /** * Handle focusLost events on any child control. This is not currently used. * * @param sender * The row containing the sending control. * @param e * The SWT FocusEvent. */ public void focusLost(TableRow sender, FocusEvent e) { } /** * Handle focusGained events on any child control. * * NEBULA_FIXME: Needs to automatically scroll horizontally if the newly-focused * control is fully or partially occluded. * * @param sender * The row containing the sending control. * @param e * The SWT FocusEvent. */ public void focusGained(TableRow sender, FocusEvent e) { boolean rowChanged = false; int senderRowNumber = getRowNumber(sender); if (senderRowNumber != currentRow) { if (needToRequestRC) { if (!fireRequestRowChangeEvent()) { // Go back if we're not allowed to be here deferredSetFocus(getControl(currentColumn, currentRow), false); } } else { needToRequestRC = true; } rowChanged = true; } currentRow = senderRowNumber; currentColumn = sender.getColumnNumber((Control) e.widget); if (rowChanged) fireRowArriveEvent(); } private PaintListener headerPaintListener = new PaintListener() { public void paintControl(PaintEvent e) { if (parent.linesVisible) { drawGridLines(e, true); } } }; private PaintListener rowPaintListener = new PaintListener() { public void paintControl(PaintEvent e) { if (parent.linesVisible) { drawGridLines(e, false); } } }; private void drawGridLines(PaintEvent e, boolean isHeader) { Color oldColor = e.gc.getForeground(); try { // Get the colors we need Display display = Display.getCurrent(); Color lineColor = display .getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW); Color secondaryColor = display .getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); Color hilightColor = display .getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW); if (!isHeader) { lineColor = display .getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); } // Get the control Control toPaint = (Control) e.widget; Point controlSize = toPaint.getSize(); // Draw the bottom line(s) e.gc.setForeground(lineColor); e.gc.drawLine(0, controlSize.y - 1, controlSize.x, controlSize.y - 1); if (isHeader) { e.gc.setForeground(secondaryColor); e.gc.drawLine(0, controlSize.y - 2, controlSize.x, controlSize.y - 2); e.gc.setForeground(hilightColor); e.gc.drawLine(0, 1, controlSize.x, 1); } // Now draw lines around the child controls, if there are any if (toPaint instanceof Composite) { Composite row = (Composite) toPaint; Control[] children = row.getChildren(); for (int i = 0; i < children.length; i++) { Rectangle childBounds = children[i].getBounds(); // Paint the beginning lines if (isHeader) { e.gc.setForeground(hilightColor); e.gc.drawLine(childBounds.x - 2, 1, childBounds.x - 2, controlSize.y - 2); } // Paint the ending lines e.gc.setForeground(lineColor); int lineLeft = childBounds.x + childBounds.width + 1; e.gc.drawLine(lineLeft, 0, lineLeft, controlSize.y); if (isHeader) { e.gc.setForeground(secondaryColor); e.gc.drawLine(lineLeft - 1, 0, lineLeft - 1, controlSize.y - 1); } } } } finally { e.gc.setForeground(oldColor); } } // Event Firing // ------------------------------------------------------------------------------- /** * Fire the row construction event * * @param newControl * The new row's SWT control */ private void fireRowConstructionEvent(Control newControl) { for (Iterator rowConstructionListenersIter = parent.rowConstructionListeners .iterator(); rowConstructionListenersIter.hasNext();) { RowConstructionListener listener = (RowConstructionListener) rowConstructionListenersIter .next(); listener.rowConstructed(newControl); } } /** * Fire the row construction event * * @param newControl * The new row's SWT control */ private void fireHeaderConstructionEvent(Control newControl) { for (Iterator rowConstructionListenersIter = parent.rowConstructionListeners .iterator(); rowConstructionListenersIter.hasNext();) { RowConstructionListener listener = (RowConstructionListener) rowConstructionListenersIter .next(); listener.headerConstructed(newControl); } } /** * Indicate to listeners that the focus is arriving on the specified row */ private void fireRowArriveEvent() { if (rows.size() < 1 || !isRowVisible(currentRow)) { return; } for (Iterator rowChangeListenersIter = parent.rowFocusListeners .iterator(); rowChangeListenersIter.hasNext();) { IRowFocusListener listener = (IRowFocusListener) rowChangeListenersIter.next(); // currentRow() can be null if it's scrolled off the top or bottom TableRow row = currentRow(); Control control = row != null ? row.getRowControl() : null; listener.arrive(parent, topRow + currentRow, control); } } /** * Request permission from all listeners to leave the current row. * * @return true if all listeners permit the row change; false otherwise. */ private boolean fireRequestRowChangeEvent() { if (rows.size() < 1 || !isRowVisible(currentRow)) { return true; } if (currentRow > rows.size() - 1) { // (if the other row is already gone) return true; } for (Iterator rowChangeListenersIter = parent.rowFocusListeners .iterator(); rowChangeListenersIter.hasNext();) { IRowFocusListener listener = (IRowFocusListener) rowChangeListenersIter .next(); // currentRow() can be null if it's scrolled off the top or bottom TableRow row = currentRow(); Control control = row != null ? row.getRowControl() : null; if (!listener.requestRowChange(parent, topRow + currentRow, control)) { return false; } } fireRowDepartEvent(); return true; } /** * Indicate to listeners that the focus is about to leave the current row. */ private void fireRowDepartEvent() { if (rows.size() < 1 || !isRowVisible(currentRow)) { return; } for (Iterator rowChangeListenersIter = parent.rowFocusListeners .iterator(); rowChangeListenersIter.hasNext();) { IRowFocusListener listener = (IRowFocusListener) rowChangeListenersIter .next(); // currentRow() can be null if it's scrolled off the top or bottom TableRow row = currentRow(); Control control = row != null ? row.getRowControl() : null; if (control != null) listener.depart(parent, topRow + currentRow, control); } } /** * Request deletion of the current row from the underlying data structure. * * @return true if the deletion was successful; false otherwise. */ private boolean fireDeleteEvent() { if (parent.deleteHandlers.size() < 1) { return false; } int absoluteRow = topRow + currentRow; for (Iterator deleteHandlersIter = parent.deleteHandlers.iterator(); deleteHandlersIter .hasNext();) { IDeleteHandler handler = (IDeleteHandler) deleteHandlersIter.next(); if (!handler.canDelete(absoluteRow)) { return false; } } for (Iterator deleteHandlersIter = parent.deleteHandlers.iterator(); deleteHandlersIter .hasNext();) { IDeleteHandler handler = (IDeleteHandler) deleteHandlersIter.next(); handler.deleteRow(absoluteRow); } return true; } private void fireRowDeletedEvent() { int absoluteRow = topRow + currentRow; for (Iterator deleteHandlersIter = parent.deleteHandlers.iterator(); deleteHandlersIter .hasNext();) { IDeleteHandler handler = (IDeleteHandler) deleteHandlersIter.next(); handler.rowDeleted(absoluteRow); } } /** * Request that the model insert a new row into itself. * * @return The 0-based offset of the new row from the start of the * collection or -1 if a new row could not be inserted. */ private int fireInsertEvent() { if (parent.insertHandlers.size() < 1) { return -1; } for (Iterator insertHandlersIter = parent.insertHandlers.iterator(); insertHandlersIter .hasNext();) { IInsertHandler handler = (IInsertHandler) insertHandlersIter.next(); int resultRow = handler.insert(topRow + currentRow); if (resultRow >= 0) { return resultRow; } } return -1; } /** * Tell listeners that we just scrolled. * @param scrollEvent */ private void fireScrollEvent(ScrollEvent scrollEvent) { if (parent.scrollListeners.size() < 1) { return; } for (Iterator scrollListenersIter = parent.scrollListeners.iterator(); scrollListenersIter.hasNext();) { ScrollListener scrollListener = (ScrollListener) scrollListenersIter.next(); scrollListener.tableScrolled(scrollEvent); } } // Event Handling, utility methods // ------------------------------------------------------------ /** * Set the widget's selection to an empty selection. * * @param widget * The widget to deselect */ private void deselect(Widget widget) { if (DuckType.instanceOf(ISelectableRegionControl.class, widget)) { ISelectableRegionControl control = (ISelectableRegionControl) DuckType .implement(ISelectableRegionControl.class, widget); control.setSelection(0, 0); } } /** * Try to go to the next row in the collection. */ private void handleNextRowNavigation() { if (currentRow < numRowsVisible - 1) { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; deselect(getControl(currentColumn, currentRow)); deferredSetFocus(getControl(0, currentRow + 1), false); } else { if (topRow + numRowsVisible >= numRowsInCollection) { // We're at the end; don't go anywhere return; } // We have to scroll forwards if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; deselect(getControl(currentColumn, currentRow)); doSetTopRow(topRow + 1, currentRow); deferredSetFocus(getControl(0, currentRow), true); } } /** * Try to go to the previous row in the collection. * * @param row * The current table row. */ private void handlePreviousRowNavigation(TableRow row) { if (currentRow == 0) { if (topRow == 0) { // We're at the beginning of the table; don't go anywhere return; } // We have to scroll backwards if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; deselect(getControl(currentColumn, currentRow)); doSetTopRow(topRow - 1, currentRow); deferredSetFocus(getControl(row.getNumColumns() - 1, 0), true); } else { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; deselect(getControl(currentColumn, currentRow)); deferredSetFocus( getControl(row.getNumColumns() - 1, currentRow - 1), false); } } /** * Gets the current TableRow. * * @return the current TableRow */ private TableRow currentRow() { if (currentRow < 0 || currentRow > rows.size() - 1) { return null; } return (TableRow) rows.get(currentRow); } /** * Returns the SWT control corresponding to the current row. * * @return the current row control. */ public Control getCurrentRowControl() { TableRow currentRow = currentRow(); if (currentRow == null) { return null; } return currentRow().getRowControl(); } /** * Method getRowControls. Returns an array of SWT controls where each * control represents a row control in the CompositeTable's current scrolled * position. If CompositeTable is resized, scrolled, such that the rows that * the CompositeTable control is displaying change in any way, the array * that is returned by this method will become out of date and need to be * retrieved again. * * @return Control[] An array of SWT Control objects, each representing an * SWT row object. */ public Control[] getRowControls() { Control[] rowControls = new Control[rows.size()]; for (int i = 0; i < rowControls.length; i++) { rowControls[i] = getRowByNumber(i).getRowControl(); } return rowControls; } private Menu menu = null; /* (non-Javadoc) * @see org.eclipse.swt.widgets.Control#setMenu(org.eclipse.swt.widgets.Menu) */ public void setMenu(final Menu menu) { this.menu = menu; setMenuOnCollection(rows, menu); setMenuOnCollection(spareRows, menu); } private void setMenuOnCollection(LinkedList collection, Menu menu) { for (Iterator rowsIter = collection.iterator(); rowsIter.hasNext();) { TableRow row = (TableRow) rowsIter.next(); row.getRowControl().setMenu(menu); } } /** * Method getControlRow. Given a row control, returns its row number * relative to the topRow. * * @param rowControl The row object to find * @return The row number of the rowControl relative to the topRow (0-based) * @throws IllegalArgumentException if rowControl is not currently visible */ public int getControlRow(Control rowControl) { for (int row = 0; row < rows.size(); row++) { if (getRowByNumber(row).getRowControl() == rowControl) { return row; } } throw new IllegalArgumentException("getControlRow passed a control that is not visible inside CompositeTable"); } /** * Method getControlRowObject. Given a row control, returns its row number * relative to the topRow. * * @param rowControl The row object to find * @return The row object managing the rowControl * @throws IllegalArgumentException if rowControl is not currently visible */ public TableRow getControlRowObject(Control rowControl) { for (Iterator rowsIter = rows.iterator(); rowsIter.hasNext();) { TableRow row = (TableRow) rowsIter.next(); if (row.getRowControl() == rowControl) { return row; } } throw new IllegalArgumentException("getControlRowObject passed a control that is not visible inside CompositeTable"); } /** * Returns the TableRow by the specified 0-based offset from the top visible * row. * * @param rowNumber * 0-based offset of the requested fow starting from the top * visible row. * @return The corresponding TableRow or null if there is none. */ private TableRow getRowByNumber(int rowNumber) { if (rowNumber > rows.size() - 1 || rowNumber < 0) { return null; } return (TableRow) rows.get(rowNumber); } /** * Return the SWT control at (column, row), where row is a 0-based number * starting from the top visible row. * * @param column * the 0-based column. * @param row * the 0-based row starting from the top visible row. * @return the SWT control at (column, row) */ private Control getControl(int column, int row) { TableRow rowObject = getRowByNumber(row); if (rowObject == null) { throw new IndexOutOfBoundsException("Request for a nonexistent row"); //$NON-NLS-1$ } Control result = rowObject.getColumnControl(column); return result; } /** * Return the 0-based row number corresponding to a particular TableRow * object. * * @param row * The TableRow to translate to row coordinates. * @return the 0-based row number or -1 if the specified TableRow is not * visible. */ private int getRowNumber(TableRow row) { int rowNumber = 0; for (Iterator rowIter = rows.iterator(); rowIter.hasNext();) { TableRow candidate = (TableRow) rowIter.next(); if (candidate == row) { return rowNumber; } ++rowNumber; } return -1; } /** * Set the focus to the specified (column, row). If rowChange is true, fire * a row change event, otherwise be silent. * * @param column * The 0-based column to focus * @param row * The 0-based row to focus * @param rowChange * true if a row change event should be fired; false otherwise. */ private void internalSetSelection(int column, int row, boolean rowChange) { Control toFocus = getControl(column, row); if (toFocus == null) { return; } if (toFocus.isFocusControl()) { toFocus.notifyListeners(SWT.FocusIn, new Event()); } else { deferredSetFocus(toFocus, rowChange); } } /** * Set the focus to the specified control after allowing all pending events * to complete first. If rowChange is true, fire a row arrive event after * the focus has been set. * * @param toFocus * The SWT Control to focus * @param rowChange * true if the rowArrive event should be fired; false otherwise. */ private void deferredSetFocus(final Control toFocus, final boolean rowChange) { if (toFocus == null) { return; } Display.getCurrent().asyncExec(new Runnable() { public void run() { if (toFocus.isDisposed()) return; toFocus.setFocus(); if (rowChange) { fireRowArriveEvent(); } } }); } public void doFocusInitialRow() { if (topRow <= 0) { return; } if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; Widget widget = getDisplay().getFocusControl(); deselect(widget); // Used to be e.widget // If the focus is already in the top visible row, we will need // to explicitly // fire an arrive event. boolean needToArrive = true; if (currentRow > 0) { needToArrive = false; } doSetTopRow(0, currentRow); if (needToArrive) { internalSetSelection(currentColumn, 0, true); } else { internalSetSelection(currentColumn, 0, false); } } public void doFocusLastRow() { if (topRow + numRowsVisible < numRowsInCollection) { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; Widget widget = getDisplay().getFocusControl(); deselect(widget); // Used to be e.widget // If the focus is already in the last visible row, we will // need to explicitly // fire an arrive event. boolean needToArrive = true; if (currentRow < numRowsVisible - 1) { needToArrive = false; } doSetTopRow(numRowsInCollection - numRowsVisible, currentRow); if (needToArrive) { internalSetSelection(currentColumn, numRowsVisible - 1, true); } else { internalSetSelection(currentColumn, numRowsVisible - 1, false); } } } public void doPageUp() { if (topRow > 0) { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; int newTopRow = topRow - numRowsInDisplay; if (newTopRow < 0) { newTopRow = 0; } doSetTopRow(newTopRow, 0); internalSetSelection(currentColumn, currentRow, true); } } public void doPageDown() { if (topRow + numRowsVisible < numRowsInCollection) { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; int newTopRow = topRow + numRowsVisible; if (newTopRow >= numRowsInCollection - 1) { newTopRow = numRowsInCollection - 1; } doSetTopRow(newTopRow, numRowsVisible - 1); internalSetSelection(currentColumn, currentRow, true); } } public void doRowUp() { if (maxRowsVisible <= 1) return; if (currentRow > 0) { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; Widget widget = getDisplay().getFocusControl(); deselect(widget); // Used to be e.widget internalSetSelection(currentColumn, currentRow - 1, false); return; } if (topRow > 0) { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; Widget widget = getDisplay().getFocusControl(); deselect(widget); // Used to be e.widget doSetTopRow(topRow - 1, currentRow); internalSetSelection(currentColumn, currentRow, true); return; } } public void doRowDown() { if (maxRowsVisible <= 1) return; if (currentRow < numRowsVisible - 1) { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; Widget widget = getDisplay().getFocusControl(); deselect(widget); // Used to be e.widget internalSetSelection(currentColumn, currentRow + 1, false); return; } if (topRow + numRowsVisible < numRowsInCollection) { if (!fireRequestRowChangeEvent()) { return; } needToRequestRC = false; Widget widget = getDisplay().getFocusControl(); deselect(widget); // Used to be e.widget doSetTopRow(topRow + 1, currentRow); internalSetSelection(currentColumn, currentRow, true); return; } } public boolean doInsertRow() { // If no insertHandler has been registered, bail out if (parent.insertHandlers.size() < 1) { return false; } // Make sure we can leave the current row if (!fireRequestRowChangeEvent()) { return false; } needToRequestRC = false; // Insert the new object int newRowPosition = fireInsertEvent(); if (newRowPosition < 0) { // This should never happen, but... throw new IllegalArgumentException("Insert < row 0???"); } disposeEmptyTablePlaceholder(); // If the current widget has a selection, deselect it Widget widget = getDisplay().getFocusControl(); deselect(widget); // Used to be e.widget // If the new row is in the visible space, refresh it if (topRow <= newRowPosition && numRowsVisible > newRowPosition - topRow) { insertRowAt(newRowPosition - topRow); ++numRowsInCollection; updateVisibleRows(); int newRowNumber = newRowPosition - topRow; if (newRowNumber != currentRow) { internalSetSelection(currentColumn, newRowNumber, false); } else { internalSetSelection(currentColumn, newRowNumber, true); } return true; } // else... ++numRowsInCollection; // If the new row is above us, scroll up to it if (newRowPosition < topRow + currentRow) { doSetTopRow(newRowPosition, currentRow); Display.getDefault().asyncExec(new Runnable() { public void run() { updateVisibleRows(); if (currentRow != 0) { internalSetSelection(currentColumn, 0, false); } else { internalSetSelection(currentColumn, 0, true); } } }); } else { // If we're appending if (numRowsInDisplay > numRowsVisible) { updateVisibleRows(); int newRowNumber = newRowPosition - topRow; if (newRowNumber != currentRow) { internalSetSelection(currentColumn, newRowNumber, false); } else { internalSetSelection(currentColumn, newRowNumber, true); } } else { // It's somewhere in the middle below us; scroll down to // it doSetTopRow(newRowPosition - numRowsVisible + 1, currentRow); int newRowNumber = numRowsVisible - 1; if (newRowNumber != currentRow) { internalSetSelection(currentColumn, newRowNumber, false); } else { internalSetSelection(currentColumn, newRowNumber, true); } } } return false; } public boolean doDeleteRow() { if (fireDeleteEvent()) { // We know the object is gone if we made it here, so now // refresh the display... --numRowsInCollection; // If we deleted the last row in the list if (currentRow >= numRowsVisible - 1) { // If that wasn't the last row in the collection, move // the focus if (numRowsInCollection > 0) { // If we're only displaying one row, scroll first if (currentRow < 1) { needToRequestRC = false; deleteRowAt(currentRow); doSetTopRow(topRow - 1, currentRow); internalSetSelection(currentColumn, currentRow, true); } else { needToRequestRC = false; internalSetSelection(currentColumn, currentRow - 1, false); Display.getCurrent().asyncExec(new Runnable() { public void run() { deleteRowAt(currentRow + 1); updateVisibleRows(); } }); } } else { // Otherwise, show the placeholder object and give // it focus deleteRowAt(currentRow); --numRowsVisible; createEmptyTablePlaceholer(); emptyTablePlaceholder.setFocus(); } } else { // else, keep the focus where it was deleteRowAt(currentRow); updateVisibleRows(); internalSetSelection(currentColumn, currentRow, true); } fireRowDeletedEvent(); } return false; } }