void initAccessibility () {
	// TODO: does this all work if CTable is virtual?
	final Accessible accessibleTable = getAccessible();
	accessibleTable.addAccessibleListener(new AccessibleAdapter() {
		public void getName(AccessibleEvent e) {
			/* CTables take their name from the preceding Label, if any. */
			Control[] siblings = getParent().getChildren();
			for (int i = 0; i < siblings.length; i++) {
				if (i != 0 && siblings[i] == CTable.this) {
					Control control = siblings[i-1];
					if (control instanceof Label) {
						e.result = ((Label) control).getText();
					}
				}
			}
		}
		public void getHelp(AccessibleEvent e) {
			/* A CTable's toolTip text (if any) can be used as its help text. */
			e.result = getToolTipText();
		}
	});
	accessibleTable.addAccessibleControlListener(new AccessibleControlAdapter() {
		/* Child IDs are assigned as follows:
		 * - column header ids (if any) are numbered from 0 to columnCount - 1
		 * - cell ids are numbered in row-major order (starting from columnCount if there are columns)
		 * Accessibles are returned in getChild.
		 */
		public void getChild(AccessibleControlEvent e) {
			int childID = e.childID;
			if (childID == ACC.CHILDID_CHILD_AT_INDEX) childID = e.detail; // childID == index
			if (columns.length > 0 && 0 <= childID && childID < columns.length) { // header cell
				CTableColumn column = columns [childID];
				e.accessible = column.getAccessible(accessibleTable);
			} else { // item cell
				int columnCount = columns.length > 0 ? columns.length : 1;
				if (columns.length > 0) childID -= columnCount;
				if (0 <= childID && childID < itemsCount * columnCount) {
					int rowIndex = childID / columnCount;
					int columnIndex = childID - rowIndex * columnCount;
					e.accessible = items[rowIndex].getAccessible (accessibleTable, columnIndex);
				}
			}
		}
		public void getChildAtPoint(AccessibleControlEvent e) {
			Point point = toControl(e.x, e.y);
			if (columns.length > 0 && point.y < getHeaderHeight ()) { // header cell
				int columnIndex = computeColumnIntersect (point.x, 0);
				if (columnIndex != -1) {
					CTableColumn column = columns [columnIndex];
					e.accessible = column.getAccessible (accessibleTable);
				}
			} else { // item cell
				int columnIndex = columns.length > 0 ? computeColumnIntersect (point.x, 0) : 0;
				if (columnIndex != -1) {
					int rowIndex = (point.y - getHeaderHeight ()) / itemHeight + topIndex;
					if (0 <= rowIndex && rowIndex < itemsCount) {
						if (items [rowIndex].getHitBounds ().contains (point)) {  /* considers the x value */
							e.accessible = items[rowIndex].getAccessible (accessibleTable, columnIndex);
						}
					}
				}
			}
		}
		public void getChildCount(AccessibleControlEvent e) {
			e.detail = columns.length > 0 ? columns.length + itemsCount * columns.length : itemsCount;
		}
		public void getChildren(AccessibleControlEvent e) {
			int childIdCount = columns.length > 0 ? columns.length + itemsCount * columns.length : itemsCount;
			Object[] children = new Object[childIdCount];
			for (int i = 0; i < childIdCount; i++) {
				children[i] = new Integer(i);
			}
			e.children = children;
		}
		public void getFocus(AccessibleControlEvent e) {
			e.childID = isFocusControl() ? ACC.CHILDID_SELF : ACC.CHILDID_NONE;
		}
		public void getLocation(AccessibleControlEvent e) {
			Rectangle location = null;
			Point pt = null;
			int childID = e.childID;
			if (childID == ACC.CHILDID_SELF) { // table
				location = getBounds();
				pt = getParent().toDisplay(location.x, location.y);
			} else if (columns.length > 0 && 0 <= childID && childID < columns.length) { // header cell
				CTableColumn column = columns [childID];
				location = new Rectangle (column.getX (), 0, column.getWidth(), getHeaderHeight());
				pt = toDisplay(location.x, location.y);
			} else { // item cell
				int columnCount = columns.length > 0 ? columns.length : 1;
				if (columns.length > 0) childID -= columnCount;
				if (0 <= childID && childID < itemsCount * columnCount) {
					int rowIndex = childID / columnCount;
					int columnIndex = childID - rowIndex * columnCount;
					location = items[rowIndex].getBounds(columnIndex);
					pt = toDisplay(location.x, location.y);
				}
			}
			if (location != null && pt != null) {
				e.x = pt.x;
				e.y = pt.y;
				e.width = location.width;
				e.height = location.height;
			}
		}
		public void getRole(AccessibleControlEvent e) {
			e.detail = e.childID == ACC.CHILDID_SELF ? ACC.ROLE_TABLE : ACC.ROLE_TABLECELL;
		}
		public void getSelection(AccessibleControlEvent e) {
			int columnCount = columns.length > 0 ? columns.length : 1;
			int [] selectionIndices = getSelectionIndices();
			Object[] selectedChildren = new Object[selectionIndices.length * columnCount];
			for (int i = 0; i < selectionIndices.length; i++) {
				int row = selectionIndices[i];
				for (int col = 0; col < columnCount; col++) {
					selectedChildren[i] = new Integer(row * columnCount + col);
				}
			}
			e.children = selectedChildren;
		}
		public void getState(AccessibleControlEvent e) {
			int state = ACC.STATE_NORMAL;
			int childID = e.childID;
			if (childID == ACC.CHILDID_SELF) { // table
				state |= ACC.STATE_FOCUSABLE;
				if (isFocusControl()) {
					state |= ACC.STATE_FOCUSED;
				}
			} else if (columns.length > 0 && 0 <= childID && childID < columns.length) { // header cell
				/* CTable does not support header cell focus or selection. */
				state |= ACC.STATE_SIZEABLE;
			} else { // item cell
				int columnCount = columns.length > 0 ? columns.length : 1;
				if (columns.length > 0) childID -= columnCount;
				if (0 <= childID && childID < itemsCount * columnCount) {
					/* CTable does not support cell selection (only row selection). */
					int rowIndex = childID / columnCount;
					state |= ACC.STATE_SELECTABLE;
					if (isFocusControl()) {
						state |= ACC.STATE_FOCUSABLE;
					}
					if (items[rowIndex].isSelected()) {
						state |= ACC.STATE_SELECTED;
						if (isFocusControl()) {
							state |= ACC.STATE_FOCUSED;
						}
					}
				}
			}
			e.detail = state;
		}
	});
	accessibleTable.addAccessibleTableListener(new AccessibleTableAdapter() {
		public void deselectColumn(AccessibleTableEvent e) {
			/* CTable does not support column selection. */
		}
		public void deselectRow(AccessibleTableEvent e) {
			deselect(e.row);
			e.result = ACC.OK;
		}
		public void getCaption(AccessibleTableEvent e) {
			// TODO: What is a caption? How does it differ from name? Should app supply?
			e.result = "This is the Custom Table's Test Caption";
		}
		public void getCell(AccessibleTableEvent e) {
			int index = e.row;
			if (0 <= index && index < itemsCount) {
				CTableItem row = items [index];
				index = e.column;
				if (columns.length == 0 || 0 <= index && index < columns.length) {
					e.accessible = row.getAccessible (accessibleTable, index);
				}
			}
		}
		public void getColumnCount(AccessibleTableEvent e) {
			int columnCount = columns.length > 0 ? columns.length : 1;
			e.count = columnCount;
		}
		public void getColumnDescription(AccessibleTableEvent e) {
			// TODO: What is a description? How does it differ from name? Should app supply?
			e.result = "This is the Custom Table's Test Description for column " + e.column;
		}
//		public void getColumnHeader(AccessibleTableEvent e) {
//			e.accessible = header.getAccessible();
//		}
		public void getColumnHeaderCells(AccessibleTableEvent e) {
			if (columns.length == 0) {
				/* The CTable is being used as a list, and there are no headers. */
				e.accessibles = null;
			} else {
				Accessible[] accessibles = new Accessible[columns.length];
				for (int i = 0; i < columns.length; i++) {
					CTableColumn column = columns [i];
					accessibles[i] = column.getAccessible (accessibleTable);
				}
				e.accessibles = accessibles;
			}
		}
		public void getRowCount(AccessibleTableEvent e) {
			e.count = itemsCount;
		}
		public void getRowDescription(AccessibleTableEvent e) {
			// TODO: What is a description? How does it differ from name? Should app supply?
			e.result = "This is the Custom Table's Test Description for row " + e.row;
		}
		public void getRowHeader(AccessibleTableEvent e) {
			/* CTable does not support row headers. */
		}
		public void getSelectedCellCount(AccessibleTableEvent e) {
			int columnCount = columns.length > 0 ? columns.length : 1;
			e.count = selectedItems.length * columnCount;
		}
		public void getSelectedCells(AccessibleTableEvent e) {
			int columnCount = columns.length > 0 ? columns.length : 1;
			Accessible[] accessibles = new Accessible[selectedItems.length * columnCount];
			for (int r = 0; r < selectedItems.length; r++) {
				CTableItem row = selectedItems [r];
				for (int c = 0; c < columnCount; c++)
					accessibles[r+c] = row.getAccessible (accessibleTable, c);
			}
			e.accessibles = accessibles;
		}
		public void getSelectedColumnCount(AccessibleTableEvent e) {
			e.count = 0; /* CTable does not support column selection. */
		}
		public void getSelectedColumns(AccessibleTableEvent e) {
			/* CTable does not support column selection. */
		}
		public void getSelectedRowCount(AccessibleTableEvent e) {
			e.count = selectedItems.length;
		}
		public void getSelectedRows(AccessibleTableEvent e) {
			int[] selectedIndices = new int[selectedItems.length];
			for (int i = 0; i < selectedItems.length; i++) {
				selectedIndices[i] = selectedItems [i].index;
			}
			e.selected = selectedIndices;
		}
		public void getSummary(AccessibleTableEvent e) {
			// TODO: What is a summary? How does it differ from name? Should app supply?
			e.result = "This is the Custom Table's Summary";
		}
		public void isColumnSelected(AccessibleTableEvent e) {
			e.isSelected = false; /* CTable does not support column selection. */
		}
		public void isRowSelected(AccessibleTableEvent e) {
			e.isSelected = isSelected(e.row);
		}
		public void selectColumn(AccessibleTableEvent e) {
			/* CTable does not support column selection. */
		}
		public void selectRow(AccessibleTableEvent e) {
			select(e.row);
			e.result = ACC.OK;
		}
		public void setSelectedColumn(AccessibleTableEvent e) {
			/* CTable does not support column selection. */
		}
		public void setSelectedRow(AccessibleTableEvent e) {
			setSelection(e.row);
			e.result = ACC.OK;
		}
	});
}