3 Replies Latest reply: Mar 7, 2003 1:16 PM by 843807 RSS

    AccessibleChild text not being read out by screen reader

    843807
      Hi,

      I am attempting to make a custom JComponent implement Accessible and I am having some problems getting it to work correctly. The basic problem is that I want my screen reader to read the active child of my custom JComponent, but the screen reader only reads the AccessibleName of the custom JComponent. The code for all of this is at the bottom of this note.

      A quick overview of the way it works:
      - There is a custom Component - MyComponent which subclasses JComponent and implements Accessible
      - MyComponent instantiates 3 TextRegion objects.
      - MyComponent will display these 3 TextRegion objects on itself.
      - A TextRegion object is a subclass of Object but implements Accessible
      - A TextRegion object may not have focus. Only MyComponent may have focus
      - The AccessibleContext for the TextRegion (AccessibleTextRegion) implements AccessibleContext and AccessibleComponent
      - So there is one component in the component tree (MyComponent), but an Accessible in the Accessibility Tree (AccessibleMyComponent) with 3 children (AccessibleTextRegions) which do not correlate to any components.
      - By pressing any key on MyComponent the active child changes to the next TextRegion. At this point I want my screen reader to read out the text of the TextRegion.

      When I run my implementation on Windows, using JAWS for my screen reader, all I ever get is the AccessibleName for MyComponent being read out. JAWS never asks for the text from the active child.

      I could code getAccessibleName in MyComponent to do this explicitly, but I BELIEVE that this is not the correct way this should be coded.

      Any help, advice or explanation about how to rectify my code will be gratefully received. The reason for the way that this is coded, is that it is a prototype for an existing complex custom JComponent, which is why the TextRegions are not subclasses of JComponent.


      A couple of other questions:
      - Should AccessibleContext implement AccessibleComponent? I believe it should since the Javadoc for AccessibleComponent states:
      "The AccessibleComponent interface should be supported by any object that is rendered on the screen."

      - Should the AccessibleRole of the TextRegion be TEXT or something else? How important is the AccessibileRole anyway?

      Thanks,
      Phil


      //----------------------------------------------------------------
      // Class: MyJComponent
      import java.awt.BorderLayout;
      import java.awt.Color;
      import java.awt.Dimension;
      import java.awt.Graphics;

      import java.awt.event.KeyAdapter;
      import java.awt.event.KeyEvent;

      import javax.accessibility.Accessible;
      import javax.accessibility.AccessibleContext;

      import javax.swing.JComponent;
      import javax.swing.JFrame;
      import javax.swing.JTextField;

      //
      // A simple subclass of JComponent, which has the following features
      // - It has 3 TextRegion objects which it displays on the screen.
      // - It has a conecpt of one of the TextRegions being active.
      // - Pressing any key will shift the "active object" to the next TextRegion.
      //

      public class MyComponent extends JComponent implements Accessible {

      private TextRegion[] textRegions;
      private int activeIndex;

      // Constructor
      public MyComponent() {
           // Create the 3 TextRegions and add them to an array.
           textRegions = new TextRegion[] {
           new TextRegion(this, 20, 20, "adam", 0),
           new TextRegion(this, 20, 120, "bob", 1),
           new TextRegion(this, 20, 220, "colin", 2)
           };

           // Set the "active object" to be the first item;
           activeIndex = 0;

           // Add a key adapter to shift the "active object"
           addKeyListener(new KeyAdapter() {
           public void keyPressed(KeyEvent ke) {
                TextRegion oldRegion = textRegions[activeIndex];
                activeIndex++;
                if (activeIndex == textRegions.length) {
                activeIndex = 0;
                }
                TextRegion newRegion = textRegions[activeIndex];
           
                // Fire a propertyChange so that the correct child is
                // active
                getAccessibleContext().firePropertyChange(
                AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY,
                oldRegion,
                newRegion);

                repaint();
           }
           });
      }

      public void paintComponent(Graphics g) {
           g.setColor(Color.red);
           g.fillRect(0, 0, getWidth(), getHeight());

           for (int i=0; i < textRegions.length; i++) {
           // Ask each of the TextRegions to paint themselves
           textRegions.paint(g);

           // If the TextRegion is the active object, border it with a
           // yellow rectangle
           if (i == activeIndex) {
                g.setColor(Color.yellow);
                g.drawRect(textRegions[i].getXPos(),
                     textRegions[i].getYPos(),
                     textRegions[i].getWidth(),
                     textRegions[i].getHeight());

           }
           }
      }

      // Make my component focusable
      public boolean isFocusable() {
           return true;
      }

      // Give it a Dimension so it can be packed
      public Dimension getPreferredSize() {
           return new Dimension(500,500);
      }

      // Method to return the current active index
      public int getActiveIndex() {
           return activeIndex;
      }

      // Method to return all of the TextRegions
      public TextRegion[] getTextRegions() {
           return textRegions;
      }

      // Accessible method
      public AccessibleContext getAccessibleContext() {
           if(accessibleContext == null) {
           accessibleContext = new AccessibleMyComponent();
           }
           return accessibleContext;
      }

      // Accessible inner class
      class AccessibleMyComponent extends JComponent.AccessibleJComponent {
           public String getAccessibleName() {
           return "Accessible Name of MyComponent";
           }

           public String getAccessibleDescription() {
           return "Accessible Description of MyComponent";
           }

           public int getAccessibleChildrenCount() {
           System.out.println("MyComponent: getAccessibleChildrenCount()");
           return textRegions.length;
           }

           public Accessible getAccessibleChild(int i) {
           System.out.println("MyComponent: getAccessibleChild #"+i);
           if ((i < 0) || (i >= textRegions.length)) {
                return null;
           }
           return textRegions[i];
           }
      }

      public static void main(String args[]) {
           // Put MyComponent and a JTextField in a Frame
           JFrame jf = new JFrame();
           jf.getContentPane().add(BorderLayout.CENTER, new MyComponent());

           jf.getContentPane().add(BorderLayout.SOUTH,
                     new JTextField("Just to allow focus to shift"));

           jf.pack();
           jf.setVisible(true);
      }
      }


      //----------------------------------------------------------------
      // Class: TextRegion
      import java.awt.Color;
      import java.awt.Graphics;

      import javax.accessibility.*;

      //
      // A simple class that will displays a text string when asked.
      //
      public class TextRegion implements Accessible {

      private MyComponent parent;
      private String text;
      private AccessibleContext accessibleContext;
      private int xPos;

      private int yPos;
      private int index;

      // Constructor that takes a bunch of parameters and stores them away
      public TextRegion(MyComponent parent,
                int xPos,
                int yPos,
                String text,
                int index) {
           this.text = text;
           this.parent = parent;
           this.xPos = xPos;
           this.yPos = yPos;
           this.index = index;
      }

      // Draw the text string in black
      public void paint(Graphics g) {
           g.setColor(Color.black);
           g.drawString(text, xPos+15, yPos+15);
      }

      // Some accessor methods for the fields
      public int getXPos() {
           return xPos;
      }

      public int getYPos() {
           return yPos;
      }
      public int getWidth() {
           return 100;
      }
      public int getHeight() {
           return 30;
      }

      public String getText() {
           return text;
      }

      public int getIndex() {
           return index;
      }

      // Accessible method
      public AccessibleContext getAccessibleContext() {
           if(accessibleContext == null) {
           accessibleContext = new AccessibleTextRegion(parent, this, index);
           }
           return accessibleContext;
      }
      }



      //----------------------------------------------------------------
      // Class: AccessibleTextRegion
      import java.awt.Color;
      import java.awt.Cursor;
      import java.awt.Dimension;
      import java.awt.Font;
      import java.awt.FontMetrics;
      import java.awt.Point;
      import java.awt.Rectangle;

      import java.awt.event.FocusListener;

      import java.beans.PropertyChangeListener;
      import java.text.BreakIterator;
      import java.util.Locale;

      import javax.accessibility.*;
      import javax.swing.text.AttributeSet;

      //
      // The AccessibleContext for the TextRegion class
      //
      public class AccessibleTextRegion extends AccessibleContext
      implements AccessibleComponent,
                                    AccessibleText {

      private TextRegion textRegion;
      private MyComponent parent;
                                    
      public AccessibleTextRegion(MyComponent parent,
                          TextRegion textRegion,
                          int index) {
           this.textRegion = textRegion;
           this.parent = parent;
      }

      // AccessibleContext methods
      public void addPropertyChangeListener(PropertyChangeListener listener) {
           System.out.println("ATR: addPropertyChangeListener");
           // This is a transient child and so should not have
           // PropertyChangeListeners.
      }

      public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
           System.out.println("ATR: firePropertyChange");
           // This is a transient child and so should not have
           // PropertyChangeListeners.
      }

      public AccessibleAction getAccessibleAction() {
           System.out.println("ATR: getAccessibleAction");
           // There are no actions
           return null;
      }
           
      public Accessible getAccessibleChild(int i) {
           System.out.println("ATR: getAccessibleChild");
           // There are no children
           return null;
      }

      public int getAccessibleChildrenCount() {
           System.out.println("ATR: getAccessibleChildrenCount");
           // There are no children
           return 0;
      }

      public AccessibleComponent getAccessibleComponent() {
           System.out.println("ATR: getAccessibleComponent");
           return this;
      }

      public String getAccessibleDescription() {
           return "This is a superb description of the AccessibleTextRegion";
      }

      public AccessibleEditableText getAccessibleEditableText() {
           System.out.println("ATR: getAccessibleEditableText");
           // The text is not editable
           return null;
      }

      public AccessibleIcon[] getAccessibleIcon() {
           System.out.println("ATR: getAccessibleIcon");
           return null;     
      }

      public int getAccessibleIndexInParent() {
           System.out.println("ATR: getAccessibleIndexInParent");
           TextRegion[] ta = parent.getTextRegions();
           for (int i=0; i < ta.length; i++) {
           if (ta[i] == textRegion) {
                return i;
           }
           }
           return -1;
      }
           
      public String getAccessibleName() {
           return "AccessibleTextRegion: " + textRegion.getIndex();
      }

      public Accessible getAccessibleParent() {
           System.out.println("ATR: getAccessibleParent");
           return parent;
      }

      public AccessibleRelationSet getAccessibleRelationSet() {
           System.out.println("ATR: getAccessibleRelationSet");
           return null;
      }
                
      public AccessibleRole getAccessibleRole() {
           System.out.println("ATR: getAccessibleRole");
           // It's essentially some text.
           return AccessibleRole.TEXT;
      }

      public AccessibleSelection getAccessibleSelection() {
           System.out.println("ATR: getAccessibleSelection");
           // Selection is not supported
           return null;
      }

      public AccessibleStateSet getAccessibleStateSet() {
           System.out.println("ATR: getAccessibleStateSet");
           AccessibleStateSet ass = new AccessibleStateSet();

           if (parent.getActiveIndex() == textRegion.getIndex()) {
           ass.add(AccessibleState.ACTIVE);
           }

           ass.add(AccessibleState.ENABLED);
           ass.add(AccessibleState.SHOWING);
           ass.add(AccessibleState.TRANSIENT);
           ass.add(AccessibleState.VISIBLE);

           // Is this really necessary?
           ass.add(AccessibleState.SINGLE_LINE);

           return ass;
      }

      public AccessibleTable getAccessibleTable() {
           System.out.println("ATR: getAccessibleTable");
           return null;
      }

      public AccessibleText getAccessibleText() {
           System.out.println("ATR: getAccessibleText");
           return this;
      }

      public AccessibleValue getAccessibleValue() {
           System.out.println("ATR: getAccessibleValue");
           return null;
      }

      public Locale getLocale() {
           System.out.println("ATR: getLocale");
           return parent.getAccessibleContext().getLocale();
      }

      public void removePropertyChangeListener(PropertyChangeListener pcl) {
           System.out.println("ATR: removePropertyChangeListener");
           // This is a transient child and so should not have
           // PropertyChangeListeners.
      }

      public void setAccessibleDescription(String s) {
           System.out.println("ATR: setAccessibleDescription");
           super.setAccessibleDescription(s);
      }
      public void setAccessibleName(String s) {
           System.out.println("ATR: setAccessibleName");
           super.setAccessibleName(s);
      }
      public void setAccessibleParent(Accessible a) {
           System.out.println("ATR: setAccessibleParent");
           super.setAccessibleParent(a);
      }


      // AccesibleText Methods
      public String getAfterIndex(int part, int index) {
           System.out.println("ATR: getAfterIndex "+part+" : "+index);
           return getAtIndex(part, index, 1);
      }

      public String getAtIndex(int part, int index) {
           System.out.println("ATR: getAtIndex "+part+" : "+index);
           return getAtIndex(part, index, 0);
      }

      public String getBeforeIndex(int part, int index) {
           System.out.println("ATR: getBeforeIndex " + part + " : " + index);
           return getAtIndex(part, index, -1);
      }

      /**
      * Gets the word, sentence, or character at <code>index</code>.
      * If <code>direction</code> is non-null this will find the
      * next/previous word/sentence/character.
      */
      private String getAtIndex(int part, int index, int direction) {
           String s = textRegion.getText();

           if ((index < 0) || (index >= s.length())) {
           return null;
           }

           switch(part) {
           case AccessibleText.CHARACTER:
                if ((index + direction < s.length()) &&
                (index + direction >= 0)) {
                return s.substring(index+direction, index+direction+1);
                }
                break;

           case AccessibleText.WORD:
                BreakIterator breakiterator = BreakIterator.getWordInstance();
                breakiterator.setText(s);
                int startIndex;
                int endIndex;
                if (direction != -1) {
                startIndex = breakiterator.previous();
                endIndex = breakiterator.following(index);
                } else {
                startIndex = breakiterator.following(index);
                endIndex = breakiterator.previous();
                }
                if (startIndex == -1) {
                startIndex = 0;
                }
                System.out.println("StartIndex = "+startIndex+ " endIndex = "+endIndex);
                return s.substring(startIndex, endIndex);

           case AccessibleText.SENTENCE:
                BreakIterator breakiterator1 = BreakIterator.getSentenceInstance();
                breakiterator1.setText(s);
                int startIndex1;
                int endIndex1;
                if (direction != -1) {
                startIndex1 = breakiterator1.previous();
                endIndex1 = breakiterator1.following(index);
                } else {
                startIndex1 = breakiterator1.following(index);
                endIndex1 = breakiterator1.previous();
                }
                return s.substring(startIndex1, endIndex1);

           default:
                break;
           }
           return null;
      }

      public int getCaretPosition() {
           System.out.println("ATR: getCaretPosition ");
           return 0;
      }

      public AttributeSet getCharacterAttribute(int i) {
           System.out.println("ATR: getCharacterAttribute "+i);
           return null;
      }

      public Rectangle getCharacterBounds(int i) {
           System.out.println("ATR: getCharacterBounds "+i);
           // Just return a valid rectangle
           return new Rectangle(0,0,10,10);
      }

      public int getCharCount() {
           System.out.println("ATR: getCharCount");
           return textRegion.getText().length();
      }

      public int getIndexAtPoint(Point point) {
           System.out.println("ATR: getIndexAtpoint "+point);
           return -1;
      }

      public String getSelectedText() {
           System.out.println("ATR: getSelectionText");
           // Selection is not supported. Return nothing.
           return null;
      }

      public int getSelectionEnd() {
           System.out.println("ATR: getSelectionEnd");
           // Selection is not supported. Return -1.
           return -1;
      }

      public int getSelectionStart() {
           System.out.println("ATR: getSelectionStart");
           // Selection is not supported. Return -1.
           return -1;
      }

      // AccessibleComponent Methods
      public void addFocusListener(FocusListener l) {
           System.out.println("ATR: addFocusListener");
      }

      public boolean contains(Point p) {
           System.out.println("ATR: contains");
           return false;
      }

      public Accessible getAccessibleAt(Point p) {
           System.out.println("ATR: getAccessibleAt");
           return null;
      }

      public Color getBackground() {
           System.out.println("ATR: getBackground");
           // Delegate to parent
           return parent.getBackground();
      }

      public Rectangle getBounds(){
           System.out.println("ATR: getBounds");
           return new Rectangle(textRegion.getXPos(), textRegion.getYPos(),
                     textRegion.getWidth(), textRegion.getHeight() );
      }

      public Cursor getCursor(){
           System.out.println("ATR: getCursor");
           // There is no cursor
           return null;
      }

      public Font getFont(){
           System.out.println("ATR: getFont");
           // Delegate to parent
           return parent.getFont();
      }

      public FontMetrics getFontMetrics(Font f){
           System.out.println("ATR: getFontMetrics");
           // Delegate to parent
           return parent.getFontMetrics(f);
      }

      public Color getForeground(){
           System.out.println("ATR: getForeground");
           // Delegate to parent
           return parent.getForeground();
      }

      public Point getLocation(){
           System.out.println("ATR: getLocation");
           return null;
      }

      public Point getLocationOnScreen(){
           System.out.println("ATR: getLocationOnScreen");
           return null;
      }

      public Dimension getSize(){
           System.out.println("ATR: getSize");
           return null;
      }

      public boolean isEnabled(){
           System.out.println("ATR: isEnabled");
           // It is enabled
           return true;
      }

      public boolean isFocusTraversable(){
           System.out.println("ATR: isFocusTraversable");
           // It is not focus traversable
           return false;
      }

      public boolean isShowing(){
           System.out.println("ATR: isShowing");
           // It is showing
           return true;
      }

      public boolean isVisible(){
           System.out.println("ATR: isVisible");
           // It is visible
           return true;
      }

      public void removeFocusListener(FocusListener l){
           System.out.println("ATR: removeFocusListener");
      }
      public void requestFocus(){
           System.out.println("ATR: requestFocus");
      }
      public void setBackground(Color c){
           System.out.println("ATR: setBackground");
      }
      public void setBounds(Rectangle r){
           System.out.println("ATR: setBounds");
      }
      public void setCursor(Cursor cursor){
           System.out.println("ATR: setCursor");
      }
      public void setEnabled(boolean b){
           System.out.println("ATR: setEnabled");
      }
      public void setFont(Font f){
           System.out.println("ATR: setFont");
      }
      public void setForeground(Color c){
           System.out.println("ATR: setForeground");
      }
      public void setLocation(Point p){
           System.out.println("ATR: setLocation");
      }
      public void setSize(Dimension d){
           System.out.println("ATR: setSize");
      }
      public void setVisible(boolean b) {
           System.out.println("ATR: setVisible");
      }
      }