Оглавление
Специальные возможностиВведениеСпециальные возможности в компьютерном программном обеспечении позволяют создавать удобные в использовании программы для людей с ограниченными способностями. Это может быть достигнуто путем использования горячих клавиш, высококонтрастного пользовательского интерфейса с использованием специально отобранных цветов и шрифтов или поддержкой вспомогательных инструментов, таких как экранные дикторы или дисплеи для слепых. Приложение обычно связывается со вспомогательными инструментами не напрямую, а через вспомогательную технологию, которая является мостом для обмена информацией между приложениями и инструментами. Информация об элементах пользовательского интерфейса, таких как кнопки и полосы прокрутки, предоставляется вспомогательными технологиями. Qt поддерживает Microsoft Active Accessibility (MSAA) в Windows, Mac OS X Accessibility в Mac OS X и AT-SPI в Unix/X11. В Unix/X11 поддержка предварительная. Индивидуальные технологии абстрагированы от Qt и существует только один интерфейс для рассмотрения. Мы будем использовать MSAA везде в этом документе, когда будет необходимость обратиться к проблемам, связанным с технологиями. В этом обзорном документе мы рассмотрим архитектуру специальных возможностей Qt в общем, а также реализацию специальных возможностей для пользовательских виджетов и элементов. АрхитектураОбеспечение специальных возможностей - это взаимодействие между совместимой программой специальных возможностей, вспомогательной технологией и вспомогательными инструментами. Совместимая программа специальных возможностей называется ВТ-сервером, а вспомогательные инструменты - ВТ-клиентами. Приложения Qt обычно являются ВТ-серверами, но специализированные программы могут также функционировать в роли ВТ-клиентов. Приложения Qt обычно являются ВТ-серверами, но специализированные программы могут также функционировать в роли ВТ-клиентов. В остальной части этого документа мы будем говорить о клиентах и серверах, когда речь будет идти о ВТ-клиентах и ВТ-серверах. Специальные возможности в QtЭти классы обеспечивают поддержку приложений со специальными возможностями.
Когда мы устанавливаем связь со вспомогательными технологиями, нам необходимо описать пользовательский интерфейс Qt таким образом, чтобы он был понятен. Приложения Qt используют QAccessibleInterface для предоставления информации об индивидуальных элементах пользовательского интерфейса. В настоящее время Qt предоставляет поддержку для виджетов и частей виджетов, например, ручек ползунка, но интерфейс может быть при необходимости реализован для любого QObject. QAccessible содержит перечисления, которые описывают пользовательский интерфейс. Описание базируется главным образом на MSAA и не зависит от Qt. Мы будем рассматривать перечисления по ходу этого документа. Структура пользовательского интерфейса представлена в виде дерева подклассов QAccessibleInterface. Можно считать, что это представление пользовательского интерфейса похоже на дерево QObject, построенное Qt. Объекты могут быть виджетами или частями виджетов (такими как ручка линейки прокрутки). Мы рассмотрим дерево подробнее в следующем разделе. Серверы уведомляют клиентов об изменениях в объектах через updateAccessibility(), отправляя события, а клиенты регистрируются для получения этих событий. Доступные события определены в перечислении QAccessible::Event. Затем клиенты могут запросить объект, который сгенерировал событие, через QAccessible::queryAccessibleInterface(). Три перечисления в QAccessible помогают клиентам запрашивать и изменять объекты специальных возможностей:
Клиенты также имеют некоторые возможности для получения содержимого объектов, например, текста кнопки; объект обеспечивает строки, определенные в перечислении QAccessible::Text, которые предоставляют информацию о содержимом. Объекты могут находиться в нескольких различных состояниях, которые определены в перечислении State. Примеры состояний: объект выключен, имеет фокус или обеспечивает всплывающее меню. Дерево объектов специальных возможностейКак уже упоминалось, древовидная структура строится на основе объектов специальных возможностей приложения. Перемещаясь по дереву, клиенты могут получить доступ ко всем элементам интерфейса пользователя. Связи между объектами дают клиенту информацию об интерфейсе пользователя. Например, ручка ползунка - это дочерний элемент ползунка, которому он принадлежит. QAccessible::Relation описывает различные связи, которые клиент может запросить у объекта. Обратите внимание на то, что нет никакого прямого соответствия между деревом QObject Qt и деревом объектов специальных возможностей. Например, ручки линейки прокрутки - объекты специальных возможностей, но не виджеты или объекты в Qt. ВТ-клиенты имеют доступ к дереву объектов специальных возможностей через корневой объект в дереве, которым является QApplication. Они могут запросить другие объекты через QAccessible::navigate(), которая выбирает объекты на основе отношений. Дочерние элементы любого узла нумеруются начиная с 1. Дочерним элементом с номером 0 является сам объект. Дочерние элементы всех интерфейсов нумеруются таким же образом, т.е. нумерация не является фиксированной от корневого узла во всем дереве. Qt предоставляет интерфейсы специальных возможностей для своих виджетов. Интерфейсы для любого подкласса QObject могут быть запрошены через QAccessible::queryInterface(). Если более специализированный интерфейс не определен, то предоставляется реализация по умолчанию. ВТ-клиент не может получить интерфейс объектов специальных возможностей, которые не имеют эквивалента QObject, например, ручки линейки прокрутки, но они выглядят нормальными объектами при доступе через интерфейс родительских объектов специальных возможностей, например, вы можете запросить их отношения с помощью QAccessible::relationTo(). Для иллюстрации приведём изображение с деревом объектов специальных возможностей. Ниже дерева приведена таблица с примерами отношений объектов. Метки сверху вниз: имя класса QAccessibleInterface, виджет, для которого предоставлен интерфейс, и роль объекта. Position, PageLeft и PageRight соответствуют ручке ползунка, зоне прокрутки на страницу влево и зоне прокрутки на страницу вправо. Эти объекты специальных возможностей не имеют эквивалентна QObject.
Статические функции QAccessibleСпециальными возможностями управляют статические функции QAccessible, которые мы коротко рассмотрим. Они производят интерфейсы QAccessible, создают дерево объектов и инициируют соединение с MSAA или другой платформой технологий. Если вы заинтересованы только в том, чтобы создать ваше приложение специальных возможностей, вы можете спокойно пропустить эту часть до раздела Реализация специальных возможностей. Связь между клиентом и сервером устанавливается, когда вызывается setRootObject(). Это происходит при создании экземпляра класса QApplication, так что вы не должны делать это самостоятельно. Когда QObject вызывает updateAccessibility(), клиенты, которые слушают события, уведомляются об изменениях. Эта функция используется для отправки событий вспомогательной технологии, и события специальных возможностей отправляются по updateAccessibility(). queryAccessibleInterface() возвращает интерфейсы специальных возможностей для QObject. Все виджеты в Qt предоставляют интерфейсы; если же вам необходимы интерфейсы для управления поведением других подклассов QObject, вы должны реализовать интерфейсы самостоятельно, хотя класс QAccessibleObject реализует для вас часть функциональности. Фабрика, которая производит интерфейсы специальных возможностей для QObject, является функцией типа QAccessible::InterfaceFactory. Можно иметь несколько установленных фабрик. Последняя установленная фабрика будет первой, у которой будут запрошены интерфейсы. queryAccessibleInterface() использует фабрики для создания интерфейсов для QObject. Как правило, вы не должны беспокоиться о фабриках, поскольку вы можете реализовать подключаемые модули, которые производят интерфейсы. Мы дадим примеры обоих подходов позже. Включение поддержки специальных возможностейПо умолчанию, на Windows и Mac OS X приложения Qt запускаются с включенной поддержкой специальных возможностей. На платформах Unix/X11, приложения должны быть запущенны в окружении с переменной QT_ACCESSIBILITY с установленной в 1. Например, это устанавливается следующим образом в командном интерпретаторе bash: export QT_ACCESSIBILITY=1 Средства специальных возможностей собираются в Qt по умолчанию при конфигурировании и сборке библиотек. Реализация специальных возможностейДля обеспечения поддержки специальных возможностей в виджетах и других элементах интерфейса пользователя, вам необходимо реализовать интерфейс QAccessibleInterface и разместить его в QAccessiblePlugin. Также можно скомпилировать интерфейс в приложении и обеспечить для него QAccessible::InterfaceFactory. Фабрика может быть использована, если вы используете статическую линковку и не хотите дополнительно усложнять подключаемый модуль. Это может быть преимуществом, если вы, например, поставляете стороннюю библиотеку. Все виджеты и другие элементы интерфейса пользователя должны иметь интерфейсы и подключаемые модули. Если вы хотите, чтобы ваше приложение поддерживало специальные возможности, вам необходимо учитывать следующее:
В целом, желательно чтобы вы были хоть немного знакомы с MSAA, для которого первоначально была сделана поддержка средств специальных возможностей Qt. Вам необходимо также изучить значения перечислений QAccessible, описывающих роли, действия, отношения и события, которые вам нужно принимать во внимание. Обратите внимание на то, что вы можете исследовать, как виджеты Qt реализуют свои специальные возможности. Одной из основных проблем со стандартом MSAA является то, что интерфейсы часто реализуются противоречивыми способами. Это осложняет жизнь клиентам и часто заставляет строить предположения о функциональности объектов. Можно создать интерфейсы, унаследовав QAccessibleInterface и реализовав его чисто виртуальные функции. На практике, однако, предпочтительнее наследовать от QAccessibleObject или QAccessibleWidget, которые уже реализуют часть функциональности для вас. В следующем разделе мы увидим пример реализации специальных возможностей для виджета путем наследования класса QAccessibleWidget. Вспомогательные классы QAccessibleObject и QAccessibleWidgetПри реализации специальных возможностей для виджетов, как правило, наследуют QAccessibleWidget, который является вспомогательным классом для виджетов. Другим доступным вспомогательным классом, от которого наследуется QAccessibleWidget, является QAccessibleObject, который реализует часть интерфейса для QObjects. QAccessibleWidget предоставляет следующую функциональность:
Пример QAccessibleWidgetВместо того чтобы создавать пользовательский виджет и реализовывать интерфейс для него, мы покажем, как специальные возможности реализованы в одном из стандартных виджетов Qt: QSlider. Интерфейс специальных возможностей, QAccessibleSlider, унаследован от QAccessibleAbstractSlider, который, в свою очередь, унаследован от QAccessibleWidget. Вам не нужно изучать класс QAccessibleAbstractSlider чтобы читать этот раздел. Если вы хотите изучить, то код всех интерфейсов специальных возможностей Qt находится в каталоге in src/plugins/accessible/widgets. Вот конструктор QAccessibleSlider'а: QAccessibleSlider::QAccessibleSlider(QWidget *w) : QAccessibleAbstractSlider(w) { Q_ASSERT(slider()); addControllingSignal(QLatin1String("valueChanged(int)")); } Ползунок - сложный элемент управления, который функционирует как контроллер для дочерних элементов специальных возможностей. Эти отношения должны быть известны через интерфейс (relationTo() и navigate()). Это можно сделать с помощью управляющего сигнала, который является механизмом, предоставляемым QAccessibleWidget. Делаем это в конструкторе: Выбор показанного сигнала не важен, те же принципы применяются ко всем сигналам, объявленным таким же образом. Обратите внимание на то, что мы используем QLatin1String, чтобы гарантировать, что имя сигнала указано корректно. Когда объект специальных возможностей изменяется таким образом, что пользователи должны об этом узнать, он уведомляет клиентов об изменении, отправляя им событие через интерфейс специальных возможностей. Так же как QSlider вызвал updateAccessibility(), чтобы показать, что его значение изменилось: void QAbstractSlider::setValue(int value) ... QAccessible::updateAccessibility(this, 0, QAccessible::ValueChanged); ... } Обратите внимание на то, что вызов осуществляется после изменения значения ползунка, поскольку клиенты могут запросить новое значение сразу же после получения события. Интерфейс должен быть в состоянии вычислить ограничительные прямоугольники себя и любых дочерних элементов, которые не предоставляют собственный интерфейс. QAccessibleSlider имеет три таких дочерних элемента, определенных в закрытом перечислении SliderElements, у которого есть следующие значения: PageLeft (прямоугольник с левой стороны от ручки ползунка), PageRight (прямоугольник с правой стороны от ручки ползунка) и Position (ручка ползунка). Вот реализация функции rect(): QRect QAccessibleSlider::rect(int child) const { ... switch (child) { case PageLeft: if (slider()->orientation() == Qt::Vertical) rect = QRect(0, 0, slider()->width(), srect.y()); else rect = QRect(0, 0, srect.x(), slider()->height()); break; case Position: rect = srect; break; case PageRight: if (slider()->orientation() == Qt::Vertical) rect = QRect(0, srect.y() + srect.height(), slider()->width(), slider()->height()- srect.y() - srect.height()); else rect = QRect(srect.x() + srect.width(), 0, slider()->width() - srect.x() - srect.width(), slider()->height()); break; default: return QAccessibleAbstractSlider::rect(child); } ... Первая часть функции, которую мы опустили, использует текущий стиль для подсчета ограничительного прямоугольника ручки ползунка; он сохраняется в srect. Обратите внимание, что дочерний элемент child со значением 0, обрабатываемый меткой default в коде выше, это сам ползунок, так что мы можем просто вернуть ограничивающий прямоугольник QSlider, полученный из родительского класса, который фактически получает значение из QAccessibleWidget::rect(). QPoint tp = slider()->mapToGlobal(QPoint(0,0)); return QRect(tp.x() + rect.x(), tp.y() + rect.y(), rect.width(), rect.height()); } Перед тем, как прямоугольник будет возвращен, он должен быть отображен на экранные координаты. QAccessibleSlider должен переопределить QAccessibleInterface::childCount(), поскольку он управляет дочерними элементами без интерфейсов. Функция text() возвращает строки из набора QAccessible::Text для ползунка: QString QAccessibleSlider::text(Text t, int child) const { if (!slider()->isVisible()) return QString(); switch (t) { case Value: if (!child || child == 2) return QString::number(slider()->value()); return QString(); case Name: switch (child) { case PageLeft: return slider()->orientation() == Qt::Horizontal ? QSlider::tr("Page left") : QSlider::tr("Page up"); case Position: return QSlider::tr("Position"); case PageRight: return slider()->orientation() == Qt::Horizontal ? QSlider::tr("Page right") : QSlider::tr("Page down"); } break; default: break; } return QAccessibleAbstractSlider::text(t, child); } Функция slider() возвращает указатель на интерфейс QSlider. Для некоторых значений оставлена реализация родительского класса. Не все значения соответствуют всем объектам специальных возможностей, как вы можете видеть в случае QAccessible::Value. Вы должны вернуть пустую строку для тех значений, где соответствующий текст не может быть предоставлен. Реализация функции role() очень простая: QAccessible::Role QAccessibleSlider::role(int child) const { switch (child) { case PageLeft: case PageRight: return PushButton; case Position: return Indicator; default: return Slider; } } Функция role должна быть переопределена для всех объектов и описывать роль себя и детей, которые не обеспечивают для себя интерфейсы специальных возможностей. Далее, интерфейс специальных возможностей должен вернуть состояния, в которых может находиться ползунок. Рассмотрим часть реализации функции state(), чтобы показать, как обрабатывать всего несколько состояний: QAccessible::State QAccessibleSlider::state(int child) const { const State parentState = QAccessibleAbstractSlider::state(0); ... switch (child) { case PageLeft: if (slider->value() <= slider->minimum()) state |= Unavailable; break; case PageRight: if (slider->value() >= slider->maximum()) state |= Unavailable; break; case Position: default: break; } return state; } Реализация функции state() в родительском классе использует реализацию QAccessibleInterface::state(). Мы просто должны отключить кнопки, если ползунок находится в минимуме или максимуме. Теперь мы предоставляем информацию, которую мы знаем о ползунке, клиентам. Для того чтобы клиенты имели возможность изменять ползунок, например, его значение, мы должны предоставить информацию о действиях, которые могут быть выполнены, и выполнять их по запросу.. Мы обсудим это в следующем разделе. Обработка запросов действия от клиентовQAccessible предоставляет ряд действий, которые могут быть выполнены по запросу клиентов. Если объект специальных возможностей поддерживает действия, следует переопределить следующие функции QAccessibleInterface:
Обратите внимание на то, что клиент может запросить любое действие от объекта. Если клиент не поддерживает действие, он возвращает false из doAction(). Ни одно из стандартных действий не принимает никаких параметров. Можно предоставить пользовательские действия, принимающие параметры. В этом случае интерфейс должен также переопределить userActionCount(). Так как это не определено в спецификации MSAA, то, вероятно, это будет полезно использовать только в том случае, если вы знаете, какие ВТ-клиенты будут использовать приложение. QAccessibleInterface предоставляет клиентам другой метод для обработки объектов специальных возможностей. Он работает в целом так же, но использует понятие методов вместо действий. Доступные методы определены в перечислении QAccessible::Method. Следующие функции класса QAccessibleInterface должны быть переопределены, если объект специальных возможностей поддерживает методы:
Механизм действий, вероятно, будет заменен предоставлением методов вместо стандартных действий. В качестве примеров того, как реализовать действия и методы, вы можете посмотреть реализации QAccessibleObject и QAccessibleWidget. Также вы можете взглянуть на документацию MSAA. Реализация подключаемых модулей специальных возможностейВ этом разделе мы расскажем о процедуре реализации подключаемых модулей специальных возможностей для ваших интерфейсов. Подключаемый модуль - это класс, содержащийся в разделяемой библиотеке, которая может быть загружена во время выполнения. Это удобно для распространения интерфейсов с помощью подключаемых модулей, поскольку они будут загружаться только при необходимости. Создание подключаемого модуля специальных возможностей достигается путем наследования QAccessiblePlugin, переопределения функций keys() и create() этого класса и добавления одного или двух макросов. Файл .pro должен быть изменен для использования шаблона подключаемого модуля, а библиотека, содержащая модуль, должна быть размещена в каталоге, в котором Qt ищет подключаемые модули специальных возможностей. Мы пойдем путем реализации SliderPlugin, который является подключаемым модулем специальных возможностей и создает интерфейс QAccessibleSlider из примера QAccessibleWidget. Начнём с функции key(): QStringList SliderPlugin::keys() const { return QStringList() << QLatin1String("QSlider"); } Мы просто должны вернуть имя класса единственного интерфейса, для которого наш подключаемый модуль может создать интерфейс специальных возможностей. Подключаемый модуль может поддерживать любое число классов; просто добавьте больше имен классов в список строк. Теперь мы переходим к функции create(): QAccessibleInterface *SliderPlugin::create(const QString &classname, QObject *object) { QAccessibleInterface *interface = 0; if (classname == QLatin1String("QSlider") && object && object->isWidgetType()) interface = new QAccessibleSlider(static_cast<QWidget *>(object)); return interface; } Мы проверяем, что интерфейс, который запрашивается, это QSlider; если это так, то мы создаем и возвращаем интерфейс для него. Обратите внимание на то, что объект всегда будет экземпляром класса classname. Вы должны вернуть 0, если вы не поддерживаете этот класс. updateAccessibility() проверяет доступные подключаемые модули специальных возможностей, пока не найдет тот, который возвращает не 0. В заключение вам необходимо включить в файл cpp макросы: Q_EXPORT_STATIC_PLUGIN(SliderPlugin) Q_EXPORT_PLUGIN2(acc_sliderplugin, SliderPlugin) Макрос Q_EXPORT_PLUGIN2 экспортирует подключаемый модуль в классе SliderPlugin в библиотеку acc_sliderplugin. Первым аргументом является имя файла библиотеки подключаемых модулей, исключая суффикс, а вторым - имя класса. Для получения дополнительной информации о подключаемых модулях вы можете обратиться к обзорному документу по подключаемым модулям. Вы можете пропустить первый макрос, если вы не хотите, чтобы подключаемый модуль быть статически слинкован с приложением. Реализация интерфейсных фабрикЕсли вы не хотите предоставлять подключаемые модули для интерфейсов специальных возможностей, можно использовать интерфейс фабрики (QAccessible::InterfaceFactory), который является рекомендуемым способом обеспечения интерфейсов специальных возможностей в статически слинкованных приложениях. Фабрика является указателем на функцию, принимающую те же параметры, что и функция create() класса QAccessiblePlugin - QString и QObject. Она работает таким же образом. Вы устанавливаете фабрику функцией installFactory (). Приведём пример того, как создать фабрику для интерфейса QAccessibleSlider: QAccessibleInterface *sliderFactory(const QString &classname, QObject *object) { QAccessibleInterface *interface = 0; if (classname == QLatin1String("QSlider") && object && object->isWidgetType()) interface = new QAccessibleSlider(static_cast<QWidget *>(object)); return interface; } int main(int argv, char **args) { QApplication app(argv, args); QAccessible::installFactory(sliderFactory); ... } Дополнительные материалыВ документе Кросс-платформенная поддержка специальных возможностей в Qt 4 содержится более общий обзор функций специальных возможностей Qt и обсуждается, как они используются на каждой платформе. проблемы |
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |