Project: SWT
Bug 202672 Accessibility: ROLE_TABLE does not work correctly on GTK
Reported: 2007-09-07 14:00:31 | Status: resolved fixed

Returning a role of ACC.ROLE_TABLE[*] in Accessible.getRole() currently confuses GTK's gnopernicus, and as a result it does not speak the name strings of table headers or cells. Until this bug is fixed, a workaround for this is to return ACC.ROLE_LIST for the table, and ACC.ROLE_LISTITEM for the table cells when SWT is running on GTK. Here is an example snippet that shows this workaround (see the getRole method): import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import org.eclipse.swt.accessibility.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.graphics.*; public class AccessibleCustomTable { public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display, SWT.SHELL_TRIM); shell.setLayout(new GridLayout()); CustomTable table = new CustomTable(shell, SWT.BORDER); table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); table.setColumnCount(3); table.setRowCount(5); for (int row = 0; row < table.getRowCount(); row++) { for (int col = 0; col < table.getColumnCount(); col++) { CustomTableCell cell = new CustomTableCell(table, SWT.NONE); cell.setText("cell " + col + "/" + row); } } Button button = new Button(shell, SWT.PUSH); button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); button.setText("Sibling control for testing focus changes"); table.setFocus(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } static class CustomTableCell { String text; Rectangle bounds; public CustomTableCell(Composite parent, int style) { ((CustomTable)parent).addCell(this); } public void setText(String text) { this.text = text; } public String getText() { return text; } public Rectangle getBounds() { return bounds; } } static class CustomTable extends Composite { CustomTableCell cells[] = new CustomTableCell[0]; int columnCount, rowCount; int focusIndex = 0; public CustomTable(Composite parent, int style) { super(parent, style); initAccessible(); int[] events = new int[]{ SWT.FocusIn, SWT.FocusOut, SWT.KeyDown, SWT.MouseDown, SWT.Paint, SWT.Resize, SWT.Traverse, }; for (int i = 0; i < events.length; i++) { addListener(events[i], new Listener() { public void handleEvent(Event event) { switch (event.type) { case SWT.FocusIn: onFocus(event); break; case SWT.FocusOut: onFocus(event); break; case SWT.KeyDown: onKeyDown(event); break; case SWT.MouseDown: onMouseDown(event); break; case SWT.Paint: onPaint(event); break; case SWT.Resize: onResize(event); break; case SWT.Traverse: onTraverse(event); break; } } }); } } public void setColumnCount(int columnCount) { this.columnCount = columnCount; } public int getColumnCount() { return columnCount; } public void setRowCount(int rowCount) { this.rowCount = rowCount; } public int getRowCount() { return rowCount; } void addCell(CustomTableCell customTableCell) { CustomTableCell[] newCells = new CustomTableCell[cells.length + 1]; System.arraycopy(cells, 0, newCells, 0, cells.length); cells = newCells; cells[cells.length - 1] = customTableCell; } protected void onFocus(Event event) { redraw(); getAccessible().setFocus(focusIndex); } void onKeyDown(Event event) { // Hook a key event so that the custom table can take focus. } void onMouseDown(Event event) { Point pt = new Point(event.x, event.y); for (int index = 0; index < cells.length; index++) { if (cells[index].getBounds().contains(pt)) { focusIndex = index; break; } } redraw(); getAccessible().setFocus(focusIndex); } void onPaint(Event event) { GC gc = event.gc; // Draw the grid lines Rectangle bounds = getBounds(); int columnWidth = bounds.width / columnCount; int rowHeight = bounds.height / rowCount; int x = columnWidth; for (int col = 0; col < columnCount - 1; col++) { gc.drawLine(x, 0, x, bounds.height); x += columnWidth; } int y = rowHeight; for (int row = 0; row < rowCount - 1; row++) { gc.drawLine(0, y, bounds.width, y); y += rowHeight; } // Draw the text centered in the cells int fontHeight = getFont().getFontData()[0].getHeight(); int yOffset = (rowHeight - fontHeight) / 2; for (int col = 0; col < columnCount; col++) { for (int row = 0; row < rowCount; row++) { int index = row * columnCount + col; String text = cells[index].getText(); int stringWidth = gc.stringExtent(text).x; int xOffset = (columnWidth - stringWidth) / 2; gc.drawString(text, col * columnWidth + xOffset, row * rowHeight + yOffset, true); } } // Draw the focus rectangle if (isFocusControl()) { int row = focusIndex / columnCount; int col = focusIndex - row * columnCount; gc.drawFocus(col * columnWidth, row * rowHeight, columnWidth, rowHeight); } } void onResize(Event event) { // Cache the bounds of each cell Rectangle bounds = getBounds(); int columnWidth = bounds.width / columnCount; int rowHeight = bounds.height / rowCount; int x = 0; for (int col = 0; col < columnCount; col++) { int y = 0; for (int row = 0; row < rowCount; row++) { int index = row * columnCount + col; cells[index].bounds = new Rectangle(x, y, columnWidth, rowHeight); y += rowHeight; } x += columnWidth; } } void onTraverse(Event event) { switch (event.detail) { case SWT.TRAVERSE_ESCAPE: case SWT.TRAVERSE_RETURN: case SWT.TRAVERSE_TAB_NEXT: case SWT.TRAVERSE_TAB_PREVIOUS: if (isFocusControl()) event.doit = true; break; case SWT.TRAVERSE_ARROW_NEXT: focusIndex++; if (focusIndex >= cells.length) focusIndex = 0; event.doit = true; event.detail = SWT.TRAVERSE_NONE; getAccessible().setFocus(focusIndex); break; case SWT.TRAVERSE_ARROW_PREVIOUS: if (focusIndex <= 0) focusIndex = cells.length; focusIndex--; event.doit = true; event.detail = SWT.TRAVERSE_NONE; getAccessible().setFocus(focusIndex); break; } redraw(); } void initAccessible() { Accessible accessible = getAccessible(); accessible.addAccessibleListener(new AccessibleAdapter() { public void getName(AccessibleEvent e) { if (e.childID != ACC.CHILDID_SELF) { e.result = cells[e.childID].text; } } public void getHelp(AccessibleEvent e) { if (e.childID != ACC.CHILDID_SELF) { int row = e.childID / columnCount; int col = e.childID - row * columnCount; e.result = "This is the cell in row " + row + " column " + col; } } }); accessible.addAccessibleControlListener(new AccessibleControlAdapter() { public void getChildAtPoint(AccessibleControlEvent e) { int childID = ACC.CHILDID_NONE; Point pt = toControl(e.x, e.y); for (int index = 0; index < cells.length; index++) { if (cells[index].getBounds().contains(pt)) { childID = index; break; } } if (childID == ACC.CHILDID_NONE) { if (getBounds().contains(pt)) { childID = ACC.CHILDID_SELF; } } e.childID = childID; } public void getLocation(AccessibleControlEvent e) { Rectangle bounds = getBounds(); if (e.childID != ACC.CHILDID_SELF) { bounds = cells[e.childID].getBounds(); } Point pt = toDisplay(bounds.x, bounds.y); e.x = pt.x; e.y = pt.y; e.width = bounds.width; e.height = bounds.height; } public void getChildCount(AccessibleControlEvent e) { e.detail = cells.length; } public void getFocus(AccessibleControlEvent e) { if (isFocusControl()) { e.childID = focusIndex; } else { e.childID = ACC.CHILDID_NONE; } } public void getRole(AccessibleControlEvent e) { if (e.childID == ACC.CHILDID_SELF) { e.detail = (SWT.getPlatform().equals("gtk")) ? ACC.ROLE_LIST : ACC.ROLE_TABLE; } else if (e.childID >= 0 && e.childID < cells.length) { e.detail = (SWT.getPlatform().equals("gtk")) ? ACC.ROLE_LISTITEM : ACC.ROLE_TABLECELL; } } public void getSelection(AccessibleControlEvent e) { e.childID = (focusIndex == -1) ? ACC.CHILDID_NONE : focusIndex; } public void getState(AccessibleControlEvent e) { int state = 0; if (e.childID == ACC.CHILDID_SELF) { state = ACC.STATE_NORMAL; } else if (e.childID >= 0 && e.childID < cells.length) { state = ACC.STATE_SELECTABLE; if (isFocusControl()) { state |= ACC.STATE_FOCUSABLE; } if (e.childID == focusIndex) { state |= ACC.STATE_SELECTED; if (isFocusControl()) { state |= ACC.STATE_FOCUSED; } } } e.detail = state; } public void getChildren(AccessibleControlEvent e) { Object[] children = new Object[cells.length]; for (int i = 0; i < cells.length; i++) { children[i] = new Integer(i); } e.children = children; } }); } } }

Files changed in fix:
bundles/org.eclipse.swt/Eclipse SWT Accessibility/gtk/org/eclipse/swt/accessibility/AccessibleObject.java