Index: src/org/eclipse/mat/ui/MemoryAnalyserPlugin.java =================================================================== --- src/org/eclipse/mat/ui/MemoryAnalyserPlugin.java (revision 1089) +++ src/org/eclipse/mat/ui/MemoryAnalyserPlugin.java (working copy) @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2010 SAP AG and others. + * Copyright (c) 2008, 2011 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,6 +11,7 @@ *******************************************************************************/ package org.eclipse.mat.ui; +import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -116,6 +117,11 @@ private ErrorLogHandler errorLogHandler; private boolean useParentHandlers; + // Mappings to permit textual descriptions of Images to be recovered from Images. + // TODO - clean out these maps when the plugin is disabled + private Map imageTextMap = new HashMap(20); + private Map descriptorTextMap = new HashMap(20); + public MemoryAnalyserPlugin() { } @@ -146,6 +152,9 @@ for (Image image : imageCache.values()) image.dispose(); imageCache.clear(); + // Clear mappings from Image/Descriptor to descriptive text. + imageTextMap.clear(); + descriptorTextMap.clear(); logger.removeHandler(errorLogHandler); logger.setUseParentHandlers(useParentHandlers); @@ -166,7 +175,8 @@ public static ImageDescriptor getImageDescriptor(String path) { - return AbstractUIPlugin.imageDescriptorFromPlugin(PLUGIN_ID, path); + // Use singleton instance so that ImageDescriptor can be mapped to text. + return MemoryAnalyserPlugin.getDefault().getPluginImageDescriptor(path); } public static Image getImage(String name) @@ -174,6 +184,19 @@ return MemoryAnalyserPlugin.getDefault().getImage(getImageDescriptor(name)); } + private ImageDescriptor getPluginImageDescriptor(String path) + { + ImageDescriptor descriptor = AbstractUIPlugin.imageDescriptorFromPlugin(PLUGIN_ID, path); + if (descriptor != null) + { // Add map entry for new descriptor to appropriate text. + // This should not result in a memory leak, assuming that two + // equivalent ImageDescriptors match under equals(). + // This is already assumed in the usage of imageCache. + descriptorTextMap.put(descriptor, getIconString(path)); + } + return descriptor; + } + public Image getImage(ImageDescriptor descriptor) { Image image = imageCache.get(descriptor); @@ -181,6 +204,9 @@ { image = descriptor.createImage(); imageCache.put(descriptor, image); + // Map new Image to descriptive text. + // Should not cause memory leak as this must be a new descriptor. + imageTextMap.put(image, descriptorTextMap.get(descriptor)); } return image; } @@ -200,6 +226,10 @@ { descriptor = ImageDescriptor.createFromURL(path); imagePathCache.put(pathKey, descriptor); + // Map new descriptor to descriptive text for the Image. + // Should not cause a memory leak as this is a new descriptor, + // and equivalent descriptors should overwrite existing entries. + descriptorTextMap.put(descriptor, getIconString(path)); } return descriptor; @@ -222,6 +252,50 @@ return imageDescriptor == null ? null : getImage(imageDescriptor); } + /** + * @param url + * URL of image file for which a description is required. + * @return String with meaningful description of image given by input url. + */ + private String getIconString(URL url) + { + return getIconString(url.getPath()); // Delegate lookup based on path + // element of URL. + } + + /** + * @param path + * String representing the path to an image file for which a + * description is needed. + * @return String with meaningful description of image located at input + * path. + */ + private String getIconString(String path) + { + // TODO - implement mapping from icon file pathnames to suitable NLS + // readable descriptions. + // See OpenIconAssistAction.java for a starting point. + // Temporary non-NLS implementation follows: base file name with _ + // replaced with ' '. + return new File(path).getName().split("\\.")[0].replace('_', ' '); // Should + // not + // throw + // Exception. + } + + /** + * @param image + * The Image for which descriptive text is to be retrieved. + * @return Descriptive text for the Image object, retrieved from + * imageTextMap. + */ + public String getImageText(Image image) + { + String text = imageTextMap.get(image); // May be null + return (text == null) ? "Unknown Image" : text; // Return default string + // if image not in map. + } + public IExtensionTracker getExtensionTracker() { return tracker; Index: src/org/eclipse/mat/ui/compare/CompareBasketView.java =================================================================== --- src/org/eclipse/mat/ui/compare/CompareBasketView.java (revision 1089) +++ src/org/eclipse/mat/ui/compare/CompareBasketView.java (working copy) @@ -43,6 +43,7 @@ import org.eclipse.mat.ui.MemoryAnalyserPlugin; import org.eclipse.mat.ui.Messages; import org.eclipse.mat.ui.QueryExecution; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.actions.QueryDropDownMenuAction; import org.eclipse.mat.ui.editor.AbstractEditorPane; import org.eclipse.mat.ui.editor.MultiPaneEditor; @@ -97,6 +98,7 @@ { tableViewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION); this.table = tableViewer.getTable(); + AccessibleCompositeAdapter.access(table); TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.LEFT); TableColumn tableColumn = column.getColumn(); Index: src/org/eclipse/mat/ui/internal/browser/QueryBrowserPopup.java =================================================================== --- src/org/eclipse/mat/ui/internal/browser/QueryBrowserPopup.java (revision 1089) +++ src/org/eclipse/mat/ui/internal/browser/QueryBrowserPopup.java (working copy) @@ -38,6 +38,7 @@ import org.eclipse.mat.ui.MemoryAnalyserPlugin; import org.eclipse.mat.ui.Messages; import org.eclipse.mat.ui.QueryExecution; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.editor.MultiPaneEditor; import org.eclipse.mat.ui.util.ErrorHelper; import org.eclipse.mat.ui.util.IPolicy; @@ -222,6 +223,7 @@ textLayout.setFont(table.getFont()); textLayout.setText(Messages.QueryBrowserPopup_Categories); textLayout.setFont(boldFont); + AccessibleCompositeAdapter.access(table); tableColumnLayout.setColumnData(new TableColumn(table, SWT.NONE), new ColumnWeightData(100, 100)); table.getShell().addControlListener(new ControlAdapter() Index: src/org/eclipse/mat/ui/internal/query/arguments/ArgumentsTable.java =================================================================== --- src/org/eclipse/mat/ui/internal/query/arguments/ArgumentsTable.java (revision 1089) +++ src/org/eclipse/mat/ui/internal/query/arguments/ArgumentsTable.java (working copy) @@ -41,6 +41,7 @@ import org.eclipse.mat.snapshot.model.IObject; import org.eclipse.mat.snapshot.query.IHeapObjectArgument; import org.eclipse.mat.ui.Messages; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.internal.query.arguments.LinkEditor.Mode; import org.eclipse.mat.ui.internal.query.arguments.TextEditor.DecoratorType; import org.eclipse.mat.util.MessageUtil; @@ -110,6 +111,7 @@ table.setFont(parentFont); table.setLinesVisible(true); table.setHeaderVisible(true); + AccessibleCompositeAdapter.access(table); TableColumn column = new TableColumn(table, SWT.NONE); column.setText(ARGUMENT); Index: src/org/eclipse/mat/ui/internal/acquire/ProviderConfigurationDialog.java =================================================================== --- src/org/eclipse/mat/ui/internal/acquire/ProviderConfigurationDialog.java (revision 1089) +++ src/org/eclipse/mat/ui/internal/acquire/ProviderConfigurationDialog.java (working copy) @@ -28,6 +28,7 @@ import org.eclipse.mat.query.registry.ArgumentFactory; import org.eclipse.mat.snapshot.acquire.IHeapDumpProvider; import org.eclipse.mat.ui.Messages; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.internal.browser.QueryContextHelp; import org.eclipse.mat.util.IProgressListener; import org.eclipse.mat.util.MessageUtil; @@ -259,6 +260,7 @@ availableProvidersTable.setHeaderVisible(true); availableProvidersTable.setLinesVisible(true); + AccessibleCompositeAdapter.access(availableProvidersTable); Collection providers = HeapDumpProviderRegistry.instance().getHeapDumpProviders(); for (HeapDumpProviderDescriptor heapDumpProviderDescriptor : providers) Index: src/org/eclipse/mat/ui/internal/viewer/RefinedTableViewer.java =================================================================== --- src/org/eclipse/mat/ui/internal/viewer/RefinedTableViewer.java (revision 1089) +++ src/org/eclipse/mat/ui/internal/viewer/RefinedTableViewer.java (working copy) @@ -18,6 +18,7 @@ import org.eclipse.mat.query.refined.RefinedTable; import org.eclipse.mat.query.refined.TotalsRow; import org.eclipse.mat.query.registry.QueryResult; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.editor.AbstractEditorPane; import org.eclipse.mat.ui.editor.MultiPaneEditor; import org.eclipse.swt.SWT; @@ -176,6 +177,7 @@ table = new Table(parent, SWT.VIRTUAL | SWT.FULL_SELECTION | SWT.MULTI); table.setHeaderVisible(true); table.setLinesVisible(true); + AccessibleCompositeAdapter.access(table); table.addListener(SWT.SetData, new Listener() { Index: src/org/eclipse/mat/ui/internal/viewer/RefinedTreeViewer.java =================================================================== --- src/org/eclipse/mat/ui/internal/viewer/RefinedTreeViewer.java (revision 1089) +++ src/org/eclipse/mat/ui/internal/viewer/RefinedTreeViewer.java (working copy) @@ -26,6 +26,7 @@ import org.eclipse.mat.query.refined.TotalsRow; import org.eclipse.mat.query.registry.QueryResult; import org.eclipse.mat.ui.Messages; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.editor.AbstractEditorPane; import org.eclipse.mat.ui.editor.AbstractPaneJob; import org.eclipse.mat.ui.editor.MultiPaneEditor; @@ -549,6 +550,7 @@ tree = new Tree(parent, SWT.FULL_SELECTION | SWT.MULTI); tree.setHeaderVisible(true); tree.setLinesVisible(true); + AccessibleCompositeAdapter.access(tree); return tree; } Index: src/org/eclipse/mat/ui/internal/views/NavigatorViewPage.java =================================================================== --- src/org/eclipse/mat/ui/internal/views/NavigatorViewPage.java (revision 1089) +++ src/org/eclipse/mat/ui/internal/views/NavigatorViewPage.java (working copy) @@ -38,6 +38,7 @@ import org.eclipse.mat.ui.Messages; import org.eclipse.mat.ui.QueryExecution; import org.eclipse.mat.ui.MemoryAnalyserPlugin.ISharedImages; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.compare.CompareBasketView; import org.eclipse.mat.ui.editor.AbstractEditorPane; import org.eclipse.mat.ui.editor.CompositeHeapEditorPane; @@ -154,6 +155,7 @@ { treeViewer = new TreeViewer(parent); createContextMenu(treeViewer.getTree()); + AccessibleCompositeAdapter.access(treeViewer.getTree()); treeViewer.setContentProvider(new NavigatorContentProvider()); treeViewer.setLabelProvider(new NavigatorLabelProvider()); Index: src/org/eclipse/mat/ui/internal/views/SnapshotHistoryView.java =================================================================== --- src/org/eclipse/mat/ui/internal/views/SnapshotHistoryView.java (revision 1089) +++ src/org/eclipse/mat/ui/internal/views/SnapshotHistoryView.java (working copy) @@ -31,6 +31,7 @@ import org.eclipse.mat.ui.SnapshotHistoryService; import org.eclipse.mat.ui.MemoryAnalyserPlugin.ISharedImages; import org.eclipse.mat.ui.SnapshotHistoryService.Entry; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.editor.PathEditorInput; import org.eclipse.mat.ui.snapshot.views.SnapshotOutlinePage; import org.eclipse.mat.ui.util.ErrorHelper; @@ -196,6 +197,7 @@ tableColumn.setText(Messages.SnapshotHistoryView_RecentlyUsedFiles); tableColumn.setWidth(400); table.setHeaderVisible(true); + AccessibleCompositeAdapter.access(table); table.addMouseListener(new MouseAdapter() { Index: src/org/eclipse/mat/ui/accessibility/AccessibleCompositeAdapter.java =================================================================== --- src/org/eclipse/mat/ui/accessibility/AccessibleCompositeAdapter.java (revision 0) +++ src/org/eclipse/mat/ui/accessibility/AccessibleCompositeAdapter.java (revision 0) @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2011 IBM Corporation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial implementation + *******************************************************************************/ + +package org.eclipse.mat.ui.accessibility; + +import org.eclipse.mat.ui.MemoryAnalyserPlugin; +import org.eclipse.swt.accessibility.ACC; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleControlAdapter; +import org.eclipse.swt.accessibility.AccessibleControlEvent; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +/** + * AccessibleCompositeAdapter Accessibility utility class provides a single + * encapsulated implementation of accessibility enhancements for Table & Tree + * Controls within MAT. The implementation is essentially the same for both + * types of view, adding an Accessible Listener to cause a specially constructed + * name for each item to be passed to the accessibility client (Screen Reader). + * + * @author Jonathan Lawrence + */ +public class AccessibleCompositeAdapter +{ + + // Constants for String construction. + private static final char space = ' '; + + // Public methods provide interface ensuring only Table or Tree are used. + /** + * @param table + * The Table to decorate with Accessible. + */ + public static void access(Table table) + { + access(table, ACC.ROLE_TABLE); // Delegate to generic private method. + } + + /** + * @param tree + * The Tree to decorate with Accessible. + */ + public static void access(Tree tree) + { + access(tree, ACC.ROLE_TREE); // Delegate to generic private method. + } + + /** + * @param composite + * The Composite (Table/Tree) to add Accessibility + * @param role + * The ACC.ROLE constant representing the type of Composite. + */ + private static void access(final Composite composite, final int role) + { + // Add addAccessibleListener to override getName. + composite.getAccessible().addAccessibleListener(new AccessibleAdapter() + { + + @Override + public void getName(AccessibleEvent e) + { + if (e.childID == ACC.CHILDID_SELF) + { + // TODO - provide a suitable name for the Tree/Table. + } + else + { // Name is required for a child of the Composite. + + // Get the item... + Item item = null; + try + { + // Defensive coding. For Tree Controls, JAWS 12 + // sometimes + // calls this method with invalid childIDs. + int maxchild = getItemCount(composite, role); + if (e.childID >= 0 && e.childID < maxchild) + { // Valid range + // Get Item + item = getItem(composite, role, e.childID); + } + // Otherwise use first selected item if any. + else + { + item = getSelection(composite, role)[0]; + } + } + catch (IndexOutOfBoundsException ie) + { // Do nothing + // item will be null, no name will be returned in e. + + } // catch() + + // Construct a row of readable text for the Table/TreeItem + if (item != null) // Valid item + { + int ncol = getColumnCount(composite, role); + int[] colorder = getColumnOrder(composite, role); + StringBuffer rowbuf = new StringBuffer(); + Item column = null; + Image image = null; + for (int icol = 0; icol < ncol; icol++) // For each + // column + { + int jcol = colorder[icol]; // The index of the + // column when created. + image = getImage(item, role, jcol); // Get image if + // any + if (image != null) // Image exists in this column + { // Append the descriptive text for this image + rowbuf.append(MemoryAnalyserPlugin.getDefault().getImageText(image)); + rowbuf.append(space); + } + column = getColumn(composite, role, jcol); // Get + // relevant + // Column + rowbuf.append(column.getText()); // Append column + // header + rowbuf.append(space); + rowbuf.append(getText(item, role, jcol)); // Append + // column + // content + rowbuf.append(space); + } + e.result = rowbuf.toString(); + } // if() + } // if() + } // getName() + + }); + + // Experimentation with JAWS 12 shows that the following is also + // required to ensure + // that JAWS will read out the name returned for the Item. + composite.getAccessible().addAccessibleControlListener(new AccessibleControlAdapter() + { + @Override + public void getRole(AccessibleControlEvent e) + { + if (e.childID == ACC.CHILDID_SELF) + { + e.detail = role; // The ACC.ROLE constant for the Control. + } + else + { + e.detail = ACC.ROLE_TEXT; // Return TEXT for an Item. + } // if() + } // getRole() + }); + + } + + // ////////////////////////////////////////////////////////////// + // Utility methods to map inquiry methods onto the Control-type + // specific variants for Table or Tree, as required. + // ////////////////////////////////////////////////////////////// + + private static Item getColumn(Composite composite, int role, int index) + { + return (role == ACC.ROLE_TABLE) ? ((Table) composite).getColumn(index) : ((Tree) composite).getColumn(index); + } + + private static int getColumnCount(Composite composite, int role) + { + return (role == ACC.ROLE_TABLE) ? ((Table) composite).getColumnCount() : ((Tree) composite).getColumnCount(); + } + + private static int[] getColumnOrder(Composite composite, int role) + { + return (role == ACC.ROLE_TABLE) ? ((Table) composite).getColumnOrder() : ((Tree) composite).getColumnOrder(); + } + + private static Item getItem(Composite composite, int role, int index) + { + return (role == ACC.ROLE_TABLE) ? ((Table) composite).getItem(index) : ((Tree) composite).getItem(index); + } + + private static Item[] getSelection(Composite composite, int role) + { + return (role == ACC.ROLE_TABLE) ? ((Table) composite).getSelection() : ((Tree) composite).getSelection(); + } + + private static int getItemCount(Composite composite, int role) + { + return (role == ACC.ROLE_TABLE) ? ((Table) composite).getItemCount() : ((Tree) composite).getItemCount(); + } + + private static Image getImage(Item item, int role, int index) + { + return (role == ACC.ROLE_TABLE) ? ((TableItem) item).getImage(index) : ((TreeItem) item).getImage(index); + } + + private static String getText(Item item, int role, int index) + { + return (role == ACC.ROLE_TABLE) ? ((TableItem) item).getText(index) : ((TreeItem) item).getText(index); + } + +} Index: src/org/eclipse/mat/ui/snapshot/actions/OpenIconAssistAction.java =================================================================== --- src/org/eclipse/mat/ui/snapshot/actions/OpenIconAssistAction.java (revision 1089) +++ src/org/eclipse/mat/ui/snapshot/actions/OpenIconAssistAction.java (working copy) @@ -37,6 +37,7 @@ import org.eclipse.mat.query.registry.QueryRegistry; import org.eclipse.mat.ui.MemoryAnalyserPlugin; import org.eclipse.mat.ui.Messages; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.snapshot.ImageHelper; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; @@ -284,6 +285,7 @@ shell = new Shell(parent, checkStyle(style)); TableViewer viewer = new TableViewer(shell, SWT.WRAP | SWT.V_SCROLL | SWT.H_SCROLL); table = viewer.getTable(); + AccessibleCompositeAdapter.access(table); TableColumn tc1 = new TableColumn(table, SWT.CENTER); TableColumn tc2 = new TableColumn(table, SWT.LEFT); tc1.setWidth(25); Index: src/org/eclipse/mat/ui/snapshot/views/SnapshotOutlinePage.java =================================================================== --- src/org/eclipse/mat/ui/snapshot/views/SnapshotOutlinePage.java (revision 1089) +++ src/org/eclipse/mat/ui/snapshot/views/SnapshotOutlinePage.java (working copy) @@ -28,6 +28,7 @@ import org.eclipse.jface.viewers.Viewer; import org.eclipse.mat.snapshot.ISnapshot; import org.eclipse.mat.snapshot.SnapshotInfo; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.snapshot.editor.ISnapshotEditorInput; import org.eclipse.mat.util.MessageUtil; import org.eclipse.swt.SWT; @@ -253,6 +254,8 @@ createColumns(); + AccessibleCompositeAdapter.access(treeViewer.getTree()); + treeViewer.getTree().setLinesVisible(true); treeViewer.getTree().setHeaderVisible(true); Index: src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorView.java =================================================================== --- src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorView.java (revision 1089) +++ src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorView.java (working copy) @@ -56,6 +56,7 @@ import org.eclipse.mat.snapshot.model.IPrimitiveArray; import org.eclipse.mat.ui.MemoryAnalyserPlugin; import org.eclipse.mat.ui.Messages; +import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; import org.eclipse.mat.ui.snapshot.ImageHelper; import org.eclipse.mat.ui.snapshot.editor.HeapEditor; import org.eclipse.mat.ui.snapshot.editor.ISnapshotEditorInput; @@ -443,6 +444,7 @@ topTableViewer = new TableViewer(composite, SWT.FULL_SELECTION | SWT.MULTI); Table table = topTableViewer.getTable(); + AccessibleCompositeAdapter.access(table); TableColumnLayout columnLayout = new TableColumnLayout(); composite.setLayout(columnLayout); @@ -568,6 +570,7 @@ classHierarchyTree.setLabelProvider(new HierarchyLabelProvider(-1)); Tree tree = classHierarchyTree.getTree(); + AccessibleCompositeAdapter.access(tree); TreeColumnLayout columnLayout = new TreeColumnLayout(); composite.setLayout(columnLayout); @@ -586,6 +589,7 @@ final TableViewer viewer = new TableViewer(composite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.MULTI); Table table = viewer.getTable(); + AccessibleCompositeAdapter.access(table); viewer.setContentProvider(new FieldsContentProvider()); viewer.setLabelProvider(new FieldsLabelProvider(this, table.getFont()));