Accessibility in Qt

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 24 | Документация


by Geir Vattekar

Accessibility in computer software is the practice of making applicationsusable for people with disabilities. This can be implemented by theapplication itself — for instance, by using a high-contrast user interfacewith specially selected colors and fonts — or by providing support for externaltools, such as screen readers and Braille displays. In this article, we willimplement accessibility support for a custom widget.We will also look at support for assistive tools in Qt softwaredevelopment today.

Содержание


The generic term for the process of providing support for accessible tools isAT (Assistive Technology). This term covers both the tools (AT-clients) and theapplications (AT-servers); we will refer to these as just clients and servers.Although servers can cooperate with clients directly, a separate technologyusually participates as a bridge to exchange information between them.

Accessibility in Qt typically takes the form of support for such technologies.Further functionality can be implemented by applications; for instance, usinguniversal design, which integrates accessibility as part of the software designprocess, which is not common for software design tools.

[править] Qt Accessibility Architecture

Accessibility support in Qt consists of a generic interface, implementedfor a technology on each platform: MSAA on Windows, Mac OS X accessibilityon the Mac, and Unix/X11 AT-SPI on Linux. Qt's accessibility interfaceclosely follows the MSAA (Microsoft Active Accessibility) standard, whichmost clients support. Other technologies used by Qt provide similarfunctionality.

The structure of the user interface is represented as a tree of accessibleobjects. Qt uses a similar approach by building a tree of QObjects. The QObject tree is traversed, for instance, when painting the user interface(UI). Each node inthis accessible tree represents an element in the UI; e.g., a push button, acheck box, or a slider handle. There is no direct mapping between accessibleobjects and QObjects. A slider handle, for instance, is an accessibilityobject that does not have an equivalent QObject. When the accessible treeis built, each individual QObject is responsible for building a sub-treethat represents it in the accessibility tree.

All accessible objects are subclasses of QAccessibleInterface, which exposesinformation about objects and receives requests from accessible clients. QAccessible contains enums that define the information available from theobject, and the possibilities clients have to alter the object. These aredefined as follows:

  • Role: Describes the role the object fills in the user interface;e.g., if it is a main window, a text caret, or a cell in an item view.
  • Action: Describes the actions that clients can perform on theobjects; e.g., pushing a button.
  • Relation: Describes the relationship between objects in theobject tree. This is used by clients when navigating the tree.

When an assistive tool requests information from a Qt application, ittraverses the accessibility tree and queries the accessible objects. Inaddition to the objects' roles, actions, and relations, information is alsoavailable through text properties defined by the Text enum in QAccessible. Examples are Description and Value, which give ageneral description of the object and its value; e.g., a label text.

Static functions of QAccessible manage the communicationbetween server and clients. They send events to clients on behalfof widgets, build the accessible object tree, and connect to MSAAor one of the other supported technologies.

[править] Implementing Accessibility

Qt implements accessibility for its own widgets. Thus, application developersonly need to add support for custom widgets. As an example, we takeAnalogClock, a QWidget subclass that displays an analogclock, and we implement an accessible interface for it. Theclock can tell clients the current time and notify them when the time changes.It also supports setting the time by moving the hands of the clock.To provide accessibility for our clock widget, we need to implement anaccessible interface for it and send accessible events from the widgetwhen the time changes. We will first take a look at the implementationof the interface; here is its header file:

    class AccessibleClock : public QAccessibleWidget
 
    {
    public:
      enum ClockElements {
           ClockSelf = 0,
           HourHand,
           MinuteHand
      };
 
      AccessibleClock(QWidget *widget, Role role = Client,
                      const QString & name = QString());
 
      int childCount() const;
      QRect rect(int child) const;
      QString text(Text text, int child) const;
      Role role(int child) const;
      State state(int child) const;
      int navigate(RelationFlag flag, int entry,
                   QAccessibleInterface **target);
 
      QString actionText(int action, Text t, int child) const;
      bool doAction(int action, int child,
                    const QVariantList &params = 
                    QVariantList());
 
    private:
      AnalogClock *clock() const
        { return qobject_cast<AnalogClock *>(widget()); }
    };

We extend the QAccessibleWidget class. This class inherits from QAccessibleInterface and helps us by handling relationships and navigationto other objects in the accessible tree. It also keeps track of events, states,roles, texts, bounding rectangles, hit testing, and actions common to allwidgets. Accessible interfaces for all subclasses of QWidget should use QAccessibleWidget as their base class.

Custom interfaces must implement roles, states, and give appropriate stringsfor the Text enum. This is implemented by the text(), role(), andstate() functions. A widget can support actions by reimplementingactionText() and doAction().

Our clock consists of three accessible objects: the clock itself, as well asthe hour and minute hands; we use the enum ClockElements to identify them.The image below shows the accessible sub-tree of the clock with the relationsbetween the accessible objects.

center

[править] Navigation and Bounding Rectangles

Since the hands have no equivalent objects in the QObject tree (the entireclock is just one widget in Qt), we need to implement navigation andrelationships between them and the clock. We do this in navigate(). We alsoneed to give bounding rectangles for them by reimplementing rect().

    QRect AccessibleClock::rect(int child) const
    {
      QRect rect;
 
      switch (child) {
          case ClockSelf:
              rect = clock()->rect();
              break;
          case HourHand:
              rect = clock()->hourHandRect();
              break;
          case MinuteHand:
              rect = clock()->minuteHandRect();
          default:
              return QAccessibleWidget::rect(child);
      }
      QPoint topLeft = clock()->mapToGlobal(QPoint(0,0));
      return QRect(topLeft.x() + rect.x(),
                   topLeft.y() + rect.y(),
                   rect.width(), rect.height());
    }

Notice that the accessible interface's pixel resolution coincides with thewidget's; i.e., we can simply call the QWidget::rect() function tocalculate the rectangle for the entire clock. If a custom widget isstyle-aware — it draws its sub-elements with the current QStyle — itsaccessible interface should use the style to calculate boundingrectangles of sub-elements. Qt's interfaces use the style for this job.

The bounding rectangles of the hands are calculated by AnalogClockwhenever it draws itself. We need to map the rectangle to screencoordinates before we return it.

    int AccessibleClock::navigate(RelationFlag flag,
        int entry, QAccessibleInterface **target)
    {
      *target = 0;
 
      if (entry)
        switch (entry) {
          case Child:
            return entry <= childCount() ? entry : -1;
 
          case Left:
          case Right:
            if (entry == MinuteHand || entry == HourHand)
              return entry == MinuteHand ?
                              HourHand : MinuteHand;
            break;
 
          case Down:
            if (entry == ClockSelf)
              return MinuteHand;
            break;
 
          case Up:
            if (entry == MinuteHand || entry == HourHand)
              return ClockSelf;
          default:
            ;
        }
     return QAccessibleWidget::navigate(flag, entry, target);
    }

Clients use navigate() to discover relationships between the interface andobjects in its sub-tree. This function returns the child with the specifiedRelation to the clock widget and points target to its interface. Noticethat the accessible interfaces of objects that have no equivalent QObjectcannot be acquired because they don't have a separate interface; they are onlyavailable through their parent.

We handle the sub-tree of our clock widget, and let QAccessibleWidget handleother navigation. The relationships can be seen in the accessible tree givenabove.

For clients to navigate our tree correctly, we need to specify therelationships between the objects. The clock is a Controller for the hands,which are in turn Controlled by the clock. These kinds of relationships canbe specified by adding a controlling signal. We do this in the constructor ofAccessibleClock:

    AccessibleClock::AccessibleClock(QWidget *widget,
        Role role, const QString &amp;name)
        : QAccessibleWidget(widget, role, name)
    {
        addControllingSignal("valueChanged(int)");
    }

Note that the choice of signal is not important.

[править] Object Description and State

An accessible object describes itself through its interface. It provides varioustext strings to describe itself and give information about its contents. Ourobjects do not have any special states, so we can safely let QAccessibleWidget handle our states for us. Let's take a look at how rolesand text properties are resolved for an object.

    QAccessible::Role AccessibleClock::role(int child) const
    {
      switch (child) {
        case ClockSelf:
          return Clock;
        case HourHand:
        case MinuteHand:
          return Slider;
        default:
          ;
      }
      return QAccessibleWidget::role(child);
    }

Selecting a role for an object involves finding a value from the Role enumthat best fits the object's purpose. In the case of our clock widget, we werelucky enough to find the Clock role. The hands behave like dials, which area kind of slider, so they get the Slider role.

    QString AccessibleClock::text(Text text, int child) const
    {
      if (!widget()->isVisible())
        return QString();
 
      switch (text) {
        case Description:
        case Name:
          switch (child) {
            case ClockSelf:
              return "Analog Clock";
            case HourHand:
              return "Hour Hand";
            case MinuteHand:
              return "Minute Hand";
            default:
              ;
          }

When reimplementing text(), one examines the Text enum and decides whichvalues are relevant to the interface being implemented. For the objects of theclock, we have chosen Name, Description, and Value.

      case Value: {
        QString minutes = QString::number(clock()->
          currentTime().minute());
        QString hours = QString::number(clock()->
 
          currentTime().hour());
 
          switch (child) {
            case ClockSelf:
              return hours + " : " + minutes;
            case HourHand:
              return hours;
            case MinuteHand:
              return minutes;
            default:
              ;
          }
      }

The Value enum constant is used for storing the time. Accessible objectsuse text to store all information. For instance, QSlider stores its valueas an int, while its interface stores text in the same manner as our accessibleclock. Note that properties are read-only for most objects. To support thesetting of text properties, setText() should also be reimplemented.

[править] Setting the Time

To make it possible for a client to set the time by moving the hands, weprovide strings that describe the actions available, and perform them uponrequest. We have a fixed number of actions recognized by clients; these aredefined by the QAccessible::Action enum. It is possible to define customactions, but clients may not respect them.

    QString AccessibleClock::actionText(int action, Text text,
                                        int child) const
    {
      if (action == Increase &amp;&amp; child == MinuteHand)
        if (text == Name || text == Description)
          return "Wind the minute hand one minute forward.";
      if (action == Increase &amp;&amp; child == HourHand)
        if (text == Name || text == Description)
          return "Wind the hour hand one hour forward";
      ...
 
      return QAccessibleWidget::actionText(action, text, child);
    }

The two Actions suitable for our purpose are Increase andDecrease — to wind the hands clockwise or anticlockwise. TheactionText() function returns a string that describes what the requestedaction does for the specified child. If the child object does not support therequested action or it does not have a string for the specified Text, thefunction returns an empty string.Here, we let QAccessibleWidget try to find a string before admitting defeat.

    bool AccessibleClock::doAction(int action, int child,
                                   const QVariantList &amp;params)
    {
      if (!widget()->isEnabled())
        return false;
 
      if (action == Increase) {
        if (child == MinuteHand)
          clock()->addMinutes(1);
        else if (child == HourHand)
          clock()->addHours(1);
      }
 
      if (action == Decrease) {
        if (child == MinuteHand)
          clock()->addMinutes(-1);
        else if (child == HourHand)
          clock()->addHours(-1);
      }
 
      return QAccessibleWidget::doAction(action, child,
                                         params);
    }

In doAction(), we perform the actions we support, which are increasingor decreasing the clock hands. We let QAccessibleWidget handle actionssupported by all widgets. Note that we need to check whether the clock isenabled; we cannot rely on clients to respect this.

[править] The Passing of Time

The clock should probably inform its clients about the passing of time.Accessible objects can post events to clients through the staticupdateAccessibility() function of QAccessible. The available eventsare described by its Event enum. Since we use the object's value to storethe current time, it is only natural that we choose ValueChanged for theevent type.

    void AnalogClock::timeout()
    {
        ...
        QAccessible::updateAccessibility(this, 0,
            QAccessible::ValueChanged);
 
        update();
    }

The timeout() function is connected to a QTimer which fires everyminute. Actually posting the accessible event is straightforward. The widget isused by Qt to look up or create an interface for the widget.

center

[править] Current Standards and Technologies

Currently, the availability of accessibility standards varies greatly insoftware. For the communication between clients and servers, MSAA is thetechnology most commonly supported by clients. MSAA is infamous for itsambiguous specification, lack of support for UI elements, and generalquirkiness; we will look at some implementation issues related to thisshortly.

The lack of support for several UI elements, such as labels, results in clientsusing MSAA in an inconsistent way. The information to clients is given astext assigned to roles; e.g, text displayed and general description forseparate UI elements. Some roles conflict and that has resulted in clientsusing the roles in an inconsistent manner.

MSAA is supported by several of the best known assistive tools — forinstance the screen readers, JAWS, Window-Eyes and ZoomText, and the speechengines, Dragon and Narrator. However, because development of some tools beganbefore MSAA was available, and because of the missing features and quirks,clients often employ proprietary solutions. JAWS, for instance, assumes theapplication was developed with the Win32 API and fetches the window handle ofcommon UI elements; e.g, push buttons and scroll bars. Only if this fails, willJAWS turn to MSAA.

[править] Dealing with Quirks

The lack of standards, the shortcomings of MSAA, and hacks by server andclient vendors demand that application developers need to do some hacking andtricks themselves. Often, the applications must be tested against specificclients that should be supported. We will now look at a few examples on how todeal with common problems encountered in Qt application development.

Servers notify clients when a new object receives focus. Often, clients performa bottom-up traversal of the accessible tree and pass on to the user thedescriptive texts of objects able to accept focus. This can cause problemsbecause leaf objects in the Qt object tree that do not accept focus, such aslabels, may never be discovered by the client.

Another related problem is knowing whether labels describe other objects. Atext field, for instance, often has a label describing it. A user of anassistive client will not know what the field is for if he/she is not informedabout the label. Qt solves this in that, if a widget's buddy is a label, itincludes the text of the label in the widget's description. A similartechnique could be used for labels in leaf nodes.

With MSAA there is no direct function to know how many children a list has.This leaves it up to the individual servers to figure out how to pass thisinformation to clients. Qt appends a description of this to the Descriptiontext role.

The level of the current item in a tree cannot be queried. This is due to lazyevaluations. Qt solves this by using the Value text role to store the level ofthe current tree item.

As mentioned previously, in some cases, more that one text role can beappropriate for the same information. An example is the text of labels, forwhich Acrobat Reader uses the Value role while Qt widgets and most clients usethe Name role.

There can also be ambiguity between states and text roles. We have a statictext role, but also a read-only state, both of which can be used for disabledtext objects. In Qt, we use the accessible name for label text; forconsistency in applications, all widgets that display text should also use thethis text role.

MSAA provides support for text carets through a caret role. However, mostclients do not use this, but use proprietary solutions; e.g, checking on thescreen for a vertical blinking line.

[править] The Future

In a cross-platform framework, such as Qt, the lack of a standard interfacerespected by assistive tools on the various platforms presents severalproblems. In particular, it is difficult to find a common abstraction betweentechnologies, and at the same time provide sufficient support for clients.Currently, Qt's implementation closely follows IAccessible, which is the APIof the accessible interface of MSAA.

However, a new API, IAccessible2, has been developed to complement andfill the gaps in IAccessible — notable improvements are additionalfunctionality for rich text tables, spreadsheets, and common mainstreamapplications; e.g., Web 2.0 applications.

Support for the IAccessible2 interface is under development in Qt, but we donot currently have an estimate for when it will be in place. A full discussionof IAccessible2 is therefore best left to a future article or overview.

[править] Summary

The apparent challenge for developers implementing accessibility today isdealing with the quirks and shortcomings of available technologies, andproprietary solutions of client vendors.

We have discussed some remedies with regard to Qt application development.Still, it may be a good idea to test applications with the most common assitivetools. In cases where clients are in conflict, one will just have to choosewhich client one wishes to support. Обсудить...