1 /*******************************************************************************
2  * Copyright (c) 2000, 2016 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.swt.examples.accessibility;
15 
16 import org.eclipse.swt.SWT;
17 import org.eclipse.swt.SWTException;
18 import org.eclipse.swt.accessibility.ACC;
19 import org.eclipse.swt.accessibility.Accessible;
20 import org.eclipse.swt.accessibility.AccessibleAdapter;
21 import org.eclipse.swt.accessibility.AccessibleControlAdapter;
22 import org.eclipse.swt.accessibility.AccessibleControlEvent;
23 import org.eclipse.swt.accessibility.AccessibleEvent;
24 import org.eclipse.swt.accessibility.AccessibleTableAdapter;
25 import org.eclipse.swt.accessibility.AccessibleTableEvent;
26 import org.eclipse.swt.events.SelectionEvent;
27 import org.eclipse.swt.events.SelectionListener;
28 import org.eclipse.swt.graphics.Color;
29 import org.eclipse.swt.graphics.Font;
30 import org.eclipse.swt.graphics.GC;
31 import org.eclipse.swt.graphics.Image;
32 import org.eclipse.swt.graphics.ImageData;
33 import org.eclipse.swt.graphics.PaletteData;
34 import org.eclipse.swt.graphics.Point;
35 import org.eclipse.swt.graphics.RGB;
36 import org.eclipse.swt.graphics.Rectangle;
37 import org.eclipse.swt.widgets.Canvas;
38 import org.eclipse.swt.widgets.Composite;
39 import org.eclipse.swt.widgets.Control;
40 import org.eclipse.swt.widgets.Display;
41 import org.eclipse.swt.widgets.Event;
42 import org.eclipse.swt.widgets.Label;
43 import org.eclipse.swt.widgets.Listener;
44 import org.eclipse.swt.widgets.Menu;
45 import org.eclipse.swt.widgets.ScrollBar;
46 import org.eclipse.swt.widgets.Shell;
47 import org.eclipse.swt.widgets.Tracker;
48 import org.eclipse.swt.widgets.TypedListener;
49 import org.eclipse.swt.widgets.Widget;
50 
51 /**
52  * Instances of this class implement a selectable user interface
53  * object that displays a list of images and strings and issues
54  * notification when selected.
55  * <p>
56  * The item children that may be added to instances of this class
57  * must be of type <code>TableItem</code>.
58  * </p><p>
59  * Style <code>VIRTUAL</code> is used to create a <code>Table</code> whose
60  * <code>TableItem</code>s are to be populated by the client on an on-demand basis
61  * instead of up-front.  This can provide significant performance improvements for
62  * tables that are very large or for which <code>TableItem</code> population is
63  * expensive (for example, retrieving values from an external source).
64  * </p><p>
65  * Here is an example of using a <code>Table</code> with style <code>VIRTUAL</code>:
66  * <code><pre>
67  *  final Table table = new Table (parent, SWT.VIRTUAL | SWT.BORDER);
68  *  table.setItemCount (1000000);
69  *  table.addListener (SWT.SetData, new Listener () {
70  *      public void handleEvent (Event event) {
71  *          TableItem item = (TableItem) event.item;
72  *          int index = table.indexOf (item);
73  *          item.setText ("Item " + index);
74  *          System.out.println (item.getText ());
75  *      }
76  *  });
77  * </pre></code>
78  * </p><p>
79  * Note that although this class is a subclass of <code>Composite</code>,
80  * it does not normally make sense to add <code>Control</code> children to
81  * it, or set a layout on it, unless implementing something like a cell
82  * editor.
83  * </p><p>
84  * <dl>
85  * <dt><b>Styles:</b></dt>
86  * <dd>SINGLE, MULTI, CHECK, FULL_SELECTION, HIDE_SELECTION, VIRTUAL, NO_SCROLL</dd>
87  * <dt><b>Events:</b></dt>
88  * <dd>Selection, DefaultSelection, SetData, MeasureItem, EraseItem, PaintItem</dd>
89  * </dl>
90  * </p><p>
91  * Note: Only one of the styles SINGLE, and MULTI may be specified.
92  * </p><p>
93  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
94  * </p>
95  *
96  * @see <a href="http://www.eclipse.org/swt/snippets/#table">Table, TableItem, TableColumn snippets</a>
97  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
98  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
99  * @noextend This class is not intended to be subclassed by clients.
100  */
101 public class CTable extends Composite {
102 	Canvas header;
103 	CTableColumn[] columns = new CTableColumn [0];
104 	CTableColumn[] orderedColumns;
105 	CTableItem[] items = new CTableItem [0];
106 	CTableItem[] selectedItems = new CTableItem [0];
107 	CTableItem focusItem, anchorItem, lastClickedItem;
108 	Event lastSelectionEvent;
109 	boolean linesVisible, ignoreKey, ignoreDispose, customHeightSet;
110 	int itemsCount = 0;
111 	int topIndex = 0, horizontalOffset = 0;
112 	int fontHeight = 0, imageHeight = 0, itemHeight = 0;
113 	int col0ImageWidth = 0;
114 	int headerImageHeight = 0;
115 	CTableColumn resizeColumn;
116 	int resizeColumnX = -1;
117 	int drawCount = 0;
118 	CTableColumn sortColumn;
119 	int sortDirection = SWT.NONE;
120 
121 	/* column header tooltip */
122 	Listener toolTipListener;
123 	Shell toolTipShell;
124 	Label toolTipLabel;
125 
126 	Rectangle arrowBounds, checkboxBounds, clientArea;
127 
128 	static final int MARGIN_IMAGE = 3;
129 	static final int MARGIN_CELL = 1;
130 	static final int SIZE_HORIZONTALSCROLL = 5;
131 	static final int TOLLERANCE_COLUMNRESIZE = 2;
132 	static final int WIDTH_HEADER_SHADOW = 2;
133 	static final int WIDTH_CELL_HIGHLIGHT = 1;
134 	static final int [] toolTipEvents = new int[] {SWT.MouseExit, SWT.MouseHover, SWT.MouseMove, SWT.MouseDown};
135 	static final String ELLIPSIS = "...";						//$NON-NLS-1$
136 	static final String ID_UNCHECKED = "UNCHECKED";			//$NON-NLS-1$
137 	static final String ID_GRAYUNCHECKED = "GRAYUNCHECKED";	//$NON-NLS-1$
138 	static final String ID_CHECKMARK = "CHECKMARK";			//$NON-NLS-1$
139 	static final String ID_ARROWUP = "ARROWUP";				//$NON-NLS-1$
140 	static final String ID_ARROWDOWN = "ARROWDOWN";			//$NON-NLS-1$
141 
142 	Display display;
143 
144 //TEMPORARY CODE
145 boolean hasFocus;
146 @Override
isFocusControl()147 public boolean isFocusControl() {
148 	return hasFocus;
149 }
150 
151 /**
152  * Constructs a new instance of this class given its parent
153  * and a style value describing its behavior and appearance.
154  * <p>
155  * The style value is either one of the style constants defined in
156  * class <code>SWT</code> which is applicable to instances of this
157  * class, or must be built by <em>bitwise OR</em>'ing together
158  * (that is, using the <code>int</code> "|" operator) two or more
159  * of those <code>SWT</code> style constants. The class description
160  * lists the style constants that are applicable to the class.
161  * Style bits are also inherited from superclasses.
162  * </p>
163  *
164  * @param parent a composite control which will be the parent of the new instance (cannot be null)
165  * @param style the style of control to construct
166  *
167  * @exception IllegalArgumentException <ul>
168  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
169  * </ul>
170  * @exception SWTException <ul>
171  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
172  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
173  * </ul>
174  *
175  * @see SWT#SINGLE
176  * @see SWT#MULTI
177  * @see SWT#CHECK
178  * @see SWT#FULL_SELECTION
179  * @see SWT#HIDE_SELECTION
180  * @see SWT#VIRTUAL
181  * @see SWT#NO_SCROLL
182  * @see Widget#checkSubclass
183  * @see Widget#getStyle
184  */
CTable(Composite parent, int style)185 public CTable (Composite parent, int style) {
186 	super (parent, checkStyle (style));
187 	this.display = parent.getDisplay ();
188 	setForeground (null);	/* set foreground and background to chosen default colors */
189 	setBackground (null);
190 	GC gc = new GC (this);
191 	fontHeight = gc.getFontMetrics ().getHeight ();
192 	gc.dispose ();
193 	itemHeight = fontHeight + (2 * getCellPadding ());
194 	initImages (display);
195 	checkboxBounds = getUncheckedImage ().getBounds ();
196 	arrowBounds = getArrowDownImage ().getBounds ();
197 	clientArea = getClientArea ();
198 
199 	Listener listener = event -> handleEvents (event);
200 	addListener (SWT.Paint, listener);
201 	addListener (SWT.MouseDown, listener);
202 	addListener (SWT.MouseUp, listener);
203 	addListener (SWT.MouseDoubleClick, listener);
204 	addListener (SWT.Dispose, listener);
205 	addListener (SWT.Resize, listener);
206 	addListener (SWT.KeyDown, listener);
207 	addListener (SWT.FocusOut, listener);
208 	addListener (SWT.FocusIn, listener);
209 	addListener (SWT.Traverse, listener);
210 
211 	initAccessibility ();
212 
213 	header = new Canvas (this, SWT.NO_REDRAW_RESIZE | SWT.NO_FOCUS);
214 	header.setVisible (false);
215 	header.setBounds (0, 0, 0, fontHeight + 2 * getHeaderPadding ());
216 	header.addListener (SWT.Paint, listener);
217 	header.addListener (SWT.MouseDown, listener);
218 	header.addListener (SWT.MouseUp, listener);
219 	header.addListener (SWT.MouseHover, listener);
220 	header.addListener (SWT.MouseDoubleClick, listener);
221 	header.addListener (SWT.MouseMove, listener);
222 	header.addListener (SWT.MouseExit, listener);
223 	header.addListener (SWT.MenuDetect, listener);
224 
225 	toolTipListener = event -> {
226 		switch (event.type) {
227 			case SWT.MouseHover:
228 			case SWT.MouseMove:
229 				if (headerUpdateToolTip (event.x)) break;
230 				// FALL THROUGH
231 			case SWT.MouseExit:
232 			case SWT.MouseDown:
233 				headerHideToolTip ();
234 				break;
235 		}
236 	};
237 
238 	ScrollBar hBar = getHorizontalBar ();
239 	if (hBar != null) {
240 		hBar.setValues (0, 0, 1, 1, 1, 1);
241 		hBar.setVisible (false);
242 		hBar.addListener (SWT.Selection, listener);
243 	}
244 	ScrollBar vBar = getVerticalBar ();
245 	if (vBar != null) {
246 		vBar.setValues (0, 0, 1, 1, 1, 1);
247 		vBar.setVisible (false);
248 		vBar.addListener (SWT.Selection, listener);
249 	}
250 }
251 /**
252  * Adds the listener to the collection of listeners who will
253  * be notified when the user changes the receiver's selection, by sending
254  * it one of the messages defined in the <code>SelectionListener</code>
255  * interface.
256  * <p>
257  * When <code>widgetSelected</code> is called, the item field of the event object is valid.
258  * If the receiver has the <code>SWT.CHECK</code> style and the check selection changes,
259  * the event object detail field contains the value <code>SWT.CHECK</code>.
260  * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked.
261  * The item field of the event object is valid for default selection, but the detail field is not used.
262  * </p>
263  *
264  * @param listener the listener which should be notified when the user changes the receiver's selection
265  *
266  * @exception IllegalArgumentException <ul>
267  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
268  * </ul>
269  * @exception SWTException <ul>
270  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
271  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
272  * </ul>
273  *
274  * @see SelectionListener
275  * @see #removeSelectionListener
276  * @see SelectionEvent
277  */
addSelectionListener(SelectionListener listener)278 public void addSelectionListener (SelectionListener listener) {
279 	checkWidget ();
280 	if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
281 	TypedListener typedListener = new TypedListener (listener);
282 	addListener (SWT.Selection, typedListener);
283 	addListener (SWT.DefaultSelection, typedListener);
284 }
checkData(CTableItem item, boolean redraw)285 boolean checkData (CTableItem item, boolean redraw) {
286 	if (item.cached) return true;
287 	if ((getStyle () & SWT.VIRTUAL) != 0) {
288 		item.cached = true;
289 		Event event = new Event ();
290 		event.item = item;
291 		event.index = indexOf (item);
292 		notifyListeners (SWT.SetData, event);
293 		if (isDisposed () || item.isDisposed ()) return false;
294 		if (redraw) redrawItem (item.index, false);
295 	}
296 	return true;
297 }
checkStyle(int style)298 static int checkStyle (int style) {
299 	/*
300 	* Feature in Windows.  Even when WS_HSCROLL or
301 	* WS_VSCROLL is not specified, Windows creates
302 	* trees and tables with scroll bars.  The fix
303 	* is to set H_SCROLL and V_SCROLL.
304 	*
305 	* NOTE: This code appears on all platforms so that
306 	* applications have consistent scroll bar behavior.
307 	*/
308 	if ((style & SWT.NO_SCROLL) == 0) {
309 		style |= SWT.H_SCROLL | SWT.V_SCROLL;
310 	}
311 	style |= SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED;
312 	//TEMPORARY CODE
313 	style |= SWT.FULL_SELECTION;
314 	return checkBits (style, SWT.SINGLE, SWT.MULTI, 0, 0, 0, 0);
315 }
checkBits(int style, int int0, int int1, int int2, int int3, int int4, int int5)316 static int checkBits (int style, int int0, int int1, int int2, int int3, int int4, int int5) {
317 	int mask = int0 | int1 | int2 | int3 | int4 | int5;
318 	if ((style & mask) == 0) style |= int0;
319 	if ((style & int0) != 0) style = (style & ~mask) | int0;
320 	if ((style & int1) != 0) style = (style & ~mask) | int1;
321 	if ((style & int2) != 0) style = (style & ~mask) | int2;
322 	if ((style & int3) != 0) style = (style & ~mask) | int3;
323 	if ((style & int4) != 0) style = (style & ~mask) | int4;
324 	if ((style & int5) != 0) style = (style & ~mask) | int5;
325 	return style;
326 }
327 /**
328  * Clears the item at the given zero-relative index in the receiver.
329  * The text, icon and other attributes of the item are set to the default
330  * value.  If the table was created with the <code>SWT.VIRTUAL</code> style,
331  * these attributes are requested again as needed.
332  *
333  * @param index the index of the item to clear
334  *
335  * @exception IllegalArgumentException <ul>
336  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
337  * </ul>
338  * @exception SWTException <ul>
339  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
340  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
341  * </ul>
342  *
343  * @see SWT#VIRTUAL
344  * @see SWT#SetData
345  *
346  * @since 3.0
347  */
clear(int index)348 public void clear (int index) {
349 	checkWidget ();
350 	if (!(0 <= index && index < itemsCount)) SWT.error (SWT.ERROR_INVALID_RANGE);
351 	Rectangle bounds = items [index].getBounds (false);
352 	int oldRightX = bounds.x + bounds.width;
353 	items [index].clear ();
354 	if (columns.length == 0) updateHorizontalBar (0, -oldRightX);
355 	redrawItem (index, false);
356 }
357 /**
358  * Removes the items from the receiver which are between the given
359  * zero-relative start and end indices (inclusive).  The text, icon
360  * and other attributes of the items are set to their default values.
361  * If the table was created with the <code>SWT.VIRTUAL</code> style,
362  * these attributes are requested again as needed.
363  *
364  * @param start the start index of the item to clear
365  * @param end the end index of the item to clear
366  *
367  * @exception IllegalArgumentException <ul>
368  *    <li>ERROR_INVALID_RANGE - if either the start or end are not between 0 and the number of elements in the list minus 1 (inclusive)</li>
369  * </ul>
370  * @exception SWTException <ul>
371  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
372  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
373  * </ul>
374  *
375  * @see SWT#VIRTUAL
376  * @see SWT#SetData
377  *
378  * @since 3.0
379  */
clear(int start, int end)380 public void clear (int start, int end) {
381 	checkWidget ();
382 	if (start > end) return;
383 	if (!(0 <= start && start <= end && end < itemsCount)) {
384 		SWT.error (SWT.ERROR_INVALID_RANGE);
385 	}
386 	for (int i = start; i <= end; i++) {
387 		items [i].clear ();
388 	}
389 	updateHorizontalBar ();
390 	redrawItems (start, end, false);
391 }
392 /**
393  * Clears the items at the given zero-relative indices in the receiver.
394  * The text, icon and other attributes of the items are set to their default
395  * values.  If the table was created with the <code>SWT.VIRTUAL</code> style,
396  * these attributes are requested again as needed.
397  *
398  * @param indices the array of indices of the items
399  *
400  * @exception IllegalArgumentException <ul>
401  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
402  *    <li>ERROR_NULL_ARGUMENT - if the indices array is null</li>
403  * </ul>
404  * @exception SWTException <ul>
405  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
406  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
407  * </ul>
408  *
409  * @see SWT#VIRTUAL
410  * @see SWT#SetData
411  *
412  * @since 3.0
413  */
clear(int [] indices)414 public void clear (int [] indices) {
415 	checkWidget ();
416 	if (indices == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
417 	if (indices.length == 0) return;
418 	for (int i = 0; i < indices.length; i++) {
419 		if (!(0 <= indices [i] && indices [i] < itemsCount)) {
420 			SWT.error (SWT.ERROR_INVALID_RANGE);
421 		}
422 	}
423 
424 	for (int index : indices) {
425 		items [index].clear ();
426 	}
427 	updateHorizontalBar ();
428 	for (int index : indices) {
429 		redrawItem (index, false);
430 	}
431 }
432 /**
433  * Clears all the items in the receiver. The text, icon and other
434  * attributes of the items are set to their default values. If the
435  * table was created with the <code>SWT.VIRTUAL</code> style, these
436  * attributes are requested again as needed.
437  *
438  * @exception SWTException <ul>
439  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
440  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
441  * </ul>
442  *
443  * @see SWT#VIRTUAL
444  * @see SWT#SetData
445  *
446  * @since 3.0
447  */
clearAll()448 public void clearAll () {
449 	checkWidget ();
450 	clear (0, itemsCount - 1);
451 }
452 /*
453  * Returns the ORDERED index of the column that the specified x falls within,
454  * or -1 if the x lies to the right of the last column.
455  */
computeColumnIntersect(int x, int startColumn)456 int computeColumnIntersect (int x, int startColumn) {
457 	CTableColumn[] orderedColumns = getOrderedColumns ();
458 	if (orderedColumns.length - 1 < startColumn) return -1;
459 	int rightX = orderedColumns [startColumn].getX ();
460 	for (int i = startColumn; i < orderedColumns.length; i++) {
461 		rightX += orderedColumns [i].width;
462 		if (x < rightX) return i;
463 	}
464 	return -1;
465 }
466 @Override
computeSize(int wHint, int hHint, boolean changed)467 public Point computeSize (int wHint, int hHint, boolean changed) {
468 	checkWidget ();
469 	int width = 0, height = 0;
470 	if (wHint != SWT.DEFAULT) {
471 		width = wHint;
472 	} else {
473 		if (columns.length == 0) {
474 			for (int i = 0; i < itemsCount; i++) {
475 				Rectangle itemBounds = items [i].getBounds (false);
476 				width = Math.max (width, itemBounds.x + itemBounds.width);
477 			}
478 		} else {
479 			CTableColumn[] orderedColumns = getOrderedColumns ();
480 			CTableColumn lastColumn = orderedColumns [orderedColumns.length - 1];
481 			width = lastColumn.getX () + lastColumn.width;
482 		}
483 	}
484 	if (hHint != SWT.DEFAULT) {
485 		height = hHint;
486 	} else {
487 		height = getHeaderHeight () + itemsCount * itemHeight;
488 	}
489 	Rectangle result = computeTrim (0, 0, width, height);
490 	return new Point (result.width, result.height);
491 }
createItem(CTableColumn column, int index)492 void createItem (CTableColumn column, int index) {
493 	CTableColumn[] newColumns = new CTableColumn [columns.length + 1];
494 	System.arraycopy (columns, 0, newColumns, 0, index);
495 	newColumns [index] = column;
496 	System.arraycopy (columns, index, newColumns, index + 1, columns.length - index);
497 	columns = newColumns;
498 
499 	if (orderedColumns != null) {
500 		int insertIndex = 0;
501 		if (index > 0) {
502 			insertIndex = columns [index - 1].getOrderIndex () + 1;
503 		}
504 		CTableColumn[] newOrderedColumns = new CTableColumn [orderedColumns.length + 1];
505 		System.arraycopy (orderedColumns, 0, newOrderedColumns, 0, insertIndex);
506 		newOrderedColumns [insertIndex] = column;
507 		System.arraycopy (
508 			orderedColumns,
509 			insertIndex,
510 			newOrderedColumns,
511 			insertIndex + 1,
512 			orderedColumns.length - insertIndex);
513 		orderedColumns = newOrderedColumns;
514 	}
515 
516 	/* allow all items to update their internal structures accordingly */
517 	for (int i = 0; i < itemsCount; i++) {
518 		items [i].addColumn (column);
519 	}
520 
521 	/* existing items become hidden when going from 0 to 1 column (0 width) */
522 	if (columns.length == 1 && itemsCount > 0) {
523 		redrawFromItemDownwards (topIndex);
524 	} else {
525 		/* checkboxes become hidden when creating a column with index == orderedIndex == 0 (0 width) */
526 		if (itemsCount > 0 && (getStyle () & SWT.CHECK) != 0 && index == 0 && column.getOrderIndex () == 0) {
527 			redrawFromItemDownwards (topIndex);
528 		}
529 	}
530 
531 	/* Columns were added, so notify the accessible. */
532 	int[] eventData = new int[5];
533 	eventData[0] = ACC.INSERT;
534 	eventData[1] = 0;
535 	eventData[2] = 0;
536 	eventData[3] = index;
537 	eventData[4] = 1;
538 	getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
539 }
createItem(CTableItem item)540 void createItem (CTableItem item) {
541 	int index = item.index;
542 	if (itemsCount == items.length) {
543 		int grow = drawCount <= 0 ? 4 : Math.max (4, items.length * 3 / 2);
544 		CTableItem[] newItems = new CTableItem [items.length + grow];
545 		System.arraycopy (items, 0, newItems, 0, items.length);
546 		items = newItems;
547 	}
548 	if (index != itemsCount) {
549 		/* new item is not at end of list, so shift other items right to create space for it */
550 		System.arraycopy (items, index, items, index + 1, itemsCount - index);
551 	}
552 	items [index] = item;
553 	itemsCount++;
554 
555 	/* update the index for items bumped down by this new item */
556 	for (int i = index + 1; i < itemsCount; i++) {
557 		items [i].index = i;
558 	}
559 
560 	/* Rows were added, so notify the accessible. */
561 	int[] eventData = new int[5];
562 	eventData[0] = ACC.INSERT;
563 	eventData[1] = index;
564 	eventData[2] = 1;
565 	eventData[3] = 0;
566 	eventData[4] = 0;
567 	getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
568 
569 	/* update scrollbars */
570 	updateVerticalBar ();
571 	Rectangle bounds = item.getBounds (false);
572 	int rightX = bounds.x + bounds.width;
573 	updateHorizontalBar (rightX, rightX);
574 	/*
575 	 * If new item is above viewport then adjust topIndex and the vertical
576 	 * scrollbar so that the current viewport items will not change.
577 	 */
578 	if (item.index < topIndex) {
579 		topIndex++;
580 		ScrollBar vBar = getVerticalBar ();
581 		if (vBar != null) vBar.setSelection (topIndex);
582 		return;
583 	}
584 	/*
585 	 * If this is the first item and the receiver has focus then its boundary
586 	 * focus ring must be removed.
587 	 */
588 	if (itemsCount == 1 && isFocusControl ()) {
589 		focusItem = item;
590 		redraw ();
591 		return;
592 	}
593 	if (item.isInViewport ()) {
594 		redrawFromItemDownwards (index);
595 	}
596 }
597 /**
598  * Deselects the item at the given zero-relative index in the receiver.
599  * If the item at the index was already deselected, it remains
600  * deselected. Indices that are out of range are ignored.
601  *
602  * @param index the index of the item to deselect
603  *
604  * @exception SWTException <ul>
605  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
606  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
607  * </ul>
608  */
deselect(int index)609 public void deselect (int index) {
610 	checkWidget ();
611 	if (!(0 <= index && index < itemsCount)) return;
612 	CTableItem item = items [index];
613 	int selectIndex = getSelectionIndex (item);
614 	if (selectIndex == -1) return;
615 
616 	CTableItem[] newSelectedItems = new CTableItem [selectedItems.length - 1];
617 	System.arraycopy (selectedItems, 0, newSelectedItems, 0, selectIndex);
618 	System.arraycopy (selectedItems, selectIndex + 1, newSelectedItems, selectIndex, newSelectedItems.length - selectIndex);
619 	selectedItems = newSelectedItems;
620 
621 	if (isFocusControl () || (getStyle () & SWT.HIDE_SELECTION) == 0) {
622 		redrawItem (item.index, false);
623 	}
624 	getAccessible().selectionChanged();
625 }
626 /**
627  * Deselects the items at the given zero-relative indices in the receiver.
628  * If the item at the given zero-relative index in the receiver
629  * is selected, it is deselected.  If the item at the index
630  * was not selected, it remains deselected.  The range of the
631  * indices is inclusive. Indices that are out of range are ignored.
632  *
633  * @param start the start index of the items to deselect
634  * @param end the end index of the items to deselect
635  *
636  * @exception SWTException <ul>
637  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
638  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
639  * </ul>
640  */
deselect(int start, int end)641 public void deselect (int start, int end) {
642 	checkWidget ();
643 	if (start == 0 && end == itemsCount - 1) {
644 		deselectAll ();
645 	} else {
646 		start = Math.max (start, 0);
647 		end = Math.min (end, itemsCount - 1);
648 		for (int i = start; i <= end; i++) {
649 			deselect (i);
650 		}
651 	}
652 }
653 /**
654  * Deselects the items at the given zero-relative indices in the receiver.
655  * If the item at the given zero-relative index in the receiver
656  * is selected, it is deselected.  If the item at the index
657  * was not selected, it remains deselected. Indices that are out
658  * of range and duplicate indices are ignored.
659  *
660  * @param indices the array of indices for the items to deselect
661  *
662  * @exception IllegalArgumentException <ul>
663  *    <li>ERROR_NULL_ARGUMENT - if the set of indices is null</li>
664  * </ul>
665  * @exception SWTException <ul>
666  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
667  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
668  * </ul>
669  */
deselect(int [] indices)670 public void deselect (int [] indices) {
671 	checkWidget ();
672 	if (indices == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
673 	if (indices.length == 0) return;
674 	for (int index : indices) {
675 		deselect (index);
676 	}
677 }
678 /**
679  * Deselects all selected items in the receiver.
680  *
681  * @exception SWTException <ul>
682  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
683  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
684  * </ul>
685  */
deselectAll()686 public void deselectAll () {
687 	checkWidget ();
688 	CTableItem[] oldSelection = selectedItems;
689 	selectedItems = new CTableItem [0];
690 	if (isFocusControl () || (getStyle () & SWT.HIDE_SELECTION) == 0) {
691 		for (CTableItem element : oldSelection) {
692 			redrawItem (element.index, true);
693 		}
694 	}
695 	for (CTableItem element : oldSelection) {
696 		element.getAccessible(getAccessible(), 0).selectionChanged();
697 	}
698 	if (oldSelection.length > 0) getAccessible().selectionChanged();
699 }
deselectItem(CTableItem item)700 void deselectItem (CTableItem item) {
701 	int index = getSelectionIndex (item);
702 	if (index == -1) return;
703 	CTableItem[] newSelectedItems = new CTableItem [selectedItems.length - 1];
704 	System.arraycopy (selectedItems, 0, newSelectedItems, 0, index);
705 	System.arraycopy (
706 		selectedItems,
707 		index + 1,
708 		newSelectedItems,
709 		index,
710 		newSelectedItems.length - index);
711 	selectedItems = newSelectedItems;
712 	item.getAccessible(getAccessible(), 0).selectionChanged();
713 }
destroyItem(CTableColumn column)714 void destroyItem (CTableColumn column) {
715 	headerHideToolTip ();
716 	int index = column.getIndex ();
717 	int orderedIndex = column.getOrderIndex ();
718 
719 	CTableColumn[] newColumns = new CTableColumn [columns.length - 1];
720 	System.arraycopy (columns, 0, newColumns, 0, index);
721 	System.arraycopy (columns, index + 1, newColumns, index, newColumns.length - index);
722 	columns = newColumns;
723 
724 	if (orderedColumns != null) {
725 		if (columns.length < 2) {
726 			orderedColumns = null;
727 		} else {
728 			int removeIndex = column.getOrderIndex ();
729 			CTableColumn[] newOrderedColumns = new CTableColumn [orderedColumns.length - 1];
730 			System.arraycopy (orderedColumns, 0, newOrderedColumns, 0, removeIndex);
731 			System.arraycopy (
732 				orderedColumns,
733 				removeIndex + 1,
734 				newOrderedColumns,
735 				removeIndex,
736 				newOrderedColumns.length - removeIndex);
737 			orderedColumns = newOrderedColumns;
738 		}
739 	}
740 
741 	/* ensure that column 0 always has left-alignment */
742 	if (index == 0 && columns.length > 0) {
743 		int style = columns [0].getStyle ();
744 		style |= SWT.LEFT;
745 		style &= ~(SWT.CENTER | SWT.RIGHT);
746 		columns [0].setStyle (style);
747 	}
748 
749 	/* allow all items to update their internal structures accordingly */
750 	for (int i = 0; i < itemsCount; i++) {
751 		items [i].removeColumn (column, index);
752 	}
753 
754 	/* update horizontal scrollbar */
755 	int lastColumnIndex = columns.length - 1;
756 	if (lastColumnIndex < 0) {		/* no more columns */
757 		updateHorizontalBar ();
758 	} else {
759 		int newWidth = 0;
760 		for (CTableColumn column2 : columns) {
761 			newWidth += column2.width;
762 		}
763 		ScrollBar hBar = getHorizontalBar ();
764 		if (hBar != null) {
765 			hBar.setMaximum (newWidth);
766 			hBar.setVisible (clientArea.width < newWidth);
767 		}
768 		int selection = hBar.getSelection ();
769 		if (selection != horizontalOffset) {
770 			horizontalOffset = selection;
771 			redraw ();
772 			if (header.isVisible () && drawCount <= 0) header.redraw ();
773 		}
774 	}
775 	CTableColumn[] orderedColumns = getOrderedColumns ();
776 	for (int i = orderedIndex; i < orderedColumns.length; i++) {
777 		if (!orderedColumns [i].isDisposed ()) {
778 			orderedColumns [i].notifyListeners (SWT.Move, new Event ());
779 		}
780 	}
781 
782 	int[] eventData = new int[5];
783 	eventData[0] = ACC.DELETE;
784 	eventData[1] = 0;
785 	eventData[2] = 0;
786 	eventData[3] = index;
787 	eventData[4] = 1;
788 	getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
789 
790 	if (sortColumn == column) {
791 		sortColumn = null;
792 	}
793 }
794 /*
795  * Allows the Table to update internal structures it has that may contain the
796  * item being destroyed.
797  */
798 void destroyItem (CTableItem item) {
799 	if (item == focusItem) reassignFocus ();
800 
801 	int index = item.index;
802 	Rectangle bounds = item.getBounds (false);
803 	int rightX = bounds.x + bounds.width;
804 
805 	if (index != itemsCount - 1) {
806 		/* item is not at end of items list, so must shift items left to reclaim its slot */
807 		System.arraycopy (items, index + 1, items, index, itemsCount - index - 1);
808 		items [itemsCount - 1] = null;
809 	} else {
810 		items [index] = null;	/* last item, so no array copy needed */
811 	}
812 	itemsCount--;
813 
814 	if (drawCount <= 0 && items.length - itemsCount == 4) {
815 		/* shrink the items array */
816 		CTableItem[] newItems = new CTableItem [itemsCount];
817 		System.arraycopy (items, 0, newItems, 0, newItems.length);
818 		items = newItems;
819 	}
820 
821 	/* update the index on affected items */
822 	for (int i = index; i < itemsCount; i++) {
823 		items [i].index = i;
824 	}
825 	item.index = -1;
826 
827 	int oldTopIndex = topIndex;
828 	updateVerticalBar ();
829 	updateHorizontalBar (0, -rightX);
830 	/*
831 	 * If destroyed item is above viewport then adjust topIndex and the vertical
832 	 * scrollbar so that the current viewport items will not change.
833 	 */
834 	if (index < topIndex) {
835 		topIndex = oldTopIndex - 1;
836 		ScrollBar vBar = getVerticalBar ();
837 		if (vBar != null) vBar.setSelection (topIndex);
838 	}
839 
840 	/* selectedItems array */
841 	if (item.isSelected ()) {
842 		int selectionIndex = getSelectionIndex (item);
843 		CTableItem[] newSelectedItems = new CTableItem [selectedItems.length - 1];
844 		System.arraycopy (selectedItems, 0, newSelectedItems, 0, selectionIndex);
845 		System.arraycopy (
846 			selectedItems,
847 			selectionIndex + 1,
848 			newSelectedItems,
849 			selectionIndex,
850 			newSelectedItems.length - selectionIndex);
851 		selectedItems = newSelectedItems;
852 	}
853 	if (item == anchorItem) anchorItem = null;
854 	if (item == lastClickedItem) lastClickedItem = null;
855 	/*
856 	 * If this was the last item and the receiver has focus then its boundary
857 	 * focus ring must be redrawn.
858 	 */
859 	if (itemsCount == 0 && isFocusControl ()) {
860 		redraw ();
861 	}
862 
863 	int[] eventData = new int[5];
864 	eventData[0] = ACC.DELETE;
865 	eventData[1] = index;
866 	eventData[2] = 1;
867 	eventData[3] = 0;
868 	eventData[4] = 0;
869 	getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
870 }
871 Image getArrowDownImage () {
872 	return (Image) display.getData (ID_ARROWDOWN);
873 }
874 Image getArrowUpImage () {
875 	return (Image) display.getData (ID_ARROWUP);
876 }
877 int getCellPadding () {
878 	return MARGIN_CELL + WIDTH_CELL_HIGHLIGHT;
879 }
880 Image getCheckmarkImage () {
881 	return (Image) display.getData (ID_CHECKMARK);
882 }
883 @Override
884 public Control[] getChildren () {
885 	checkWidget ();
886 	Control[] controls = super.getChildren ();
887 	if (header == null) return controls;
888 	Control[] result = new Control [controls.length - 1];
889 	/* remove the Header from the returned set of children */
890 	int index = 0;
891 	for (Control control : controls) {
892 		if (control != header) {
893 			result [index++] = control;
894 		}
895 	}
896 	return result;
897 }
898 /**
899  * Returns the column at the given, zero-relative index in the
900  * receiver. Throws an exception if the index is out of range.
901  * Columns are returned in the order that they were created.
902  * If no <code>TableColumn</code>s were created by the programmer,
903  * this method will throw <code>ERROR_INVALID_RANGE</code> despite
904  * the fact that a single column of data may be visible in the table.
905  * This occurs when the programmer uses the table like a list, adding
906  * items but never creating a column.
907  *
908  * @param index the index of the column to return
909  * @return the column at the given index
910  *
911  * @exception IllegalArgumentException <ul>
912  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
913  * </ul>
914  * @exception SWTException <ul>
915  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
916  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
917  * </ul>
918  *
919  * @see CTable#getColumnOrder()
920  * @see CTable#setColumnOrder(int[])
921  * @see CTableColumn#getMoveable()
922  * @see CTableColumn#setMoveable(boolean)
923  * @see SWT#Move
924  */
925 public CTableColumn getColumn (int index) {
926 	checkWidget ();
927 	if (!(0 <= index && index < columns.length)) SWT.error (SWT.ERROR_INVALID_RANGE);
928 	return columns [index];
929 }
930 /**
931  * Returns the number of columns contained in the receiver.
932  * If no <code>TableColumn</code>s were created by the programmer,
933  * this value is zero, despite the fact that visually, one column
934  * of items may be visible. This occurs when the programmer uses
935  * the table like a list, adding items but never creating a column.
936  *
937  * @return the number of columns
938  *
939  * @exception SWTException <ul>
940  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
941  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
942  * </ul>
943  */
944 public int getColumnCount () {
945 	checkWidget ();
946 	return columns.length;
947 }
948 /**
949  * Returns an array of zero-relative integers that map
950  * the creation order of the receiver's items to the
951  * order in which they are currently being displayed.
952  * <p>
953  * Specifically, the indices of the returned array represent
954  * the current visual order of the items, and the contents
955  * of the array represent the creation order of the items.
956  * </p><p>
957  * Note: This is not the actual structure used by the receiver
958  * to maintain its list of items, so modifying the array will
959  * not affect the receiver.
960  * </p>
961  *
962  * @return the current visual order of the receiver's items
963  *
964  * @exception SWTException <ul>
965  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
966  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
967  * </ul>
968  *
969  * @see CTable#setColumnOrder(int[])
970  * @see CTableColumn#getMoveable()
971  * @see CTableColumn#setMoveable(boolean)
972  * @see SWT#Move
973  *
974  * @since 3.1
975  */
976 public int[] getColumnOrder () {
977 	checkWidget ();
978 	int[] result = new int [columns.length];
979 	if (orderedColumns != null) {
980 		for (int i = 0; i < result.length; i++) {
981 			result [i] = orderedColumns [i].getIndex ();
982 		}
983 	} else {
984 		for (int i = 0; i < columns.length; i++) {
985 			result [i] = i;
986 		}
987 	}
988 	return result;
989 }
990 /**
991  * Returns an array of <code>TableColumn</code>s which are the
992  * columns in the receiver.  Columns are returned in the order
993  * that they were created.  If no <code>TableColumn</code>s were
994  * created by the programmer, the array is empty, despite the fact
995  * that visually, one column of items may be visible. This occurs
996  * when the programmer uses the table like a list, adding items but
997  * never creating a column.
998  * <p>
999  * Note: This is not the actual structure used by the receiver
1000  * to maintain its list of items, so modifying the array will
1001  * not affect the receiver.
1002  * </p>
1003  *
1004  * @return the items in the receiver
1005  *
1006  * @exception SWTException <ul>
1007  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1008  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1009  * </ul>
1010  *
1011  * @see CTable#getColumnOrder()
1012  * @see CTable#setColumnOrder(int[])
1013  * @see CTableColumn#getMoveable()
1014  * @see CTableColumn#setMoveable(boolean)
1015  * @see SWT#Move
1016  */
1017 public CTableColumn[] getColumns () {
1018 	checkWidget ();
1019 	CTableColumn[] result = new CTableColumn [columns.length];
1020 	System.arraycopy (columns, 0, result, 0, columns.length);
1021 	return result;
1022 }
1023 Image getGrayUncheckedImage () {
1024 	return (Image) display.getData (ID_GRAYUNCHECKED);
1025 }
1026 /**
1027  * Returns the width in pixels of a grid line.
1028  *
1029  * @return the width of a grid line in pixels
1030  *
1031  * @exception SWTException <ul>
1032  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1033  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1034  * </ul>
1035  */
1036 public int getGridLineWidth () {
1037 	checkWidget ();
1038 	return 1;
1039 }
1040 /**
1041  * Returns the height of the receiver's header
1042  *
1043  * @return the height of the header or zero if the header is not visible
1044  *
1045  * @exception SWTException <ul>
1046  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1047  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1048  * </ul>
1049  *
1050  * @since 2.0
1051  */
1052 public int getHeaderHeight () {
1053 	checkWidget ();
1054 	if (!header.getVisible ()) return 0;
1055 	return header.getSize ().y;
1056 }
1057 int getHeaderPadding () {
1058 	return MARGIN_CELL + WIDTH_HEADER_SHADOW;
1059 }
1060 /**
1061  * Returns <code>true</code> if the receiver's header is visible,
1062  * and <code>false</code> otherwise.
1063  * <p>
1064  * If one of the receiver's ancestors is not visible or some
1065  * other condition makes the receiver not visible, this method
1066  * may still indicate that it is considered visible even though
1067  * it may not actually be showing.
1068  * </p>
1069  *
1070  * @return the receiver's header's visibility state
1071  *
1072  * @exception SWTException <ul>
1073  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1074  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1075  * </ul>
1076  */
1077 public boolean getHeaderVisible () {
1078 	checkWidget ();
1079 	return header.getVisible ();
1080 }
1081 /**
1082  * Returns the item at the given, zero-relative index in the
1083  * receiver. Throws an exception if the index is out of range.
1084  *
1085  * @param index the index of the item to return
1086  * @return the item at the given index
1087  *
1088  * @exception IllegalArgumentException <ul>
1089  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
1090  * </ul>
1091  * @exception SWTException <ul>
1092  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1093  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1094  * </ul>
1095  */
1096 public CTableItem getItem (int index) {
1097 	checkWidget ();
1098 	if (!(0 <= index && index < itemsCount)) SWT.error (SWT.ERROR_INVALID_RANGE);
1099 	return items [index];
1100 }
1101 /**
1102  * Returns the item at the given point in the receiver
1103  * or null if no such item exists. The point is in the
1104  * coordinate system of the receiver.
1105  * <p>
1106  * The item that is returned represents an item that could be selected by the user.
1107  * For example, if selection only occurs in items in the first column, then null is
1108  * returned if the point is outside of the item.
1109  * Note that the SWT.FULL_SELECTION style hint, which specifies the selection policy,
1110  * determines the extent of the selection.
1111  * </p>
1112  *
1113  * @param point the point used to locate the item
1114  * @return the item at the given point, or null if the point is not in a selectable item
1115  *
1116  * @exception IllegalArgumentException <ul>
1117  *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
1118  * </ul>
1119  * @exception SWTException <ul>
1120  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1121  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1122  * </ul>
1123  */
1124 public CTableItem getItem (Point point) {
1125 	checkWidget ();
1126 	if (point == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
1127 	int index = (point.y - getHeaderHeight ()) / itemHeight + topIndex;
1128 	if (!(0 <= index && index < itemsCount)) return null;		/* below the last item */
1129 	CTableItem result = items [index];
1130 	if (!result.getHitBounds ().contains (point)) return null;	/* considers the x value */
1131 	return result;
1132 }
1133 /**
1134  * Returns the number of items contained in the receiver.
1135  *
1136  * @return the number of items
1137  *
1138  * @exception SWTException <ul>
1139  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1140  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1141  * </ul>
1142  */
1143 public int getItemCount () {
1144 	checkWidget ();
1145 	return itemsCount;
1146 }
1147 /**
1148  * Returns the height of the area which would be used to
1149  * display <em>one</em> of the items in the receiver.
1150  *
1151  * @return the height of one item
1152  *
1153  * @exception SWTException <ul>
1154  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1155  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1156  * </ul>
1157  */
1158 public int getItemHeight () {
1159 	checkWidget ();
1160 	return itemHeight;
1161 }
1162 /**
1163  * Returns a (possibly empty) array of <code>TableItem</code>s which
1164  * are the items in the receiver.
1165  * <p>
1166  * Note: This is not the actual structure used by the receiver
1167  * to maintain its list of items, so modifying the array will
1168  * not affect the receiver.
1169  * </p>
1170  *
1171  * @return the items in the receiver
1172  *
1173  * @exception SWTException <ul>
1174  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1175  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1176  * </ul>
1177  */
1178 public CTableItem[] getItems () {
1179 	checkWidget ();
1180 	CTableItem[] result = new CTableItem [itemsCount];
1181 	System.arraycopy (items, 0, result, 0, itemsCount);
1182 	return result;
1183 }
1184 /*
1185  * Returns the current y-coordinate that the specified item should have.
1186  */
1187 int getItemY (CTableItem item) {
1188 	return (item.index - topIndex) * itemHeight + getHeaderHeight ();
1189 }
1190 /**
1191  * Returns <code>true</code> if the receiver's lines are visible,
1192  * and <code>false</code> otherwise. Note that some platforms draw
1193  * grid lines while others may draw alternating row colors.
1194  * <p>
1195  * If one of the receiver's ancestors is not visible or some
1196  * other condition makes the receiver not visible, this method
1197  * may still indicate that it is considered visible even though
1198  * it may not actually be showing.
1199  * </p>
1200  *
1201  * @return the visibility state of the lines
1202  *
1203  * @exception SWTException <ul>
1204  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1205  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1206  * </ul>
1207  */
1208 public boolean getLinesVisible () {
1209 	checkWidget ();
1210 	return linesVisible;
1211 }
1212 CTableColumn[] getOrderedColumns () {
1213 	if (orderedColumns != null) return orderedColumns;
1214 	return columns;
1215 }
1216 /**
1217  * Returns an array of <code>TableItem</code>s that are currently
1218  * selected in the receiver. The order of the items is unspecified.
1219  * An empty array indicates that no items are selected.
1220  * <p>
1221  * Note: This is not the actual structure used by the receiver
1222  * to maintain its selection, so modifying the array will
1223  * not affect the receiver.
1224  * </p>
1225  * @return an array representing the selection
1226  *
1227  * @exception SWTException <ul>
1228  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1229  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1230  * </ul>
1231  */
1232 public CTableItem[] getSelection () {
1233 	checkWidget ();
1234 	CTableItem[] result = new CTableItem [selectedItems.length];
1235 	System.arraycopy (selectedItems, 0, result, 0, selectedItems.length);
1236 	sortAscent (result);
1237 	return result;
1238 }
1239 /**
1240  * Returns the number of selected items contained in the receiver.
1241  *
1242  * @return the number of selected items
1243  *
1244  * @exception SWTException <ul>
1245  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1246  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1247  * </ul>
1248  */
1249 public int getSelectionCount () {
1250 	checkWidget ();
1251 	return selectedItems.length;
1252 }
1253 /**
1254  * Returns the zero-relative index of the item which is currently
1255  * selected in the receiver, or -1 if no item is selected.
1256  *
1257  * @return the index of the selected item
1258  *
1259  * @exception SWTException <ul>
1260  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1261  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1262  * </ul>
1263  */
1264 public int getSelectionIndex () {
1265 	checkWidget ();
1266 	if (selectedItems.length == 0) return -1;
1267 	return selectedItems [0].index;
1268 }
1269 /*
1270  * Returns the index of the argument in the receiver's array of currently-
1271  * selected items, or -1 if the item is not currently selected.
1272  */
1273 int getSelectionIndex (CTableItem item) {
1274 	for (int i = 0; i < selectedItems.length; i++) {
1275 		if (selectedItems [i] == item) return i;
1276 	}
1277 	return -1;
1278 }
1279 /**
1280  * Returns the zero-relative indices of the items which are currently
1281  * selected in the receiver. The order of the indices is unspecified.
1282  * The array is empty if no items are selected.
1283  * <p>
1284  * Note: This is not the actual structure used by the receiver
1285  * to maintain its selection, so modifying the array will
1286  * not affect the receiver.
1287  * </p>
1288  * @return the array of indices of the selected items
1289  *
1290  * @exception SWTException <ul>
1291  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1292  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1293  * </ul>
1294  */
1295 public int [] getSelectionIndices () {
1296 	checkWidget ();
1297 	int[] result = new int [selectedItems.length];
1298 	for (int i = 0; i < selectedItems.length; i++) {
1299 		result [i] = selectedItems [i].index;
1300 	}
1301 	sortAscent (result);
1302 	return result;
1303 }
1304 /**
1305  * Returns the column which shows the sort indicator for
1306  * the receiver. The value may be null if no column shows
1307  * the sort indicator.
1308  *
1309  * @return the sort indicator
1310  *
1311  * @exception SWTException <ul>
1312  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1313  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1314  * </ul>
1315  *
1316  * @see #setSortColumn(CTableColumn)
1317  *
1318  * @since 3.2
1319  */
1320 public CTableColumn getSortColumn () {
1321 	checkWidget ();
1322 	return sortColumn;
1323 }
1324 /**
1325  * Returns the direction of the sort indicator for the receiver.
1326  * The value will be one of <code>UP</code>, <code>DOWN</code>
1327  * or <code>NONE</code>.
1328  *
1329  * @return the sort direction
1330  *
1331  * @exception SWTException <ul>
1332  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1333  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1334  * </ul>
1335  *
1336  * @see #setSortDirection(int)
1337  *
1338  * @since 3.2
1339  */
1340 public int getSortDirection () {
1341 	checkWidget ();
1342 	return sortDirection;
1343 }
1344 /**
1345  * Returns the zero-relative index of the item which is currently
1346  * at the top of the receiver. This index can change when items are
1347  * scrolled or new items are added or removed.
1348  *
1349  * @return the index of the top item
1350  *
1351  * @exception SWTException <ul>
1352  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1353  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1354  * </ul>
1355  */
1356 public int getTopIndex () {
1357 	checkWidget ();
1358 	return topIndex;
1359 }
1360 Image getUncheckedImage () {
1361 	return (Image) display.getData (ID_UNCHECKED);
1362 }
1363 void handleEvents (Event event) {
1364 	switch (event.type) {
1365 		case SWT.Paint:
1366 			if (event.widget == header) {
1367 				headerOnPaint (event);
1368 			} else {
1369 				onPaint (event);
1370 			}
1371 			break;
1372 		case SWT.MenuDetect: {
1373 			notifyListeners (SWT.MenuDetect, event);
1374 			break;
1375 			}
1376 		case SWT.MouseDown:
1377 			if (event.widget == header) {
1378 				headerOnMouseDown (event);
1379 			} else {
1380 				onMouseDown (event);
1381 			}
1382 			break;
1383 		case SWT.MouseUp:
1384 			if (event.widget == header) {
1385 				headerOnMouseUp (event);
1386 			} else {
1387 				onMouseUp (event);
1388 			}
1389 			break;
1390 		case SWT.MouseHover:
1391 			headerOnMouseHover (event); break;
1392 		case SWT.MouseMove:
1393 			headerOnMouseMove (event); break;
1394 		case SWT.MouseDoubleClick:
1395 			if (event.widget == header) {
1396 				headerOnMouseDoubleClick (event);
1397 			} else {
1398 				onMouseDoubleClick (event);
1399 			}
1400 			break;
1401 		case SWT.MouseExit:
1402 			headerOnMouseExit (); break;
1403 		case SWT.Dispose:
1404 			onDispose (event); break;
1405 		case SWT.KeyDown:
1406 			onKeyDown (event); break;
1407 		case SWT.Resize:
1408 			onResize (event); break;
1409 		case SWT.Selection:
1410 			if (event.widget == getHorizontalBar ()) {
1411 				onScrollHorizontal (event);
1412 			}
1413 			if (event.widget == getVerticalBar ()) {
1414 				onScrollVertical (event);
1415 			}
1416 			break;
1417 		case SWT.FocusOut:
1418 			onFocusOut (); break;
1419 		case SWT.FocusIn:
1420 			onFocusIn (); break;
1421 		case SWT.Traverse:
1422 			switch (event.detail) {
1423 				case SWT.TRAVERSE_ESCAPE:
1424 				case SWT.TRAVERSE_RETURN:
1425 				case SWT.TRAVERSE_TAB_NEXT:
1426 				case SWT.TRAVERSE_TAB_PREVIOUS:
1427 				case SWT.TRAVERSE_PAGE_NEXT:
1428 				case SWT.TRAVERSE_PAGE_PREVIOUS:
1429 					event.doit = true;
1430 					break;
1431 			}
1432 			break;
1433 	}
1434 }
1435 String headerGetToolTip (int x) {
1436 	if (resizeColumn != null) return null;
1437 	int orderedIndex = computeColumnIntersect (x, 0);
1438 	if (orderedIndex == -1) return null;
1439 	CTableColumn[] orderedColumns = getOrderedColumns ();
1440 	CTableColumn column = orderedColumns [orderedIndex];
1441 	if (column.toolTipText == null) return null;
1442 
1443 	/* no tooltip should appear if the hover is at a column resize opportunity */
1444 	int columnX = column.getX ();
1445 	if (orderedIndex > 0 && orderedColumns [orderedIndex - 1].resizable) {
1446 		/* left column bound is resizable */
1447 		if (x - columnX <= TOLLERANCE_COLUMNRESIZE) return null;
1448 	}
1449 	if (column.resizable) {
1450 		/* right column bound is resizable */
1451 		int columnRightX = columnX + column.width;
1452 		if (columnRightX - x <= TOLLERANCE_COLUMNRESIZE) return null;
1453 	}
1454 	return removeMnemonics (column.toolTipText);
1455 }
1456 void headerHideToolTip() {
1457 	if (toolTipShell == null) return;
1458 	for (int toolTipEvent : toolTipEvents) {
1459 		header.removeListener (toolTipEvent, toolTipListener);
1460 	}
1461 	toolTipShell.dispose ();
1462 	toolTipShell = null;
1463 	toolTipLabel = null;
1464 }
1465 void headerOnMouseDoubleClick (Event event) {
1466 	if (!isFocusControl ()) setFocus ();
1467 	if (columns.length == 0) return;
1468 	CTableColumn[] orderedColumns = getOrderedColumns ();
1469 	int x = -horizontalOffset;
1470 	for (int i = 0; i < orderedColumns.length; i++) {
1471 		CTableColumn column = orderedColumns [i];
1472 		x += column.width;
1473 		if (event.x < x) {
1474 			/* found the clicked column */
1475 			CTableColumn packColumn = null;
1476 			if (x - event.x <= TOLLERANCE_COLUMNRESIZE) {
1477 				/* clicked on column bound for this column */
1478 				packColumn = column;
1479 			} else {
1480 				if (i > 0 && event.x - column.getX () <= TOLLERANCE_COLUMNRESIZE) {
1481 					/* clicked on column bound that applies to previous column */
1482 					packColumn = orderedColumns [i - 1];
1483 				}
1484 			}
1485 			if (packColumn != null) {
1486 				packColumn.pack ();
1487 				resizeColumn = null;
1488 				if (Math.abs (packColumn.getX () + packColumn.width - event.x) > TOLLERANCE_COLUMNRESIZE) {
1489 					/* column separator has relocated away from pointer location */
1490 					setCursor (null);
1491 				}
1492 				return;
1493 			}
1494 			/* did not click on column separator, so just fire column event */
1495 			Event newEvent = new Event ();
1496 			newEvent.widget = column;
1497 			column.notifyListeners (SWT.DefaultSelection, newEvent);
1498 			return;
1499 		}
1500 	}
1501 }
1502 void headerOnMouseDown (Event event) {
1503 	if (event.button != 1) return;
1504 	CTableColumn[] orderedColumns = getOrderedColumns ();
1505 	int x = -horizontalOffset;
1506 	for (CTableColumn column : orderedColumns) {
1507 		x += column.width;
1508 		/* if close to a resizable column separator line then begin column resize */
1509 		if (column.resizable && Math.abs (x - event.x) <= TOLLERANCE_COLUMNRESIZE) {
1510 			resizeColumn = column;
1511 			resizeColumnX = x;
1512 			return;
1513 		}
1514 		/*
1515 		 * If within column but not near separator line then start column drag
1516 		 * if column is moveable, or just fire column Selection otherwise.
1517 		 */
1518 		if (event.x < x) {
1519 			if (column.moveable) {
1520 				/* open tracker on the dragged column's header cell */
1521 				int columnX = column.getX ();
1522 				int pointerOffset = event.x - columnX;
1523 				headerHideToolTip ();
1524 				Tracker tracker = new Tracker (this, SWT.NONE);
1525 				tracker.setRectangles (new Rectangle[] {
1526 					new Rectangle (columnX, 0, column.width, getHeaderHeight ())
1527 				});
1528 				if (!tracker.open ()) return;	/* cancelled */
1529 				/* determine which column was dragged onto */
1530 				Rectangle result = tracker.getRectangles () [0];
1531 				int pointerX = result.x + pointerOffset;
1532 				if (pointerX < 0) return;	/* dragged too far left */
1533 				x = -horizontalOffset;
1534 				for (int destIndex = 0; destIndex < orderedColumns.length; destIndex++) {
1535 					CTableColumn destColumn = orderedColumns [destIndex];
1536 					x += destColumn.width;
1537 					if (pointerX < x) {
1538 						int oldIndex = column.getOrderIndex ();
1539 						if (destIndex == oldIndex) {	/* dragged onto self */
1540 							Event newEvent = new Event ();
1541 							newEvent.widget = column;
1542 							column.notifyListeners (SWT.Selection, newEvent);
1543 							return;
1544 						}
1545 						int leftmostIndex = Math.min (destIndex, oldIndex);
1546 						int[] oldOrder = getColumnOrder ();
1547 						int[] newOrder = new int [oldOrder.length];
1548 						System.arraycopy (oldOrder, 0, newOrder, 0, leftmostIndex);
1549 						if (leftmostIndex == oldIndex) {
1550 							/* column moving to the right */
1551 							System.arraycopy (oldOrder, oldIndex + 1, newOrder, oldIndex, destIndex - oldIndex);
1552 						} else {
1553 							/* column moving to the left */
1554 							System.arraycopy (oldOrder, destIndex, newOrder, destIndex + 1, oldIndex - destIndex);
1555 						}
1556 						newOrder [destIndex] = oldOrder [oldIndex];
1557 						int rightmostIndex = Math.max (destIndex, oldIndex);
1558 						System.arraycopy (
1559 							oldOrder,
1560 							rightmostIndex + 1,
1561 							newOrder,
1562 							rightmostIndex + 1,
1563 							newOrder.length - rightmostIndex - 1);
1564 						setColumnOrder (newOrder);
1565 						return;
1566 					}
1567 				}
1568 				return;		/* dragged too far right */
1569 			}
1570 			/* column is not moveable */
1571 			Event newEvent = new Event ();
1572 			newEvent.widget = column;
1573 			column.notifyListeners (SWT.Selection, newEvent);
1574 			return;
1575 		}
1576 	}
1577 }
1578 void headerOnMouseExit () {
1579 	if (resizeColumn != null) return;
1580 	setCursor (null);	/* ensure that a column resize cursor does not escape */
1581 }
1582 void headerOnMouseHover (Event event) {
1583 	headerShowToolTip (event.x);
1584 }
1585 void headerOnMouseMove (Event event) {
1586 	if (resizeColumn == null) {
1587 		/* not currently resizing a column */
1588 		for (CTableColumn column : columns) {
1589 			int x = column.getX () + column.width;
1590 			if (Math.abs (x - event.x) <= TOLLERANCE_COLUMNRESIZE) {
1591 				if (column.resizable) {
1592 					setCursor (display.getSystemCursor (SWT.CURSOR_SIZEWE));
1593 				} else {
1594 					setCursor (null);
1595 				}
1596 				return;
1597 			}
1598 		}
1599 		setCursor (null);
1600 		return;
1601 	}
1602 
1603 	/* currently resizing a column */
1604 
1605 	/* don't allow the resize x to move left of the column's x position */
1606 	if (event.x <= resizeColumn.getX ()) return;
1607 
1608 	/* redraw the resizing line at its new location */
1609 	GC gc = new GC (this);
1610 	gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK));
1611 	int lineHeight = clientArea.height;
1612 	redraw (resizeColumnX - 1, 0, 1, lineHeight, false);
1613 	resizeColumnX = event.x;
1614 	gc.drawLine (resizeColumnX - 1, 0, resizeColumnX - 1, lineHeight);
1615 	gc.dispose ();
1616 }
1617 void headerOnMouseUp (Event event) {
1618 	if (resizeColumn == null) return;	/* not resizing a column */
1619 
1620 	/* remove the resize line */
1621 	GC gc = new GC (this);
1622 	redraw (resizeColumnX - 1, 0, 1, clientArea.height, false);
1623 	gc.dispose ();
1624 
1625 	int newWidth = resizeColumnX - resizeColumn.getX ();
1626 	if (newWidth != resizeColumn.width) {
1627 		setCursor (null);
1628 		updateColumnWidth (resizeColumn, newWidth);
1629 	}
1630 	resizeColumnX = -1;
1631 	resizeColumn = null;
1632 }
1633 void headerOnPaint (Event event) {
1634 	CTableColumn[] orderedColumns = getOrderedColumns ();
1635 	int numColumns = orderedColumns.length;
1636 	GC gc = event.gc;
1637 	Rectangle clipping = gc.getClipping ();
1638 	int startColumn = -1, endColumn = -1;
1639 	if (numColumns > 0) {
1640 		startColumn = computeColumnIntersect (clipping.x, 0);
1641 		if (startColumn != -1) {	/* the clip x is within a column's bounds */
1642 			endColumn = computeColumnIntersect (clipping.x + clipping.width, startColumn);
1643 			if (endColumn == -1) endColumn = numColumns - 1;
1644 		}
1645 	} else {
1646 		startColumn = endColumn = 0;
1647 	}
1648 
1649 	/* paint the column header shadow that spans the full header width */
1650 	Point headerSize = header.getSize ();
1651 	headerPaintHShadows (gc, 0, 0, headerSize.x, headerSize.y);
1652 
1653 	/* if all damage is to the right of the last column then finished */
1654 	if (startColumn == -1) return;
1655 
1656 	/* paint each of the column headers */
1657 	if (numColumns == 0) return;	/* no headers to paint */
1658 	for (int i = startColumn; i <= endColumn; i++) {
1659 		headerPaintVShadows (gc, orderedColumns [i].getX (), 0, orderedColumns [i].width, headerSize.y);
1660 		orderedColumns [i].paint (gc);
1661 	}
1662 }
1663 void headerPaintHShadows (GC gc, int x, int y, int width, int height) {
1664 	gc.setClipping (x, y, width, height);
1665 	int endX = x + width;
1666 	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
1667 	gc.drawLine (x, y, endX, y);					/* highlight shadow */
1668 	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_NORMAL_SHADOW));
1669 	gc.drawLine (x, height - 2, endX, height - 2);	/* lowlight shadow */
1670 	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_DARK_SHADOW));
1671 	gc.drawLine (x, height - 1, endX, height - 1);	/* outer shadow */
1672 }
1673 void headerPaintVShadows (GC gc, int x, int y, int width, int height) {
1674 	gc.setClipping (x, y, width, height);
1675 	int endX = x + width;
1676 	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
1677 	gc.drawLine (x, y, x, y + height - 1);					/* highlight shadow */
1678 	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_NORMAL_SHADOW));
1679 	gc.drawLine (endX - 2, y + 1, endX - 2, height - 2);	/* light inner shadow */
1680 	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_DARK_SHADOW));
1681 	gc.drawLine (endX - 1, y, endX - 1, height - 1);		/* dark outer shadow */
1682 }
1683 void headerShowToolTip (int x) {
1684 	String tooltip = headerGetToolTip (x);
1685 	if (tooltip == null || tooltip.length () == 0) return;
1686 
1687 	if (toolTipShell == null) {
1688 		toolTipShell = new Shell (getShell (), SWT.ON_TOP | SWT.TOOL);
1689 		toolTipLabel = new Label (toolTipShell, SWT.CENTER);
1690 		Display display = toolTipShell.getDisplay ();
1691 		toolTipLabel.setForeground (display.getSystemColor (SWT.COLOR_INFO_FOREGROUND));
1692 		toolTipLabel.setBackground (display.getSystemColor (SWT.COLOR_INFO_BACKGROUND));
1693 		for (int toolTipEvent : toolTipEvents) {
1694 			header.addListener (toolTipEvent, toolTipListener);
1695 		}
1696 	}
1697 	if (headerUpdateToolTip (x)) {
1698 		toolTipShell.setVisible (true);
1699 	} else {
1700 		headerHideToolTip ();
1701 	}
1702 }
1703 boolean headerUpdateToolTip (int x) {
1704 	String tooltip = headerGetToolTip (x);
1705 	if (tooltip == null || tooltip.length () == 0) return false;
1706 	if (tooltip.equals (toolTipLabel.getText ())) return true;
1707 
1708 	toolTipLabel.setText (tooltip);
1709 	CTableColumn column = getOrderedColumns () [computeColumnIntersect (x, 0)];
1710 	toolTipShell.setData (Integer.valueOf (column.getIndex ()));
1711 	Point labelSize = toolTipLabel.computeSize (SWT.DEFAULT, SWT.DEFAULT, true);
1712 	labelSize.x += 2; labelSize.y += 2;
1713 	toolTipLabel.setSize (labelSize);
1714 	toolTipShell.pack ();
1715 	/*
1716 	 * On some platforms, there is a minimum size for a shell
1717 	 * which may be greater than the label size.
1718 	 * To avoid having the background of the tip shell showing
1719 	 * around the label, force the label to fill the entire client area.
1720 	 */
1721 	Rectangle area = toolTipShell.getClientArea ();
1722 	toolTipLabel.setSize (area.width, area.height);
1723 
1724 	/* Position the tooltip and ensure it's not located off the screen */
1725 	Point cursorLocation = getDisplay ().getCursorLocation ();
1726 	int cursorHeight = 21;	/* assuming cursor is 21x21 */
1727 	Point size = toolTipShell.getSize ();
1728 	Rectangle rect = getMonitor ().getBounds ();
1729 	Point pt = new Point (cursorLocation.x, cursorLocation.y + cursorHeight + 2);
1730 	pt.x = Math.max (pt.x, rect.x);
1731 	if (pt.x + size.x > rect.x + rect.width) pt.x = rect.x + rect.width - size.x;
1732 	if (pt.y + size.y > rect.y + rect.height) pt.y = cursorLocation.y - 2 - size.y;
1733 	toolTipShell.setLocation (pt);
1734 	return true;
1735 }
1736 /**
1737  * Searches the receiver's list starting at the first column
1738  * (index 0) until a column is found that is equal to the
1739  * argument, and returns the index of that column. If no column
1740  * is found, returns -1.
1741  *
1742  * @param column the search column
1743  * @return the index of the column
1744  *
1745  * @exception IllegalArgumentException <ul>
1746  *    <li>ERROR_NULL_ARGUMENT - if the column is null</li>
1747  * </ul>
1748  * @exception SWTException <ul>
1749  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1750  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1751  * </ul>
1752  */
1753 public int indexOf (CTableColumn column) {
1754 	checkWidget ();
1755 	if (column == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
1756 	if (column.parent != this) return -1;
1757 	return column.getIndex ();
1758 }
1759 /**
1760  * Searches the receiver's list starting at the first item
1761  * (index 0) until an item is found that is equal to the
1762  * argument, and returns the index of that item. If no item
1763  * is found, returns -1.
1764  *
1765  * @param item the search item
1766  * @return the index of the item
1767  *
1768  * @exception IllegalArgumentException <ul>
1769  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
1770  * </ul>
1771  * @exception SWTException <ul>
1772  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1773  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1774  * </ul>
1775  */
1776 public int indexOf (CTableItem item) {
1777 	checkWidget ();
1778 	if (item == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
1779 	if (item.parent != this) return -1;
1780 	return item.index;
1781 }
1782 
1783 void initAccessibility () {
1784 	// TODO: does this all work if CTable is virtual?
1785 	final Accessible accessibleTable = getAccessible();
1786 	accessibleTable.addAccessibleListener(new AccessibleAdapter() {
1787 		@Override
1788 		public void getName(AccessibleEvent e) {
1789 			/* CTables take their name from the preceding Label, if any. */
1790 			Control[] siblings = getParent().getChildren();
1791 			for (int i = 0; i < siblings.length; i++) {
1792 				if (i != 0 && siblings[i] == CTable.this) {
1793 					Control control = siblings[i-1];
1794 					if (control instanceof Label) {
1795 						e.result = ((Label) control).getText();
1796 					}
1797 				}
1798 			}
1799 		}
1800 		@Override
1801 		public void getHelp(AccessibleEvent e) {
1802 			/* A CTable's toolTip text (if any) can be used as its help text. */
1803 			e.result = getToolTipText();
1804 		}
1805 	});
1806 	accessibleTable.addAccessibleControlListener(new AccessibleControlAdapter() {
1807 		/* Child IDs are assigned as follows:
1808 		 * - column header ids (if any) are numbered from 0 to columnCount - 1
1809 		 * - cell ids are numbered in row-major order (starting from columnCount if there are columns)
1810 		 * Accessibles are returned in getChild.
1811 		 */
1812 		@Override
1813 		public void getChild(AccessibleControlEvent e) {
1814 			int childID = e.childID;
1815 			if (childID == ACC.CHILDID_CHILD_AT_INDEX) childID = e.detail; // childID == index
1816 			if (columns.length > 0 && 0 <= childID && childID < columns.length) { // header cell
1817 				CTableColumn column = columns [childID];
1818 				e.accessible = column.getAccessible(accessibleTable);
1819 			} else { // item cell
1820 				int columnCount = columns.length > 0 ? columns.length : 1;
1821 				if (columns.length > 0) childID -= columnCount;
1822 				if (0 <= childID && childID < itemsCount * columnCount) {
1823 					int rowIndex = childID / columnCount;
1824 					int columnIndex = childID - rowIndex * columnCount;
1825 					e.accessible = items[rowIndex].getAccessible (accessibleTable, columnIndex);
1826 				}
1827 			}
1828 		}
1829 		@Override
1830 		public void getChildAtPoint(AccessibleControlEvent e) {
1831 			Point point = toControl(e.x, e.y);
1832 			if (columns.length > 0 && point.y < getHeaderHeight ()) { // header cell
1833 				int columnIndex = computeColumnIntersect (point.x, 0);
1834 				if (columnIndex != -1) {
1835 					CTableColumn column = columns [columnIndex];
1836 					e.accessible = column.getAccessible (accessibleTable);
1837 				}
1838 			} else { // item cell
1839 				int columnIndex = columns.length > 0 ? computeColumnIntersect (point.x, 0) : 0;
1840 				if (columnIndex != -1) {
1841 					int rowIndex = (point.y - getHeaderHeight ()) / itemHeight + topIndex;
1842 					if (0 <= rowIndex && rowIndex < itemsCount) {
1843 						if (items [rowIndex].getHitBounds ().contains (point)) {  /* considers the x value */
1844 							e.accessible = items[rowIndex].getAccessible (accessibleTable, columnIndex);
1845 						}
1846 					}
1847 				}
1848 			}
1849 		}
1850 		@Override
1851 		public void getChildCount(AccessibleControlEvent e) {
1852 			e.detail = columns.length > 0 ? columns.length + itemsCount * columns.length : itemsCount;
1853 		}
1854 		@Override
1855 		public void getChildren(AccessibleControlEvent e) {
1856 			int childIdCount = columns.length > 0 ? columns.length + itemsCount * columns.length : itemsCount;
1857 			Object[] children = new Object[childIdCount];
1858 			for (int i = 0; i < childIdCount; i++) {
1859 				children[i] = Integer.valueOf(i);
1860 			}
1861 			e.children = children;
1862 		}
1863 		@Override
1864 		public void getFocus(AccessibleControlEvent e) {
1865 			e.childID = isFocusControl() ? ACC.CHILDID_SELF : ACC.CHILDID_NONE;
1866 		}
1867 		@Override
1868 		public void getLocation(AccessibleControlEvent e) {
1869 			Rectangle location = null;
1870 			Point pt = null;
1871 			int childID = e.childID;
1872 			if (childID == ACC.CHILDID_SELF) { // table
1873 				location = getBounds();
1874 				pt = getParent().toDisplay(location.x, location.y);
1875 			} else if (columns.length > 0 && 0 <= childID && childID < columns.length) { // header cell
1876 				CTableColumn column = columns [childID];
1877 				location = new Rectangle (column.getX (), 0, column.getWidth(), getHeaderHeight());
1878 				pt = toDisplay(location.x, location.y);
1879 			} else { // item cell
1880 				int columnCount = columns.length > 0 ? columns.length : 1;
1881 				if (columns.length > 0) childID -= columnCount;
1882 				if (0 <= childID && childID < itemsCount * columnCount) {
1883 					int rowIndex = childID / columnCount;
1884 					int columnIndex = childID - rowIndex * columnCount;
1885 					location = items[rowIndex].getBounds(columnIndex);
1886 					pt = toDisplay(location.x, location.y);
1887 				}
1888 			}
1889 			if (location != null && pt != null) {
1890 				e.x = pt.x;
1891 				e.y = pt.y;
1892 				e.width = location.width;
1893 				e.height = location.height;
1894 			}
1895 		}
1896 		@Override
1897 		public void getRole(AccessibleControlEvent e) {
1898 			e.detail = e.childID == ACC.CHILDID_SELF ? ACC.ROLE_TABLE : ACC.ROLE_TABLECELL;
1899 		}
1900 		@Override
1901 		public void getSelection(AccessibleControlEvent e) {
1902 			int columnCount = columns.length > 0 ? columns.length : 1;
1903 			int [] selectionIndices = getSelectionIndices();
1904 			Object[] selectedChildren = new Object[selectionIndices.length * columnCount];
1905 			for (int i = 0; i < selectionIndices.length; i++) {
1906 				int row = selectionIndices[i];
1907 				for (int col = 0; col < columnCount; col++) {
1908 					selectedChildren[i] = Integer.valueOf(row * columnCount + col);
1909 				}
1910 			}
1911 			e.children = selectedChildren;
1912 		}
1913 		@Override
1914 		public void getState(AccessibleControlEvent e) {
1915 			int state = ACC.STATE_NORMAL;
1916 			int childID = e.childID;
1917 			if (childID == ACC.CHILDID_SELF) { // table
1918 				state |= ACC.STATE_FOCUSABLE;
1919 				if (isFocusControl()) {
1920 					state |= ACC.STATE_FOCUSED;
1921 				}
1922 			} else if (columns.length > 0 && 0 <= childID && childID < columns.length) { // header cell
1923 				/* CTable does not support header cell focus or selection. */
1924 				state |= ACC.STATE_SIZEABLE;
1925 			} else { // item cell
1926 				int columnCount = columns.length > 0 ? columns.length : 1;
1927 				if (columns.length > 0) childID -= columnCount;
1928 				if (0 <= childID && childID < itemsCount * columnCount) {
1929 					/* CTable does not support cell selection (only row selection). */
1930 					int rowIndex = childID / columnCount;
1931 					state |= ACC.STATE_SELECTABLE;
1932 					if (isFocusControl()) {
1933 						state |= ACC.STATE_FOCUSABLE;
1934 					}
1935 					if (items[rowIndex].isSelected()) {
1936 						state |= ACC.STATE_SELECTED;
1937 						if (isFocusControl()) {
1938 							state |= ACC.STATE_FOCUSED;
1939 						}
1940 					}
1941 				}
1942 			}
1943 			e.detail = state;
1944 		}
1945 	});
1946 	accessibleTable.addAccessibleTableListener(new AccessibleTableAdapter() {
1947 		@Override
1948 		public void deselectColumn(AccessibleTableEvent e) {
1949 			/* CTable does not support column selection. */
1950 		}
1951 		@Override
1952 		public void deselectRow(AccessibleTableEvent e) {
1953 			deselect(e.row);
1954 			e.result = ACC.OK;
1955 		}
1956 		@Override
1957 		public void getCaption(AccessibleTableEvent e) {
1958 			// TODO: What is a caption? How does it differ from name? Should app supply?
1959 			e.result = "This is the Custom Table's Test Caption";
1960 		}
1961 		@Override
1962 		public void getCell(AccessibleTableEvent e) {
1963 			int index = e.row;
1964 			if (0 <= index && index < itemsCount) {
1965 				CTableItem row = items [index];
1966 				index = e.column;
1967 				if (columns.length == 0 || 0 <= index && index < columns.length) {
1968 					e.accessible = row.getAccessible (accessibleTable, index);
1969 				}
1970 			}
1971 		}
1972 		@Override
1973 		public void getColumnCount(AccessibleTableEvent e) {
1974 			int columnCount = columns.length > 0 ? columns.length : 1;
1975 			e.count = columnCount;
1976 		}
1977 		@Override
1978 		public void getColumnDescription(AccessibleTableEvent e) {
1979 			// TODO: What is a description? How does it differ from name? Should app supply?
1980 			e.result = "This is the Custom Table's Test Description for column " + e.column;
1981 		}
1982 //		public void getColumnHeader(AccessibleTableEvent e) {
1983 //			e.accessible = header.getAccessible();
1984 //		}
1985 		@Override
1986 		public void getColumnHeaderCells(AccessibleTableEvent e) {
1987 			if (columns.length == 0) {
1988 				/* The CTable is being used as a list, and there are no headers. */
1989 				e.accessibles = null;
1990 			} else {
1991 				Accessible[] accessibles = new Accessible[columns.length];
1992 				for (int i = 0; i < columns.length; i++) {
1993 					CTableColumn column = columns [i];
1994 					accessibles[i] = column.getAccessible (accessibleTable);
1995 				}
1996 				e.accessibles = accessibles;
1997 			}
1998 		}
1999 		@Override
2000 		public void getRowCount(AccessibleTableEvent e) {
2001 			e.count = itemsCount;
2002 		}
2003 		@Override
2004 		public void getRowDescription(AccessibleTableEvent e) {
2005 			// TODO: What is a description? How does it differ from name? Should app supply?
2006 			e.result = "This is the Custom Table's Test Description for row " + e.row;
2007 		}
2008 		@Override
2009 		public void getRowHeader(AccessibleTableEvent e) {
2010 			/* CTable does not support row headers. */
2011 		}
2012 		@Override
2013 		public void getSelectedCellCount(AccessibleTableEvent e) {
2014 			int columnCount = columns.length > 0 ? columns.length : 1;
2015 			e.count = selectedItems.length * columnCount;
2016 		}
2017 		@Override
2018 		public void getSelectedCells(AccessibleTableEvent e) {
2019 			int columnCount = columns.length > 0 ? columns.length : 1;
2020 			Accessible[] accessibles = new Accessible[selectedItems.length * columnCount];
2021 			for (int r = 0; r < selectedItems.length; r++) {
2022 				CTableItem row = selectedItems [r];
2023 				for (int c = 0; c < columnCount; c++)
2024 					accessibles[r+c] = row.getAccessible (accessibleTable, c);
2025 			}
2026 			e.accessibles = accessibles;
2027 		}
2028 		@Override
2029 		public void getSelectedColumnCount(AccessibleTableEvent e) {
2030 			e.count = 0; /* CTable does not support column selection. */
2031 		}
2032 		@Override
2033 		public void getSelectedColumns(AccessibleTableEvent e) {
2034 			/* CTable does not support column selection. */
2035 		}
2036 		@Override
2037 		public void getSelectedRowCount(AccessibleTableEvent e) {
2038 			e.count = selectedItems.length;
2039 		}
2040 		@Override
2041 		public void getSelectedRows(AccessibleTableEvent e) {
2042 			int[] selectedIndices = new int[selectedItems.length];
2043 			for (int i = 0; i < selectedItems.length; i++) {
2044 				selectedIndices[i] = selectedItems [i].index;
2045 			}
2046 			e.selected = selectedIndices;
2047 		}
2048 		@Override
2049 		public void getSummary(AccessibleTableEvent e) {
2050 			// TODO: What is a summary? How does it differ from name? Should app supply?
2051 			e.result = "This is the Custom Table's Summary";
2052 		}
2053 		@Override
2054 		public void isColumnSelected(AccessibleTableEvent e) {
2055 			e.isSelected = false; /* CTable does not support column selection. */
2056 		}
2057 		@Override
2058 		public void isRowSelected(AccessibleTableEvent e) {
2059 			e.isSelected = isSelected(e.row);
2060 		}
2061 		@Override
2062 		public void selectColumn(AccessibleTableEvent e) {
2063 			/* CTable does not support column selection. */
2064 		}
2065 		@Override
2066 		public void selectRow(AccessibleTableEvent e) {
2067 			select(e.row);
2068 			e.result = ACC.OK;
2069 		}
2070 		@Override
2071 		public void setSelectedColumn(AccessibleTableEvent e) {
2072 			/* CTable does not support column selection. */
2073 		}
2074 		@Override
2075 		public void setSelectedRow(AccessibleTableEvent e) {
2076 			setSelection(e.row);
2077 			e.result = ACC.OK;
2078 		}
2079 	});
2080 }
2081 
2082 static void initImages (final Display display) {
2083 	PaletteData arrowPalette = new PaletteData (new RGB (0, 0, 0), new RGB (255, 255, 255));
2084 	if (display.getData (ID_ARROWDOWN) == null) {
2085 		ImageData arrowDown = new ImageData (
2086 			7, 4, 1,
2087 			arrowPalette, 1,
2088 			new byte[] {0x00, (byte)0x83, (byte)0xC7, (byte)0xEF});
2089 		arrowDown.transparentPixel = 0x1;	/* use white for transparency */
2090 		display.setData (ID_ARROWDOWN, new Image (display, arrowDown));
2091 	}
2092 	if (display.getData (ID_ARROWUP) == null) {
2093 		ImageData arrowUp = new ImageData (
2094 			7, 4, 1,
2095 			arrowPalette, 1,
2096 			new byte[] {(byte)0xEF, (byte)0xC7, (byte)0x83, 0x00});
2097 		arrowUp.transparentPixel = 0x1;		/* use white for transparency */
2098 		display.setData (ID_ARROWUP, new Image (display, arrowUp));
2099 	}
2100 
2101 	PaletteData checkMarkPalette = new PaletteData (new RGB (0, 0, 0), new RGB (252, 3, 251));
2102 	byte[] checkbox = new byte[] {0, 0, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 0, 0};
2103 	ImageData checkmark = new ImageData (7, 7, 1, checkMarkPalette, 1, new byte[] {-4, -8, 112, 34, 6, -114, -34});
2104 	checkmark.transparentPixel = 1;
2105 	if (display.getData (ID_CHECKMARK) == null) {
2106 		display.setData (ID_CHECKMARK, new Image (display, checkmark));
2107 	}
2108 
2109 	if (display.getData (ID_UNCHECKED) == null) {
2110 		PaletteData uncheckedPalette = new PaletteData (new RGB (128, 128, 128), new RGB (255, 255, 255));
2111 		ImageData unchecked = new ImageData (11, 11, 1, uncheckedPalette, 2, checkbox);
2112 		display.setData (ID_UNCHECKED, new Image (display, unchecked));
2113 	}
2114 
2115 	if (display.getData (ID_GRAYUNCHECKED) == null) {
2116 		PaletteData grayUncheckedPalette = new PaletteData (new RGB (128, 128, 128), new RGB (192, 192, 192));
2117 		ImageData grayUnchecked = new ImageData (11, 11, 1, grayUncheckedPalette, 2, checkbox);
2118 		display.setData (ID_GRAYUNCHECKED, new Image (display, grayUnchecked));
2119 	}
2120 
2121 	display.disposeExec (() -> {
2122 		Image unchecked = (Image) display.getData (ID_UNCHECKED);
2123 		if (unchecked != null) unchecked.dispose ();
2124 		Image grayUnchecked = (Image) display.getData (ID_GRAYUNCHECKED);
2125 		if (grayUnchecked != null) grayUnchecked.dispose ();
2126 		Image checkmark1 = (Image) display.getData (ID_CHECKMARK);
2127 		if (checkmark1 != null) checkmark1.dispose ();
2128 		Image arrowDown = (Image) display.getData (ID_ARROWDOWN);
2129 		if (arrowDown != null) arrowDown.dispose ();
2130 		Image arrowUp = (Image) display.getData (ID_ARROWUP);
2131 		if (arrowUp != null) arrowUp.dispose ();
2132 
2133 		display.setData (ID_UNCHECKED, null);
2134 		display.setData (ID_GRAYUNCHECKED, null);
2135 		display.setData (ID_CHECKMARK, null);
2136 		display.setData (ID_ARROWDOWN, null);
2137 		display.setData (ID_ARROWUP, null);
2138 	});
2139 }
2140 /**
2141  * Returns <code>true</code> if the item is selected,
2142  * and <code>false</code> otherwise.  Indices out of
2143  * range are ignored.
2144  *
2145  * @param index the index of the item
2146  * @return the selection state of the item at the index
2147  *
2148  * @exception SWTException <ul>
2149  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2150  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2151  * </ul>
2152  */
2153 public boolean isSelected (int index) {
2154 	checkWidget ();
2155 	if (!(0 <= index && index < itemsCount)) return false;
2156 	return items [index].isSelected ();
2157 }
2158 @Override
2159 public void notifyListeners (int eventType, Event event) {
2160 	super.notifyListeners(eventType, event);
2161 	if (eventType == SWT.Selection && event.detail != SWT.CHECK) getAccessible().selectionChanged();
2162 }
2163 void onArrowDown (int stateMask) {
2164 	if ((stateMask & (SWT.SHIFT | SWT.CTRL)) == 0) {
2165 		/* Down Arrow with no modifiers */
2166 		int newFocusIndex = focusItem.index + 1;
2167 		if (newFocusIndex == itemsCount) return; 	/* at bottom */
2168 		selectItem (items [newFocusIndex], false);
2169 		setFocusItem (items [newFocusIndex], true);
2170 		redrawItem (newFocusIndex, true);
2171 		showItem (items [newFocusIndex]);
2172 		Event newEvent = new Event ();
2173 		newEvent.item = items [newFocusIndex];
2174 		notifyListeners (SWT.Selection, newEvent);
2175 		return;
2176 	}
2177 	if ((getStyle () & SWT.SINGLE) != 0) {
2178 		if ((stateMask & SWT.CTRL) != 0) {
2179 			/* CTRL+Down Arrow, CTRL+Shift+Down Arrow */
2180 			int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
2181 			if (itemsCount <= topIndex + visibleItemCount) return;	/* at bottom */
2182 			update ();
2183 			topIndex++;
2184 			ScrollBar vBar = getVerticalBar ();
2185 			if (vBar != null) vBar.setSelection (topIndex);
2186 			GC gc = new GC (this);
2187 			gc.copyArea (
2188 				0, 0,
2189 				clientArea.width, clientArea.height,
2190 				0, -itemHeight);
2191 			gc.dispose ();
2192 			return;
2193 		}
2194 		/* Shift+Down Arrow */
2195 		int newFocusIndex = focusItem.index + 1;
2196 		if (newFocusIndex == itemsCount) return; 	/* at bottom */
2197 		selectItem (items [newFocusIndex], false);
2198 		setFocusItem (items [newFocusIndex], true);
2199 		redrawItem (newFocusIndex, true);
2200 		showItem (items [newFocusIndex]);
2201 		Event newEvent = new Event ();
2202 		newEvent.item = items [newFocusIndex];
2203 		notifyListeners (SWT.Selection, newEvent);
2204 		return;
2205 	}
2206 	/* SWT.MULTI */
2207 	if ((stateMask & SWT.CTRL) != 0) {
2208 		if ((stateMask & SWT.SHIFT) != 0) {
2209 			/* CTRL+Shift+Down Arrow */
2210 			int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
2211 			if (itemsCount <= topIndex + visibleItemCount) return;	/* at bottom */
2212 			update ();
2213 			topIndex++;
2214 			ScrollBar vBar = getVerticalBar ();
2215 			if (vBar != null) vBar.setSelection (topIndex);
2216 			GC gc = new GC (this);
2217 			gc.copyArea (
2218 				0, 0,
2219 				clientArea.width, clientArea.height,
2220 				0, -itemHeight);
2221 			gc.dispose ();
2222 			return;
2223 		}
2224 		/* CTRL+Down Arrow */
2225 		int focusIndex = focusItem.index;
2226 		if (focusIndex == itemsCount - 1) return;	/* at bottom */
2227 		CTableItem newFocusItem = items [focusIndex + 1];
2228 		setFocusItem (newFocusItem, true);
2229 		redrawItem (newFocusItem.index, true);
2230 		showItem (newFocusItem);
2231 		return;
2232 	}
2233 	/* Shift+Down Arrow */
2234 	int newFocusIndex = focusItem.index + 1;
2235 	if (newFocusIndex == itemsCount) return; 	/* at bottom */
2236 	if (anchorItem == null) anchorItem = focusItem;
2237 	if (focusItem.index < anchorItem.index) {
2238 		deselectItem (focusItem);
2239 		redrawItem (focusItem.index, true);
2240 	}
2241 	selectItem (items [newFocusIndex], true);
2242 	setFocusItem (items [newFocusIndex], true);
2243 	redrawItem (newFocusIndex, true);
2244 	showItem (items [newFocusIndex]);
2245 	Event newEvent = new Event ();
2246 	newEvent.item = items [newFocusIndex];
2247 	notifyListeners (SWT.Selection, newEvent);
2248 }
2249 void onArrowLeft (int stateMask) {
2250 	if (horizontalOffset == 0) return;
2251 	int newSelection = Math.max (0, horizontalOffset - SIZE_HORIZONTALSCROLL);
2252 	update ();
2253 	GC gc = new GC (this);
2254 	gc.copyArea (
2255 		0, 0,
2256 		clientArea.width, clientArea.height,
2257 		horizontalOffset - newSelection, 0);
2258 	gc.dispose ();
2259 	if (header.getVisible ()) {
2260 		header.update ();
2261 		Rectangle headerClientArea = header.getClientArea ();
2262 		gc = new GC (header);
2263 		gc.copyArea (
2264 			0, 0,
2265 			headerClientArea.width, headerClientArea.height,
2266 			horizontalOffset - newSelection, 0);
2267 		gc.dispose();
2268 	}
2269 	horizontalOffset = newSelection;
2270 	ScrollBar hBar = getHorizontalBar ();
2271 	if (hBar != null) hBar.setSelection (horizontalOffset);
2272 }
2273 void onArrowRight (int stateMask) {
2274 	ScrollBar hBar = getHorizontalBar ();
2275 	if (hBar == null) return;
2276 	int maximum = hBar.getMaximum ();
2277 	int clientWidth = clientArea.width;
2278 	if ((horizontalOffset + clientArea.width) == maximum) return;
2279 	if (maximum <= clientWidth) return;
2280 	int newSelection = Math.min (horizontalOffset + SIZE_HORIZONTALSCROLL, maximum - clientWidth);
2281 	update ();
2282 	GC gc = new GC (this);
2283 	gc.copyArea (
2284 		0, 0,
2285 		clientArea.width, clientArea.height,
2286 		horizontalOffset - newSelection, 0);
2287 	gc.dispose ();
2288 	if (header.getVisible ()) {
2289 		Rectangle headerClientArea = header.getClientArea ();
2290 		header.update ();
2291 		gc = new GC (header);
2292 		gc.copyArea (
2293 			0, 0,
2294 			headerClientArea.width, headerClientArea.height,
2295 			horizontalOffset - newSelection, 0);
2296 		gc.dispose();
2297 	}
2298 	horizontalOffset = newSelection;
2299 	hBar.setSelection (horizontalOffset);
2300 }
2301 void onArrowUp (int stateMask) {
2302 	if ((stateMask & (SWT.SHIFT | SWT.CTRL)) == 0) {
2303 		/* Up Arrow with no modifiers */
2304 		int newFocusIndex = focusItem.index - 1;
2305 		if (newFocusIndex < 0) return; 		/* at top */
2306 		CTableItem item = items [newFocusIndex];
2307 		selectItem (item, false);
2308 		setFocusItem (item, true);
2309 		redrawItem (newFocusIndex, true);
2310 		showItem (item);
2311 		Event newEvent = new Event ();
2312 		newEvent.item = item;
2313 		notifyListeners (SWT.Selection, newEvent);
2314 		return;
2315 	}
2316 	if ((getStyle () & SWT.SINGLE) != 0) {
2317 		if ((stateMask & SWT.CTRL) != 0) {
2318 			/* CTRL+Up Arrow, CTRL+Shift+Up Arrow */
2319 			if (topIndex == 0) return;	/* at top */
2320 			update ();
2321 			topIndex--;
2322 			ScrollBar vBar = getVerticalBar ();
2323 			if (vBar != null) vBar.setSelection (topIndex);
2324 			GC gc = new GC (this);
2325 			gc.copyArea (
2326 				0, 0,
2327 				clientArea.width, clientArea.height,
2328 				0, itemHeight);
2329 			gc.dispose ();
2330 			return;
2331 		}
2332 		/* Shift+Up Arrow */
2333 		int newFocusIndex = focusItem.index - 1;
2334 		if (newFocusIndex < 0) return; 	/* at top */
2335 		CTableItem item = items [newFocusIndex];
2336 		selectItem (item, false);
2337 		setFocusItem (item, true);
2338 		redrawItem (newFocusIndex, true);
2339 		showItem (item);
2340 		Event newEvent = new Event ();
2341 		newEvent.item = item;
2342 		notifyListeners (SWT.Selection, newEvent);
2343 		return;
2344 	}
2345 	/* SWT.MULTI */
2346 	if ((stateMask & SWT.CTRL) != 0) {
2347 		if ((stateMask & SWT.SHIFT) != 0) {
2348 			/* CTRL+Shift+Up Arrow */
2349 			if (topIndex == 0) return;	/* at top */
2350 			update ();
2351 			topIndex--;
2352 			ScrollBar vBar = getVerticalBar ();
2353 			if (vBar != null) vBar.setSelection (topIndex);
2354 			GC gc = new GC (this);
2355 			gc.copyArea (
2356 				0, 0,
2357 				clientArea.width, clientArea.height,
2358 				0, itemHeight);
2359 			gc.dispose ();
2360 			return;
2361 		}
2362 		/* CTRL+Up Arrow */
2363 		int focusIndex = focusItem.index;
2364 		if (focusIndex == 0) return;	/* at top */
2365 		CTableItem newFocusItem = items [focusIndex - 1];
2366 		setFocusItem (newFocusItem, true);
2367 		showItem (newFocusItem);
2368 		redrawItem (newFocusItem.index, true);
2369 		return;
2370 	}
2371 	/* Shift+Up Arrow */
2372 	int newFocusIndex = focusItem.index - 1;
2373 	if (newFocusIndex < 0) return; 		/* at top */
2374 	if (anchorItem == null) anchorItem = focusItem;
2375 	if (anchorItem.index < focusItem.index) {
2376 		deselectItem (focusItem);
2377 		redrawItem (focusItem.index, true);
2378 	}
2379 	CTableItem item = items [newFocusIndex];
2380 	selectItem (item, true);
2381 	setFocusItem (item, true);
2382 	redrawItem (newFocusIndex, true);
2383 	showItem (item);
2384 	Event newEvent = new Event ();
2385 	newEvent.item = item;
2386 	notifyListeners (SWT.Selection, newEvent);
2387 }
2388 void onCR () {
2389 	if (focusItem == null) return;
2390 	Event event = new Event ();
2391 	event.item = focusItem;
2392 	notifyListeners (SWT.DefaultSelection, event);
2393 }
2394 void onDispose (Event event) {
2395 	if (isDisposed ()) return;
2396 	if (ignoreDispose) return;
2397 	ignoreDispose = true;
2398 	notifyListeners(SWT.Dispose, event);
2399 	event.type = SWT.None;
2400 	for (int i = 0; i < itemsCount; i++) {
2401 		items [i].dispose (false);
2402 	}
2403 	for (CTableColumn column : columns) {
2404 		column.dispose (false);
2405 	}
2406 	if (toolTipShell != null) {
2407 		toolTipShell.dispose ();
2408 		toolTipShell = null;
2409 		toolTipLabel = null;
2410 	}
2411 	toolTipListener = null;
2412 	itemsCount = topIndex = horizontalOffset = 0;
2413 	items = selectedItems = null;
2414 	columns = orderedColumns = null;
2415 	focusItem = anchorItem = lastClickedItem = null;
2416 	lastSelectionEvent = null;
2417 	header = null;
2418 	resizeColumn = sortColumn = null;
2419 }
2420 void onEnd (int stateMask) {
2421 	int lastAvailableIndex = itemsCount - 1;
2422 	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
2423 		/* End with no modifiers */
2424 		if (focusItem.index == lastAvailableIndex) return; 	/* at bottom */
2425 		CTableItem item = items [lastAvailableIndex];
2426 		selectItem (item, false);
2427 		setFocusItem (item, true);
2428 		redrawItem (lastAvailableIndex, true);
2429 		showItem (item);
2430 		Event newEvent = new Event ();
2431 		newEvent.item = item;
2432 		notifyListeners (SWT.Selection, newEvent);
2433 		return;
2434 	}
2435 	if ((getStyle () & SWT.SINGLE) != 0) {
2436 		if ((stateMask & SWT.CTRL) != 0) {
2437 			/* CTRL+End, CTRL+Shift+End */
2438 			int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
2439 			setTopIndex (itemsCount - visibleItemCount);
2440 			return;
2441 		}
2442 		/* Shift+End */
2443 		if (focusItem.index == lastAvailableIndex) return; /* at bottom */
2444 		CTableItem item = items [lastAvailableIndex];
2445 		selectItem (item, false);
2446 		setFocusItem (item, true);
2447 		redrawItem (lastAvailableIndex, true);
2448 		showItem (item);
2449 		Event newEvent = new Event ();
2450 		newEvent.item = item;
2451 		notifyListeners (SWT.Selection, newEvent);
2452 		return;
2453 	}
2454 	/* SWT.MULTI */
2455 	if ((stateMask & SWT.CTRL) != 0) {
2456 		if ((stateMask & SWT.SHIFT) != 0) {
2457 			/* CTRL+Shift+End */
2458 			showItem (items [lastAvailableIndex]);
2459 			return;
2460 		}
2461 		/* CTRL+End */
2462 		if (focusItem.index == lastAvailableIndex) return; /* at bottom */
2463 		CTableItem item = items [lastAvailableIndex];
2464 		setFocusItem (item, true);
2465 		showItem (item);
2466 		redrawItem (item.index, true);
2467 		return;
2468 	}
2469 	/* Shift+End */
2470 	if (anchorItem == null) anchorItem = focusItem;
2471 	CTableItem selectedItem = items [lastAvailableIndex];
2472 	if (selectedItem == focusItem && selectedItem.isSelected ()) return;
2473 	int anchorIndex = anchorItem.index;
2474 	int selectIndex = selectedItem.index;
2475 	CTableItem[] newSelection = new CTableItem [selectIndex - anchorIndex + 1];
2476 	int writeIndex = 0;
2477 	for (int i = anchorIndex; i <= selectIndex; i++) {
2478 		newSelection [writeIndex++] = items [i];
2479 	}
2480 	setSelection (newSelection, false);
2481 	setFocusItem (selectedItem, true);
2482 	redrawItems (anchorIndex, selectIndex, true);
2483 	showItem (selectedItem);
2484 	Event newEvent = new Event ();
2485 	newEvent.item = selectedItem;
2486 	notifyListeners (SWT.Selection, newEvent);
2487 }
2488 void onFocusIn () {
2489 	hasFocus = true;
2490 	if (itemsCount == 0) {
2491 		redraw ();
2492 		return;
2493 	}
2494 	if ((getStyle () & (SWT.HIDE_SELECTION | SWT.MULTI)) == (SWT.HIDE_SELECTION | SWT.MULTI)) {
2495 		for (CTableItem selectedItem : selectedItems) {
2496 			redrawItem (selectedItem.index, true);
2497 		}
2498 	}
2499 	if (focusItem != null) {
2500 		redrawItem (focusItem.index, true);
2501 		return;
2502 	}
2503 	/* an initial focus item must be selected */
2504 	CTableItem initialFocus;
2505 	if (selectedItems.length > 0) {
2506 		initialFocus = selectedItems [0];
2507 	} else {
2508 		initialFocus = items [topIndex];
2509 	}
2510 	setFocusItem (initialFocus, false);
2511 	redrawItem (initialFocus.index, true);
2512 	return;
2513 }
2514 void onFocusOut () {
2515 	hasFocus = false;
2516 	if (itemsCount == 0) {
2517 		redraw ();
2518 		return;
2519 	}
2520 	if (focusItem != null) {
2521 		redrawItem (focusItem.index, true);
2522 	}
2523 	if ((getStyle () & (SWT.HIDE_SELECTION | SWT.MULTI)) == (SWT.HIDE_SELECTION | SWT.MULTI)) {
2524 		for (CTableItem selectedItem : selectedItems) {
2525 			redrawItem (selectedItem.index, true);
2526 		}
2527 	}
2528 }
2529 void onHome (int stateMask) {
2530 	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
2531 		/* Home with no modifiers */
2532 		if (focusItem.index == 0) return; 		/* at top */
2533 		CTableItem item = items [0];
2534 		selectItem (item, false);
2535 		setFocusItem (item, true);
2536 		redrawItem (0, true);
2537 		showItem (item);
2538 		Event newEvent = new Event ();
2539 		newEvent.item = item;
2540 		notifyListeners (SWT.Selection, newEvent);
2541 		return;
2542 	}
2543 	if ((getStyle () & SWT.SINGLE) != 0) {
2544 		if ((stateMask & SWT.CTRL) != 0) {
2545 			/* CTRL+Home, CTRL+Shift+Home */
2546 			setTopIndex (0);
2547 			return;
2548 		}
2549 		/* Shift+Home */
2550 		if (focusItem.index == 0) return; 		/* at top */
2551 		CTableItem item = items [0];
2552 		selectItem (item, false);
2553 		setFocusItem (item, true);
2554 		redrawItem (0, true);
2555 		showItem (item);
2556 		Event newEvent = new Event ();
2557 		newEvent.item = item;
2558 		notifyListeners (SWT.Selection, newEvent);
2559 		return;
2560 	}
2561 	/* SWT.MULTI */
2562 	if ((stateMask & SWT.CTRL) != 0) {
2563 		if ((stateMask & SWT.SHIFT) != 0) {
2564 			/* CTRL+Shift+Home */
2565 			setTopIndex (0);
2566 			return;
2567 		}
2568 		/* CTRL+Home */
2569 		if (focusItem.index == 0) return; /* at top */
2570 		CTableItem item = items [0];
2571 		setFocusItem (item, true);
2572 		showItem (item);
2573 		redrawItem (item.index, true);
2574 		return;
2575 	}
2576 	/* Shift+Home */
2577 	if (anchorItem == null) anchorItem = focusItem;
2578 	CTableItem selectedItem = items [0];
2579 	if (selectedItem == focusItem && selectedItem.isSelected ()) return;
2580 	int anchorIndex = anchorItem.index;
2581 	int selectIndex = selectedItem.index;
2582 	CTableItem[] newSelection = new CTableItem [anchorIndex + 1];
2583 	int writeIndex = 0;
2584 	for (int i = anchorIndex; i >= 0; i--) {
2585 		newSelection [writeIndex++] = items [i];
2586 	}
2587 	setSelection (newSelection, false);
2588 	setFocusItem (selectedItem, true);
2589 	redrawItems (anchorIndex, selectIndex, true);
2590 	showItem (selectedItem);
2591 	Event newEvent = new Event ();
2592 	newEvent.item = selectedItem;
2593 	notifyListeners (SWT.Selection, newEvent);
2594 }
2595 void onKeyDown (Event event) {
2596 	if (ignoreKey) {
2597 		ignoreKey = false;
2598 		return;
2599 	}
2600 	ignoreKey = true;
2601 	notifyListeners (event.type, event);
2602 	event.type = SWT.None;
2603 	if (!event.doit) return;
2604 	if (focusItem == null) return;
2605 	if ((event.stateMask & SWT.SHIFT) == 0 && event.keyCode != SWT.SHIFT) {
2606 		anchorItem = null;
2607 	}
2608 	switch (event.keyCode) {
2609 		case SWT.ARROW_UP:
2610 			onArrowUp (event.stateMask);
2611 			return;
2612 		case SWT.ARROW_DOWN:
2613 			onArrowDown (event.stateMask);
2614 			return;
2615 		case SWT.ARROW_LEFT:
2616 			onArrowLeft (event.stateMask);
2617 			return;
2618 		case SWT.ARROW_RIGHT:
2619 			onArrowRight (event.stateMask);
2620 			return;
2621 		case SWT.PAGE_UP:
2622 			onPageUp (event.stateMask);
2623 			return;
2624 		case SWT.PAGE_DOWN:
2625 			onPageDown (event.stateMask);
2626 			return;
2627 		case SWT.HOME:
2628 			onHome (event.stateMask);
2629 			return;
2630 		case SWT.END:
2631 			onEnd (event.stateMask);
2632 			return;
2633 	}
2634 	if (event.character == ' ') {
2635 		onSpace ();
2636 		return;
2637 	}
2638 	if (event.character == SWT.CR) {
2639 		onCR ();
2640 		return;
2641 	}
2642 	if ((event.stateMask & SWT.CTRL) != 0) return;
2643 
2644 	int initialIndex = focusItem.index;
2645 	char character = Character.toLowerCase (event.character);
2646 	/* check available items from current focus item to bottom */
2647 	for (int i = initialIndex + 1; i < itemsCount; i++) {
2648 		CTableItem item = items [i];
2649 		String text = item.getText (0, false);
2650 		if (text.length () > 0) {
2651 			if (Character.toLowerCase (text.charAt (0)) == character) {
2652 				selectItem (item, false);
2653 				setFocusItem (item, true);
2654 				redrawItem (i, true);
2655 				showItem (item);
2656 				Event newEvent = new Event ();
2657 				newEvent.item = item;
2658 				notifyListeners (SWT.Selection, newEvent);
2659 				return;
2660 			}
2661 		}
2662 	}
2663 	/* check available items from top to current focus item */
2664 	for (int i = 0; i < initialIndex; i++) {
2665 		CTableItem item = items [i];
2666 		String text = item.getText (0, false);
2667 		if (text.length () > 0) {
2668 			if (Character.toLowerCase (text.charAt (0)) == character) {
2669 				selectItem (item, false);
2670 				setFocusItem (item, true);
2671 				redrawItem (i, true);
2672 				showItem (item);
2673 				Event newEvent = new Event ();
2674 				newEvent.item = item;
2675 				notifyListeners (SWT.Selection, newEvent);
2676 				return;
2677 			}
2678 		}
2679 	}
2680 }
2681 void onMouseDoubleClick (Event event) {
2682 	if (!isFocusControl ()) setFocus ();
2683 	int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex;
2684 	if  (!(0 <= index && index < itemsCount)) return;	/* not on an available item */
2685 	CTableItem selectedItem = items [index];
2686 
2687 	/*
2688 	 * If the two clicks of the double click did not occur over the same item then do not
2689 	 * consider this to be a default selection.
2690 	 */
2691 	if (selectedItem != lastClickedItem) return;
2692 
2693 	if (!selectedItem.getHitBounds ().contains (event.x, event.y)) return;	/* considers x */
2694 
2695 	Event newEvent = new Event ();
2696 	newEvent.item = selectedItem;
2697 	notifyListeners (SWT.DefaultSelection, newEvent);
2698 }
2699 void onMouseDown (Event event) {
2700 	if (!isFocusControl ()) forceFocus ();
2701 	int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex;
2702 	if (!(0 <= index && index < itemsCount)) return;	/* not on an available item */
2703 	CTableItem selectedItem = items [index];
2704 
2705 	/* if click was in checkbox */
2706 	if ((getStyle () & SWT.CHECK) != 0 && selectedItem.getCheckboxBounds ().contains (event.x, event.y)) {
2707 		if (event.button != 1) return;
2708 		selectedItem.setChecked (!selectedItem.checked);
2709 		Event newEvent = new Event ();
2710 		newEvent.item = selectedItem;
2711 		newEvent.detail = SWT.CHECK;
2712 		notifyListeners (SWT.Selection, newEvent);
2713 		return;
2714 	}
2715 
2716 	if (!selectedItem.getHitBounds ().contains (event.x, event.y)) return;
2717 
2718 	if ((event.stateMask & SWT.SHIFT) == 0 && event.keyCode != SWT.SHIFT) anchorItem = null;
2719 
2720 	boolean sendSelection = true;
2721 	/* Detect when this is the second click of a DefaultSelection and don't fire Selection */
2722 	if (lastSelectionEvent != null && lastSelectionEvent.item == selectedItem) {
2723 		if (event.time - lastSelectionEvent.time <= display.getDoubleClickTime ()) {
2724 			sendSelection = false;
2725 		} else {
2726 			lastSelectionEvent = event;
2727 			event.item = selectedItem;
2728 		}
2729 	} else {
2730 		lastSelectionEvent = event;
2731 		event.item = selectedItem;
2732 	}
2733 
2734 	if ((getStyle () & SWT.SINGLE) != 0) {
2735 		if (!selectedItem.isSelected ()) {
2736 			if (event.button == 1) {
2737 				selectItem (selectedItem, false);
2738 				setFocusItem (selectedItem, true);
2739 				redrawItem (selectedItem.index, true);
2740 				if (sendSelection) {
2741 					Event newEvent = new Event ();
2742 					newEvent.item = selectedItem;
2743 					notifyListeners (SWT.Selection, newEvent);
2744 				}
2745 				return;
2746 			}
2747 			if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
2748 				selectItem (selectedItem, false);
2749 				setFocusItem (selectedItem, true);
2750 				redrawItem (selectedItem.index, true);
2751 				if (sendSelection) {
2752 					Event newEvent = new Event ();
2753 					newEvent.item = selectedItem;
2754 					notifyListeners (SWT.Selection, newEvent);
2755 				}
2756 				return;
2757 			}
2758 		}
2759 		/* item is selected */
2760 		if (event.button == 1) {
2761 			/* fire a selection event, though the selection did not change */
2762 			if (sendSelection) {
2763 				Event newEvent = new Event ();
2764 				newEvent.item = selectedItem;
2765 				notifyListeners (SWT.Selection, newEvent);
2766 			}
2767 			return;
2768 		}
2769 	}
2770 	/* SWT.MULTI */
2771 	if (!selectedItem.isSelected ()) {
2772 		if (event.button == 1) {
2773 			if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == SWT.SHIFT) {
2774 				if (anchorItem == null) anchorItem = focusItem;
2775 				int anchorIndex = anchorItem.index;
2776 				int selectIndex = selectedItem.index;
2777 				CTableItem[] newSelection = new CTableItem [Math.abs (anchorIndex - selectIndex) + 1];
2778 				int step = anchorIndex < selectIndex ? 1 : -1;
2779 				int writeIndex = 0;
2780 				for (int i = anchorIndex; i != selectIndex; i += step) {
2781 					newSelection [writeIndex++] = items [i];
2782 				}
2783 				newSelection [writeIndex] = items [selectIndex];
2784 				setSelection (newSelection, false);
2785 				setFocusItem (selectedItem, true);
2786 				redrawItems (
2787 					Math.min (anchorIndex, selectIndex),
2788 					Math.max (anchorIndex, selectIndex),
2789 					true);
2790 				if (sendSelection) {
2791 					Event newEvent = new Event ();
2792 					newEvent.item = selectedItem;
2793 					notifyListeners (SWT.Selection, newEvent);
2794 				}
2795 				return;
2796 			}
2797 			selectItem (selectedItem, (event.stateMask & SWT.CTRL) != 0);
2798 			setFocusItem (selectedItem, true);
2799 			redrawItem (selectedItem.index, true);
2800 			if (sendSelection) {
2801 				Event newEvent = new Event ();
2802 				newEvent.item = selectedItem;
2803 				notifyListeners (SWT.Selection, newEvent);
2804 			}
2805 			return;
2806 		}
2807 		/* button 3 */
2808 		if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
2809 			selectItem (selectedItem, false);
2810 			setFocusItem (selectedItem, true);
2811 			redrawItem (selectedItem.index, true);
2812 			if (sendSelection) {
2813 				Event newEvent = new Event ();
2814 				newEvent.item = selectedItem;
2815 				notifyListeners (SWT.Selection, newEvent);
2816 			}
2817 			return;
2818 		}
2819 	}
2820 	/* item is selected */
2821 	if (event.button != 1) return;
2822 	if ((event.stateMask & SWT.CTRL) != 0) {
2823 		removeSelectedItem (getSelectionIndex (selectedItem));
2824 		setFocusItem (selectedItem, true);
2825 		redrawItem (selectedItem.index, true);
2826 		if (sendSelection) {
2827 			Event newEvent = new Event ();
2828 			newEvent.item = selectedItem;
2829 			notifyListeners (SWT.Selection, newEvent);
2830 		}
2831 		return;
2832 	}
2833 	if ((event.stateMask & SWT.SHIFT) != 0) {
2834 		if (anchorItem == null) anchorItem = focusItem;
2835 		int anchorIndex = anchorItem.index;
2836 		int selectIndex = selectedItem.index;
2837 		CTableItem[] newSelection = new CTableItem [Math.abs (anchorIndex - selectIndex) + 1];
2838 		int step = anchorIndex < selectIndex ? 1 : -1;
2839 		int writeIndex = 0;
2840 		for (int i = anchorIndex; i != selectIndex; i += step) {
2841 			newSelection [writeIndex++] = items [i];
2842 		}
2843 		newSelection [writeIndex] = items [selectIndex];
2844 		setSelection (newSelection, false);
2845 		setFocusItem (selectedItem, true);
2846 		redrawItems (
2847 			Math.min (anchorIndex, selectIndex),
2848 			Math.max (anchorIndex, selectIndex),
2849 			true);
2850 		if (sendSelection) {
2851 			Event newEvent = new Event ();
2852 			newEvent.item = selectedItem;
2853 			notifyListeners (SWT.Selection, newEvent);
2854 		}
2855 		return;
2856 	}
2857 	selectItem (selectedItem, false);
2858 	setFocusItem (selectedItem, true);
2859 	redrawItem (selectedItem.index, true);
2860 	if (sendSelection) {
2861 		Event newEvent = new Event ();
2862 		newEvent.item = selectedItem;
2863 		notifyListeners (SWT.Selection, newEvent);
2864 	}
2865 }
2866 void onMouseUp (Event event) {
2867 	int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex;
2868 	if (!(0 <= index && index < itemsCount)) return;	/* not on an available item */
2869 	lastClickedItem = items [index];
2870 }
2871 void onPageDown (int stateMask) {
2872 	int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
2873 	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
2874 		/* PageDown with no modifiers */
2875 		int newFocusIndex = focusItem.index + visibleItemCount - 1;
2876 		newFocusIndex = Math.min (newFocusIndex, itemsCount - 1);
2877 		if (newFocusIndex == focusItem.index) return;
2878 		CTableItem item = items [newFocusIndex];
2879 		selectItem (item, false);
2880 		setFocusItem (item, true);
2881 		showItem (item);
2882 		redrawItem (item.index, true);
2883 		return;
2884 	}
2885 	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == (SWT.CTRL | SWT.SHIFT)) {
2886 		/* CTRL+Shift+PageDown */
2887 		int newTopIndex = topIndex + visibleItemCount;
2888 		newTopIndex = Math.min (newTopIndex, itemsCount - visibleItemCount);
2889 		if (newTopIndex == topIndex) return;
2890 		setTopIndex (newTopIndex);
2891 		return;
2892 	}
2893 	if ((getStyle () & SWT.SINGLE) != 0) {
2894 		if ((stateMask & SWT.SHIFT) != 0) {
2895 			/* Shift+PageDown */
2896 			int newFocusIndex = focusItem.index + visibleItemCount - 1;
2897 			newFocusIndex = Math.min (newFocusIndex, itemsCount - 1);
2898 			if (newFocusIndex == focusItem.index) return;
2899 			CTableItem item = items [newFocusIndex];
2900 			selectItem (item, false);
2901 			setFocusItem (item, true);
2902 			showItem (item);
2903 			redrawItem (item.index, true);
2904 			return;
2905 		}
2906 		/* CTRL+PageDown */
2907 		int newTopIndex = topIndex + visibleItemCount;
2908 		newTopIndex = Math.min (newTopIndex, itemsCount - visibleItemCount);
2909 		if (newTopIndex == topIndex) return;
2910 		setTopIndex (newTopIndex);
2911 		return;
2912 	}
2913 	/* SWT.MULTI */
2914 	if ((stateMask & SWT.CTRL) != 0) {
2915 		/* CTRL+PageDown */
2916 		int bottomIndex = Math.min (topIndex + visibleItemCount - 1, itemsCount - 1);
2917 		if (focusItem.index != bottomIndex) {
2918 			/* move focus to bottom item in viewport */
2919 			setFocusItem (items [bottomIndex], true);
2920 			redrawItem (bottomIndex, true);
2921 		} else {
2922 			/* at bottom of viewport, so set focus to bottom item one page down */
2923 			int newFocusIndex = Math.min (itemsCount - 1, bottomIndex + visibleItemCount);
2924 			if (newFocusIndex == focusItem.index) return;
2925 			setFocusItem (items [newFocusIndex], true);
2926 			showItem (items [newFocusIndex]);
2927 			redrawItem (newFocusIndex, true);
2928 		}
2929 		return;
2930 	}
2931 	/* Shift+PageDown */
2932 	if (anchorItem == null) anchorItem = focusItem;
2933 	int anchorIndex = anchorItem.index;
2934 	int bottomIndex = Math.min (topIndex + visibleItemCount - 1, itemsCount - 1);
2935 	int selectIndex;
2936 	if (focusItem.index != bottomIndex) {
2937 		/* select from focus to bottom item in viewport */
2938 		selectIndex = bottomIndex;
2939 	} else {
2940 		/* already at bottom of viewport, so select to bottom of one page down */
2941 		selectIndex = Math.min (itemsCount - 1, bottomIndex + visibleItemCount);
2942 		if (selectIndex == focusItem.index && focusItem.isSelected ()) return;
2943 	}
2944 	CTableItem selectedItem = items [selectIndex];
2945 	CTableItem[] newSelection = new CTableItem [Math.abs (anchorIndex - selectIndex) + 1];
2946 	int step = anchorIndex < selectIndex ? 1 : -1;
2947 	int writeIndex = 0;
2948 	for (int i = anchorIndex; i != selectIndex; i += step) {
2949 		newSelection [writeIndex++] = items [i];
2950 	}
2951 	newSelection [writeIndex] = items [selectIndex];
2952 	setSelection (newSelection, false);
2953 	setFocusItem (selectedItem, true);
2954 	showItem (selectedItem);
2955 	Event newEvent = new Event ();
2956 	newEvent.item = selectedItem;
2957 	notifyListeners (SWT.Selection, newEvent);
2958 }
2959 void onPageUp (int stateMask) {
2960 	int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
2961 	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
2962 		/* PageUp with no modifiers */
2963 		int newFocusIndex = Math.max (0, focusItem.index - visibleItemCount + 1);
2964 		if (newFocusIndex == focusItem.index) return;
2965 		CTableItem item = items [newFocusIndex];
2966 		selectItem (item, false);
2967 		setFocusItem (item, true);
2968 		showItem (item);
2969 		redrawItem (item.index, true);
2970 		return;
2971 	}
2972 	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == (SWT.CTRL | SWT.SHIFT)) {
2973 		/* CTRL+Shift+PageUp */
2974 		int newTopIndex = Math.max (0, topIndex - visibleItemCount);
2975 		if (newTopIndex == topIndex) return;
2976 		setTopIndex (newTopIndex);
2977 		return;
2978 	}
2979 	if ((getStyle () & SWT.SINGLE) != 0) {
2980 		if ((stateMask & SWT.SHIFT) != 0) {
2981 			/* Shift+PageUp */
2982 			int newFocusIndex = Math.max (0, focusItem.index - visibleItemCount + 1);
2983 			if (newFocusIndex == focusItem.index) return;
2984 			CTableItem item = items [newFocusIndex];
2985 			selectItem (item, false);
2986 			setFocusItem (item, true);
2987 			showItem (item);
2988 			redrawItem (item.index, true);
2989 			return;
2990 		}
2991 		/* CTRL+PageUp */
2992 		int newTopIndex = Math.max (0, topIndex - visibleItemCount);
2993 		if (newTopIndex == topIndex) return;
2994 		setTopIndex (newTopIndex);
2995 		return;
2996 	}
2997 	/* SWT.MULTI */
2998 	if ((stateMask & SWT.CTRL) != 0) {
2999 		/* CTRL+PageUp */
3000 		if (focusItem.index != topIndex) {
3001 			/* move focus to top item in viewport */
3002 			setFocusItem (items [topIndex], true);
3003 			redrawItem (topIndex, true);
3004 		} else {
3005 			/* at top of viewport, so set focus to top item one page up */
3006 			int newFocusIndex = Math.max (0, focusItem.index - visibleItemCount);
3007 			if (newFocusIndex == focusItem.index) return;
3008 			setFocusItem (items [newFocusIndex], true);
3009 			showItem (items [newFocusIndex]);
3010 			redrawItem (newFocusIndex, true);
3011 		}
3012 		return;
3013 	}
3014 	/* Shift+PageUp */
3015 	if (anchorItem == null) anchorItem = focusItem;
3016 	int anchorIndex = anchorItem.index;
3017 	int selectIndex;
3018 	if (focusItem.index != topIndex) {
3019 		/* select from focus to top item in viewport */
3020 		selectIndex = topIndex;
3021 	} else {
3022 		/* already at top of viewport, so select to top of one page up */
3023 		selectIndex = Math.max (0, topIndex - visibleItemCount);
3024 		if (selectIndex == focusItem.index && focusItem.isSelected ()) return;
3025 	}
3026 	CTableItem selectedItem = items [selectIndex];
3027 	CTableItem[] newSelection = new CTableItem [Math.abs (anchorIndex - selectIndex) + 1];
3028 	int step = anchorIndex < selectIndex ? 1 : -1;
3029 	int writeIndex = 0;
3030 	for (int i = anchorIndex; i != selectIndex; i += step) {
3031 		newSelection [writeIndex++] = items [i];
3032 	}
3033 	newSelection [writeIndex] = items [selectIndex];
3034 	setSelection (newSelection, false);
3035 	setFocusItem (selectedItem, true);
3036 	showItem (selectedItem);
3037 	Event newEvent = new Event ();
3038 	newEvent.item = selectedItem;
3039 	notifyListeners (SWT.Selection, newEvent);
3040 }
3041 void onPaint (Event event) {
3042 	CTableColumn[] orderedColumns = getOrderedColumns ();
3043 	GC gc = event.gc;
3044 	Rectangle clipping = gc.getClipping ();
3045 	int headerHeight = getHeaderHeight ();
3046 	int numColumns = orderedColumns.length;
3047 	int startColumn = -1, endColumn = -1;
3048 	if (numColumns > 0) {
3049 		startColumn = computeColumnIntersect (clipping.x, 0);
3050 		if (startColumn != -1) {	/* the clip x is within a column's bounds */
3051 			endColumn = computeColumnIntersect (clipping.x + clipping.width, startColumn);
3052 			if (endColumn == -1) endColumn = numColumns - 1;
3053 		}
3054 	} else {
3055 		startColumn = endColumn = 0;
3056 	}
3057 
3058 	/* Determine the items to be painted */
3059 	int startIndex = (clipping.y - headerHeight) / itemHeight + topIndex;
3060 	int endIndex = -1;
3061 	if (startIndex < itemsCount) {
3062 		endIndex = startIndex + (int)Math.ceil((float)clipping.height / itemHeight);
3063 	}
3064 	startIndex = Math.max (0, startIndex);
3065 	endIndex = Math.min (endIndex, itemsCount - 1);
3066 
3067 	/* fill background not handled by items */
3068 	gc.setBackground (getBackground ());
3069 	gc.setClipping (clipping);
3070 	int bottomY = endIndex >= 0 ? getItemY (items [endIndex]) + itemHeight : 0;
3071 	int fillHeight = Math.max (0, clientArea.height - bottomY);
3072 	if (fillHeight > 0) {	/* space below bottom item */
3073 		gc.fillRectangle (0, bottomY, clientArea.width, fillHeight);
3074 		//drawBackground (gc, 0, bottomY, clientArea.width, fillHeight);
3075 	}
3076 	if (columns.length > 0) {
3077 		CTableColumn column = orderedColumns [orderedColumns.length - 1];	/* last column */
3078 		int rightX = column.getX () + column.width;
3079 		if (rightX < clientArea.width) {
3080 			gc.fillRectangle (rightX, 0, clientArea.width - rightX, clientArea.height - fillHeight);
3081 			//drawBackground (gc, rightX, 0, clientArea.width - rightX, clientArea.height - fillHeight);
3082 		}
3083 	}
3084 
3085 	/* paint the items */
3086 	boolean noFocusDraw = false;
3087 	int[] lineDash = gc.getLineDash ();
3088 	int lineWidth = gc.getLineWidth ();
3089 	for (int i = startIndex; i <= Math.min (endIndex, itemsCount - 1); i++) {
3090 		CTableItem item = items [i];
3091 		if (!item.isDisposed ()) {	/* ensure that item was not disposed in a callback */
3092 			if (startColumn == -1) {
3093 				/* indicates that region to paint is to the right of the last column */
3094 				noFocusDraw = item.paint (gc, null, true) || noFocusDraw;
3095 			} else {
3096 				if (numColumns == 0) {
3097 					noFocusDraw = item.paint (gc, null, false) || noFocusDraw;
3098 				} else {
3099 					for (int j = startColumn; j <= Math.min (endColumn, columns.length - 1); j++) {
3100 						if (!item.isDisposed ()) {	/* ensure that item was not disposed in a callback */
3101 							noFocusDraw = item.paint (gc, orderedColumns [j], false) || noFocusDraw;
3102 						}
3103 						if (isDisposed () || gc.isDisposed ()) return;	/* ensure that receiver was not disposed in a callback */
3104 					}
3105 				}
3106 			}
3107 		}
3108 		if (isDisposed () || gc.isDisposed ()) return;	/* ensure that receiver was not disposed in a callback */
3109 	}
3110 
3111 	/* repaint grid lines */
3112 	gc.setClipping(clipping);
3113 	gc.setLineWidth (lineWidth);
3114 	if (linesVisible) {
3115 		gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_LIGHT_SHADOW));
3116 		gc.setLineDash (lineDash);
3117 		if (numColumns > 0 && startColumn != -1) {
3118 			/* vertical column lines */
3119 			for (int i = startColumn; i <= endColumn; i++) {
3120 				int x = orderedColumns [i].getX () + orderedColumns [i].width - 1;
3121 				gc.drawLine (x, clipping.y, x, clipping.y + clipping.height);
3122 			}
3123 		}
3124 		/* horizontal item lines */
3125 		bottomY = clipping.y + clipping.height;
3126 		int rightX = clipping.x + clipping.width;
3127 		int y = (clipping.y - headerHeight) / itemHeight * itemHeight + headerHeight;
3128 		while (y <= bottomY) {
3129 			gc.drawLine (clipping.x, y, rightX, y);
3130 			y += itemHeight;
3131 		}
3132 	}
3133 
3134 	/* paint focus rectangle */
3135 	if (!noFocusDraw && isFocusControl ()) {
3136 		if (focusItem != null) {
3137 			Rectangle focusBounds = focusItem.getFocusBounds ();
3138 			if (focusBounds.width > 0) {
3139 				gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK));
3140 				gc.setClipping (focusBounds);
3141 				if (focusItem.isSelected ()) {
3142 					gc.setLineDash (new int[] {2, 2});
3143 				} else {
3144 					gc.setLineDash (new int[] {1, 1});
3145 				}
3146 				gc.drawFocus (focusBounds.x, focusBounds.y, focusBounds.width, focusBounds.height);
3147 			}
3148 		} else {
3149 			/* no items, so draw focus border around Table */
3150 			int y = headerHeight + 1;
3151 			int width = Math.max (0, clientArea.width - 2);
3152 			int height = Math.max (0, clientArea.height - headerHeight - 2);
3153 			gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK));
3154 			gc.setClipping (1, y, width, height);
3155 			gc.setLineDash (new int[] {1, 1});
3156 			gc.drawFocus (1, y, width, height);
3157 		}
3158 	}
3159 }
3160 void onResize (Event event) {
3161 	clientArea = getClientArea ();
3162 	/* vertical scrollbar */
3163 	ScrollBar vBar = getVerticalBar ();
3164 	if (vBar != null) {
3165 		int clientHeight = (clientArea.height - getHeaderHeight ()) / itemHeight;
3166 		int thumb = Math.min (clientHeight, itemsCount);
3167 		vBar.setThumb (thumb);
3168 		vBar.setPageIncrement (thumb);
3169 		int index = vBar.getSelection ();
3170 		if (index != topIndex) {
3171 			topIndex = index;
3172 			redraw ();
3173 		}
3174 		boolean visible = clientHeight < itemsCount;
3175 		if (visible != vBar.getVisible ()) {
3176 			vBar.setVisible (visible);
3177 			clientArea = getClientArea ();
3178 		}
3179 	}
3180 
3181 	/* horizontal scrollbar */
3182 	ScrollBar hBar = getHorizontalBar ();
3183 	if (hBar != null) {
3184 		int hBarMaximum = hBar.getMaximum ();
3185 		int thumb = Math.min (clientArea.width, hBarMaximum);
3186 		hBar.setThumb (thumb);
3187 		hBar.setPageIncrement (thumb);
3188 		horizontalOffset = hBar.getSelection ();
3189 		boolean visible = clientArea.width < hBarMaximum;
3190 		if (visible != hBar.getVisible ()) {
3191 			hBar.setVisible (visible);
3192 			clientArea = getClientArea ();
3193 		}
3194 	}
3195 
3196 	/* header */
3197 	int headerHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding ();
3198 	header.setSize (clientArea.width, headerHeight);
3199 
3200 	/* if this is the focus control but there are no items then the boundary focus ring must be repainted */
3201 	if (itemsCount == 0 && isFocusControl ()) redraw ();
3202 }
3203 void onScrollHorizontal (Event event) {
3204 	ScrollBar hBar = getHorizontalBar ();
3205 	if (hBar == null) return;
3206 	int newSelection = hBar.getSelection ();
3207 	update ();
3208 	if (itemsCount > 0) {
3209 		GC gc = new GC (this);
3210 		gc.copyArea (
3211 			0, 0,
3212 			clientArea.width, clientArea.height,
3213 			horizontalOffset - newSelection, 0);
3214 		gc.dispose ();
3215 	} else {
3216 		redraw ();	/* ensure that static focus rectangle updates properly */
3217 	}
3218 
3219 	if (drawCount <= 0 && header.isVisible ()) {
3220 		header.update ();
3221 		Rectangle headerClientArea = header.getClientArea ();
3222 		GC gc = new GC (header);
3223 		gc.copyArea (
3224 			0, 0,
3225 			headerClientArea.width, headerClientArea.height,
3226 			horizontalOffset - newSelection, 0);
3227 		gc.dispose ();
3228 	}
3229 	horizontalOffset = newSelection;
3230 }
3231 void onScrollVertical (Event event) {
3232 	ScrollBar vBar = getVerticalBar ();
3233 	if (vBar == null) return;
3234 	int newSelection = vBar.getSelection ();
3235 	update ();
3236 	GC gc = new GC (this);
3237 	gc.copyArea (
3238 		0, 0,
3239 		clientArea.width, clientArea.height,
3240 		0, (topIndex - newSelection) * itemHeight);
3241 	gc.dispose ();
3242 	topIndex = newSelection;
3243 }
3244 void onSpace () {
3245 	if (focusItem == null) return;
3246 	if (!focusItem.isSelected ()) {
3247 		selectItem (focusItem, (getStyle () & SWT.MULTI) != 0);
3248 		redrawItem (focusItem.index, true);
3249 	}
3250 	if ((getStyle () & SWT.CHECK) != 0) {
3251 		focusItem.setChecked (!focusItem.checked);
3252 	}
3253 	showItem (focusItem);
3254 	Event event = new Event ();
3255 	event.item = focusItem;
3256 	notifyListeners (SWT.Selection, event);
3257 	if ((getStyle () & SWT.CHECK) == 0) return;
3258 
3259 	/* SWT.CHECK */
3260 	event = new Event ();
3261 	event.item = focusItem;
3262 	event.detail = SWT.CHECK;
3263 	notifyListeners (SWT.Selection, event);
3264 }
3265 /*
3266  * The current focus item is about to become unavailable, so reassign focus.
3267  */
3268 void reassignFocus () {
3269 	if (focusItem == null) return;
3270 
3271 	/*
3272 	 * reassign to the previous root-level item if there is one, or the next
3273 	 * root-level item otherwise
3274 	 */
3275 	int index = focusItem.index;
3276 	if (index != 0) {
3277 		index--;
3278 	} else {
3279 		index++;
3280 	}
3281 	if (index < itemsCount) {
3282 		CTableItem item = items [index];
3283 		setFocusItem (item, false);
3284 		showItem (item);
3285 	} else {
3286 		setFocusItem (null, false);		/* no items left */
3287 	}
3288 }
3289 @Override
3290 public void redraw () {
3291 	checkWidget ();
3292 	if (drawCount <= 0) super.redraw ();
3293 }
3294 @Override
3295 public void redraw (int x, int y, int width, int height, boolean all) {
3296 	checkWidget ();
3297 	if (drawCount <= 0) super.redraw (x, y, width, height, all);
3298 }
3299 /*
3300  * Redraws from the specified index down to the last available item inclusive.  Note
3301  * that the redraw bounds do not extend beyond the current last item, so clients
3302  * that reduce the number of available items should use #redrawItems(int,int) instead
3303  * to ensure that redrawing extends down to the previous bottom item boundary.
3304  */
3305 void redrawFromItemDownwards (int index) {
3306 	redrawItems (index, itemsCount - 1, false);
3307 }
3308 /*
3309  * Redraws the table item at the specified index.  It is valid for this index to reside
3310  * beyond the last available item.
3311  */
3312 void redrawItem (int itemIndex, boolean focusBoundsOnly) {
3313 	if (itemIndex < itemsCount && !items [itemIndex].isInViewport ()) return;
3314 	redrawItems (itemIndex, itemIndex, focusBoundsOnly);
3315 }
3316 /*
3317  * Redraws the table between the start and end item indices inclusive.  It is valid
3318  * for the end index value to extend beyond the last available item.
3319  */
3320 void redrawItems (int startIndex, int endIndex, boolean focusBoundsOnly) {
3321 	if (drawCount > 0) return;
3322 
3323 	int startY = (startIndex - topIndex) * itemHeight + getHeaderHeight ();
3324 	int height = (endIndex - startIndex + 1) * itemHeight;
3325 	if (focusBoundsOnly) {
3326 		boolean custom = isListening (SWT.EraseItem) || isListening (SWT.PaintItem);
3327 		if (!custom && columns.length > 0) {
3328 			CTableColumn lastColumn;
3329 			if ((getStyle () & SWT.FULL_SELECTION) != 0) {
3330 				CTableColumn[] orderedColumns = getOrderedColumns ();
3331 				lastColumn = orderedColumns [orderedColumns.length - 1];
3332 			} else {
3333 				lastColumn = columns [0];
3334 			}
3335 			int rightX = lastColumn.getX () + lastColumn.getWidth ();
3336 			if (rightX <= 0) return;	/* focus column(s) not visible */
3337 		}
3338 		endIndex = Math.min (endIndex, itemsCount - 1);
3339 		for (int i = startIndex; i <= endIndex; i++) {
3340 			CTableItem item = items [i];
3341 			if (item.isInViewport ()) {
3342 				/* if custom painting is being done then repaint the full item */
3343 				if (custom) {
3344 					redraw (0, getItemY (item), clientArea.width, itemHeight, false);
3345 				} else {
3346 					/* repaint the item's focus bounds */
3347 					Rectangle bounds = item.getFocusBounds ();
3348 					redraw (bounds.x, startY, bounds.width, height, false);
3349 				}
3350 			}
3351 		}
3352 	} else {
3353 		redraw (0, startY, clientArea.width, height, false);
3354 	}
3355 }
3356 /**
3357  * Removes the item from the receiver at the given
3358  * zero-relative index.
3359  *
3360  * @param index the index for the item
3361  *
3362  * @exception IllegalArgumentException <ul>
3363  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
3364  * </ul>
3365  * @exception SWTException <ul>
3366  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3367  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3368  * </ul>
3369  */
3370 public void remove (int index) {
3371 	checkWidget ();
3372 	if (!(0 <= index && index < itemsCount)) SWT.error (SWT.ERROR_INVALID_RANGE);
3373 	items [index].dispose ();
3374 	int[] eventData = new int[5];
3375 	eventData[0] = ACC.DELETE;
3376 	eventData[1] = index;
3377 	eventData[2] = 1;
3378 	eventData[3] = 0;
3379 	eventData[4] = 0;
3380 	getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
3381 }
3382 /**
3383  * Removes the items from the receiver which are
3384  * between the given zero-relative start and end
3385  * indices (inclusive).
3386  *
3387  * @param start the start of the range
3388  * @param end the end of the range
3389  *
3390  * @exception IllegalArgumentException <ul>
3391  *    <li>ERROR_INVALID_RANGE - if either the start or end are not between 0 and the number of elements in the list minus 1 (inclusive)</li>
3392  * </ul>
3393  * @exception SWTException <ul>
3394  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3395  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3396  * </ul>
3397  */
3398 public void remove (int start, int end) {
3399 	checkWidget ();
3400 	if (start > end) return;
3401 	if (!(0 <= start && start <= end && end < itemsCount)) {
3402 		SWT.error (SWT.ERROR_INVALID_RANGE);
3403 	}
3404 	if (start == 0 && end == itemsCount - 1) {
3405 		removeAll ();
3406 	} else {
3407 		for (int i = end; i >= start; i--) {
3408 			items [i].dispose ();
3409 		}
3410 
3411 		int[] eventData = new int[5];
3412 		eventData[0] = ACC.DELETE;
3413 		eventData[1] = start;
3414 		eventData[2] = end - start + 1;
3415 		eventData[3] = 0;
3416 		eventData[4] = 0;
3417 		getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
3418 
3419 	}
3420 }
3421 /**
3422  * Removes the items from the receiver's list at the given
3423  * zero-relative indices.
3424  *
3425  * @param indices the array of indices of the items
3426  *
3427  * @exception IllegalArgumentException <ul>
3428  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
3429  *    <li>ERROR_NULL_ARGUMENT - if the indices array is null</li>
3430  * </ul>
3431  * @exception SWTException <ul>
3432  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3433  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3434  * </ul>
3435  */
3436 public void remove (int [] indices) {
3437 	checkWidget ();
3438 	if (indices == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
3439 	if (indices.length == 0) return;
3440 	int [] newIndices = new int [indices.length];
3441 	System.arraycopy (indices, 0, newIndices, 0, indices.length);
3442 	sortDescent (newIndices);
3443 	int start = newIndices [newIndices.length - 1], end = newIndices [0];
3444 	if (!(0 <= start && start <= end && end < itemsCount)) {
3445 		SWT.error (SWT.ERROR_INVALID_RANGE);
3446 	}
3447 	int lastRemovedIndex = -1;
3448 	int[] eventData = new int[5];
3449 	for (int newIndice : newIndices) {
3450 		if (newIndice != lastRemovedIndex) {
3451 			items [newIndice].dispose ();
3452 			eventData[0] = ACC.DELETE;
3453 			eventData[1] = newIndice;
3454 			eventData[2] = 1;
3455 			eventData[3] = 0;
3456 			eventData[4] = 0;
3457 			getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
3458 			lastRemovedIndex = newIndice;
3459 		}
3460 	}
3461 }
3462 /**
3463  * Removes all of the items from the receiver.
3464  *
3465  * @exception SWTException <ul>
3466  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3467  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3468  * </ul>
3469  */
3470 public void removeAll () {
3471 	checkWidget ();
3472 	if (itemsCount == 0) return;
3473 	setRedraw (false);
3474 
3475 	setFocusItem (null, false);
3476 	for (int i = 0; i < itemsCount; i++) {
3477 		items [i].dispose (false);
3478 	}
3479 	items = new CTableItem [0];
3480 	selectedItems = new CTableItem [0];
3481 	int oldCount = itemsCount;
3482 	itemsCount = topIndex = 0;
3483 	anchorItem = lastClickedItem = null;
3484 	lastSelectionEvent = null;
3485 
3486 	int[] eventData = new int[5];
3487 	eventData[0] = ACC.DELETE;
3488 	eventData[1] = 0;
3489 	eventData[2] = oldCount;
3490 	eventData[3] = 0;
3491 	eventData[4] = 0;
3492 	getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
3493 
3494 	ScrollBar vBar = getVerticalBar ();
3495 	if (vBar != null) {
3496 		vBar.setMaximum (1);
3497 		vBar.setVisible (false);
3498 	}
3499 	if (columns.length == 0) {
3500 		horizontalOffset = 0;
3501 		ScrollBar hBar = getHorizontalBar ();
3502 		if (hBar != null) {
3503 			hBar.setMaximum (1);
3504 			hBar.setVisible (false);
3505 		}
3506 	}
3507 
3508 	setRedraw (true);
3509 }
3510 String removeMnemonics (String string) {
3511 	/* removes single ampersands and preserves double-ampersands */
3512 	char [] chars = new char [string.length ()];
3513 	string.getChars (0, chars.length, chars, 0);
3514 	int i = 0, j = 0;
3515 	for ( ; i < chars.length; i++, j++) {
3516 		if (chars[i] == '&') {
3517 			if (++i == chars.length) break;
3518 			if (chars[i] == '&') {
3519 				chars[j++] = chars[i - 1];
3520 			}
3521 		}
3522 		chars[j] = chars[i];
3523 	}
3524 	if (i == j) return string;
3525 	return new String (chars, 0, j);
3526 }
3527 void removeSelectedItem (int index) {
3528 	CTableItem[] newSelectedItems = new CTableItem [selectedItems.length - 1];
3529 	System.arraycopy (selectedItems, 0, newSelectedItems, 0, index);
3530 	System.arraycopy (selectedItems, index + 1, newSelectedItems, index, newSelectedItems.length - index);
3531 	selectedItems = newSelectedItems;
3532 }
3533 /**
3534  * Removes the listener from the collection of listeners who will
3535  * be notified when the user changes the receiver's selection.
3536  *
3537  * @param listener the listener which should no longer be notified
3538  *
3539  * @exception IllegalArgumentException <ul>
3540  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
3541  * </ul>
3542  * @exception SWTException <ul>
3543  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3544  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3545  * </ul>
3546  *
3547  * @see SelectionListener
3548  * @see #addSelectionListener(SelectionListener)
3549  */
3550 public void removeSelectionListener (SelectionListener listener) {
3551 	checkWidget ();
3552 	if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
3553 	removeListener (SWT.Selection, listener);
3554 	removeListener (SWT.DefaultSelection, listener);
3555 }
3556 /**
3557  * Selects the item at the given zero-relative index in the receiver.
3558  * If the item at the index was already selected, it remains
3559  * selected. Indices that are out of range are ignored.
3560  *
3561  * @param index the index of the item to select
3562  *
3563  * @exception SWTException <ul>
3564  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3565  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3566  * </ul>
3567  */
3568 public void select (int index) {
3569 	checkWidget ();
3570 	if (!(0 <= index && index < itemsCount)) return;
3571 	selectItem (items [index], (getStyle () & SWT.MULTI) != 0);
3572 	if (isFocusControl () || (getStyle () & SWT.HIDE_SELECTION) == 0) {
3573 		redrawItem (index, false);
3574 	}
3575 	getAccessible().selectionChanged();
3576 }
3577 /**
3578  * Selects the items in the range specified by the given zero-relative
3579  * indices in the receiver. The range of indices is inclusive.
3580  * The current selection is not cleared before the new items are selected.
3581  * <p>
3582  * If an item in the given range is not selected, it is selected.
3583  * If an item in the given range was already selected, it remains selected.
3584  * Indices that are out of range are ignored and no items will be selected
3585  * if start is greater than end.
3586  * If the receiver is single-select and there is more than one item in the
3587  * given range, then all indices are ignored.
3588  * </p>
3589  *
3590  * @param start the start of the range
3591  * @param end the end of the range
3592  *
3593  * @exception SWTException <ul>
3594  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3595  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3596  * </ul>
3597  *
3598  * @see CTable#setSelection(int,int)
3599  */
3600 public void select (int start, int end) {
3601 	checkWidget ();
3602 	if (end < 0 || start > end || ((getStyle () & SWT.SINGLE) != 0 && start != end)) return;
3603 	if (itemsCount == 0 || start >= itemsCount) return;
3604 	start = Math.max (start, 0);
3605 	end = Math.min (end, itemsCount - 1);
3606 	for (int i = start; i <= end; i++) {
3607 		selectItem (items [i], (getStyle () & SWT.MULTI) != 0);
3608 	}
3609 	if (isFocusControl () || (getStyle () & SWT.HIDE_SELECTION) == 0) {
3610 		redrawItems (start, end, false);
3611 	}
3612 	getAccessible().selectionChanged();
3613 }
3614 /**
3615  * Selects the items at the given zero-relative indices in the receiver.
3616  * The current selection is not cleared before the new items are selected.
3617  * <p>
3618  * If the item at a given index is not selected, it is selected.
3619  * If the item at a given index was already selected, it remains selected.
3620  * Indices that are out of range and duplicate indices are ignored.
3621  * If the receiver is single-select and multiple indices are specified,
3622  * then all indices are ignored.
3623  * </p>
3624  *
3625  * @param indices the array of indices for the items to select
3626  *
3627  * @exception IllegalArgumentException <ul>
3628  *    <li>ERROR_NULL_ARGUMENT - if the array of indices is null</li>
3629  * </ul>
3630  * @exception SWTException <ul>
3631  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3632  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3633  * </ul>
3634  *
3635  * @see CTable#setSelection(int[])
3636  */
3637 public void select (int [] indices) {
3638 	checkWidget ();
3639 	if (indices == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
3640 	if (indices.length == 0 || ((getStyle () & SWT.SINGLE) != 0 && indices.length > 1)) return;
3641 	for (int index : indices) {
3642 		if (0 <= index && index < itemsCount) {
3643 			selectItem (items [index], (getStyle () & SWT.MULTI) != 0);
3644 		}
3645 	}
3646 	if (isFocusControl () || (getStyle () & SWT.HIDE_SELECTION) == 0) {
3647 		for (int index : indices) {
3648 			if (0 <= index && index < itemsCount) {
3649 				redrawItem (index, false);
3650 			}
3651 		}
3652 	}
3653 	getAccessible().selectionChanged();
3654 }
3655 /**
3656  * Selects all of the items in the receiver.
3657  * <p>
3658  * If the receiver is single-select, do nothing.
3659  * </p>
3660  *
3661  * @exception SWTException <ul>
3662  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3663  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3664  * </ul>
3665  */
3666 public void selectAll () {
3667 	checkWidget ();
3668 	if ((getStyle () & SWT.SINGLE) != 0) return;
3669 	selectedItems = new CTableItem [itemsCount];
3670 	System.arraycopy (items, 0, selectedItems, 0, itemsCount);
3671 	if (isFocusControl () || (getStyle () & SWT.HIDE_SELECTION) == 0) {
3672 		redraw ();
3673 	}
3674 	for (CTableItem selectedItem : selectedItems) {
3675 		selectedItem.getAccessible(getAccessible(), 0).selectionChanged();
3676 	}
3677 	getAccessible().selectionChanged();
3678 }
3679 void selectItem (CTableItem item, boolean addToSelection) {
3680 	CTableItem[] oldSelectedItems = selectedItems;
3681 	if (!addToSelection || (getStyle () & SWT.SINGLE) != 0) {
3682 		selectedItems = new CTableItem[] {item};
3683 		if (isFocusControl () || (getStyle () & SWT.HIDE_SELECTION) == 0) {
3684 			for (CTableItem oldSelectedItem : oldSelectedItems) {
3685 				if (oldSelectedItem != item) {
3686 					redrawItem (oldSelectedItem.index, true);
3687 				}
3688 			}
3689 		}
3690 		for (CTableItem oldSelectedItem : oldSelectedItems) {
3691 			oldSelectedItem.getAccessible(getAccessible(), 0).selectionChanged();
3692 		}
3693 	} else {
3694 		if (item.isSelected ()) return;
3695 		selectedItems = new CTableItem [selectedItems.length + 1];
3696 		System.arraycopy (oldSelectedItems, 0, selectedItems, 0, oldSelectedItems.length);
3697 		selectedItems [selectedItems.length - 1] = item;
3698 	}
3699 
3700 	item.getAccessible(getAccessible(), 0).selectionChanged();
3701 	getAccessible().selectionChanged();
3702 }
3703 @Override
3704 public void setBackground (Color color) {
3705 	checkWidget ();
3706 	if (color == null) color = display.getSystemColor (SWT.COLOR_LIST_BACKGROUND);
3707 	super.setBackground (color);
3708 }
3709 @Override
3710 public void setForeground (Color color) {
3711 	checkWidget ();
3712 	if (color == null) color = display.getSystemColor (SWT.COLOR_LIST_FOREGROUND);
3713 	super.setForeground (color);
3714 }
3715 /**
3716  * Sets the order that the items in the receiver should
3717  * be displayed in to the given argument which is described
3718  * in terms of the zero-relative ordering of when the items
3719  * were added.
3720  *
3721  * @param order the new order to display the items
3722  *
3723  * @exception SWTException <ul>
3724  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3725  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3726  * </ul>
3727  * @exception IllegalArgumentException <ul>
3728  *    <li>ERROR_NULL_ARGUMENT - if the item order is null</li>
3729  *    <li>ERROR_INVALID_ARGUMENT - if the item order is not the same length as the number of items</li>
3730  * </ul>
3731  *
3732  * @see CTable#getColumnOrder()
3733  * @see CTableColumn#getMoveable()
3734  * @see CTableColumn#setMoveable(boolean)
3735  * @see SWT#Move
3736  *
3737  * @since 3.1
3738  */
3739 public void setColumnOrder (int [] order) {
3740 	checkWidget ();
3741 	if (order == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
3742 	if (columns.length == 0) {
3743 		if (order.length != 0) SWT.error (SWT.ERROR_INVALID_ARGUMENT);
3744 		return;
3745 	}
3746 	if (order.length != columns.length) SWT.error (SWT.ERROR_INVALID_ARGUMENT);
3747 	boolean reorder = false;
3748 	boolean [] seen = new boolean [columns.length];
3749 	int[] oldOrder = getColumnOrder ();
3750 	for (int i = 0; i < order.length; i++) {
3751 		int index = order [i];
3752 		if (index < 0 || index >= columns.length) SWT.error (SWT.ERROR_INVALID_RANGE);
3753 		if (seen [index]) SWT.error (SWT.ERROR_INVALID_ARGUMENT);
3754 		seen [index] = true;
3755 		if (index != oldOrder [i]) reorder = true;
3756 	}
3757 	if (!reorder) return;
3758 
3759 	headerHideToolTip ();
3760 	int[] oldX = new int [columns.length];
3761 	for (int i = 0; i < columns.length; i++) {
3762 		oldX [i] = columns [i].getX ();
3763 	}
3764 	orderedColumns = new CTableColumn [order.length];
3765 	for (int i = 0; i < order.length; i++) {
3766 		orderedColumns [i] = columns [order [i]];
3767 	}
3768 	for (CTableColumn orderedColumn : orderedColumns) {
3769 		CTableColumn column = orderedColumn;
3770 		if (!column.isDisposed () && column.getX () != oldX [column.getIndex ()]) {
3771 			column.notifyListeners (SWT.Move, new Event ());
3772 		}
3773 	}
3774 
3775 	redraw ();
3776 	if (drawCount <= 0 && header.isVisible ()) header.redraw ();
3777 }
3778 void setFocusItem (CTableItem item, boolean redrawOldFocus) {
3779 	if (item == focusItem) return;
3780 	CTableItem oldFocusItem = focusItem;
3781 	if (oldFocusItem != null) oldFocusItem.getAccessible(getAccessible(), 0).setFocus(ACC.CHILDID_SELF);
3782 	focusItem = item;
3783 	if (redrawOldFocus && oldFocusItem != null) {
3784 		redrawItem (oldFocusItem.index, true);
3785 	}
3786 	if (focusItem != null) focusItem.getAccessible(getAccessible(), 0).setFocus(ACC.CHILDID_SELF);
3787 }
3788 @Override
3789 public void setFont (Font value) {
3790 	checkWidget ();
3791 	Font oldFont = getFont ();
3792 	super.setFont (value);
3793 	Font font = getFont ();
3794 	if (font.equals (oldFont)) return;
3795 
3796 	GC gc = new GC (this);
3797 
3798 	/* recompute the receiver's cached font height and item height values */
3799 	fontHeight = gc.getFontMetrics ().getHeight ();
3800 	setItemHeight (Math.max (fontHeight, imageHeight) + 2 * getCellPadding ());
3801 	Point headerSize = header.getSize ();
3802 	int newHeaderHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding ();
3803 	if (headerSize.y != newHeaderHeight) {
3804 		header.setSize (headerSize.x, newHeaderHeight);
3805 	}
3806 	header.setFont (font);
3807 
3808 	/*
3809 	 * Notify all columns and items of the font change so that elements that
3810 	 * use the receiver's font can recompute their cached string widths.
3811 	 */
3812 	for (CTableColumn column : columns) {
3813 		column.updateFont (gc);
3814 	}
3815 	for (int i = 0; i < itemsCount; i++) {
3816 		items [i].updateFont (gc);
3817 	}
3818 
3819 	gc.dispose ();
3820 
3821 	if (drawCount <= 0 && header.isVisible ()) header.redraw ();
3822 
3823 	/* update scrollbars */
3824 	if (columns.length == 0) updateHorizontalBar ();
3825 	ScrollBar vBar = getVerticalBar ();
3826 	if (vBar != null) {
3827 		int thumb = (clientArea.height - getHeaderHeight ()) / itemHeight;
3828 		vBar.setThumb (thumb);
3829 		vBar.setPageIncrement (thumb);
3830 		topIndex = vBar.getSelection ();
3831 		vBar.setVisible (thumb < vBar.getMaximum ());
3832 	}
3833 	redraw ();
3834 }
3835 void setHeaderImageHeight (int value) {
3836 	headerImageHeight = value;
3837 	Point headerSize = header.getSize ();
3838 	int newHeaderHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding ();
3839 	if (headerSize.y != newHeaderHeight) {
3840 		header.setSize (headerSize.x, newHeaderHeight);
3841 	}
3842 }
3843 /**
3844  * Marks the receiver's header as visible if the argument is <code>true</code>,
3845  * and marks it invisible otherwise.
3846  * <p>
3847  * If one of the receiver's ancestors is not visible or some
3848  * other condition makes the receiver not visible, marking
3849  * it visible may not actually cause it to be displayed.
3850  * </p>
3851  *
3852  * @param show the new visibility state
3853  *
3854  * @exception SWTException <ul>
3855  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3856  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3857  * </ul>
3858  */
3859 public void setHeaderVisible (boolean value) {
3860 	checkWidget ();
3861 	if (header.getVisible () == value) return;		/* no change */
3862 	headerHideToolTip ();
3863 	header.setVisible (value);
3864 	updateVerticalBar ();
3865 	redraw ();
3866 }
3867 void setImageHeight (int value) {
3868 	imageHeight = value;
3869 	setItemHeight (Math.max (fontHeight, imageHeight) + 2 * getCellPadding ());
3870 }
3871 /**
3872  * Sets the number of items contained in the receiver.
3873  *
3874  * @param count the number of items
3875  *
3876  * @exception SWTException <ul>
3877  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3878  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3879  * </ul>
3880  *
3881  * @since 3.0
3882  */
3883 public void setItemCount (int count) {
3884 	checkWidget ();
3885 	count = Math.max (0, count);
3886 	if (count == itemsCount) return;
3887 	int oldCount = itemsCount;
3888 	int redrawStart, redrawEnd;
3889 
3890 	/* if the new item count is less than the current count then remove all excess items from the end */
3891 	if (count < itemsCount) {
3892 		redrawStart = count;
3893 		redrawEnd = itemsCount - 1;
3894 		for (int i = count; i < itemsCount; i++) {
3895 			items [i].dispose (false);
3896 		}
3897 
3898 		int newSelectedCount = 0;
3899 		for (CTableItem selectedItem : selectedItems) {
3900 			if (!selectedItem.isDisposed ()) newSelectedCount++;
3901 		}
3902 		if (newSelectedCount != selectedItems.length) {
3903 			/* one or more selected items have been disposed */
3904 			CTableItem[] newSelectedItems = new CTableItem [newSelectedCount];
3905 			int pos = 0;
3906 			for (CTableItem selectedItem : selectedItems) {
3907 				CTableItem item = selectedItem;
3908 				if (!item.isDisposed ()) {
3909 					newSelectedItems [pos++] = item;
3910 				}
3911 			}
3912 			selectedItems = newSelectedItems;
3913 		}
3914 
3915 		if (anchorItem != null && anchorItem.isDisposed ()) anchorItem = null;
3916 		if (lastClickedItem != null && lastClickedItem.isDisposed ()) lastClickedItem = null;
3917 		if (focusItem != null && focusItem.isDisposed ()) {
3918 			CTableItem newFocusItem = count > 0 ? items [count - 1] : null;
3919 			setFocusItem (newFocusItem, false);
3920 		}
3921 		int[] eventData = new int[5];
3922 		eventData[0] = ACC.DELETE;
3923 		eventData[1] = redrawStart;
3924 		eventData[2] = redrawEnd - redrawStart;
3925 		eventData[3] = 0;
3926 		eventData[4] = 0;
3927 		getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
3928 
3929 		itemsCount = count;
3930 		if (columns.length == 0) updateHorizontalBar ();
3931 	} else {
3932 		redrawStart = itemsCount;
3933 		redrawEnd = count - 1;
3934 		CTableItem[] newItems = new CTableItem [count];
3935 		System.arraycopy (items, 0, newItems, 0, itemsCount);
3936 		items = newItems;
3937 		for (int i = itemsCount; i < count; i++) {
3938 			items [i] = new CTableItem (this, SWT.NONE, i, false);
3939 			itemsCount++;
3940 		}
3941 
3942 		int[] eventData = new int[5];
3943 		eventData[0] = ACC.INSERT;
3944 		eventData[1] = redrawStart;
3945 		eventData[2] = count;
3946 		eventData[3] = 0;
3947 		eventData[4] = 0;
3948 		getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
3949 		if (oldCount == 0) focusItem = items [0];
3950 	}
3951 
3952 	updateVerticalBar ();
3953 	/*
3954 	 * If this is the focus control and the item count is going from 0->!0 or !0->0 then the
3955 	 * receiver must be redrawn to ensure that its boundary focus ring is updated.
3956 	 */
3957 	if ((oldCount == 0 || itemsCount == 0) && isFocusControl ()) {
3958 		redraw ();
3959 		return;
3960 	}
3961 	redrawItems (redrawStart, redrawEnd, false);
3962 }
3963 boolean setItemHeight (int value) {
3964 	boolean update = !customHeightSet || itemHeight < value;
3965 	if (update) itemHeight = value;
3966 	return update;
3967 }
3968 /**
3969  * Marks the receiver's lines as visible if the argument is <code>true</code>,
3970  * and marks it invisible otherwise. Note that some platforms draw grid lines
3971  * while others may draw alternating row colors.
3972  * <p>
3973  * If one of the receiver's ancestors is not visible or some
3974  * other condition makes the receiver not visible, marking
3975  * it visible may not actually cause it to be displayed.
3976  * </p>
3977  *
3978  * @param show the new visibility state
3979  *
3980  * @exception SWTException <ul>
3981  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3982  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3983  * </ul>
3984  */
3985 public void setLinesVisible (boolean value) {
3986 	checkWidget ();
3987 	if (linesVisible == value) return;		/* no change */
3988 	linesVisible = value;
3989 	redraw ();
3990 }
3991 @Override
3992 public void setMenu (Menu menu) {
3993 	super.setMenu (menu);
3994 	header.setMenu (menu);
3995 }
3996 @Override
3997 public void setRedraw (boolean value) {
3998 	checkWidget();
3999 	if (value) {
4000 		if (--drawCount == 0) {
4001 			if (items.length - itemsCount > 3) {
4002 				CTableItem[] newItems = new CTableItem [itemsCount];
4003 				System.arraycopy (items, 0, newItems, 0, itemsCount);
4004 				items = newItems;
4005 			}
4006 			updateVerticalBar ();
4007 			updateHorizontalBar ();
4008 		}
4009 	} else {
4010 		drawCount++;
4011 	}
4012 	super.setRedraw (value);
4013 	header.setRedraw (value);
4014 }
4015 /**
4016  * Sets the receiver's selection to the given item.
4017  * The current selection is cleared before the new item is selected.
4018  * <p>
4019  * If the item is not in the receiver, then it is ignored.
4020  * </p>
4021  *
4022  * @param item the item to select
4023  *
4024  * @exception IllegalArgumentException <ul>
4025  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
4026  *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
4027  * </ul>
4028  * @exception SWTException <ul>
4029  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4030  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4031  * </ul>
4032  *
4033  * @since 3.2
4034  */
4035 public void setSelection (CTableItem item) {
4036 	checkWidget ();
4037 	if (item == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
4038 	setSelection (new CTableItem[] {item}, true);
4039 }
4040 /**
4041  * Sets the receiver's selection to be the given array of items.
4042  * The current selection is cleared before the new items are selected.
4043  * <p>
4044  * Items that are not in the receiver are ignored.
4045  * If the receiver is single-select and multiple items are specified,
4046  * then all items are ignored.
4047  * </p>
4048  *
4049  * @param items the array of items
4050  *
4051  * @exception IllegalArgumentException <ul>
4052  *    <li>ERROR_NULL_ARGUMENT - if the array of items is null</li>
4053  *    <li>ERROR_INVALID_ARGUMENT - if one of the items has been disposed</li>
4054  * </ul>
4055  * @exception SWTException <ul>
4056  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4057  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4058  * </ul>
4059  *
4060  * @see CTable#deselectAll()
4061  * @see CTable#select(int[])
4062  * @see CTable#setSelection(int[])
4063  */
4064 public void setSelection (CTableItem[] items) {
4065 	checkWidget ();
4066 	if (items == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
4067 	setSelection (items, true);
4068 }
4069 void setSelection (CTableItem[] items, boolean updateViewport) {
4070 	if (items.length == 0 || ((getStyle () & SWT.SINGLE) != 0 && items.length > 1)) {
4071 		deselectAll ();
4072 		return;
4073 	}
4074 	CTableItem[] oldSelection = selectedItems;
4075 
4076 	/* remove null and duplicate items */
4077 	int index = 0;
4078 	selectedItems = new CTableItem [items.length];	/* assume all valid items */
4079 	for (CTableItem item2 : items) {
4080 		CTableItem item = item2;
4081 		if (item != null && item.parent == this && !item.isSelected ()) {
4082 			selectedItems [index++] = item;
4083 		}
4084 	}
4085 	if (index != items.length) {
4086 		/* an invalid item was provided so resize the array accordingly */
4087 		CTableItem[] temp = new CTableItem [index];
4088 		System.arraycopy (selectedItems, 0, temp, 0, index);
4089 		selectedItems = temp;
4090 	}
4091 	if (selectedItems.length == 0) {	/* no valid items */
4092 		deselectAll ();
4093 		return;
4094 	}
4095 
4096 	boolean tableSelectionChanged = false;
4097 	if (isFocusControl () || (getStyle () & SWT.HIDE_SELECTION) == 0) {
4098 		for (CTableItem item : oldSelection) {
4099 			if (!item.isSelected ()) {
4100 				redrawItem (item.index, true);
4101 				item.getAccessible(getAccessible(), 0).selectionChanged();
4102 				tableSelectionChanged = true;
4103 			}
4104 		}
4105 		for (CTableItem selectedItem : selectedItems) {
4106 			redrawItem (selectedItem.index, true);
4107 			selectedItem.getAccessible(getAccessible(), 0).selectionChanged();
4108 			tableSelectionChanged = true;
4109 		}
4110 	}
4111 	if (updateViewport) {
4112 		showItem (selectedItems [0]);
4113 		setFocusItem (selectedItems [0], true);
4114 	}
4115 
4116 	if (tableSelectionChanged) getAccessible().selectionChanged();
4117 }
4118 /**
4119  * Sets the column used by the sort indicator for the receiver. A null
4120  * value will clear the sort indicator.  The current sort column is cleared
4121  * before the new column is set.
4122  *
4123  * @param column the column used by the sort indicator or <code>null</code>
4124  *
4125  * @exception IllegalArgumentException <ul>
4126  *    <li>ERROR_INVALID_ARGUMENT - if the column is disposed</li>
4127  * </ul>
4128  * @exception SWTException <ul>
4129  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4130  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4131  * </ul>
4132  *
4133  * @since 3.2
4134  */
4135 public void setSortColumn (CTableColumn column) {
4136 	checkWidget ();
4137 	if (column != null && column.isDisposed ()) SWT.error (SWT.ERROR_INVALID_ARGUMENT);
4138 	if (column == sortColumn) return;
4139 	if (sortColumn != null && !sortColumn.isDisposed ()) {
4140 		sortColumn.setSortDirection (SWT.NONE);
4141 	}
4142 	sortColumn = column;
4143 	if (sortColumn != null) {
4144 		sortColumn.setSortDirection (sortDirection);
4145 	}
4146 }
4147 /**
4148  * Sets the direction of the sort indicator for the receiver. The value
4149  * can be one of <code>UP</code>, <code>DOWN</code> or <code>NONE</code>.
4150  *
4151  * @param direction the direction of the sort indicator
4152  *
4153  * @exception SWTException <ul>
4154  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4155  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4156  * </ul>
4157  *
4158  * @since 3.2
4159  */
4160 public void setSortDirection (int direction) {
4161 	checkWidget ();
4162 	if (direction != SWT.UP && direction != SWT.DOWN && direction != SWT.NONE) return;
4163 	sortDirection = direction;
4164 	if (sortColumn == null || sortColumn.isDisposed ()) return;
4165 	sortColumn.setSortDirection (sortDirection);
4166 }
4167 /**
4168  * Selects the item at the given zero-relative index in the receiver.
4169  * The current selection is first cleared, then the new item is selected.
4170  *
4171  * @param index the index of the item to select
4172  *
4173  * @exception SWTException <ul>
4174  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4175  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4176  * </ul>
4177  *
4178  * @see CTable#deselectAll()
4179  * @see CTable#select(int)
4180  */
4181 public void setSelection (int index) {
4182 	checkWidget ();
4183 	deselectAll ();
4184 	if (!(0 <= index && index < itemsCount)) return;
4185 	selectItem (items [index], false);
4186 	setFocusItem (items [index], true);
4187 	redrawItem (index, true);
4188 	showSelection ();
4189 	getAccessible().selectionChanged();
4190 }
4191 /**
4192  * Selects the items in the range specified by the given zero-relative
4193  * indices in the receiver. The range of indices is inclusive.
4194  * The current selection is cleared before the new items are selected.
4195  * <p>
4196  * Indices that are out of range are ignored and no items will be selected
4197  * if start is greater than end.
4198  * If the receiver is single-select and there is more than one item in the
4199  * given range, then all indices are ignored.
4200  * </p>
4201  *
4202  * @param start the start index of the items to select
4203  * @param end the end index of the items to select
4204  *
4205  * @exception SWTException <ul>
4206  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4207  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4208  * </ul>
4209  *
4210  * @see CTable#deselectAll()
4211  * @see CTable#select(int,int)
4212  */
4213 public void setSelection (int start, int end) {
4214 	checkWidget ();
4215 	deselectAll ();
4216 	if (end < 0 || start > end || ((getStyle () & SWT.SINGLE) != 0 && start != end)) return;
4217 	if (itemsCount == 0 || start >= itemsCount) return;
4218 	start = Math.max (0, start);
4219 	end = Math.min (end, itemsCount - 1);
4220 	select (start, end);
4221 	setFocusItem (items [start], true);
4222 	showSelection ();
4223 }
4224 /**
4225  * Selects the items at the given zero-relative indices in the receiver.
4226  * The current selection is cleared before the new items are selected.
4227  * <p>
4228  * Indices that are out of range and duplicate indices are ignored.
4229  * If the receiver is single-select and multiple indices are specified,
4230  * then all indices are ignored.
4231  * </p>
4232  *
4233  * @param indices the indices of the items to select
4234  *
4235  * @exception IllegalArgumentException <ul>
4236  *    <li>ERROR_NULL_ARGUMENT - if the array of indices is null</li>
4237  * </ul>
4238  * @exception SWTException <ul>
4239  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4240  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4241  * </ul>
4242  *
4243  * @see CTable#deselectAll()
4244  * @see CTable#select(int[])
4245  */
4246 public void setSelection (int [] indices) {
4247 	checkWidget ();
4248 	if (indices == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
4249 	deselectAll ();
4250 	int length = indices.length;
4251 	if (length == 0 || ((getStyle () & SWT.SINGLE) != 0 && length > 1)) return;
4252 	select (indices);
4253 	int focusIndex = -1;
4254 	for (int i = 0; i < indices.length && focusIndex == -1; i++) {
4255 		if (0 <= indices [i] && indices [i] < itemsCount) {
4256 			focusIndex = indices [i];
4257 		}
4258 	}
4259 	if (focusIndex != -1) setFocusItem (items [focusIndex], true);
4260 	showSelection ();
4261 }
4262 /**
4263  * Sets the zero-relative index of the item which is currently
4264  * at the top of the receiver. This index can change when items
4265  * are scrolled or new items are added and removed.
4266  *
4267  * @param index the index of the top item
4268  *
4269  * @exception SWTException <ul>
4270  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4271  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4272  * </ul>
4273  */
4274 public void setTopIndex (int index) {
4275 	checkWidget ();
4276 	if (!(0 <= index && index < itemsCount)) return;
4277 	int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
4278 	if (itemsCount <= visibleItemCount) return;
4279 	index = Math.min (index, itemsCount - visibleItemCount);
4280 	if (index == topIndex) return;
4281 
4282 	update ();
4283 	int change = topIndex - index;
4284 	topIndex = index;
4285 	ScrollBar vBar = getVerticalBar ();
4286 	if (vBar != null) vBar.setSelection (topIndex);
4287 	if (drawCount <= 0) {
4288 		GC gc = new GC (this);
4289 		gc.copyArea (0, 0, clientArea.width, clientArea.height, 0, change * itemHeight);
4290 		gc.dispose ();
4291 	}
4292 }
4293 /**
4294  * Shows the column.  If the column is already showing in the receiver,
4295  * this method simply returns.  Otherwise, the columns are scrolled until
4296  * the column is visible.
4297  *
4298  * @param column the column to be shown
4299  *
4300  * @exception IllegalArgumentException <ul>
4301  *    <li>ERROR_NULL_ARGUMENT - if the column is null</li>
4302  *    <li>ERROR_INVALID_ARGUMENT - if the column has been disposed</li>
4303  * </ul>
4304  * @exception SWTException <ul>
4305  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4306  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4307  * </ul>
4308  *
4309  * @since 3.0
4310  */
4311 public void showColumn (CTableColumn column) {
4312 	checkWidget ();
4313 	if (column == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
4314 	if (column.isDisposed ()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4315 	if (column.parent != this) return;
4316 
4317 	int x = column.getX ();
4318 	int rightX = x + column.width;
4319 	if (0 <= x && rightX <= clientArea.width) return;	 /* column is fully visible */
4320 
4321 	headerHideToolTip ();
4322 	int absX = 0;	/* the X of the column irrespective of the horizontal scroll */
4323 	CTableColumn[] orderedColumns = getOrderedColumns ();
4324 	for (int i = 0; i < column.getOrderIndex (); i++) {
4325 		absX += orderedColumns [i].width;
4326 	}
4327 	if (x < clientArea.x) { 	/* column is to left of viewport */
4328 		horizontalOffset = absX;
4329 	} else {
4330 		horizontalOffset = absX + column.width - clientArea.width;
4331 	}
4332 	ScrollBar hBar = getHorizontalBar ();
4333 	if (hBar != null) hBar.setSelection (horizontalOffset);
4334 	redraw ();
4335 	if (drawCount <= 0 && header.isVisible ()) header.redraw ();
4336 }
4337 /**
4338  * Shows the item.  If the item is already showing in the receiver,
4339  * this method simply returns.  Otherwise, the items are scrolled until
4340  * the item is visible.
4341  *
4342  * @param item the item to be shown
4343  *
4344  * @exception IllegalArgumentException <ul>
4345  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
4346  *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
4347  * </ul>
4348  * @exception SWTException <ul>
4349  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4350  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4351  * </ul>
4352  *
4353  * @see CTable#showSelection()
4354  */
4355 public void showItem (CTableItem item) {
4356 	checkWidget ();
4357 	if (item == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
4358 	if (item.isDisposed ()) SWT.error (SWT.ERROR_INVALID_ARGUMENT);
4359 	if (item.parent != this) return;
4360 
4361 	int index = item.index;
4362 	int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
4363 	/* nothing to do if item is already in viewport */
4364 	if (topIndex <= index && index < topIndex + visibleItemCount) return;
4365 
4366 	if (index <= topIndex) {
4367 		/* item is above current viewport, so show on top */
4368 		setTopIndex (item.index);
4369 	} else {
4370 		/* item is below current viewport, so show on bottom */
4371 		visibleItemCount = Math.max (visibleItemCount, 1);	/* item to show should be top item */
4372 		setTopIndex (Math.min (index - visibleItemCount + 1, itemsCount - 1));
4373 	}
4374 }
4375 /**
4376  * Shows the selection.  If the selection is already showing in the receiver,
4377  * this method simply returns.  Otherwise, the items are scrolled until
4378  * the selection is visible.
4379  *
4380  * @exception SWTException <ul>
4381  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4382  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4383  * </ul>
4384  *
4385  * @see CTable#showItem(CTableItem)
4386  */
4387 public void showSelection () {
4388 	checkWidget ();
4389 	if (selectedItems.length == 0) return;
4390 	showItem (selectedItems [0]);
4391 }
4392 void sortDescent (int [] items) {
4393 	/* Shell Sort from K&R, pg 108 */
4394 	int length = items.length;
4395 	for (int gap = length / 2; gap > 0; gap /= 2) {
4396 		for (int i = gap; i < length; i++) {
4397 			for (int j = i - gap; j >= 0; j -= gap) {
4398 				if (items [j] <= items [j + gap]) {
4399 					int swap = items [j];
4400 					items [j] = items [j + gap];
4401 					items [j + gap] = swap;
4402 				}
4403 			}
4404 		}
4405 	}
4406 }
4407 void sortAscent (int [] items) {
4408 	/* Shell Sort from K&R, pg 108 */
4409 	int length = items.length;
4410 	for (int gap = length / 2; gap > 0; gap /= 2) {
4411 		for (int i = gap; i < length; i++) {
4412 			for (int j = i - gap; j >= 0; j -= gap) {
4413 				if (items [j] >= items [j + gap]) {
4414 					int swap = items [j];
4415 					items [j] = items [j + gap];
4416 					items [j + gap] = swap;
4417 				}
4418 			}
4419 		}
4420 	}
4421 }
4422 void sortAscent (CTableItem [] items) {
4423 	/* Shell Sort from K&R, pg 108 */
4424 	int length = items.length;
4425 	for (int gap = length / 2; gap > 0; gap /= 2) {
4426 		for (int i = gap; i < length; i++) {
4427 			for (int j = i - gap; j >= 0; j -= gap) {
4428 				if (items [j].index >= items [j + gap].index) {
4429 					CTableItem swap = items [j];
4430 					items [j] = items [j + gap];
4431 					items [j + gap] = swap;
4432 				}
4433 			}
4434 		}
4435 	}
4436 }
4437 void updateColumnWidth (CTableColumn column, int width) {
4438 	headerHideToolTip ();
4439 	int oldWidth = column.width;
4440 	int columnX = column.getX ();
4441 	int x = columnX + oldWidth - 1;	/* -1 ensures that grid line is included */
4442 
4443 	update ();
4444 	GC gc = new GC (this);
4445 	gc.copyArea (x, 0, clientArea.width - x, clientArea.height, columnX + width - 1, 0);	/* dest x -1 offsets x's -1 above */
4446 	if (width > oldWidth) {
4447 		/* column width grew */
4448 		int change = width - oldWidth + 1;	/* +1 offsets x's -1 above */
4449 		/* -1/+1 below ensure that right bound of selection redraws correctly in column */
4450 		redraw (x - 1, 0, change + 1, clientArea.height, false);
4451 	} else {
4452 		int change = oldWidth - width + 1;	/* +1 offsets x's -1 above */
4453 		redraw (clientArea.width - change, 0, change, clientArea.height, false);
4454 	}
4455 	/* the focus box must be repainted because its stipple may become shifted as a result of its new width */
4456 	if (focusItem != null) redrawItem (focusItem.index, true);
4457 
4458 	GC headerGC = new GC (header);
4459 	if (drawCount <= 0 && header.getVisible ()) {
4460 		Rectangle headerBounds = header.getClientArea ();
4461 		header.update ();
4462 		x -= 1;	/* -1 ensures that full header column separator is included */
4463 		headerGC.copyArea (x, 0, headerBounds.width - x, headerBounds.height, columnX + width - 2, 0);	/* dest x -2 offsets x's -1s above */
4464 		if (width > oldWidth) {
4465 			/* column width grew */
4466 			int change = width - oldWidth + 2;	/* +2 offsets x's -1s above */
4467 			header.redraw (x, 0, change, headerBounds.height, false);
4468 		} else {
4469 			int change = oldWidth - width + 2;	/* +2 offsets x's -1s above */
4470 			header.redraw (headerBounds.width - change, 0, change, headerBounds.height, false);
4471 		}
4472 	}
4473 
4474 	column.width = width;
4475 
4476 	/*
4477 	 * Notify column and all items of column width change so that display labels
4478 	 * can be recomputed if needed.
4479 	 */
4480 	column.updateWidth (headerGC);
4481 	headerGC.dispose ();
4482 	for (int i = 0; i < itemsCount; i++) {
4483 		items [i].updateColumnWidth (column, gc);
4484 	}
4485 	gc.dispose ();
4486 
4487 	int maximum = 0;
4488 	for (CTableColumn column2 : columns) {
4489 		maximum += column2.width;
4490 	}
4491 	ScrollBar hBar = getHorizontalBar ();
4492 	if (hBar != null) {
4493 		hBar.setMaximum (Math.max (1, maximum));	/* setting a value of 0 here is ignored */
4494 		if (hBar.getThumb () != clientArea.width) {
4495 			hBar.setThumb (clientArea.width);
4496 			hBar.setPageIncrement (clientArea.width);
4497 		}
4498 		int oldHorizontalOffset = horizontalOffset;	/* hBar.setVisible() can modify horizontalOffset */
4499 		hBar.setVisible (clientArea.width < maximum);
4500 		int selection = hBar.getSelection ();
4501 		if (selection != oldHorizontalOffset) {
4502 			horizontalOffset = selection;
4503 			redraw ();
4504 			if (drawCount <= 0 && header.getVisible ()) header.redraw ();
4505 		}
4506 	}
4507 
4508 	column.notifyListeners (SWT.Resize, new Event ());
4509 	CTableColumn[] orderedColumns = getOrderedColumns ();
4510 	for (int i = column.getOrderIndex () + 1; i < orderedColumns.length; i++) {
4511 		if (!orderedColumns [i].isDisposed ()) {
4512 			orderedColumns [i].notifyListeners (SWT.Move, new Event ());
4513 		}
4514 	}
4515 
4516 	if (itemsCount == 0) redraw ();	/* ensure that static focus rectangle updates properly */
4517 }
4518 /*
4519  * This is a naive implementation that computes the value from scratch.
4520  */
4521 void updateHorizontalBar () {
4522 	if (drawCount > 0) return;
4523 	ScrollBar hBar = getHorizontalBar ();
4524 	if (hBar == null) return;
4525 
4526 	int maxX = 0;
4527 	if (columns.length > 0) {
4528 		for (CTableColumn column : columns) {
4529 			maxX += column.width;
4530 		}
4531 	} else {
4532 		for (int i = 0; i < itemsCount; i++) {
4533 			Rectangle itemBounds = items [i].getCellBounds (0);
4534 			maxX = Math.max (maxX, itemBounds.x + itemBounds.width + horizontalOffset);
4535 		}
4536 	}
4537 
4538 	int clientWidth = clientArea.width;
4539 	if (maxX != hBar.getMaximum ()) {
4540 		hBar.setMaximum (Math.max (1, maxX));	/* setting a value of 0 here is ignored */
4541 	}
4542 	int thumb = Math.min (clientWidth, maxX);
4543 	if (thumb != hBar.getThumb ()) {
4544 		hBar.setThumb (thumb);
4545 		hBar.setPageIncrement (thumb);
4546 	}
4547 	hBar.setVisible (clientWidth < maxX);
4548 
4549 	/* reclaim any space now left on the right */
4550 	if (maxX < horizontalOffset + thumb) {
4551 		horizontalOffset = maxX - thumb;
4552 		hBar.setSelection (horizontalOffset);
4553 		redraw ();
4554 	} else {
4555 		int selection = hBar.getSelection ();
4556 		if (selection != horizontalOffset) {
4557 			horizontalOffset = selection;
4558 			redraw ();
4559 		}
4560 	}
4561 }
4562 /*
4563  * Update the horizontal bar, if needed, in response to an item change (eg.- created,
4564  * disposed, expanded, etc.).  newRightX is the new rightmost X value of the item,
4565  * and rightXchange is the change that led to the item's rightmost X value becoming
4566  * newRightX (so oldRightX + rightXchange = newRightX)
4567  */
4568 void updateHorizontalBar (int newRightX, int rightXchange) {
4569 	if (drawCount > 0) return;
4570 	ScrollBar hBar = getHorizontalBar ();
4571 	if (hBar == null) return;
4572 
4573 	newRightX += horizontalOffset;
4574 	int barMaximum = hBar.getMaximum ();
4575 	if (newRightX > barMaximum) {	/* item has extended beyond previous maximum */
4576 		hBar.setMaximum (newRightX);
4577 		int clientAreaWidth = clientArea.width;
4578 		int thumb = Math.min (newRightX, clientAreaWidth);
4579 		hBar.setThumb (thumb);
4580 		hBar.setPageIncrement (thumb);
4581 		hBar.setVisible (clientAreaWidth <= newRightX);
4582 		return;
4583 	}
4584 
4585 	int previousRightX = newRightX - rightXchange;
4586 	if (previousRightX != barMaximum) {
4587 		/* this was not the rightmost item, so just check for client width change */
4588 		int clientAreaWidth = clientArea.width;
4589 		int thumb = Math.min (barMaximum, clientAreaWidth);
4590 		hBar.setThumb (thumb);
4591 		hBar.setPageIncrement (thumb);
4592 		hBar.setVisible (clientAreaWidth <= barMaximum);
4593 		return;
4594 	}
4595 	updateHorizontalBar ();		/* must search for the new rightmost item */
4596 }
4597 void updateVerticalBar () {
4598 	if (drawCount > 0) return;
4599 	ScrollBar vBar = getVerticalBar ();
4600 	if (vBar == null) return;
4601 
4602 	int pageSize = (clientArea.height - getHeaderHeight ()) / itemHeight;
4603 	int maximum = Math.max (1, itemsCount);	/* setting a value of 0 here is ignored */
4604 	if (maximum != vBar.getMaximum ()) {
4605 		vBar.setMaximum (maximum);
4606 	}
4607 	int thumb = Math.min (pageSize, maximum);
4608 	if (thumb != vBar.getThumb ()) {
4609 		vBar.setThumb (thumb);
4610 		vBar.setPageIncrement (thumb);
4611 	}
4612 	vBar.setVisible (pageSize < maximum);
4613 
4614 	/* reclaim any space now left on the bottom */
4615 	if (maximum < topIndex + thumb) {
4616 		topIndex = maximum - thumb;
4617 		vBar.setSelection (topIndex);
4618 		redraw ();
4619 	} else {
4620 		int selection = vBar.getSelection ();
4621 		if (selection != topIndex) {
4622 			topIndex = selection;
4623 			redraw ();
4624 		}
4625 	}
4626 }
4627 }
4628