L'accessibilité dans Qt

| Geir Vattekar | traducteur : Thibaut Cuvelier | Qt Quarterly |

L'accessibilité dans les applications, c'est rendre ces applications utilisables par des personnes handicapées. Ceci peut être implémenté dans l'application elle-même (par exemple, en utilisant une interface à haut contraste avec des couleurs spécialement choisies, ainsi que des polices adaptées, ou bien en fournissant un support pour des outils externes (des lecteurs d'écran ou des affichages en Braille).

Dans cet article, nous allons implémenter un support pour l'accessibilité pour un widget personnalisé. Nous allons aussi jeter un oeil au support pour les outils externes dans les applications Qt d'aujourd'hui.

Le terme général pour la fourniture d'un support pour des outils d'accessibilité est AT, pour Assistive Technology (technologie assistive). Il couvre, et les outils (les clients), et les applications (les serveurs). Bien que les serveurs puissent coopérer avec les clients directement, habituellement, une technologie séparée les relie pour échanger des infos.

L'accessibilité, dans Qt, se montre principalement dans le support de telles technologies. D'autres fonctionnalités peuvent être implémentées par les applications. Par exemple, l'utilisation d'un design universel, qui intègre l'accessibilité dans le processus de design logiciel, ce qui n'est pas fréquent pour des outils de design logiciel.

Cet article est une traduction autorisée de Accessibility in Qt, par Geir Vattekar.

Commentez

- Titre : L'accessibilité dans Qt

- Auteurs : Geir Vattekar, traducteur : Thibaut Cuvelier, Qt Quarterly

- Parution : 22 mars 2009

- Mise à jour : 1 août 2011

- Licence : Copyright ® 2007 Geir Vattekar. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. L'organisation de Qt

- III. Implémenter l'accessibilité

- IV. Navigation et rectangles-frontières

- V. Description de l'objet et état

- VI. Mettre la pendule à l'heure

- VII. Le temps passe

- VIII. Standards actuels et technologies

- IX. Le cas de la forte malpropreté

- X. Le futur

- XI. En résumé

- XII. Divers

I - L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Accessibility in Qt de Geir Vattekar paru dans la Qt Quarterly Issue 24.

Cet article est une traduction d'un des tutoriels écrits par Nokia Corporation and/or its subsidiary(-ies) inclus dans la documentation de Qt, en anglais. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II - L'organisation de Qt

L'accessibilité dans Qt consiste en une interface générique, implémentée pour une technologie sur chaque plateforme : MSAA sur Windows, Mac OS X Accessibility sur Mac et UNIX/X11 AT-SPI sur Linux. Cette interface suit de près le standard MSAA, que la grande majorité des clients supporte. Les autres technologies utilisées par Qt fournissent des fonctionnalités similaires.

La structure de l'interface utilisateur est représentée comme un arbre d'objets accessibles. Qt utilise une approche similaire en construisant un arbre de QObjects. Cet arbre est traversé, par exemple, lors du dessin de l'interface utilisateur. Chaque noeud représente un élément de l'interface : par exemple, un bouton. Il n'y a pas de lien direct entre les objets accessibles et les QObjects : certains objets de l'interface n'ont pas de QObject équivalent. Lors de la construction de l'arbre, chaque QObject est responsable de la construction d'un sous-arbre qu'il représente dans l'arbre.

Tous les objets accessibles sont des dérivés de QAccessibleInterface, qui expose les objets et reçoit les requêtes des clients. QAccessible contient des énumérations qui définissent les informations disponibles de l'objet et les altérations qu'il peut subir. Celles-ci sont définies comme suit.

- Un rôle : description du rôle que l'objet remplit dans l'interface (fenêtre principale, texte, cellule d'une vue...)

- Une action : description des actions que le client peut effectuer sur l'objet (appuyer sur un bouton, cocher une case, remplir un champ...).

- Une relation : description de la relation entre objets dans l'arbre. Utilisé lors du parcours de l'arbre.

Quand un client demande une information d'une application Qt, il traverse l'arbre et questionne les objets. En plus des rôles, actions et relations de l'objet, les informations sont disponibles via des propriétés textuelles définies par l'énumération Text dans QAccessible. Des exemples ? Description et Value, qui donnent des descriptions générales de l'objet et de sa valeur.

Les fonctions statiques de QAccessible s'occupent de la communication entre les serveurs et les clients. Elles envoient aux clients les événements sur demande des widgets, construisent l'arbre et se connectent aux clients (MSAA ou autres).

III - Implémenter l'accessibilité

Qt implémente l'accessibilité pour ses propres widgets. Ainsi, les développeurs ne doivent l'implémenter que pour leurs widgets personnalisés. Comme exemple, nous pouvons prendre AnalogClock, un dérivé de QWidget, qui affiche une horloge analogique : nous allons en implémenter une interface accessible. L'horloge peut donner l'heure et dire quand l'heure change. Elle supporte aussi que l'on change l'heure en bougeant ses aiguilles. Pour lui fournir l'accessibilité, nous devons implémenter une interface accessible et envoyer des événements accessibles quand l'heure change. Nous allons commencer par regarder l'interface, dont voici l'entête.

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());
}

};

Nous étendons la classe QAccessibleWidget. Cette classe hérite de QAccessibleInterface et nous aide dans notre noble tâche dans les relations et la navigation vers d'autres objets de l'arbre. Elle garde aussi une trace des événements, des états, des rôles, des actions communes à tous les widgets. Toutes les classes d'accessibilité pour widgets devraient utiliser cette classe comme classe de base.

Une interface comme celle-ci doit implémenter les rôles, les états et donner des chaînes de caractère appropriées pour l'énumération Text. Ceci est fait dans les fonctions role(), state() et text(). Un widget peut supporter des actions en réimplémentant actionText() et doAction().

Notre horloge se décompose en trois objets accessibles : l'horloge elle-même, les aiguilles des heures et des minutes. Nous utilisons une énumération, ClockElements, pour les identifier. Cette illustration montre le sous-arbre accessible de notre horloge ainsi que les relations entre les différents objets accessibles.

IV - Navigation et rectangles-frontières

Vu que les aiguilles n'ont pas d'équivalent QObject (l'horloge entière est un seul et même objet), nous devons implémenter la navigation et les relations entre elles et l'horloge, ce que nous faisons dans navigate(). Nous devons aussi donner les rectangles-frontières qui délimitent les objets, grâce à 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());
}

Remarquez que la résolution de l'interface coïncide avec celle du widget ; nous pouvons donc appeler simplement QWidget::rect pour calculer le rectangle de l'horloge entière. Si un widget est stylisable (il utilise QStyle pour se dessiner), il devrait utiliser ce style pour calculer ses rectangles, à la manière de Qt.

Les rectangles des aiguilles sont calculés par AnalogClock quand il se dessine. Nous devons reporter les rectangles dans le repère de l'écran avant de les retourner.

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);
}

Les clients utilisent navigate() pour découvrir les relations entre l'interface et ses composantes. Cette fonction retourne l'enfant avec la Relation spécifiée au widget et pointe target sur son interface. Remarquez que les interfaces accessibles des objets sans équivalent QObject ne peuvent pas être demandées, parce qu'ils n'ont pas d'interface séparée : ils sont accessibles uniquement via leurs parents.

Nous nous occupons du sous-arbre de notre widget, et laissons QAccessibleWidget s'occuper du reste. La relation peut être vue dans l'illustration ci-dessus.

Pour que les clients puissent traverser notre arbre sans problème, nous devons spécifier les relations entre objets. L'horloge est un Controller pour les aiguilles, qui sont Controlled par l'horloge. Ce genre de relations peut être défini par un signal de contrôle. Nous pouvons le faire dans AccessibleClock. Notez que le choix du signal importe peu.

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

V - Description de l'objet et état

Un objet accessible se décrit dans son interface. Il fournit divers textes pour se décrire et donne des informations sur son contenu. Nos objets n'ont pas d'états particuliers, nous pouvons donc laisser QAccessibleWidget s'en occuper pour nous. Regardons comment les rôles et les propriétés sont résolues dans un objet.

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

Sélectionner un rôle pour un objet implique de trouver une valeur de l'énumération Rolequi correspond au mieux au but de l'objet. Dans le cas de notre horloge, nous sommes assez chanceux pour trouver le rôle horloge.

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:
;
}

Lors de la réimplémentation de text(), on examine l'énumération Text et décide quelles valeurs conviennent à l'interface implémentée. Pour les objets de l'horloge, nous avons choisi nom, description et valeur.

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:
;
}
}

L'énumération Value est utilisée pour stocker l'heure. Les objets accessibles utilisent du texte pour stocker toutes leurs informations. Notez que les propriétés sont en lecture-seule pour la majorité des objets. Pour supporter leur écriture, setText() devra aussi être réimplémentée.

VI - Mettre la pendule à l'heure

Pour que les clients puissent définir l'heure, en bougeant les aiguilles, nous fournissons des chaînes de caractères qui décrivent les actions, et les effectuons sur demande. Nous avons un certain nombre d'actions reconnues par le client, définies dans l'énumération QAccessible::Action. Il est possible de définir des actions personnalisées, mais la majorité des clients de les respectent pas.

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

return QAccessibleWidget::actionText(action, text, child);
}

Les deux actions possibles pour nous sont l'incrémentation et la décrémentation de l'heure ou des minutes. La fonction actionText() retourne une chaîne qui décrit ce que l'action fait sur l'enfant spécifié. Si l'enfant ne supporte pas l'action ou n'a pas de chaîne pour l'action, la fonction retourne une chaîne vide. Ici, nous laissons QAccessibleWidget essayer de trouver une chaîne avant de battre en retraite.

bool AccessibleClock::doAction(int action, int child, const QVariantList &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);
}

Dans doAction(), nous effectuons les actions que nous supportons. Nous laissons QAccessibleWidget s'occuper des actions supportées par tous les widgets. Notez que nous avons besoin de vérifier si l'horloge est activée : nous ne pouvons pas compter sur les clients pour supporter ceci.

VII - Le temps passe

L'horloge devrait probablement informer ses clients que le temps passe. Les objets peuvent poster des événements aux clients grâce à la fonction statique updateAccessibility() de QAccessible. Les événements disponibles sont décrits par son énumération Event. Vu que nous utilisons les valeurs de l'objet pour stocker l'heure actuelle, il est seulement naturel que nous choisissions ValueChanged pour le type d'événements.

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

update();
}

La fonction timeout() est connectée à un QTimer qui réagit à chaque minute. En fait, poster l'événement est simple et rapide. Le widget est utilisé par Qt pour regarder ou créer une interface pour le widget.

VIII - Standards actuels et technologies

Actuellement, la disponibilité des standards d'accessibilité varie fortement entre les différents logiciels. Pour la communication entre clients et serveurs, MSAA est la technologie la plus couramment supportées par les clients. Cependant, MSAA est infâme pour sa spécification ambigüe, son manque de support pour des éléments de l'interface, et son manque de propreté général.

Le manque de support pour plusieurs éléments de l'interface, comme les labels, ont pour résultat, dans les clients utilisant MSAA, des résultats variables. L'information pour les clients est donnée comme un texte assigné aux rôles. Par exemple, du texte affiché et des descriptions générales pour les éléments séparés de l'interface. Quelques rôles entrent en conflit, ce qui a induit les clients à utiliser les rôles d'une manière inconsistante.

MSAA est supportée par beaucoup des outils les plus connus, comme, par exemple, parmi les lecteurs d'écran, JAWS, Windows-Eyes et ZoomText, et, parmi les moteurs vocaux, Dragon et Narrator. Cependant, parce que le développement de certains de ces outils a commencé avant la disponibilité de MSAA, et à cause de fonctionnalités manquantes et de malpropreté, les clients emploient souvent des solutions propriétaires. JAWS, par exemple, décide que l'application a été développée avec l'API Win32. S'il remarque qu'il s'est fortement trompé, il utilise MSAA.

IX - Le cas de la forte malpropreté

Le manque de standards et la jeunesse de MSAA obligent les développeurs à détourner ces pièges et à tricher eux-mêmes. Souvent, les applications doivent être testées avec des clients spécifiques qui devraient être supportés.

Les serveurs notifient les clients quand un nouvel objet reçoit le focus. Souvent, les clients effectuent une traversée de haut en bas de l'arbre d'accessibilité et donnent à l'utilisateur les descriptions des objets qui peuvent recevoir le focus. Ceci peut poser des problèmes, car certains objets, qui n'acceptent pas le focus (comme les labels), risquent de ne jamais être découverts par le client.

Un autre problème lié est de savoir si les labels décrivent d'autres objets. Un champ de texte, par exemple, a souvent un label qui le décrit. Un utilisateur ne va pas savoir comment remplir le champ s'il n'entend pas parler du label. Qt résout ceci de cette manière : si un widget est un label, il inclut le texte du label dans la description du label.

Avec MSAA, il n'y a pas de fonction directe pour savoir combien d'enfant a une liste. Il reste aux serveurs de deviner comment passer cette information aux clients. Qt ajoute une description de ceci à la définition du rôle.

Le niveau de l'item actuel ne peut être demandé. Ceci est dû à des évaluations rapides. Qt résout ceci en utilisant le texte de la valeur du rôle pour stocker le niveau de l'item actuel.

Comme mentionné précédemment, dans quelques cas, plus d'une description peut être appropriée pour la même information. Un exemple est le texte de labels, pour lequel Acrobat Reader utilise le rôle Valeur, tandis que les widgets Qt et la majorité des clients utilisent le rôle Nom.

Il peut aussi y avoir une ambigüité entre les états et les rôles. Nous avons un rôle statique, mais aussi un état en lecture seule, et les deux peuvent être utilisés pour des textes désactivés. Dans Qt, nous utilisons le nom accessible pour un label ; pour garder une certaine cohérence dans les applications, tous les widgets qui affichent du texte devraient aussi utilise ce rôle.

X - Le futur

Dans un framework multiplateformes comme Qt, le manque d'une interface standard respectée par les clients sur les diverses plateformes présente quelques problèmes. En particulier, il est difficile de trouver une abstraction commune entre les technologies, et, en même temps, de fournir un support suffisant pour les clients. Actuellement, l'implémentation de Qt suit de très près IAccessible, l'API de l'interface accessible de MSAA.

Cependant, une nouvelle API, IAccessible2, a été développée pour compléter et remplir les béants trous géants d'IAccessible - parmi les plus notables changements, citons les tableaux pour du texte enrichi et les tableurs.

Le support pour l'interface IAccessible2 est en développement, mais nous n'avons pas de date précise pour sa sortie.

XI - En résumé

Le challenge apparent pour les développeurs qui essayent d'implémenter l'accessibilité aujourd'hui est de jouer avec la malpropreté et la jeunesse des technologies disponibles, sans oublier les solutions propriétaires.

Nous avons un peu discuté des remèdes en rapport avec le développement d'applications Qt. Il est toujours, cependant, une très bonne idée de tester les applications avec les clients les plus répandus. En cas de conflit, il suffit de choisir lequel on désire supporter.

XII - Divers

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisé la traduction de cet article !

J'aimerais aussi adresser un tout grand merci à ram-0000 pour sa rapide relecture !