Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qcocoaaccessibilityelement.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <AppKit/AppKit.h>
5
8#include "qcocoahelpers.h"
9#include "qcocoawindow.h"
10#include "qcocoascreen.h"
11
12#include <QtCore/qlogging.h>
13#include <QtGui/private/qaccessiblecache_p.h>
14#include <QtGui/private/qaccessiblebridgeutils_p.h>
15#include <QtGui/qaccessible.h>
16
18
19Q_LOGGING_CATEGORY(lcAccessibilityTable, "qt.accessibility.table")
20
21using namespace Qt::Literals::StringLiterals;
22
23#if QT_CONFIG(accessibility)
24
38static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *offset, NSUInteger *start = 0, NSUInteger *end = 0)
39{
40 Q_ASSERT(*line == -1 || *offset == -1);
41 Q_ASSERT(*line != -1 || *offset != -1);
42 Q_ASSERT(*offset <= text->characterCount());
43
44 int curLine = -1;
45 int curStart = 0, curEnd = 0;
46
47 do {
48 curStart = curEnd;
49 text->textAtOffset(curStart, QAccessible::LineBoundary, &curStart, &curEnd);
50 // If the text is empty then we just return
51 if (curStart == -1 || curEnd == -1) {
52 if (start)
53 *start = 0;
54 if (end)
55 *end = 0;
56 return;
57 }
58 ++curLine;
59 {
60 // check for a case where a single word longer than the text edit's width and gets wrapped
61 // in the middle of the word; in this case curEnd will be an offset belonging to the next line
62 // and therefore nextEnd will not be equal to curEnd
63 int nextStart;
64 int nextEnd;
65 text->textAtOffset(curEnd, QAccessible::LineBoundary, &nextStart, &nextEnd);
66 if (nextEnd == curEnd)
67 ++curEnd;
68 }
69 } while ((*line == -1 || curLine < *line) && (*offset == -1 || (curEnd <= *offset)) && curEnd <= text->characterCount());
70
71 curEnd = qMin(curEnd, text->characterCount());
72
73 if (*line == -1)
74 *line = curLine;
75 if (*offset == -1)
76 *offset = curStart;
77
78 Q_ASSERT(curStart >= 0);
79 Q_ASSERT(curEnd >= 0);
80 if (start)
81 *start = curStart;
82 if (end)
83 *end = curEnd;
84}
85
86@implementation QMacAccessibilityElement {
87 QAccessible::Id axid;
88 int m_rowIndex;
89 int m_columnIndex;
90
91 // used by NSAccessibilityTable
92 NSMutableArray<QMacAccessibilityElement *> *rows; // corresponds to accessibilityRows
93 NSMutableArray<QMacAccessibilityElement *> *columns; // corresponds to accessibilityColumns
94
95 // If synthesizedRole is set, this means that this objects does not have a corresponding
96 // QAccessibleInterface, but it is synthesized by the cocoa plugin in order to meet the
97 // NSAccessibility requirements.
98 // The ownership is controlled by the parent object identified with the axid member variable.
99 // (Therefore, if this member is set, this objects axid member is the same as the parents axid
100 // member)
101 NSString *synthesizedRole;
102}
103
104- (instancetype)initWithId:(QAccessible::Id)anId
105{
106 return [self initWithId:anId role:nil];
107}
108
109- (instancetype)initWithId:(QAccessible::Id)anId role:(NSAccessibilityRole)role
110{
111 Q_ASSERT((int)anId < 0);
112 self = [super init];
113 if (self) {
114 axid = anId;
115 m_rowIndex = -1;
116 m_columnIndex = -1;
117 rows = nil;
118 columns = nil;
119 synthesizedRole = role;
120 // table: if this is not created as an element managed by the table, then
121 // it's either the table itself, or an element created for an already existing
122 // cell interface (or an element that's not at all related to a table).
123 if (!synthesizedRole) {
124 if (QAccessibleInterface *iface = QAccessible::accessibleInterface(axid)) {
125 if (iface->tableInterface()) {
126 [self updateTableModel];
127 } else if (const auto *cell = iface->tableCellInterface()) {
128 // If we create an element for a table cell, initialize it with row/column
129 // and insert it into the corresponding row's columns array.
130 m_rowIndex = cell->rowIndex();
131 m_columnIndex = cell->columnIndex();
132 QAccessibleInterface *table = cell->table();
134 QAccessibleTableInterface *tableInterface = table->tableInterface();
135 if (tableInterface) {
136 auto *tableElement = [QMacAccessibilityElement elementWithInterface:table];
137 Q_ASSERT(tableElement);
138 if (!tableElement->rows
139 || int(tableElement->rows.count) <= m_rowIndex
140 || int(tableElement->rows.count) != tableInterface->rowCount()) {
141 qCWarning(lcAccessibilityTable)
142 << "Cell requested for row" << m_rowIndex << "is out of"
143 << "bounds for table with" << (tableElement->rows ?
144 tableElement->rows.count : tableInterface->rowCount())
145 << "rows! Resizing table model.";
146 [tableElement updateTableModel];
147 }
148
149 Q_ASSERT(tableElement->rows);
150 Q_ASSERT(int(tableElement->rows.count) > m_rowIndex);
151
152 auto *rowElement = tableElement->rows[m_rowIndex];
153 if (!rowElement->columns || int(rowElement->columns.count) != tableInterface->columnCount()) {
154 if (rowElement->columns) {
155 qCWarning(lcAccessibilityTable)
156 << "Table representation column count is out of sync:"
157 << rowElement->columns.count << "!=" << tableInterface->columnCount();
158 }
159 rowElement->columns = [rowElement populateTableRow:rowElement->columns
160 count:tableInterface->columnCount()];
161 }
162
163 qCDebug(lcAccessibilityTable) << "Creating cell representation for"
164 << m_rowIndex << m_columnIndex
165 << "in table with"
166 << tableElement->rows.count << "rows and"
167 << rowElement->columns.count << "columns";
168
169 rowElement->columns[m_columnIndex] = self;
170 }
171 }
172 }
173 }
174 }
175
176 return self;
177}
178
179+ (instancetype)elementWithId:(QAccessible::Id)anId
180{
181 Q_ASSERT(anId);
182 if (!anId)
183 return nil;
184
185 QAccessibleCache *cache = QAccessibleCache::instance();
186
187 QMacAccessibilityElement *element = cache->elementForId(anId);
188 if (!element) {
189 Q_ASSERT(QAccessible::accessibleInterface(anId));
190 element = [[self alloc] initWithId:anId];
191 cache->insertElement(anId, element);
192 }
193 return element;
194}
195
196+ (instancetype)elementWithInterface:(QAccessibleInterface *)iface
197{
198 Q_ASSERT(iface);
199 if (!iface)
200 return nil;
201
202 const QAccessible::Id anId = QAccessible::uniqueId(iface);
203 return [self elementWithId:anId];
204}
205
206- (void)invalidate {
207 axid = 0;
208 rows = nil;
209 columns = nil;
210 synthesizedRole = nil;
211
212 NSAccessibilityPostNotification(self, NSAccessibilityUIElementDestroyedNotification);
213 [self release];
214}
215
216- (void)dealloc {
217 if (rows)
218 [rows release]; // will also release all entries first
219 if (columns)
220 [columns release]; // will also release all entries first
221 [super dealloc];
222}
223
224- (BOOL)isEqual:(id)object {
225 if ([object isKindOfClass:[QMacAccessibilityElement class]]) {
226 QMacAccessibilityElement *other = object;
227 return other->axid == axid && other->synthesizedRole == synthesizedRole;
228 } else {
229 return NO;
230 }
231}
232
233- (NSUInteger)hash {
234 return axid;
235}
236
237- (BOOL)isManagedByParent {
238 return synthesizedRole != nil;
239}
240
241- (NSMutableArray *)populateTableArray:(NSMutableArray *)array role:(NSAccessibilityRole)role count:(int)count
242{
243 if (QAccessibleInterface *iface = self.qtInterface) {
244 if (array && int(array.count) != count) {
245 [array release];
246 array = nil;
247 }
248 if (!array) {
249 array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count];
250 [array retain];
251 } else {
252 [array removeAllObjects];
253 }
255 for (int n = 0; n < count; ++n) {
256 // columns will have same axid as table (but not inserted in cache)
257 QMacAccessibilityElement *element =
258 [[QMacAccessibilityElement alloc] initWithId:axid role:role];
259 if (element) {
260 if (role == NSAccessibilityRowRole)
261 element->m_rowIndex = n;
262 else if (role == NSAccessibilityColumnRole)
263 element->m_columnIndex = n;
264 [array addObject:element];
265 [element release];
266 } else {
267 qWarning("QCocoaAccessibility: invalid child");
268 }
269 }
270 return array;
271 }
272 return nil;
273}
274
275- (NSMutableArray *)populateTableRow:(NSMutableArray *)array count:(int)count
276{
277 Q_ASSERT(synthesizedRole == NSAccessibilityRowRole);
278 if (array && int(array.count) != count) {
279 [array release];
280 array = nil;
281 }
282
283 if (!array) {
284 array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count];
285 [array retain];
286 // When macOS asks for the children of a row, then we populate the row's column
287 // array with synthetic elements as place holders. This way, we don't have to
288 // create QAccessibleInterfaces for every cell before they are really needed.
289 // We don't add those synthetic elements into the cache, and we give them the
290 // same axid as the table. This way, we can get easily to the table, and from
291 // there to the QAccessibleInterface for the cell, when we have to eventually
292 // associate such an interface with the element (at which point it is no longer
293 // a placeholder).
294 for (int n = 0; n < count; ++n) {
295 // columns will have same axid as table (but not inserted in cache)
296 QMacAccessibilityElement *cell =
297 [[QMacAccessibilityElement alloc] initWithId:axid role:NSAccessibilityCellRole];
298 if (cell) {
299 cell->m_rowIndex = m_rowIndex;
300 cell->m_columnIndex = n;
301 [array addObject:cell];
302 }
303 }
304 }
306 return array;
307}
308
309- (void)updateTableModel
310{
311 if (QAccessibleInterface *iface = self.qtInterface) {
312 if (QAccessibleTableInterface *table = iface->tableInterface()) {
313 Q_ASSERT(!self.isManagedByParent);
314 qCDebug(lcAccessibilityTable) << "Updating table representation with"
315 << table->rowCount() << table->columnCount();
316 rows = [self populateTableArray:rows role:NSAccessibilityRowRole count:table->rowCount()];
317 columns = [self populateTableArray:columns role:NSAccessibilityColumnRole count:table->columnCount()];
318 }
319 }
320}
321
322- (QAccessibleInterface *)qtInterface
323{
324 QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
325 if (!iface || !iface->isValid())
326 return nullptr;
327
328 // If this is a placeholder element for a table cell, associate it with the
329 // cell interface (which will be created now, if needed). The current axid is
330 // for the table to which the cell belongs, so iface is pointing at the table.
331 if (synthesizedRole == NSAccessibilityCellRole) {
332 // get the cell interface - there must be a valid one
333 QAccessibleTableInterface *table = iface->tableInterface();
335 QAccessibleInterface *cell = table->cellAt(m_rowIndex, m_columnIndex);
336 if (!cell)
337 return nullptr;
338 Q_ASSERT(cell->isValid());
339 iface = cell;
340
341 // no longer a placeholder
342 axid = QAccessible::uniqueId(cell);
343 synthesizedRole = nil;
344
345 QAccessibleCache *cache = QAccessibleCache::instance();
346 if (QMacAccessibilityElement *cellElement = cache->elementForId(axid)) {
347 // there already is another, non-placeholder element in the cache
348 Q_ASSERT(cellElement->synthesizedRole == nil);
349 // we have to release it if it's not us
350 if (cellElement != self) {
351 // for the same cell position
352 Q_ASSERT(cellElement->m_rowIndex == m_rowIndex && cellElement->m_columnIndex == m_columnIndex);
353 [cellElement release];
354 }
355 }
356
357 cache->insertElement(axid, self);
358 }
359 return iface;
360}
361
362//
363// accessibility protocol
364//
365
366- (BOOL)isAccessibilityFocused
367{
368 // Just check if the app thinks we're focused.
369 id focusedElement = NSApp.accessibilityApplicationFocusedUIElement;
370 return [focusedElement isEqual:self];
371}
372
373// attributes
374
375+ (id) lineNumberForIndex: (int)index forText:(const QString &)text
376{
377 auto textBefore = QStringView(text).left(index);
378 qsizetype newlines = textBefore.count(u'\n');
379 return @(newlines);
380}
381
382- (BOOL) accessibilityNotifiesWhenDestroyed {
383 return YES;
384}
385
386- (NSString *) accessibilityRole {
387 // shortcut for cells, rows, and columns in a table
388 if (synthesizedRole)
389 return synthesizedRole;
390 if (QAccessibleInterface *iface = self.qtInterface)
391 return QCocoaAccessible::macRole(iface);
392 return NSAccessibilityUnknownRole;
393}
394
395- (NSString *) accessibilitySubRole {
396 if (QAccessibleInterface *iface = self.qtInterface)
397 return QCocoaAccessible::macSubrole(iface);
398 return NSAccessibilityUnknownRole;
399}
400
401- (NSString *) accessibilityRoleDescription {
402 if (QAccessibleInterface *iface = self.qtInterface)
403 return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole);
404 return NSAccessibilityUnknownRole;
405}
406
407- (NSArray *) accessibilityChildren {
408 // shortcut for cells
409 if (synthesizedRole == NSAccessibilityCellRole)
410 return nil;
411
412 QAccessibleInterface *iface = self.qtInterface;
413 if (!iface)
414 return nil;
415 if (QAccessibleTableInterface *table = iface->tableInterface()) {
416 // either a table or table rows/columns
417 if (!synthesizedRole) {
418 // This is the table element, parent of all rows and columns
419 /*
420 * Typical 2x2 table hierarchy as can be observed in a table found under
421 * Apple -> System Settings -> General -> Login Items (macOS 13)
422 *
423 * (AXTable)
424 * | Columns: NSArray* (2 items)
425 * | Rows: NSArray* (2 items)
426 * | Visible Columns: NSArray* (2 items)
427 * | Visible Rows: NSArray* (2 items)
428 * | Children: NSArray* (5 items)
429 +----<--| Header: (AXGroup)
430 | * +-- (AXRow)
431 | * | +-- (AXText)
432 | * | +-- (AXTextField)
433 | * +-- (AXRow)
434 | * | +-- (AXText)
435 | * | +-- (AXTextField)
436 | * +-- (AXColumn)
437 | * | Header: "Item" (sort button)
438 | * | Index: 0
439 | * | Rows: NSArray* (2 items)
440 | * | Visible Rows: NSArray* (2 items)
441 | * +-- (AXColumn)
442 | * | Header: "Kind" (sort button)
443 | * | Index: 1
444 | * | Rows: NSArray* (2 items)
445 | * | Visible Rows: NSArray* (2 items)
446 +----> +-- (AXGroup)
447 * +-- (AXButton/AXSortButton) Item [NSAccessibilityTableHeaderCellProxy]
448 * +-- (AXButton/AXSortButton) Kind [NSAccessibilityTableHeaderCellProxy]
449 */
450 NSArray *rs = [self accessibilityRows];
451 NSArray *cs = [self accessibilityColumns];
452 const int rCount = int([rs count]);
453 const int cCount = int([cs count]);
454 int childCount = rCount + cCount;
455 NSMutableArray<QMacAccessibilityElement *> *tableChildren =
456 [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:childCount];
457 for (int i = 0; i < rCount; ++i) {
458 [tableChildren addObject:[rs objectAtIndex:i]];
459 }
460 for (int i = 0; i < cCount; ++i) {
461 [tableChildren addObject:[cs objectAtIndex:i]];
462 }
463 return NSAccessibilityUnignoredChildren(tableChildren);
464 } else if (synthesizedRole == NSAccessibilityColumnRole) {
465 return nil;
466 } else if (synthesizedRole == NSAccessibilityRowRole) {
467 // axid matches the parent table axid so that we can easily find the parent table
468 // children of row are cell/any items
469 Q_ASSERT(m_rowIndex >= 0);
470 const int numColumns = table->columnCount();
471 columns = [self populateTableRow:columns count:numColumns];
472 return NSAccessibilityUnignoredChildren(columns);
473 }
474 }
475 return QCocoaAccessible::unignoredChildren(iface);
476}
477
478- (NSArray *) accessibilitySelectedChildren {
479 QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
480 if (!iface || !iface->isValid())
481 return nil;
482
483 QAccessibleSelectionInterface *selection = iface->selectionInterface();
484 if (!selection)
485 return nil;
486
487 const QList<QAccessibleInterface *> selectedList = selection->selectedItems();
488 const qsizetype numSelected = selectedList.size();
489 NSMutableArray<QMacAccessibilityElement *> *selectedChildren =
490 [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numSelected];
491 for (QAccessibleInterface *selectedChild : selectedList) {
492 if (selectedChild && selectedChild->isValid()) {
493 QAccessible::Id id = QAccessible::uniqueId(selectedChild);
494 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId:id];
495 if (element)
496 [selectedChildren addObject:element];
497 }
498 }
499 return NSAccessibilityUnignoredChildren(selectedChildren);
500}
501
502- (id) accessibilityWindow {
503 // We're in the same window as our parent.
504 return [self.accessibilityParent accessibilityWindow];
505}
506
507- (id) accessibilityTopLevelUIElementAttribute {
508 // We're in the same top level element as our parent.
509 return [self.accessibilityParent accessibilityTopLevelUIElementAttribute];
510}
511
512- (NSString *) accessibilityTitle {
513 if (QAccessibleInterface *iface = self.qtInterface) {
514 if (iface->role() == QAccessible::StaticText)
515 return nil;
516 if (self.isManagedByParent)
517 return nil;
518 return iface->text(QAccessible::Name).toNSString();
519 }
520 return nil;
521}
522
523- (BOOL) isAccessibilityEnabled {
524 if (QAccessibleInterface *iface = self.qtInterface)
525 return !iface->state().disabled;
526 return false;
527}
528
529- (id)accessibilityParent {
530 if (synthesizedRole == NSAccessibilityCellRole) {
531 // a synthetic cell without interface - shortcut to the row
532 QMacAccessibilityElement *tableElement =
533 [QMacAccessibilityElement elementWithId:axid];
534 Q_ASSERT(tableElement && tableElement->rows);
535 Q_ASSERT(int(tableElement->rows.count) > m_rowIndex);
536 QMacAccessibilityElement *rowElement = tableElement->rows[m_rowIndex];
537 return rowElement;
538 }
539
540 QAccessibleInterface *iface = self.qtInterface;
541 if (!iface)
542 return nil;
543
544 if (self.isManagedByParent) {
545 // axid is the same for the parent element
546 return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId:axid]);
547 }
548
549 // macOS expects that the hierarchy is:
550 // App -> Window -> Children
551 // We don't actually have the window reflected properly in QAccessibility.
552 // Check if the parent is the application and then instead return the native window.
553
554 if (QAccessibleInterface *parent = iface->parent()) {
555 if (parent->tableInterface()) {
556 QMacAccessibilityElement *tableElement =
557 [QMacAccessibilityElement elementWithInterface:parent];
558
559 // parent of cell should be row
560 int rowIndex = -1;
561 if (m_rowIndex >= 0 && m_columnIndex >= 0)
562 rowIndex = m_rowIndex;
563 else if (QAccessibleTableCellInterface *cell = iface->tableCellInterface())
564 rowIndex = cell->rowIndex();
565 Q_ASSERT(tableElement->rows);
566 if (rowIndex > int([tableElement->rows count]) || rowIndex == -1)
567 return nil;
568 QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex];
569 return NSAccessibilityUnignoredAncestor(rowElement);
570 }
571 if (parent->role() != QAccessible::Application)
572 return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithInterface: parent]);
573 }
574
575 if (QWindow *window = iface->window()) {
576 QPlatformWindow *platformWindow = window->handle();
577 if (platformWindow) {
578 QCocoaWindow *win = static_cast<QCocoaWindow*>(platformWindow);
579 return NSAccessibilityUnignoredAncestor(qnsview_cast(win->view()));
580 }
581 }
582 return nil;
583}
584
585- (NSRect)accessibilityFrame {
586 QAccessibleInterface *iface = self.qtInterface;
587 if (!iface)
588 return NSZeroRect;
589
590 QRect rect;
591 if (self.isManagedByParent) {
592 if (QAccessibleTableInterface *table = iface->tableInterface()) {
593 // Construct the geometry of the Row/Column by looking at the individual table cells
594 // ### Assumes that cells logical coordinates have spatial ordering (e.g finds the
595 // rows width by taking the union between the leftmost item and the rightmost item in
596 // a row).
597 // Otherwise, we have to iterate over *all* cells in a row/columns to
598 // find out the Row/Column geometry
599 const bool isRow = synthesizedRole == NSAccessibilityRowRole;
600 QPoint cellPos;
601 int &row = isRow ? cellPos.ry() : cellPos.rx();
602 int &col = isRow ? cellPos.rx() : cellPos.ry();
603
604 NSUInteger trackIndex = self.accessibilityIndex;
605 if (trackIndex != NSNotFound) {
606 row = int(trackIndex);
607 if (QAccessibleInterface *firstCell = table->cellAt(cellPos.y(), cellPos.x())) {
608 rect = firstCell->rect();
609 col = isRow ? table->columnCount() : table->rowCount();
610 if (col > 1) {
611 --col;
612 if (QAccessibleInterface *lastCell =
613 table->cellAt(cellPos.y(), cellPos.x()))
614 rect = rect.united(lastCell->rect());
615 }
616 }
617 }
618 }
619 } else {
620 rect = iface->rect();
621 }
622
624}
625
626- (NSString*)accessibilityLabel {
627 if (QAccessibleInterface *iface = self.qtInterface)
628 return iface->text(QAccessible::Description).toNSString();
629 qWarning() << "Called accessibilityLabel on invalid object: " << axid;
630 return nil;
631}
632
633- (void)setAccessibilityLabel:(NSString*)label{
634 if (QAccessibleInterface *iface = self.qtInterface)
635 iface->setText(QAccessible::Description, QString::fromNSString(label));
636}
637
638- (id) accessibilityValue {
639 if (QAccessibleInterface *iface = self.qtInterface) {
640 // VoiceOver asks for the value attribute for all elements. Return nil
641 // if we don't want the element to have a value attribute.
642 if (QCocoaAccessible::hasValueAttribute(iface))
643 return QCocoaAccessible::getValueAttribute(iface);
644 }
645 return nil;
646}
647
648- (NSInteger) accessibilityNumberOfCharacters {
649 if (QAccessibleInterface *iface = self.qtInterface) {
650 if (QAccessibleTextInterface *text = iface->textInterface())
651 return text->characterCount();
652 }
653 return 0;
654}
655
656- (NSString *) accessibilitySelectedText {
657 if (QAccessibleInterface *iface = self.qtInterface) {
658 if (QAccessibleTextInterface *text = iface->textInterface()) {
659 int start = 0;
660 int end = 0;
661 text->selection(0, &start, &end);
662 return text->text(start, end).toNSString();
663 }
664 }
665 return nil;
666}
667
668- (NSRange) accessibilitySelectedTextRange {
669 QAccessibleInterface *iface = self.qtInterface;
670 if (!iface)
671 return NSRange();
672 if (QAccessibleTextInterface *text = iface->textInterface()) {
673 int start = 0;
674 int end = 0;
675 if (text->selectionCount() > 0) {
676 text->selection(0, &start, &end);
677 } else {
678 start = text->cursorPosition();
679 end = start;
680 }
681 return NSMakeRange(quint32(start), quint32(end - start));
682 }
683 return NSMakeRange(0, 0);
684}
685
686- (NSInteger)accessibilityLineForIndex:(NSInteger)index {
687 QAccessibleInterface *iface = self.qtInterface;
688 if (!iface)
689 return 0;
690 if (QAccessibleTextInterface *text = iface->textInterface()) {
691 QString textToPos = text->text(0, index);
692 return textToPos.count('\n');
693 }
694 return 0;
695}
696
697- (NSRange)accessibilityVisibleCharacterRange {
698 QAccessibleInterface *iface = self.qtInterface;
699 if (!iface)
700 return NSRange();
701 // FIXME This is not correct and may impact performance for big texts
702 if (QAccessibleTextInterface *text = iface->textInterface())
703 return NSMakeRange(0, static_cast<uint>(text->characterCount()));
704 return NSMakeRange(0, static_cast<uint>(iface->text(QAccessible::Name).length()));
705}
706
707- (NSInteger) accessibilityInsertionPointLineNumber {
708 QAccessibleInterface *iface = self.qtInterface;
709 if (!iface)
710 return 0;
711 if (QAccessibleTextInterface *text = iface->textInterface()) {
712 int position = text->cursorPosition();
713 return [self accessibilityLineForIndex:position];
714 }
715 return 0;
716}
717
718- (NSArray *)accessibilityParameterizedAttributeNames {
719
720 QAccessibleInterface *iface = self.qtInterface;
721 if (!iface) {
722 qWarning() << "Called attribute on invalid object: " << axid;
723 return nil;
724 }
725
726 if (iface->textInterface()) {
727 return @[
728 NSAccessibilityStringForRangeParameterizedAttribute,
729 NSAccessibilityLineForIndexParameterizedAttribute,
730 NSAccessibilityRangeForLineParameterizedAttribute,
731 NSAccessibilityRangeForPositionParameterizedAttribute,
732// NSAccessibilityRangeForIndexParameterizedAttribute,
733 NSAccessibilityBoundsForRangeParameterizedAttribute,
734// NSAccessibilityRTFForRangeParameterizedAttribute,
735 NSAccessibilityStyleRangeForIndexParameterizedAttribute,
736 NSAccessibilityAttributedStringForRangeParameterizedAttribute
737 ];
738 }
739
740 return nil;
741}
742
743- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
744 QAccessibleInterface *iface = self.qtInterface;
745 if (!iface) {
746 qWarning() << "Called attribute on invalid object: " << axid;
747 return nil;
748 }
749
750 if (!iface->textInterface())
751 return nil;
752
753 if ([attribute isEqualToString: NSAccessibilityStringForRangeParameterizedAttribute]) {
754 NSRange range = [parameter rangeValue];
755 QString text = iface->textInterface()->text(range.location, range.location + range.length);
756 return text.toNSString();
757 }
758 if ([attribute isEqualToString: NSAccessibilityLineForIndexParameterizedAttribute]) {
759 int index = [parameter intValue];
760 if (index < 0 || index > iface->textInterface()->characterCount())
761 return nil;
762 int line = 0; // true for all single line edits
763 if (iface->state().multiLine) {
764 line = -1;
765 convertLineOffset(iface->textInterface(), &line, &index);
766 }
767 return @(line);
768 }
769 if ([attribute isEqualToString: NSAccessibilityRangeForLineParameterizedAttribute]) {
770 int line = [parameter intValue];
771 if (line < 0)
772 return nil;
773 int lineOffset = -1;
774 NSUInteger startOffset = 0;
775 NSUInteger endOffset = 0;
776 convertLineOffset(iface->textInterface(), &line, &lineOffset, &startOffset, &endOffset);
777 return [NSValue valueWithRange:NSMakeRange(startOffset, endOffset - startOffset)];
778 }
779 if ([attribute isEqualToString: NSAccessibilityBoundsForRangeParameterizedAttribute]) {
780 NSRange range = [parameter rangeValue];
781 QRect firstRect = iface->textInterface()->characterRect(range.location);
782 QRectF rect;
783 if (range.length > 0) {
784 NSUInteger position = range.location + range.length - 1;
785 if (position > range.location && iface->textInterface()->text(position, position + 1) == "\n"_L1)
786 --position;
787 QRect lastRect = iface->textInterface()->characterRect(position);
788 rect = firstRect.united(lastRect);
789 } else {
790 rect = firstRect;
791 rect.setWidth(1);
792 }
793 return [NSValue valueWithRect:QCocoaScreen::mapToNative(rect)];
794 }
795 if ([attribute isEqualToString: NSAccessibilityAttributedStringForRangeParameterizedAttribute]) {
796 NSRange range = [parameter rangeValue];
797 QString text = iface->textInterface()->text(range.location, range.location + range.length);
798 return [[NSAttributedString alloc] initWithString:text.toNSString()];
799 } else if ([attribute isEqualToString: NSAccessibilityRangeForPositionParameterizedAttribute]) {
800 QPoint point = QCocoaScreen::mapFromNative([parameter pointValue]).toPoint();
801 int offset = iface->textInterface()->offsetAtPoint(point);
802 return [NSValue valueWithRange:NSMakeRange(static_cast<NSUInteger>(offset), 1)];
803 } else if ([attribute isEqualToString: NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
804 int start = 0;
805 int end = 0;
806 iface->textInterface()->attributes([parameter intValue], &start, &end);
807 return [NSValue valueWithRange:NSMakeRange(static_cast<NSUInteger>(start), static_cast<NSUInteger>(end - start))];
808 }
809 return nil;
810}
811
812- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
813 QAccessibleInterface *iface = self.qtInterface;
814 if (!iface)
815 return NO;
816
817 if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
818 return iface->state().focusable ? YES : NO;
819 } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
820 if (iface->textInterface() && iface->state().editable)
821 return YES;
822 if (iface->valueInterface())
823 return YES;
824 return NO;
825 } else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
826 return iface->textInterface() ? YES : NO;
827 }
828 return NO;
829}
830
831- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
832 QAccessibleInterface *iface = self.qtInterface;
833 if (!iface)
834 return;
835 if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
836 if (QAccessibleActionInterface *action = iface->actionInterface())
837 action->doAction(QAccessibleActionInterface::setFocusAction());
838 } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
839 if (iface->textInterface()) {
840 QString text = QString::fromNSString((NSString *)value);
841 iface->setText(QAccessible::Value, text);
842 } else if (QAccessibleValueInterface *valueIface = iface->valueInterface()) {
843 double val = [value doubleValue];
844 valueIface->setCurrentValue(val);
845 }
846 } else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
847 if (QAccessibleTextInterface *text = iface->textInterface()) {
848 NSRange range = [value rangeValue];
849 if (range.length > 0)
850 text->setSelection(0, range.location, range.location + range.length);
851 else
852 text->setCursorPosition(range.location);
853 }
854 }
855}
856
857// actions
858
859- (NSArray *)accessibilityActionNames {
860 NSMutableArray *nsActions = [[NSMutableArray new] autorelease];
861 QAccessibleInterface *iface = self.qtInterface;
862 if (!iface)
863 return nsActions;
864
865 const QStringList &supportedActionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
866 for (const QString &qtAction : supportedActionNames) {
867 NSString *nsAction = QCocoaAccessible::getTranslatedAction(qtAction);
868 if (nsAction)
869 [nsActions addObject : nsAction];
870 }
871
872 return nsActions;
873}
874
875- (NSString *)accessibilityActionDescription:(NSString *)action {
876 QAccessibleInterface *iface = self.qtInterface;
877 if (!iface)
878 return nil; // FIXME is that the right return type??
879 QString qtAction = QCocoaAccessible::translateAction(action, iface);
880 QString description;
881 // Return a description from the action interface if this action is not known to the OS.
882 if (qtAction.isEmpty()) {
883 if (QAccessibleActionInterface *actionInterface = iface->actionInterface()) {
884 qtAction = QString::fromNSString((NSString *)action);
885 description = actionInterface->localizedActionDescription(qtAction);
886 }
887 } else {
888 description = qAccessibleLocalizedActionDescription(qtAction);
889 }
890 return description.toNSString();
891}
892
893- (void)accessibilityPerformAction:(NSString *)action {
894 if (QAccessibleInterface *iface = self.qtInterface) {
895 const QString qtAction = QCocoaAccessible::translateAction(action, iface);
897 }
898}
899
900// misc
901
902- (BOOL)accessibilityIsIgnored {
903 // Short-cut for placeholders and synthesized elements. Working around a bug
904 // that corrups lists returned by NSAccessibilityUnignoredChildren, otherwise
905 // we could ignore rows and columns that are outside the table.
906 if (self.isManagedByParent)
907 return false;
908
909 if (QAccessibleInterface *iface = self.qtInterface)
910 return QCocoaAccessible::shouldBeIgnored(iface);
911 return true;
912}
913
914- (id)accessibilityHitTest:(NSPoint)point {
915 QAccessibleInterface *iface = self.qtInterface;
916 if (!iface) {
917// qDebug("Hit test: INVALID");
918 return NSAccessibilityUnignoredAncestor(self);
919 }
920
921 QPointF screenPoint = QCocoaScreen::mapFromNative(point);
922 QAccessibleInterface *childInterface = iface->childAt(screenPoint.x(), screenPoint.y());
923 // No child found, meaning we hit this element.
924 if (!childInterface || !childInterface->isValid())
925 return NSAccessibilityUnignoredAncestor(self);
926
927 // find the deepest child at the point
928 QAccessibleInterface *childOfChildInterface = nullptr;
929 do {
930 childOfChildInterface = childInterface->childAt(screenPoint.x(), screenPoint.y());
931 if (childOfChildInterface && childOfChildInterface->isValid())
932 childInterface = childOfChildInterface;
933 } while (childOfChildInterface && childOfChildInterface->isValid());
934
935 // hit a child, forward to child accessible interface.
936 QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface];
937 if (accessibleElement)
938 return NSAccessibilityUnignoredAncestor(accessibleElement);
939 return NSAccessibilityUnignoredAncestor(self);
940}
941
942- (id)accessibilityFocusedUIElement {
943 QAccessibleInterface *iface = self.qtInterface;
944 if (!iface) {
945 qWarning("FocusedUIElement for INVALID");
946 return nil;
947 }
948
949 QAccessibleInterface *childInterface = iface->focusChild();
950 if (childInterface && childInterface->isValid()) {
951 QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface];
952 return NSAccessibilityUnignoredAncestor(accessibleElement);
953 }
954
955 return NSAccessibilityUnignoredAncestor(self);
956}
957
958- (NSString *) accessibilityHelp {
959 if (QAccessibleInterface *iface = self.qtInterface) {
960 const QString helpText = iface->text(QAccessible::Help);
961 if (!helpText.isEmpty())
962 return helpText.toNSString();
963 }
964 return nil;
965}
966
967/*
968 * Support for table
969 */
970- (NSInteger) accessibilityIndex {
971 NSInteger index = 0;
972 if (synthesizedRole == NSAccessibilityCellRole)
973 return m_columnIndex;
974 if (QAccessibleInterface *iface = self.qtInterface) {
975 if (self.isManagedByParent) {
976 // axid matches the parent table axid so that we can easily find the parent table
977 // children of row are cell/any items
978 if (QAccessibleTableInterface *table = iface->tableInterface()) {
979 if (m_rowIndex >= 0)
980 index = NSInteger(m_rowIndex);
981 else if (m_columnIndex >= 0)
982 index = NSInteger(m_columnIndex);
983 }
984 }
985 }
986 return index;
987}
988
989- (NSArray *) accessibilityRows {
990 if (!synthesizedRole && rows) {
991 QAccessibleInterface *iface = self.qtInterface;
992 if (iface && iface->tableInterface())
993 return NSAccessibilityUnignoredChildren(rows);
994 }
995 return nil;
996}
997
998- (NSArray *) accessibilityColumns {
999 if (!synthesizedRole && columns) {
1000 QAccessibleInterface *iface = self.qtInterface;
1001 if (iface && iface->tableInterface())
1002 return NSAccessibilityUnignoredChildren(columns);
1003 }
1004 return nil;
1005}
1006
1007@end
1008
1009#endif // QT_CONFIG(accessibility)
1010
static bool isEqual(const aiUVTransform &a, const aiUVTransform &b)
The QAccessible class provides enums and static functions related to accessibility.
static CGPoint mapToNative(const QPointF &pos, QCocoaScreen *screen=QCocoaScreen::primaryScreen())
static QPointF mapFromNative(CGPoint pos, QCocoaScreen *screen=QCocoaScreen::primaryScreen())
qsizetype size() const noexcept
Definition qlist.h:397
The QPlatformWindow class provides an abstraction for top-level windows.
\inmodule QtCore\reentrant
Definition qpoint.h:217
\inmodule QtCore\reentrant
Definition qpoint.h:25
constexpr int & ry() noexcept
Returns a reference to the y coordinate of this point.
Definition qpoint.h:160
\inmodule QtCore\reentrant
Definition qrect.h:484
\inmodule QtCore\reentrant
Definition qrect.h:30
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
constexpr QStringView left(qsizetype n) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
qsizetype count(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4833
\inmodule QtGui
Definition qwindow.h:63
QHash< int, QWidget * > hash
[35multi]
QString text
QCache< int, Employee > cache
[0]
rect
[4]
QStringList effectiveActionNames(QAccessibleInterface *iface)
bool performEffectiveAction(QAccessibleInterface *iface, const QString &actionName)
constexpr QBindableInterface iface
Definition qproperty.h:666
Definition qcompare.h:63
QString self
Definition language.cpp:58
QNSView * qnsview_cast(NSView *view)
Returns the view cast to a QNSview if possible.
long NSInteger
unsigned long NSUInteger
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
EGLOutputLayerEXT EGLint attribute
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLuint index
[2]
GLuint GLuint end
GLenum GLuint GLenum GLsizei length
GLenum GLuint id
[7]
GLenum GLenum GLsizei count
GLuint object
[3]
GLsizei range
GLuint GLsizei const GLchar * label
[43]
GLuint start
GLenum GLuint GLintptr offset
GLfloat n
GLuint GLfloat * val
GLenum array
GLenum GLenum GLsizei void * row
GLenum GLenum GLsizei void * table
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static QString helpText(const QCommandLineParser &p, const PluginInformation &pluginInfo)
Definition main.cpp:764
unsigned int quint32
Definition qtypes.h:50
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
QWidget * win
Definition settings.cpp:6
QSharedPointer< T > other(t)
[5]
QItemSelection * selection
[0]
aWidget window() -> setWindowTitle("New Window Title")
[2]