357 lines
9.6 KiB
C++
357 lines
9.6 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "ui/accessible/ui_accessible_item.h"
|
|
|
|
#include "ui/rp_widget.h"
|
|
|
|
namespace Ui::Accessible {
|
|
|
|
Item::Item(not_null<RpWidget*> parent, int index)
|
|
: _parent(parent)
|
|
, _index(index)
|
|
, _identity(parent->accessibilityChildIdentity(index)) {
|
|
}
|
|
|
|
int Item::currentIndex() const {
|
|
const auto parent = _parent.get();
|
|
if (!parent) {
|
|
return -1;
|
|
}
|
|
if (!_identity) {
|
|
return _index;
|
|
}
|
|
// Fast path: while the row stays where we last saw it, one identity
|
|
// check confirms it - a full accessibilityChildIndexByIdentity() scan
|
|
// on every property read would make enumeration O(n^2).
|
|
const auto count = parent->accessibilityChildCount();
|
|
if (_index >= 0
|
|
&& (count < 0 || _index < count)
|
|
&& parent->accessibilityChildIdentity(_index) == _identity) {
|
|
return _index;
|
|
}
|
|
_index = parent->accessibilityChildIndexByIdentity(_identity);
|
|
return _index;
|
|
}
|
|
|
|
bool Item::isValid() const {
|
|
const auto parent = _parent.get();
|
|
if (!parent || !parent->isVisible()) {
|
|
return false;
|
|
}
|
|
const auto index = currentIndex();
|
|
if (index < 0) {
|
|
return false;
|
|
}
|
|
const auto count = parent->accessibilityChildCount();
|
|
return count < 0 || index < count;
|
|
}
|
|
|
|
QObject *Item::object() const {
|
|
return nullptr;
|
|
}
|
|
|
|
QWindow *Item::window() const {
|
|
const auto parent = _parent.get();
|
|
const auto w = parent ? parent->window() : nullptr;
|
|
return w ? w->windowHandle() : nullptr;
|
|
}
|
|
|
|
QAccessible::Role Item::role() const {
|
|
const auto parent = _parent.get();
|
|
return parent
|
|
? parent->accessibilityChildRole()
|
|
: QAccessible::Role();
|
|
}
|
|
|
|
QAccessible::State Item::state() const {
|
|
const auto parent = _parent.get();
|
|
const auto index = parent ? currentIndex() : -1;
|
|
return (index >= 0)
|
|
? parent->accessibilityChildState(index)
|
|
: QAccessible::State();
|
|
}
|
|
|
|
QString Item::text(QAccessible::Text t) const {
|
|
const auto parent = _parent.get();
|
|
const auto index = parent ? currentIndex() : -1;
|
|
if (index < 0) {
|
|
return {};
|
|
}
|
|
switch (t) {
|
|
case QAccessible::Name:
|
|
return parent->accessibilityChildName(index);
|
|
case QAccessible::Description:
|
|
return parent->accessibilityChildDescription(index);
|
|
case QAccessible::Value:
|
|
return parent->accessibilityChildValue(index);
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
void Item::setText(QAccessible::Text t, const QString &text) {
|
|
}
|
|
|
|
QRect Item::rect() const {
|
|
const auto parent = _parent.get();
|
|
const auto index = parent ? currentIndex() : -1;
|
|
if (index < 0) {
|
|
return {};
|
|
}
|
|
const auto local = parent->accessibilityChildRect(index);
|
|
if (local.isEmpty()) {
|
|
return {};
|
|
}
|
|
return QRect(parent->mapToGlobal(local.topLeft()), local.size());
|
|
}
|
|
|
|
int Item::childCount() const {
|
|
const auto parent = _parent.get();
|
|
const auto index = parent ? currentIndex() : -1;
|
|
if (index < 0) {
|
|
return 0;
|
|
}
|
|
return parent->accessibilityChildColumnCount(index);
|
|
}
|
|
|
|
QAccessibleInterface *Item::child(int index) const {
|
|
const auto parent = _parent.get();
|
|
const auto current = parent ? currentIndex() : -1;
|
|
const auto columns = (current >= 0)
|
|
? parent->accessibilityChildColumnCount(current)
|
|
: 0;
|
|
if (index < 0 || index >= columns) {
|
|
return nullptr;
|
|
}
|
|
if (!_subitems) {
|
|
_subitems = std::make_unique<SubItems>();
|
|
}
|
|
auto &ids = _subitems->list;
|
|
// Sub-items carrying a row identity resolve their current row themselves,
|
|
// so the cache stays valid across reorders. Without an identity they can
|
|
// only describe their construction-time row, so drop them when it moved.
|
|
if (!_identity && _subitemsIndex != current) {
|
|
ids.clear();
|
|
}
|
|
_subitemsIndex = current;
|
|
if (int(ids.size()) != columns) {
|
|
ids.resize(columns); // UniqueId handles deregistration.
|
|
}
|
|
if (!ids[index]) {
|
|
ids[index] = UniqueId(
|
|
QAccessible::registerAccessibleInterface(
|
|
new SubItem(parent, current, index, _identity)));
|
|
}
|
|
return ids[index].get();
|
|
}
|
|
|
|
int Item::indexOfChild(const QAccessibleInterface *child) const {
|
|
if (const auto subItem = dynamic_cast<const SubItem*>(child)) {
|
|
const auto mine = _identity
|
|
? (subItem->identity() == _identity)
|
|
: (subItem->row() == currentIndex());
|
|
if (mine) {
|
|
return subItem->column();
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
QAccessibleInterface *Item::childAt(int x, int y) const {
|
|
return nullptr;
|
|
}
|
|
|
|
QAccessibleInterface *Item::parent() const {
|
|
return QAccessible::queryAccessibleInterface(_parent.get());
|
|
}
|
|
|
|
void *Item::interface_cast(QAccessible::InterfaceType type) {
|
|
if (type == QAccessible::ActionInterface) {
|
|
// Expose the action interface only when the owner opted in for this
|
|
// child. Otherwise the Windows UIA bridge would advertise Invoke /
|
|
// SetFocus / SelectionItem and report success while doing nothing.
|
|
const auto parent = _parent.get();
|
|
const auto index = parent ? currentIndex() : -1;
|
|
if (index >= 0
|
|
&& parent->accessibilityChildSupportsActions(index)) {
|
|
return static_cast<QAccessibleActionInterface*>(this);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QStringList Item::actionNames() const {
|
|
const auto parent = _parent.get();
|
|
const auto index = parent ? currentIndex() : -1;
|
|
if (index < 0 || !parent->accessibilityChildSupportsActions(index)) {
|
|
return {};
|
|
}
|
|
const auto childState = parent->accessibilityChildState(index);
|
|
// Pressing invokes the item's default action (e.g. open the chat).
|
|
auto names = QStringList{ QAccessibleActionInterface::pressAction() };
|
|
// Only advertise focusing when the row claims to be focusable,
|
|
// matching the state() we report to the screen reader.
|
|
if (childState.focusable) {
|
|
names.append(QAccessibleActionInterface::setFocusAction());
|
|
}
|
|
// Selecting a list item maps to making it the current row. Advertise it
|
|
// (and, more importantly, handle it in doAction) so SelectionItem.Select
|
|
// does not silently report success: Qt routes ListItem selection through
|
|
// toggleAction() on the Qt 5 Windows bridge.
|
|
if (childState.selectable) {
|
|
names.append(QAccessibleActionInterface::toggleAction());
|
|
}
|
|
return names;
|
|
}
|
|
|
|
void Item::doAction(const QString &actionName) {
|
|
const auto parent = _parent.get();
|
|
if (!parent) {
|
|
return;
|
|
}
|
|
// Dispatch by stable identity, not index: the owner resolves it to the
|
|
// current row on the main thread, so a stale action either hits the right
|
|
// row (if it just moved) or fails safely (if it is gone). We forward the
|
|
// identity without touching widget state here, because the Windows UIA
|
|
// bridge invokes actions on a background thread.
|
|
const auto identity = _identity;
|
|
if (actionName == QAccessibleActionInterface::setFocusAction()
|
|
|| actionName == QAccessibleActionInterface::toggleAction()) {
|
|
parent->accessibilityChildSetFocus(identity);
|
|
} else if (actionName == QAccessibleActionInterface::pressAction()) {
|
|
parent->accessibilityChildActivate(identity);
|
|
}
|
|
}
|
|
|
|
QStringList Item::keyBindingsForAction(const QString &actionName) const {
|
|
return {};
|
|
}
|
|
|
|
SubItem::SubItem(
|
|
not_null<RpWidget*> parent,
|
|
int row,
|
|
int column,
|
|
quintptr identity)
|
|
: _parent(parent)
|
|
, _row(row)
|
|
, _column(column)
|
|
, _identity(identity) {
|
|
}
|
|
|
|
int SubItem::currentRow() const {
|
|
const auto parent = _parent.get();
|
|
if (!parent) {
|
|
return -1;
|
|
}
|
|
if (!_identity) {
|
|
return _row;
|
|
}
|
|
const auto count = parent->accessibilityChildCount();
|
|
if (_row >= 0
|
|
&& (count < 0 || _row < count)
|
|
&& parent->accessibilityChildIdentity(_row) == _identity) {
|
|
return _row;
|
|
}
|
|
_row = parent->accessibilityChildIndexByIdentity(_identity);
|
|
return _row;
|
|
}
|
|
|
|
bool SubItem::isValid() const {
|
|
const auto parent = _parent.get();
|
|
if (_column < 0 || !parent || !parent->isVisible()) {
|
|
return false;
|
|
}
|
|
const auto row = currentRow();
|
|
if (row < 0) {
|
|
return false;
|
|
}
|
|
const auto count = parent->accessibilityChildCount();
|
|
const auto columns = parent->accessibilityChildColumnCount(row);
|
|
return (count < 0 || row < count) && _column < columns;
|
|
}
|
|
|
|
QObject *SubItem::object() const {
|
|
return nullptr;
|
|
}
|
|
|
|
QWindow *SubItem::window() const {
|
|
const auto parent = _parent.get();
|
|
const auto w = parent ? parent->window() : nullptr;
|
|
return w ? w->windowHandle() : nullptr;
|
|
}
|
|
|
|
QAccessible::Role SubItem::role() const {
|
|
const auto parent = _parent.get();
|
|
return parent
|
|
? parent->accessibilityChildSubItemRole()
|
|
: QAccessible::Role();
|
|
}
|
|
|
|
QAccessible::State SubItem::state() const {
|
|
return {};
|
|
}
|
|
|
|
QString SubItem::text(QAccessible::Text t) const {
|
|
const auto parent = _parent.get();
|
|
const auto row = parent ? currentRow() : -1;
|
|
if (row < 0 || _column < 0) {
|
|
return {};
|
|
}
|
|
switch (t) {
|
|
case QAccessible::Name:
|
|
return parent->accessibilityChildSubItemName(row, _column);
|
|
case QAccessible::Value:
|
|
return parent->accessibilityChildSubItemValue(row, _column);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void SubItem::setText(QAccessible::Text t, const QString &text) {
|
|
}
|
|
|
|
QRect SubItem::rect() const {
|
|
const auto parent = _parent.get();
|
|
const auto row = parent ? currentRow() : -1;
|
|
if (row < 0) {
|
|
return QRect();
|
|
}
|
|
const auto local = parent->accessibilityChildRect(row);
|
|
if (local.isEmpty()) {
|
|
return QRect();
|
|
}
|
|
return QRect(parent->mapToGlobal(local.topLeft()), local.size());
|
|
}
|
|
|
|
int SubItem::childCount() const {
|
|
return 0;
|
|
}
|
|
|
|
QAccessibleInterface *SubItem::child(int index) const {
|
|
return nullptr;
|
|
}
|
|
|
|
int SubItem::indexOfChild(const QAccessibleInterface *child) const {
|
|
return -1;
|
|
}
|
|
|
|
QAccessibleInterface *SubItem::childAt(int x, int y) const {
|
|
return nullptr;
|
|
}
|
|
|
|
QAccessibleInterface *SubItem::parent() const {
|
|
const auto parent = _parent.get();
|
|
if (!parent) {
|
|
return nullptr;
|
|
}
|
|
const auto iface = QAccessible::queryAccessibleInterface(parent);
|
|
const auto row = currentRow();
|
|
if (iface && row >= 0 && row < iface->childCount()) {
|
|
return iface->child(row);
|
|
}
|
|
return iface;
|
|
}
|
|
|
|
} // namespace Ui::Accessible
|