Talking Java

A few years ago, I published an article on how to write code without looking.

The article went well, so well that I was invited to raise accessibility in an IntelliJ-based IDE from its knees.

With Windows, everything was ok there, but for MacOS, intervention was required, and I intervened. We drove under the cut, I’ll tell you everything there. And who doesn’t like beeches, below is a video from my report at SnowOne on this topic.


IntelliJ works for us based on Swing. That, in turn, is part of OpenJDK, so let’s get into it. But let’s go in order.

Availability

Accessibility, A11y in English environment

In UI development, it is about making software usable by as many people as possible, including those with limited abilities in some way.

For us, this means that we provide an alternative interface for people with disabilities. In our case, for the blind. This means that a blind person must receive enough information to interact with our product.

How it works?

We will consider the implementation example for MacOS. Because there is only one Accessibility API on macOS, and it will be enough for us to understand the principle.

What do we have?

  • VoiceOver accessibility client;

  • NSAccessibility protocol;

  • Graphical user interface in Swing.

When we perform some action, such as using the Tab key to move the keyboard focus to another element, an event is sent inside the JVM in which the CAccessibility object that implements the listner focus is informed that the focus has changed.

CAccessibility calls a native method that dispatches an NSAccessibilityUIFocuseChangeNotification with the native representation of the bean that fired the event.

An accessibility client sitting in the system notification center hears this notification and calls accessibilityUIFocusedElement on the attached component. The implementation of this selector asks via JNI there in Java which object is now in focus, and gets the native implementation of this new bean.

After all this, the client conveys information about this new focused component to the user. In the case of VO, using speech synthesis, it reads the values ​​returned by the native selectors accessibilityLabel, accessibilityRole, etc. They are in bulk there.

Similarly, work with other changes occurs: the selected row of the list, the table cell, the expanded/collapsed tree node. Only through another listner and other native notifications.

NSAccessibility

A long time ago, apple had a protocol based on NSObject methods.

It consisted of several methods that provided a list of available attributes, and given the attribute name, returned its value, for example:

  • NSAccessibilityTitleAttribute is the name of the object;

  • NSaccessibilityHelpAttribute – hint;

  • NSAccessibilityRoleAttribute – The role of the object.

It was replaced by a role-based protocol. Apple has provided 18 role protocols, by adopting one of them, you can implement an accessibility representation for the corresponding object.

  • NSAccessibilityElement;

  • NSAccessibilityButton;

  • NSAccessibilityCheckBox;

  • NSAccessibilityRadioButton;

  • NSAccessibilitySwitch; -NSAccessibilityStaticText;

  • NSAccessibilityNavigableStaticText;

  • NSAccessibilityImage;

  • NSAccessibilityProgressIndicator;

The most remarkable of them is NSAccessibilityElement, it will allow you to implement any element for the role of which there was no protocol.

Hierarchy

Apple has more roles than protocols, but fewer roles than Java. What to do with other roles? Ignore, that’s as easy as that.

As in the traditional UI, in Accessibility all components are arranged in a hierarchy by nesting, but for our user, information about root panels, view ports and other thousands of panels that the UI developer used to make this button exactly 39 cherries from the edge of the window, is not needed, so we simply omit these extra roles with some exceptions.

The picture shows the javax.accessibility.AccessibleContext hierarchy compared to the NSAccessibility element hierarchy. It can be seen that there are more elements in the Java hierarchy, but we omit all the panels for the native view.

What is the case when we still need panels?

When a list item, table cell or tree node is a complex renderer, for example, two labels, as in the picture below.

What’s next

We’ve upstreamed all of these changes into OpenJDK, so if you’re using Swing to build your UI, starting with OpenJDK 17.0.2, you’ll have accessibility on MacOS at the JVM level.

But this is not enough if you want to provide a quality accessibility interface. You will still need to make some changes.

Example 1

        JLabel label = new JLabel("This is the second text field:");
        JTextField secondTextField = new JTextField("some text 2");

        JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout());
        panel.add(label);
        panel.add(secondTextField);

Create a label and a text field. But if the focus is in such a field, VO will only say the text inside, and the name of the field, which is indicated in the label, will not be said.
To fix this, let’s add setLableFor().

        JLabel label = new JLabel("This is the second text field:");
        JTextField secondTextField = new JTextField("some text 2");
        label.setLabelFor(secondTextField);

        JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout());
        panel.add(label);
        panel.add(secondTextField);

Example 2

Here’s another common mistake. In the list of custom renderer:

    public static class AccessibleJListTestRenderer extends JPanel implements ListCellRenderer {
        private JLabel labelAJT = new JLabel("AJL");
        private JLabel itemName = new JLabel();

// …

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            itemName.setText(((String) value));
            return this;
        }
    }

But the elements of this list will all be spoken the same way, because if the component does not have a name, the role is usually pronounced. So the panel must be given a name.

public static class AccessibleJListTestRenderer extends JPanel implements ListCellRenderer {
        private JLabel labelAJT = new JLabel("AJL");
        private JLabel itemName = new JLabel();

// …

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            itemName.setText(((String) value));

            getAccessibleContext().setAccessibleName(labelAJT.getText() + ", " + itemName.getText());
            return this;
        }
    }

That’s all. Thank you for taking the time to read this article. I hope you find it interesting. As promised, below is a video from my report. I’m sorry, I think I was a bit tongue-tied there.

And now let’s discuss in the comments!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *