diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm | 679 |
1 files changed, 517 insertions, 162 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index 9e26f93b9d..8d4d6d683d 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <AppKit/AppKit.h> @@ -45,13 +9,18 @@ #include "qcocoawindow.h" #include "qcocoascreen.h" +#include <QtCore/qlogging.h> #include <QtGui/private/qaccessiblecache_p.h> #include <QtGui/private/qaccessiblebridgeutils_p.h> #include <QtGui/qaccessible.h> QT_USE_NAMESPACE -#ifndef QT_NO_ACCESSIBILITY +Q_LOGGING_CATEGORY(lcAccessibilityTable, "qt.accessibility.table") + +using namespace Qt::Literals::StringLiterals; + +#if QT_CONFIG(accessibility) /** * Converts between absolute character offsets and line numbers of a @@ -116,14 +85,92 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of @implementation QMacAccessibilityElement { QAccessible::Id axid; + int m_rowIndex; + int m_columnIndex; + + // used by NSAccessibilityTable + NSMutableArray<QMacAccessibilityElement *> *rows; // corresponds to accessibilityRows + NSMutableArray<QMacAccessibilityElement *> *columns; // corresponds to accessibilityColumns + + // If synthesizedRole is set, this means that this objects does not have a corresponding + // QAccessibleInterface, but it is synthesized by the cocoa plugin in order to meet the + // NSAccessibility requirements. + // The ownership is controlled by the parent object identified with the axid member variable. + // (Therefore, if this member is set, this objects axid member is the same as the parents axid + // member) + NSString *synthesizedRole; } - (instancetype)initWithId:(QAccessible::Id)anId { + return [self initWithId:anId role:nil]; +} + +- (instancetype)initWithId:(QAccessible::Id)anId role:(NSAccessibilityRole)role +{ Q_ASSERT((int)anId < 0); self = [super init]; if (self) { axid = anId; + m_rowIndex = -1; + m_columnIndex = -1; + rows = nil; + columns = nil; + synthesizedRole = role; + // table: if this is not created as an element managed by the table, then + // it's either the table itself, or an element created for an already existing + // cell interface (or an element that's not at all related to a table). + if (!synthesizedRole) { + if (QAccessibleInterface *iface = QAccessible::accessibleInterface(axid)) { + if (iface->tableInterface()) { + [self updateTableModel]; + } else if (const auto *cell = iface->tableCellInterface()) { + // If we create an element for a table cell, initialize it with row/column + // and insert it into the corresponding row's columns array. + m_rowIndex = cell->rowIndex(); + m_columnIndex = cell->columnIndex(); + QAccessibleInterface *table = cell->table(); + Q_ASSERT(table); + QAccessibleTableInterface *tableInterface = table->tableInterface(); + if (tableInterface) { + auto *tableElement = [QMacAccessibilityElement elementWithInterface:table]; + Q_ASSERT(tableElement); + if (!tableElement->rows + || int(tableElement->rows.count) <= m_rowIndex + || int(tableElement->rows.count) != tableInterface->rowCount()) { + qCWarning(lcAccessibilityTable) + << "Cell requested for row" << m_rowIndex << "is out of" + << "bounds for table with" << (tableElement->rows ? + tableElement->rows.count : tableInterface->rowCount()) + << "rows! Resizing table model."; + [tableElement updateTableModel]; + } + + Q_ASSERT(tableElement->rows); + Q_ASSERT(int(tableElement->rows.count) > m_rowIndex); + + auto *rowElement = tableElement->rows[m_rowIndex]; + if (!rowElement->columns || int(rowElement->columns.count) != tableInterface->columnCount()) { + if (rowElement->columns) { + qCWarning(lcAccessibilityTable) + << "Table representation column count is out of sync:" + << rowElement->columns.count << "!=" << tableInterface->columnCount(); + } + rowElement->columns = [rowElement populateTableRow:rowElement->columns + count:tableInterface->columnCount()]; + } + + qCDebug(lcAccessibilityTable) << "Creating cell representation for" + << m_rowIndex << m_columnIndex + << "in table with" + << tableElement->rows.count << "rows and" + << rowElement->columns.count << "columns"; + + rowElement->columns[m_columnIndex] = self; + } + } + } + } } return self; @@ -139,30 +186,45 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of QMacAccessibilityElement *element = cache->elementForId(anId); if (!element) { - QAccessibleInterface *iface = QAccessible::accessibleInterface(anId); - Q_ASSERT(iface); - if (!iface || !iface->isValid()) - return nil; + Q_ASSERT(QAccessible::accessibleInterface(anId)); element = [[self alloc] initWithId:anId]; cache->insertElement(anId, element); } return element; } ++ (instancetype)elementWithInterface:(QAccessibleInterface *)iface +{ + Q_ASSERT(iface); + if (!iface) + return nil; + + const QAccessible::Id anId = QAccessible::uniqueId(iface); + return [self elementWithId:anId]; +} + - (void)invalidate { axid = 0; + rows = nil; + columns = nil; + synthesizedRole = nil; + NSAccessibilityPostNotification(self, NSAccessibilityUIElementDestroyedNotification); [self release]; } - (void)dealloc { + if (rows) + [rows release]; // will also release all entries first + if (columns) + [columns release]; // will also release all entries first [super dealloc]; } - (BOOL)isEqual:(id)object { if ([object isKindOfClass:[QMacAccessibilityElement class]]) { QMacAccessibilityElement *other = object; - return other->axid == axid; + return other->axid == axid && other->synthesizedRole == synthesizedRole; } else { return NO; } @@ -172,16 +234,137 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return axid; } +- (BOOL)isManagedByParent { + return synthesizedRole != nil; +} + +- (NSMutableArray *)populateTableArray:(NSMutableArray *)array role:(NSAccessibilityRole)role count:(int)count +{ + if (QAccessibleInterface *iface = self.qtInterface) { + if (array && int(array.count) != count) { + [array release]; + array = nil; + } + if (!array) { + array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count]; + [array retain]; + } else { + [array removeAllObjects]; + } + Q_ASSERT(array); + for (int n = 0; n < count; ++n) { + // columns will have same axid as table (but not inserted in cache) + QMacAccessibilityElement *element = + [[QMacAccessibilityElement alloc] initWithId:axid role:role]; + if (element) { + if (role == NSAccessibilityRowRole) + element->m_rowIndex = n; + else if (role == NSAccessibilityColumnRole) + element->m_columnIndex = n; + [array addObject:element]; + [element release]; + } else { + qWarning("QCocoaAccessibility: invalid child"); + } + } + return array; + } + return nil; +} + +- (NSMutableArray *)populateTableRow:(NSMutableArray *)array count:(int)count +{ + Q_ASSERT(synthesizedRole == NSAccessibilityRowRole); + if (array && int(array.count) != count) { + [array release]; + array = nil; + } + + if (!array) { + array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count]; + [array retain]; + // When macOS asks for the children of a row, then we populate the row's column + // array with synthetic elements as place holders. This way, we don't have to + // create QAccessibleInterfaces for every cell before they are really needed. + // We don't add those synthetic elements into the cache, and we give them the + // same axid as the table. This way, we can get easily to the table, and from + // there to the QAccessibleInterface for the cell, when we have to eventually + // associate such an interface with the element (at which point it is no longer + // a placeholder). + for (int n = 0; n < count; ++n) { + // columns will have same axid as table (but not inserted in cache) + QMacAccessibilityElement *cell = + [[QMacAccessibilityElement alloc] initWithId:axid role:NSAccessibilityCellRole]; + if (cell) { + cell->m_rowIndex = m_rowIndex; + cell->m_columnIndex = n; + [array addObject:cell]; + } + } + } + Q_ASSERT(array); + return array; +} + +- (void)updateTableModel +{ + if (QAccessibleInterface *iface = self.qtInterface) { + if (QAccessibleTableInterface *table = iface->tableInterface()) { + Q_ASSERT(!self.isManagedByParent); + qCDebug(lcAccessibilityTable) << "Updating table representation with" + << table->rowCount() << table->columnCount(); + rows = [self populateTableArray:rows role:NSAccessibilityRowRole count:table->rowCount()]; + columns = [self populateTableArray:columns role:NSAccessibilityColumnRole count:table->columnCount()]; + } + } +} + +- (QAccessibleInterface *)qtInterface +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); + if (!iface || !iface->isValid()) + return nullptr; + + // If this is a placeholder element for a table cell, associate it with the + // cell interface (which will be created now, if needed). The current axid is + // for the table to which the cell belongs, so iface is pointing at the table. + if (synthesizedRole == NSAccessibilityCellRole) { + // get the cell interface - there must be a valid one + QAccessibleTableInterface *table = iface->tableInterface(); + Q_ASSERT(table); + QAccessibleInterface *cell = table->cellAt(m_rowIndex, m_columnIndex); + if (!cell) + return nullptr; + Q_ASSERT(cell->isValid()); + iface = cell; + + // no longer a placeholder + axid = QAccessible::uniqueId(cell); + synthesizedRole = nil; + + QAccessibleCache *cache = QAccessibleCache::instance(); + if (QMacAccessibilityElement *cellElement = cache->elementForId(axid)) { + // there already is another, non-placeholder element in the cache + Q_ASSERT(cellElement->synthesizedRole == nil); + // we have to release it if it's not us + if (cellElement != self) { + // for the same cell position + Q_ASSERT(cellElement->m_rowIndex == m_rowIndex && cellElement->m_columnIndex == m_columnIndex); + [cellElement release]; + } + } + + cache->insertElement(axid, self); + } + return iface; +} + // // accessibility protocol // - (BOOL)isAccessibilityFocused { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { - return false; - } // Just check if the app thinks we're focused. id focusedElement = NSApp.accessibilityApplicationFocusedUIElement; return [focusedElement isEqual:self]; @@ -192,7 +375,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of + (id) lineNumberForIndex: (int)index forText:(const QString &)text { auto textBefore = QStringView(text).left(index); - int newlines = textBefore.count(QLatin1Char('\n')); + qsizetype newlines = textBefore.count(u'\n'); return @(newlines); } @@ -201,31 +384,119 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSString *) accessibilityRole { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return NSAccessibilityUnknownRole; - return QCocoaAccessible::macRole(iface); + // shortcut for cells, rows, and columns in a table + if (synthesizedRole) + return synthesizedRole; + if (QAccessibleInterface *iface = self.qtInterface) + return QCocoaAccessible::macRole(iface); + return NSAccessibilityUnknownRole; } - (NSString *) accessibilitySubRole { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return NSAccessibilityUnknownRole; - return QCocoaAccessible::macSubrole(iface); + if (QAccessibleInterface *iface = self.qtInterface) + return QCocoaAccessible::macSubrole(iface); + return NSAccessibilityUnknownRole; } - (NSString *) accessibilityRoleDescription { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return NSAccessibilityUnknownRole; - return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole); + if (QAccessibleInterface *iface = self.qtInterface) + return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole); + return NSAccessibilityUnknownRole; } - (NSArray *) accessibilityChildren { + // shortcut for cells + if (synthesizedRole == NSAccessibilityCellRole) + return nil; + + QAccessibleInterface *iface = self.qtInterface; + if (!iface) + return nil; + if (QAccessibleTableInterface *table = iface->tableInterface()) { + // either a table or table rows/columns + if (!synthesizedRole) { + // This is the table element, parent of all rows and columns + /* + * Typical 2x2 table hierarchy as can be observed in a table found under + * Apple -> System Settings -> General -> Login Items (macOS 13) + * + * (AXTable) + * | Columns: NSArray* (2 items) + * | Rows: NSArray* (2 items) + * | Visible Columns: NSArray* (2 items) + * | Visible Rows: NSArray* (2 items) + * | Children: NSArray* (5 items) + +----<--| Header: (AXGroup) + | * +-- (AXRow) + | * | +-- (AXText) + | * | +-- (AXTextField) + | * +-- (AXRow) + | * | +-- (AXText) + | * | +-- (AXTextField) + | * +-- (AXColumn) + | * | Header: "Item" (sort button) + | * | Index: 0 + | * | Rows: NSArray* (2 items) + | * | Visible Rows: NSArray* (2 items) + | * +-- (AXColumn) + | * | Header: "Kind" (sort button) + | * | Index: 1 + | * | Rows: NSArray* (2 items) + | * | Visible Rows: NSArray* (2 items) + +----> +-- (AXGroup) + * +-- (AXButton/AXSortButton) Item [NSAccessibilityTableHeaderCellProxy] + * +-- (AXButton/AXSortButton) Kind [NSAccessibilityTableHeaderCellProxy] + */ + NSArray *rs = [self accessibilityRows]; + NSArray *cs = [self accessibilityColumns]; + const int rCount = int([rs count]); + const int cCount = int([cs count]); + int childCount = rCount + cCount; + NSMutableArray<QMacAccessibilityElement *> *tableChildren = + [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:childCount]; + for (int i = 0; i < rCount; ++i) { + [tableChildren addObject:[rs objectAtIndex:i]]; + } + for (int i = 0; i < cCount; ++i) { + [tableChildren addObject:[cs objectAtIndex:i]]; + } + return NSAccessibilityUnignoredChildren(tableChildren); + } else if (synthesizedRole == NSAccessibilityColumnRole) { + return nil; + } else if (synthesizedRole == NSAccessibilityRowRole) { + // axid matches the parent table axid so that we can easily find the parent table + // children of row are cell/any items + Q_ASSERT(m_rowIndex >= 0); + const int numColumns = table->columnCount(); + columns = [self populateTableRow:columns count:numColumns]; + return NSAccessibilityUnignoredChildren(columns); + } + } + return QCocoaAccessible::unignoredChildren(iface); +} + +- (NSArray *) accessibilitySelectedChildren { QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); if (!iface || !iface->isValid()) return nil; - return QCocoaAccessible::unignoredChildren(iface); + + QAccessibleSelectionInterface *selection = iface->selectionInterface(); + if (!selection) + return nil; + + const QList<QAccessibleInterface *> selectedList = selection->selectedItems(); + const qsizetype numSelected = selectedList.size(); + NSMutableArray<QMacAccessibilityElement *> *selectedChildren = + [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numSelected]; + for (QAccessibleInterface *selectedChild : selectedList) { + if (selectedChild && selectedChild->isValid()) { + QAccessible::Id id = QAccessible::uniqueId(selectedChild); + QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId:id]; + if (element) + [selectedChildren addObject:element]; + } + } + return NSAccessibilityUnignoredChildren(selectedChildren); } - (id) accessibilityWindow { @@ -239,36 +510,66 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSString *) accessibilityTitle { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return nil; - if (iface->role() == QAccessible::StaticText) - return nil; - return iface->text(QAccessible::Name).toNSString(); + if (QAccessibleInterface *iface = self.qtInterface) { + if (iface->role() == QAccessible::StaticText) + return nil; + if (self.isManagedByParent) + return nil; + return iface->text(QAccessible::Name).toNSString(); + } + return nil; } - (BOOL) isAccessibilityEnabled { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return false; - return !iface->state().disabled; + if (QAccessibleInterface *iface = self.qtInterface) + return !iface->state().disabled; + return false; } - (id)accessibilityParent { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + if (synthesizedRole == NSAccessibilityCellRole) { + // a synthetic cell without interface - shortcut to the row + QMacAccessibilityElement *tableElement = + [QMacAccessibilityElement elementWithId:axid]; + Q_ASSERT(tableElement && tableElement->rows); + Q_ASSERT(int(tableElement->rows.count) > m_rowIndex); + QMacAccessibilityElement *rowElement = tableElement->rows[m_rowIndex]; + return rowElement; + } + + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return nil; + if (self.isManagedByParent) { + // axid is the same for the parent element + return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId:axid]); + } + // macOS expects that the hierarchy is: // App -> Window -> Children // We don't actually have the window reflected properly in QAccessibility. // Check if the parent is the application and then instead return the native window. if (QAccessibleInterface *parent = iface->parent()) { - if (parent->role() != QAccessible::Application) { - QAccessible::Id parentId = QAccessible::uniqueId(parent); - return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId: parentId]); + if (parent->tableInterface()) { + QMacAccessibilityElement *tableElement = + [QMacAccessibilityElement elementWithInterface:parent]; + + // parent of cell should be row + int rowIndex = -1; + if (m_rowIndex >= 0 && m_columnIndex >= 0) + rowIndex = m_rowIndex; + else if (QAccessibleTableCellInterface *cell = iface->tableCellInterface()) + rowIndex = cell->rowIndex(); + Q_ASSERT(tableElement->rows); + if (rowIndex > int([tableElement->rows count]) || rowIndex == -1) + return nil; + QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex]; + return NSAccessibilityUnignoredAncestor(rowElement); } + if (parent->role() != QAccessible::Application) + return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithInterface: parent]); } if (QWindow *window = iface->window()) { @@ -282,78 +583,91 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSRect)accessibilityFrame { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return NSZeroRect; - return QCocoaScreen::mapToNative(iface->rect()); -} -- (NSString*)accessibilityLabel { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { - qWarning() << "Called accessibilityLabel on invalid object: " << axid; - return nil; + QRect rect; + if (self.isManagedByParent) { + if (QAccessibleTableInterface *table = iface->tableInterface()) { + // Construct the geometry of the Row/Column by looking at the individual table cells + // ### Assumes that cells logical coordinates have spatial ordering (e.g finds the + // rows width by taking the union between the leftmost item and the rightmost item in + // a row). + // Otherwise, we have to iterate over *all* cells in a row/columns to + // find out the Row/Column geometry + const bool isRow = synthesizedRole == NSAccessibilityRowRole; + QPoint cellPos; + int &row = isRow ? cellPos.ry() : cellPos.rx(); + int &col = isRow ? cellPos.rx() : cellPos.ry(); + + NSUInteger trackIndex = self.accessibilityIndex; + if (trackIndex != NSNotFound) { + row = int(trackIndex); + if (QAccessibleInterface *firstCell = table->cellAt(cellPos.y(), cellPos.x())) { + rect = firstCell->rect(); + col = isRow ? table->columnCount() : table->rowCount(); + if (col > 1) { + --col; + if (QAccessibleInterface *lastCell = + table->cellAt(cellPos.y(), cellPos.x())) + rect = rect.united(lastCell->rect()); + } + } + } + } + } else { + rect = iface->rect(); } - return iface->text(QAccessible::Description).toNSString(); -} -- (void)setAccessibilityLabel:(NSString*)label{ - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return; - iface->setText(QAccessible::Description, QString::fromNSString(label)); + return QCocoaScreen::mapToNative(rect); } -- (id) accessibilityMinValue:(QAccessibleInterface*)iface { - if (QAccessibleValueInterface *val = iface->valueInterface()) - return @(val->minimumValue().toDouble()); +- (NSString*)accessibilityLabel { + if (QAccessibleInterface *iface = self.qtInterface) + return iface->text(QAccessible::Description).toNSString(); + qWarning() << "Called accessibilityLabel on invalid object: " << axid; return nil; } -- (id) accessibilityMaxValue:(QAccessibleInterface*)iface { - if (QAccessibleValueInterface *val = iface->valueInterface()) - return @(val->maximumValue().toDouble()); - return nil; +- (void)setAccessibilityLabel:(NSString*)label{ + if (QAccessibleInterface *iface = self.qtInterface) + iface->setText(QAccessible::Description, QString::fromNSString(label)); } - (id) accessibilityValue { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return nil; - - // VoiceOver asks for the value attribute for all elements. Return nil - // if we don't want the element to have a value attribute. - if (!QCocoaAccessible::hasValueAttribute(iface)) - return nil; - - return QCocoaAccessible::getValueAttribute(iface); + if (QAccessibleInterface *iface = self.qtInterface) { + // VoiceOver asks for the value attribute for all elements. Return nil + // if we don't want the element to have a value attribute. + if (QCocoaAccessible::hasValueAttribute(iface)) + return QCocoaAccessible::getValueAttribute(iface); + } + return nil; } - (NSInteger) accessibilityNumberOfCharacters { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return 0; - if (QAccessibleTextInterface *text = iface->textInterface()) - return text->characterCount(); + if (QAccessibleInterface *iface = self.qtInterface) { + if (QAccessibleTextInterface *text = iface->textInterface()) + return text->characterCount(); + } return 0; } - (NSString *) accessibilitySelectedText { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return nil; - if (QAccessibleTextInterface *text = iface->textInterface()) { - int start = 0; - int end = 0; - text->selection(0, &start, &end); - return text->text(start, end).toNSString(); + if (QAccessibleInterface *iface = self.qtInterface) { + if (QAccessibleTextInterface *text = iface->textInterface()) { + int start = 0; + int end = 0; + text->selection(0, &start, &end); + return text->text(start, end).toNSString(); + } } return nil; } - (NSRange) accessibilitySelectedTextRange { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return NSRange(); if (QAccessibleTextInterface *text = iface->textInterface()) { int start = 0; @@ -370,8 +684,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSInteger)accessibilityLineForIndex:(NSInteger)index { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return 0; if (QAccessibleTextInterface *text = iface->textInterface()) { QString textToPos = text->text(0, index); @@ -381,8 +695,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSRange)accessibilityVisibleCharacterRange { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return NSRange(); // FIXME This is not correct and may impact performance for big texts if (QAccessibleTextInterface *text = iface->textInterface()) @@ -391,8 +705,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSInteger) accessibilityInsertionPointLineNumber { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return 0; if (QAccessibleTextInterface *text = iface->textInterface()) { int position = text->cursorPosition(); @@ -403,8 +717,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of - (NSArray *)accessibilityParameterizedAttributeNames { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { + QAccessibleInterface *iface = self.qtInterface; + if (!iface) { qWarning() << "Called attribute on invalid object: " << axid; return nil; } @@ -427,8 +741,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { + QAccessibleInterface *iface = self.qtInterface; + if (!iface) { qWarning() << "Called attribute on invalid object: " << axid; return nil; } @@ -468,7 +782,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of QRectF rect; if (range.length > 0) { NSUInteger position = range.location + range.length - 1; - if (position > range.location && iface->textInterface()->text(position, position + 1) == QStringLiteral("\n")) + if (position > range.location && iface->textInterface()->text(position, position + 1) == "\n"_L1) --position; QRect lastRect = iface->textInterface()->characterRect(position); rect = firstRect.united(lastRect); @@ -496,8 +810,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return NO; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { @@ -515,8 +829,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { if (QAccessibleActionInterface *action = iface->actionInterface()) @@ -544,8 +858,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of - (NSArray *)accessibilityActionNames { NSMutableArray *nsActions = [[NSMutableArray new] autorelease]; - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return nsActions; const QStringList &supportedActionNames = QAccessibleBridgeUtils::effectiveActionNames(iface); @@ -559,8 +873,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSString *)accessibilityActionDescription:(NSString *)action { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return nil; // FIXME is that the right return type?? QString qtAction = QCocoaAccessible::translateAction(action, iface); QString description; @@ -577,8 +891,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (void)accessibilityPerformAction:(NSString *)action { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (iface && iface->isValid()) { + if (QAccessibleInterface *iface = self.qtInterface) { const QString qtAction = QCocoaAccessible::translateAction(action, iface); QAccessibleBridgeUtils::performEffectiveAction(iface, qtAction); } @@ -587,15 +900,20 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of // misc - (BOOL)accessibilityIsIgnored { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return true; - return QCocoaAccessible::shouldBeIgnored(iface); + // Short-cut for placeholders and synthesized elements. Working around a bug + // that corrups lists returned by NSAccessibilityUnignoredChildren, otherwise + // we could ignore rows and columns that are outside the table. + if (self.isManagedByParent) + return false; + + if (QAccessibleInterface *iface = self.qtInterface) + return QCocoaAccessible::shouldBeIgnored(iface); + return true; } - (id)accessibilityHitTest:(NSPoint)point { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { + QAccessibleInterface *iface = self.qtInterface; + if (!iface) { // qDebug("Hit test: INVALID"); return NSAccessibilityUnignoredAncestor(self); } @@ -614,26 +932,23 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of childInterface = childOfChildInterface; } while (childOfChildInterface && childOfChildInterface->isValid()); - QAccessible::Id childId = QAccessible::uniqueId(childInterface); // hit a child, forward to child accessible interface. - QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childId]; + QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface]; if (accessibleElement) return NSAccessibilityUnignoredAncestor(accessibleElement); return NSAccessibilityUnignoredAncestor(self); } - (id)accessibilityFocusedUIElement { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - - if (!iface || !iface->isValid()) { + QAccessibleInterface *iface = self.qtInterface; + if (!iface) { qWarning("FocusedUIElement for INVALID"); return nil; } QAccessibleInterface *childInterface = iface->focusChild(); if (childInterface && childInterface->isValid()) { - QAccessible::Id childAxid = QAccessible::uniqueId(childInterface); - QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childAxid]; + QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface]; return NSAccessibilityUnignoredAncestor(accessibleElement); } @@ -641,8 +956,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSString *) accessibilityHelp { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (iface && iface->isValid()) { + if (QAccessibleInterface *iface = self.qtInterface) { const QString helpText = iface->text(QAccessible::Help); if (!helpText.isEmpty()) return helpText.toNSString(); @@ -650,6 +964,47 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return nil; } +/* + * Support for table + */ +- (NSInteger) accessibilityIndex { + NSInteger index = 0; + if (synthesizedRole == NSAccessibilityCellRole) + return m_columnIndex; + if (QAccessibleInterface *iface = self.qtInterface) { + if (self.isManagedByParent) { + // axid matches the parent table axid so that we can easily find the parent table + // children of row are cell/any items + if (QAccessibleTableInterface *table = iface->tableInterface()) { + if (m_rowIndex >= 0) + index = NSInteger(m_rowIndex); + else if (m_columnIndex >= 0) + index = NSInteger(m_columnIndex); + } + } + } + return index; +} + +- (NSArray *) accessibilityRows { + if (!synthesizedRole && rows) { + QAccessibleInterface *iface = self.qtInterface; + if (iface && iface->tableInterface()) + return NSAccessibilityUnignoredChildren(rows); + } + return nil; +} + +- (NSArray *) accessibilityColumns { + if (!synthesizedRole && columns) { + QAccessibleInterface *iface = self.qtInterface; + if (iface && iface->tableInterface()) + return NSAccessibilityUnignoredChildren(columns); + } + return nil; +} + @end -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) + |