InternalCompositeTable.java
上传用户:hengxinggs
上传日期:2008-01-15
资源大小:212k
文件大小:44k
源码类别:

PlugIns编程

开发平台:

Java

  1. /*
  2.  * Copyright (C) 2005 David Orme <djo@coconut-palm-software.com>
  3.  * 
  4.  * All rights reserved. This program and the accompanying materials
  5.  * are made available under the terms of the Eclipse Public License v1.0
  6.  * which accompanies this distribution, and is available at
  7.  * http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Contributors:
  10.  *     David Orme     - Initial API and implementation
  11.  */
  12. package org.eclipse.jface.examples.databinding.compositetable;
  13. import java.lang.reflect.Constructor;
  14. import java.util.Iterator;
  15. import java.util.LinkedList;
  16. import org.eclipse.jface.examples.databinding.compositetable.internal.EmptyTablePlaceholder;
  17. import org.eclipse.jface.examples.databinding.compositetable.internal.ISelectableRegionControl;
  18. import org.eclipse.jface.examples.databinding.compositetable.internal.TableRow;
  19. import org.eclipse.jface.examples.databinding.compositetable.reflect.DuckType;
  20. import org.eclipse.swt.SWT;
  21. import org.eclipse.swt.events.FocusEvent;
  22. import org.eclipse.swt.events.KeyEvent;
  23. import org.eclipse.swt.events.PaintEvent;
  24. import org.eclipse.swt.events.PaintListener;
  25. import org.eclipse.swt.events.SelectionEvent;
  26. import org.eclipse.swt.events.SelectionListener;
  27. import org.eclipse.swt.events.TraverseEvent;
  28. import org.eclipse.swt.graphics.Color;
  29. import org.eclipse.swt.graphics.Point;
  30. import org.eclipse.swt.graphics.Rectangle;
  31. import org.eclipse.swt.layout.FillLayout;
  32. import org.eclipse.swt.layout.GridData;
  33. import org.eclipse.swt.layout.GridLayout;
  34. import org.eclipse.swt.widgets.Composite;
  35. import org.eclipse.swt.widgets.Control;
  36. import org.eclipse.swt.widgets.Display;
  37. import org.eclipse.swt.widgets.Event;
  38. import org.eclipse.swt.widgets.Layout;
  39. import org.eclipse.swt.widgets.Listener;
  40. import org.eclipse.swt.widgets.Slider;
  41. import org.eclipse.swt.widgets.Widget;
  42. /** (non-API)
  43.  * Class InternalCompositeTable.  This is the run-time CompositeTableControl.  It gets its prototype
  44.  * row and (optional) header objects from its SWT parent, then uses them to implement an SWT 
  45.  * virtual table control.
  46.  * 
  47.  * @author djo
  48.  */
  49. public class InternalCompositeTable extends Composite implements Listener {
  50. // The internal UI controls that make up this control.
  51. private Composite sliderHolder = null;
  52. private Composite controlHolder = null;
  53. private Slider slider = null;
  54. private EmptyTablePlaceholder emptyTablePlaceholder = null;
  55. // My parent CompositeTable
  56. private CompositeTable parent;
  57. // Property fields
  58. private int maxRowsVisible;
  59. private int numRowsInDisplay;
  60. private int numRowsInCollection;
  61. private int topRow;
  62. private int currentRow;
  63. private int currentColumn;
  64. // The visible/invisible row objects and bookeeping info about them
  65. private int currentVisibleTopRow = 0;
  66. private int numRowsVisible = 0;
  67. private LinkedList rows = new LinkedList();
  68. private LinkedList spareRows = new LinkedList();
  69. // The prototype header/row objects and Constructors so we can duplicate them
  70. private Constructor headerConstructor;
  71. private Constructor rowConstructor;
  72. private Control headerControl;
  73. private Control myHeader = null;
  74. private Control rowControl;
  75. /**
  76.  * Constructor InternalCompositeTable.  The usual SWT constructor.  The same style bits are
  77.  * allowed here as are allowed on Composite.
  78.  * 
  79.  * @param parentControl The SWT parent.
  80.  * @param style Style bits.
  81.  */
  82. public InternalCompositeTable(Composite parentControl, int style) {
  83. super(parentControl, style);
  84. initialize();
  85. this.parent = (CompositeTable) parentControl;
  86. setBackground(parentControl.getBackground());
  87. controlHolder.addListener(SWT.MouseWheel, this);
  88. maxRowsVisible = parent.getMaxRowsVisible();
  89. numRowsInCollection = parent.getNumRowsInCollection();
  90. topRow = parent.getTopRow();
  91. headerConstructor = parent.getHeaderConstructor();
  92. rowConstructor = parent.getRowConstructor();
  93. headerControl = parent.getHeaderControl();
  94. rowControl = parent.getRowControl();
  95. currentVisibleTopRow = topRow;
  96. showHeader();
  97. updateVisibleRows();
  98. if (numRowsVisible < 1) {
  99. createEmptyTablePlaceholer();
  100. }
  101. }
  102. public void setBackground(Color color) {
  103. super.setBackground(color);
  104. controlHolder.setBackground(color);
  105. }
  106. /**
  107.  * Initialize the overall table UI.
  108.  */
  109. private void initialize() {
  110. GridLayout gl = new GridLayout();
  111. gl.numColumns = 2;
  112. gl.verticalSpacing = 0;
  113. gl.marginWidth = 0;
  114. gl.marginHeight = 0;
  115. gl.horizontalSpacing = 0;
  116. this.setLayout(gl);
  117. createControlHolder();
  118. createSliderHolder();
  119. }
  120. /**
  121.  * Initialize the controlHolder, which is the holder Composite for the header object (if
  122.  * applicable) and the row objects.
  123.  */
  124. private void createControlHolder() {
  125. GridData gridData = new org.eclipse.swt.layout.GridData();
  126. gridData.horizontalAlignment = org.eclipse.swt.layout.GridData.FILL;
  127. gridData.grabExcessHorizontalSpace = true;
  128. gridData.grabExcessVerticalSpace = true;
  129. gridData.verticalAlignment = org.eclipse.swt.layout.GridData.FILL;
  130. controlHolder = new Composite(this, SWT.NONE);
  131. controlHolder.setLayoutData(gridData);
  132. controlHolder.setLayout(new Layout() {
  133. protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
  134. if (rowControl != null) {
  135. int height = 0;
  136. int width = 0;
  137. if (headerControl != null) {
  138. Point headerSize = headerControl.getSize();
  139. width = headerSize.x;
  140. height = headerSize.y;
  141. }
  142. Point rowSize = rowControl.getSize();
  143. height += rowSize.y * 2;
  144. if (width < rowSize.x) {
  145. width = rowSize.x;
  146. }
  147. return new Point(height, width);
  148. }
  149. return new Point(50, 50);
  150. }
  151. protected void layout(Composite composite, boolean flushCache) {
  152. layoutControlHolder();
  153. }
  154. });
  155. }
  156. /**
  157.  * Initialize the sliderHolder and slider.  The SliderHolder is a Composite that is 
  158.  * responsible for showing and hiding the vertical slider upon request.
  159.  */
  160. private void createSliderHolder() {
  161. GridData gd = getSliderGridData();
  162. sliderHolder = new Composite(this, SWT.NONE);
  163. slider = new Slider(sliderHolder, SWT.VERTICAL);
  164. slider.addSelectionListener(sliderSelectionListener);
  165. sliderHolder.setLayout(new FillLayout());
  166. sliderHolder.setLayoutData(gd);
  167. sliderHolder.setTabList(new Control[] {});
  168. }
  169. // Slider utility methods ---------------------------------------------------------------------
  170. /**
  171.  * Returns a GridData for the SliderHolder appropriate for if the slider is visible or not.
  172.  * 
  173.  * @return A GridData with a widthHint of 0 if the slider is not visible or with a widthHint
  174.  * of SWT.DEFAULT otherwise.
  175.  */
  176. private GridData getSliderGridData() {
  177. GridData gd = new org.eclipse.swt.layout.GridData();
  178. gd.grabExcessVerticalSpace = true;
  179. gd.verticalAlignment = org.eclipse.swt.layout.GridData.FILL;
  180. gd.verticalSpan = 1;
  181. if (!sliderVisible) {
  182. gd.widthHint = 0;
  183. }
  184. gd.horizontalAlignment = org.eclipse.swt.layout.GridData.CENTER;
  185. return gd;
  186. }
  187. private boolean sliderVisible = false;
  188. /**
  189.  * Sets if the slider is visible or not.
  190.  * 
  191.  * @param visible true if the slider should be visible; false otherwise.
  192.  */
  193. public void setSliderVisible(boolean visible) {
  194. this.sliderVisible = visible;
  195. sliderHolder.setLayoutData(getSliderGridData());
  196. Display.getCurrent().asyncExec(new Runnable() {
  197. public void run() {
  198. sliderHolder.getParent().layout(true);
  199. sliderHolder.layout(true);
  200. Point sliderHolderSize = sliderHolder.getSize();
  201. slider.setBounds(0, 0, sliderHolderSize.x, sliderHolderSize.y);
  202. }
  203. });
  204. }
  205. /**
  206.  * Returns if the slider is visible.
  207.  * 
  208.  * @return true if the slider is visible; false otherwise.
  209.  */
  210. public boolean isSliderVisible() {
  211. return sliderVisible;
  212. }
  213. /* (non-Javadoc)
  214.  * @see org.eclipse.swt.widgets.Widget#dispose()
  215.  */
  216. public void dispose() {
  217. disposeRows(rows);
  218. disposeRows(spareRows);
  219. super.dispose();
  220. }
  221. /**
  222.  * Disposes all the row objects in the specified LinkedList.
  223.  * 
  224.  * @param rowsCollection The collection containing TableRow objects to dispose.
  225.  */
  226. private void disposeRows(LinkedList rowsCollection) {
  227. for (Iterator rowsIter = rowsCollection.iterator(); rowsIter.hasNext();) {
  228. TableRow row = (TableRow) rowsIter.next();
  229. row.dispose();
  230. }
  231. }
  232. // Row object layout --------------------------------------------------------------------------
  233. /**
  234.  * Layout the child controls within the controlHolder Composite.
  235.  */
  236. protected void layoutControlHolder() {
  237. if (myHeader != null)
  238. layoutChild(myHeader);
  239. for (Iterator rowsIter = rows.iterator(); rowsIter.hasNext();) {
  240. TableRow row = (TableRow) rowsIter.next();
  241. layoutChild(row.getRowControl());
  242. }
  243. updateVisibleRows();
  244. }
  245. /**
  246.  * Layout a particular row or header control (child control of the controlHolder).
  247.  * If the child control has a layout manager, we delegate to that layout manager.
  248.  * Otherwise, we use the built in table layout manager.
  249.  * 
  250.  * @param child The row or header control to layout.
  251.  * @return height of child
  252.  */
  253. private int layoutChild(Control child) {
  254. if (child instanceof Composite) {
  255. Composite composite = (Composite) child;
  256. if (composite.getLayout() == null) {
  257. return parent.layoutHeaderOrRow(composite);
  258. }
  259. composite.layout(true);
  260. return composite.getSize().y;
  261. }
  262. return child.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y;
  263. }
  264. // Table control layout -- utility methods ----------------------------------------------------
  265. /**
  266.  * Construct a header or row object on demand.  Logs an error and returns null on failure.
  267.  * 
  268.  * @param parent The SWT parent.
  269.  * @param constructor The header or row object's constructor.
  270.  * @return The constructed control or null if none could be constructed.
  271.  */
  272. private Control createInternalControl(Composite parent, Constructor constructor) {
  273. Control result = null;
  274. try {
  275. result = (Control) constructor.newInstance(new Object[] {parent, new Integer(SWT.NULL)});
  276. } catch (Exception e) {
  277. throw new IllegalArgumentException("Unable to construct control"); //$NON-NLS-1$
  278. }
  279. return result;
  280. }
  281. /**
  282.  * If the header control hasn't been created yet, create and show it.
  283.  */
  284. private void showHeader() {
  285. if (myHeader == null && headerConstructor != null) {
  286. myHeader = createInternalControl(controlHolder, headerConstructor);
  287. if (myHeader instanceof Composite) {
  288. Composite headerComp = (Composite) myHeader;
  289. if (headerComp.getLayout() == null) {
  290. headerComp.addPaintListener(headerPaintListener);
  291. }
  292. }
  293. layoutChild(myHeader);
  294. }
  295. }
  296. // Table control layout -- main refresh algorithm ---------------------------------------------
  297. /**
  298.  * Main refresh algorithm entry point.  This method refreshes everything in the table:
  299.  * 
  300.  * <ul>
  301.  * <li>Makes sure the correct number of rows are visible
  302.  * <li>Makes sure each row has been refreshed with data from the underlying model
  303.  * </ul>
  304.  */
  305. void updateVisibleRows() {
  306. // If we don't have our prototype row object yet, bail out
  307. if (rowControl == null) {
  308. return;
  309. }
  310. // Figure out how many rows we can stack vertically
  311. int clientAreaHeight = controlHolder.getSize().y;
  312. if (clientAreaHeight <= 0) {
  313. return;
  314. }
  315. int topPosition = 0;
  316. int headerHeight = 0;
  317. if (myHeader != null) {
  318. headerHeight = headerControl.getSize().y + 3;
  319. clientAreaHeight -= headerHeight;
  320. topPosition += headerHeight;
  321. }
  322. numRowsInDisplay = clientAreaHeight / rowControl.getSize().y;
  323. // Make sure we have something to lay out to begin with
  324. if (numRowsInCollection > 0) {
  325. numRowsVisible = numRowsInDisplay;
  326. disposeEmptyTablePlaceholder();
  327. int displayableRows = numRowsInCollection - topRow;
  328. if (numRowsVisible > displayableRows) {
  329. numRowsVisible = displayableRows;
  330. }
  331. if (numRowsVisible > maxRowsVisible) {
  332. numRowsVisible = maxRowsVisible;
  333. }
  334. if (numRowsVisible < 1) {
  335. numRowsVisible = 1;
  336. }
  337. // Scroll the view so that the right number of row
  338. // objects are showing and they have the right data
  339. if (rows.size() - Math.abs(currentVisibleTopRow - topRow) > 0) {
  340. if (currentRow >= numRowsVisible) {
  341. deleteRowAt(0);
  342. ++currentVisibleTopRow;
  343. ++topRow;
  344. --currentRow;
  345. }
  346. scrollTop();
  347. fixNumberOfRows();
  348. } else {
  349. currentVisibleTopRow = topRow;
  350. // The order of number fixing/refresh is important in order to
  351. // minimize the number of screen redraw operations
  352. if (rows.size() > numRowsVisible) {
  353. fixNumberOfRows();
  354. refreshAllRows();
  355. } else {
  356. refreshAllRows();
  357. fixNumberOfRows();
  358. }
  359. }
  360. } else {
  361. numRowsVisible = 0;
  362. topRow=0;
  363. currentRow=0;
  364. currentColumn=0;
  365. currentVisibleTopRow = 0;
  366. numRowsVisible = 0;
  367. if (emptyTablePlaceholder == null) {
  368. fixNumberOfRows();
  369. createEmptyTablePlaceholer();
  370. }
  371. }
  372. // Show, hide, reset the scroll bar
  373. if (numRowsVisible < numRowsInCollection) {
  374. int extra = numRowsInCollection - numRowsVisible;
  375. int pageIncrement = numRowsVisible;
  376. if (pageIncrement > extra)
  377. pageIncrement = extra;
  378. slider.setMaximum(numRowsInCollection);
  379. slider.setMinimum(0);
  380. slider.setIncrement(1);
  381. slider.setPageIncrement(pageIncrement);
  382. slider.setThumb(numRowsInCollection - (numRowsInCollection - numRowsVisible));
  383. slider.setSelection(topRow);
  384. if (!isSliderVisible()) {
  385. setSliderVisible(true);
  386. }
  387. } else {
  388. setSliderVisible(false);
  389. }
  390. // Lay out the header and rows correctly in the display
  391. int width = controlHolder.getSize().x;
  392. // First, the header...
  393. if (myHeader != null) {
  394. myHeader.setBounds(0, 0, width, headerHeight);
  395. }
  396. // Make sure we have rows to lay out...
  397. if (numRowsInCollection < 1) {
  398. return;
  399. }
  400. // Now the rows.
  401. int rowHeight = 50;
  402. rowHeight = rowControl.getSize().y;
  403. for (Iterator rowsIter = rows.iterator(); rowsIter.hasNext();) {
  404. TableRow row = (TableRow) rowsIter.next();
  405. Control rowControl = row.getRowControl();
  406. rowControl.setBounds(0, topPosition, width, rowHeight);
  407. layoutChild(rowControl);
  408. topPosition += rowHeight;
  409. }
  410. }
  411. /**
  412.  * Utility method: Makes sure that the currently visible top row is the same as the top row
  413.  * specified in the TopRow property.
  414.  */
  415. private void scrollTop() {
  416. while (currentVisibleTopRow < topRow) {
  417. deleteRowAt(0);
  418. ++currentVisibleTopRow;
  419. }
  420. while (currentVisibleTopRow > topRow) {
  421. --currentVisibleTopRow;
  422. insertRowAt(0);
  423. }
  424. }
  425. /**
  426.  * Utility method: Makes sure that the number of rows that are visible correspond with
  427.  * what should be visible given the table control's size, where it is scrolled, and
  428.  * the number of rows in the underlying collection.
  429.  */
  430. private void fixNumberOfRows() {
  431. int numRows = rows.size();
  432. while (numRows > numRowsVisible) {
  433. deleteRowAt(numRows-1);
  434. numRows = rows.size();
  435. }
  436. while (numRows < numRowsVisible) {
  437. insertRowAt(numRows);
  438. numRows = rows.size();
  439. }
  440. }
  441. /**
  442.  * Fire the refresh event on all visible rows.
  443.  */
  444. void refreshAllRows() {
  445. int row=0;
  446. for (Iterator rowsIter = rows.iterator(); rowsIter.hasNext();) {
  447. TableRow rowControl = (TableRow) rowsIter.next();
  448. fireRefreshEvent(topRow + row, rowControl.getRowControl());
  449. ++row;
  450. }
  451. }
  452. /**
  453.  * Insert a new row object at the specified 0-based position relatve to the topmost row.
  454.  * 
  455.  * @param position The 0-based position relative to the topmost row.
  456.  */
  457. private void insertRowAt(int position) {
  458. TableRow newRow = getNewRow();
  459. if (position > rows.size()) {
  460. position = rows.size();
  461. }
  462. rows.add(position, newRow);
  463. fireRefreshEvent(currentVisibleTopRow + position, newRow.getRowControl());
  464. }
  465. /**
  466.  * Delete the row at the specified 0-based position relative to the topmost row.
  467.  * 
  468.  * @param position The 0-based position relative to the topmost row.
  469.  */
  470. private void deleteRowAt(int position) {
  471. TableRow row = (TableRow) rows.remove(position);
  472. row.setVisible(false);
  473. spareRows.addLast(row);
  474. }
  475. /**
  476.  * Utility method: Creates a new row object or recycles one that had been previously 
  477.  * created but was no longer needed.
  478.  * 
  479.  * @return The new row object.
  480.  */
  481. private TableRow getNewRow() {
  482. if (spareRows.size() > 0) {
  483. TableRow recycledRow = (TableRow) spareRows.removeFirst();
  484. recycledRow.setVisible(true);
  485. return recycledRow;
  486. }
  487. TableRow newRow = new TableRow(this, createInternalControl(controlHolder, rowConstructor));
  488. fireRowConstructionEvent(newRow.getRowControl());
  489. if (newRow.getRowControl() instanceof Composite) {
  490. Composite rowComp = (Composite) newRow.getRowControl();
  491. if (rowComp.getLayout() == null) {
  492. rowComp.setBackground(getBackground());
  493. rowComp.addPaintListener(rowPaintListener);
  494. }
  495. }
  496. return newRow;
  497. }
  498. // Property getters/setters --------------------------------------------------------------
  499. /*
  500.  * These are internal API.
  501.  * <p>
  502.  * Plese refer to the JavaDoc on CompositeTable for detailed description of these property methods.
  503.  */
  504. /** (non-API)
  505.  * Method getHeaderControl.  Return the prototype control being used as a header.
  506.  * 
  507.  * @return The header control
  508.  */
  509. public Control getHeaderControl() {
  510. return headerControl;
  511. }
  512. /**
  513.  * Method setMaxRowsVisible.  Sets the maximum number of rows that will be permitted 
  514.  * in the table at once.  For example, setting this property to 1 will have the effect of 
  515.  * creating a single editing area with a scroll bar on the right allowing the user to scroll
  516.  * through all rows using either the mouse or the PgUp/PgDn keys.  The default value is
  517.  * Integer.MAX_VALUE.
  518.  * 
  519.  * @param maxRowsVisible the maximum number of rows that are permitted to be visible at one time, regardless
  520.  * of the control's size.
  521.  */
  522. public void setMaxRowsVisible(int maxRowsVisible) {
  523. this.maxRowsVisible = maxRowsVisible;
  524. updateVisibleRows();
  525. }
  526. /**
  527.  * Method getNumRowsVisible.  Returns the actual number of rows that are currently visible.
  528.  * Normally CompositeTable displays as many rows as will fit vertically given the control's
  529.  * size.  This value can be clamped to a maximum using the MaxRowsVisible property.
  530.  * 
  531.  * @return the actual number of rows that are currently visible.
  532.  */
  533. public int getNumRowsVisible() {
  534. return numRowsVisible;
  535. }
  536. /**
  537.  * Method setNumRowsInCollection.  Sets the number of rows in the data structure that is
  538.  * being edited.
  539.  * 
  540.  * @param numRowsInCollection the number of rows represented by the underlying data structure.
  541.  */
  542. public void setNumRowsInCollection(int numRowsInCollection) {
  543. this.topRow = 0;
  544. if (currentRow > 0) {
  545. currentRow = 0;
  546. }
  547. this.numRowsInCollection = numRowsInCollection;
  548. updateVisibleRows();
  549. refreshAllRows();
  550. }
  551. /**
  552.  * Method setTopRow. Set the number of the line that is being displayed in the top row
  553.  * of the CompositeTable editor (0-based).  If the new top row is not equal to the current
  554.  * top row, the table will automatically be scrolled to the new position.  This number must
  555.  * be greater than 0 and less than NumRowsInCollection.
  556.  * 
  557.  * @param topRow the line number of the new top row.
  558.  */
  559. public void setTopRow(int topRow) {
  560. fireRowDepartEvent();
  561. this.topRow = topRow;
  562. updateVisibleRows();
  563. fireRowArriveEvent();
  564. }
  565. /**
  566.  * Method getTopRow.  Return the number of the line that is being displayed in the top row
  567.  * of the CompositeTable editor (0-based).
  568.  * 
  569.  * @return the number of the top line.
  570.  */
  571. public int getTopRow() {
  572. return topRow;
  573. }
  574. /**
  575.  * Method getSelection.  Returns the currently-selected (column, row) pair where the row 
  576.  * specifies the offset from the top of the table window.  In order to get the current 
  577.  * row in the underlying data structure, use getSelection().y + getCurrentRow().
  578.  * 
  579.  * @return  the currently-selected (column, row) pair where the row specifies the offset 
  580.  * from the top of the table window.
  581.  */
  582. public Point getSelection() {
  583. return new Point(currentColumn, currentRow);
  584. }
  585. /**
  586.  * Method setSelection.  Sets the currently-selected (column, row) pair where the row 
  587.  * specifies the offset from the top of the table window.  In order to get the current 
  588.  * row in the underlying data structure, use getSelection().y + getCurrentRow().
  589.  * 
  590.  * @param column the column to select
  591.  * @param row the row to select
  592.  */
  593. public void setSelection(int column, int row) {
  594. if (row == currentRow)
  595. internalSetSelection(column, row, false);
  596. else {
  597. if (fireRequestRowChangeEvent())
  598. internalSetSelection(column, row, true);
  599. }
  600. }
  601. /**
  602.  * Method setWeights.  Indicates that the column weights were just set and we should re-layout
  603.  * the control holder object.
  604.  */
  605. public void setWeights() {
  606. layoutControlHolder();
  607. }
  608. // Refresh Event API --------------------------------------------------------------------------
  609. /**
  610.  * Adds the specified listener to the set of listeners that will be notified when a row
  611.  * refresh event occurs.
  612.  * 
  613.  * @param listener the listener to add.
  614.  */
  615. public void addRefreshContentProvider(IRowContentProvider listener) {
  616. parent.contentProviders.add(listener);
  617. }
  618. /**
  619.  * Remove the specified listener from the set of listeners that will be notified when a
  620.  * row refresh event occurs.
  621.  * 
  622.  * @param listener the listener to remove.
  623.  */
  624. public void removeRefreshContentProvider(IRowContentProvider listener) {
  625. parent.contentProviders.remove(listener);
  626. }
  627. private void fireRefreshEvent(int positionInCollection, Control rowControl) {
  628. if (numRowsInCollection < 1) {
  629. return;
  630. }
  631. for (Iterator refreshListenersIter = parent.contentProviders.iterator(); refreshListenersIter.hasNext();) {
  632. IRowContentProvider listener = (IRowContentProvider) refreshListenersIter.next();
  633. listener.refresh(parent, positionInCollection, rowControl);
  634. }
  635. }
  636. // Empty table placeholder --------------------------------------------------------------------
  637. private void createEmptyTablePlaceholer() {
  638. emptyTablePlaceholder = new EmptyTablePlaceholder(controlHolder, SWT.NULL);
  639. if (rowControl != null)
  640. emptyTablePlaceholder.setBackground(rowControl.getBackground());
  641. emptyTablePlaceholder.setMessage(parent.getInsertHint());
  642. }
  643. private void disposeEmptyTablePlaceholder() {
  644. if (emptyTablePlaceholder != null) {
  645. emptyTablePlaceholder.dispose();
  646. emptyTablePlaceholder = null;
  647. }
  648. }
  649. // Event Handling -----------------------------------------------------------------------------
  650. private boolean needToRequestRC=true;
  651. /**
  652.  * Handle a keyPressed event on any row control.
  653.  * 
  654.  * @param sender The row that is sending the event
  655.  * @param e the actual KeyEvent
  656.  */
  657. public void keyPressed(TableRow sender, KeyEvent e) {
  658. if ((e.stateMask & SWT.CONTROL) != 0) {
  659. switch (e.keyCode) {
  660. case SWT.HOME:
  661. if (topRow <= 0) {
  662. return;
  663. }
  664. if (!fireRequestRowChangeEvent()) {
  665. return;
  666. }
  667. needToRequestRC = false;
  668. deselect(e.widget);
  669. // If the focus is already in the top visible row, we will need to explicitly 
  670. // fire an arrive event.
  671. boolean needToArrive = true;
  672. if (currentRow > 0) {
  673. needToArrive = false;
  674. }
  675. setTopRow(0);
  676. if (needToArrive) {
  677. internalSetSelection(currentColumn, 0, true);
  678. } else {
  679. internalSetSelection(currentColumn, 0, false);
  680. }
  681. return;
  682. case SWT.END:
  683. if (topRow + numRowsVisible < numRowsInCollection) {
  684. if (!fireRequestRowChangeEvent()) {
  685. return;
  686. }
  687. needToRequestRC = false;
  688. deselect(e.widget);
  689. // If the focus is already in the last visible row, we will need to explicitly 
  690. // fire an arrive event.
  691. needToArrive = true;
  692. if (currentRow < numRowsVisible-1) {
  693. needToArrive = false;
  694. }
  695. setTopRow(numRowsInCollection - numRowsVisible);
  696. if (needToArrive) {
  697. internalSetSelection(currentColumn, numRowsVisible-1, true);
  698. } else {
  699. internalSetSelection(currentColumn, numRowsVisible-1, false);
  700. }
  701. }
  702. return;
  703. case SWT.DEL:
  704. if (fireDeleteEvent()) {
  705. // We know the object is gone if we made it here, so now refresh the display...
  706. --numRowsInCollection;
  707. // If we deleted the last row in the list
  708. if (currentRow >= numRowsVisible-1) {
  709. // If that wasn't the last row in the collection, move the focus
  710. if (numRowsInCollection > 0) {
  711. // If we're only displaying one row, scroll first
  712. if (currentRow < 1) {
  713. needToRequestRC = false;
  714. deleteRowAt(currentRow);
  715. setTopRow(topRow-1);
  716. internalSetSelection(currentColumn, currentRow, true);
  717. } else {
  718. needToRequestRC = false;
  719. internalSetSelection(currentColumn, currentRow-1, false);
  720. Display.getCurrent().asyncExec(new Runnable() {
  721. public void run() {
  722. deleteRowAt(currentRow+1);
  723. updateVisibleRows();
  724. }
  725. });
  726. }
  727. } else {
  728. // Otherwise, show the placeholder object and give it focus
  729. deleteRowAt(currentRow);
  730. --numRowsVisible;
  731. createEmptyTablePlaceholer();
  732. emptyTablePlaceholder.setFocus();
  733. }
  734. } else {
  735. // else, keep the focus where it was
  736. deleteRowAt(currentRow);
  737. updateVisibleRows();
  738. internalSetSelection(currentColumn, currentRow, true);
  739. }
  740. }
  741. return;
  742. default:
  743. return;
  744. }
  745. }
  746. switch (e.keyCode) {
  747. case SWT.INSERT:
  748. // If no insertHandler has been registered, bail out 
  749.   if (parent.insertHandlers.size() < 1) {
  750. return;
  751. }
  752. // Make sure we can leave the current row
  753. if (!fireRequestRowChangeEvent()) {
  754. return;
  755. }
  756. needToRequestRC = false;
  757. // Insert the new object
  758. int newRowPosition = fireInsertEvent();
  759. if (newRowPosition < 0) {
  760. // This should never happen, but...
  761. return;
  762. }
  763. disposeEmptyTablePlaceholder();
  764. // If the current widget has a selection, deselect it
  765. deselect(e.widget);
  766. // If the new row is in the visible space, refresh it
  767. if (topRow <= newRowPosition && 
  768. numRowsVisible > newRowPosition - topRow) {
  769. insertRowAt(newRowPosition - topRow);
  770. ++numRowsInCollection;
  771. updateVisibleRows();
  772. int newRowNumber = newRowPosition - topRow;
  773. if (newRowNumber != currentRow) {
  774. internalSetSelection(currentColumn, newRowNumber, false);
  775. } else {
  776. internalSetSelection(currentColumn, newRowNumber, true);
  777. }
  778. return;
  779. }
  780. // else...
  781. ++numRowsInCollection;
  782. // If the new row is above us, scroll up to it
  783. if (newRowPosition < topRow + currentRow) {
  784. setTopRow(newRowPosition);
  785. Display.getDefault().asyncExec(new Runnable() {
  786. public void run() {
  787. updateVisibleRows();
  788. if (currentRow != 0) {
  789. internalSetSelection(currentColumn, 0, false);
  790. } else {
  791. internalSetSelection(currentColumn, 0, true);
  792. }
  793. }
  794. });
  795. } else {
  796. // If we're appending
  797. if (numRowsInDisplay > numRowsVisible) {
  798. updateVisibleRows();
  799. int newRowNumber = newRowPosition - topRow;
  800. if (newRowNumber != currentRow) {
  801. internalSetSelection(currentColumn, newRowNumber, false);
  802. } else {
  803. internalSetSelection(currentColumn, newRowNumber, true);
  804. }
  805. } else {
  806. // It's somewhere in the middle below us; scroll down to it
  807. setTopRow(newRowPosition-numRowsVisible+1);
  808. int newRowNumber = numRowsVisible-1;
  809. if (newRowNumber != currentRow) {
  810. internalSetSelection(currentColumn, newRowNumber, false);
  811. } else {
  812. internalSetSelection(currentColumn, newRowNumber, true);
  813. }
  814. }
  815. }
  816. return;
  817. case SWT.ARROW_UP:
  818. if (maxRowsVisible <= 1)
  819. return;
  820. if (currentRow > 0) {
  821. if (!fireRequestRowChangeEvent()) {
  822. return;
  823. }
  824. needToRequestRC = false;
  825. deselect(e.widget);
  826. internalSetSelection(currentColumn, currentRow-1, false);
  827. return;
  828. }
  829. if (topRow > 0) {
  830. if (!fireRequestRowChangeEvent()) {
  831. return;
  832. }
  833. needToRequestRC = false;
  834. deselect(e.widget);
  835. setTopRow(topRow - 1);
  836. internalSetSelection(currentColumn, currentRow, true);
  837. return;
  838. }
  839. return;
  840. case SWT.ARROW_DOWN:
  841. if (maxRowsVisible <= 1)
  842. return;
  843. if (currentRow < numRowsVisible-1) {
  844. if (!fireRequestRowChangeEvent()) {
  845. return;
  846. }
  847. needToRequestRC = false;
  848. deselect(e.widget);
  849. internalSetSelection(currentColumn, currentRow+1, false);
  850. return;
  851. }
  852. if (topRow + numRowsVisible < numRowsInCollection) {
  853. if (!fireRequestRowChangeEvent()) {
  854. return;
  855. }
  856. needToRequestRC = false;
  857. deselect(e.widget);
  858. setTopRow(topRow + 1);
  859. internalSetSelection(currentColumn, currentRow, true);
  860. return;
  861. }
  862. return;
  863. case SWT.PAGE_UP:
  864. if (topRow > 0) {
  865. if (!fireRequestRowChangeEvent()) {
  866. return;
  867. }
  868. needToRequestRC = false;
  869. int newTopRow = topRow - numRowsInDisplay;
  870. if (newTopRow < 0) {
  871. newTopRow = 0;
  872. }
  873. setTopRow(newTopRow);
  874. internalSetSelection(currentColumn, currentRow, true);
  875. }
  876. return;
  877. case SWT.PAGE_DOWN:
  878. if (topRow + numRowsVisible < numRowsInCollection) {
  879. if (!fireRequestRowChangeEvent()) {
  880. return;
  881. }
  882. needToRequestRC = false;
  883. int newTopRow = topRow + numRowsVisible;
  884. if (newTopRow >= numRowsInCollection - 1) {
  885. newTopRow = numRowsInCollection - 1;
  886. }
  887. setTopRow(newTopRow);
  888. if (currentRow < numRowsVisible) {
  889. internalSetSelection(currentColumn, currentRow, true);
  890. } else {
  891. internalSetSelection(currentColumn, numRowsVisible-1, true);
  892. }
  893. }
  894. return;
  895. }
  896. }
  897. /**
  898.  * Handle the keyTraversed event on any child control in the table.
  899.  * 
  900.  * @param sender The row sending the event.
  901.  * @param e The SWT TraverseEvent
  902.  */
  903. public void keyTraversed(TableRow sender, TraverseEvent e) {
  904. if (e.detail == SWT.TRAVERSE_TAB_NEXT) {
  905. if (currentColumn >= sender.getNumColumns() - 1) {
  906. e.detail = SWT.TRAVERSE_NONE;
  907. handleNextRowNavigation();
  908. }
  909. } else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
  910. if (currentColumn == 0) {
  911. e.detail = SWT.TRAVERSE_NONE;
  912. handlePreviousRowNavigation(sender);
  913. }
  914. } else if (e.detail == SWT.TRAVERSE_RETURN) {
  915. e.detail = SWT.TRAVERSE_NONE;
  916. if (currentColumn >= sender.getNumColumns() - 1) {
  917. handleNextRowNavigation();
  918. } else {
  919. deferredSetFocus(getControl(currentColumn+1, currentRow), false);
  920. }
  921. }
  922. }
  923. /**
  924.  * The SelectionListener for the table's slider control.
  925.  */
  926. private SelectionListener sliderSelectionListener = new SelectionListener() {
  927. public void widgetSelected(SelectionEvent e) {
  928. if (slider.getSelection() == topRow) {
  929. return;
  930. }
  931. if (!fireRequestRowChangeEvent()) {
  932. slider.setSelection(topRow);
  933. return;
  934. }
  935. deselect(getControl(currentColumn, currentRow));
  936. setTopRow(slider.getSelection());
  937. deferredSetFocus(getControl(currentColumn, currentRow), true);
  938. }
  939. public void widgetDefaultSelected(SelectionEvent e) {
  940. widgetSelected(e);
  941. }
  942. };
  943. /**
  944.  * Scroll wheel event handling.
  945.  */
  946. public void handleEvent(Event event) {
  947. if (event.count > 0) {
  948. if (topRow > 0) {
  949. if (!fireRequestRowChangeEvent()) {
  950. return;
  951. }
  952. deselect(getControl(currentColumn, currentRow));
  953. setTopRow(topRow - 1);
  954. deferredSetFocus(getControl(currentColumn, currentRow), true);
  955. }
  956. } else {
  957. if (topRow < numRowsInCollection - numRowsVisible) {
  958. if (!fireRequestRowChangeEvent()) {
  959. return;
  960. }
  961. deselect(getControl(currentColumn, currentRow));
  962. setTopRow(topRow + 1);
  963. deferredSetFocus(getControl(currentColumn, currentRow), true);
  964. }
  965. }
  966. }
  967. /**
  968.  * Handle focusLost events on any child control.  This is not currently used.
  969.  * 
  970.  * @param sender The row containing the sending control.
  971.  * @param e The SWT FocusEvent.
  972.  */
  973. public void focusLost(TableRow sender, FocusEvent e) {
  974. }
  975. /**
  976.  * Handle focusGained events on any child control.
  977.  * 
  978.  * @param sender The row containing the sending control.
  979.  * @param e The SWT FocusEvent.
  980.  */
  981. public void focusGained(TableRow sender, FocusEvent e) {
  982. boolean rowChanged = false;
  983. if (getRowNumber(sender) != currentRow) {
  984. if (needToRequestRC) {
  985. if (!fireRequestRowChangeEvent()) {
  986. // Go back if we're not allowed to be here
  987. deferredSetFocus(getControl(currentColumn, currentRow), false);
  988. }
  989. } else {
  990. needToRequestRC = true;
  991. }
  992. rowChanged = true;
  993. }
  994. currentRow = getRowNumber(sender);
  995. currentColumn = sender.getColumnNumber((Control)e.widget);
  996. if (rowChanged)
  997. fireRowArriveEvent();
  998. }
  999. private PaintListener headerPaintListener = new PaintListener() {
  1000. public void paintControl(PaintEvent e) {
  1001. if (parent.gridLinesOn) {
  1002. drawGridLines(e, true);
  1003. }
  1004. }
  1005. };
  1006. private PaintListener rowPaintListener = new PaintListener() {
  1007. public void paintControl(PaintEvent e) {
  1008. if (parent.gridLinesOn) {
  1009. drawGridLines(e, false);
  1010. }
  1011. }
  1012. };
  1013. private void drawGridLines(PaintEvent e, boolean isHeader) {
  1014. Color oldColor = e.gc.getForeground();
  1015. try {
  1016. // Get the colors we need
  1017. Display display = Display.getCurrent();
  1018. Color lineColor = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW);
  1019. Color secondaryColor = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
  1020. Color hilightColor = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
  1021. if (!isHeader) {
  1022. lineColor = display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
  1023. }
  1024. // Get the control
  1025. Control toPaint = (Control) e.widget;
  1026. Point controlSize = toPaint.getSize();
  1027. // Draw the bottom line(s)
  1028. e.gc.setForeground(lineColor);
  1029. e.gc.drawLine(0, controlSize.y-1, controlSize.x, controlSize.y-1);
  1030. if (isHeader) {
  1031. e.gc.setForeground(secondaryColor);
  1032. e.gc.drawLine(0, controlSize.y-2, controlSize.x, controlSize.y-2);
  1033. e.gc.setForeground(hilightColor);
  1034. e.gc.drawLine(0, 1, controlSize.x, 1);
  1035. }
  1036. // Now draw lines around the child controls, if there are any
  1037. if (toPaint instanceof Composite) {
  1038. Composite row = (Composite) toPaint;
  1039. Control[] children = row.getChildren();
  1040. for (int i = 0; i < children.length; i++) {
  1041. Rectangle childBounds = children[i].getBounds();
  1042. // Paint the beginning lines
  1043. if (isHeader) {
  1044. e.gc.setForeground(hilightColor);
  1045. e.gc.drawLine(childBounds.x-2, 1, childBounds.x-2, controlSize.y-2);
  1046. }
  1047. // Paint the ending lines
  1048. e.gc.setForeground(lineColor);
  1049. int lineLeft = childBounds.x + childBounds.width+1;
  1050. e.gc.drawLine(lineLeft, 0, lineLeft, controlSize.y);
  1051. if (isHeader) {
  1052. e.gc.setForeground(secondaryColor);
  1053. e.gc.drawLine(lineLeft-1, 0, lineLeft-1, controlSize.y-1);
  1054. }
  1055. }
  1056. }
  1057. } finally {
  1058. e.gc.setForeground(oldColor);
  1059. }
  1060. }
  1061. // Event Firing -------------------------------------------------------------------------------
  1062. /**
  1063.  * Fire the row construction event
  1064.  * 
  1065.  * @param newControl The new row's SWT control
  1066.  */
  1067. private void fireRowConstructionEvent(Control newControl) {
  1068. for (Iterator rowConstructionListenersIter = parent.rowConstructionListeners.iterator(); rowConstructionListenersIter.hasNext();) {
  1069. IRowConstructionListener listener = (IRowConstructionListener) rowConstructionListenersIter.next();
  1070. listener.rowConstructed(newControl);
  1071. }
  1072. }
  1073. /**
  1074.  * Indicate to listeners that the focus is arriving on the specified row
  1075.  */
  1076. private void fireRowArriveEvent() {
  1077. if (rows.size() < 1) {
  1078. return;
  1079. }
  1080. for (Iterator rowChangeListenersIter = parent.rowFocusListeners.iterator(); rowChangeListenersIter.hasNext();) {
  1081. IRowFocusListener listener = (IRowFocusListener) rowChangeListenersIter.next();
  1082. listener.arrive(parent, topRow+currentRow, currentRow().getRowControl());
  1083. }
  1084. }
  1085. /**
  1086.  * Request permission from all listeners to leave the current row.
  1087.  * 
  1088.  * @return true if all listeners permit the row change; false otherwise.
  1089.  */
  1090. private boolean fireRequestRowChangeEvent() {
  1091. if (rows.size() < 1) {
  1092. return true;
  1093. }
  1094. if (currentRow > rows.size()-1) {
  1095. // (if the other row is already gone)
  1096. return true;
  1097. }
  1098. for (Iterator rowChangeListenersIter = parent.rowFocusListeners.iterator(); rowChangeListenersIter.hasNext();) {
  1099. IRowFocusListener listener = (IRowFocusListener) rowChangeListenersIter.next();
  1100. if (!listener.requestRowChange(parent, topRow+currentRow, currentRow().getRowControl())) {
  1101. return false;
  1102. }
  1103. }
  1104. fireRowDepartEvent();
  1105. return true;
  1106. }
  1107. /**
  1108.  * Indicate to listeners that the focus is about to leave the current row.
  1109.  */
  1110. private void fireRowDepartEvent() {
  1111. if (rows.size() < 1) {
  1112. return;
  1113. }
  1114. for (Iterator rowChangeListenersIter = parent.rowFocusListeners.iterator(); rowChangeListenersIter.hasNext();) {
  1115. IRowFocusListener listener = (IRowFocusListener) rowChangeListenersIter.next();
  1116. listener.depart(parent, topRow+currentRow, currentRow().getRowControl());
  1117. }
  1118. }
  1119. /**
  1120.  * Request deletion of the current row from the underlying data structure.
  1121.  * 
  1122.  * @return true if the deletion was successful; false otherwise.
  1123.  */
  1124. private boolean fireDeleteEvent() {
  1125. if (parent.deleteHandlers.size() < 1) {
  1126. return false;
  1127. }
  1128. int absoluteRow = topRow + currentRow;
  1129. for (Iterator deleteHandlersIter = parent.deleteHandlers.iterator(); deleteHandlersIter.hasNext();) {
  1130. IDeleteHandler handler = (IDeleteHandler) deleteHandlersIter.next();
  1131. if (!handler.canDelete(absoluteRow)) {
  1132. return false;
  1133. }
  1134. }
  1135. for (Iterator deleteHandlersIter = parent.deleteHandlers.iterator(); deleteHandlersIter.hasNext();) {
  1136. IDeleteHandler handler = (IDeleteHandler) deleteHandlersIter.next();
  1137. handler.deleteRow(absoluteRow);
  1138. }
  1139. return true;
  1140. }
  1141. /**
  1142.  * Request that the model insert a new row into itself.
  1143.  * 
  1144.  * @return The 0-based offset of the new row from the start of the collection or -1 if a 
  1145.  * new row could not be inserted.
  1146.  */
  1147. private int fireInsertEvent() {
  1148. if (parent.insertHandlers.size() < 1) {
  1149. return -1;
  1150. }
  1151. for (Iterator insertHandlersIter = parent.insertHandlers.iterator(); insertHandlersIter.hasNext();) {
  1152. IInsertHandler handler = (IInsertHandler) insertHandlersIter.next();
  1153. int resultRow = handler.insert(topRow+currentRow);
  1154. if (resultRow >= 0) {
  1155. return resultRow;
  1156. }
  1157. }
  1158. return -1;
  1159. }
  1160. // Event Handling, utility methods ------------------------------------------------------------
  1161. /**
  1162.  * Set the widget's selection to an empty selection.
  1163.  * 
  1164.  * @param widget The widget to deselect
  1165.  */
  1166. private void deselect(Widget widget) {
  1167. if (DuckType.instanceOf(ISelectableRegionControl.class, widget)) {
  1168. ISelectableRegionControl control = (ISelectableRegionControl) DuckType.implement(ISelectableRegionControl.class, widget);
  1169. control.setSelection(0, 0);
  1170. }
  1171. }
  1172. /**
  1173.  * Try to go to the next row in the collection.
  1174.  */
  1175. private void handleNextRowNavigation() {
  1176. if (currentRow < numRowsVisible-1) {
  1177. if (!fireRequestRowChangeEvent()) {
  1178. return;
  1179. }
  1180. needToRequestRC = false;
  1181. deselect(getControl(currentColumn, currentRow));
  1182. deferredSetFocus(getControl(0, currentRow+1), false);
  1183. } else {
  1184. if (topRow + numRowsVisible >= numRowsInCollection) {
  1185. // We're at the end; don't go anywhere
  1186. return;
  1187. }
  1188. // We have to scroll forwards
  1189. if (!fireRequestRowChangeEvent()) {
  1190. return;
  1191. }
  1192. needToRequestRC = false;
  1193. deselect(getControl(currentColumn, currentRow));
  1194. setTopRow(topRow+1);
  1195. deferredSetFocus(getControl(0, currentRow), true);
  1196. }
  1197. }
  1198. /**
  1199.  * Try to go to the previous row in the collection.
  1200.  * 
  1201.  * @param row The current table row.
  1202.  */
  1203. private void handlePreviousRowNavigation(TableRow row) {
  1204. if (currentRow == 0) {
  1205. if (topRow == 0) {
  1206. // We're at the beginning of the table; don't go anywhere
  1207. return;
  1208. }
  1209. // We have to scroll backwards
  1210. if (!fireRequestRowChangeEvent()) {
  1211. return;
  1212. }
  1213. needToRequestRC = false;
  1214. deselect(getControl(currentColumn, currentRow));
  1215. setTopRow(topRow-1);
  1216. deferredSetFocus(getControl(row.getNumColumns()-1, 0), true);
  1217. } else {
  1218. if (!fireRequestRowChangeEvent()) {
  1219. return;
  1220. }
  1221. needToRequestRC = false;
  1222. deselect(getControl(currentColumn, currentRow));
  1223. deferredSetFocus(getControl(row.getNumColumns()-1, currentRow-1), false);
  1224. }
  1225. }
  1226. /**
  1227.  * Gets the current TableRow.
  1228.  * 
  1229.  * @return the current TableRow
  1230.  */
  1231. private TableRow currentRow() {
  1232. if (currentRow > rows.size()-1) {
  1233. return null;
  1234. }
  1235. return (TableRow) rows.get(currentRow);
  1236. }
  1237. /**
  1238.  * Returns the SWT control corresponding to the current row.
  1239.  * 
  1240.  * @return the current row control.
  1241.  */
  1242. public Control getCurrentRowControl() {
  1243. TableRow currentRow = currentRow();
  1244. if (currentRow == null) {
  1245. return null;
  1246. }
  1247. return currentRow().getRowControl();
  1248. }
  1249. /**
  1250.  * Returns the TableRow by the specified 0-based offset from the top visible row.
  1251.  * 
  1252.  * @param rowNumber 0-based offset of the requested fow starting from the top visible row.
  1253.  * @return The corresponding TableRow or null if there is none.
  1254.  */
  1255. private TableRow getRowByNumber(int rowNumber) {
  1256. if (rowNumber > rows.size()-1) {
  1257. return null;
  1258. }
  1259. return (TableRow) rows.get(rowNumber);
  1260. }
  1261. /**
  1262.  * Return the SWT control at (column, row), where row is a 0-based number starting
  1263.  * from the top visible row.
  1264.  * 
  1265.  * @param column the 0-based column.
  1266.  * @param row the 0-based row starting from the top visible row.
  1267.  * @return the SWT control at (column, row)
  1268.  */
  1269. private Control getControl(int column, int row) {
  1270. TableRow rowObject = getRowByNumber(row);
  1271. if (rowObject == null) {
  1272. throw new IndexOutOfBoundsException("Request for a nonexistent row"); //$NON-NLS-1$
  1273. }
  1274. Control result = rowObject.getColumnControl(column);
  1275. return result;
  1276. }
  1277. /**
  1278.  * Return the 0-based row number corresponding to a particular TableRow object.
  1279.  * 
  1280.  * @param row The TableRow to translate to row coordinates.
  1281.  * @return the 0-based row number or -1 if the specified TableRow is not visible.
  1282.  */
  1283. private int getRowNumber(TableRow row) {
  1284. int rowNumber = 0;
  1285. for (Iterator rowIter = rows.iterator(); rowIter.hasNext();) {
  1286. TableRow candidate = (TableRow) rowIter.next();
  1287. if (candidate == row) {
  1288. return rowNumber;
  1289. }
  1290. ++rowNumber;
  1291. }
  1292. return -1;
  1293. }
  1294. /**
  1295.  * Set the focus to the specified (column, row).  If rowChange is true, fire a
  1296.  * row change event, otherwise be silent.
  1297.  * 
  1298.  * @param column The 0-based column to focus
  1299.  * @param row The 0-based row to focus
  1300.  * @param rowChange true if a row change event should be fired; false otherwise.
  1301.  */
  1302. private void internalSetSelection(int column, int row, boolean rowChange) {
  1303. Control toFocus = getControl(column, row);
  1304. deferredSetFocus(toFocus, rowChange);
  1305. }
  1306. /**
  1307.  * Set the focus to the specified control after allowing all pending events to complete
  1308.  * first.  If rowChange is true, fire a row arrive event after the focus has been set.
  1309.  * 
  1310.  * @param toFocus The SWT Control to focus
  1311.  * @param rowChange true if the rowArrive event should be fired; false otherwise.
  1312.  */
  1313. private void deferredSetFocus(final Control toFocus, final boolean rowChange) {
  1314. Display.getCurrent().asyncExec(new Runnable() {
  1315. public void run() {
  1316. toFocus.setFocus();
  1317. if (rowChange) {
  1318. fireRowArriveEvent();
  1319. }
  1320. }
  1321. });
  1322. }
  1323. }