Skip to main content

Creating Accessible Applications in Eclipse (2nd Edition)

Version 0.2
This is an early version of this document. Additional reviews and updates are anticipated.
Readers with constructive suggestions for improvements may contact the authors. Any supplemental code fragments, examples, links to such, or additions to knowledge are greatly appreciated.

1. Table of Contents

Creating Accessible Application in Eclipse (2nd Edition)

1. Table of Contents

2 Introduction

  • 2.1 Purpose
    This document provides the necessary background and information to write an accessible Eclipse-based client application. Creating an accessible application has important business implications. Government agencies, educational institutions as well as private companies have accessibility requirements for software purchased. For your application to comply with accessibility standards, the software and content must be enable for use by people with disabilities
  • 2.2 Intended audience
  • 2.3 Document contents
  • 2.4 Acknowledgements

3 How Eclipse enables accessibility

4 Accessibility Design and Additions

5 Enabling code

6 Custom Controls

7 Composite Custom Controls

8 Canvas Custom Controls

9 IAccessible2 enhancements

10 Accessible Graphics using GEF

11 Testing tools

12 Reference, links, and further reading

13 Code Samples


1.1 History of recent changes

Version Changes
0.2 Compilation of previous changes to the document

Authors of 2nd Edition

Brian Cragun, IBM AbilityLab

Susann Keohane, IBM AbilityLab

Acknowledgements:
The authors acknowledge the contributions of the 1st edition authors, listed below; they provided a base of content which this version is built upon. The authors also acknowledge and thank Carolyn MacLeod, whois the lead developer of the Eclipse implementation and author of a great number of code snippets and examples.

1.2 Contributors to 1st Edition (2005)

Mark Pilgrim, IBM Software Group
( Now at Google)

Barry Feigenbaum, IBM Worldwide Accessibility Center

Kip Harris, IBM Worldwide Accessibility Center

Richard S Schwerdtfeger, Accessibility Strategist, IBM Software Group

Todd Creasy’s original article: Designing Accessible Plug-ins in Eclipse

2 Introduction

2.1 Purpose

This document provides the necessary background and information to write an accessible Eclipse-based client application.  Creating an accessible application has important business implications. Government agencies, educational institutions as well as private companies have accessibility requirements for software purchased.  For your application to comply with accessibility standards, the software and content must be enable for use by people with disabilities

2.2 Intended audience

This is intended for developers, architects and planners who are creating Eclipse-based applications. It assumes the reader has a general knowledge of Eclipse and developing using Eclipse Standard Widget Toolkit (SWT). It assumes the reader understands the accommodations needed for persons with various disabilities. It also assumes a general knowledge of the assistive technologies used by people with disabilities. If accessibility training is needed, please refer to the Software developers education roadmap for a list of available courses

2.3 Document contents

The document explains the features of the Eclipse platform that enable accessibility. It then teaches how to use those features to create accessible elements.

2.4 Acknowledgements

This document has morphed from an article by Todd Creasey to a paper (1st Edition) and now to an extended paper (2nd Edition) with expansion to support IAccessible2 features in 3.6 and 3.7 versions of Eclipse SWT Toolkit.

The current authors are greatly indebted to the original content provided by earlier authors.

3 How Eclipse enables accessibility

3.1 Introduction

Eclipse has achieved a basic level of accessibility for people with vision and mobility impairments on multiple platforms.

Accessibility features for the Windows® platform were implemented in Eclipse 2.0 and further refined and stabilized in Eclipse 2.1. Basic support for the GNOMETM platform was added in Eclipse 3.0. Support for the IAccessible2 API was added in Eclipse 3.6.

There is an ongoing effort of interoperability testing with major assistive technology (AT) vendors to ensure that Eclipse is a completely accessible solution across all supported platforms.

3.2 AT vendor dependencies

AT vendors exploit a wide variety of heuristic techniques to provide an accessible solution to the end user.

The following diagram illustrates the current state of the art of accessibility on Windows® ME / 2000 / XP. Although less complicated, a similar situation exists for the GNOMETM environment.

Accessibility architecture on Windows
Figure 3.1. Accessibility architecture on Windows®


Shown are the techniques exploited by AT vendors on Windows® to acquire accessibility information. This is a very complex diagram, with more than 50 components labeled and arranged within one of six bands. It is provided here more to illustrates the large variety of heuristic techniques that are exploited by the AT vendor on Windows®, rather than to provide specific reference material regarding Windows®. From bottom to top, the bands are labeled AT Building Blocks, Windows 98®, Windows XP®, Windows 2000®, AT Layer, AT Platform Layer, Unmanaged Application Layer, and Tools Layer. A horizontal box outlines the band labeled AT Platform Layer. This band depicts a number of the interoperability strategies that are employed by assistive technology vendors. These techniques include MSAA/IAccessible2(COM), system message hooks, and UIA (for Windows Vista)

The MSAA specification is a base to all AT interoperability.  Unfortunately, MSAA enablement features are limited, and fall short of those required for a complete and robust user experience.  This required AT vendors to develop special purpose product support specific to each application, or specific to a certain type of widget. Such techniques were viewed as necessary in order to deliver a sufficiently rich presentation to the end user. However, this reduces interoperability between platforms, tends to be fragile, and limits functionality to those applications with the specialized adaptations.  The complete and robust solution is IAccessible2 (IA2), a supplemental set of AT interfaces that extends the capability of the Microsoft Active Accessibility (MSAA) API by providing support for text controls, tables, hyperlinks, and relations between accessible objects.  IA2 is closely mapped to parallel interfaces on other platforms, such as Linux.  This makes it easier for developers to create cross-platform applications that are accessible.  Firefox is an example of such an application.

3.3 Introducing org.eclipse.swt.accessibility

The Eclipse accessibility API is provided in the org.eclipse.swt.accessibility package.

It is important to remember that, although written in JavaTM, Eclipse applications follow the accessibility API of the target desktop operating system. Eclipse provides a rich plug-in environment whereby the norm is to create plug-ins for native widgets. Most of the SWT widgets that come with Eclipse are actually JavaTM wrappers for native GUI widgets. (This is the complete opposite of a pure JavaTM GUI written in Swing in which higher level components do not translate to native operating system widgets.)  When a widget does not exist on all platforms, it is emulated in code on the deficient platform.  Therefore SWT Widgets utilize accessibility features of the underlying operating system widgets when available, and provide accessibility using MSAA and IAccessible2 in emulated widgets.

From a capabilities perspective, release 3.7 accessibility features are based on the capabilities provided by the MSAA 2.0 programming model; that is, an AccessibleObject can be associated with each control, and the set of methods in the org.eclipse.swt.accessibility interfaces correspond to the set of messages contained in boththe MSAA 2.0 IAccessible and IAccessible 2 definitions. The SWT Accessible object provides a bridge which routes accessibility related messages from the underlying platform framework to the applications SWT widgets used by an application, so that accessibility information can be captured and provided by application’s components back to the platform. The platform accessibility architecture defines the interface between the platform accessibility framework and the assistive technology user agents. The following diagram illustrates this arrangement.

Components of an accessible Eclipse-based solution
Figure 3.2. Components of an accessible Eclipse-based solution


The first of three components is the application (or plug-in) which is built using SWT. This component is platform independent because SWT isolates the application from the platform. The org.eclipse.swt.accessibility package runtime provides the bridge from the application to the platform. The second component is the platform accessibility framework, such as Windows® MSAA or GNOMETM GAP. The third component is the assistive technology, such as the JAWS screen reader. At runtime, the AT obtains information about the user interface elements by calling the platform accessibility framework, which in turn calls into the .swt.accessibilitypackage.

3.4 org.eclipse.swt.accessibility design goals

The org.eclipse.swt.accessibility package provides the Eclipse application programmer with the set of facilities designed to enable the application for interoperability with assistive technologies on Windows® and Linux®.

From an application programming perspective, this work consists of several tasks:

  1. Providing visual and programmatic focus
  2. Respecting system settings for high contrast, colors, and large fonts, or providing application-specific alternatives
  3. Providing textural names and descriptions for interface elements
  4. Providing semantic information such as roles of objects and relationships between them
  5. Providing the ability to navigate and access all functions using the keyboard
  6. Providing notification of UI events as they occur

The facilities of the underlying platform accessibility frameworks will accommodate the IBM accessibility checklist requirements to varying degrees. On Windows®, for example, MSAA generally provides facilities for obtaining names and descriptions of UI elements, but the ability to define relationships between the elements is limited.

The cross platform Eclipse org.eclipse.swt.accessibility package actually provides one or more improvements in the accessibility support to the application programmer, over what is available in the underlying Windows® platform. The Windows® MSAA design does not generally provide support for application programmer control over the accessibility attributes on UI elements. The names and descriptions are automatically decided by MSAA based on internal algorithms which are specific to each control. For example, MSAA automatically uses the text on a button as the MSAA name property. If the application programmer wants to provide a different name or description than what MSAA has decided, then he must undertake the expensive task of creating an MSAA proxy.

The Eclipse org.eclipse.swt.accessibility design improves on the underlying MSAA platform by performing the proxy work automatically. This makes it straightforward for the application programmer to define appropriate attribute values for the various UI elements (such as accessible name and description), all within the SWT environment.

3.5 Changes in Eclipse 3.0

Several key accessibility features were added in the Eclipse 3.0 release.

First, the existing org.eclipse.swt.accessibility API, as defined in release 2.1, is now supported on the Linux®/GNOMETM platform. Furthermore, a new set of features for text controls are introduced on GNOMETM.

The new facilities for developing accessible text widgets are provided through the AccessibleTextListener interface (and corresponding AccessibleTextAdapter and AccessibleTextEvent classes), and the three new text{*}methods on org.eclipse.swt.accessibility.Accessible. These new facilities are based on a small subset of those from AtkText interface from the GNOMETM Accessibility Project.

3.6 Changes in Eclipse 3.6

Eclipse 3.6 (4Q2010) provides an implementation of most of the IAccessible2 APIs. The updates in 3.6 represent the most significant accessibility improvements in Eclipse since those provided in version 3.0. Remaining incomplete are edit capabilities in rich text fields.

3.7 Changes in Eclipse 3.7

Eclipse 3.7 provides (2Q2011) the final implementation of IAccessible2 APIs. Styled text provides full support for accessible cut and paste.

3.8 Cross-platform API compatibility

The Eclipse accessibility API works on multiple platforms, despite wide differences in the underlying accessibility APIs provided by each operating system.

On the Windows® platform, most of the available functionality for operating the accessibility features is provided through a single MSAA interface: the IAccessible interface. MSAA 1.3 properties and methods are defined by IAccessible interface for the most common attributes and behaviors. All UI controls are accessed through this interface. When a particular property or method is not applicable to a particular type of UI element, the property becomes a "don't care" value; or in the case of a non-applicable method, it becomes a null operation. Several additional text interfaces were added in MSAA 2.0.

The GNOMETM Accessibility Project (GAP), in contrast, uses a more object oriented approach that provides more specialized interfaces for a variety of UI elements. GAP defines a collection of about a dozen accessibility interfaces in the Accessibility Tool Kit (ATK). Each UI element provides accessibility services by implementing one or more of these interfaces, as appropriate for the specialized behavior of that widget. GAP provides a richer set of features than does MSAA that an AT can exploit.

The org.eclipse.swt.accessibility package was initially modeled as essentially a single interface with the MSAA 1.3 level of capabilities (the MSAA 1.3 methods were actually partitioned into two interfaces, one for standard widgets and another for custom controls). In Eclipse 3.0, support for GNOMETM was added, which included mapping Eclipse's MSAA-based architecture to work with GNOMETM. In Eclipse 3.6 and 3.7 complete IA2 support was added.

To the extent that Eclipse supports the underlying platforms' accessibility APIs, you do not need to worry too much about the differences between them. All accessibility support in Eclipse flows through org.eclipse.swt.accessibility, which contains platform-specific code for talking to the accessibility API on the underlying platform.

Eclipse uses the native controls of the platform whenever possible encapsulating them as minimally as possible. This provides a native look and feel by using the native controls, rather than mimicking them. When a control is not present on a platform, Eclipse provides code to mimic the missing control. Some controls are implemented directly by Eclipse across all platforms. The styled text edit control is an example of a widget implement in Eclipse code on all platforms.

Because Eclipse uses the native controls, accessibility functions provided by the platform are available for free to the user. For example, native buttons on the windows platform have full support for the accessibility through MSAA. However, native tables do not have IA2 natively, and do not support screen readers in reading headers and cell information. Thus, developers must implement fully accessible table with their own classes.

4 Accessibility Design and Additions

4.1 Enable all functions for keyboard use

The user must be able to accomplish all tasks using only the keyboard (without the mouse). You should provide easy mechanisms for users to access functions via the keyboard. This helps users that cannot use pointing devices. Full keyboard access also helps the general user as it removes the need to take the hands off the keyboard.

Full keyboard access includes menu activation, scrolling, drag-and-drop, drawing and any other action your user interface provides. You should leverage host operating system and Eclipse provided support for these tasks. You can also define alternate approaches (for example, sizing dialogs vs. drag-and-drop sizing) to performing functions.

The first instinct may be to attempt to provide direct keyboard shortcuts (called accelerators) for every action your application can take. These definitions are often in the form of Ctrl, Alt and/or Shift modifier keys. Frequently used menu items (such as Save, Copy, Paste, etc.) should have standard keyboard shortcuts assigned to them. Most operating systems have standard keyboard shortcuts, and you should adhere to these as closely as possible.

However, you should resist the urge to assign an accelerator to every menu item and button in your application. For one thing, you will quickly run out of keys, even in a moderately complex application. For another thing, you must consider the effect of translations. Many languages do not have 26 letters to play with, so having numerous mnemonics and key sequences make it difficult to translate in a meaningful way in the context of the menu item or action. This is not an excuse to make sure that everything can be done with a keyboard somehow, but you must balance this requirement with other constraints. Resist the urge to define an accelerator for every possible action.

For less frequently-used menu items, use mnemonics. This lets users access them relatively quickly with the keyboard, without fully navigating the menu hierarchy, and without an accelerator. Users activate the menu with the keyboard (for example, Shift-F10 on Windows), then select the appropriate menu item with its mnemonic, without needing to use the arrow keys repeatedly to scroll each item.

In Eclipse, mnemonics are easy to define for text items like Labels and MenuItems. Just place an ampersand (&) before the mnemonic character (such as Copy). For MenuItems, accelerators can be defined by an at-sign (@) suffix (such as Save@Ctrl+S) that specifies the key combination that is the accelerator.

All input (field, button, list, etc.) areas should be accessible via the keyboard. This usually means creating a tab-order to allow the user to walk the user interface in a pre-determined order. Often this order should follow the physical placement of the visual controls. Often a left-to-right, top-to-bottom order is used, but other orders may be more effective depending on the actual layout.

For most GUI systems, the default tab order is the order in which the controls are added to the GUI component hierarchy. You can override this by using the org.eclipse.swt.widgets.Composite getTabList and setTabList methods.

However, do not rely solely on tabbing to move around with the keyboard. Imagine a complex form with 40 controls of various types, and you want to get to the 20th one... over and over again. Mnemonics help here, if you know in advance that a particular control is important. But there are other conventions that can help ease the burden of keyboard-only navigation. A common convention is to define logical groups or large objects to tab to (a list box, table, radio button group, or checkbox group) and use the arrow keys to navigate between the child controls. Users can then tab around the form in "chunks" and arrow around once they get within the correct logical group. We will explore this further in Chapter 8, Case Study: DatePickerCanvas.

Any keyboard definitions should be consistent with similar key definitions used by the operating system and other applications used on it. This means you may need to parameterize the assignment of keys if you want to be cross-platform (a good thing to do anyway so the user can also customize them). Avoid the use of multiple modifier keys (e.g., Ctrl-Alt-Shift-D) as this can be quite hard for mobility disabled users to enter. Use at most one modifier key.

You should explicitly establish focus within your composite control by requesting focus to one of the child controls. You can do this by using the org.eclipse.swt.widgets.Control forceFocus method.

Do not replace or interfere with any key assignments used by operating system standard services (such as the use of Alt in Windows® to access the menu bar) or accessibility features. Make sure all your keyboard definitions are described in accessible documentation.

4.2 Provide contextual information while navigating

All controls must be able to provide several kinds of descriptive information about themselves.

The most basic information is a name and a role. Names and roles are typically what an AT reads to a user when the object receives focus or is changed. For example, the text of a button is often used its name. The role, such as "push button" or "text", lets the AT know what can be done to the control.

Another basic type of information is the object state, such as being selected or having focus. Other, generally more elaborate, descriptive information can be provided by controls, such as tool tips and descriptions. Help selectors can be assigned to controls to provide contextual help. Some controls also have a current value. For example, the CCombo control has a getText()method. (CLabel and StyledTextdo too.) This current value must be exposed via the getValue()method of an AccessibleControlListener associated with the control.

You must at least provide name support for non-text components. For example, a button with only an image and no text will not have a usable name. In these situations a name must be explicitly established for it. You can do this be creating an AccessibleListener, described in DatePickerComposite example.

For more complex graphical components, you can also define help text explaining the component's purpose. See Section 5for an example of how to add an AccessibleListeners.

If you label a control, such as a Text or ComboBox, place the label immediately before the labeled control. This is typically done by creating the label immediately before creating the labeled control. You can override this natural ordering by using the org.eclipse.swt.widgets.Control moveAbove and moveBelow methods.

Finally, make sure that the AT can always determine which object has focus. If you use standard controls thus should be automatic. If you use custom controls, you can do this by using the org.eclipse.swt.widgets.Control forceFocus method.

4.3 Enable users to adjust their environment

The program must respect user-defined fonts, colors, and volume settings.

Operating systems provide theming mechanisms designed to support accessibility. Users that are color blind or who have low vision, benefit from a high contrast color scheme. Other users prefer a "large print" theme, that makes all fonts larger and increases spacing between lines. Application components should respond to changes in system font and color changes to follow these user preferences.

If your application uses color to convey information, the colors must be configurable for people who cannot use your application's default color selections. Respecting the underlying operating system's color scheme is the best choice for basic elements like dialogs and scroll bars, but user-configurable options are also acceptable.

Color must not be used as the only means of conveying information. Some users will not be able to distinguish colors, and assistive technologies such as screen readers will not indicate color differences. Also, in High Contrast theme, virtually all color information in UI elements is lost.

If your application uses sounds or audio, you should provide an option to enable the user to disable sound and adjust the volume. Users may not be able to hear or distinguish sounds at certain volumes, so they need the ability to adjust the volume.

4.4 Make documentation accessible, too

Program documentation must also be accessible. This is mentioned last, but it will be the first thing a disabled user relies on to learn more about your application's accessibility features.

Provide documentation in one of the following accessible formats:

  1. ASCII text
  2. JavaTMDoc in JDK 1.4 is now accessible
  3. Accessible HTML. Follow the IBM Web Accessibility Checklist to produce accessible HTML.
  4. Accessible PDF. Documentation in Adobe® Portable Document Format (PDF) can be accessible if it consists primarily of text, or if all images are properly tagged with alternate text. Products which provide PDF documentation should follow the Adobe® guidelines for accessibility. For additional information, see the article Optimizing Adobe® PDF Files for Accessibility.

If your documentation contains illustrations or graphics, you must include text descriptions of the information contained in those illustrations and graphics.

If the documentation is not available in an accessible electronic format (for example, if it is only available on paper), you must be prepared to provide documentation in Braille or audio cassette upon request from the customer.

4.4.1 Documentation of Accessibility Features

People with disabilities cannot effectively use the software if they cannot access information on how to use accessibility features. This is particularly important for keyboard access. Since most products focus on navigation with the mouse, it is not always clear how to use the product with the keyboard. All keyboard navigation which does not follow documented system conventions should be documented.

The following techniques are required:

  1. Provide a section where all accessibility features are documented
  2. Provide a section where unique keyboard accessibility features are documented. If the software uses standard system keyboard commands for navigation, they do not have to be documented. The keyboard accessibility information could also be included as part of the general accessibility section.
  3. When the software provides instructions for completing tasks using the mouse, include the instructions for doing those tasks using the keyboard.

The following techniques are recommended but not required:

  1. Include a keyword search and help topic item for accessibility.
  2. Document any shortcut keys in the software by adding the information next to the command in the pull-down menu.
  3. Have the author document custom user interface extensions or AT-specific scripts for assistive technologies such as JAWS scripts.

4.4.2 Testing for Documentation Accessibility

How to test your documentation to ensure that it complies with accessibility requirements:

  1. If the documentation is available in ASCII text, it is accessible and no additional testing is required.
  2. If the documentation is available in HTML, or JavaTMDoc use assistive technology such as a screen readerTM to verify it is accessible.
  3. If documentation is provided in PDF format, use one of the methods offered by Adobe® to convert the PDF file to HTML or text. Verify the converted document content is equivalent to the PDF document.

How to test that your documentation adequately documents the accessibility features of your software:

  1. If the software does not use standard keyboard access or does not support the accessibility options available in the operating system, the accessibility features must be documented. Open the software documentation and verify there is a section which discusses accessibility features in the product.
  2. If the product provides "how to" instructions or pop-up help, verify that instructions for performing the actions using the keyboard are available in addition to instructions for using the mouse.

5 Enabling code

This section details techniques to use the Eclipse features available using SWT standard widgets and the Eclipse 3.6 APIs.

5.1 Tab order

All input and interaction areas (field, button, list, etc.) should be accessible via the keyboard. Each interactive element is reached using the Tab (and Shift-Tab) keys. This navigation method is an expected way for most users to travel logically through all the elements in a dialog. Ctrl-Tab (and Shift-Ctrl-Tab) navigate within related widgets, such as tabbed dialogs, and allows the user to traverse out of text widgets and editors.

For most GUI systems, the default tab order is the order in which the controls are added to the GUI component hierarchy. It can be overridden by using the org.eclipse.swt.widgets.Composite getTabList and setTabList methods.

Typically the order should follow the physical placement of the visual controls. Often a left−to−right, top−to−bottom order is used, but other orders may be more effective depending on the actual layout.

5.2 Static Text

Static text that appears in dialogs must be enabled for reading by screen readers. The most common screen readers read static text prior to the first entry field the first time the dialog appears. However they do not read other static text on the dialog. Text that is not associated with elements in the tab order is not read.

There are a few ways to deal with this.

5.3 Accelerator keys

Add accelerator keys for the most common and frequently used actions. Use standard assignments when doing standard operations. For example you should implement the common Save, Copy, Paste with the standard accelerators for the operating system you are targeting. If possible allow the accelerator keys to be reassigned through user profile options.

Menus on any of the views provided by Eclipse are already accessible. F10 will give the main window menu focus, ShiftF10 will bring up the context menu for a view, CtrlF10 will activate the view menu if there is one and Altwill bring up the system menu for the view or editor. This much is done for you; no application code is required.

The following is the pertinent code fragment showing the how to an accelerator to Ctrl+A. It is taken from Snippet29.java available from the Eclipse.org SWT Snippets page.

MenuItem item = new MenuItem (submenu, SWT.PUSH);
item.setText ("Select &All\tCtrl+A");
item.setAccelerator (SWT.MOD1 + 'A');

An accelerator is a property of a MenuItem. Here the constant SWT.MOD1 resolves to the primary modifier key, which is set to Ctrl on Windows and *nix system, and Cmd on the Mac. The MenuItem object must be created first before the Accelerator can be assigned to it. To make the accelerator feature known to the user, it is appended to the text of the MenuItem. In this way it will be displayed each time the menu is displayed. As discussed in detail in the next section, the code fragment also shows how to set a mnemonic.

Always think aboutkeyboard access when designing a view or dialog. Every piece of information within the view or dialog should be reachable using the keyboard. That means two things:

  1. Every control that the user needs to manipulate (such as combo boxes, text fields, and buttons) should be navigable with the keyboard. This means being able to move through all the controls using the tab key or other keyboard shortcuts. In a complex graphical application, there may be a combination of keyboard shortcuts for navigating through various "levels" of the interface. For instance, Eclipse uses CtrlF6, CtrlF7, and CtrlF8 to switch between editors, views and perspectives respectively, then Tab to navigate within them. Eclipse also offer a “Quick Access” key for navigation, Ctrl-3, allowing the user to find views, perspectives, and editor windows by typing the first few identifying characters of their title.
  2. Everything that needs scrolling to be seen needs to be scrollable with the keyboard. The usual way to do this is to listen for tabs or the page up and page down keys and to move the viewing area appropriately.

One thing that can be easily overlooked is toolbars. Although originally designed as a quick mouse−based alternative access to actions defined elsewhere (usually in menu items), toolbars have taken on a life of their own and now frequently define actions not available elsewhere. Your entire toolbar must be accessible with the keyboard. If you use the built−in Eclipse toolbar widget with SWT.FLAT style, your toolbar will be focusable and tabbable, and therefore fully keyboard navigable. If you build your own toolbar widget, you will need to make sure it is accessible with the keyboard.

Another thing that is frequently overlooked is drag−and−drop interfaces. For example, you may provide two lists and the ability to select items by dragging them from one list to another. In this case, you will need to provide a way to move the items without the mouse, for example, being able to select an item from one list and then tab to and select a button that moves it to the other list. Another example is a component palette from which you can drag widgets onto a canvas. In this case, each item in your palette will need to be focusable and selectable, and you will need to provide a keyboard−based way to place the selected palette item onto the canvas. This can be quite tricky, and is explained in greater detail in Chapter 10, Designing Accessible Graphics−Intense Applications with GEF.

5.4 Mnemonics

Enable mnemonics for all menu items. This lets users access them relatively quickly with the keyboard, without fully navigating the menu hierarchy, and without an accelerator. Users activate the menu with the keyboard (for example, F10 on Windows), then select the appropriate menu item with its mnemonic, without needing to use the arrow keys repeatedly to scroll each item.

Mnemonics facilitate the creation and use of macros by Assistive Technologies.

In Eclipse, mnemonics are easy to define for text items like Labels and MenuItems. Just place an ampersand (&) before the mnemonic character in the text.
The following is the pertinent code fragment showing the setting of a menu mnemonic. It is taken from Snippet29.java available from the Eclipse.org SWT Snippets page.

MenuItem item = new MenuItem (submenu, SWT.PUSH);
item.setText ("Select &All\tCtrl+A");

A mnemonic is encoded directly in the text displayed on the menu. Here, the letter “a” is assigned as the mnemonic for the menu action “Select All”. The mnemonic is not case sensitive. The MenuItem object must be created first before the text and the mnemonic can be assigned to it.

By allowing the mnemonic to be set directly in the text, the mnemonic becomes part of the translated text. This allows individual languages to use mnemonics that are better suited to the language. It also means that mnemonics must be checked for each language after translation.

Operating systems have a built-in convention for dealing with multiple menu items assigned the same mnemonic: repeated presses of the same key select the first, second, and so on, menu choice. The user then presses enter to activate the menu choice.

Mnemonics should also be provided for buttons, lists, entry fields and other elements in the tab order.

5.5 Associating labels and controls

The operating system must know which text widget is associated with which label widget for proper reading by AT. On Windows®, this can be inferred by creation order association, or the relationship may be coded directly.

5.5.1 Creation Order Association

Instantiating widgets and their children creates a tree hierarchy of controls. By default, the order of controls within the hierarchy is the order in which the widgets are created, but it can be modified using Control.moveAbove() and Control.moveBelow(). A label widget must be immediately before the text widget or combo box that it is labeling in the order of its parent for it to be "associated" with it. (More precisely, it must be the previous sibling in the tree hierarchy). This also implies that the two widgets must have the same parent for this behavior to occur. This is done by writing the code so that each label widget is created before the text widget to be associated with it.

Therefore create the label and then its associated text, rather than creating both labels first. For the sake of usability, we are also going to put the name before the text horizontally rather than vertically, because this is the way that people are used to reading a computer screen. This is not required; the important thing to watch here is the creation order of the UI elements.

public void createPartControl(Composite parent) {
Composite workArea = new Composite(parent, SWT.BORDER);
Label titleLabel = new Label(workArea, SWT.NONE);
titleLabel.setText("Enter Name and Password:");
titleLabel.setFont(JFaceResources.getBannerFont());
Label nameTitle = new Label(workArea, SWT.NONE);
nameTitle.setText("&Name:");
nameEntryField = new Text(workArea, SWT.PASSWORD|SWT.BORDER);
Label passwordTitle = new Label(workArea, SWT.NONE);
passwordTitle.setText("&Password:");
passwordEntryField = new Text(workArea, SWT.BORDER);
Button enterButton = new Button(workArea, SWT.PUSH);
enterButton.setText("&Login");
}

There are now numerous screen readers that can read aloud elements of the user interface. Screen readers attempt to infer the relevant information about a widget and those that surround it and present that information to the user. They do this by taking the widget that has focus and inferring which of its siblings and parents has useful information.

As text and combo box widgets do not have a label associated with them we have several ways that we can get a screen reader to infer a label for a widget with focus:

  1. A label immediately precedes the focus widget in the tree hierarchy.
  2. The focus widget is the direct child of the shell (in this case the shell label gets read).
  3. The focus widget is the child of a widget that has a label and is labeled itself. For instance, a labeled text inside of a group box will have both its label and the label of the group box read. But if there is an unlabeled widget (say an intermediate Composite) between the label and the group box, the group box will not be read.

In the example above, a screen reader will use "Name:" as the label for the name field and "Password:" for the label of the password field.

A screen reader will not only read the label of a widget with focus, but it will also read the labels of any parents of that widget that have a label (such as a group) and the title of the dialog or window. A screen reader will generally stop at the first unlabelled parent, so be sure there are no intermediate Composites between labels to be read. To label a group of widgets, contain them in a group box.

5.5.2 Assigning a name to a control

Any control can be given a name which can be retrieved by the assistive technology.  This is done by adding an AccessibleListener to the control’s accessible and implementing the get name method. Examples are found in Snippet164.java and Snippet291.java available from the Eclipse.org SWT Snippets page.

The following code fragment from Snippet164 shows an example of implementing getName(). The accessible of the tree control is retrieved and an AccessibleListener is created and added.  The method getName is provided.  The results are passed back in the AccessibleEvent member result.

tree.getAccessible().addAccessibleListener(new AccessibleAdapter() {
public void getName(AccessibleEvent e) {
if (e.childID == ACC.CHILDID_SELF) {
e.result = "This is the Accessible Name for the Tree";
} else {
TreeItem item = (TreeItem)display.findWidget(tree, e.childID);
if (item != null) {
e.result = "This is the Accessible Name for the TreeItem: " + item.getText();
}
}
}
});

5.5.3 Associations using Relations

Version 3.6 of Eclipse and later provide support for IAccessible2 relations. Relations take the guesswork out of associating fields with labels. See Relations.

5.6 Logical groups

The group widget allows related widgets to be identified by an additional label for clarification when reading. The name of the group will be read in addition to the name of the widget with focus. This is helpful when a set of widgets are meaningful in context of each other. For example, to provide a dialog when they user must answer a status from one of a few multiple choices, the requested information can be placed in the group text, and the various possible answers can be placed as radio buttons within the group.

The following code snippet shows how a group can augment information in the contained widgets.

Group group = new Group (shell, SWT.NONE);
group.setText ("Contact preference");
Button button1 = new Button (group, SWT.RADIO);
Button1.setText ("Standard ground mail");
Button button2 = new Button (group, SWT.RADIO);
Button2.setText ("email");
Button button3 = new Button (group, SWT.RADIO);
Button3.setText ("Telephone");
Button button4 = new Button (group, SWT.RADIO);
Button4.setText ("Do not contact");

Examples of using groups are found in Snippet214.java and Snippet292.java available from the Eclipse.org SWT Snippets page

5.7 Requesting Focus

An application can control the field which has focus using the setFocus method.  Here is an example fragment:

final Text text = new Text (table, SWT.NONE);
text.selectAll ();
text.setFocus ();

5.8 Providing role and state

As previously mentioned, the role and state are essential basic information that an AT requests and provides to the user. Role describes the function of the control, such as “check box”. State provides information about current state, such as “checked” or “not checked”.

Role and state information are provided by adding an AccessibleControlAdapter.

The following code fragment shows how to provide the Role and State of a control. It is taken from Snippet162.java available from the Eclipse.org SWT Snippets page.

table.getAccessible ().addAccessibleControlListener (new AccessibleControlAdapter () {
  public void getRole(AccessibleControlEvent e) {
    e.detail = ACC.ROLE_CHECKBUTTON;
}
  public void getState (AccessibleControlEvent e) {
    if (e.childID >= 0 && e.childID < table.getItemCount ()) {
      TableItem item = table.getItem (e.childID);
    if (item.getImage() == checkedImage) {
      e.detail |= ACC.STATE_CHECKED;
      }
    }
  }
});

5.9 System Colors

Users with vision limitations may not see all colors or may need to use high contrast settings. Best practice is to inherit system colors and font sizes, which standard widgets do without modification.

If you still want to override the system colors and have a color that you set yourself, you must have a way to adjust it for different color settings. Most developers use a ColorFieldEditor to allow the user to change these values. It is recommended that you only do this if there really is no system color you want to use, because it adds extra complexity to the accessible setup for a user.

5.10 Adopting High Contrast Themes

Eclipse provides a getHighContrast() method, which tells you whether the user is currently using a High Contrast theme. This allows you to write code like this, which sets the background color to white for most themes, and to a standard background color in a High Contrast theme:

private Color getBackgroundColor() {
Display display = getDisplay();
boolean highContrast = display.getHighContrast();
if (highContrast) {
return Display.getCurrent().getSystemColor(
SWT.COLOR_WIDGET_BACKGROUND);
}
else {
return display.getSystemColor(SWT.COLOR_WHITE);
}
}

You must ensure that other graphic elements are still readable, even in High Contrast mode.

Color is frequently used to liven up the look of an application, but you should remember the following guidelines:

  1. Color must be configurable for people who cannot use your application's default color selections. Respecting the underlying operating system's color scheme is the best choice, but user-configurable options are also acceptable.
  2. Color must not be used as the only means of conveying information. Some users will not be able to distinguish colors. Also, in High Contrast theme, virtually all color information in UI elements is lost.

5.11 Adopting System Fonts

Like colors, fonts also need to be configurable for those users who cannot see small fonts.

The Windows® operating system offers Large and Extra Large font size modes for setting the fonts to be more readable to those with low vision. These options are configurable in Properties->Appearance from the Windows® desktop. GNOMETM also offers similar themes to address this, in the Theme Preferences dialog. The GNOMETM desktop also provides multiple themes, including a high contrast theme.

All dialogs and windows should be tested with the fonts set to these Large and Extra Large sizes to ensure that none of the text is cut off, and that it fits in the visible window. Note that Windows® dialogs are not the best example here, since not all of them respond properly to system font changes. GNOMETM dialogs are a much better example; the window manager can also scale the window size to accommodate larger fonts.

Another consideration with dialogs is that if an application is to be translated, the strings that are displayed and entered are often significantly longer than the corresponding text in English. Asian language characters are often taller. Because of this all windows, dialog boxes, and entry fields must have the capability to resize automatically to reduce truncating, wrapping and scrolling of the text.

As with colors, applications often require more font choice than the OS provides. The OS font for a control can be retrieved from the OS with control.getFont(), assuming that the font has not been set to something else by the application. The default system font can be found from any Display by calling getSystemFont().

Eclipse also defines these fonts:

Avoid creating private fonts because you will then have to provide a way for the user to change them.  Instead, use one of the user-configurable fonts as shown:

Font labelFont = JFaceResources.getBannerFont();

Using, for example, the Eclipse banner font, you can pick up the setting an Eclipse user has made for the banner font in Eclipse and apply it to your views. These fonts are set in the Workbench−>Fonts preference page.

5.12 Associating Names with Graphics

Graphics need to have names explicitly associated with them, otherwise some other name may be associated by the screen reader.

  1. SWT’s org.eclipse.swt.accessibility.AccessibleListener can be used to add an accessible name to a control. 

org.eclipse.swt.accessibility.AccessibleAdapter class contains empty implementations of all of the methods in AccessibleListener. To give a graphic a consistent name, add an AccessibleListener that implements getName(). Subclass this in an inner class because we only want to override getName().

Here is how to implement a basic AccessibleListener to give a name to the topLabel:

topLabel.getAccessible().addAccessibleListener(
new AccessibleAdapter() {
public void getName(AccessibleEvent e) {
e.result = "Welcome to our online store!"
}
}
);

An additional example is found Snippet164.java.

After we have made this change, we can verify it on the Windows® operating system using the Inspect tool, which is part of the Microsoft® Active Accessibility SDK. When we run Inspect, we can see that when we hover over the label, we get a usable name. This name is passed on to accessibility tools and may be spoken to a blind user using a screen reader.

6 Custom Controls

This chapter concentrates on Standard Windows Toolkit (SWT) controls. It provides some input on using the JFace SWT extensions and some comments about issues related to plug-in support. This should prove useful to developers using SWT/JFace to build applications or plug-ins for Eclipse or the various Eclipse-based products such as the WebSphere Studio products.

This chapter assumes you are familiar with creating GUIs using SWT and JFace and that you are familiar with Eclipse plug-in development.

6.1 Two Approaches to Custom Controls

There are two basic approaches to creating custom controls: extending Canvas or extending Composite. Extend Composite to build a control out of other controls. Use Canvas (a Composite subclass) to do a custom drawn control or mixed control.

Later we will see the implementation of some of the predefined custom controls that come with Eclipse: CLabel, CCombo and CTabFolder.

Note it is generally not possible to subclass any other SWT control, such as a Button or Table. These classes do not promise to provide stable implementations across host platforms or Eclipse versions. Any subclass would be subject to change. Thus these classes produce runtime errors if subclassed. If you must extend these classes, be aware of the dependency you create. Also you will need to put your classes in the same package (i.e., org.eclipse.swt.widgets) and override the Widget.checkSubclass method.

It is highly recommended that you use pure-SWT support to build your controls. This is the easiest technique. All coding is in JavaTM. Also pure SWT controls are portable to all Eclipse runtime environments.

Only if this is not technically feasible to implement some required feature for your control should you attempt to create a native (using host operating system services directly) control. This is much harder. You generally need to code in C/C++ and create native libraries that need to be distributed. Native controls are not portable so you will need to create an implementation for each operating system platform you need to support. You would be doing what the Eclipse team did for controls such as Button and Table. For more information on making Eclipse controls see Writing Your Own Widget.

Note it is usually easier to build custom controls as Composite subclasses (vs. Canvas) as they are just made up of other controls and are thus little different from building a GUI such as an input dialog or a view. Like with dialogs and views, you need to ensure each control has sufficient accessible information (e.g., name, description, help text, keyboard shortcuts, etc.). Most of these values can be defaulted to the MSAA (or other platform accessibility support) supplied values augmented by setting control attributes such as ToolTip Text and Keyboard Accelerators/Mnemonics.

If this is not the case you need to setup extra accessibility helpers (i.e., listeners). You can also take a defensive approach and always establish an accessible helper on each control and explicitly return the desired values. While this takes a little more work on your part, it offers more predictable behavior, especially across platforms, and is thus the recommended approach.

6.2 Inspection Tools

Tools like Inspect32 show the information available to ATs through architected system interfaces. You may notice, especially for complex controls such as tables and styled text view/editors, that some of the information visually presented by the control is not available to the tool.

This is an unfortunate situation. As explained earlier many ATs get information from other non-architected sources, such as off-screen displays, to compensate. When using these controls, it is not possible for you to fully enable accessibility of the control through the SWT accessibility API. You are dependent on the particular AT used to compensate.

Thus to test for an accessible solution, you must use some set of ATs in addition to inspection tools like Inspect32.

6.3 Introducing AccessibleContext

As described earlier, the normal way to add accessible values to a control is to add an AccessibleListener implementation to each and every control. This can be tedious, so we are suggesting, as shown in the examples below, a simpler approach using an AccessibleContext that can be associated with a control and provides additional behaviors for that control.

Note that you can only provide information for Controls; other Widget subclasses (such as menu, toolbar and table items) cannot have added accessibility support. AccessibleContextprovides this interface:

/**
* Access accessible information for an org.eclipse.swt.wigets.Control.
*
* @see org.eclipse.swt.wigets.Control
*/
public class AccessibleContext
implements AccessibleListener, AccessibleControlListener
{
  public static final String NAME = "name";
  public static final String DESCRIPTION = "description";
  public static final String DESC = DESCRIPTION;
  public static final String HELP = "help";
  public static final String SHORTCUT = "shortcut";
  public static final String ROLE = "role";
  public static final String STATE = "state";
  public static final String VALUE = "value";
  public AccessibleContext(Control control);
  public AccessibleContext(Control control, Properties props);
  public AccessibleContext(Control control, AccessibleContext other);
  public static Accessible getAccessible(Control control);
  public Accessible getAccessible();
  public static AccessibleContext getAccessibleContext(Control control);
  public static Control getControl(Accessible acc);
  public Control getControl();
  public Accessible getAccessible();
  public static String[] getPropertyKeys();
  public boolean getQualifyProperties();
  public void setQualifyProperties(boolean f);
  public String getId();
  public void setId(String id);
  public void setProperty(String name, String value);
  public void setProperties(Properties properties);
  public String getProperty(String name);
  public String getProperty(String name, String def);
  public Iterator getPropertyNames();
  public PrintStream getTraceStream();
  public void setTraceStream(PrintStream ps);
  public boolean isDisposed();
  public void dispose(); // normally disposed with associated control
  // subclass should override to provide a custom listener
  protected AccessibleControlListener makeAdapter();
}

Each AccessibleContext acts like a java.util.Properties object in that it can store pre−defined values for the various accessible information values. These can be explicitly set, loaded from a properties file (or equivalently, a resource bundle) and/or copied from a preexisting AccessibleContext. The values can be changed at any time.

For the rare case where you need to redefine the services provide in the AccessibleControlListener interface, you can register a listener with the AccessibleContext to delegate to.

With the above standard SWT approach it can be tedious setting up accessible values for each individual control. We have made this easier. The property values held by an AccessibleContext can be qualified by the control name, thus a single properties file can define all the values for one, many or all of your controls.

6.4 Introducing IAccessibleControl

Each AccessibleContext has a String id value. It can be used to qualify each control with a unique name. If you use controls that implement the IAccessibleControl interface, then the control can have its own id and that will be used.

IAccessibleControl is an interface that can be implemented by Controls. It has the following definition:

package com.ibm.wac.eclipse.accessibility;
public interface IAccessibleControl
{
AccessibleContext getContext();
String getId();
void setId(String id);
// Convenience accessors
String getAccessibleProperty(String name);
String getAccessibleProperty(String name, String def);
void setAccessibleProperty(String name, String value);
void setAccessibleProperties(java.util.Properties properties);
}

The Property and Properties methods delegate to the associated context.

To make automating the use of AccessibleContexts easier, we have created a utility that writes Controls that implement IAccessibleControl and forward many methods to a standard control that provides the actual GUI. For example, the generated equivalent of a Button is an AccessibleButton. By using these generated control (instead of the standard SWT controls) you can more easily add accessibility features to the control. With a simple extension of the generator program other features can be added as well. This is a form of Aspect-Oriented Programming (AOP).

All the generated controls use the delegation model. They implement the IDelegatedControl interface. Delegated controls are the Eclipse recommended way (vs. subclassing) to create custom controls that reuse the services of existing controls. For more information using the delegation model see Writing Your Own Widget.

6.5 Introducing DatePickerComposite

As an extended example of creating custom controls based on Composite please see the DataPickerComposite class. As an example, this class provides enhanced accessibility through the use of multiple accessibility value definition approaches; a real solution would select only one of the approaches.

This component looks like this:

It is composed of Button and Label controls. There are previous, next, blank and date buttons. The previous and next buttons change the month. The blank buttons are no operations. The date buttons cause an event to be fired to indicate a date is picked.

The control is embedded inside a Composite host. The "Press Me" and "Exit" buttons are example usage of the AccessibleButton wrapper class.

This shows the accessible state of the DatePicker itself. Notice that the name, description and help values are defined.

These are not the normal values returned by MSAA if accessibility extension were not added. Also notice the list of
children and their names. The normal MSAA children names would be the button text (i.e., the date) not the longer forms shown.

Now consider this second Inspect32 screenshot:

This screenshot shows the accessible state of the one of the date buttons in the DataPicker. Notice that the name, description and help values are defined. Again these are not the normal values returned by MSAA.

6.6 DatePickerComposite Walkthrough

Now let us look at the code for DatePickerComposite. Some of its features are discussed in the CLabel discussion later in this document (DatePickerComposite is derived from the Clabel source file) and others are not critical to accessibility. The full source is included in , _DatePickerComposite.java_. The class DatePickerComposite is a subclass of Composite thus it can have child controls.

protected Calendar cal;
public void setCalendar(Calendar cal) {
  if ( cal == null ) {
  SWT.error(SWT.ERROR_NULL_ARGUMENT);
  }
  this.cal = cal;
  DateFormat mdf = new SimpleDateFormat("MMMMMMMMMMMMMMMMMMMM");
  monthName = mdf.format(cal.getTime()).trim().toUpperCase();
  yearName = Integer.toString(cal.get(Calendar.YEAR));
  monthYearName = monthName + ' ' + yearName;
  month = cal.get(cal.MONTH);
  day = cal.get(cal.DAY_OF_MONTH);
  selected = day;
  year = cal.get(cal.YEAR);
  days = cal.getActualMaximum(cal.DAY_OF_MONTH);
  Calendar first = new GregorianCalendar(year, month, 1);
  firstDow = first.get(first.DAY_OF_WEEK) - first.getFirstDayOfWeek();
  Calendar last = new GregorianCalendar(year, month, cal.getActualMaximum(cal.DAY_OF_MONTH));
  lastDow = last.get(last.DAY_OF_WEEK) - last.getFirstDayOfWeek();
  weeks = weeksInMonth(dowConverter(firstDow), days);
  updateGui();
  pack();
  redraw();
}
protected int selected = −1; // selected index

The class is created. The standard SWT parent and style value are required. The desired date and child accessibility enable flag are optional.

public DatePickerComposite(Composite parent, int style, Calendar cal, boolean enable) {
  super(parent, checkStyle(style));
  enableChildAcc = enable;
  // add listeners
  initGui(); // go make my GUI
  initAccessible();
  setCalendar(cal != null ? cal : Calendar.getInstance());
}

Several focus, mouse and key event handlers are defined. Through arrow keys you can select a new date. The Enter key fires a date change event.

// event callbacks
protected boolean mouseOver;
protected void onMouseOver(Event event, boolean over) {
  mouseOver = over;
}

protected boolean haveFocus;
protected void onFocus(Event event, boolean gained) {
  haveFocus = gained;
  trace("onFocus: " + gained + "," + event);
}

protected void onKeyPress(Event event) {
  trace("onKeyPress: " + event + ',' + event.keyCode + ',' + event.character);
  if (event.keyCode == 0 && event.character == ' ') {
    // *** consider adding handler here ***
  }
  else if (event.keyCode == SWT.ARROW_LEFT) { // next day
    int xday = day - 1;
    if (xday >= 1) {
      setCalendar(new GregorianCalendar(year, month, xday));
    }
    else {
      getDisplay().beep();
    }
  }
  else if (event.keyCode == SWT.ARROW_RIGHT) { // previous day
    int xday = day + 1;
    if (xday <= days) {
      setCalendar(new GregorianCalendar(year, month, xday));
    }
    else {
      getDisplay().beep();
    }
  }
  else if (event.keyCode == SWT.ARROW_UP) { // previous week
    int xday = day - 7;
    if (xday >= 1) {
      setCalendar(new GregorianCalendar(year, month, xday));
    }
    else {
      getDisplay().beep();
    }
  }
  else if (event.keyCode == SWT.ARROW_DOWN) { // next week
    int xday = day + 7;
    if (xday <= days) {
      setCalendar(new GregorianCalendar(year, month, xday));
    }
    else {
      getDisplay().beep();
    }
  }
  else if (event.keyCode == '\r') { // notify listeners of change
    fireListeners(event, cal);
  }
  else if (event.keyCode == '\t') { // tab to first child
    prevButton.forceFocus();
  }
  else {
    getDisplay().beep();
  }
}

Here we construct the GUI. The various buttons and labels described earlier are created. Each button will have an associated AccessibleContext. It can also have an AccessibleListener implementation based on the enableChildAcc flag. The button's listener can override the listener established by the AccessibleContext. Notice that no AccessibleControlListener is established. This is because a Composite is used and it can provide all the needed information about its children and role. This is typically the case with Composite subclasses.

// build my GUI
protected GridLayout layout;
protected Button prevButton, nextButton;
protected Label monthLabel;
protected Button[] dayButtons = new Button[7 * 6]; // for all possible days and weeks
protected static final String BUTTON_DATA = "buttonData";
protected Button makeButton(Composite parent, int style) {
  Button b = new Button(parent, style);
  AccessibleContext ctx = new AccessibleContext(b); // can forget ctx
  if (enableChildAcc) {
    Accessible acc = b.getAccessible();
    AccessibleListener al = new ButtonAccessibleListener();
    acc.addAccessibleListener(al);
  }
return b;
}
protected void initGui() {
  layout = new GridLayout(7, true);
  layout.makeColumnsEqualWidth = true;
  setLayout(layout);
  GridData gd = null;

  prevButton = makeButton(this, SWT.PUSH);
  prevButton.setText("<"); // add icon
  prevButton.addSelectionListener(new SelectionAdapter() {
  public void widgetSelected(SelectionEvent e) {
  prevMonth();
  }
  });

  monthLabel = new Label(this, SWT.LEFT);
  gd = new GridData();
  gd.horizontalSpan = 5;
  gd.horizontalAlignment = gd.BEGINNING;
  monthLabel.setLayoutData(gd);
  nextButton = makeButton(this, SWT.PUSH);
  nextButton.setText(">"); // **** consider replace with icon ****
  nextButton.addSelectionListener(new SelectionAdapter() {
    public void widgetSelected(SelectionEvent e) {
      nextMonth();
    }
  });
  int xday = 0;
  for (int i = 0; i < dayButtons.length; i++) {
    xday++;
    Button b = makeButton(this, SWT.PUSH);
    b.setText("??"); // updated later
    b.setEnabled(false);
    b.setAlignment(SWT.RIGHT);
    gd = new GridData();
    gd.horizontalSpan = 1;
    gd.horizontalAlignment = gd.FILL;
    b.setLayoutData(gd);
    b.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        Widget w = e.widget;
        Calendar xcal = (GregorianCalendar)w.getData(BUTTON_DATA);
        if (xcal != null) {
          setCalendar(xcal);
          fireListeners(null, cal);
        }
      }
    });
    b.addListener (SWT.KeyUp, new Listener () {
      public void handleEvent (Event event) {
        onKeyPress(event);
      }
    });
    dayButtons[i] = b;
  }
}

The date button's AccessibleListener is an example of conventional accessibility enablement, where each control has its own listener. In this example the listener processes the name, help and description callbacks. Only requests for the button itself are processed, not any children requests (which should not occur as buttons are typically not implemented as a Composite). In each case the button's AccessibleContext is used to get the button that was selected. From that button the associated Calendar object is obtained. The calendar is used to format the requested information.

/**
* AccessibleListener for each nested button.
*
* Needs to be public but do not use outside this class
*/
public class ButtonAccessibleListener extends AccessibleAdapter
{
  public void getName(AccessibleEvent e) {
    Object oldResult = e.result;
    if (e.childID == ACC.CHILDID_SELF) {
      if (enableChildAcc) {
        try {
          Accessible acc = (Accessible)e.getSource();
          Button xb = (Button)AccessibleContext.getControl(acc);
          Object data = xb.getData(BUTTON_DATA);
          if (data != null) {
            e.result =
_dateFormat.format(((GregorianCalendar)data).getTime());
          }
        }
        catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    }
  public void getHelp(AccessibleEvent e) {
    Object oldResult = e.result;
    if (e.childID == ACC.CHILDID_SELF) {
      if (enableChildAcc) {
        try {
          Accessible acc = (Accessible)e.getSource();
          Button xb = (Button)AccessibleContext.getControl(acc);
          Object data = xb.getData(BUTTON_DATA);
          if (data != null) {
            e.result = "Selected date: " +             
_dateFormat
.format(((GregorianCalendar)data).getTime());
          }
        }
        catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    }
  public void getDescription(AccessibleEvent e) {
    Object oldResult = e.result;
    if (e.childID == ACC.CHILDID_SELF) {
      e.result = "Select this button to change the date";
      if (enableChildAcc) {
        try {
          Accessible acc = (Accessible)e.getSource();
          Button xb = (Button)AccessibleContext.getControl(acc);
          Object data = xb.getData(BUTTON_DATA);
          if (data != null) {
            e.result = "A 2D-month date picker currently set to date: " + _dateFormat.format(((GregorianCalendar)data).getTime());
          }
        }
        catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    }
  };

Each time a new date (possibly in a new month) is selected the GUI is updated.

// update values in the GUI due to date changes
protected void updateGui() {
  String ttt = getToolTipText();
  prevButton.setToolTipText("Select Prior Month");
  nextButton.setToolTipText("Select Next Month");
  monthLabel.setText(monthYearName);
   //trace("updateGui: " + monthYearName);
  monthLabel.setToolTipText("Selecting from " + monthYearName);
  monthLabel.setAlignment(SWT.CENTER);
  boolean started = false;
  int dayNo = 0;
  int index = 0;
  // update each week
  for (int week = 0; week < 6; week++) {
    // update each day
    for (int dow = 0; dow < 7; dow++) {
      String dn = " ";
      boolean enabled = false;
      String xttt = "Not selectable";
      Calendar xcal = null;
      Button b = dayButtons[index++];
      if (started || dow == firstDow) {
        started = true;
        String xdn = Integer.toString(++dayNo);
        if (dayNo <= days) {
          dn = xdn;
          enabled = true;
          xcal = new GregorianCalendar(year, month, dayNo);
          xttt = _dateFormat.format(xcal.getTime());
        }
      }
      if (dayNo == day) {
        b.forceFocus();
      }
      b.setText(dn);
      b.setToolTipText(xttt);
      b.setEnabled(enabled);
      b.setData(BUTTON_DATA, xcal);
    }
  }
}

Create an AccessibleListener for the main Composite. In this example the listener processes the name, help and description callbacks. Except for names, only requests for the button itself are processed. For the name, childIDs are used to select the correct date and format a name.

// initialize accessible listeners
protected void initAccessible() {
  Accessible accessible = getAccessible();
  accessible.addAccessibleListener(new MainAccessibleListener());
}
public class MainAccessibleListener extends AccessibleAdapter
{
  public void getName(AccessibleEvent e) {
    Object oldResult = e.result;
    Calendar xcal = null;
    if (e.childID == ACC.CHILDID_SELF) {
      xcal = cal;
    }
    if (e.childID >= 0) {
      xcal = (Calendar)dayButtons[e.childID].getData(BUTTON_DATA);
    }
    if (xcal != null) {
      e.result = _dateFormat.format(xcal.getTime());
    }
  }
  public void getHelp(AccessibleEvent e) {
    Object oldResult = e.result;
    if (e.childID == ACC.CHILDID_SELF) {
      e.result = "Selected date: " +
      _dateFormat.format(cal.getTime());
    }
  }
  public void getDescription(AccessibleEvent e) {
    Object oldResult = e.result;
    if (e.childID == ACC.CHILDID_SELF) {
      e.result = "A 2D-month date picker currently set to date: " +
      _dateFormat.format(cal.getTime());
    }
  }
}
}

7 Composite Custom Controls

Future addition

8 Canvas Custom Controls

As an alternative to creating custom controls from Composite, this chapter will reformulate the DatePicker class as a descendant of Canvas. This technique provides enhanced accessibility. Let's start at the end and work backwards. The finished component will look like this:

This screenshot shows that the DatePicker control is composed of a Canvas that draws all of its controls. At the operating system level there is only one control − the Canvas; all GUI and accessibility support is provided by that Canvas. There are previous, next and date buttons. The previous and next buttons change the month. The blank buttons do nothing. The date buttons cause an event to be fired to indicate a date is picked.

8.1 DatePickerCanvas Walkthrough

Now let us look at the code for DatePickerCanvas. Some of its features were discussed in the CLabel discussion earlier in this document (DatePickerCanvas is derived from the Clabel source file) and others are not critical to accessibility. The full source is included in  _DatePickerCanvas.java_.

package com.ibm.wac.eclipse.accessibility;
public class DatePickerCanvas extends Canvas
{  

The currently selected date is keep as a Calendar value. Each time it is changed some values are cached and the selected date is displayed. The Accessible.setFocus method is called to let any listening AT know the Control has been updated.

private Calendar cal;
public void setCalendar(Calendar cal) {
  checkWidget();
  if ( cal == null ) {
  SWT.error(SWT.ERROR_NULL_ARGUMENT); }
  this.cal = cal;
  DateFormat mdf = new SimpleDateFormat("MMMMMMMMMMMMMMMMMMMM");
  monthName = mdf.format(cal.getTime()).trim().toUpperCase();
  yearName = Integer.toString(cal.get(Calendar.YEAR));
  monthYearName = monthName + ' ' + yearName;
  // *** need to I18N day names here ***
  month = cal.get(cal.MONTH);
  day = cal.get(cal.DAY_OF_MONTH);
  selected = day;
  year = cal.get(cal.YEAR);
  days = cal.getActualMaximum(cal.DAY_OF_MONTH);
  Calendar first = new GregorianCalendar(year, month, 1);
  firstDow = first.get(first.DAY_OF_WEEK) - first.getFirstDayOfWeek();
  Calendar last = new GregorianCalendar(year, month, cal.getActualMaximum(cal.DAY_OF_MONTH));
  lastDow = last.get(last.DAY_OF_WEEK) - last.getFirstDayOfWeek();
  weeks = weeksInMonth(dowConverter(firstDow), days);
  pack();
  redraw();
  Accessible acc = getAccessible();
  if (acc != null) {
    acc.setFocus(day - 1);
  }
}

Several focus, mouse and key event handlers are defined. Through arrow keys you can select a new date. The Enter key fires a date change event. Changing the date causes the DatePicker to repaint.

private boolean mouseOver;
private void onMouseOver(Event event, boolean over) {
  mouseOver = over;
}
private boolean haveFocus;
private void onFocus(Event event, boolean gained) {
  haveFocus = gained;
}
private void onKeyPress(Event event) {
  if (event.keyCode == 0 && event.character == ' ') {
  // *** add here ***
}
  else if (event.keyCode == SWT.ARROW_LEFT) {
    int xday = day - 1;
    if (xday >= 1) {
      setCalendar(new GregorianCalendar(year, month, xday));
    }
    else {
      getDisplay().beep();
    }
  }
  else if (event.keyCode == SWT.ARROW_RIGHT) {
    int xday = day + 1;
    if (xday <= days) {
      setCalendar(new GregorianCalendar(year, month, xday));
    }
    else {
      getDisplay().beep();
    }
  }
  else if (event.keyCode == SWT.ARROW_UP) {
    int xday = day - 7;
    if (xday > 1) {
      setCalendar(new GregorianCalendar(year, month, xday));
    }
    else {
      getDisplay().beep();
    }
  }
  else if (event.keyCode == SWT.ARROW_DOWN) {
    int xday = day + 7;
    if (xday <= days) {
      setCalendar(new GregorianCalendar(year, month, xday));
    }
    else {
      getDisplay().beep();
    }
  }
  else if (event.keyCode == '\r') {
    fireListeners(event, cal);
  }
  else {
    getDisplay().beep();
  }
}
private void onMouseClick(Event event, int count) {
  onMouseClick(event, count, false);
}
private void onMouseClick(Event event, int count, boolean down) {
  trace("onMouseClick: " + event + "," + count + ',' + down + ',' + event.button );
  if (event.button == 1) {
    if (count == 1) {
      int xday = hitTest(event.x, event.y);
      if (xday >= 0) {
        setCalendar(new GregorianCalendar(year, month, xday));
      }
      if (down && contains(leftRect, event.x, event.y)) {
        prevMonth();
      }
      if (down && contains(rightRect, event.x, event.y)) {
        nextMonth();
      }
    }
    else if (count == 2) {
      fireListeners(event, cal);
    }
  }
  else {
    if (down & count == 1) {
      initMenu();
    }
  }
}

Several utility methods are defined to size internal components and do "hit" testing.

private static boolean contains(Rectangle ext, int x, int y) {
  return x >= ext.x && x < ext.x + ext.width && y >= ext.y && y < ext.y + ext.height;
}

private int hitTest(int x, int y) {
  int xday = -1;
  // test inside
  //trace("hitTest: " + x + ':' + y + ',' + startX + ':' + startY + ',' +
endX + ':' + endY);

  if (startX <= x && endX > x && startY <= y && endY > y ) {
    int w = endX - startX, h = endY - startY;
    int dx = x / (w / 7), dy = y / (h / weeks) - 2;
    int zday = dy * 7 + dx - firstDow + 1;
    if (zday >= 1 && zday <= days) {
      xday = zday;
    }
    //trace("hitTest: " + xday + ':' + zday + ',' + dx + ':' + dy + ',' +
x + ':' + y + ',' }

  }
  return xday;
}
private Rectangle dayExtent(int day) {
  Rectangle r = null;
  int w = endX - startX, h = endY - startY;
  int dx = w / 7, dy = h / weeks;
  int xday = (day - 1) + firstDow, week = xday / 7,
zday = xday - week * 7;
  if (day >= 1 && day <= days) {
    r = new Rectangle(startX + zday * dx, startY + week * dy, dx, dy);
  }
  return r;
}

Here we construct the GUI. This is done as the GUI is painted. This is typical of drawn controls. The Canvas' client area is filled with pseudo−buttons and labels. First the Canvas' background is painted. The any border and finally the interior controls are painted. Once complete the image of the DatePicker is displayed. No behavior is defined.

int startX, startY, endX, endY;

Rectangle leftRect = new Rectangle(0, 0, 8, 8), rightRect =
new Rectangle(0, 0, 8, 8);
/*
* Process the paint event
*/

private void onPaint(PaintEvent event) {
  Rectangle rect = getClientArea();
  if ( rect.width == 0 || rect.height == 0 ) return;
    Point size = getMinimumSize();
    int wDelta = Math.max((rect.width - size.x) / 2, 0);
    //int hDelta = Math.max((rect.height - size.y) / 2, 0);
    int hDelta = 0;
    // determine start position
    int x = rect.x + hIndent + wDelta,
    y = rect.y + vIndent + hDelta;
    GC gc = event.gc; // easier reference
    FontMetrics fm = gc.getFontMetrics();
    Point mnsize = gc.textExtent(monthYearName);
    // draw my background
    boolean bgPainted = false;
    if ( backgroundImage != null ) {
      try {
        Rectangle imageRect = backgroundImage.getBounds();
        gc.drawImage(backgroundImage, 0, 0, imageRect.width,
imageRect.height, 0, 0, rect.width, rect.height);
        bgPainted = true;
      }
      catch ( SWTException e ) {
    }
  }
  if (!bgPainted) {
    gc.setBackground(getBackground());
    gc.fillRectangle(rect);
  }
  // draw border
  int style = getStyle();
  if ( (style & SWT.SHADOW_IN) != 0 ||
(style & SWT.SHADOW_OUT) != 0 ) {
    paintBorder(gc, rect);
  }
  // draw the calendar
  // draw month name
  gc.setForeground(getForeground());
  gc.drawText(monthYearName, Math.max(0,
(rect.width - mnsize.x) / 2), y, true);
  leftRect.x = 2;
  leftRect.y = y;
  paintDirectionButton(gc, leftRect.x, leftRect.y, -1,
false, false);
  rightRect.x = rect.width - 8 - hIndent - wDelta;
  rightRect.y = y;
  paintDirectionButton(gc, rightRect.x, rightRect.y, 1,
false, false);
  y += mnsize.y + gap;

  // draw day of week names
  Point dnsize = gc.textExtent(_dayNames[0]);
  int dnwidth = (size.x - (7 - 1) * gap) / 7;
  int dnx = x;
  for (int dow = 0; dow < 7; dow++) {
    String dn = _dayNames[dow];
    dnsize = gc.textExtent(dn);
    int delta = Math.max(0, dnwidth - dnsize.x);
    gc.drawText(dn, dnx + delta, y, true);
    dnx += dnwidth + gap;
  }
  y += dnsize.y;

  // draw the month
  endX = startX = x;
  startY = y;
  Color baseColor = gc.getForeground();
  if (highlightColor == null) {
    highlightColor = baseColor;
  }
  Point dsize = gc.textExtent("0");
  int dwidth = (size.x - (7 - 1) * gap) / 7;
  boolean started = false;
  int dayNo = 0;
  // draw each week
  for (int week = 0; dayNo < days && week < weeks; week++) {
    int dx = x;
    // draw each day
    for (int dow = 0; dayNo < days && dow < 7; dow++) {
      if (started || dow == firstDow) {
        String dn = Integer.toString(++dayNo);
        dnsize = gc.textExtent(dn);
        Point sbsize = gc.textExtent("00");
        int delta = Math.max(0, dwidth - dnsize.x);
        gc.setForeground(dayNo == day ? highlightColor : baseColor);
        gc.drawText(dn, dx + delta, y, true);
        if (selected >= 0 && dayNo == selected) {
          paintSelectBorder(gc, dx - 2, y - 2, sbsize.x + 4,
sbsize.y + 4);
        }
        started = true;
      }
      dx += dwidth + gap;
      endX = Math.max(endX, dx);
    }
    y += dsize.y + gap;
  }
  endY = y;
}

The DatePicker's AccessibleListener is an example of accessibility enablement for a set of virtual (or pseudo−) controls. In this example the listener processes the name, help and description callbacks. The passed childID value is used to select the target pseudo−control. The calendar is used to format the requested information.

protected void initAccessible() {
  Accessible accessible = getAccessible();
  AccessibleListener al = new AccessibleAdapter() {
      public void getName(AccessibleEvent e) {
        e.result = _dateFormat.format(
        (e.childID == ACC.CHILDID_SELF ? cal : new
GregorianCalendar(year, month, e.childID + 1)).getTime());
        trace("getName: " + e.result);
      }
      public void getHelp(AccessibleEvent e) {
        e.result = "Selected date: " +
        _dateFormat.format(
        (e.childID == ACC.CHILDID_SELF ? cal : new
GregorianCalendar(year, month, day)).getTime());
      }
      public void getDescription(AccessibleEvent e) {
        e.result = "A 2D-month date picker currently set to date: " +
        _dateFormat.format(
        (e.childID == ACC.CHILDID_SELF ? cal : new
GregorianCalendar(year, month, day)).getTime());
      }
    };
    accessible.addAccessibleListener(al);

Canvas subclasses generally must provide an  AccessibleControlListener implementation which can be quite involved. It must create virtual controls that have required accessible behavior. Thus it is generally important to implement each method of the AccessibleControlListener interface. This control uses a zero−based index for childIDs. Day 1 is 0, day 2 is 1, etc. This is a typical assignment of childIDs. Most of the requests return static information based on whether the control itself or a pseudo−child is being queried. In order to select a child, the getChildAtPoint, getLocation and getFocus methods must be implemented. To access children the getChild, getChildCount and getChildren methods must be implemented. Since no real control exists for each day, there is no real Accessible object to back it, thus the getChild method must return null for day buttons. The getValue method is only required if the control has a value. The getSelection method is only required if the control's state can ever supports selection. The other methods are required and provide descriptive or state information.

// **** does not support next/previous buttons ****
AccessibleControlListener acl = new AccessibleControlAdapter() {
  public void getChildAtPoint(AccessibleControlEvent e) {
    int xChildId = e.childID;
    e.childID = ACC.CHILDID_NONE; // default
    Point pt = toControl(new Point(e.x, e.y));
    if (getBounds().contains(pt)) {
      int xday = hitTest(pt.x, pt.y);
      e.childID = xday < 0 ? ACC.CHILDID_SELF : xday - 1;
    }
    trace("getChildAtPoint: " + e.childID + "<==" + xChildId);
  }
  public void getDefaultAction(AccessibleControlEvent e) {
  //e.result = "Select";
    e.result = "Double Click";
    trace("getDefaultAction: " + e.childID + ',' + e.result);
  }
  public void getLocation(AccessibleControlEvent e) {
    Rectangle location = e.childID == ACC.CHILDID_SELF
    ? getBounds() : dayExtent(day);
    Point pt = toDisplay(new Point(location.x, location.y));
    e.x = pt.x;
    e.y = pt.y;
    e.width = location.width;
    e.height = location.height;
    trace("getLocation: " + e.childID + ',' + location);
  }
  public void getFocus(AccessibleControlEvent e) {
    int xChildId = e.childID;
    e.childID = haveFocus ? day - 1 : ACC.CHILDID_NONE;
    trace("getFocus: " + e.childID + "<==" + xChildId);
  }
  public void getChild(AccessibleControlEvent e) {
    e.accessible = e.childID == ACC.CHILDID_SELF ?
DatePickerCanvas.this.getAccessible() : null;
    trace("getChild: " + e.childID + ',' + e.accessible);
  }
  public void getChildCount(AccessibleControlEvent e) {
    e.detail = days;
    trace("getChildCount: " + e.childID + ',' + e.detail);
  }
  public void getState(AccessibleControlEvent e) {
    int xdetail = e.detail;
    e.detail |= ACC.STATE_READONLY | ACC.STATE_FOCUSABLE |
ACC.STATE_SELECTABLE;
    if (haveFocus) {
      if (e.childID == ACC.CHILDID_SELF) {
        e.detail |= ACC.STATE_FOCUSED;
      }
      else if (e.childID == day - 1) {
        e.detail |= ACC.STATE_FOCUSED | ACC.STATE_SELECTED;
      }
    }
    trace("getState: " + e.childID + ',' + e.detail + "<==" +
xdetail);
  }
  public void getValue(AccessibleControlEvent e) {
    if (e.childID != ACC.CHILDID_NONE) {
      e.result = _dateFormat.format(
        (e.childID == ACC.CHILDID_SELF ? cal : new
GregorianCalendar(year, month, e.childID + 1)).getTime());
    }
    trace("getValue: " + e.childID + ',' + e.result);
  }
  public void getRole(AccessibleControlEvent e) {
    e.detail = e.childID == ACC.CHILDID_SELF ?
ACC.ROLE_LIST : ACC.ROLE_LISTITEM;
    trace("getRole: " + e.childID + ',' + e.detail);
  }
  public void getSelection(AccessibleControlEvent e) {
    int xChildId = e.childID;
    e.childID = haveFocus ? day - 1 : ACC.CHILDID_NONE;
    trace("getSelection: " + e.childID + "<==" + xChildId);
  }
  public void getChildren(AccessibleControlEvent e) {
    Integer[] ids = new Integer[days];
    for (int i = 0; i < days; i++) {
      ids[i] = new Integer(i);
    }
    e.children = ids;
    trace("getChildren: " + e.childID + ',' + e.children);
  }
};
accessible.addAccessibleControlListener(acl);
trace("InitAccessible done");

9 IAccessible2 enhancements

9.1 Encapsulation of IAccessible2

Eclipse provides methods that fully encapsulate the underlying IAccessible2 implementation. It handles the tedious process of obtaining the IAccessible2 interface. IAccessible2 documentation for C and C++ provides instructions about using QueryInterface to get IAccessible2. As an Eclipse programmer, you do not need not to worry about this because it is handled for you.

9.1.1 Return codes

Similarly unless specifically required by the API, you are not required to provide return codes. When return codes are required, they are placed in the result member of event. This is illustrated in the following code fragment.

public void replaceText(AccessibleEditableTextEvent e) {
  valueField.setText("replaceText " + e);
  text.replaceTextRange(e.start, e.end - e.start, e.string);
  e.result = ACC.OK;
}

9.2 Text controls

Text controls have a different set of methods for edit and view mode. View methods were added in Eclipse 3.6, and edit methods were added in version 3.7.

9.2.1 Text viewing

9.2.1.1 Getting text

An accessible widget must respond to all of the requests that an AT can make of an application server regarding text. This means you should implement an AccessibleTextListener. You can use the AccessibleTextExtendedAdapter. A simple example of this is Snippet334.java available from the Eclipse.org SWT Snippets page. You can use the adapter and implement only some of the methods, but good practice is to implement all the methods of the adapter.

The getText method should handle multiple ways to specify the text to be retrieved. You receive the specification in an AccessibleTextEvent, which is a structure of values.

The type field indicates the way the text query is specified and dictates what values you will find in the other fields. In the code fragment below, note that types such as TEXT_BOUNDARY_ALL and TEXT_BOUNDARY_CHAR are specified with different values. Depending on the type, AccessibleTextEvent values contain meaning metrics for collecting the text.

public void getText(AccessibleTextEvent e) {
  int start = 0, end = text.length();
  switch (e.type) {
    case ACC.TEXT_BOUNDARY_ALL : start = e.start;
      end = e.end;
    break;
    case ACC.TEXT_BOUNDARY_CHAR : start = e.count >= 0 ?
e.start + e.count : e.start - e.count;
      start = Math.max(0, Math.min(end, start));
      end = start;
      e.count = start - e.start;
      e.start = start;
      e.end = start;
    break;
    case ACC.TEXT_BOUNDARY_LINE : int offset = e.count
<= 0 ? e.start : e.end;
      offset = Math.min(offset, text.length());
      int lineCount = 0;
      int index = 0;
      while(index != -1) {
        lineCount ++;
        index = text.indexOf("\n", index);
        if (index != -1) index++;
      }
      e.count = e.count < 0 ? Math.max(e.count, -lineCount) :
Math.min(e.count, lineCount);
      index = 0;
      int lastIndex = 0;
      String[] lines = new String[lineCount];
      for (int i = 0; i < lines.length; i++) {
        index = text.indexOf("\n", index);
        lines[i] = index != -1 ?
text.substring(lastIndex, index) : text.substring(lastIndex);
        lastIndex = index;
        index++;
      }
      int len = 0;
      int lineAtOffset = 0;
      for (int i = 0; i < lines.length; i++) {
        len += lines[i].length();
        if (len >= e.offset) {
          lineAtOffset = i;
          break;
        }
      }
      int result = Math.max(0, Math.min(lineCount-1,
lineAtOffset + e.count));
      e.count = result -lineAtOffset;
      e.result = lines[result];
    break;
  }
  e.result = text.substring(start, end);
}

You must also handle all requests related to the cursor and the text that is selected. Your text widget manages and tracks the text and the location of the cursor. In the code fragments below, a hard-coded single selection is used as the example for easy understanding. There is only one selection to be returned. It is assumed for the character count and visible range. Your application will have more to do to track these values, but the interface will remain the same.

public void getSelectionCount(AccessibleTextEvent e) {
  e.count = 1;
}
public void getSelection(AccessibleTextEvent e) {
  // there is only 1 selection, so index = 0
  getSelectionRange(e);
  e.start = e.offset;
  e.end = e.offset + e.length;
}
public void getRanges(AccessibleTextEvent e) {
  // for now, ignore bounding box
  e.start = 0;
  e.end = text.length() - 1;
}
public void getCharacterCount(AccessibleTextEvent e) {
  e.count = text.length();
}
public void getVisibleRanges(AccessibleTextEvent e) {
  e.start = 0;
  e.end = text.length() - 1;
}
});

9.2.1.2 StyledText widget

SWT strives to provide a consistent native look and feel user interface across multiple platforms. To achieve this it uses native widgets as much is possible, providing a thin Java wrapper layers around each native widget. When a native widget is not available SWT implements classes to emulate what a native widget would do, if it existed.

Because of this and the need for Eclipse to have a fully functional editor, StyledText widget was created. StyledText provides full function and fully accessible editing capabilities for rich text. It is a powerful tool in your development arsenal.

The eclipse snippets page has an entire section of sample code related to the use of StyledText widget. Further there are two articles at eclipse.org about using and customizing Styled Text widget:

The style of any displayed text is easily controlled and manipulated using the StyleRange class. For example, the following code fragment from Snippet189 illustrates setting both underlined and strikethrough appearance on text.

StyleRange style3 = new StyleRange();
style3.start = 25;
style3.length = 13;
style3.underline = true;
style3.strikeout = true;
text.setStyleRange(style3);

9.2.1.3 StyledText widget accessibility

StyledText is also a model of adding accessibility to a custom widget. The code is available for review in package org.eclipse.swt.custom. StyledText demonstrates the following capabilities (and more):

9.2.1.4 Getting Attributes

Attributes may be requested by the client. You implement a server to respond to the getTextAttributes method and provide the information in the AccessibleTextAttributeEvent.

StyledText.java in org.eclipse.swt.custom provides a good example of how to implement getTextAttributes. Key steps in returning the information are shown in the following code fragments:

If the selection area (lineLength) exists, the style is retrieved from the layout for the characters of interest and placed in the AccessibleTextAttributeEvent e.

if (lineLength > 0) {
  e.textStyle = layout.getStyle(Math.max(0,
Math.min(offset, lineLength - 1)));
}

If there was no style, create one using the defaults. Otherwise, create a temporary style to hold the values. Copy the style values of the active StyledText widget (this) to the temporary style. Copy the temporary style to the AccessibleTextAttributeEvent e.

// If no override info available, use defaults.
if (e.textStyle == null) {
  e.textStyle = new TextStyle(st.getFont(), st.foreground,
st.background);
} else {
  if (e.textStyle.foreground == null ||
e.textStyle.background == null || e.textStyle.font == null) {
    TextStyle textStyle = new TextStyle(e.textStyle);
    if (textStyle.foreground == null) textStyle.foreground =
st.foreground;
    if (textStyle.background == null) textStyle.background =
st.background;
    if (textStyle.font == null) textStyle.font = st.getFont();
    e.textStyle = textStyle;
  }
}

9.2.1.5 Manipulating selection

The selection can be set for Text fields, as shown in the following fragment:

String text = new String(searchedText.getText());
String search = new String(searchText.getText());
int location = text.indexOf(search);
if (location >=0){
  searchedText.setSelection(location, location +
searchText.getCharCount());
}

See also Snippet12 for an example of selection.

9.2.1.6 Managing the caret

The text objects in Eclipse have N+1 caret positions, which start at 0, providing a caret position before the text.  The caret position is obtained directly from the text field, as shown in the following fragment:

searchedText.getCaretPosition());

The caret and motion related to it can be monitored with a CaretListener.

text.addCaretListener(new CaretListener() {
  public void caretMoved(CaretEvent event) {
    int caretLine = text.getLineAtOffset(event.caretOffset);
    System.out.println ("caretline=" + caretLine);
    System.out.println ("selection=" + text.getSelection ());
    System.out.println ("offset=" + text.getCaretOffset());
    System.out.println ("horizIndex=" + text.getHorizontalIndex());
    System.out.println ("selectionText=" + text.getSelectionText());
  }
});

See also Snippet11 for an example of selection.

9.2.2 Text editing

Eclipse provides several methods for manipulating text by the AT. The reader should review the implementation of StyledText widget.

9.2.2.1 Inserting text

Inserting text is straightforward. The following code sample from Snippet243 illustrates an insert based on a selection range:

public void verifyText (VerifyEvent event) {
  text1.setTopIndex (text0.getTopIndex ());
  text1.setSelection (event.start, event.end);
  text1.insert (event.text);
}

9.2.2.2 Replacing text

Replacing text can be done through AccessibleEditableTextListener. The code fragment in the section on Cut and Paste shows an example implementation:

9.2.2.3 Cut and paste

Responding to cut and paste events is done through the addition of an AccessibleEditableTextListener, which has the associated methods of cut, paste, and copy. The following code fragment from org.eclipse.swt.custom StyledText shows an example implementation.

accessible.addAccessibleEditableTextListener(new
AccessibleEditableTextListener() {
public void setTextAttributes(AccessibleTextAttributeEvent e) {
// This method must be implemented by the application
  e.result = ACC.OK;
}
public void replaceText(AccessibleEditableTextEvent e) {
  StyledText st = StyledText.this;
  st.replaceTextRange(e.start, e.end - e.start, e.string);
  e.result = ACC.OK;
}
public void pasteText(AccessibleEditableTextEvent e) {
  StyledText st = StyledText.this;
  st.setSelection(e.start);
  st.paste();
  e.result = ACC.OK;
}
public void cutText(AccessibleEditableTextEvent e) {
  StyledText st = StyledText.this;
  st.setSelection(e.start, e.end);
  st.cut();
  e.result = ACC.OK;
}
public void copyText(AccessibleEditableTextEvent e) {
  StyledText st = StyledText.this;
  st.setSelection(e.start, e.end);
  st.copy();
  e.result = ACC.OK;
}
});

9.2.3 Text style attributes

Text controls can obtain and set text attributes, including, font and color. The following snippet shows how this can be done.  Here are instructions for how to test this:

  1. Start the snippet and start AccProbe 1.2.1 or greater.
  2. Now hover over the StyledText ("The quick brown fox...") at the top of the AccessibleEditableText window. AccProbe displays ia2 interfaces in its Accessibility Properties view.
  3. Expand IAccessibleText and double-click on "attributes".
  4. Enter 5 for the offset, click Invoke Method. The results display in the lower part of the dialog.
  5. Select everything in the Results line after "text=" (Note that this string extends beyond the window edge, so drag-select to get it all).
  6. Copy the string (^C or context menu->copy) to the clipboard.
  7. Hover over the StyledText again, to cause the Ia2 interfaces to be shown again. This time expand IAccessibleEditableText in AccProbe and double-click on "setAttributes".
  8. In the Invoke Method dialog, use startOffset 4 and endOffset 9 and paste the text into the "attributes" field.
  9. Change something, for example change color:rgb(255,0,0); to color:rgb(0,255,0);
  10. Press Invoke method.
  11. The word "quick" in the StyledText changes from red to green.

The full snippet can be found in the appendix. It is titled, AccessibleEditableText.

text.getAccessible().addAccessibleAttributeListener(new
AccessibleAttributeAdapter() {
  public void getTextAttributes(AccessibleTextAttributeEvent e) {
    valueField.setText("getTextAttributes" + e);
  }
});
text.getAccessible().addAccessibleEditableTextListener(new
AccessibleEditableTextAdapter() {
  public void setTextAttributes(AccessibleTextAttributeEvent e) {
    valueField.setText("setAttributes " + e);
    TextStyle textStyle = e.textStyle;
    if (textStyle != null) {
      /* Copy all of the TextStyle fields into the new StyleRange. */
      StyleRange style = new StyleRange(textStyle);
      /* Create new graphics resources because the old ones are only valid during the event. */
      if (textStyle.font != null) style.font = new
Font(display, textStyle.font.getFontData());
        if (textStyle.foreground != null) style.foreground = new
Color(display, textStyle.foreground.getRGB());
        if (textStyle.background != null) style.background = new
Color(display, textStyle.background.getRGB());
        if (textStyle.underlineColor != null) style.underlineColor = new
Color(display, textStyle.underlineColor.getRGB());
        if (textStyle.strikeoutColor != null) style.strikeoutColor = new
Color(display, textStyle.strikeoutColor.getRGB());
        if (textStyle.borderColor != null) style.borderColor = new
Color(display, textStyle.borderColor.getRGB());
          /* Set the StyleRange into the StyledText. */
          style.start = e.start;
          style.length = e.end - e.start;
          text.setStyleRange(style);
          e.result = ACC.OK;
        } else {
        text.setStyleRanges(e.start, e.end - e.start, null, null);
      }
    }
  });

9.2.4 StyledText widget

SWT strives to provide a consistent native look and feel user interface across multiple platforms. To achieve this it uses native widgets as much is possible, providing a thin Java wrapper layers around each native widget. When a native widget is not available SWT implements classes to emulate what a native widget would do, if it existed.

Because of this and the need for Eclipse to have a fully functional editor, StyledText widget was created. StyledText provides full function and fully accessible editing capabilities for rich text. It is a powerful tool in your development arsenal.

The eclipse snippets page has an entire section of sample code related to the use of StyledText widget.

The style of any displayed text is easily controlled and manipulated using the StyleRange class. For example, the following code fragment from Snippet189 illustrates setting both underlined and strikethrough appearance on text.

StyleRange style3 = new StyleRange();
style3.start = 25;
style3.length = 13;
style3.underline = true;
style3.strikeout = true;
text.setStyleRange(style3);

9.2.5 StyledText widget accessibility

StyledText is also a model of adding accessibility to a custom widget. The code is available for review in package org.eclipse.swt.custom. StyledText demonstrates the following capabilities (and more):

9.2.6 Embedded objects – Hyperlinks

IAccessible2 allows rich text fields to contain embedded objects. In fact, the embedded object does not exist within the text, but rather a marker is left in the text which references the object. This referencing of embedded objects was unfortunately named hyperlink in the IA2 specification. This has caused some confusion, because it has nothing to do with web links, as you might find in a browser. Such links are covered in the MSAA specification and are called links.

In the text string the hex code FFFC is used as a marker to denote the exact location of an embedded object. Each embedded object uses the same FFFC code. It is up to the AT to interrogate the code for the embedded object at that index of embedded objects within the string.

The following are pertinent code fragments regarding embedded objects. The first shows the encoding of the text string, with the FFFC codes.

static String text =
"One: \uFFFC, and here is another: \uFFFC. \n
This snippet shows how to embed widgets in a StyledText.\n"
;

The second shows the getHyperlinkCount method on the AccessibleTextExtendedAdapter which handles AT requests.

styledText.getAccessible().addAccessibleTextListener(new
AccessibleTextExtendedAdapter() {
  public void getHyperlinkCount(AccessibleTextEvent e) {
  e.count = 2;
}

The third shows how the getHyperlink method must return the correct information for the correct index. The embedded objects indexes are coded with constants (i.e. BUTTON_INDEX). In this snippet, the Accessible of the embedded button object, which itself is a control, has an AccessibleHyperlinkAdapter added, that knows its own start and end offset, which is the same location.

public void getHyperlink(AccessibleTextEvent e) {
  if (e.index == BUTTON_INDEX) {
    e.accessible = button.getAccessible();
    e.accessible.addAccessibleHyperlinkListener(new
AccessibleHyperlinkAdapter() {
    public void getStartIndex(AccessibleHyperlinkEvent e) {
      e.index = buttonOffset;
    }
    public void getEndIndex(AccessibleHyperlinkEvent e) {
      e.index = buttonOffset;
    }
    public void getAnchor(AccessibleHyperlinkEvent e) {
      e.result = "this is the anchor for the button";
    }
    public void getAnchorTarget(AccessibleHyperlinkEvent e) {
      e.result = "";
    }
  });
}
… etc

9.3 Relations

Associations can be made between controls to provide a structural relationship between two elements. This is useful when making logical controls from groups, or pairings that are not built in. The assistive technology will usually read the text and content of the associated element. You should test the exact behavior of the assistive technology you are supporting.

The following is the pertinent code fragment showing the association of a label for a control. It is taken from Snippet350.java available from the Eclipse.org SWT Snippets page.

Label nameLabel = new Label(shell, SWT.NONE);
nameLabel.setText("Name:");
Text nameText = new Text(shell, SWT.BORDER);
nameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
Accessible accNameLabel = nameLabel.getAccessible();
Accessible accNameText = nameText.getAccessible();
accNameLabel.addRelation(ACC.RELATION_LABEL_FOR, accNameText);
accNameText.addRelation(ACC.RELATION_LABELLED_BY, accNameLabel);

Here, a label and a text field are created. The accessible objects for the label and field are retrieved, and then each is added as the label for and labeled by element, respectively. Snippet340.java provides an example of using ACC.RELATION_DESCRIBED_BY to associate a region of live text with a field such that the live text is read every time the value of the field is changed.

Although org.eclipse.swt.accessibility.Accessible allows you to add and remove relations, it does not allow you to get them. Internal classes and methods make the relations available to the AT, but the application itself should have no need to reference them.

9.4 Events

SWT 3.6 supports a full range of events. Events use the sendEvent method on the Accessible object, specifying first the event using one of the ACC constants, and passing a structure defined for the event as the second parameter. An example follows

int[] eventData = new int[n];
eventData[0] = value;
eventData[1] = value;

eventData[n] = value;
getAccessible().sendEvent(ACC.event, eventData);

Structures are defined for TextSegments, TableModelChanges, and Locale.

9.5 Tables

Table APIs are supported in SWT 3.6. However, table objects do not support IA2 natively. Therefore, IA2 support must be added separately via direct calls to the APIs. This means developers must create custom table classes that can respond to the accessibility events.

An example of table classes that add capability of accessible table methods and interfaces is found in the SWT Example AccessibleTableExample.

AccessibleTableExample illustrates the use of table methods and interfaces. In the example, a new class, CTable is which embodies the table, and methods for its construction and manipulation.

9.5.1 Change Table EventNotification

All changes to the structure and content of the table are sent as event notifications to any listeners.  The following snippet from CTable, illustrates how this is done.

When column is added, event data is added to a structure. That data contains the type of action which occurred (0) which is INSERT or DELETE, the index of the first row that changed (1), the number of contiguous rows that changed (2), index of the first column that changed, (3) and the number of contiguous columns that changed (4).

void createItem (CTableColumn column, int index) {
  CTableColumn[] newColumns = new CTableColumn [columns.length + 1];
  . . .
  /* Columns were added, so notify the accessible. */
  int[] eventData = new int[5];
  eventData[0] = ACC.INSERT;
  eventData[1] = 0;
  eventData[2] = 0;
  eventData[3] = index;
  eventData[4] = 1;
  getAccessible().sendEvent(ACC.EVENT_TABLE_CHANGED, eventData);
}

Similar event notifications must be provided when columns are added or removed, rows are added or removed, both for standard and list-display tables.

10 Accessible Graphics using GEF

Future addition

11 Testing tools

11.1 Inspect

Future addition

11.2 AccProbe

AccProbe is a testing tool based on Eclipse which allows you to view MSAA or IAccessible2 API data and calls. The AccProbe site at the Linux Foundation provides current downloads and documentation. Many properties and events were only fully testable as ATs created features to use them. Consequently, the more recent versions work significantly more accurately, and are recommended.

A user guide is provided in the zip file that is downloaded with the AccProbe program. It explains how to install the program, and the basic options for running.

Both the user guide and the information page on the AccProbe site provide installation instructions. Note that in order to inspect or monitor IAccessible2 based applications you must register the IAccessible2proxy.dll, as described on the information page. If only MSAA properties display when running, you have likely forgotten to register the IAccessible2proxy.dll.  If you get a message “…call to DllRegisterServer failed with error code 0x80070005”, it is because you must register using Administrator authority. Try running from a command prompt invoked as Administrator: Go to Start > All Programs > Accessories. Right click Command Prompt, select Run as Administrator.

AccProbe allows you to inspect properties for MSAA and IAccessible2 interfaces. The primary display has a tree view of processes on the left, and the properties of the window in focus on the right. As you select a window of interest using either the mouse, or the cursor, AccProbe will interrogate the window and provide a list of all the properties, grouping the IAccessible2 properties in the MSAA properties into two groups. User settings allow you to filter and control which properties are shown.

Some properties call APIs with expected and standard values. These are called for you and their results are displayed in the Accessibility Properties view. Some properties are only obtained by calling an API with specific parameters. In these cases a dialogue is provided for you where the parameters can be entered and sent to the window.

AccProbe also features an event viewer, allowing you to capture and filter system events, so they can be viewed.

AccProbe is fully accessible, and works with assistive technologies. It is 508 compliant.

13 Code Samples

This appendix provides resources to developers that want to learn by excecuting complete code. It provides links to complete code samples that are available on the web. Alternatively, it provides full runnable code samples in text forms for some of the examples.

13.1 Links to code samples available on web

Eclipse.org SWT Snippets A foundational set of code samples demonstrating SWT coding.

Eclipse.org SWT Examples Larger, more comprehensive SWT sample applications.

Other resources: SWT tutorial

13.2 AccessibleEditableTextTest

This example shows how an Eclipse StyledText widget must self-implement an AccessibleEditableTextAdapter to provide cut and paste capability.

import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.accessibility.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
public class AccessibleEditableTextTest {
  public static void main(String[] args) {
    final Display display = new Display();
    Shell shell = new Shell(display);
    shell.setText("AccessibleEditableText");
    shell.setLayout(new GridLayout());
    final StyledText text = new StyledText(shell, SWT.BORDER | SWT.MULTI);
    text.setText("The quick brown fox jumps over the lazy dog.\nThat's all folks!");
    text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    TextStyle textStyle = new TextStyle(new Font(display, "Courier", 12, SWT.BOLD),
display.getSystemColor(SWT.COLOR_RED), null);
    textStyle.strikeout = true;
    textStyle.underline = true;
    textStyle.underlineStyle = SWT.UNDERLINE_SINGLE;
    text.setStyleRanges(new int[] {4, 5}, new StyleRange[] {new StyleRange(textStyle)});
    final Text valueField = new Text(shell, SWT.MULTI | SWT.WRAP | SWT.BORDER);
    GridData data = new GridData(400, 200);
    data.horizontalAlignment = SWT.FILL;
    valueField.setLayoutData(data);
    text.getAccessible().addAccessibleAttributeListener(new
AccessibleAttributeAdapter() {
      public void getTextAttributes(AccessibleTextAttributeEvent e) {
        valueField.setText("getTextAttributes" + e);
      }
    });
    text.getAccessible().addAccessibleEditableTextListener(new
AccessibleEditableTextAdapter() {
      public void setTextAttributes(AccessibleTextAttributeEvent e) {
        valueField.setText("setAttributes " + e);
        TextStyle textStyle = e.textStyle;
        if (textStyle != null) {
          /* Copy all of the TextStyle fields into the new StyleRange. */
          StyleRange style = new StyleRange(textStyle);
          /* Create new graphics resources because the old ones are only valid during the event. */
          if (textStyle.font != null) style.font = new
Font(display, textStyle.font.getFontData());
          if (textStyle.foreground != null) style.foreground = new
Color(display, textStyle.foreground.getRGB());
          if (textStyle.background != null) style.background = new
Color(display, textStyle.background.getRGB());
          if (textStyle.underlineColor != null) style.underlineColor = new
Color(display, textStyle.underlineColor.getRGB());
          if (textStyle.strikeoutColor != null) style.strikeoutColor = new
Color(display, textStyle.strikeoutColor.getRGB());
          if (textStyle.borderColor != null) style.borderColor = new
Color(display, textStyle.borderColor.getRGB());
          /* Set the StyleRange into the StyledText. */
          style.start = e.start;
          style.length = e.end - e.start;
          text.setStyleRange(style);
          e.result = ACC.OK;
        } else {
          text.setStyleRanges(e.start, e.end - e.start, null, null);
        }
      }
    });
    shell.pack();
    shell.open();
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch()) display.sleep();
    }
  }
}